3. 核心概念速通:节点、话题、服务、动作、参数详解

好,咱们直接进入正题。ROS2 的五个核心概念——节点、话题、服务、动作、参数——说白了就是机器人程序的五种“沟通方式”。我刚开始学的时候,也觉得概念多,容易混。但只要你搞懂它们各自解决什么问题,就豁然开朗了。

节点 (Node) 话题 (Topic) 服务 (Service) 动作 (Action) 参数 (Parameter) 所有通信都围绕节点展开,节点是 ROS2 系统的最小执行单元

3.1 节点:系统的“小工人”

节点,你可以把它想象成一个独立工作的小工人。每个节点只负责一件事,比如控制电机、处理激光雷达数据、或者发布里程计信息。

我个人习惯,一个机器人项目里至少会有三四个节点。为什么?因为拆开了好调试。你想想看,如果所有代码都写在一个文件里,出问题了找 bug 找到哭。

核心要点:节点是 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()

我的经验:节点命名要规范。我曾经在一个项目里用了 "node1"、"node2" 这种名字,结果三个月后自己都看不懂哪个是干嘛的。建议用 "功能_模块" 的格式,比如 "lidar_processor"、"motor_controller"。

3.2 话题:一对多的“广播”

话题是 ROS2 里最常用的通信方式。它就像电台广播——一个节点发布消息,其他节点可以订阅收听。发布者和订阅者不需要知道对方的存在,这就是所谓的“解耦”。

举个例子,激光雷达节点不断发布扫描数据,里程计节点订阅它来做定位,路径规划节点也订阅它来做避障。大家各干各的,互不干扰。

# 发布者
self.publisher_ = self.create_publisher(String, 'chatter', 10)

# 订阅者
self.subscription = self.create_subscription(
    String, 'chatter', self.listener_callback, 10)

避坑指南:我曾经遇到过一个问题——话题名拼写错误,结果两个节点怎么都通信不上。找了半天才发现是 "chatter" 写成了 "chater"。ROS2 不会报错,只会默默等待。所以,话题名一定要仔细核对。

话题的通信模型是异步的。发布者只管发,不管有没有人收。订阅者只管收,不管是谁发的。这种模式特别适合传感器数据这种高频、实时的信息。

3.3 服务:一问一答的“请求”

服务和话题不一样。话题是“我说你听”,服务是“我问你答”。它是一种同步的通信方式,客户端发送请求,服务器处理并返回响应。

什么时候用服务?比如你让机械臂抓取一个物体,你需要知道它有没有抓成功。这时候用服务就很合适——你发请求,它回结果。

特性 话题 (Topic) 服务 (Service)
通信模式 异步(发布/订阅) 同步(请求/响应)
数据流向 单向 双向
典型场景 传感器数据、状态信息 触发动作、查询状态
超时处理 无(持续监听) 有(客户端等待超时)
# 服务端
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.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)

我的建议:服务调用要设置超时。我见过一个项目,客户端调用服务后一直死等,结果服务端挂了,整个系统卡死。加个超时判断,几秒钟没响应就重试或者报错,这才是健壮的做法。

3.4 动作:带反馈的“长任务”

动作是 ROS2 里最复杂的通信方式,但也是最强大的。它专门用来处理那些需要很长时间才能完成的任务,比如让机器人走到某个点、让机械臂执行一个轨迹。

动作的特点是什么?它可以给你实时反馈。比如机器人导航,你可以知道它走到哪了、还剩多少距离、有没有遇到障碍物。任务完成后,还会给你一个最终结果。

说白了,动作 = 服务 + 话题。它用服务来发起任务和获取结果,用话题来发布反馈信息。

# 动作服务器
self._action_server = ActionServer(
    self, Fibonacci, 'fibonacci', self.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)

核心区别:服务适合“快问快答”(比如查询温度),动作适合“慢工出细活”(比如导航到目标点)。选错了通信方式,代码会变得很别扭。

3.5 参数:节点的“配置开关”

参数,说白了就是节点的配置项。比如 PID 控制器的 Kp 值、激光雷达的扫描频率、摄像头的曝光时间。这些值可以在节点启动时设置,也可以在运行时动态修改。

我刚开始做项目时,把参数都硬编码在代码里。后来发现每次调参都要重新编译,烦死了。改用参数后,改个值只需要一条命令:

# 声明参数
self.declare_parameter('my_parameter', 'default_value')

# 获取参数
param = self.get_parameter('my_parameter').value

# 命令行设置参数
ros2 param set /my_node my_parameter new_value

避坑指南:我曾经在参数里存了一个很大的列表,结果每次读取都卡顿。后来才意识到,参数适合存简单的标量值,复杂的数据结构还是用话题或者配置文件吧。

参数还有一个好处——可以持久化。你把参数保存到 YAML 文件里,下次启动时自动加载,省得每次都要手动设置。

3.6 如何选择?一张表说清楚

好,五个概念都讲完了。你可能会问:实际项目中到底用哪个?我整理了一张表,帮你快速决策:

场景 推荐方式 原因
传感器数据持续发布 话题 异步、高频、一对多
查询当前状态 服务 一问一答,简单直接
执行复杂任务 动作 需要反馈和结果
调整配置参数 参数 运行时修改,无需重启
节点间同步调用 服务 等待结果后再继续

嗯,到这里,ROS2 的五个核心概念就讲完了。记住一句话:节点是骨架,话题、服务、动作、参数是血肉。搞懂它们,你的 ROS2 之路就迈出了最坚实的一步。