3. ROS2核心概念:节点、话题、服务、动作、参数详解
说实话,刚接触ROS2的时候,我也被这些概念搞得有点晕。节点、话题、服务、动作、参数……它们到底是什么关系?别急,我带你一个一个捋清楚。
一句话总结:ROS2是一个分布式通信框架。节点是“人”,话题是“广播”,服务是“问答”,动作是“长任务”,参数是“配置”。
3.1 节点(Node)—— 系统的“人”
节点是ROS2中最基本的执行单元。每个节点就是一个独立的进程,负责完成某一项具体的工作。
我习惯把节点想象成团队里的成员。有人负责感知(摄像头节点),有人负责决策(导航节点),有人负责执行(电机控制节点)。它们各司其职,通过通信机制协作。
我的经验:刚开始写ROS2程序时,我总喜欢把所有功能塞进一个节点。后来项目越来越复杂,一个节点崩溃就全挂了。现在我坚持“单一职责原则”——一个节点只做一件事,做好它。
创建一个节点很简单:
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()
3.2 话题(Topic)—— 异步广播通信
话题是ROS2中最常用的通信方式。它采用发布/订阅模式,数据流向是单向的。
举个例子:激光雷达节点不断发布扫描数据,导航节点订阅这些数据来做定位。发布者不知道谁在收,订阅者也不知道谁在发。这种解耦设计,说白了就是“你只管发,我负责收”。
关键特点:
- 一对多通信:一个发布者,多个订阅者
- 异步:发布者不等待订阅者处理
- 无反馈:发布者不知道数据是否被接收
发布话题的代码:
self.publisher_ = self.create_publisher(String, 'chatter', 10)
msg = String()
msg.data = 'Hello, ROS2!'
self.publisher_.publish(msg)
订阅话题的代码:
self.subscription = self.create_subscription(
String, 'chatter', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info(f'收到: {msg.data}')
避坑指南:我曾经在项目中把队列大小设成了1,结果高频数据频繁丢包。建议根据数据频率合理设置队列大小——低频数据设小一点,高频数据设大一些。
3.3 服务(Service)—— 同步请求/响应
服务是同步通信机制。客户端发送请求,服务端处理并返回响应。这就像你问问题,对方必须回答你。
什么时候用服务?比如你让机械臂去抓一个物体,你需要知道它是否抓成功了。这时候话题就不够用了,你需要服务。
定义服务接口:
# AddTwoInts.srv
int64 a
int64 b
---
int64 sum
服务端实现:
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
def add_callback(self, request, response):
response.sum = request.a + request.b
return response
我的建议:服务适合短时间完成的操作。如果操作耗时较长(比如几秒钟),服务会阻塞客户端。这时候就该用动作了。
3.4 动作(Action)—— 带反馈的长任务
动作是ROS2中最复杂的通信机制。它结合了话题和服务的特点:像服务一样有目标请求和结果响应,又像话题一样能持续反馈进度。
你想想看,让机器人从A点导航到B点——你需要发送目标位置(服务),途中需要知道当前位置(话题),到达后需要知道结果(服务)。动作把这三者完美整合了。
动作的三个阶段:
- 目标(Goal):客户端发送任务目标
- 反馈(Feedback):服务端持续报告进度
- 结果(Result):任务完成后的最终结果
我记得有一次做仓储机器人项目,需要控制机器人去货架取货。用服务的话,取货过程中客户端完全卡死。换成动作后,既能实时看到机器人走到哪了,又能随时取消任务。嗯,这才是正确的打开方式。
3.5 参数(Parameter)—— 运行时配置
参数是节点的配置项。你可以动态修改它们,而不需要重新编译代码。
比如机器人的最大速度、PID控制器的系数、传感器的采样频率……这些都可以做成参数。
声明和使用参数:
self.declare_parameter('max_speed', 1.0)
self.declare_parameter('robot_name', 'turtlebot')
speed = self.get_parameter('max_speed').value
name = self.get_parameter('robot_name').value
在启动时设置参数:
ros2 run my_package my_node --ros-args -p max_speed:=0.5 -p robot_name:=my_robot
实用技巧:我习惯把所有可调参数集中到一个YAML文件中。这样调试时改参数特别方便,不用到处翻代码。
3.6 五种机制的对比
| 特性 | 话题 | 服务 | 动作 | 参数 |
|---|---|---|---|---|
| 通信模式 | 发布/订阅 | 请求/响应 | 目标+反馈+结果 | 读取/设置 |
| 同步/异步 | 异步 | 同步 | 异步 | 同步 |
| 数据流向 | 单向 | 双向 | 双向 | 双向 |
| 适用场景 | 传感器数据、状态广播 | 短时查询、指令 | 长时任务、导航 | 配置管理 |
| 是否支持反馈 | 否 | 否 | 是 | 否 |
重要提醒:选错通信机制是新手最常见的错误。记住这个原则——数据流用话题,短请求用服务,长任务用动作,配置用参数。选对了,后面少踩很多坑。
好了,这五个核心概念就讲完了。它们就像ROS2的五根支柱,撑起了整个机器人应用。你在写代码的时候,多想想“这个场景该用哪个”,慢慢就会形成直觉。