教程 · 入门 · 阅读约 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

把占位符换成运行中的图谱所报告的类型和字段。用这条命令来检查行为,而不是把它当成一个硬编码的生产接口。

用一张决策表来选

三张并排的时序图。话题:发布者向订阅者发出六条消息,完全没有回复。服务:客户端发出一个请求,在服务器工作期间被标为阻塞,然后收到一个应答。动作:客户端发出目标,收到接受,然后收到三条反馈消息,然后发出取消并收到结果。
下表命名的同样三种交换,画成它们随时间在做什么:一段没有回复的流、一个会把调用方阻塞到被回答为止的问题,以及一个你可以看着、也可以叫停的目标。 下载 SVG
需要 接口
重复的值,而最新样本最重要 话题
一次快速查询或配置改动,只要一个回复 服务
长时间操作,带进度和取消 动作

话题不会自动就“快”,服务也不会自动就“可靠”。时序和投递取决于 QoS、中间件、执行器、负载和网络。先按交互语义来选。

一个行为可以把三种都用上。一个导航节点订阅扫描和里程计话题、接受一个导航动作、并暴露一个用来清空代价地图的短服务。边界依然清晰,因为每个接口回答的是不同类型的问题。

设计一张小型移动机器人图谱

设想一台差速机器人:

  1. /encoder_driver 发布轮子计数。
  2. /odometry 订阅计数、发布 /odom,并广播 odom → base_link。
  3. /range_driver 发布距离测量。
  4. /safety_controller 订阅测距并限制 /cmd_vel。
  5. /navigator 接受一个运动动作并发布期望速度。

不要让两个无关的节点在没有仲裁的情况下都去指挥电机。把最终执行器指令的归属交给某一个部件,或者把优先级规则显式写出来。

标准 ROS 消息类型的语义合适时就用它们。只有当数据契约确实是项目特有的,才需要自定义接口——而不是仅仅为了给已有字段改个名字。

逐层调试图谱

数据缺失时:

  1. 确认两个节点都出现在 ros2 node list 里。
  2. 确认接口名和类型。
  3. 检查发布者/订阅者数量。
  4. 比较 QoS 策略。
  5. echo 一条消息,检查时间戳和坐标系 id。
  6. 查日志和执行器负载。

动作永远不结束时,要区分“目标没被接受”“服务器不再产生反馈”和“客户端无视了结果”。服务卡住时,检查服务器回调是不是被阻塞在等待某件本该做成动作的活上。

避开那些常见的架构错误

  • **一切都用话题:**调用方无法知道一次性的请求是否成功。
  • **运动用服务:**长活缺少进度和取消语义。
  • **传感器数据默认用可靠的深队列:**处理跟不上时过期样本会堆积起来。
  • **坐标系和单位约定没有文档:**带类型的消息在语义上仍然是含糊的。
  • **一个巨型节点拥有每个子系统:**测试和故障隔离都消失了。
  • **几十个微节点包着普通函数:**图谱变得更难懂,却没有增加有用的边界。

机械臂模拟器提供了一个具体的、类似动作的任务:请求一个目标、观察运动、报告到不了或受约束的结果。接着读ROS 2 TF2 坐标系,让每个节点的几何保持一致。

探索关联图谱

本教程所属的搭建

包含本教程的项目和学习路线。

延伸阅读

参考资料