ROS学习笔记(3):通信机制之话题通信
前置知识
在介绍通信机制之前,咱们先来了解ROS中的两个个核心概念:节点(node)、节点管理器(ROS Master)
节点是ROS软件包中的一个可执行文件。ROS节点使用ROS客户端库与其他节点通信。 不同的节点甚至可以使用不同的编程语言,以及运行在不同的主机上。 更加详细的介绍可以看这:理解ROS节点
ROS Master 为 ROS 系统中的其余节点提供命名和注册服务。它跟踪主题和服务的发布者和订阅者。 Master 的作用是使各个 ROS 节点能够相互定位。一旦这些节点找到彼此,它们就会进行点对点通信。
在使用roscore 命令时,该命令加载 ROS Master 以及其他基本组件。
ROS Master
概述
ROS是机器人操作系统,机器人是一种比较复杂的系统性实现,在机器人上面可能有很多传感器以及运动控制实现,也就意味着有很多个节点。那么我们想要实现指定动作的时候,需要用到很多节点,节点之间是如何通信呢?
ROS基本通信机制有如下三种
话题通信(发布订阅模式)
服务通信(请求响应模式)
参数服务器(参数共享模式)
接下来让我们详细看看这三种通信机制。
话题通信
话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,即:一个节点发布消息,另外一个节点订阅该消息。话题通信适用于不断更新的数据传输相关的应用场景。
接下来让我们看一下话题通信的理论模型吧。
理论模型
在话题通信机制中,有三个角色 ROS Master(管理者)、Talker(发布者)、Listener(订阅者)。
职责分布:ROS Master会保存 Talker、Listener的注册信息,当Listener订阅的主题存在时,就会帮助Listener与 发布该主题的Talker 建立联系。联系建立后,ROS Master就没有什么作用了,当Talker再次发布消息,然后Listener就能看到 Talker发布的消息。
接下来让我们看一下底层是如何实现的。
咱们来详细讲一下每一个流程。
当Talker启动时,会在ROS Maser那边登记信息,有主题、RPC地址等信息
当Listener启动时,也会在ROS Maser登记信息,有需要订阅消息的话题名等
ROS Master进行匹配,如果 Listener 需要订阅的话题存在的话,就会通过RPC给 Listener发送 发布该主题 Talker的RPC地址信息。
Listener通过 RPC向 Talker发送连接信息
Talker确认连接信息后,发送自身的TCP地址信息。
Listener 通过TCP地址信息 与 Talker建立连接。
当Talker再次发布消息时,Listener就能接收到消息,完成了订阅该主题。
其中,Talker、Listener注册无先后顺序。而且Talker、Listener可以有多个,可以一个Talker对多个Listener。
现在话题通信已经比较成熟了,被封装成包了,咱们不需要实现那么多的底层操作,只需要设置一些参数即可。
C++实现话题通信
咱们来用C++实现一个话题通信的实例.
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。
首先就是创建工作空间、创建ROS包等。
1 | 创建工作空间并编译 |
可以使用 tree 命令查看文件结构,目前该工作空间结构如下图(只显示了文件夹,没有显示文件)
talker实现
然后咱们在 topic_communication/src/pub_sub/src 子目录下创建一个cpp文件,就可以开始写程序了。
1 | cd ~/topic_communication/src/pub_sub/src |
由于是初次接触,先有易到难编写程序,即先不停发送hello,后面在修改为按照频率发送消息。
下面为程序内容
1 |
|
关于这段代码,大家需要看一下函数用法。
1 | //创建发布者对象 |
第一个参数为topic,就是准备发布的主题名字;第二个参数为当消息堵塞时,缓存区的大小,也就是所能保留消息的条数;第三个可写可不写,如果为 "true",将保存在此主题上发布的最后一条信息,并在新用户连接时发送给他们。
还有一个疑惑,为什么要创建节点句柄,它的作用是什么?
后面找了一下官方教程,是这样解释的。
NodeHandle 是 roscpp 用于创建订阅者、发布者等的接口。该类用于编写节点。它为该进程的节点提供了一个 RAII 接口,即在创建第一个 NodeHandle 时,它会实例化该节点所需的一切,而当最后一个 NodeHandle 退出作用域时,它会关闭节点。
下一步是修改CMakeLists.txt
把136行注释取消,修改 ${PROJECT_NAME}_node 以及路径
取消149~151的注释,把 ${PROJECT_NAME}_node替换为刚才修改的名字
然后回到工作空间,并编译。
1 | cd ~/topic_communication/ |
最后就是运行这个包(启动内核,刷新环境变量,运行包)
由于咱们没有打印调试信息,所以咱们什么都看不到,可以在终端使用rostopic echo命令
1 | rostopic echo topic_name |
咱们之前发送的消息就是 hello,说明发布部分写正确了。接下来咱们按照要求 10HZ完成发送消息的任务。代码如下
1 |
|
下面是效果图
listener实现
接下来把listener实现一下。
首先进入到 topic_communication/src/pub_sub/src ,然后开始写订阅程序。
1 | 如果路径不同,请替换为自己的路径 |
代码如下
1 |
|
关于这段代码,有两个需要解释的地方
1 | ros::Subscriber sub = nh.subscribe<std_msgs::String>("chat",10,handle_msg); |
这个是创建一个订阅者对象,ros::Subscriber 声明了类型,通过使用nh的subscribe函数来生成了一个对象。关于subscribe函数,有13种重载(使用方法),大家可以选择任意一种
这里使用的是第二种,第一个参数为需要订阅的话题;第二个参数为缓冲区大小,即当消息堵塞时,所能保留消息的条数;第三个是一个回调函数,用于处理消息。关于回调函数,请看 回调函数(callback)是什么?一文理解回调函数(callback)
1 | ros::spin(); |
这个代码起什么作用?
ros::spin()的作用是使订阅者对象(sub)开始监听来自"chat"话题的消息。通过调用ros::spin(),程序将持续监听并处理接收到的消息,直到节点被关闭。当有新的消息到来时,ROS会自动触发回调函数handle_msg(),并将收到的消息作为参数传递给回调函数进行处理。有点STM32里面中断那味。
可以详细看一下这个 ros::spin()、ros::spinOnce():使用细节、区别 ,并于 ros::spinonce()做一个区分.
然后修改CMakeLists.txt
然后回到工作空间并编译
1 | cd ~/topic_communication/ |
最后看一下效果
记得在运行节点前,要刷新一下环境变量哈
为了更加直观显示通信的逻辑,咱们可以使用 rqt_graph 显示当前运行的节点和话题。
1 | 如果没有安装的话。请放开注释 进行安装 |
注意:当进行通信的时候,才能查看到内容。
可以看到,talker、listener节点在通过 chat话题进行通信。
Python实现话题通信
还是上面那个实例,咱们用rospy来实现一下。由易到难实现。
talker实现
首先在ROS包下创建一个 scripts文件夹,并在该目录下创建pub.py
1 | cd ~/topic_communication/src/pub_sub/ |
pub.py内容如下
1 | #! /usr/bin/env python |
对于这段代码,大家需要看一下Publisher、is_shutdown这两个函数的用法。
1 | pub = rospy.Publisher("chat_py",String,queue_size=10) |
创建 rospy.Publisher 所需的参数只有主题名称、消息类别和队列大小。还可以使用其他高级选项来配置 "发布者"
is_shutdown函数用于判断节点是否关闭,如果关闭的话,返回True,否则返回False。
保存并退出后,给该文件添加可执行权限。
1 | chmod +x pub.py |
然后去ROS包子目录下修改
1 | 此时路径为:topic_communication/src/pub_sub/scripts |
取消注释,并把py文件路径修改为自己的路径
回到工作空间,进行编译
1 | cd ~/topic_communication/ |
接下来运行节点,并使用rostopic echo 查看消息
最后,再按照实例要求,完成按照频率发布消息的任务。
1 | #! /usr/bin/env python |
最后重新编译,刷新环境变量,重新启动节点,使用 rostopic echo命令查看消息
listener实现
首先进入scripts文件夹内,并在这里创建sub.py
1 | cd ~/topic_communication/src/pub_sub/scripts |
sub.py内容如下
1 | import rospy |
其中大部分与C++实现类似,大家着重看一下函数用法
1 | sub = rospy.Subscriber("chat_py",String,handle_msg,queue_size=10) |
一般只需要这几个参数,第一个参数为需要订阅话题的名字;第二个是消息类型;第三个是回调函数;第四个缓冲区大小
保存并退出后,给该文件添加可执行权限。
1 | chmod +x sub.py |
然后去ROS包子目录下修改
1 | 此时路径为:topic_communication/src/pub_sub/scripts |
最后回到工作空间,编译,并运行sub_py、pub_py节点
最后再使用 rosrun rqt_graph rqt_graph查看图
大家可以看一下rospy官方的示例: 官方示例
C++与Python节点通信
在ROS中,使用不同编程语言的节点之间也可以进行通信。很神奇吧。
咱们在前面写过的代码里稍微修改一下,就可以实现了。
这里发布者节点为用的是C++ 节点(节点名:sub),订阅者节点使用的是Python节点(节点名:sub_py)。
为了让它们之间通信,需要修改一下sub_py节点订阅的话题。sub节点的话题为 chat,咱们需要把sub_py节点订阅的话题修改为chat。
1 | sub = rospy.Subscriber("chat",String,handle_msg,queue_size=10) |
修改这一句话就可以了。
然后就是重新编译,刷新环境变量 运行节点。
自定义msg
在前面的案例中,我们传输的数据是 std_msgs中的String数据类型,只包含了一个data字段,有一定局限性,std_msgs中其他的数据类型Int32、Int64、Char、Bool、Empty也是如此。
当传输复杂的数据的时候,比如激光雷达的信息,std_msgs就不太行了,这时候咱们可以选择自定义消息类型。
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:
int8, int16, int32, int64 (或者无符号类型: uint*)
float32, float64
string
time, duration(时间、持续时间)
other msg files(其他msg文件)
variable-length array[] and fixed-length array[C](可变长度数组、固定长度数组)
ROS中还有一种特殊类型:Header,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。
为了直观,这里咱们直接实际创建一个自定义msg。
要求:消息包含人的信息:姓名、身高、年龄等 大致流程如下
按照固定格式创建 msg 文件
编辑配置文件
编译生成可以被 Python 或 C++ 调用的中间文件
定义msg文件
首先回到功能包目录下,创建一个msg目录
1 | cd ~/topic_communication/src/pub_sub/ |
然后进入msg子目录下,创建一个 .msg文件
1 | cd msg/ |
Person.msg内容如下
1 | string name |
我来解释一下语法。
.msg文件每行具有字段类型和字段名称。第一行定义了一个字符串类型的name字段,第二行定义了一个无符号16位的整数age字段,第三行定义了一个64位浮点数的height字段。
有一点点像C++的结构体!
编辑配置文件
我在CMakeLists.txt看到了下图,配置步骤。
接下来需要修改ROS包文件夹下的package.xml文件
在这里添加编译依赖项、执行依赖项
1 | <build_depend>message_generation</build_depend> |
Package.xml是干什么的呢?
package.xml文件定义了package的属性。(例如:包名,版本号,作者,一来等等),相当于一个包的自我描述。
详细解读可以看:package.xml的使用说明和作用
然后修改ROS包下的CMakeLists.txt文件
(1)在find_package那里添加包
1 | find_package(catkin REQUIRED COMPONENTS |
find_package是查找编译依赖的其他CMake/Catkin包(声明依赖库)。自定义msg编译时需要message_generation。
注意:message_genration是基于std_msgs实现的,std_msgs不能删除
(2) 在add_message_files添加自定义msg文件,并且把注释放开
1 | add_message_files( |
add_message_files这个是消息生成器。当自定义msg需要在这里添加msg文件
(3) 把generate_messages注释放开,去生成消息
在这里调用了消息生成器,用于生成自定义的msg。
(4)catkin_package添加包
1 | catkin_package( |
catkin_package()是一个由catkin提供的CMake宏。需要指定特定的catkin信息到编译系统,而这些信息又会被用于生成pkg-config和CMake文件。
在这里,给它添加了当前项目依赖的其他catkin项目
到这里,修改配置文件已经结束了。 希望大家再看看package.xml、CMakeLists.txt的解读,不能知其然而不知其所以然!!!
ROS的CMakeList文件详解
package.xml的使用说明和作用
查看msg的中间文件
首先回到工作空间,重新编译一下。
1 | cd ~/topic_communication/ |
编译成功后,可以在工作空间下的devel文件夹内查看中间文件。
C++可以用的msg中间文件在 topic_communication/devel/include/pub_sub/Person.h topic_communication为我的工作空间名字,pub_sub为我的ROS包名字。
注意:大家路径可能不一样,因为工作空间、ROS包的名字不一样。
虽然看不太懂,但是还是可以看到已经成功创建了name、age、height这三个字段的。
当我们使用C++想要发送自定义消息的时候,就可以include这个头文件了
下面咱们可以看一下适用与Python的中间文件。
路径:topic_communication/devel/lib/python3/dist-packages/pub_sub/msg
topic_communication为我的工作空间名字,pub_sub为我的ROS包名字
同样看不懂,但是还是能看到我们创建的字段。
Python调用的时候使用from import调用即可
C++使用自定义msg
和前面的案例一样,10HZ的频率发布消息。不同的是,消息的数据类型变了。
还是在之前的ROS包下的src文件夹内创建一个cpp文件。
1 | cd ~/topic_communication/src/pub_sub/src/ |
程序内容
1 |
|
接下来修改CMakeLists.txt
这里咱们多修改了一个内容
1 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) |
添加这个的原因是:
告诉Catkin构建系统在构建该节点之前先构建所依赖的目标。通俗解释就是
咱们前面定义了msg文件并且配置好了,如果还没有编译的话;此时把cpp文件也写好了,也需要编译,但是cpp文件是依赖msg的中间文件的,那么编译器会先编译cpp文件,在编译msg,这样就会有报错。
最后编译,刷新环境变量,运行pub_custom_msg节点,使用rostopic echo命令查看消息。
注意:当使用 rostopic echo命令报如下错误的时候,需要进入工作空间,并使用source ./devel/setup.bash刷新环境变量。
是因为没有把这个添加到~/.bashrc,当开新终端的时候,不知道自定义msg的路径,需要刷新一下。
接下来完成订阅方。
还是在之前的ROS包下的src文件夹内创建一个cpp文件。
1 | cd ~/topic_communication/src/pub_sub/src/ |
程序内容
1 |
|
和前面一样配置CMakeLists.txt
然后编译、刷新环境变量,运行A、B这两个节点
Python使用自定义msg
和前面的案例一样,10HZ的频率发布消息。
还是在之前的ROS包下的scripts文件夹内创建一个py文件。
1 | cd ~/topic_communication/src/pub_sub/scripts/ |
程序内容
1 | #! /usr/bin/env python |
接下来给该python文件添加可执行权限。
1 | chmod +x pub_custom_msg.py |
修改CMakeLists.txt,给catkin_install_python添加刚才的python文件
最后重新编译 刷新一下环境变量,运行节点
最后我们来写订阅者
还是在之前的ROS包下的scripts文件夹内创建一个py文件。
1 | cd ~/topic_communication/src/pub_sub/scripts/ |
程序内容
1 | #! /usr/bin/env python |
接下来给该python文件添加可执行权限。
1 | chmod +x sub_custom_msg.py |
修改CMakeLists.txt,给catkin_install_python添加刚才的python文件
编译、刷新环境变量,运行节点
- Title: ROS学习笔记(3):通信机制之话题通信
- Author: StarHui
- Created at : 2023-12-03 21:53:00
- Updated at : 2023-12-03 22:52:45
- Link: https://renyuhui0415.github.io/post/topic_communication.html
- License: This work is licensed under CC BY-NC-SA 4.0.