2、ROS2基础:节点、话题、服务、动作,以及DDS通信机制
好,咱们正式开始聊ROS2。说实话,我第一次接触ROS2的时候,心里是有点抵触的。毕竟ROS1用了那么多年,顺手得很。但后来在项目里被ROS1的通信瓶颈坑过几次之后,我才真正理解了ROS2为什么要大改。这一章,我就带你把这些最核心的概念捋一遍。
2.1 节点(Node)—— 系统的“小工兵”
节点是什么?说白了,就是一个独立的可执行程序。每个节点只干一件事,比如控制激光雷达、处理图像、规划路径。我习惯把节点想象成一个“小工兵”,各司其职,互不干扰。
在ROS2里,启动一个节点很简单:
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
super().__init__('my_first_node')
self.get_logger().info('节点已启动!')
def main(args=None):
rclpy.init(args=args)
node = MyNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
嗯,这里要注意:rclpy.spin() 这个函数会让节点一直运行,直到你按 Ctrl+C。我曾经在调试时忘了加这一行,结果节点启动完就退出了,找了好久才发现。
2.2 话题(Topic)—— 异步的“广播站”
话题是ROS2里最常用的通信方式。它是异步的,发布者只管发,订阅者只管收,两者不需要知道对方的存在。你想想看,这就像广播电台——主持人只管播,听众只管听,谁也不认识谁。
发布者的代码大概长这样:
self.publisher_ = self.create_publisher(String, 'topic_name', 10)
msg = String()
msg.data = 'Hello, ROS2!'
self.publisher_.publish(msg)
订阅者呢?
self.subscription = self.create_subscription(
String, 'topic_name', self.listener_callback, 10)
那个数字“10”是队列深度。我在项目中遇到过一个问题:如果订阅者处理太慢,队列满了,新消息就会丢。所以,队列深度要根据你的处理速度来调,不是越大越好。
2.3 服务(Service)—— 同步的“一问一答”
话题是单向广播,服务则是双向的。客户端发请求,服务端回响应。这就像你打电话问客服:“我的订单到哪了?”客服查完告诉你结果。整个过程是同步的,你得等着。
服务端定义:
from example_interfaces.srv import AddTwoInts
def add_callback(request, response):
response.sum = request.a + request.b
return response
self.srv = self.create_service(AddTwoInts, 'add_two_ints', add_callback)
客户端调用:
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
req = AddTwoInts.Request()
req.a = 3
req.b = 5
future = self.cli.call_async(req)
rclpy.spin_until_future_complete(self, future)
2.4 动作(Action)—— 带反馈的“长任务”
动作是ROS2里最复杂的通信方式。它适合那些需要长时间执行、并且需要中途反馈的任务。比如让机器人走到某个点——你不仅想知道它最终到了没,还想知道它走到哪了。
动作由三部分组成:目标(Goal)、反馈(Feedback)、结果(Result)。
动作服务端示例:
from example_interfaces.action import Fibonacci
def execute_callback(self, goal_handle):
self.get_logger().info('开始执行...')
feedback_msg = Fibonacci.Feedback()
feedback_msg.sequence = [0, 1]
for i in range(1, goal_handle.request.order):
feedback_msg.sequence.append(
feedback_msg.sequence[i] + feedback_msg.sequence[i-1])
goal_handle.publish_feedback(feedback_msg)
goal_handle.succeed()
result = Fibonacci.Result()
result.sequence = feedback_msg.sequence
return result
self._action_server = ActionServer(
self, Fibonacci, 'fibonacci', execute_callback)
动作客户端:
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
goal_msg = Fibonacci.Goal()
goal_msg.order = 10
self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)
2.5 DDS通信机制——ROS2的“隐形引擎”
ROS2和ROS1最大的不同,就是底层通信换成了DDS(Data Distribution Service)。DDS是什么?说白了,它是一个去中心化的实时数据分发标准。
ROS1用的是TCPROS/UDPROS,中心化架构,有一个Master节点。一旦Master挂了,整个系统就瘫痪了。我当年在园区做无人车测试时,就遇到过Master节点因为网络波动宕机,所有节点瞬间失联,车直接停在路中间...那场面,别提多尴尬了。
ROS2用DDS,没有中心节点。每个节点都直接和DDS的“全局数据空间”通信。DDS负责发现节点、管理QoS(服务质量)、保证可靠性。
DDS的几个关键特性:
- 去中心化:没有单点故障,节点之间点对点通信
- QoS策略:你可以控制消息的可靠性、持久性、生命周期等
- 实时性:支持实时系统,延迟低至微秒级
- 可扩展:从嵌入式设备到云端都能跑
ROS2默认的DDS实现是Fast DDS(以前叫Fast RTPS)。你也可以换成Cyclone DDS或Connext DDS。我个人习惯用Fast DDS,开源、轻量、社区活跃。
下面这张图展示了ROS2的通信架构:
从这张图你能看到,ROS2的通信是分层的。最上层是应用层,你写节点、发话题、调服务都在这里。中间是RCL和RMW,它们负责把ROS2的API翻译成DDS能理解的语言。最底层就是DDS,它才是真正干活的。
2.6 小结
这一章我们聊了ROS2的四个核心通信方式:
- 节点:系统的最小单元,各干各的活
- 话题:异步广播,适合传感器数据流
- 服务:同步请求-响应,适合一次性查询
- 动作:带反馈的长任务,适合机器人控制
- DDS:底层通信引擎,去中心化、可配置QoS
说实话,这些概念光看文字可能觉得抽象。我建议你动手跑几个示例,把代码敲一遍。只有亲手写过,才能真正理解它们之间的区别。下一章我们会深入聊DDS的QoS策略,那才是ROS2真正强大的地方。
公众号:蓝海资料掘金营,微信deep3321