ROS软件包是ROS系统的基本单位。我们可以创建,然后编译生成一个ROS软件包,并将其向公众发布。当前我们使用的ROS发布版本是Noetic Ninjemys。我们使用catkin编译系统来编译生成ROS软件包。编译系统主要负责将用户的源码生成“目标(target)”(可执行文件或库文件)。在较老的ROS发行版本中,例如Electric和Fuerte,使用rosbuild来编译生成软件包。由于rosbuild存在各种缺陷,所以catkin应运而生了。catkin基本上基于跨平台编译器(Cross Platform Make,CMake)。它有很多优点,例如可以将软件包移植到另一个操作系统(如Windows)上。如果操作系统支持CMake和Python,就可以轻松地将基于catkin的软件包移植到该系统上。
使用ROS软件包的第一个要求是创建ROS的catkin工作区。安装好ROS后,创建一个名为catkin_ws的catkin工作区:
为了编译工作区,我们需要获取ROS的环境变量,以便访问ROS系统提供的功能:
切换到前面创建的源码文件夹src中:
初始化新的catkin工作区:
即使现在工作区中没有软件包,我们也可以编译工作区。使用以下命令切换到工作区文件夹中:
下面使用catkin_make编译工作区:
执行完上面这条命令后,在catkin工作区中将生成devel和build文件夹。各种安装文件位于devel文件夹中。要将创建的ROS工作区添加到ROS环境变量中,我们需要获取其中的一个安装文件setup.bash。此外,在每次使用以下命令启动新的bash会话时,我们都可以获取此工作区的配置文件:
配置好catkin工作区后,我们就可以创建自己的软件包了,其中将包含示例节点,用来演示ROS主题、消息、服务和动作库(actionlib)的工作方式。注意,如果没有正确设置工作区,则无法使用任何ROS命令。catkin_create_pkg命令就是用来创建ROS软件包的。我们将用它创建各种ROS概念的演示示例。
切换到catkin工作区的src文件夹后,使用以下命令创建软件包:
源码文件夹:所有的ROS软件包(无论是从头创建的还是从其他代码库中下载的)都必须放在ROS工作区的src文件夹中,否则ROS系统将无法识别它们,从而导致无法编译。
下面是创建ROS示例软件包的命令:
该功能包中的依赖项如下:
roscpp:这是ROS的C++实现,一个ROS客户端库,为C++开发人员提供API,使用ROS主题、服务和参数等生成ROS节点。包含该依赖的原因是我们要编写的是一个C++实现的ROS节点。任何使用C++节点代码的ROS软件包都必须添加此依赖项。
std_msgs:该软件包包含了基本的ROS原始数据类型,例如整型、浮点型、字符串、数组等。我们可以在节点中直接使用这些数据类型,而无须定义新的ROS消息。
actionlib:该超软件包提供了在ROS节点中创建可抢占任务的接口。我们在这个软件包中创建了基于actionlib的节点,所以我们需要包含该软件包来创建ROS节点。
actionlib_msgs:该软件包包含了与动作服务器和动作客户端交互所需的标准消息定义。
创建了软件包后,我们也可以通过编辑CMakeLists.txt和package.xml这两个文件来手动添加其他依赖项。如果成功创建了软件包,我们将收到以下信息,如图2.1所示。
图2.1 创建ROS软件包时的终端信息
创建了这个软件包后,可以使用catkin_make命令来编译生成软件包,但是它不会增加任何节点。我们必须在catkin工作区的根路径下执行此命令。以下是我们编译生成空ROS软件包的命令:
成功编译生成软件包后,我们可以将节点源码添加到工作区下的src文件夹中。
在CMake的build文件夹中主要包含了节点的可执行文件,该节点的源码位于catkin工作区的src文件夹中。devel文件夹主要包含了在编译过程中生成的Bash脚本、头文件和可执行文件。我们可以看到使用catkin_make创建并编译生成ROS节点的过程。
主题是两个节点间通信的基本方式。在本节,我们将学习主题的工作原理。下面我们将创建两个ROS节点,一个发布主题,另一个订阅该主题。进入mastering_ros_demo_pkg文件夹中,在/src源码文件夹中,demo_topic_publisher.cpp和demo_topic_subscriber.cpp是我们将要讨论的两个源码文件。
我们要讨论的第一个节点是demo_topic_publisher.cpp。此节点将在名为/numbers的主题上发布一个整型数值。我们可以将下面的代码复制到新软件包中或使用代码仓库中的现有文件。以下是完整的代码:
代码从头文件的定义开始。ros/ros.h是ROS的主要头文件。如果我们想在代码中使用roscpp客户端API的话,就必须包含此头文件。std_msgs/Int32.h是整型数据类型的标准消息定义的头文件。
这里我们通过主题发送整型数据,所以我们需要一个消息类型来处理这些整型数据。std_msgs包含了基本数据类型的标准消息定义。std_msgs/Int32.h包含了整型消息的定义。现在,我们将初始化一个带有名称的ROS节点。需要注意的是,ROS节点名应该是唯一的:
接下来,我们创建一个Nodehandle对象来与ROS系统进行通信。所有的ROS C++节点代码都必须包含此行代码:
这将创建一个主题发布者,该主题是std_msgs::Int32消息类型,主题名是/numbers。第二个参数是缓冲区大小,它表示在发送消息之前可以将多少消息放到缓冲区中。应在考虑消息发布率时设置此数字。如果你的程序发布的速度超过了队列大小,一些消息将被丢弃。队列大小的最低接受数字是1,而0意味着一个无限的队列:
下面的代码用于设置程序主循环的频率,因此,在我们的例子中,发布率也是如此:
这是一个无限的while循环,当我们按下Ctrl+C组合键时,它才会退出。如果出现一个中断,ros::ok()函数将返回0,这样就可以中断该while循环:
第一行创建了一个整型ROS消息,第二行为这条消息分配了整型数据。在这里,data是msg对象的字段名:
这将打印消息数据。下面两行代码用于输出ROS消息的日志,并将前面的消息发布到ROS网络上:
这行能提供必要的延时使发布消息的频率达到10Hz:
讨论了发布者节点之后,现在我们可以讨论订阅者节点了,即demo_topic_subscriber.cpp。
下面是订阅者节点的定义:
与前面一样,代码从头文件的定义开始。这是一个回调函数,只要有数据发送到/numbers主题上,它就会被自动调用执行。当有数据发送到此主题上时,该函数将调用并提取消息中的数值,然后将其打印在控制台上:
这里定义了一个订阅者,我们给出了订阅所需的主题名称、缓冲区大小和要执行的回调函数。我们将订阅/numbers主题,回调函数已经在前面看到过了:
这是一个无限循环,节点将在此步骤中一直等待。只要有数据被发送到主题上,此代码就会立刻执行相应主题的回调函数。只有当按下Ctrl+C组合键时,该节点才会退出:
现在代码完成了。在执行它之前,我们需要对它进行编译,这在下一节中讨论。
我们必须编辑软件包中的CMakeLists.txt文件才能编译和构建源码。切换到mastering_ros_demo_pkg以查看现有的CMakeLists.txt文件。此文件中的下面这段代码将负责构建这两个节点:
我们可以创建一个新的CMakeLists.txt文件,然后添加前面的代码,这样也可以编译这两个节点。
catkin_make命令是用来编译生成软件包的。首先我们需要切换到工作区根文件夹中:
构建ROS工作区,mastering_ros_demo_package软件包:
我们可以使用前面的命令来编译整个工作区,也可以使用-DCATKIN_WHITELIST_PACKAGES选项。使用此选项可以设置一个或多个要编译的软件包:
注意,必须还原此配置才能编译其他软件包或整个工作区。我们可以使用以下命令来完成这个操作:
如果编译完成,我们就可以执行这个节点了。首先需要启动roscore:
现在,在两个终端中分别运行这两个命令。运行发布者节点:
运行订阅者节点:
我们将看到如图2.2所示的输出。
图2.2 运行主题的发布者节点和订阅者节点
图2.3显示了节点间是如何相互通信的。我们可以看到demo_topic_publisher节点向/numbers主题发布信息,然后demo_topic_subscriber节点订阅这个主题。
图2.3 发布者和订阅者节点间的通信图
我们可以使用rosnode和rostopic工具来调试和理解这两个节点的工作方式。
●rosnode list:这将列出所有活动的节点。
●rosnode info demo_topic_publisher:这将获取发布者节点的相关信息。
●rostopic echo/numbers:这将显示发送到/numbers主题上的数据。
●rostopic type/numbers:这将打印/numbers主题的消息类型。
我们已经学习了如何使用标准消息在ROS节点之间交换信息,下面来学习如何使用自定义消息和服务。