- ROS2机器人操作系统
- 前言
- 一、topic话题
- 1.通信模型
- 2.发布订阅模型
- 3.多对多通信
- 4.异步通信
- 5.消息结构
- 二、话题实例
- 1.Hello World话题通信
- 发布者代码解析
- 订阅者代码解析
- 2.话题命令行操作
- 总结
前言
节点实现了机器人各种各样的功能,但这些功能并不是独立的,之间会有千丝万缕的联系,其中最重要的一种联系方式就是话题,它是节点间传递数据的桥梁。
一、topic话题 1.通信模型
以两个机器人节点为例。A节点的功能是驱动相机这个硬件设备,获取得到相机拍摄的图像信息,B节点的功能是视频监控,将相机拍摄到的图像实时显示给用户查看。此时从节点A到节点B传递图像数据的方式,在ROS中,我们就称之为话题,它作为一个桥梁,实现了节点之间某一个方向上的数据传输。
从话题本身的实现角度来看,使用了基于DDS的发布/订阅模型,什么叫发布和订阅呢?
话题数据传输的特性是从一个节点到另外一个节点,发送数据的对象称之为发布者,接收数据的对象称之为订阅者,每一个话题都需要有一个名字,传输的数据也需要有固定的数据类型。
怎么说呢,发布和订阅的关系就类似于,我创建了一个发布者,我把消息发布了出去,你什么订阅我不管,我反正是把消息发送到了一个频道,你订阅了变可以查看我发布的消息,同样也可以很多个人订阅。你没有订阅当然是看不到的
大家再仔细想下这些可以订阅的东西,是不是并不是唯一的,我们每个人可以订阅很多公众号、报纸、杂志,这些公众号、报纸、杂志也可以被很多人订阅,没错,ROS里的话题也是一样,发布者和订阅者的数量并不是唯一的,可以称之为是多对多的通信模型。
因为话题是多对多的模型,发布控制指令的摇杆可以有一个,也可以有2个、3个,订阅控制指令的机器人可以有1个,也可以有2个、3个,大家可以想象一下这个画面,似乎还是挺魔性的,如果存在多个发送指令的节点,建议大家要注意区分优先级,不然机器人可能不知道该听谁的了。
4.异步通信话题通信还有一个特性,那就是异步,这个词可能有同学是第一次听说?所谓异步,只要是指发布者发出数据后,并不知道订阅者什么时候可以收到,这就叫做异步。
异步的特性也让话题更适合用于一些周期发布的数据,比如传感器的数据,运动控制的指令等等,如果某些逻辑性较强的指令,比如修改某一个参数,用话题传输就不太合适了。
最后,既然是数据传输,发布者和订阅者就得统一数据的描述格式,不能一个说英文,一个理解成了中文。在ROS中,话题通信数据的描述格式称之为消息,对应编程语言中数据结构的概念。比如这里的一个图像数据,就会包含图像的长宽像素值、每个像素的RGB等等,在ROS中都有标准定义。
消息是ROS中的一种接口定义方式,与编程语言无关,我们也可以通过.msg后缀的文件自行定义,有了这样的接口,各种节点就像积木块一样,通过各种各样的接口进行拼接,组成复杂的机器人系统。
二、话题实例 1.Hello World话题通信
启动第一个终端,运行话题的发布者节点:
ros2 run learning_topic topic_helloworld_pub
启动第二个终端,运行话题的订阅者节点:
ros2 run learning_topic topic_helloworld_sub发布者代码解析
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # **字符串消息类型**
"""
创建一个发布者节点
"""
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.pub = self.create_publisher(String, "chatter", 10) # **创建发布者对象(消息类型、话题名、队列长度)**
self.timer = self.create_timer(0.5, self.timer_callback) # 创建一个定时器(单位为秒的周期,定时执行的回调函数)
def timer_callback(self): # 创建定时器周期执行的回调函数
msg = String() # **创建一个String类型的消息对象**
msg.data = 'Hello World' # **填充消息对象中的消息数据**
self.pub.publish(msg) # 发布话题消息
self.get_logger().info('Publishing: "%s"' % msg.data) # 输出日志信息,提示已经完成话题发布
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = PublisherNode("topic_helloworld_pub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={
'console_scripts': [
'topic_helloworld_pub = learning_topic.topic_helloworld_pub:main',
],
},
流程总结
对以上程序进行分析,如果我们想要实现一个发布者,流程如下:
编程接口初始化
创建节点并初始化
创建发布者对象
创建并填充话题消息
发布话题消息
销毁节点并关闭接口
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # ROS2标准定义的String消息
"""
创建一个订阅者节点
"""
class SubscriberNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.sub = self.create_subscription(
String, "chatter", self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)
def listener_callback(self, msg): # 创建回调函数,执行收到话题消息后对数据的处理
self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = SubscriberNode("topic_helloworld_sub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={
'console_scripts': [
'topic_helloworld_pub = learning_topic.topic_helloworld_pub:main',
'topic_helloworld_sub = learning_topic.topic_helloworld_sub:main',
],
},
流程总结
对以上程序进行分析,如果我们想要实现一个订阅者,流程如下:
编程接口初始化
创建节点并初始化
创建订阅者对象
回调函数处理话题数据
销毁节点并关闭接口
同样也可以用机器视觉来解释,这里就不过多赘述了,可以去看古月的视频。
2.话题命令行操作ros2 topic list # 查看话题列表 ros2 topic info总结# 查看话题信息 ros2 topic hz # 查看话题发布频率 ros2 topic bw # 查看话题传输带宽 ros2 topic echo # 查看话题数据 ros2 topic pub # 发布话题消息
话题通信的特性是单向传输、异步通信,适合周期性的数据传递,对于一个复杂的机器人系统来讲,这种特性肯定无法满足所有数据传输的需求.



