原文:
zh.annas-archive.org/md5/bf8811360e8e4fe6319d15ed2bf23ef1译者:飞龙
前言
在 2016 年,当我共同创立一家机器人初创公司时,我在寻找构建新六轴机器人手臂软件的工具和技术,偶然发现了 ROS。不知何故,我有一种直觉,认为 ROS 似乎正是我一直在寻找的东西。然而,我并不能真正理解它是什么,或者它在做什么。
我花了很长时间才理解 ROS,在学习的过程中,我意识到这个过程有多么痛苦。当时(现在也是如此)缺乏清晰的教学材料和在线资源,尤其是对于初学者。随着我多年来继续使用 ROS,我也意识到这不仅仅是我一个人遇到的问题——许多其他开发者仍然感到迷茫。这促使我创建在线课程来教授 ROS 和其他相关主题,目标是让 ROS 对每个人来说都更容易接触。
几年后,这本书是这个过程的延续。随着在 ROS(或 ROS 2——稍后会解释这个区别)的使用和教学方面积累了更多经验,我写了这本我希望在刚开始时就能拥有的书。在撰写这本书的过程中,我尽可能地站在初学者的角度,并避免学习过程中常见的两个障碍。
首先,在技术圈中,你有时会看到一些专家表现出的有毒行为,他们对你不屑一顾,会说些“你怎么连这么基本的东西都不会——这太简单了?”之类的话,或者用术语快速解释事情,然后在你不理解时让你感到愚蠢。这种行为没有帮助,也不会激励你学习。
第二,我不知道为什么,但很多人喜欢把事情搞得很复杂,这不仅仅与 ROS 或技术有关。大多数时候,一旦概念被清晰地理解,它们可以非常简单地解释。如果不需要,就没有必要让它们听起来很复杂,也没有必要花一个小时解释某件事,如果五分钟就能完成。这会产生噪音和困惑。
在这本书中,我想做的是相反的事情:不因技能不足而评判,优先提供清晰简洁的解释。通过这种方式,我希望你能高效地学习,完成这本书后比现在更少困惑,更有动力继续你的 ROS 2 和机器人之旅。
这本书面向的对象
这本书是为工程师、研究人员、学生、教师、开发者和希望以高效方式从头开始学习 ROS 2、不浪费时间的人准备的。
即使你不需要在某个领域成为专家,但这本书并不是为软件工程领域的完全新手准备的。你需要具备 Linux 和 Python 的良好基础——C++是可选的。对这些技术的良好掌握和一些经验将使你的学习更加容易。
不需要 ROS(或 ROS 1)的经验。
这本书涵盖的内容
第一章,ROS 2 简介——ROS 2 是什么?,解释了 ROS 2 究竟是什么,并消除了你可能会有的大部分疑问和困惑。
第二章,安装和设置 ROS 2,引导你完成 Ubuntu、ROS 2 和附加工具的安装和设置,这样你就有了一切使用 ROS 2 所需的东西。
第三章,揭示 ROS 2 核心概念,通过实验和动手发现介绍了主要的 ROS 2 概念,目标是培养对事物工作方式的直觉。
第四章,编写和构建 ROS 2 节点,展示了如何编写 ROS 2 程序,安装它们,并运行它们。Python 和 C++都被使用,并给出了额外的挑战来让你练习(以下章节也是如此)。
第五章,主题——节点之间通过主题发送和接收消息,解释了如何通过主题在两个节点之间进行通信。我们首先使用现实生活中的类比来解释这个概念,然后深入代码。
第六章,服务——节点之间的客户端/服务器交互,遵循了上一章的相同大纲——这次是针对 ROS 2 中第二重要的通信类型。
第七章,动作——当服务不足时,介绍了 ROS 2 的第三种也是最后一种通信类型。这一章稍微复杂一些,在第一次阅读时可以跳过。
第八章,参数——使节点更具动态性,展示了如何向你的节点添加参数,以便在运行时提供不同的设置。
第九章,启动文件——一次性启动所有节点,为你提供了一种从单个文件启动完整 ROS 2 应用程序的方法。
第十章,使用 RViz 发现 TFs,介绍了一个最重要的概念之一,这样你就可以跟踪机器人随时间变化的不同坐标。这将是几乎任何你创建的 ROS 2 应用程序的骨架。
第十一章,为机器人创建 URDF,让你开始一个新的项目,在这个项目中,你将使用 ROS 2 创建一个自定义机器人。使用 URDF,你可以创建机器人的描述。
第十二章,发布 TFs 和打包 URDF,解释了如何正确打包你的应用程序并生成所需的 TFs,这要归功于你创建的 URDF。
第十三章,在 Gazebo 中模拟机器人,教你如何将机器人适配到 Gazebo(3D 模拟工具),如何生成机器人,以及如何控制它,以便得到尽可能接近真实机器人的模拟。
第十四章,更进一步——下一步做什么,为你提供了在完成本书后,根据个人目标可以选择的不同路径的更多视角。
要充分利用本书
您需要以下基本知识:
-
Linux,特别是如何使用命令行(带有自动完成)和用文本编辑器编写代码,以及您应该了解一些关于文件系统和环境如何工作的知识(例如
.bashrc文件)。 -
Python 编程:大部分代码将使用 Python 3 编写,采用面向对象编程。您的 Python 技能越好,就越容易。
-
C++编程:您可以决定只通过遵循 Python 示例开始,因此不需要 C++。如果您还想遵循 C++示例,当然需要 C++。
关于软件和操作系统,您需要在您的计算机上安装 Ubuntu(最好是双启动,也可以在虚拟机上运行)。本书针对 Ubuntu 24.04 和 ROS 2 Jazzy,但您也应该能够从后续版本中获得最大收益。本书将提供有关如何安装 Ubuntu(在虚拟机上)和 ROS 2 的逐步说明。
如果您使用的是本书的数字版,我们建议您亲自输入代码或从本书的 GitHub 仓库(下一节中提供链接)获取代码。这样做将帮助您避免与代码的复制和粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub(github.com/PacktPublishing/ROS-2-from-Scratch)下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
使用的约定
本书使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“例如,如果您想在 ROS Jazzy 上安装abc_def软件包,那么您需要运行sudo apt install ros-jazzy-abc-def。”
代码块设置如下:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
entry_points={
'console_scripts': [
"test_node = my_py_pkg.my_first_node:main"
],
},
任何命令行输入或输出都应如下编写:
$ sudo apt update
$ sudo apt upgrade
粗体:表示新术语、重要单词或屏幕上出现的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“要启动虚拟机,请在VirtualBox 管理器中双击它,或者选择它并点击开始按钮。”
小贴士或重要注意事项
看起来是这样的。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及本书标题。
勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packtpub.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了从零开始学习 ROS 2,我们非常乐意听到您的想法!请点击此处直接进入亚马逊评论页面为这本书分享您的反馈。
您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载这本书的免费 PDF 副本
感谢您购买这本书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走?
您的电子书购买是否与您选择的设备不兼容?
别担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。直接从您喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠远不止于此,您还可以获得独家折扣、时事通讯和丰富的免费内容,每天直接发送到您的收件箱。
按照以下简单步骤获取福利:
- 扫描二维码或访问以下链接
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_QR_Free_PDF.jpg
packt.link/free-ebook/9781835881408
-
提交您的购买证明
-
就这些!我们将直接将您的免费 PDF 和其他福利发送到您的电子邮件。
第一部分:ROS 2 入门
在本第一部分,您将获得 ROS 2 的全局概述,了解它是什么,何时使用它,以及为什么。您将在您的计算机上安装 ROS 2,以及所有必要的工具,并通过动手实验来发现核心概念。
本部分包含以下章节:
-
第一章,ROS 2 简介 – ROS 2 是什么?
-
第二章,安装和设置 ROS 2
-
第三章,揭示 ROS 2 核心概念
第一章:ROS 2 简介——什么是 ROS 2?
机器人操作系统(ROS)可能会让人困惑,正如其名称所示。很难知道它确切是什么,它包含什么,以及它能做什么。此外,为什么你需要 ROS,以及何时应该使用它?
在开始之前,感到困惑是正常的——大多数人都会这样。尽管 ROS 是学习和开发机器人应用的最佳工具之一,但它也伴随着陡峭的学习曲线,第一个障碍就是理解它是什么。
在这个简短的第一章中,我将解释我们将在这本书中使用的术语。然后你会看到 ROS 为什么存在,以及它能为你解决什么问题。之后,我们将深入探讨 ROS 的四个支柱,以了解它是什么。你还会看到一些例子,说明何时以及何时不应使用它。
到本章结束时,你将更好地理解 ROS 背后的全局图景,并对最常见的困惑有清晰的认识。你还将了解在开始使用 ROS 之前你需要哪些先决条件,以及如何遵循这本书以充分利用它。这将帮助你迈出正确的第一步。
在本章中,我们将涵盖以下主题:
-
术语
-
什么是 ROS,何时应该使用它,以及为什么?
-
ROS 1 与 ROS 2 的比较
-
开始使用 ROS 2 的先决条件
-
如何遵循这本书
术语
你可能已经看到过 ROS、ROS 1、ROS 2 以及其他各种变体(带或不带空格),这些可能会让人困惑。
让我们澄清一下:
-
ROS 1 是(曾是)ROS 的第一个版本
-
ROS 2 是 ROS 的第二个和更新的版本,并将成为本书的重点
在这本书中,我将使用以下约定:
-
ROS:当谈论 ROS 的一般概念、哲学等时
-
ROS 1:当具体谈论 ROS 的第一个版本时。然而,这将会非常罕见,因为这里的重点是 ROS 2
-
ROS 2:当谈论 ROS 的第二版时
注意
我可能会在某些时候交替使用ROS或ROS 2,因为我们不会在这里关注 ROS 1。
在未来(当 ROS 1 完全消失时),ROS 2 的名字可能再次变为 ROS,这并非不可能。如果你听说过 Angular,它最初是 AngularJS,之后他们发布了 Angular2,然后几年后,它简单地变成了 Angular。我猜 ROS 也会发生类似的事情,尽管这目前只是我的一个理论。
什么是 ROS,何时我们应该使用它,以及为什么?
在我们开始理解 ROS 是什么之前,让我们先了解为什么我们需要它。
为什么需要 ROS?
让我们从机器人中经常出现的一个大问题开始。
想象一下,你刚刚在工作中接到了一个新的项目,你需要开发一个机器人应用,或者你正在进行一个新的研究论文。一个非常重要的事情是要考虑到,在现实生活中,任何项目或论文都将有一个特定的持续时间,从几个月到几年不等。
现在,接下来会发生什么?
你开始设计你项目所需的机器人系统,很快就会意识到开发机器人将花费大量时间,因为所有你找到的现有解决方案都不符合你的需求。几周后,你最终确定了规格,并开始构建你的机器人。几个月后,你仍在开发轮控和导航的基本软件。你低估了仅让机器人运行所需的时间。一年或两年后,你意识到到目前为止你所做的一切只是构建了一个机器人系统,而你还没有开始你的应用程序或研究的核心功能。现在是时候加快速度了。
你尽可能地完成机器人,做一些捷径,发布你的论文或展示那个原型。在最佳情况下,你也可以通过开源许可分享你的代码,这样其他人就可以使用它,但可能不会直接使用,因为这只是满足你自身需求的代码,而不是一个包含模块化组件、文档等完整框架或库。
然后,你转向新的项目、新的工作和新的研究。别人会取代你的位置,阅读你的代码,并意识到它并不能帮助他们构建应用程序。因此,他们不得不从头开始。
这里发生的事情是你重新发明了轮子。下一个人会重复同样的循环。这比你想象的要普遍得多。人们不断地重复发明轮子。这就是 ROS 被创造出来的首要原因:在你需要创建机器人时阻止你重新发明轮子。就像你有开源框架、工具和环境来开发网站或移动应用程序一样,为什么不为机器人做同样的事情呢?这就是 ROS 背后的哲学:为机器人应用程序提供一个标准,你可以在任何机器人上使用它。
在你学习 ROS 之后,你可以将更多时间投入到你想要添加的关键功能上,而不是基础的技能学习。你将能够迅速编程新的机器人,加入现有的项目,并且能够轻松地与团队协作。
ROS 是什么?
ROS 难以定义,因为它不仅仅是一件事。坦白说,我认为你只有开始理解如何用它编写代码,才能真正理解 ROS 是什么。
我们可以从 ROS 不是什么开始了解。
ROS 不是一个操作系统。它是四个主要部分的组合:
-
框架
-
工具集
-
即插即用的插件
-
在线社区
让我们更深入地探讨这些部分。
ROS 是一个具有管道的框架
ROS 提供了一套构建应用程序的规则。正如我们将在本书中看到的,你需要创建包,然后在那些包(节点)内部编写程序。创建和编写它们以及创建工具来构建和使用它们都有特定的方式。
任何框架都附带一套特定的规则。令人印象深刻的是,在你创建了一些项目之后,任何新的项目都将更容易、更快地设置。此外,由于每个人都遵循相同的规则,你可以更轻松地在团队中工作或理解和使用他人编写的代码。
使用此框架的直接结果是,你获得了通常被称为plumbing的访问权限,这意味着节点之间的底层通信由你管理。想象一下,你正在建造一栋房子,管道或电气系统已经为你准备好了。这将节省你大量的开发时间,而且你也不需要学习如何自己来做(因此,重新发明轮子)。
总结来说,使用 ROS,你可以轻松地将你的应用程序分成不同的子程序(称为nodes)。节点之间的通信由你处理。你可以轻松测试一个组件,如果这个组件失败,它将不会影响其他正在运行的组件。ROS 是一个模块化框架。
一套工具
ROS 附带一套工具,这些工具可以帮助你更快地开发。其中,你可以找到用于构建应用程序的命令行工具,用于监控通信流程的检查工具,日志功能,图表等等。
你还获得了 3D 可视化工具来查看你的机器人正在做什么,甚至还有一个名为Gazebo的完整模拟器,它使用真实物理,这样你就可以在尝试你的机器人之前进行现实模拟。
可用的工具相当多,我们将在本书中了解到其中许多。作为一个例子,有一个(称为bags)的功能允许你保存通信流,以便稍后回放。假设你构建了一个移动机器人,需要在下雨时进行测试,然后考虑雨水因素继续开发软件。你可能不会每天都下雨,或者你甚至无法随时接触到机器人。有了这个工具,你可以运行一次实验,保存数据,然后稍后回放以开发针对特定条件的应用程序。
功能性 – 插件和堆栈即插即用
这可能是你将节省数百小时的地方。想象两个常见的场景:
-
你开发了一个移动机器人,需要机器人能够在动态环境中自主导航。
-
你开发了一个六轴机械臂,并希望创建运动规划以在所有轴上执行平滑运动。
这看起来相当复杂,需要理解和实现几个算法,以及编写良好优化和高效的代码。这可能是你不得不重新发明轮子并浪费大量宝贵时间的地方。
对于这两种情况,你可以找到现有的插件来为你完成工作。你所需要做的就是安装插件,并配置你的机器人使其兼容。当然,这比说起来要难,但工作量可以按天/周计算,而不是按月/年计算。而且,一旦你学会了如何使用这些插件,你的下一个项目将花费的时间会少得多。
你可以使用许多插件。有些相当简单,而有些则涉及一系列插件,也被称为框架或堆栈。作为 ROS 开发者,你的工作是粘合所有这些组件,也许为尚未开发的特性创建新的组件。
在线社区
这是 ROS 的第四个支柱,它相当重要:社区。ROS 是一个开源项目,拥有宽松的许可证。我无法就许可证提供任何法律建议,但你可以在商业产品中使用 ROS 而无需重新分发你的代码。
你可以在网上找到所有 ROS 代码,以及即插即用的插件代码。所有这些都可以在 GitHub 上轻松访问。
ROS 项目还得到了一个在线社区的支持,你通常可以在以下区域找到它:
-
机器人技术问答社区(
robotics.stackexchange.com/):你可以使用这个平台来提问技术问题。如果你熟悉 Stack Overflow,就像大多数开发者一样,那么,这就是机器人领域的 Stack Overflow。 -
ROS 论坛(
discourse.ros.org/):在这里,你可以了解最新的发展、工作、社区项目、新想法等。我建议你经常检查这个网站,以保持对 ROS 走向的了解。
何时使用 ROS
现在你对 ROS 有了更多的了解,那么在你的项目中只要有“机器人”这个词,你就应该使用 ROS 吗?在本节中,我将给你一些提示,说明何时使用 ROS 是有意义的,并通过一些例子来帮助你更好地理解。
首先,如果你因为工作或大学需要学习 ROS 而阅读这本书,那么答案很简单:是的,你将在你的项目中使用 ROS。
但如果你必须自己做出决定,你应该怎么做?
让我们简化机器人技术,并说一个机器人系统包含三类事物:执行器、传感器和控制器。
执行器是产生运动的东西(例如,一个旋转轮子的电机)。传感器将从环境中读取数据(例如,摄像头、激光扫描或温度传感器)。控制器介于两者之间:它从一个或多个传感器(输入)获取可用的数据,并通过算法为机器人的执行器(输出)创建命令。从某种意义上说,控制器是机器人的大脑或大脑之一。
对于非常简单的应用,当你只有几个传感器和执行器时,你可能不需要 ROS。
这里有一些例子,说明 ROS 不是必需的:
-
当用户按下按钮时,你只需要使用 Raspberry Pi 板从相机中拍照,并将这张照片发送到网络服务器。没有必要使用 ROS——你只需在脚本中结合几个 Python 库,就完成了。在这里使用 ROS 将是一个过度设计的例子(除非你这样做是为了学习目的)。
-
当检测到运动时,你需要使用红外传感器打开/关闭门,这是一个非常简单的应用,可以很容易地使用基本的微控制器板编程——你可以用 Arduino 这样的板快速原型。
-
你已经用两个轮子和一个红外传感器构建了一个简单的机器人,并想使机器人跟随一条线。这是一个典型的工程项目,通常在工程学院的学生中给出,一个简单的 Arduino 板上的算法就可以完成。
现在,让我们考虑一些需要 ROS 的例子:
-
你有一个新的两轮激光扫描移动机器人,你想从激光扫描中读取数据,绘制环境地图,使机器人自主移动,并相应地控制两个轮子。除此之外,你还想用真实的物理属性在 3D 中模拟机器人。这时 ROS 就会变得非常有用。它不仅可以帮助你使所有组件协同工作,你还可以使用现有的路径规划算法(通过 ROS 插件)和模拟。
-
你需要创建一个包含六轴机械臂,甚至多个机械臂协同工作,以及传送带和移动机器人的系统。
-
你的机器人应用(不一定是单个机器人)包含大量你想要单独开发并模块化添加的传感器和执行器。
-
你想为某个组件创建一个硬件驱动程序,并使其他机器人开发者能够轻松使用这个组件。通过使组件与 ROS 兼容,任何了解 ROS 的人都可以轻松将其集成到他们的应用中。
如前所述示例所示,ROS 并非在每次需要编程硬件或创建机器人系统时都是必需的。当然,你可以为任何应用使用它,但这就好比你要为单个静态网页使用完整的网络框架(例如 Django)一样。
从后面的示例中,你可以看到,如果你的系统变得更加复杂,如果你想与其他开发者轻松协作,或者如果你意识到系统的一个大块部分可以用一个即插即用的插件来解决,ROS 可能就是解决方案。
当然,学习它需要时间,你的第一个项目完成时间会更长,但随着经验的积累,你会变得更快。
例如,一个经验丰富的 ROS 开发者可能不到一周就能为机械臂编写自定义代码(包括机器人模型、运动规划和硬件控制),同样,对于具有导航能力的移动机器人也是如此(前提是硬件已经有一个 ROS 驱动程序)。不到一周,你就能得到一个可工作的软件原型。
ROS 1 与 ROS 2 的比较
为了明确,这本书完全是关于 ROS 2 的,而不是 ROS 1。你将从零基础开始学习 ROS 2。这个部分可能是唯一一次我会这么详细地谈论 ROS 1。
ROS 的简要故事,以及我们如何到达 ROS 2
ROS 1(最初被称为 ROS)首次开发于 2007 年。它在接下来的几年里迅速获得了人气,并呈指数级增长。
2014 年,宣布了 ROS 2 项目。简单来说,ROS 1 对于工业应用来说有点过于局限(缺乏实时支持、安全性等),并且仅用于研究/教育。为了解决这个问题,开发者决定使 ROS 更加“适合工业”,同时也使它变得更好,这是从 ROS 开始以来学到的所有经验教训。
现在,为什么创建 ROS 2 而不是仅仅对 ROS 进行一些新更改?因为更改太大,它们将完全破坏与旧版本的兼容性。因此,决定从头开始创建一个全新的 ROS,并将其命名为 ROS 2。2014 年,ROS 2 正式宣布,项目开发开始。
2017 年 12 月,发布了第一个 ROS 2 发行版,这意味着 ROS 1 和 ROS 2 开始共存。在这个时候,ROS 2 缺少许多核心功能性和插件,使其不适合严肃的项目。大多数 ROS 开发者仍在使用 ROS 1。
年复一年,ROS 2 得到了更多的发展,插件也越来越多。它的受欢迎程度开始增长。
我认为从 2022 年开始使用 ROS 2(与 ROS 1 相比)是值得的。这可能是个人观点,有些人可能不同意,但从 2022 年和ROS 2 Humble(更多关于发行版的内容见第二章)的发布开始,我们有了长期稳定发布的版本,所有主要插件和堆栈都能正常工作,这正是编写机器人程序所需要的。
同时,宣布 ROS 1 将在 2025 年 5 月结束。在此日期之后,ROS 1 仍然存在,但将不再提供支持。
2023 年是 ROS 社区从 ROS 1 转向 ROS 2 最显著的年份。现在可以肯定地说,当开发新的 ROS 应用程序时,ROS 2 是必由之路。
所以,如果你之前听说过 ROS 1 和 ROS 2,现在你知道 ROS 2 是你需要学习的,我们可以这样说,ROS 1 是一个过时的项目。但这是真的吗?
ROS 1 已经过时了吗?
理论上是这样,但在实践中,情况总有些不同。你可能知道,有几家公司仍在使用过时和遗留的技术。原因是将软件更新到新版本通常相当昂贵,也可能存在风险。这就是为什么你仍然会看到银行系统的工作机会要求具备 Cobol 语言技能,这是一种 1960 年代的编程语言,现在已经没有人使用了。
在机器人领域,情况有些相似。一些公司已经发布了使用特定版本的 ROS 1 的机器人,并且当机器人仍在市场上时,公司不会升级,仍然使用和维护之前的版本,这也被称为遗留版本。因此,2025 年的最终过渡可能还需要几年时间。
我为什么要写这篇文章?简单来说,就是想让你知道,如果你在一家已经使用 ROS 的机器人公司找到工作,即使 ROS 1 已经正式结束,你可能会遇到一些 ROS 1 项目。然而,请放心,你拥有的所有 ROS 2 知识都可以轻松迁移到 ROS 1,因为核心概念是相同的。
总结来说,对于所有新的学习、项目、研究、教学和创业,ROS 2 是你需要的。我现在将关闭 ROS 1 的这一章节,并专注于 ROS 2。如前所述,我可能会交替使用ROS或ROS 2,因为我们这里的目标不是 ROS 1。
开始使用 ROS 2 的先决条件
要开始使用 ROS 和这本书,你需要了解一些事情。
知识先决条件
最好你对以下内容有所了解:
-
Linux 命令行:由于我们将使用Ubuntu,熟悉 Linux 是强制性的。你不需要成为专家——你只需要了解基础知识。ROS 2 中的许多工具都涉及命令行,所以知道如何打开终端并编写基本命令将极大地帮助你。
-
Python 编程:ROS 最常用的两种语言是Python和 C++。Python 更容易上手,并且允许你更快地进行原型设计。因此,我们将使用这种语言进行所有详细解释。你需要了解 Python 基础知识,面向对象编程(OOP)是一个很好的加分项,因为 ROS 2 在各个地方都大量使用 OOP。
-
可选:**C++**编程。尽管这本书的重点是 Python,但我仍然想包括我们做的所有事情中的 C++代码。如果你只想学习 Python,你可以忽略 C++代码,但当然,如果你想遵循 C++指令,你需要 C++基础知识(最好是面向对象编程)。
我想强调,如果你有良好的编程和 Linux 基础知识,学习 ROS 2 将会容易得多。学习 ROS 本身就已经相当具有挑战性(尽管有了这本书,目标是降低学习曲线),所以如果你是从零开始学习 ROS、Linux 和 Python,这可能会让你感到不知所措。
如果你正在阅读这些内容而你不知道如何编写 Python 函数或在终端中导航到目录,那么我真心建议你在这里暂停,花些时间学习 Python 和 Linux 基础知识,然后再回到这本书。没有必要花数百小时来做这件事,但投入一些时间确保基础知识正确将帮助你更快地完成这本 ROS 2 书。
硬件和软件
你需要一台计算机来阅读本书。关于规格,你不需要任何花哨的东西来开始使用 ROS 2。如果你可以打开带有几个标签的网页浏览器并且有顺畅的体验,那么我可以说你这台计算机足够开始使用了。
然后,稍后,根据你想要用 ROS 做什么,你可能需要一个更好的机器(例如,如果你想要使用大量传感器和图像处理来模拟多个机器人)。然而,可能最好等到你需要额外的性能时再升级。现在,最重要的事情是开始学习 ROS。
对于软件要求,我将在本书的整个过程中提供必要的安装说明。我们将使用的所有软件都是免费使用和开源的。
我们还将使用 Ubuntu 24.04,在其中我们将运行 ROS 2。安装 Ubuntu 是一个要求,但我会给你一个在第二章的回顾。
如何阅读本书
本书分为三个部分,包括 14 章。
每一章都可以单独阅读,尽管对于某一章,你需要所有之前章节的知识。
如果你是因为想要从头开始而得到这本书,那么很简单:按照书写的顺序阅读本书。我特意设计它,以便你一次学习一个概念,而不必考虑你应该采取什么方向。
然后,随着你的进步,随时可以回到任何章节来消除疑惑。我鼓励你这样做。第一次学习一个概念时,你并不一定能够掌握所有的细微之处。随着你继续阅读本书并使用这个概念以及其他新概念,你经常会遇到“顿悟时刻”,那时一切都会变得清晰。
如果你已经了解一些 ROS 2 基础知识(或者你已经阅读过这本书),那么你可以自由地跳到任何章节。如果一个章节是从我们在前几章中开发的代码库开始的,那么你将能够下载代码并从那里开始。
有一个 GitHub 仓库你可以用来阅读本书:github.com/PacktPublishing/ROS-2-from-Scratch。我们将编写的所有代码都托管在那里,所以请确保在阅读本书时密切使用这个 GitHub 仓库。我会在本书稍后解释如何使用这个仓库。
摘要
在本章的介绍部分,我们澄清了一些关于 ROS(机器人操作系统)最常见的困惑点:它的名称、它是什么以及不是什么、何时使用它以及为什么。你还了解了不同版本的 ROS(ROS 1 和 ROS 2),以及你学习了开始使用 ROS 2 所需具备的先决条件。
现在,你应该对整体情况有了更好的理解,即使一切看起来仍然有些混乱,也不要过于担心——当你使用 ROS 2 的概念和代码时,一切都会变得清晰。
现在,为了能够使用 ROS 2,我们需要安装它。这将是下一章的重点,并帮助你将你的环境 100%准备好用于 ROS 2。
第二章:安装和设置 ROS 2
在使用 ROS 2 之前,我们需要安装它并设置它。做这件事并不像只是下载和安装一个基本程序那样简单。有几个 ROS 2 版本(称为发行版),我们需要选择哪一个是最合适的。我们还需要选择一个 Ubuntu 版本,因为 ROS 和 Ubuntu 发行版是紧密相连的。
一旦你知道你需要哪个 ROS/Ubuntu 组合,你必须安装相应的 Ubuntu 操作系统(OS)。尽管熟悉 Linux 是这本书的先决条件,但我仍会回顾如何在虚拟机(VM)上安装 Ubuntu,以防万一,这样你就不会迷失方向,可以继续阅读这本书。
然后,我们将在 Ubuntu 上安装 ROS 2,在我们的环境中设置它,并安装额外的工具,这将使你拥有更好的开发体验。
到本章结束时,你将准备好电脑上的所有东西,以便你可以使用 ROS 2 并编写自定义程序。
即使所有安装步骤听起来有点令人畏惧,不要担心——这并不那么困难,而且每次新的安装都会变得更容易。为了给你一个概念,在有稳定的互联网连接的情况下,安装一个全新的 Ubuntu 版本大约需要 1 个小时,安装 ROS 则需要 20 分钟(其中大部分时间都花在等待安装完成上)。
在本章中,我们将涵盖以下主题:
-
选择哪个 ROS 2 发行版
-
安装操作系统(Ubuntu)
-
安装 ROS 2
-
设置 ROS 2 的开发环境
-
ROS 2 开发额外的工具
选择哪个 ROS 2 发行版
在安装 ROS 2 之前,了解你需要使用哪个发行版是很重要的。为了做出这个决定,你首先需要更多地了解 ROS 2 发行版是什么,以及每个发行版的具体特点。
什么是 ROS 2 发行版?
ROS 2 是一个持续发展的项目,不断接收新的功能或对现有功能的改进。
发行版简单地说是在某个给定点对开发进行冻结以创建一个稳定的发布版本。有了这个,你可以确信一个给定发行版的核心包不会有任何破坏性的变化。没有发行版,就不可能有一个稳定的系统,你将需要不断更新你的代码。
每年,一个新的 ROS 2 发行版都会在 5 月 23 日发布。这一天对应于世界乌龟日。正如你将能观察到的,所有 ROS 发行版都有一个乌龟作为标志;有一个名为TurtleBot的移动机器人平台,甚至还有一个名为Turtlesim的 2D 教育工具。这是基于 1967 年一个名为 Logo 的教育编程语言的参考,它包括在屏幕上移动某种乌龟机器人的功能。所以,如果你对为什么到处都是乌龟感到困惑,现在你知道了——这就是这个乌龟括号的结尾。
你可以在 ROS 2 文档发布页面上看到所有 ROS 2 发行版:docs.ros.org/en/rolling/Releases.html。
你将在每年的 5 月看到一个新的发行版。至于顺序,没有数字;相反,名称是按字母顺序排列的。第一个官方发布版被命名为 Ardent Apalone,然后是 Bouncy Bolson,以此类推。2024 年 5 月,发布了 ROS Jazzy Jalisco。在此之后,你可以期待在 2025 年有 ROS K,在 2026 年有 ROS L,依此类推。新发布版的名称通常提前一年宣布。
注意
ROS 发行版包含两个名称,但通常只提到第一个。因此,我们不会谈论 ROS Jazzy Jalisco,而是会谈论 ROS Jazzy。我们也可以写成 ROS 2 Jazzy 来指明这个发行版是为 ROS 2 而不是 ROS 1,但这是不必要的,因为 Jazzy 只是一个仅用于 ROS 2 的名称,因此 ROS Jazzy。
在所有显示的发行版之上,还有一个与之并行存在的发行版:ROS Rolling。这个发行版有些特别,是所有新开发都进行的发行版。用 Git 和版本控制系统来类比,它就像有一个 开发 分支,并使用这个分支每年发布一次稳定版本。因此,ROS Rolling 不是一个稳定发行版,我不建议用它来学习或发布产品。这是一个你可以在新功能正式发布到下一个稳定发行版之前测试这些新功能的发行版——或者如果你想为 ROS 代码做出贡献。然而,如果你正在阅读这本书,你还没有达到那个阶段。
现在你已经知道了 ROS 2 的发行版有哪些,以及如何找到它们,让我们开始看看它们之间的区别。这将使我们能够选择正确的版本。
LTS 和非 LTS 发行版
如果你仔细观察,你会发现有些发行版支持 5 年,而有些发行版支持 1.5 年(这仅适用于 2022 年之后)。你可以通过比较发布日期和 生命终结(EOL)日期来看到这一点。目前支持的发行版在屏幕上也有绿色背景,所以你可以很容易地找到它们。
当一个发行版达到其 EOL 日期时,这仅仅意味着它将不再接收官方支持和软件包更新。这并不意味着你不能使用它(实际上,许多公司仍在使用 5 年或更早的遗留版本),但你不会得到任何更新。
第一个官方 ROS 2 发行版是 ROS Ardent,于 2017 年 12 月发布。在那之后,前几个发行版还不完全,开发团队更愿意发布较短的发行版,以便更快地推进开发。
ROS Humble 是第一个支持 5 年的 长期支持(LTS)发行版(2022-2027)。
ROS Jazzy也是一个 LTS 版本,从 2024 年到 2029 年有官方支持。从这一点可以预期,每两年(偶数年:2024 年、2026 年、2028 年等等),5 月份将发布一个新的 LTS 发行版,并支持 5 年。
一些 LTS 发行版可以共存。例如,在 2026 年,随着ROS L发行版的发布,你将能够使用 ROS Humble 和 ROS Jazzy。
然后,你有非 LTS 发行版。这些发行版在奇数年发布(2023 年、2025 年、2027 年等等),并且只支持 1.5 年。这些发行版发布是为了让你能够访问某种程度的稳定版本中的新开发,而无需等待两年。然而,由于非 LTS 发行版的寿命较短,并且它们可能不太稳定(以及支持度较低),如果你的目标是学习、教学或使用 ROS 进行商业应用,最好不要使用它们。
通过这一点,你可以看到我们可以排除一半的发行版,现在只需专注于当前受支持的 LTS 发行版。让我们完成这一部分,并选择我们将为这本书使用的发行版。
如何选择 ROS 发行版
我推荐使用最新的可用 LTS 发行版。然而,我并不一定会在它发布后立即使用 LTS 发行版,因为它可能仍然包含一些错误和问题。此外,你可能需要的某些插件和其他社区包可能还没有被移植。一般来说,如果你想与一个稳定的系统一起工作,有时最好不要过于接近新技术,而应该等待一段时间。
例如,ROS Humble 于 2022 年 5 月发布。就在它可用之后,我对其进行了测试,但为了在生产环境中使用它,我必须等到 9 月甚至 11 月,以确保一切正常工作。
因此,对于这本书,我们将使用 ROS Jazzy,该版本于 2024 年 5 月发布。
注意
你可以用一个发行版学习,然后用另一个发行版开始项目。如果你有一个需要使用不同 ROS 2 发行版的项目或工作,你仍然可以用 ROS Jazzy 开始学习。发行版之间的差距非常小,尤其是对于核心功能。这本书的 99%可以应用于 2022 年之后发布的任何 ROS 2 LTS 发行版。
安装操作系统(Ubuntu)
ROS 2 在三个操作系统上运行:Ubuntu、Windows 和 macOS。尽管 Ubuntu 和 Windows 获得一级支持,但 macOS 只有三级支持,意味着“尽力而为”,而不是“完全测试”。你可以在描述 ROS 2 发布时间表和目标平台的 REP 2000 上了解更多关于一级、二级和三级支持的含义:www.ros.org/reps/rep-2000.html。
这意味着使用 macOS 进行 ROS 2 不一定是最适合学习的选择(如果你是苹果用户)。我们只剩下 Windows 或 Ubuntu。
从教学经验来看,我发现即使 ROS 在 Windows 上可以很好地工作,但正确安装和使用它并不容易。可能会出现很多错误,尤其是在 2D 和 3D 工具方面。当您学习 ROS 时,您希望有一个顺畅的体验,并且希望花时间学习功能,而不是修复配置。
因此,最佳的整体选择是使用 Ubuntu。如果您没有 Ubuntu 并且正在使用 Windows/macOS,您可以在计算机上安装 Ubuntu 作为双启动,或者使用虚拟机(还有其他一些选项,但这里不会涉及那些选项)。
既然我们已经选择了 ROS Jazzy,并且我们想在 Ubuntu 上运行它,那么问题就是:我们应该在哪个 Ubuntu 发行版上安装它?
ROS 2 与 Ubuntu 之间的关系
如果您访问 Jazzy 发布页面(docs.ros.org/en/rolling/Releases/Release-Jazzy-Jalisco.html),您会看到 ROS Jazzy 支持 Ubuntu 24.04(而不是任何其他之前的或未来的 Ubuntu 发行版)。
ROS 与 Ubuntu 发行版之间存在密切关系。这种关系相当简单:对于每个新的 Ubuntu LTS 发行版(每两年在偶数年份),都有一个新的 ROS 2 LTS 发行版:
-
Ubuntu 22.04: ROS Humble
-
Ubuntu 24.04: ROS Jazzy
-
Ubuntu 26.04: ROS L
使用正确的组合非常重要。因此,在安装 ROS Jazzy 之前,您必须做的第一件事是确保您已经在计算机上安装了 Ubuntu 24.04。如果您恰好有旧版本,我强烈建议您升级或简单地从头开始安装 Ubuntu 24.04。
注意事项
如果您必须使用另一个 Ubuntu 发行版,例如,您正在使用学校/工作场所的计算机,并且无法更改操作系统,那么请使用相应的 ROS 发行版。然而,我建议不要使用比 ROS Humble 更旧的版本,并避免使用非 LTS 发行版。您还可以在虚拟机(稍后将有描述)上安装 Ubuntu 24.04。
您可能已经在生活中某个时刻安装过 Linux 操作系统,但根据经验,我知道一些阅读此内容的人可能会在安装过程中感到困惑。因此,我将提供额外的安装说明——双启动的概述以及虚拟机的详细说明。如果您已经安装了 Ubuntu,可以自由跳过这部分内容,直接进入 ROS 2 安装部分。
使用双启动原生安装 Ubuntu 24.04
最佳选择是在您的计算机上原生安装 Ubuntu。这将使您能够跟随本书学习,并在没有任何问题的前提下继续前进。在此,我不会提供如何做到这一点的完整教程;您可以在互联网上轻松找到大量免费教程。
这里是您必须遵循的几个高级重要步骤:
-
在您的磁盘上腾出一些空间,以便您能够创建一个新的分区。我建议至少 70 GB,如果可能的话,更多。
-
从官方 Ubuntu 网站下载 Ubuntu
.iso文件(Ubuntu 24.04 LTS)。 -
使用 Balena Etcher 等工具将此镜像烧录到 SD 卡或 USB 闪存驱动器上。
-
重新启动您的计算机,并选择从外部设备启动。
-
按照安装说明进行操作。重要:当被问及您想要如何安装时,选择与 Windows 一起安装,例如——不要删除您的所有磁盘。
-
完成安装。现在,当您启动计算机时,您应该会看到一个菜单,您可以选择是否要启动 Ubuntu 或 Windows。
这些是您需要遵循的主要步骤;您可以在互联网上找到您需要的所有信息。
在虚拟机上安装 Ubuntu 24.04
如果您不能作为双启动安装 Ubuntu(由于技术限制、计算机上的管理员权限不足或其他原因),或者如果您想快速开始而不需要太多努力,只是为了学习 ROS,那么您可能希望使用虚拟机。
虚拟机(VM)的设置非常简单,对于教学和学习非常有用。例如,当我在一个初学者的研讨会中教授 ROS 离线时,我经常提供一个已经安装好所有内容的虚拟机。这样,参与者就可以更快地开始使用 ROS。后来,当他们有更多知识时,他们可以自己花时间设置一个合适的操作系统。
注意
本书第3部分(关于 3D 模拟和 Gazebo)可能不适合在虚拟机上运行。您仍然可以使用虚拟机完成本书的大部分内容,并在最后设置双启动。
现在,我将向您展示如何在虚拟机上安装 Ubuntu 24.04,这样即使您没有创建和运行虚拟机的先验知识,也可以完成它。
第 1 步 – 下载 Ubuntu .iso 文件
下载 Ubuntu 24.04 .iso文件 releases.ubuntu.com/noble/。请注意,就像 ROS 一样,Ubuntu 发行版也有一个名称。对于 Ubuntu 24.04,名称是Ubuntu Noble Numbat。我们通常只使用第一个名称,所以在这种情况下Ubuntu Noble。
点击64 位 PC (AMD64) 桌面镜像。文件大小应为 5 到 6GB,因此在下载之前请确保您有一个良好的互联网连接。
第 2 步 – 安装 VirtualBox
您可以在下载 Ubuntu .iso文件的同时开始第 2 步。
两个流行的虚拟机管理器有一个免费版本:VMware Workstation 和 VirtualBox。两者都可以使用,但在这里,我将专注于 VirtualBox,因为它使用起来稍微简单一些。
访问官方 VirtualBox 网站的下载页面:www.virtualbox.org/wiki/Downloads。在VirtualBox 平台包下,选择您正在运行的当前操作系统。例如,如果您想在 Windows 上安装 VirtualBox,请选择Windows 主机。
下载安装程序,然后像安装其他软件一样安装 VirtualBox。
第 3 步 – 创建新的虚拟机
安装好 VirtualBox 并下载了 Ubuntu .iso文件后,打开 VirtualBox 软件(VirtualBox 管理器),然后点击新建。这将打开一个弹出窗口,您可以在其中开始配置新的虚拟机:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_02_1.jpg
图 2.1 – 开始虚拟机设置过程
这里,我们有以下值:
-
Ubuntu 24.04 -book。 -
在你的用户目录中的
VirtualBox VMs文件夹,那里将安装所有虚拟机。你可以保留这个文件夹或者如果你想的话可以更改它。 -
你刚刚下载的
.iso文件。 -
Linux。 -
Ubuntu (64-bit)。 -
跳过无人值守安装:确保勾选此框。如果不勾选,可能会在以后造成问题。
点击下一步。在这里,你需要选择为机器分配多少 CPU 和 RAM:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_02_2.jpg
图 2.2 – 为虚拟机分配硬件资源
这将取决于你的电脑配置。
这里是我对 RAM 分配(VirtualBox 上的基本内存)的建议:
-
如果你电脑上有 16 GB 或更多,分配 6 GB(就像我在图 2.2中做的那样)或者更多一些;这应该足够了。
-
如果你拥有 8 GB 的 RAM,分配 4 GB。
-
对于少于 8 GB 的情况,调整 RAM 值(你现在可以设置一个值,稍后在设置中修改它),以便你可以启动虚拟机,打开VS Code和带有几个标签的 Firefox,并且你的机器不会慢太多。如果事情变得太慢,考虑使用更强大的机器来学习 ROS。
对于 CPU 分配,分配你 CPU 的一半。所以,如果你的电脑有 8 个 CPU,将值设置为 4。在我的设置中,我有 4 个 CPU 和 8 个逻辑处理器,所以我选择了 4 个 CPU。尽量不要低于 2,因为只有一个 CPU 将会非常慢。如果有疑问,现在尝试一个设置;你可以稍后更改它。
总体来说,保持在绿色区域更好。如果你必须推到橙色区域,那么确保当你运行虚拟机时,你的电脑上不要运行其他任何东西(或者可能只是打开一个标签的网页浏览器或用于这本书的 PDF 阅读器)。
点击下一步。现在要做的最后一件事是为虚拟机配置将要创建的虚拟硬盘:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_02_3.jpg
图 2.3 – 为虚拟机创建虚拟磁盘
这里是你必须在此屏幕上选择的设置:
-
你可以保留默认选项(现在创建虚拟硬盘)。默认大小是 25 GB。为了学习 ROS 2,我建议至少使用 30 到 40 GB。无论如何,虚拟机的大小将开始较低,随着你安装更多东西而扩展,所以你可以设置一个更高的最大值而不阻塞资源。
-
保持预分配完整大小框未勾选。
点击下一步。现在,你将看到之前步骤中选择的全部选项的摘要。然后,点击完成。你将在 VirtualBox 管理器左侧看到新创建的虚拟机。
在你开始虚拟机之前,我们还需要配置一些其他事情。打开虚拟机的设置(无论是选择它并点击设置按钮,或者右键点击虚拟机并选择设置)。
修改以下三个设置:
-
系统 | 加速:取消选择启用嵌套分页复选框
-
显示:取消选择启用 3D 加速复选框(这个可能已经取消选中)
-
显示:如果可能,将视频内存增加到 128 MB
使用这些设置,你可能会避免虚拟机中图形界面的意外行为和问题。
注意
每台计算机都不同,具有不同的硬件配置。对我以及很多人有效的东西可能对你不起作用。如果你在运行虚拟机时遇到奇怪的行为,也许可以尝试通过修改前面三个设置来再次尝试。一次只测试一个更改。
步骤 4 – 启动虚拟机和完成安装
现在虚拟机已正确配置,我们需要启动它来安装 Ubuntu,使用我们已下载并添加到设置的 Ubuntu .iso 文件。
要启动虚拟机,在VirtualBox 管理器中双击它,或者选择它并点击启动按钮。
你将得到一个引导菜单。第一个选择是尝试或安装 Ubuntu,它应该已经选中。按Enter。
等待几秒钟;Ubuntu 将启动安装屏幕。通过不同的窗口进行配置:
-
选择你的语言。我建议你保持英语,这样你就有和我相同的配置。
-
跳过辅助功能菜单,除非你需要设置更大的字体大小,例如。
-
选择你的键盘布局。
-
连接到互联网。为此,请选择使用有线连接。
-
在这一点上,你可能会看到一个屏幕要求你更新安装程序。在这种情况下,点击现在更新。完成后,点击关闭安装程序。在虚拟机桌面上查找安装 Ubuntu 24.04 LTS并双击它。这将从步骤 1重新开始安装;重复步骤 1到步骤 4。
-
当被问及你希望如何安装 Ubuntu 时,请选择交互式安装。
-
对于要安装的 Ubuntu 应用程序,请选择默认选择。这将减少虚拟机使用的空间,你仍然会得到一个网络浏览器,以及所有核心基础——安装 ROS 2 我们不需要更多。
-
当被问及是否要安装推荐的第三方软件时,请选择安装图形和 Wi-Fi 硬件第三方软件。
-
对于磁盘设置,选择擦除磁盘并安装 Ubuntu。这里没有风险,因为我们正在擦除我们刚刚创建的空虚拟磁盘(如果你将 Ubuntu 作为双启动安装,你必须选择另一个选项)。
-
选择用户名、计算机名和密码。这里保持简单。例如,我使用
ed作为用户名,ed-vm作为计算机的名称。同时,确保密码输入正确,尤其是如果你之前更改了键盘布局。 -
选择你的时区。
-
在摘要菜单中,点击安装并等待几分钟。
-
安装完成后,一个弹出窗口将要求你重启。点击现在重启。
-
你将看到一个消息提示请移除安装介质,然后按回车键。这里不需要做任何事情——只需按Enter键即可。
-
启动后,你将看到一个欢迎屏幕弹出。你可以跳过所有步骤,点击完成来退出弹出窗口。
这样,Ubuntu 就已经安装完成了。
为了彻底完成,打开一个终端窗口并升级现有包(并不是因为你刚刚安装了 Ubuntu,所以所有包都是最新的):
$ sudo apt update
$ sudo apt upgrade
安装到此结束。然而,还有一件特定于 VirtualBox 的事情我们必须做,以确保虚拟机窗口能够正确工作。
第 5 步 – 客户端添加 CD 镜像
到目前为止,如果你尝试调整运行虚拟机的窗口大小,你会看到桌面分辨率没有改变。此外,如果你尝试在主机和虚拟机之间复制/粘贴一些文本或代码,可能不会工作。
为了解决这个问题,我们需要安装所谓的客户端添加 CD 镜像。
首先,打开一个终端并运行以下命令(注意,复制/粘贴目前还不工作,所以你必须手动输入)。这将安装一些依赖项,所有这些依赖项都是下一步所需的:
$ sudo apt install build-essential gcc make perl dkms
然后,在虚拟机窗口的顶部菜单中,点击设备 | 插入客户端添加 CD 镜像。
你将在 Ubuntu 菜单(在左侧)中看到一个新 CD 镜像。点击它——这将打开一个文件管理器。在文件管理器中,右键点击空白区域并选择在终端中打开。以下图示将提供更多说明:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_02_4.jpg
图 2.4 – 在终端中打开客户端添加 CD 镜像文件夹
然后,你将进入一个终端,在那里你可以找到一个名为VBoxLinuxAdditions.run的文件。你需要以管理员权限运行此文件:
$ sudo ./VBoxLinuxAdditions.run
之后,你可能会看到一个弹出窗口要求你重启虚拟机。点击立即重启。如果你没有看到这个弹出窗口,只需在终端中运行sudo reboot即可。
当虚拟机再次启动时,当你改变窗口大小时,屏幕应该会自动调整大小。如果第一次没有成功,我发现再次运行命令(sudo ./VBoxLinuxAdditions.run)并重启可能会解决问题。
然后,你可以右键点击磁盘并选择弹出,这样就设置完成了。要启用主机和虚拟机之间的复制/粘贴功能,请转到顶部菜单并点击设备 | 共享剪贴板 | 双向。
现在,你的虚拟机已经完全安装和配置。如果你选择了这条路径,这将允许你至少完成这本书的第一部分和第二部分。对于第三部分,如前所述,你可能会遇到一些问题,尤其是在使用 Gazebo 时。当你到达这个阶段时,我建议你使用双启动方式本地安装 Ubuntu。
在 Ubuntu 上安装 ROS 2
现在你已经在你的计算机上安装了 Ubuntu 24.04(无论是作为双启动还是在一个虚拟机中),让我们安装 ROS 2。如前所述,我们将在这里安装 ROS Jazzy。如果你使用的是不同的 Ubuntu 发行版,请确保使用适当的 ROS 2 发行版。
安装 ROS 2 的最佳方式是遵循官方文档网站上的说明,对于二进制包安装:docs.ros.org/en/jazzy/Installation/Ubuntu-Install-Debians.html。需要运行很多命令,但别担心——你只需要逐个复制粘贴即可。
注意
由于安装说明经常更新,本书中看到的以下命令可能略与官方文档中的不同。如果这样,请从官方说明中复制/粘贴。
现在,让我们开始安装 ROS 2。
设置区域设置
确保你有支持 UTF-8 的区域设置:
$ locale
通过这种方式,你可以检查你是否有了 UTF-8。如果有疑问,只需逐个运行这些命令(我每次都这样做):
$ sudo apt update && sudo apt install locales
$ sudo locale-gen en_US en_US.UTF-8
$ sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
$ export LANG=en_US.UTF-8
现在,再次检查;这次你会看到 UTF-8:
$ locale
设置源
逐个运行这五个命令(注意最好直接从官方文档中复制/粘贴):
$ sudo apt install software-properties-common
$ sudo add-apt-repository universe
$ sudo apt update && sudo apt install curl -y
$ sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro\/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr\/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2\/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo\tee /etc/apt/sources.list.d/ros2.list > /dev/null
这些命令的主要目的是将 ROS 包服务器添加到你的 apt 源中。运行以下命令:
$ sudo apt update
你现在应该看到额外的行,类似于以下内容:
Get:5 http://packages.ros.org/ros2/ubuntu noble InRelease [4,667 B]
Get:6 http://packages.ros.org/ros2/ubuntu noble/main amd64 Packages [922 kB]
这些新的源(packages.ros.org)将允许你在使用 apt 安装时获取 ROS 2 包。在这里,你还可以看到 noble,这意味着 ROS 2 包是为 Ubuntu Noble(24.04)准备的。由于你知道 Ubuntu 和 ROS 发行版是如何关联的,这也意味着 ROS Jazzy。
现在我们有了正确的源,我们最终可以安装 ROS 2。
安装 ROS 2 包
作为最佳实践,在安装任何 ROS 2 包之前,请升级所有现有包:
$ sudo apt update
$ sudo apt upgrade
现在,你可以安装 ROS 2 包。如文档所示,你可以选择桌面安装或ROS-Base 安装。
ROS 2 不仅仅是一块软件或包。它是一系列包的集合:
-
ROS-Base:这个包包含使 ROS 2 正确工作的最基本包。
-
ROS Desktop:这个包包含 ROS-Base 中的所有包,以及许多额外的包,因此你可以访问更多工具、模拟、演示等。
由于你正在一台带有桌面的计算机上安装 ROS 2,并且你想要尽可能多地访问功能,请选择 ROS Desktop 安装。如果你要在受限环境中安装 ROS,例如在树莓派板上安装没有桌面的 Ubuntu Server,那么 ROS-Base 就是有意义的。
通过运行以下命令安装 ROS Desktop:
$ sudo apt install ros-<distro>-desktop
将****替换为发行版名称(使用小写)。所以,如果你想安装 ROS Jazzy,你将使用以下命令:
$ sudo apt install ros-jazzy-desktop
如你所见,当你运行这个命令时,将安装几百个新的软件包。这可能会花费一些时间,这取决于你的互联网连接速度以及你的计算机(或虚拟机)的性能。
如果你看到错误信息指出无法定位软件包 ros-jazzy-desktop,例如,这可能意味着你正在尝试在错误的 Ubuntu 版本上安装 ROS 2 发行版。这是一个常见错误,所以请确保使用正确的 Ubuntu/ROS 2 配对(如本章之前所述)。
注意
从这里,很容易看出如何安装任何其他 ROS 2 软件包。你只需写下ros,然后是发行版名称,然后是软件包名称(使用破折号,而不是下划线):ros-<distro>-package-name。例如,如果你想安装 ROS Jazzy 上的abc_def软件包,那么你需要运行sudo apt install ros-jazzy-abc-def。
安装完成后,你还可以安装 ROS 开发工具。在本书的剩余部分,我们需要这些工具来编译我们的代码和创建 ROS 程序。对于此命令,不需要指定 ROS 发行版;对所有它们都一样:
$ sudo apt install ros-dev-tools
一旦完成这些,ROS 2 将被安装,你将拥有所有需要的 ROS 2 工具。
我还建议你经常更新你已安装的 ROS 2 软件包。为此,只需运行sudo apt update和sudo apt upgrade,就像更新任何其他软件包一样。
设置 ROS 2 的环境
在这一点上,打开一个终端并运行以下命令:
$ ros2
ros2: command not found
你将得到一条错误消息,指出ros2命令找不到。正如我们稍后将要看到的,ros2是一个我们可以从终端运行和测试程序的命令行工具。如果这个命令不起作用,这意味着 ROS 2 没有正确设置。
即使 ROS 2 已安装,在每次新会话(或终端)中,你想要使用 ROS 2 时,你还需要做一件事:你需要在环境中源码它。
在环境中源码 ROS 2
要做到这一点,从 ROS 2 安装的位置源码此 bash 脚本:
$ source /opt/ros/<distro>/setup.bash
将****替换为你当前使用的发行版名称。对于 ROS Jazzy,运行以下命令:
$ source /opt/ros/jazzy/setup.bash
在运行此命令后,尝试再次执行ros2命令。这次,你应该得到一条不同的消息(用法消息)。这意味着 ROS 2 已正确安装在您的环境中。
注意
我们将在本书的后面部分看到如何使用ros2命令行。现在,你可以用它来检查 ROS 2 是否已正确设置。
将源码行添加到.bashrc 文件
每次打开新会话或终端时,都必须源码此 bash 脚本。为了使事情变得简单,并且你不忘记它,我们只需将此命令行添加到**.bashrc**文件中。
如果你不知道**.bashrc是什么,简单来说,它是一个 bash 脚本,每次你打开一个新的会话(可以是 SSH 会话、新的终端窗口等)时都会运行。这个.bashrc**文件对每个用户都是特定的,所以你会在你的家目录中找到它(因为它以点开头,所以是一个隐藏文件)。
你可以使用以下命令将源行添加到**.bashrc**文件中:
$ echo 'source /opt/ros/<distro>/setup.bash' >> ~/.bashrc
将**替换为你的 ROS 发行版名称。你也可以直接使用任何文本编辑器(gedit、nano 或 Vim)打开.bashrc**文件,并在末尾添加源行。确保只添加一次。
一旦你完成了这个步骤,每次你打开一个新的终端时,你可以确信这个终端已经被正确配置,因此你可以在其中使用 ROS 2。
现在,为了进行最后的检查,打开一个终端并运行以下命令(现在你不需要理解任何内容;这只是为了验证安装):
$ ros2 run turtlesim turtlesim_node
这将打印一些日志,你应该会看到一个中间有海龟的新窗口。要停止程序,请在运行命令的终端中按Ctrl + C。
这就是安装和配置 ROS 2 的全部内容。我将给你一些建议,关于在开发 ROS 时哪些开发工具可能是有用的。
ROS 开发的额外工具
除了安装和设置 ROS 2 的必经步骤之外,任何其他开发工具都由你决定。如果你有自己喜欢使用终端、文本编辑器或集成开发环境(IDE)的方式,这完全没问题。
在本节中,我将向你展示一些许多 ROS 开发者(包括我)使用的一些工具,我认为这些工具可以帮助你在开发 ROS 时获得更好的体验。
Visual Studio Code
Visual Studio Code(VS Code)是一个非常受欢迎的 IDE,被许多开发者使用。对我们来说,它对 ROS 开发的良好支持使其变得很棒。
VS Code 是免费且开源的;你甚至可以在 GitHub 上找到它的代码。要安装 VS Code,打开终端并运行以下命令:
$ sudo snap install code --classic
安装只需要一行,并使用 Ubuntu 的 Snap 功能。安装后,你可以在 Ubuntu 应用程序中搜索它,或者在终端中简单地运行code。
现在,启动 VS Code 并转到扩展面板——你可以在左侧菜单中找到它。
在那里,你可以通过输入ros来搜索 ROS 扩展。有很多选择;请选择由微软开发的那个。这个扩展与 ROS 1 和 ROS 2 都兼容,所以这里没有问题:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_02_5.jpg
图 2.5 – 在 VS Code 中安装的 ROS 扩展
安装此扩展。这将还会安装一些其他扩展,特别是 Python 和 C++扩展,这在编写代码时非常有用。
此外,我还通常会安装由 twxs 提供的 CMake 扩展(只需键入 cmake,你就能找到它)。有了这个,我们在编写 CMakeLists.txt 文件时可以获得漂亮的语法高亮,这是我们使用 ROS 2 时经常会做的事情。
终端和其他工具
当你使用 ROS 2 进行开发时,你通常会需要打开几个终端:一个用于编译和安装,几个用于运行你的应用程序的不同程序,还有几个用于检查和调试。
跟踪你使用的所有终端可能会变得相当困难,因此,最佳实践是拥有一个可以轻松在一个窗口中处理多个终端的工具。
做这件事的工具有很多。这里我要介绍的是名为终结者的工具。它不仅名字有趣,而且使用起来也非常实用。
要安装 终结者,请运行以下命令:
$ sudo apt install terminator
然后,你可以在应用程序菜单中找到它,运行它,右键单击左侧栏菜单,并选择固定到仪表盘,这样它就会在那里,并且启动变得容易。
你可以在网上找到 终结者 的所有命令,但以下是一些开始时最重要的命令:
-
Ctrl + Shift + O: 水平拆分选定的终端。
-
Ctrl + Shift + E: 垂直拆分选定的终端。
-
Ctrl + Shift + X: 使当前终端填充整个窗口。再次使用以恢复。
-
Ctrl + Shift + W: 关闭一个终端。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_02_6.jpg
图 2.6 – 四个终端的终结者
每当你拆分一个终端时,这个终端就会变成两个不同的终端,每个都是一个会话。因此,你可以轻松地将你的终端拆分为四个或六个;这足以运行你大多数的 ROS 2 应用程序。由于我们之前已经在 .bashrc 文件中添加了源 ROS 2 的行,所以你可以在每个新的终端中直接使用 ROS 2。
摘要
在选择 ROS 2 发行版时,我建议你选择最新的 LTS 发行版,因为它已经有一段时间了,并且包含了你需要的所有功能。
要安装和设置 ROS 2,你首先需要安装 Ubuntu。每个 ROS 2 发行版都与特定的 Ubuntu 发行版相关联。
对于 ROS 2 Jazzy,你必须安装 Ubuntu 24.04。最佳选择是原生双启动,但为了快速开始,你也可以选择在虚拟机中安装它。然后,你可以安装 ROS 2 软件包。
之后,通过从 ROS 安装文件夹中源一个 bash 脚本来源 ROS 2 的环境非常重要。你可以将源此脚本的行添加到你的 .bashrc 文件中,这样你就不需要在每次打开新的终端时都这样做。
最后,为了获得更好的 ROS 开发体验,我建议使用带有 ROS 扩展的 VS Code,以及一个允许你将终端拆分为多个终端的工具,例如 终结者。
使用这个设置,你就可以完全准备好开始使用 ROS 2 了。在下一章中,你将运行你的第一个 ROS 2 程序并发现核心概念。
第三章:揭示 ROS 2 核心概念
现在,你将开始你的第一个 ROS 2 程序。正如你将看到的,一个 ROS 2 程序被称为 节点。
节点内部有什么,它做什么,节点之间是如何通信的?你如何配置节点并同时启动多个节点?
这就是我们将在本章中关注的内容。我们不会编写任何代码,而是通过实际实验来探索概念,使用与 ROS 2 一起安装的现有演示。
到本章结束时,你将对 ROS 2 的主要核心概念有一个全局的理解。你还将熟悉你将在所有项目中使用的最重要的 ROS 2 工具。
重要注意事项
在本章中,我不会解释所有内容。我们将开始一个探索阶段,在这个阶段中,我们将使用不同的核心概念并猜测它们是如何工作的。现在不是所有东西都必须有意义,如果你对某些概念仍然有些模糊,请不要过于担心。只需尝试通过运行所有命令来通过本章。
这里的目标不是获得完整的理解或记住所有命令,而是获得对事物工作方式的直观感受。这将极大地帮助你理解第二部分,当我们更详细地介绍每个概念并与之一起开发时。
在本章中,我们将涵盖以下主题:
-
运行你的第一个节点
-
主题
-
服务
-
动作
-
参数
-
启动文件
运行你的第一个节点
要理解节点是什么,我们将简单地运行一个节点并使用一些最有用的 ROS 2 工具进行一些观察。
对于本章,我建议打开几个终端。你可以启动几个终端窗口并将它们排列在你的屏幕上,或者使用 Terminator(见第二章中的ROS 开发额外工具)并至少打开三个标签页。为了在运行命令时消除任何混淆,我还会告诉你应该在哪个终端运行该命令(终端 1、终端 2 等)。
使用 ros2 run 从终端启动节点
让我们探索你的第一个 ROS 2 工具,可能是最重要的一个:ros2 命令行工具。你将在未来的项目中一直使用这个工具。
ros2 包含了许多功能。在本章中,我们将探索其中的一些,在后续章节中还将介绍更多。没有必要记住所有命令:现在只需使用它们来建立理解,以后你将能够轻松地从终端中检索它们。
要启动一个节点,你必须遵循以下模板:ros2 run <包名> <可执行文件>。
正如我们稍后将要看到的,节点组织在包内部。这就是为什么你首先需要指定节点所在的包名以及该节点的可执行文件名。由于我们安装了 ROS 桌面,许多演示包已经包含在内,例如,demo_nodes_cpp。
在终端 1 中,从 demo_nodes_cpp 包启动 talker 节点:
$ ros2 run demo_nodes_cpp talker
[INFO] [1710223859.331462978] [talker]: Publishing: 'Hello World: 1'
[INFO] [1710223860.332262491] [talker]: Publishing: 'Hello World: 2'
[INFO] [1710223861.333233619] [talker]: Publishing: 'Hello World: 3'
^C[INFO] [1710223862.456938986] [rclcpp]: signal_handler(signum=2)
在你运行此命令后,节点开始。要停止它,只需在节点运行的终端中按 Ctrl + C。
那么,这里发生了什么?从我们能观察到的来看,这个节点只是一个程序,它会在终端每秒打印一条日志。
现在,保持节点存活,或者如果你停止了它,再次启动它。在另一个终端(终端 2)中,让我们启动一个不同的节点,即来自同一包的监听节点:
$ ros2 run demo_nodes_cpp listener
[INFO] [1710224252.496221751] [listener]: I heard: [Hello World: 9]
[INFO] [1710224253.497121609] [listener]: I heard: [Hello World: 10]
[INFO] [1710224254.495878769] [listener]: I heard: [Hello World: 11]
这个节点也是一个简单的程序,将在终端打印一些日志。然而,正如你所看到的,当两个节点运行时(说话者和监听者),在说话者上打印的任何内容似乎也会被监听者接收,然后打印出来。
在这个例子中,我们有两个节点正在运行,我们可以清楚地看到它们正在相互通信。如果你停止了说话节点,你会看到监听节点也会停止打印日志。当你重新启动说话节点时,监听节点开始打印说话节点“发送”的内容。
注意
这里有一些在使用 ros2 命令行工具时的提示:
尽可能多地使用自动补全。这将使你更快地输入命令,但更重要的是,你可以确保你输入了正确的命令、包名、节点名等等。
如果你对一个命令或子命令有疑问,你可以在命令中添加 -h 来从终端获取帮助。例如,使用 ros2 -h 获取全局帮助,或者使用 ros2 run -h 获取针对运行子命令的帮助。如果你知道在哪里找到信息,就没有必要记住所有的命令。
使用 rqt_graph 检查节点
在这里,我们将发现另一个非常有用的工具,它是命令行的良好补充:rqt_graph。这个工具将用漂亮的视觉效果显示所有正在运行的节点。
保持两个节点(终端 1 和 2)存活,并在终端 3 中启动 rqt_graph。命令与工具名称相同:
$ rqt_graph
这将打开一个新的图形窗口,你应该能看到两个节点。如果你什么都没有看到,确保两个节点都在运行,并通过点击左上角带有刷新图标的按钮来刷新视图。你还可以从左上角的下拉菜单中选择 Nodes/Topics (all)。然后,你应该会得到类似以下的内容:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_03_1.jpg
图 3.1 – 带有两个节点的 rqt_graph
在这里,我们可以看到两个节点都在运行(目前没有新的内容),但我们还看到从说话节点到一个方框的箭头,以及从那个方框到监听节点的另一个箭头。这是 ROS 2 通信,允许说话节点向监听节点发送一些数据——在这里,是一些文本。我们将在本章的下一节中讨论这种通信。
我们现在可以得出的结论是,我们在两个不同的终端中使用了 ros2 run 命令启动了两个不同的 ROS 程序(节点)。看起来这两个程序正在相互通信,我们可以用 rqt_graph 来确认这一点。
在我们进一步了解 ROS 通信的类型之前,让我们运行另一组节点。
运行 2D 机器人模拟
我们运行的前两个节点是非常简单的程序,它们在终端上打印日志并在彼此之间发送一些文本。
现在,停止所有现有的节点(在每个终端中按 Ctrl + C),然后让我们用一些其他节点重新开始。在终端 1 中运行以下命令:
$ ros2 run turtlesim turtlesim_node
Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
[INFO] [1710229365.273668657] [turtlesim]: Starting turtlesim with node name /turtlesim
[INFO] [1710229365.288027379] [turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]
你会看到一些日志,但更重要的是,你将得到一个带有蓝色背景和中间有海龟的新窗口。这个海龟代表一个(非常简化的)在 2D 空间中移动的模拟机器人。
在终端 2 中,启动这个第二个节点:
$ ros2 run turtlesim turtle_teleop_key
Reading from keyboard
---------------------------
Use arrow keys to move the turtle.
Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. 'F' to cancel a rotation.
'Q' to quit.
看到这一点后,确保终端 2 已选中,并使用箭头键(上、下、左、右)。当你这样做的时候,你应该看到海龟机器人移动。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_03_2.jpg
图 3.2 – 移动模拟海龟机器人(TurtleSim)
我们可以猜测,我们启动的第二个节点(使用 turtle_teleop_key)正在读取你按在键盘上的键,并将某种信息/命令发送到 turtlesim 节点,然后该节点使海龟机器人移动。为了确认这一点,请在终端 3 上再次启动 rqt_graph:
$ rqt_graph
如果需要,刷新视图几次。选择 节点/主题(全部),你会看到类似这样的内容:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_03_3.jpg
图 3.3 – 带有 turtlesim 和 teleop_turtle 节点的 rqt_graph
我们可以找到一个名为 turtlesim 的节点和一个名为 teleop_turtle 的节点,我们可以清楚地看到这两个节点正在彼此通信。
注意
如你所见,我们用来启动节点的可执行文件名(turtle_teleop_key)不一定与节点名(teleop_turtle)相同。我们将在本书的后面回到这个问题。
回顾 – 节点
从这两个实验中我们能得到什么?正如你所见,ROS 2 节点可以是任何包含以下内容的计算机程序:
-
指令用于在终端打印日志
-
图形窗口(2D,也可以是 3D)
-
硬件驱动程序等
除了是一个计算机程序之外,节点还受益于 ROS 2 的功能:日志、与其他节点的通信以及本书中将发现的其它特性。
现在你已经看到了如何启动节点和使用 ros2 命令行工具,让我们关注它们是如何相互通信的。
主题
节点使用 ROS 2 通信功能相互通信。有三种类型的通信:主题、服务和动作。我们将发现所有三种,从 主题 开始。
在这里,我们将做一些基本的发现,以了解 ROS 2 主题是什么,你将了解更多关于它们的信息,包括如何为主题编写代码,在第五章中。
运行主题发布者和订阅者
停止所有正在运行的节点(Ctrl + C),然后让我们回到我们的第一个例子。
在终端 1 中,输入以下命令:
$ ros2 run demo_nodes_cpp talker
在终端 2 中,输入以下命令:
$ ros2 run demo_nodes_cpp listener
在终端 3 中,输入以下命令:
$ rqt_graph
如果需要,刷新视图几次,选择Nodes/Topics (all),你应该会得到与图 3相同的视觉效果。
在中间,你会看到一个**/chatter框。这个框代表一个 ROS 2 主题。你还可以看到说话节点正在向/chatter**主题发送信息,这些信息随后将被监听节点接收。
我们说说话节点是一个发布者,监听节点是一个订阅者。
一个重要的细节是,说话节点实际上并没有直接向监听节点发送数据。说话节点在**/chatter主题上发布,监听节点订阅/chatter**主题。正因为如此,数据从说话节点流向监听节点。
一个名称和一个接口(数据类型)
从rqt_graph中,我们已能看到一个节点可以通过主题向另一个节点发送数据。一个重要点是,主题是由名称定义的。发布者和订阅者使用相同的名称来确保通信成功。
不仅仅是名称。让我们回到终端,使用ros2命令行工具来发现更多信息。
你之前使用ros2 run来启动节点。我们还有ros2 topic来与主题交互。你可以使用ros2 topic -h获取所有可用主题命令的帮助。ros2 topic list命令将列出所有可用主题,这意味着所有运行节点之间的主题通信。
在终端 3(如果你停止了rqt_graph)或终端 4 中,运行以下命令:
$ ros2 topic list
/chatter
/parameter_events
/rosout
对于你创建的任何节点,你总会看到**/rosout和/parameter_events**。这些目前并不重要,你可以忽略它们。重要的是**/chatter**主题。我们已经知道它用于说话节点和监听节点之间,但现在的问题是:正在发送什么类型的数据?
要获取这些信息,我们可以使用ros2 topic info <topic_name>:
$ ros2 topic info /chatter
Type: std_msgs/msg/String
Publisher count: 1
Subscription count: 1
在这里,我们可以看到有多少节点正在发布和订阅这个主题。我们有一个发布者(说话节点)和一个订阅者(监听节点)。我们还可以看到正在发送的消息类型:std_msgs/msg/String。在 ROS 2 中,这种消息被称为接口。
要查看接口内部的内容,运行ros2 interface show <interface_name>:
$ ros2 interface show std_msgs/msg/String
# Some comments
string data
可能有一堆注释(以**#开头),你可以忽略。重要的是这一点:字符串数据。这告诉我们该主题上正在发送什么。在这里,它是一个名为data**的字符串(字符链)。
因此,当说话节点想要向**/chatter主题发送消息时,它需要发送一个类型为字符串的data字段。为了获取这些信息,监听节点需要订阅/chatter**,并期望接收相同的数据类型。
这就是主题是如何定义的:一个名称和一个界面(数据类型)。发布者和订阅者都应该使用相同的名称和界面来进行通信。
这是有道理的:作为一个类比,想象你和我通过在线聊天进行交流。如果我们不在同一个聊天室(同一个主题名称),我们就找不到对方。此外,如果我用你不懂的语言和你说话,这对你来说就没有意义。为了交流,我们都需要同意使用什么语言(相同的界面)。
对主题进行更多实验
让我们通过一个挑战来练习一下。这次,我不会直接展示要运行的命令,而是给你一个挑战,这样你可以自己练习。
注意
我会在本书中有时给你一些挑战/活动,难度各不相同——这个第一个挑战相当小。然后我会给你解决方案(或其中一部分)。当然,我鼓励你在阅读说明后停止阅读,只使用前面的页面来解决挑战。然后,阅读解决方案,并与你所做的内容进行比较。
挑战
运行我们之前用 2D turtlesim机器人(两个节点)做的第二个例子。
我现在挑战你做的是找到turtle_teleop节点用来向turtlesim节点发送速度命令的主题名称和界面。使用本章前面的命令尝试获取这些信息。
解决方案
启动两个节点和rqt_graph。
在终端 1 中,输入以下内容:
$ ros2 run turtlesim turtlesim_node
在终端 2 中,输入以下内容:
$ ros2 run turtlesim turtle_teleop_key
在终端 3 中,输入以下内容:
$ rqt_graph
确保你在rqt_graph上刷新视图并选择节点/主题(全部)。你将得到与之前相同的内容,如图 3。3*。
屏幕上还有一些其他内容,但我们只需要一条信息。正如你所见,有一个**/turtle1/cmd_vel框,这里代表一个主题。teleop_turtle节点是一个发布者,而turtlesim**节点是该主题的订阅者。
这很有逻辑性:teleop_turtle节点将读取你按下的键,然后在该主题上发布。在其末端,turtlesim节点将订阅该主题以获取机器人的最新速度命令。
我们可以从终端中获得大致相同的信息:
$ ros2 topic list
/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose
从所有运行的主题列表中,我们可以找到**/****turtle1/cmd_vel**主题。
现在,为了获取该主题的界面(数据类型),请运行以下命令:
$ ros2 topic info /turtle1/cmd_vel
Type: geometry_msgs/msg/Twist
Publisher count: 1
Subscription count: 1
要了解界面的详细信息,请运行以下命令:
$ ros2 interface show geometry_msgs/msg/Twist
# This expresses velocity in free space broken into its linear and angular parts.
Vector3 linear
float64 x
float64 y
float64 z
Vector3 angular
float64 x
float64 y
float64 z
这个界面比我们之前用的要复杂一些。现在没有必要去理解所有这些内容,因为我们会在本书的后面部分深入探讨界面。这里的目的是仅仅找到主题名称和界面。
从这些信息中,假设我们想让机器人向前移动。我们可以猜测,我们需要在linear字段内设置x字段的值(因为在 ROS 中,x指向前方)。
概述 – 主题
通过这两个实验,你可以看到节点通过主题相互通信。一个节点可以发布或订阅一个主题。发布时,节点发送一些数据。订阅时,它接收数据。
主题由一个名称和数据类型定义。现在你需要记住的就是这些。让我们切换到第二种通信类型:服务。
服务
主题非常有用,可以从一个节点向另一个节点发送数据/命令流。然而,这并不是唯一的通信方式。你还可以在 ROS 2 中找到客户端/服务器通信。在这种情况下,服务将被使用。
正如我们对主题所做的那样,我们将运行两个节点通过服务相互通信,这次我们将尝试使用 ROS 2 工具分析正在发生的事情以及通信是如何工作的。
在 第六章 中,你将得到关于服务、何时使用它们与主题相比以及如何在代码中包含它们的更详细解释。现在,让我们继续我们的探索阶段。
运行服务服务器和客户端
停止所有正在运行的节点。这次,我们将从 demo_nodes_cpp 中启动另一个节点,它包含一个简单的服务服务器,用于添加两个整数。我们还将启动一个 客户端 节点,该节点将向 服务器 节点发送请求。
在终端 1 中,输入以下命令:
$ ros2 run demo_nodes_cpp add_two_ints_server
在终端 2 中,输入以下命令:
$ ros2 run demo_nodes_cpp add_two_ints_client
一旦运行客户端节点,你可以在终端 1(服务器)中看到这个日志:
[INFO] [1710301834.789596504] [add_two_ints_server]: Incoming request
a: 2 b: 3
你也可以在终端 2(客户端)中看到这个日志:
[INFO] [1710301834.790073100] [add_two_ints_client]: Result of add_two_ints: 5
从我们在这里观察到的来看,似乎服务器节点正在挂起并等待。客户端节点将向服务器发送一个请求,包含两个整数,在这个例子中是:2 和 3。服务器节点接收请求,相加数字,并返回结果:5。然后,客户端获取响应并打印结果。
这基本上是 ROS 2 中服务工作的方式。你运行一个包含服务器的节点,然后任何其他节点(客户端)都可以向该服务器发送请求。服务器处理请求并返回响应给客户端节点。
一个名称和一个接口(数据类型)
对于主题,服务由两个东西定义:一个名称和一个接口(数据类型)。唯一的区别是接口将包含两部分:一个 请求 和一个 响应。
不幸的是,rqt_graph 不支持服务内省——尽管有一些计划在未来 ROS 2 发行版中实现这一功能。
要找到服务的名称,我们可以再次使用 ros2 命令行工具,这次使用 service 命令,后面跟 list。正如你所见,如果你理解了如何列出所有主题,那么对于服务来说也是一样的。
到目前为止,你仍然在终端 1 上运行着服务节点,而终端 2 上没有运行任何东西(因为客户端在收到响应后停止了)。在终端 2 或 3 上,运行以下命令:
$ ros2 service list
/add_two_ints
/add_two_ints_server/describe_parameters
/add_two_ints_server/get_parameter_types
/add_two_ints_server/get_parameters
/add_two_ints_server/list_parameters
/add_two_ints_server/set_parameters
/add_two_ints_server/set_parameters_atomically
这有很多服务。其中大部分都可以丢弃。对于每个节点,你都会自动获得六个额外的服务,它们都包含名称parameter。如果我们忽略它们,我们可以看到**/add_two_ints服务,这是在add_two_ints_server**节点上运行的服务器。
太好了,我们找到了名称。现在,为了获取数据类型,我们可以使用ros2 service type <service_name>,然后ros2 interface show <interface_name>:
$ ros2 service type /add_two_ints
example_interfaces/srv/AddTwoInts
$ ros2 interface show example_interfaces/srv/AddTwoInts
int64 a
int64 b
---
int64 sum
你可以看到接口中有一条包含三个短横线(—)的行。这是请求和响应之间的分隔。有了这个,你知道作为客户端向服务器发送请求时,你需要发送一个名为a的整数和一个名为b的整数。然后,你将收到一个包含一个名为sum的整数数字的响应。
从终端发送请求
我们不仅可以运行add_two_ints_client节点,还可以直接从终端发送请求。我把它加在这里是因为这是一种非常有用的测试服务的方法,而不需要现有的客户端节点。
语法是ros2 service call <service_name> <interface_name> “<request_in_json>”。正如你所见,我们需要提供服务名称和接口。
这里是如何做到这一点的示例(确保服务器节点仍在运行):
$ ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 4, b: 7}"
waiting for service to become available...
requester: making request: example_interfaces.srv.AddTwoInts_Request(a=4, b=7)
response:
example_interfaces.srv.AddTwoInts_Response(sum=11)
使用这个命令,我们发送了一个包含4和7的请求。服务器节点将打印以下日志:
[INFO] [1710302858.634838573] [add_two_ints_server]: Incoming request
a: 4 b: 7
最后,在客户端,我们得到了包含sum=11的响应。
更多关于服务的实验
这是你练习使用服务的另一个挑战。
挑战
启动turtlesim节点,列出现有服务,并找出如何在终端中生成新的海龟机器人,在 2D 屏幕上。
再次建议你花点时间自己尝试做这个。你可以随意回顾本章的所有先前命令。不需要记住所有这些命令,因为你可以很容易地在书中找到它们,使用Tab键进行自动完成,或者通过在任意命令后添加**-h**。
解决方案
停止所有正在运行的节点。
在终端 1 中,输入以下内容:
$ ros2 run turtlesim turtlesim_node
在终端 2 中,输入以下内容:
$ ros2 service list
/clear
/kill
/reset
/spawn
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
# There are more services containing "parameter" that we can ignore
这些是我们可以为turtlesim节点使用的所有服务。正如你所见,我们已经有相当多了。在这个挑战中,你必须生成一个海龟。太好了,我们可以找到一个**/****spawn**服务。
我们已经有了名称;现在,让我们找到接口(请求,响应):
$ ros2 service type /spawn
turtlesim/srv/Spawn
$ ros2 interface show turtlesim/srv/Spawn
float32 x
float32 y
float32 theta
string name # Optional. A unique name will be created and returned if this is empty
---
string name
现在,我们有了所有需要的信息。要向服务器发送请求,我们必须使用**/spawn服务和turtlesim/srv/Spawn接口。我们可以发送包含(x,y,theta)坐标的请求,以及一个可选的名称。实际上,请注意,请求中的所有字段都是可选的。如果你没有为字段提供值,数字的默认值将是0**,字符串的默认值将是**“”**。
现在,让我们从终端发送我们的请求:
$ ros2 service call /spawn turtlesim/srv/Spawn "{x: 3.0, y: 4.0}"
waiting for service to become available...
requester: making request: turtlesim.srv.Spawn_Request(x=3.0, y=4.0, theta=0.0, name='')
response:
turtlesim.srv.Spawn_Response(name='turtle2')
如果你查看 2D 窗口,你会看到一个新海龟。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_03_4.jpg
图 3.4 – 生成新海龟后的 TurtleSim 窗口
这个海龟已经在请求中提供的(x,y,theta)坐标处生成。您可以尝试再次运行 ros2 service call 命令几次,使用不同的坐标,这样您就可以在屏幕上生成更多的海龟。
回顾 – 服务
您已经成功在两个节点之间运行了客户端/服务器通信。再次强调,服务是由名称和接口(请求,响应)定义的。
关于何时使用主题与服务的更多细节,请继续阅读,因为这是我们将在本书的后面部分看到的,那时您将更了解每个概念。现在,您已经看到了节点之间的两种通信方式。每种通信都有一个名称和接口,我们已经在终端中可以与之互动。
现在还有一个需要发现的 ROS 2 通信:动作。
动作
ROS 2 的 动作基本上与服务(客户端/服务器通信)相同,但设计用于更长时间的任务,并且当您可能希望在执行过程中也获得一些反馈,能够取消执行等。
在机器人领域,我们使机器人移动。使机器人移动不是瞬间发生的事情。它可能只需要一秒钟的几分之一,但有时一个任务可能需要几秒钟/几分钟或更长时间。ROS 2 服务是为快速执行设计的,例如:计算,或者立即动作,比如在屏幕上生成海龟。当客户端/服务器通信可能需要更多时间,并且我们希望对其有更多控制时,就会使用动作。
我们将在 第七章 中更详细地探讨动作。动作是我认为的一个中级概念,而不是初级概念,所以现在我不会深入探讨。让我们只是通过一个非常简单的例子继续探索阶段,以便了解它是如何工作的。
运行动作服务器
停止所有正在运行的节点,并在终端 1 中再次启动 turtlesim 节点:
$ ros2 run turtlesim turtlesim_node
正如您已经通过主题和服务实践过的,以下 ros2 命令将开始对您来说变得熟悉。在终端 2 中列出所有现有的动作:
$ ros2 action list
/turtle1/rotate_absolute
从我们所观察到的来看,似乎 turtlesim 节点包含一个名为 /turtle1/rotate_absolute 的动作服务器。目前还没有为这个动作创建客户端节点,因此我们将尝试从终端与之交互。当然,我们需要两样东西:名称和接口。
名称和接口(数据类型)
关于主题和服务,一个动作将由一个名称和一个接口定义。这次,接口包含三个部分:目标、结果和反馈。
目标和结果类似于服务的请求和响应。反馈是服务器可以发送的附加数据,可以在目标执行期间提供一些反馈。
要获取动作接口,你可以运行 ros2 action info <action_name> -t 命令。别忘了添加 -t(表示类型),否则,你会看到一些细节,但不会有接口:
$ ros2 action info /turtle1/rotate_absolute -t
Action: /turtle1/rotate_absolute
Action clients: 0
Action servers: 1
/turtlesim [turtlesim/action/RotateAbsolute]
我们可以看到动作在一个服务器(turtlesim 节点)内部运行,我们还找到了接口:turtlesim/action/RotateAbsolute。
让我们看看这个接口内部是什么:
$ ros2 interface show turtlesim/action/RotateAbsolute
# The desired heading in radians
float32 theta
---
# The angular displacement in radians to the starting position
float32 delta
---
# The remaining rotation in radians
float32 remaining
你可以看到由三个短横线(—)分隔的两个部分。第一部分是目标,第二部分是结果,第三部分是反馈。这个动作相当简单;我们每个接口部分只有一个浮点数。
作为客户端,我们发送旋转所需的期望角度。服务器节点将接收目标并处理它,同时可选地发送一些反馈。当目标完成时,服务器将结果发送给客户端。
从终端发送目标
作为动作客户端,我们首先对接口的目标部分感兴趣。在这里,我们需要发送一个浮点数,它对应于我们想要旋转海龟的角度(以弧度为单位)。
从终端发送目标的语法是 ros2 action send_goal <action_name> <action_interface> “<goal_in_json>”。再次强调,你需要提供名称和接口。
确保终端 2 中的 turtlesim 节点是活跃的,然后从终端 2 发送一个目标:
$ ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.0}"
Waiting for an action server to become available...
Sending goal:
theta: 1.0
Goal accepted with ID: 3ba92096282a4053b552a161292afc8e
Result:
delta: -0.9919999837875366
Goal finished with status: SUCCEEDED
在运行命令后,你应该看到海龟机器人在 2D 窗口中旋转。一旦达到期望的角度,动作将完成,你将收到结果。
概括 – 动作
你已经在 ROS 2 中运行了第一个动作通信。一个 动作 由两件事定义:一个名称和一个接口(目标、结果、反馈)。当需要客户端/服务器类型的通信,并且动作的持续时间可能需要一些时间——而不是立即执行时,使用动作。
通过这个,你已经看到了 ROS 2 中的三种通信类型:主题、服务和动作。每个都会在 第二部分 中有自己的章节,这样你可以详细地了解它们是如何工作的,如何在代码中使用它们,以及如何使用 ROS 2 工具完全内省它们。
参数
我们现在将回到节点本身,并讨论另一个重要的 ROS 2 概念:参数。
这次,不是关于通信,而是关于如何在启动节点时给它不同的设置。
让我们快速了解参数是如何工作的,你将在第八章中获得更详细的解释,包括更多示例和用例。
获取节点的参数
停止所有正在运行的节点,并在终端 1 中启动 turtlesim 节点:
$ ros2 run turtlesim turtlesim_node
然后,要列出所有参数,相当简单,你可能能猜到命令。如果我们有 ros2 topic list 用于主题,ros2 service list 用于服务,ros2 action list 用于动作,那么,对于参数,我们就有 ros2 param list。唯一的特殊性是我们使用单词 param 而不是 parameter。在终端 2 中运行此命令:
$ ros2 param list
/turtlesim:
background_b
background_g
background_r
holonomic
qos_overrides./parameter_events.publisher.depth
qos_overrides./parameter_events.publisher.durability
qos_overrides./parameter_events.publisher.history
qos_overrides./parameter_events.publisher.reliability
start_type_description_service
use_sim_time
注意
有时,ros2 param list命令可能无法正常工作,你将看不到任何参数或不是所有参数。这种情况也可能发生在一些其他的ros2命令中。在这种情况下,只需再次运行命令,如果需要的话,多次运行,这应该会工作。这可能是ros2命令行工具本身的某种类型的错误,但无需担心:应用程序正在正常运行。
我们首先看到turtlesim节点(实际上写作**/turtlesim**,带有一个前置斜杠),然后是这个节点下的一个带有缩进的名称列表。这些名称是参数,它们属于该节点。这是 ROS 2 中参数的第一个特点:它们存在于节点内部。如果你停止这个turtlesim节点,那么参数也会被销毁。
有一些参数你可以忽略:use_sim_time、start_type_description_service以及包含qos_overrides的所有参数。那些将存在于你启动的任何节点中。如果我们去掉它们,我们剩下一些参数,包括background_b、background_g、backgound_r。
从这个观察结果来看,我们似乎可以在启动turtlesim节点时改变 2D 窗口的背景颜色。
现在,那些参数里面是什么?是什么类型的值?是一个整数,一个浮点数,还是一个字符串?让我们用**ros2 param get <node_name> <param_name>**来找出答案。在终端 2 中运行以下命令:
$ ros2 param get /turtlesim background_b
Integer value is: 255
$ ros2 param get /turtlesim background_g
Integer value is: 86
$ ros2 param get /turtlesim background_r
Integer value is: 69
从这个观察结果中,我们可以推测背景的红、绿、蓝(RGB)值是(69,86,255)。看起来参数值是一个从0到255的整数。
为节点设置参数值
现在我们已经找到了每个参数的名称以及我们应该使用什么类型的值,让我们在启动节点时自己修改这些值。
为了做到这一点,我们需要重新启动节点,使用与之前相同的语法:ros2 run <package_name> <executable_name>。然后我们将添加**–ros-args**(只添加一次),并为每个要修改的参数添加**-p <param_name>:=value**。
在终端 1 上停止turtlesim节点,然后重新启动它,并为一些参数设置不同的值:
$ ros2 run turtlesim turtlesim_node --ros-args -p background_b:=0 -p background_r:=0
在这里,我们决定蓝色和红色颜色都将是0。我们没有为background_g指定任何值,这意味着将使用默认值(如前所述:86)。
运行此命令后,你应该会看到 2D 屏幕出现,但这次背景是深绿色。
概括——参数
参数是可以在运行时提供的设置(这意味着当我们运行节点时)。它们允许我们轻松配置我们启动的不同节点,因此,它们使 ROS 2 应用程序更加动态。
参数存在于节点内部。你可以找到节点的所有参数并获取每个参数的值。在启动节点时,你可以为要修改的参数提供一个自定义值。
启动文件
让我们用启动文件来结束这个 ROS 2 概念的列表。
启动文件将允许你从一个文件中启动多个节点和参数,这意味着你可以通过一条命令行启动整个应用程序。
在 第九章 中,你将学习如何编写自己的启动文件,但现在,让我们先启动几个来看看它们的作用。
启动启动文件
要在终端中启动单个节点,你已经看到了 ros2 run 命令。对于启动文件,我们将使用 ros2 launch <package_name> <launch_file>。
停止所有正在运行的节点,并从 demo_nodes_cpp 包中启动 talker_listener 启动文件。在终端 1 中运行以下命令:
$ ros2 launch demo_nodes_cpp talker_listener_launch.py
[INFO] [launch]: All log files can be found below /home/ed/.ros/log/2024-03-14-16-09-27-384050-ed-vm-2867
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [2868]
[INFO] [listener-2]: process started with pid [2871]
[talker-1] [INFO] [1710403768.481156318] [talker]: Publishing: 'Hello World: 1'
[listener-2] [INFO] [1710403768.482142732] [listener]: I heard: [Hello World: 1]
如你所见,似乎说话者和监听节点都已经启动。你可以在终端 2 中轻松验证这一点:
$ ros2 node list
/listener
/talker
使用 rqt_graph,你还可以检查节点之间是否相互通信。我们有日志证明这一点:在同一屏幕上,我们获取了说话者和监听节点双方的日志,看起来监听节点正在接收消息(使用我们之前看到的 /chatter 主题)。
最后,这和我们在两个终端上启动两个节点是一样的。启动文件将简单地在一个终端中启动这两个节点。
如果我们更仔细地阅读日志,我们可以看到每个节点将在不同的进程中启动。要停止启动文件,请按 Ctrl + C。这将停止所有进程(节点),并且你的应用程序将结束。
现在我们尝试从 turtlesim 包中启动另一个启动文件。在终端 1 中停止启动文件,并从 turtlesim 包中启动 multisim 启动文件:
$ ros2 launch turtlesim multisim.launch.py
[INFO] [launch]: All log files can be found below /home/ed/.ros/log/2024-03-14-16-14-41-043618-ed-vm-2962
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [turtlesim_node-1]: process started with pid [2963]
[INFO] [turtlesim_node-2]: process started with pid [2965]
这样,你将看到两个二维窗口,每个窗口中都有一个海龟机器人。正如日志所示,我们正在启动两个 turtlesim 节点(两个具有不同名称的相同节点)。
我们也可以从终端中检查:
$ ros2 node list
/turtlesim1/turtlesim
/turtlesim2/turtlesim
节点已经被重命名。我们得到的不再是 /turtlesim,而是 /turtlesim1/turtlesim 和 /turtlesim2/turtlesim。这些名称是在启动文件内部选择的。
复习 – 启动文件
启动文件对于从一个文件中启动多个节点(以及这些节点的参数)非常有用。只需一条命令行(ros2 launch),你就可以启动整个 ROS 2 应用程序。
目前关于启动文件的讨论就这么多,因为这个概念相当简单(真正的挑战在于编写启动文件,而不是启动它)。我们现在已经完成了对 ROS 2 主要概念的探索。
概述
通过本章,你已经发现了 ROS 2 最重要的一些概念:节点、主题、服务、动作、参数和启动文件。
ROS 2 程序被称为节点。简单来说,它们是常规的软件程序,也可以从 ROS 2 功能中受益:日志、通信、参数等等。
有三种通信类型:主题、服务和动作。主题用于从一个或多个节点向另一个或多个其他节点发送数据/命令流。服务用于需要客户端/服务器通信的情况。动作基本上与服务相同,但对于可能需要一些时间的目标执行。
除了通信功能外,节点还可以使用参数来指定运行时的设置。参数允许节点在启动时轻松配置。
最后,我们可以通过一个启动文件从一条命令行启动所有节点和参数。
对于核心概念(目前是这样),你已经发现了ros2命令行工具和rqt_graph。这些工具是无价的,你将经常使用它们。我们使用这些工具进行的实验与你在未来的 ROS 2 项目中将要做的非常相似。
这一章节有点特别,因为它并没有从 A 到 Z 完整地解释一个概念。正如引言中所述,它更像是一个概念巡游,通过实际操作发现主要概念。你得到的不是完整的理解,而是对事物工作方式的直觉,一些工具的使用经验,以及对整体图景的认识。
随时可以回到这一章节,在你阅读本书的过程中再次运行实验。一切都会变得更加有意义。
现在,你已经准备好继续进行第二部分,在那里,你将从头开始创建一个完整的 ROS 2 应用程序,使用 Python 和 C++代码。你迄今为止看到的每个概念都将有自己的专属章节。在这里培养的直觉将非常有用。
第二部分:使用 ROS 2 进行开发 – Python 和 C++
这一部分主要关注使用 ROS 2 编写代码并构建可扩展的机器人应用。您已经在 第三章 中发现了主要概念,并对它们的工作原理有了直观的了解。现在,您将逐一深入到每个概念,通过实际生活中的类比、深入代码(使用 Python 和 C++)以及额外的挑战来帮助您练习。
本部分包含以下章节:
-
第四章, 编写和构建 ROS 2 节点
-
第五章, 主题 – 节点之间发送和接收消息
-
第六章, 服务 – 节点之间的客户端/服务器交互
-
第七章, 动作 – 当服务不足时
-
第八章, 参数 – 使节点更加动态
-
第九章, 启动文件 – 一次性启动所有节点
第四章:编写和构建 ROS 2 节点
要使用 ROS 2 编写自己的自定义代码,你必须创建 ROS 2 程序,换句话说,就是节点。你已经在 第三章 中发现了节点的概念。在本章中,我们将更深入地探讨,你将使用 Python 和 C++ 编写你的第一个节点。
在你创建节点之前,有一些设置要做:你需要创建一个 ROS 2 工作空间,你将在其中构建你的应用程序。在这个工作空间中,你将添加软件包以更好地组织你的节点。然后,在这些软件包中,你可以开始编写你的节点。在你编写一个节点之后,你将构建它并运行它。
我们将一起完成这个完整的过程,一路上都有动手代码和命令行。这是你在开发 ROS 2 应用程序时创建任何新节点时需要重复的过程。
到本章结束时,你将能够使用 Python 和 C++ 创建自己的软件包和 ROS 2 节点。你还将能够从终端运行和检查你的节点。这是学习任何其他 ROS 2 功能性所需的基础。没有节点,就没有主题、服务、动作、参数或启动文件。
所有解释都将从 Python 开始,然后是 C++,我们将快速介绍。如果你只想用 Python 学习,你可以跳过 C++ 部分。然而,如果你想用 C++ 学习,阅读之前的 Python 解释对于理解是强制性的。
本章的所有代码示例都可以在本书 GitHub 仓库的 ch4 文件夹中找到(github.com/PacktPublishing/ROS-2-from-Scratch)。
在本章中,我们将涵盖以下主题:
-
创建和设置 ROS 2 工作空间
-
创建一个软件包
-
创建一个 Python 节点
-
创建一个 C++ 节点
-
Python 和 C++ 节点的节点模板
-
检查你的节点
技术要求
要跟随本章,你需要以下内容:
-
安装了 Ubuntu 24.04(双启动或虚拟机)
-
ROS Jazzy
-
文本编辑器或 IDE(例如,带有 ROS 扩展的 VS Code)
这些要求将适用于 第二部分 中的所有章节。
创建和设置 ROS 2 工作空间
在我们编写任何代码之前,我们需要做一些组织工作。节点将存在于软件包中,而你所有的软件包都将存在于一个 ROS 2 工作空间 中。
什么是 ROS 2 工作空间?工作空间 仅仅是一个文件夹组织,你将在其中创建和构建你的软件包。你的整个 ROS 2 应用程序都将生活在这个工作空间中。
要创建一个工作空间,你必须遵循某些规则。让我们一步一步地创建你的第一个工作空间,并正确地设置它。
创建工作空间
要创建一个工作空间,你只需在你的家目录中创建一个新的目录。
关于工作空间的名称,让我们现在保持简单,使用一个易于识别的名称:ros2_ws。
注意
工作空间的名称并不重要,它不会影响你的应用程序中的任何内容。因为我们刚开始,我们只有一个工作空间。当你取得进展并开始处理多个应用程序时,最佳实践是为每个工作空间命名应用程序或机器人的名称。例如,如果你为名为ABC V3的机器人创建一个工作空间,那么你可以将其命名为abc_v3_ws。
打开一个终端,导航到你的家目录,并创建工作空间:
$ cd
$ mkdir ros2_ws
然后,进入工作空间并创建一个名为src的新目录。这就是你将编写所有 ROS 2 应用程序代码的地方:
$ cd ros2_ws/
$ mkdir src
这就是全部内容。要设置新的工作空间,你只需创建一个新的目录(在你的家目录中的任何位置),并在其中创建一个src目录。
构建工作空间
即使工作空间为空(我们还没有创建任何包),我们仍然可以构建它。为此,请按照以下步骤操作:
-
导航到工作空间根目录。确保你处于正确的位置。
-
运行
colcon build命令。colcon是 ROS 2 的构建系统,在你安装ros-dev-tools包时已安装(见第二章)。
让我们构建工作空间:
$ cd ~/ros2_ws/
$ colcon build
Summary: 0 packages finished [0.73s]
如你所见,没有构建任何包,但让我们列出**~/ros2_ws**下的所有目录:
$ ls
build install log src
如你所见,我们有了三个新的目录:build、install和log。build目录将包含整体构建所需的中间文件。在log中,你会找到每个构建的日志。对你来说最重要的目录是install,这是你在构建工作空间后所有节点将被安装的地方。
注意
你应该始终从工作空间目录的根目录运行colcon build,而不是从其他任何地方。如果你犯了一个错误,并从另一个目录(比如说,从工作空间的src目录,或者在一个包内部)运行此命令,只需简单地删除在错误位置创建的新install、build和log目录。然后回到工作空间根目录并重新构建。
源代码工作空间
如果你导航到新创建的install目录内部,你可以看到一个setup.bash文件:
$ cd install/
$ ls
COLCON_IGNORE _local_setup_util_ps1.py setup.ps1
local_setup.bash _local_setup_util_sh.py setup.sh
local_setup.ps1 local_setup.zsh setup.zsh
local_setup.sh setup.bash
这可能看起来很熟悉。如果你记得,在我们安装 ROS 2 之后,我们从 ROS 2 安装目录(/opt/ros/jazzy/setup.bash)源代码了一个类似的 bash 脚本,以便我们可以在环境中使用 ROS 2。我们还需要为我们的工作空间做同样的事情。
每次你构建工作空间时,都必须源代码它,以便环境(你所在的会话)了解工作空间中的新更改。
要源代码工作空间,源代码这个setup.bash脚本:
$ source ~/ros2_ws/install/setup.bash
然后,就像我们之前做的那样,我们将把这一行添加到我们的**.bashrc**中。这样,你就不需要在每次打开新终端时都源代码工作空间。
使用你想要的任何文本编辑器打开你的**.bashrc**(位于你的家目录中,路径为**~/.bashrc**):
$ gedit ~/.bashrc
在全局 ROS 2 安装的源代码行之后添加一行,用于源代码工作空间的setup.bash脚本。这里的顺序非常重要。您必须首先源代码全局 ROS 2 安装,然后是您的 workspace,而不是反过来:
source /opt/ros/jazzy/setup.bash
source ~/ros2_ws/install/setup.bash
确保保存**.bashrc**。现在,无论是 ROS 2 还是您的工作空间,在您打开任何新的终端时都会被源代码。
注意
如果您在一个已经源代码的环境中构建工作空间,您仍然需要再次源代码工作空间,因为有一些变化,环境并不知情。在这种情况下,您可以直接源代码工作空间的 setup.bash 脚本,源代码 .bashrc,或者打开一个新的终端。
您的工作空间现在已正确设置,您可以构建您的应用程序。下一步:创建一个包。
创建一个包
您创建的任何节点都将存在于一个包中。因此,要创建一个节点,您首先必须创建一个包(在您的 workspace 中)。您现在将学习如何创建自己的包,我们将看到 Python 包和 C++包之间的区别。
但首先,包究竟是什么?
ROS 2 包是什么?
ROS 2 包是您应用程序的一个子部分。
让我们考虑一个我们想要用来拾取和放置物体的机械臂。在我们创建任何节点之前,我们可以尝试将这个应用程序分成几个子部分,或者包。
我们可以有一个包来处理相机,另一个包用于硬件控制(电机),还有一个包用于计算机器人的运动规划。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_04_1.jpg
图 4.1 – 机器人包组织示例
每个包都是一个独立的单元,负责您应用程序的一个子部分。
包对于组织您的节点非常有用,并且可以正确处理依赖项,正如我们将在本书后面看到的那样。
现在,让我们创建一个包,在这里您必须做出选择。如果您想用 Python 创建节点,您将创建一个 Python 包,如果您想用 C++创建节点,您将创建一个 C++包。每种包类型的架构都相当不同。
创建一个 Python 包
您将创建所有包在您的 ROS 2 工作空间的 src 目录中。因此,在您做其他任何事情之前,请确保导航到这个目录:
$ cd ~/ros2_ws/src/
这是构建包的命令构造方法:
-
ros2 pkg create <pkg_name>: 这是您需要编写的最小内容。 -
您可以使用
--build_type <build_type>指定构建类型。对于 Python 包,我们需要使用ament_python。 -
您还可以使用
--dependencies <list_of_dependencies_separated_with_spaces>指定一些可选依赖项。在包中添加依赖项总是可能的。
让我们创建我们的第一个包,命名为my_py_pkg。我们将使用此名称作为示例来处理主要的 ROS 2 概念。然后,随着我们的进展,我们将使用更有意义的名称。在您工作区的src目录中运行以下命令:
$ ros2 pkg create my_py_pkg --build-type ament_python --dependencies rclpy
使用此命令,我们表示我们想要创建一个名为my_py_pkg的包,使用ament_python构建类型,并指定一个依赖项:rclpy——这是您将在每个 Python 节点中使用的 ROS 2 的 Python 库。
这将打印出很多日志,显示已经创建了哪些文件。您也可能收到一条关于缺少许可证的**[警告]**日志,但由于我们没有任何意图将此包发布到任何地方,我们现在不需要许可证文件。您可以忽略此警告。
您可以看到,有一个名为my_py_pkg的新目录。这是您新创建的 Python 包的架构:
/home/<user>/ros2_ws/src/my_py_pkg
├── my_py_pkg
│ └── __init__.py
├── package.xml
├── resource
│ └── my_py_pkg
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
目前并非所有文件都很重要。我们将在稍后看到如何使用这些文件来配置和安装我们的节点。
这里是关于最重要的文件和目录的快速概述:
-
my_py_pkg:如您所见,在包内部,还有一个同名的目录。这个目录已经包含了一个__init__.py文件。这就是我们将创建我们的 Python 节点的地方。 -
package.xml:每个 ROS 2 包(Python 或 C++)都必须包含此文件。我们将使用它来提供有关包以及依赖项的更多信息。 -
setup.py:这是您将编写构建和安装您的 Python 节点的说明的地方。
创建 C++包
在这本书中,我们将大量使用 Python,但为了完整性,我还会包括所有示例的 C++代码。这些代码要么在书中解释,要么在 GitHub 仓库中提供。
创建 C++包与创建 Python 包非常相似;然而,包的架构将会有很大的不同。
确保您导航到您工作区的src目录,然后创建一个新的包。让我们使用与 Python 相同的模式,将包命名为my_cpp_pkg:
$ cd ~/ros2_ws/src/
$ ros2 pkg create my_cpp_pkg --build-type ament_cmake --dependencies rclcpp
我们选择ament_cmake作为构建类型(这意味着这将是一个 C++包),并指定一个依赖项:rclcpp——这是 ROS 2 的 C++库,我们将用于每个 C++节点。
再次提醒,您应该会看到很多日志,包括新创建的文件,以及可能关于许可证的警告,但您可以忽略。
您的新 C++包的架构将如下所示:
/home/ed/ros2_ws/src/my_cpp_pkg/
├── CMakeLists.txt
├── include
│ └── my_cpp_pkg
├── package.xml
└── src
这里是关于每个文件或目录角色的快速解释:
-
CMakeLists.txt:这将用于提供如何编译您的 C++节点、创建库等的说明。 -
include目录:在 C++项目中,您可以将代码分成实现文件(.cpp扩展名)和头文件(.hpp扩展名)。如果您将 C++节点分成.cpp和.hpp文件,您将把头文件放在include目录中。 -
package.xml:此文件是任何类型的 ROS 2 包所必需的。它包含有关包的更多信息,以及其他包的依赖关系。 -
src目录:这是你将编写你的 C++ 节点(.cpp文件)的地方。
构建包
现在你已经创建了一个或多个包,你可以构建它们,即使包中还没有任何节点。
要构建包,回到你的 ROS 2 工作空间根目录并运行 colcon build。再次强调,就像在本章前面所看到的,运行此命令的位置非常重要。
$ cd ~/ros2_ws/
$ colcon build
Starting >>> my_cpp_pkg
Starting >>> my_py_pkg
Finished <<< my_py_pkg [1.60s]
Finished <<< my_cpp_pkg [3.46s]
Summary: 2 packages finished [3.72s]
两个包都已经构建完成。每次你在包内添加或修改节点时,你都必须这样做。
重要的是要注意这一行:Finished <<< <package_name> [time]。这意味着包已正确构建。即使你看到额外的警告日志,如果你也看到了 Finished 行,你就知道包已经构建。
注意
在构建任何包之后,你还需要源你的工作空间,以便环境能够意识到新的更改。你可以执行以下任何一项操作:
-
打开一个新的终端,因为所有配置都在
.bashrc文件中 -
直接源
setup.bash脚本(source ~/ros2_ws/install/setup.bash) -
手动源
.bashrc文件 (source ~/.bashrc)
要仅构建特定的包,可以使用 –packages-select 选项,后跟包的名称。以下是一个示例:
$ colcon build --packages-select my_py_pkg
Starting >>> my_py_pkg
Finished <<< my_py_pkg [1.01s]
Summary: 1 package finished [1.26s]
这样,你不需要每次都构建整个应用程序,只需专注于一个包即可。
现在我们已经创建了一些包,并且我们知道如何构建它们,我们可以在包中创建节点。但我们如何组织它们呢?
包中的节点是如何组织的?
要开发 ROS 2 应用程序,你将在节点内部编写代码。如第三章中所示,节点只是 ROS 2 程序的名称。
节点是应用程序的子程序,负责一件事情。如果你有两个不同的功能需要实现,那么你将有两个节点。节点通过 ROS 2 通信(主题、服务和动作)相互通信。
你将在包内部组织你的节点。对于某个包(应用程序的子部分),你可以有多个节点(功能)。要完全理解如何组织包和节点,你需要实践和经验。现在,让我们用一个例子来获得一个概念。
让我们回到我们在 图 4*.1* 中看到的包架构,并在包内添加节点:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/ros2-scr/img/B22403_04_2.jpg
图 4.2 – 包组织节点的示例
如你所见,在相机包中,我们可以有一个节点负责处理相机硬件。这个节点会将图像发送到图像处理节点,而这个后者会提取机器人拾取物体的坐标。
同时,一个运动规划节点(在运动规划包中)将根据特定的命令计算机器人应执行的运动。路径校正节点可以使用从图像处理节点接收到的数据来支持这种运动规划。
最后,为了使机器人移动,一个硬件驱动节点将负责硬件通信(电机、编码器)并从运动规划节点接收命令。还可以有一个额外的状态发布节点,用于向其他节点发布有关机器人的额外数据。
这种节点组织纯粹是虚构的,这里只是为了给你一个 ROS 2 应用程序如何设计的总体概念,以及节点在这个应用程序中可以扮演哪些角色。
现在,你终于要编写你的第一个 ROS 2 节点了。ROS 2 在你可以实际编写代码之前需要相当多的安装和配置,但好消息是,我们已经完成了所有这些,现在我们可以专注于代码了。
目前我们不会做任何太复杂的事情;我们不会深入复杂的特性或通信。我们将编写一个基本的节点,你可以将其作为模板来开始任何未来的节点。我们还将构建这个节点,看看如何运行它。
创建一个 Python 节点
让我们创建我们的第一个 Python 节点,或者换句话说,我们的第一个 ROS 2 Python 程序。
创建 Python 和 C++节点的过程非常不同。这就是为什么我为每个都写了单独的部分。我们将从 Python 开始,提供完整的逐步说明。然后我们将看到如何用 C++做同样的事情。如果你想跟随 C++节点部分,请确保先阅读这一部分。
要创建一个节点,你必须做以下事情:
-
为节点创建一个文件。
-
编写节点。我们将使用面向对象编程(OOP),这是 ROS 2 官方推荐的(并且你几乎可以找到的每个现有的 ROS 2 代码都使用 OOP)。
-
构建节点存在的包。
-
运行节点以测试它。
让我们开始我们的第一个 Python 节点。
为节点创建一个文件
要编写节点,我们首先需要创建一个文件。我们应该在哪里创建这个文件?
如果你记得,当我们创建my_py_pkg包时,在包内部创建了一个名为my_py_pkg的目录。这就是我们将编写节点的地方。对于每个 Python 包,你必须进入与包同名目录。如果你的包名是abc,那么你将进入**~/ros2_ws/src/abc/abc/**。
在这个目录中创建一个新文件并使其可执行:
$ cd ~/ros2_ws/src/my_py_pkg/my_py_pkg/
$ touch my_first_node.py
$ chmod +x my_first_node.py
然后,打开这个文件进行编写。你可以使用任何你想要的文本编辑器或 IDE,只要你不迷失在所有文件中。
如果你不知道该使用什么,我建议使用带有 ROS 扩展的 VS Code(如第二章中所述)。这是我用于所有 ROS 开发的工具。
注意
如果你使用 VS Code,最佳方式是首先在终端中导航到你的工作区src目录,然后打开它。这样,你可以访问工作区中的所有包,并且这将使处理已识别的依赖项和自动完成变得更容易:
$ cd ~/ros2_ws/src/
$ code .
编写一个最小的 ROS 2 Python 节点
这是任何你将创建的 Python 节点的起始代码。你可以将此代码写入my_first_node.py文件:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
class MyCustomNode(Node):
def __init__(self):
super().__init__('my_node_name')
def main(args=None):
rclpy.init(args=args)
node = MyCustomNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
如你所见,我们在这里使用了面向对象编程(OOP)。在 ROS 2 中,OOP 无处不在,这是默认(也是推荐)的编写节点的方式。
让我们一步一步地回到这段代码,来理解它在做什么:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
我们首先导入rclpy,这是 ROS 2 的 Python 库。在这个库中,我们可以获取Node类。
我们接着创建一个新的类,它继承自rclpy的Node类:
class MyCustomNode(Node):
def __init__(self):
super().__init__('my_node_name')
在这个类中,确保你使用**super()**调用父构造函数。这也是你指定节点名称的地方。
这个节点目前什么都没做;我们将在一分钟内添加一些功能。让我们完成代码:
def main(args=None):
rclpy.init(args=args)
node = MyCustomNode()
rclpy.spin(node)
rclpy.shutdown()
在类之后,我们在其中创建一个**main()**函数,执行以下操作:
-
使用
rclpy.init()初始化 ROS 2 通信。这应该是你main()函数中的第一行。 -
从我们之前写的
MyCustomNode类创建一个对象。这将初始化节点。不需要在之后销毁节点,因为当程序退出时,这会自动发生。 -
让节点旋转。如果你省略这一行,节点将被创建,然后程序将退出,节点将被销毁。让节点旋转意味着我们在这里阻塞执行,程序保持活跃,因此节点也保持活跃。在此期间,正如我们很快将看到的,所有注册的节点回调都可以被处理。当你按下Ctrl + C时,节点将停止旋转,这个函数将返回。
-
在节点被杀死后,使用
rclpy.shutdown()关闭 ROS 2 通信。这将是你main()函数中的最后一行。
这就是所有你的 ROS 2 程序的工作方式。如你所见,节点实际上是我们程序中创建的一个对象(节点本身不是程序,但当我们谈论程序时,仍然非常常见地提到“节点”这个词)。创建后,节点可以在旋转时保持活跃并发挥其作用。我们很快会回到这个旋转的概念。
最后,我们还添加了这两行:
if __name__ == '__main__':
main()
这是一个纯 Python 的东西,与 ROS 2 无关。它只是意味着如果你直接运行 Python 脚本,main()函数将被调用,因此你可以尝试你的程序而无需使用colcon安装它。
太好了,你已经编写了你的第一个最小 Python 节点。在你构建和运行它之前,在节点的构造函数中添加一行,这样它就可以做些事情:
class MyCustomNode(Node):
def __init__(self):
super().__init__('my_node_name')
self.get_logger().info("Hello World")
这行代码将在节点启动时打印Hello World。
由于 MyCustomNode 类继承自 Node 类,我们可以访问所有 ROS 2 的节点功能。这将使我们的事情变得相当方便。这里有一个使用日志功能的例子:我们从 Node 中获取 get_logger() 方法。然后,使用 info() 方法,我们可以打印一个信息级别的日志。
构建节点
你现在将要构建节点以便运行它。
你可能会想:为什么我们需要构建一个 Python 节点?Python 是一种解释型语言;我们难道不能直接运行文件本身吗?
是的,这是真的:你只需在终端中运行代码即可测试它 ($ python3 my_first_node.py)。然而,我们真正想要做的是在我们的工作区中安装文件,这样我们就可以使用 ros2 run 启动节点,稍后还可以从启动文件中启动。
我们通常使用“构建”这个词,因为要安装 Python 节点,我们必须运行 colcon build。
要构建(安装)节点,我们需要在软件包中做一件事。打开 my_py_pkg 软件包中的 setup.py 文件。在文件末尾找到 entry_points 和 ‘console_scripts’。对于我们要构建的每个节点,我们必须在 ‘console_scripts’ 数组中添加一行:
entry_points={
'console_scripts': [
"test_node = my_py_pkg.my_first_node:main"
],
},
这里是语法:
<executable_name> = <package_name>.<file_name>:<function_name>.
在正确编写这一行时有一些重要的事情需要注意:
-
首先,选择一个可执行文件名。这将是你使用
ros2 run <pkg_name> <executable_name>时的名称。 -
对于文件名,跳过
.py扩展名。 -
函数名是
main,因为我们已经在代码中创建了一个main()函数。 -
如果你想要为另一个节点添加另一个可执行文件,别忘了在每个可执行文件之间添加逗号,并且每个可执行文件占一行。
注意
在学习 ROS 2 时,节点名、文件名和可执行文件名之间有一个常见的混淆:
-
节点名:在代码中的构造函数内定义。这就是你在
ros2 node list或rqt_graph中看到的内容。 -
文件名:你编写代码的文件。
-
可执行文件名:在
setup.py中定义并用于ros2 run。
在这个第一个例子中,我确保为每个使用了不同的名称,这样你可以意识到这些都是三件不同的事情。但有时这三个名称可以是相同的。例如,你可以创建一个 temperature_sensor.py 文件,然后给你的节点和可执行文件命名为 temperature_sensor。
现在你已经给出了创建新可执行文件的指令,请前往你的工作区根目录并构建该软件包:
$ cd ~/ros2_ws/
$ colcon build
你也可以添加 –packages-select my_py_pkg 以仅构建此软件包。
可执行文件现在应该已创建并安装在工作区中(它将被放置在 install 目录中)。我们可以这样说,你的 Python 节点已经构建或安装了。
运行节点
现在你可以运行你的第一个节点了,但在运行之前,请确保工作区已在你的环境中源码:
$ source ~/.bashrc
这个文件已经包含了源码工作空间的行;你也可以打开一个新的终端,或者从工作空间中源码setup.bash脚本。
你现在可以使用ros2 run来运行你的节点(如果你有任何疑问,请回到我们在第三章中做的实验):
$ ros2 run my_py_pkg test_node
[INFO] [1710922181.325254037] [my_node_name]: Hello World
太好了,我们看到日志Hello World。你的第一个节点已经成功运行。请注意,我们在ros2 run命令中写了test_node,因为这是我们选择在setup.py文件中的可执行文件名。
现在,你可能会注意到程序在那里挂起了。节点仍然活着,因为它正在自旋。要停止节点,请按Ctrl + C。
改进节点——计时器和回调
到目前为止,你可能觉得编写、构建和运行一个节点是一个漫长且复杂的过程。实际上,它并不那么复杂,而且随着你创建的新节点的增加而变得更容易。此外,修改现有的节点甚至更容易。我们现在就看看。
我们运行的这个节点非常基础。让我们再添加一个功能,做一些更有趣的事情。
我们的这个节点在启动时会打印一段文本。现在我们希望节点每秒打印一个字符串,只要它还活着。
“每 Y 秒执行 X 动作”这种行为在机器人技术中非常常见。例如,你可以有一个节点“每 2 秒读取一次温度”,或者“每 0.1 秒发送一个新的电机命令”。
如何做到这一点?我们将在我们的节点中添加一个计时器。计时器将以指定的速率触发一个回调函数。
让我们回到代码中,并修改MyCustomNode类。其余的代码保持不变:
class MyCustomNode(Node):
def __init__(self):
super().__init__('my_node_name')
self.counter_ = 0
self.timer_ = self.create_timer(1.0, self.print_hello)
def print_hello(self):
self.get_logger().info("Hello " + str(self.counter_))
self.counter_ += 1
我们仍然有带有super()的构造函数,但现在日志在单独的方法中。此外,我们不再只是打印Hello World,在这里我们创建了一个**counter_**属性,每次我们使用日志时都会增加它。
注意
如果你想知道为什么每个类属性末尾都有一个尾随下划线_,这是一个常见的面向对象编程约定,我遵循这个约定来指定一个变量是类属性。它仅仅是一个视觉上的帮助,没有其他功能。你可以遵循相同的约定或使用另一个约定——只要确保在一个项目中保持一致性。
最重要的一行是创建计时器的代码。为了创建计时器,我们使用Node类的**create_timer()**方法。我们需要提供两个参数:我们想要调用函数的速率(浮点数)和回调函数。请注意,回调函数应指定不带任何括号。
这条指令意味着我们想要每1.0秒调用一次print_hello方法。
现在我们来尝试一下代码。因为我们已经在setup.py文件中指定了如何从这个文件创建可执行文件,所以我们不需要再次做这件事。
我们需要做的只是构建、源码和运行。记住:“构建、源码、运行。”每次你创建一个新的节点或修改现有的节点时,你都必须“构建、源码、运行。”
在终端中,转到你的 ROS 2 工作空间的根目录并构建包:
$ cd ~/ros2_ws/
$ colcon build --packages-select my_py_pkg
注意
在 --packages-select <pkg_name> 的基础上,你可以添加 --symlink-install 选项,这样你就不必每次修改 Python 节点时都构建包;例如,$ colcon build --packages-select my_py_pkg --symlink-install。
你可能会看到一些警告日志,但只要看到以 Finished <<< my_py_pkg 开头的行,就说明它已经正确工作了。这将安装可执行文件,但如果修改了代码,你应该能够运行它而无需再次构建。
两个重要的事情:这仅适用于 Python 包,并且你仍然需要为任何新创建的可执行文件构建包。
然后,从这个终端或另一个终端,执行以下操作:
$ source ~/.bashrc
$ ros2 run my_py_pkg test_node
[INFO] [1710999909.533443384] [my_node_name]: Hello 0
[INFO] [1710999910.533169531] [my_node_name]: Hello 1
[INFO] [1710999911.532731467] [my_node_name]: Hello 2
[INFO] [1710999912.534052411] [my_node_name]: Hello 3
如您所见,构建、源代码和运行的过程相当快,并不复杂。在这里,我们可以看到节点每秒打印一条日志,并且计数器在每条新日志中递增。
现在这是怎么做到的?print_hello() 方法是如何被调用的?我们确实创建了一个计时器,但是代码中并没有直接调用 print_hello()。
它之所以能工作,是因为节点正在旋转,这要归功于 rclpy.spin(node)。这意味着节点被保持活跃状态,并且在此期间可以调用所有已注册的回调。我们使用 create_timer() 做的事情仅仅是注册一个回调,这样当节点在旋转时就可以被调用。
这是你第一个回调示例,正如你将在本书的后续章节中看到的,在 ROS 2 中,一切操作都是通过回调来运行的。到目前为止,如果你在语法、回调和旋转方面还有一些困难,不要过于担心。随着你对本书的进步,你会多次重复这个过程。在学习 ROS 2 时,理解伴随着动手实践。
我们现在已经完成了这个 Python 节点。根据你所看到的,你应该能够创建你自己的新 Python 节点(在同一个包或另一个包中)。现在让我们切换到 C++。如果你目前只对使用 Python 学习 ROS 2 感兴趣,你可以跳过 C++ 部分。
创建一个 C++ 节点
我们将要做的与为 Python 节点所做的是完全相同的:创建一个文件,编写节点,构建,源代码,然后运行。
确保你已经阅读了之前的 Python 部分,因为我不会在这里重复所有内容。我们基本上只是看看如何为一个 C++ 节点应用这个过程。
要创建一个 C++ 节点,我们首先需要一个 C++ 包。我们将使用之前创建的 my_cpp_pkg 包。
编写 C++ 节点
让我们为节点创建一个文件。转到 my_cpp_pkg 包内的 src 目录并创建一个 .cpp 文件:
$ cd ~/ros2_ws/src/my_cpp_pkg/src/
$ touch my_first_node.cpp
你也可以直接从你的 IDE 创建文件,而不使用终端。
现在,如果你之前没有这样做,请使用 VS Code 或任何其他 IDE 打开你的工作空间:
$ cd ~/ros2_ws/src/
$ code .
打开 my_first_node.cpp。以下是编写 C++ 节点的最小代码:
#include "rclcpp/rclcpp.hpp"
class MyCustomNode : public rclcpp::Node
{
public:
MyCustomNode() : Node("my_node_name")
{
}
private:
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<MyCustomNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
注意
如果你使用 VS Code 并输入此代码,你可能会看到 rclcpp 库的包含错误。请确保保存文件并等待几秒钟。如果包含仍然没有被识别,请转到 扩展 选项卡,禁用并重新启用 ROS 扩展。
正如你所见(这与 Python 类似),在 ROS 2 中,我们大量使用 C++ 节点的面向对象编程。
让我们一步一步分析这段代码:
#include "rclcpp/rclcpp.hpp"
我们首先包含 rclcpp,这是 ROS 2 的 C++ 库。这个库包含 rclcpp::Node 类:
class MyCustomNode : public rclcpp::Node
{
public:
MyCustomNode() : Node("my_node_name")
{
}
private:
};
就像我们对 Python 所做的那样,我们创建了一个继承自 Node 类的类。语法不同,但原理相同。从这个 Node 类,我们将能够访问所有 ROS 2 功能:记录器、计时器等。正如你所见,我们在构造函数中也指定了节点名称。目前,节点什么也不做;我们将在一分钟内添加更多功能:
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<MyCustomNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
如果你想要能够运行你的 C++ 程序,你需要一个 main() 函数。在这个函数中,我们与 Python 完全相同,只是在语法上有些差异:
-
使用
rclcpp::init()初始化 ROS 2 通信。 -
从你新编写的类中创建一个节点对象。正如你所见,我们不是直接创建一个对象,而是创建对该对象的智能指针。在 ROS 2 和 C++ 中,你创建的几乎所有内容都将是一个智能指针(共享、唯一等)。
-
然后我们使用
rclcpp::spin()让节点运行。 -
最后,当节点停止时(Ctrl + C),我们使用
rclcpp::shutdown()关闭所有 ROS 2 通信。
这个 main() 函数的结构将非常类似于你所有的 ROS 2 程序。正如你所见,再次强调,节点本身不是程序。节点是在程序内部创建的。
在我们进一步构建、源代码和运行我们的节点之前,让我们现在通过计时器、回调和日志来改进它。
修改 MyCustomNode 类,其余保持不变:
class MyCustomNode : public rclcpp::Node
{
public:
MyCustomNode() : Node("my_node_name"), counter_(0)
{
timer_ = this->create_wall_timer(std::chrono::seconds(1), std::bind(&MyCustomNode::print_hello, this));
}
void print_hello()
{
RCLCPP_INFO(this->get_logger(), "Hello %d", counter_);
counter_++;
}
private:
int counter_;
rclcpp::TimerBase::SharedPtr timer_;
};
这个代码示例将执行与 Python 节点相同的功能。我们创建一个计时器,以便每 1.0 秒调用一次回调函数。在这个回调函数中,我们打印 Hello 后跟一个计数器,每次调用时都会增加。
与 C++ 相关有一些特定性:
-
对于计时器,我们必须创建一个类属性。正如你所见,我们在这里也创建了一个共享指针:
rclcpp::TimerBase::SharedPtr。 -
我们使用
this->create_wall_timer()创建计时器。this->在这里不是必需的,但我添加它来强调我们正在使用Node类的create_wall_timer()方法。 -
要在计时器中指定回调,由于我们处于 C++ 类中,我们必须使用
std::bind(&ClassName::method_name, this)。确保你不对方法名称使用任何括号。
节点现在已经完成,因此我们可以构建它。
构建和运行节点
我们不能直接运行 C++ 文件;我们首先必须编译它并创建一个可执行文件。为此,我们将编辑 CMakeLists.txt 文件。打开此文件,在几行之后,你会找到类似以下的内容:
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
查找 rclcpp 的行在这里,因为我们创建包时使用了 ros2 pkg create 并提供了 –dependencies rclcpp。之后,如果这个包中的节点需要更多的依赖,你可以在这里添加依赖,每行一个。
在此行之后,添加一个额外的空行,然后添加以下指令:
add_executable(test_node src/my_first_node.cpp)
ament_target_dependencies(test_node rclcpp)
install(TARGETS
test_node
DESTINATION lib/${PROJECT_NAME}/
)
要构建一个 C++ 节点,我们需要做三件事:
-
使用
add_executable()函数添加一个新的可执行文件。在这里,你必须为可执行文件选择一个名称(将用于ros2 run <pkg_name> <executable_name>),我们还需要指定 C++ 文件的相对路径。 -
使用
ament_target_dependencies()函数链接此可执行文件的所有依赖。 -
使用
install()指令安装可执行文件,这样我们就可以在运行ros2 run时找到它。在这里,我们将可执行文件放在lib/<package_name>目录中。
然后,对于你创建的每个新的可执行文件,你需要重复 步骤 1 和 步骤 2,并在 install() 指令内添加可执行文件,每行一个,不要加逗号。不需要为每个可执行文件创建一个新的 install() 指令。
注意
你的 CMakeLists.txt 文件的末尾将包含一个以 if(BUILD_TESTING) 开头的块,然后是 ament_package()。由于我们这里没有进行任何构建测试,你可以删除整个 if 块。只需确保保留 ament_package() 行,这应该是文件的最后一行。
现在,你可以使用 colcon build 构建包,这将创建并安装可执行文件:
$ cd ~/ros2_ws/
$ colcon build --packages-select my_cpp_pkg
如果在构建过程中遇到任何错误,请首先修复你的代码,然后再次构建。然后,你可以源环境,并运行你的可执行文件:
$ source ~/.bashrc
$ ros2 run my_cpp_pkg test_node
[INFO] [1711006463.017149024] [my_node_name]: Hello 0
[INFO] [1711006464.018055674] [my_node_name]: Hello 1
[INFO] [1711006465.015927319] [my_node_name]: Hello 2
[INFO] [1711006466.015355747] [my_node_name]: Hello 3
如你所见,我们运行了 test_node 可执行文件(由 my_first_node.cpp 文件构建),这将启动 my_node_name 节点。
你现在已经成功编写了一个 C++ 节点。对于你创建的每个新节点,你将必须创建一个新的 C++ 文件,编写节点类,在 CMakeLists.txt 中为新可执行文件设置构建指令,并构建包。然后,要启动节点,源环境并使用 ros2 run 运行可执行文件。
Python 和 C++ 节点的模板
本书开始的所有节点都将遵循相同的结构。为了快速入门提供额外的帮助,我创建了一个节点模板,你可以用它来编写任何 Python 或 C++ 节点的基类。我在创建新节点时也使用这些模板,因为代码可能相当重复。
你可以直接从这本书复制粘贴模板,或者从 GitHub 仓库下载它们:github.com/PacktPublishing/ROS-2-from-Scratch。
Python 节点的模板
使用此代码来启动任何新的 Python 节点:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
class MyCustomNode(Node): # MODIFY NAME
def __init__(self):
super().__init__("node_name") # MODIFY NAME
def main(args=None):
rclpy.init(args=args)
node = MyCustomNode() # MODIFY NAME
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
您需要做的就是移除 MODIFY NAME 注释,并更改类名(MyCustomNode)和节点名(“node_name”)。使用有意义的名称会更好。例如,如果您正在编写一个从温度传感器读取数据的节点,您可以将类命名为 TemperatureSensorNode,节点可以是 temperature_sensor。
C++ 节点的模板
使用此代码来启动任何新的 C++ 节点:
#include "rclcpp/rclcpp.hpp"
class MyCustomNode : public rclcpp::Node // MODIFY NAME
{
public:
MyCustomNode() : Node("node_name") // MODIFY NAME
{
}
private:
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<MyCustomNode>(); // MODIFY NAME
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
移除 MODIFY NAME 注释并重命名类和节点。
这两个模板将允许您更快地启动节点。我建议您尽可能多地使用它们。
检查您的节点
为了完成本章,我们将使用 ros2 node 命令行进行一些练习。
到目前为止,您已经看到了如何编写节点、构建它并运行它。一个缺失的部分是了解如何检查您的节点。即使节点可以运行,这也并不意味着它将完全按照您希望的方式执行。
能够检查您的节点将帮助您修复您可能在代码中犯的错误。它还将允许您轻松地找到有关您启动但未编写的其他节点的更多信息(如我们在 第三章 中的发现阶段所做的那样)。
对于 第二部分 中的每个核心概念,我们将花一些时间来实验与该概念相关的命令行工具。节点的命令行工具是 ros2 node。
首先,在我们使用 ros2 node 之前,我们必须启动一个节点。作为回顾,要启动节点,我们使用 ros2 run <package_name> <executable_name>。如果我们启动本章中创建的 Python 节点,我们使用这个:
$ ros2 run my_py_pkg test_node
只有在我们启动了一个节点之后,我们才能使用 ros2 node 进行一些检查。
ros2 node 命令行
要列出所有正在运行的节点,使用 ros2 node list:
$ ros2 node list
/my_node_name
我们找到了在代码中定义的节点名称。
一旦我们有了节点名称,我们可以使用 ros2 node info <node_name> 获取更多关于它的信息:
$ ros2 node info /my_node_name
/my_node_name
Subscribers:
Publishers:
/parameter_events: rcl_interfaces/msg/ParameterEvent
/rosout: rcl_interfaces/msg/Log
Service Servers:
/my_node_name/describe_parameters: rcl_interfaces/srv/DescribeParameters
/my_node_name/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
/my_node_name/get_parameters: rcl_interfaces/srv/GetParameters
/my_node_name/get_type_description: type_description_interfaces/srv/GetTypeDescription
/my_node_name/list_parameters: rcl_interfaces/srv/ListParameters
/my_node_name/set_parameters: rcl_interfaces/srv/SetParameters
/my_node_name/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
Service Clients:
Action Servers:
Action Clients:
如您所见,终端上有很多东西。我们将在接下来的章节中了解它们。使用 ros2 node info <node_name> 可以查看此节点运行的所有主题(发布者/订阅者)、服务和动作。
在运行时更改节点名称
随着我们在本书中的进展,我将为您提供一些关于使用 ROS 2 和命令行的额外技巧。这里有一个:在启动可执行文件时,您可以选择使用默认的节点名称(代码中定义的名称)或用新名称替换它。
要向 ros2 run 添加任何额外的参数,首先需要添加 –ros-args(只添加一次)。
然后,要重命名节点,添加 -r __node:=<new_name>。-r 表示重映射;您也可以使用 –remap。例如,如果我们想将节点命名为 abc,我们可以使用这个:
$ ros2 run my_py_pkg test_node --ros-args -r __node:=abc
[INFO] [1711010078.801996629] [abc]: Hello 0
[INFO] [1711010079.805748394] [abc]: Hello 1
如从日志中所示,我们看到的不是 my_node_name,而是 abc。
列出所有正在运行的节点:
$ ros2 node list
/abc
这个功能非常有用,它让你能够更好地控制如何启动节点,而无需直接修改代码。
注意
当运行多个节点时,你应该确保每个节点都有一个独特的名称。如果有两个节点具有相同的名称,可能会导致一些意想不到的问题,这些问题可能需要很长时间才能调试。在未来,你可能需要运行同一个节点多次,例如,三个temperature_sensor节点,每个节点对应不同的传感器。你可以将它们重命名为temperature_sensor_1、temperature_sensor_2和temperature_sensor_3。
摘要
在本章中,你已经创建了你的第一个节点。让我们快速回顾一下所有步骤。
在创建任何节点之前,你需要遵循以下步骤:
-
你首先需要创建并设置一个 ROS 2 工作区。
-
在这个工作区中,你可以创建几个包(Python 或 C++),它们代表应用程序的不同子部分。
然后,在一个包中你可以创建一个或多个节点。对于每个节点,你都需要执行以下操作:
-
在包内创建一个文件。
-
编写节点(以 OOP 模板为基础)。
-
设置构建说明(Python 的
setup.py,C++的CMakeLists.txt)。 -
构建包。
要运行节点,别忘了首先源代码工作区,然后使用**ros2 run <pkg_name> <executable_name>**启动节点。
最后,当你启动节点时,可以使用ros2 node命令行来检查你的节点,甚至更改它们的名称。
随时可以回到本章,查看创建 Python 和 C++节点的完整过程。所有代码都可在 GitHub 上找到,网址为github.com/PacktPublishing/ROS-2-from-Scratch。在那里你可以找到 Python 和 C++的 OOP 模板代码,my_py_pkg包和my_cpp_pkg包。
在本章中,你还看到了如何创建计时器和回调函数。你对自旋机制的工作原理有了更好的理解,以及它是如何使节点保持活跃并运行回调的。这将在后续章节中非常有用。
在下一章中,我们将看到节点如何通过主题进行通信。你将在节点内部编写自己的主题(发布者/订阅者)并对其进行实验。
2万+


&spm=1001.2101.3001.11974&articleId=159774264&d=1&t=3&u=c1576dd794e646fa9f2650cac4970e7d)

被折叠的 条评论
为什么被折叠?



