教程 · 入门 · 阅读约 19 分钟
ROS 2 中的节点、话题、服务与动作
了解 ROS 2 节点之间如何通信、什么时候该用话题、服务或动作,以及如何用实用的命令行检查来查看机器人的通信图。
Robot Operating System 2(ROS 2)让彼此独立运行的程序交换带类型的数据。一台相机驱动、一个电机控制器、一个规划器、一个用户界面,可以是彼此分开的节点,通过 ROS 图谱连起来。重要的设计决策不只是消息类型,而是这次交互究竟是一段流、一次短请求,还是一个长时间目标。
本文保持发行版中立。命令和核心概念是稳定的,但安装细节和软件包版本应当跟随你实际使用的那个 ROS 2 发行版的文档。
一个节点只承担一项职责
节点是 ROS 图谱里的一个参与者。好的节点边界是跟着职责走的:一颗激光雷达驱动发布扫描,一个里程计节点估计运动,一个导航器接受目标。不要把每个函数都做成一个节点——进程与通信开销只有当那条边界改善了复用、隔离、部署或归属时才划得来。
好的名字让图谱读得懂。用描述性的节点名和接口名,比如 /front_lidar、/wheel_odometry 和 /cmd_vel。命名空间则让两台机器人跑等价图谱时互不冲突。
在改动一个正在运行的系统之前先检查它:
ros2 node list
ros2 node info /wheel_odometry
ros2 topic list -t
ros2 service list -t
ros2 action list -t
node info 揭示连到那个节点上的发布者、订阅者、服务 server/client 和动作。它常常是找一个缺失或命名错误的接口最快的办法。
连续流用话题
话题是异步的发布/订阅通信。发布者发出带类型的消息而不等一个直接回复,任意多个订阅者都可以接收。话题适合随时间变化的数据:
- 相机图像和激光雷达扫描;
- 关节态和轮子里程计;
- 电池状态;
- 指令速度;
- 诊断事件。
从命令行检查一段流:
ros2 topic echo /scan --once
ros2 topic hz /scan
ros2 topic info /scan --verbose
消息类型只是契约的一部分。坐标系、单位、时间戳和协方差与字段名同样重要。如果订阅者不知道那个值是 map 里的米还是某个相机坐标系里的毫米,它就无法安全地解释这个位置。
服务质量(QoS)设置影响投递。传感器流通常更愿意要新数据而不是重传过期样本,而配置类状态可能需要可靠或瞬态本地的行为。发布者与订阅者 QoS 策略不兼容时,它们可能出现在图谱里却不交换任何数据。
短请求应答用服务
服务客户端发出一个请求,等一个服务器响应。服务适合有确定答案的快速操作:
- 读或更新一份配置;
- 重置里程计;
- 清空一张地图;
- 问某个部件是否就绪。
它们对连续传感器数据、或者可能要几秒钟的动作是不合适的。一个等待慢电机动作的同步调用会阻塞调用方,而且不提供任何标准的进度反馈或取消能力。
这样检查和调用一个服务:
ros2 service type /reset_odometry
ros2 interface show example_interfaces/srv/Trigger
ros2 service call /reset_odometry example_interfaces/srv/Trigger "{}"
最后那条命令里的确切类型必须与服务器一致。把回复设计成既报告传输成功、又报告有意义的操作状态;收到了回复并不保证被请求的那个硬件动作是可执行的。
长时间目标用动作
动作建模一个需要时间的目标。它支持目标接受、周期性反馈、最终结果,以及取消或抢占。导航到某个位姿、让机械臂走完一段轨迹,都是天然的动作。
这个交互有三个语义部分:
- **目标:**客户端想要什么,比如一个目标位姿。
- **反馈:**执行期间的进展,比如剩余距离。
- **结果:**终局结果和任何返回的数据。
动作服务器应当迅速响应取消,并把硬件留在一个有文档说明的状态。客户端应当处理拒绝、取消、中止和成功,而不是把每一个终局结果都当成完成。
从命令行:
ros2 action info /navigate_to_pose
ros2 action send_goal /navigate_to_pose <action_type> '<goal_yaml>' --feedback
把占位符换成运行中的图谱所报告的类型和字段。用这条命令来检查行为,而不是把它当成一个硬编码的生产接口。
用一张决策表来选
| 需要 | 接口 |
|---|---|
| 重复的值,而最新样本最重要 | 话题 |
| 一次快速查询或配置改动,只要一个回复 | 服务 |
| 长时间操作,带进度和取消 | 动作 |
话题不会自动就“快”,服务也不会自动就“可靠”。时序和投递取决于 QoS、中间件、执行器、负载和网络。先按交互语义来选。
一个行为可以把三种都用上。一个导航节点订阅扫描和里程计话题、接受一个导航动作、并暴露一个用来清空代价地图的短服务。边界依然清晰,因为每个接口回答的是不同类型的问题。
设计一张小型移动机器人图谱
设想一台差速机器人:
/encoder_driver发布轮子计数。/odometry订阅计数、发布/odom,并广播odom → base_link。/range_driver发布距离测量。/safety_controller订阅测距并限制/cmd_vel。/navigator接受一个运动动作并发布期望速度。
不要让两个无关的节点在没有仲裁的情况下都去指挥电机。把最终执行器指令的归属交给某一个部件,或者把优先级规则显式写出来。
标准 ROS 消息类型的语义合适时就用它们。只有当数据契约确实是项目特有的,才需要自定义接口——而不是仅仅为了给已有字段改个名字。
逐层调试图谱
数据缺失时:
- 确认两个节点都出现在
ros2 node list里。 - 确认接口名和类型。
- 检查发布者/订阅者数量。
- 比较 QoS 策略。
echo一条消息,检查时间戳和坐标系 id。- 查日志和执行器负载。
动作永远不结束时,要区分“目标没被接受”“服务器不再产生反馈”和“客户端无视了结果”。服务卡住时,检查服务器回调是不是被阻塞在等待某件本该做成动作的活上。
避开那些常见的架构错误
- **一切都用话题:**调用方无法知道一次性的请求是否成功。
- **运动用服务:**长活缺少进度和取消语义。
- **传感器数据默认用可靠的深队列:**处理跟不上时过期样本会堆积起来。
- **坐标系和单位约定没有文档:**带类型的消息在语义上仍然是含糊的。
- **一个巨型节点拥有每个子系统:**测试和故障隔离都消失了。
- **几十个微节点包着普通函数:**图谱变得更难懂,却没有增加有用的边界。
机械臂模拟器提供了一个具体的、类似动作的任务:请求一个目标、观察运动、报告到不了或受约束的结果。接着读ROS 2 TF2 坐标系,让每个节点的几何保持一致。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读