2、ROS2核心概念详解:深入理解节点、话题、服务、动作的通信机制
各位同学,欢迎来到第二章。
上一章我们把ROS2装好了,也跑通了小乌龟。但说实话,那只是「知其然」。这一章,我们来「知其所以然」。ROS2的四个核心概念——节点、话题、服务、动作,是搭建任何机器人平台的基石。我见过不少新手,代码能跑,但一问到「为什么用话题不用服务」就卡壳了。嗯,今天我们就彻底把它掰扯清楚。
2.1 节点:机器人的「器官」
节点,说白了就是ROS2里的一个独立进程。你可以把它想象成机器人的一个器官:眼睛是一个节点,负责看路;手臂是一个节点,负责抓取;大脑是一个节点,负责决策。
每个节点只干一件事,并且干好。这是ROS2的设计哲学——单一职责。我刚开始做项目时,喜欢把所有代码塞进一个文件里,结果调试起来简直噩梦。后来我学乖了,一个传感器一个节点,一个算法一个节点。出了问题,直接定位到那个节点,重启它就行,其他部分不受影响。
创建一个节点很简单,Python里这样写:
import rclpy
from rclpy.node import Node
class MyRobotNode(Node):
def __init__(self):
super().__init__('my_robot_node')
self.get_logger().info('节点已启动!')
def main(args=None):
rclpy.init(args=args)
node = MyRobotNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
注意那个 rclpy.spin(node),它让节点一直活着,等待消息或定时器触发。如果你忘了写这行,节点会瞬间跑完然后退出——我曾经犯过这个错,找了半天bug。
2.2 话题:一对多的「广播」
话题是ROS2里最常用的通信方式。它是异步的、一对多的。发布者只管发,订阅者只管收,双方不需要知道对方的存在。
你想想看,这像不像电台广播?电台不知道谁在听,听众也不知道电台在哪。但信息就这么传过去了。
实际项目中,话题最适合传输连续的数据流:
- 摄像头图像(/camera/image_raw)
- 激光雷达点云(/scan)
- 里程计数据(/odom)
- 电机速度指令(/cmd_vel)
我在做AGV小车时,激光雷达节点以10Hz的频率发布扫描数据。导航节点订阅它,做SLAM建图。电机驱动节点也订阅它,做避障。你看,一个话题被多个节点消费,这就是话题的优势。
发布话题的代码示例:
class Talker(Node):
def __init__(self):
super().__init__('talker')
self.publisher_ = self.create_publisher(String, 'chatter', 10)
timer_period = 0.5 # 0.5秒发一次
self.timer = self.create_timer(timer_period, self.timer_callback)
def timer_callback(self):
msg = String()
msg.data = 'Hello, ROS2!'
self.publisher_.publish(msg)
self.get_logger().info('发布了: "%s"' % msg.data)
2.3 服务:一问一答的「请求-响应」
话题是广播,服务就是打电话。你打过去,对方接,然后给你一个答复。它是同步的、一对一的。
什么时候用服务?当你需要执行一个操作并等待结果时。比如:
- 请求机器人拍照并返回图片
- 请求机械臂移动到某个位置,返回是否成功
- 请求地图服务器保存当前地图
我记得有一次做巡检机器人,需要远程触发「开始录制视频」的功能。如果用话题,你得一直监听一个状态标志,很麻烦。用服务就简单了:客户端发一个请求,服务端开始录制,返回「录制成功」。干净利落。
服务定义文件(.srv)长这样:
# AddTwoInts.srv
int64 a
int64 b
---
int64 sum
服务端代码:
class AddTwoIntsServer(Node):
def __init__(self):
super().__init__('add_two_ints_server')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
def add_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('收到请求: %d + %d = %d' % (request.a, request.b, response.sum))
return response
2.4 动作:带反馈的「长期任务」
动作是ROS2里最强大的通信方式。它结合了话题和服务的特点:
- 像服务一样,有目标请求和最终结果
- 像话题一样,可以在执行过程中持续反馈进度
- 还支持取消任务
什么时候用动作?当任务需要较长时间执行,并且你想知道进度时。比如:
- 导航到某个目标点(过程中持续反馈当前位置)
- 机械臂执行一个复杂轨迹(反馈当前关节角度)
- 无人机起飞到指定高度(反馈当前高度)
动作的定义文件(.action)包含三部分:
# NavigateToPose.action
# 目标
geometry_msgs/PoseStamped target_pose
---
# 结果
bool success
string message
---
# 反馈
geometry_msgs/PoseStamped current_pose
float32 distance_remaining
我做过一个送餐机器人,用动作来实现「送餐到3号桌」。客户端发送目标点,服务端(导航节点)开始规划路径并移动。过程中,客户端实时收到反馈:当前在哪、还剩多远。如果客人取消订单,客户端可以随时取消动作,机器人就会原地停下。这种体验,用话题和服务都很难实现得这么优雅。
2.5 如何选择?一张表说清楚
很多同学会问:我到底该用哪个?我整理了一张表,你照着选就行:
| 通信方式 | 通信模式 | 是否同步 | 是否支持反馈 | 典型场景 |
|---|---|---|---|---|
| 话题 | 发布/订阅 | 异步 | 否 | 传感器数据、控制指令 |
| 服务 | 请求/响应 | 同步 | 否 | 触发操作、查询状态 |
| 动作 | 目标/结果/反馈 | 异步 | 是 | 导航、机械臂运动 |
简单粗暴的选型原则:
- 数据一直在变,多个节点需要 → 话题
- 偶尔调用一次,需要立即知道结果 → 服务
- 任务跑好几秒,想看进度,还能中途取消 → 动作
2.6 实际项目中的组合应用
一个真实的机器人项目,往往是三种通信方式混用的。拿我做的巡检机器人举例:
- 话题:激光雷达以20Hz发布扫描数据;摄像头以15Hz发布图像;电机驱动订阅/cmd_vel话题接收速度指令
- 服务:远程控制台调用/start_recording服务开始录像;调用/get_battery服务查询电量
- 动作:调度系统发送/navigate_to目标点,导航节点执行路径规划并实时反馈位置
你看,各司其职,互不干扰。这就是ROS2的设计魅力。
核心要点回顾:
- 节点是基本单元,一个节点只做一件事
- 话题用于连续数据流,一对多异步通信
- 服务用于请求-响应,同步调用
- 动作用于长期任务,带进度反馈和取消功能
- 实际项目中三者配合使用
好了,这一章的内容就到这里。下一章我们会动手写一个完整的节点,把话题、服务、动作都用上。到时候你会发现,理解了这些概念,写代码就像搭积木一样简单。