topic 在ros 中的作用
节点实现了机器人各种各样的功能,但这些功能并不是独立的,之间会有千丝万缕的联系,其中最重要的一种联系方式就是话题,它是节点间传递数据的桥梁。
大家可以想一下,这两个节点是不是必然存在某种关系?没错,节点A要将获取的图像数据传输给节点B,有了数据,节点B才能做这样可视化的渲染。
此时从节点A到节点B传递图像数据的方式,在ROS中,我们就称之为话题,它作为一个桥梁,实现了节点之间某一个方向上的数据传输。
发布/订阅模型
从话题本身的实现角度来看,使用了基于DDS的发布/订阅模型,什么叫发布和订阅呢?
话题数据传输的特性是从一个节点到另外一个节点,发送数据的对象称之为发布者,接收数据的对象称之为订阅者,每一个话题都需要有一个名字,传输的数据也需要有固定的数据类型。
多对多通信
大家再仔细想下这些可以订阅的东西,是不是并不是唯一的,我们每个人可以订阅很多公众号、报纸、杂志,这些公众号、报纸、杂志也可以被很多人订阅,没错,ROS里的话题也是一样,发布者和订阅者的数量并不是唯一的,可以称之为是多对多的通信模型。
因为话题是多对多的模型,发布控制指令的摇杆可以有一个,也可以有2个、3个,订阅控制指令的机器人可以有1个,也可以有2个、3个,大家可以想象一下这个画面,似乎还是挺魔性的,如果存在多个发送指令的节点,建议大家要注意区分优先级,不然机器人可能不知道该听谁的了。
示例:自定义话题消息发布和订阅
了解了话题的基本原理,接下来我们就要开始编写代码啦。
还是从Hello World例程开始,我们来创建一个发布者,发布话题“chatter”,周期发送“Hello World”这个字符串,消息类型是ROS中标准定义的String,再创建一个订阅者,订阅“chatter”这个话题,从而接收到“Hello World”这个字符串。
如果我们想要实现一个发布者和订阅者,主要流程如下:
- 创建功能包
- 在功能包下新增发布者代码
- 在功能包下新增订阅者代码
- 修改功能包下setup.py 的脚本文件
- 编译
- 运行测试
创建功能包
ros2 pkg create --build-type ament_python learning_topic
用vccode 打开功能包目录结构如下
在learning_topic 目录下新增 helloworld_pub.py, 代码如下
创建一个topic 为hello 的话题,并发送hello
#!/usr/bin/env python3
# -*- coding: utf-8 -*-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, "hello", 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("helloworld_pub") # 创建ROS2节点对象并进行初始化rclpy.spin(node) # 循环等待ROS2退出node.destroy_node() # 销毁节点对象rclpy.shutdown() # 关闭ROS2 Python接口
创建一个监听 helloworld_sub.py
代码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-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, "hello", 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("helloworld_sub") # 创建ROS2节点对象并进行初始化rclpy.spin(node) # 循环等待ROS2退出node.destroy_node() # 销毁节点对象rclpy.shutdown() # 关闭ROS2 Python接口
修改setup.py
'console_scripts': ['helloworld_pub = learning_topic.helloworld_pub:main','helloworld_sub = learning_topic.helloworld_sub:main',],
编译
回到项目空间目录
指定learning_topic 编译
colcon build --packages-select learning_topic
开始编译
编译成功
设置环境变量
source install/local_setup.bash
运行测试:
ros2 run learning_topic helloworld_pub
新建命令窗口运行订阅
ros2 run learning_topic helloworld_sub
监听成功