2. ROS2核心概念:节点、话题、服务、动作、参数,以及ROS2的通信模型

好,咱们进入正题。这一章我打算把ROS2的五个核心概念掰开揉碎了讲清楚。你想想看,搞自动驾驶中间件选型,如果连这些基础都不熟,后面根本没法聊。

我个人习惯把ROS2的通信模型比作一个「消息工厂」。节点是工人,话题是传送带,服务是临时工,动作是长期项目,参数就是工厂的配置手册。嗯,这个比喻不一定完美,但能帮你快速建立画面感。

2.1 节点:自动驾驶的「细胞」

节点是ROS2里最小的可执行单元。说白了,每个节点只干一件事——比如激光雷达节点只管发点云,规划节点只管算轨迹。

我在项目中遇到过一个问题:有人把感知、定位、规划全写在一个节点里。结果呢?改一个模块就得重新编译整个工程,调试起来想死的心都有。所以我的建议是:一个节点只做一件事,做好一件事。

节点设计原则:

  • 每个节点有唯一的名称(别重名,否则节点管理器会报错)
  • 节点之间通过话题/服务/动作通信,不直接调用
  • 节点可以动态启停,不影响其他节点
// 创建一个节点的典型写法(C++)
#include "rclcpp/rclcpp.hpp"

class MyNode : public rclcpp::Node {
public:
    MyNode() : Node("my_node") {
        RCLCPP_INFO(this->get_logger(), "节点已启动");
    }
};

int main(int argc, char **argv) {
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<MyNode>());
    rclcpp::shutdown();
    return 0;
}

2.2 话题:异步的「广播站」

话题是ROS2里最常用的通信方式。发布者只管发,订阅者只管收,双方不需要知道对方的存在。这就像广播电台——你放你的歌,我听我的,互不干扰。

为什么会这样设计?因为自动驾驶里很多数据是周期性的,比如摄像头以30fps发图像,激光雷达以10Hz发点云。如果用同步通信,整个系统会被拖死。

我的经验:话题的QoS(服务质量)设置很关键。比如激光雷达的点云数据,丢几帧没关系,但延迟不能高。所以我会用SensorDataQoS。而控制指令必须可靠传输,那就用ReliableQoS。

# 发布话题的Python示例
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.pub = self.create_publisher(String, 'chatter', 10)
        self.timer = self.create_timer(1.0, self.timer_callback)

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello, ROS2!'
        self.pub.publish(msg)
        self.get_logger().info(f'发布: {msg.data}')

2.3 服务:同步的「一问一答」

服务和话题最大的区别是:服务是同步的。客户端发请求,服务端处理完再返回响应。这就像你去柜台办事——你递材料,工作人员处理完,把结果给你。

我曾经在项目中用话题来实现「查询地图数据」的功能,结果发现订阅者收到的是旧数据。后来改成服务,每次请求都实时查询,问题就解决了。所以记住:需要实时响应的,用服务;需要持续订阅的,用话题。

特性 话题 服务
通信模式 异步 同步
数据流向 单向 双向
典型场景 传感器数据、状态发布 参数查询、触发计算
可靠性 取决于QoS 保证响应

2.4 动作:带反馈的「长期任务」

动作是ROS2里最复杂的通信模式。它结合了话题和服务的特点:你发起一个目标,中间可以收到反馈,最后得到结果。这就像你叫了个外卖——下单(目标),骑手在路上(反馈),送到(结果)。

自动驾驶里典型的动作场景是「路径规划」。你给一个目的地,规划器一边算路径一边告诉你进度,最后返回完整轨迹。如果用服务,中间状态你就完全不知道了。

注意:动作的实现比话题和服务复杂得多。它内部用了三个话题(目标、反馈、结果)和一个服务(取消)。所以非必要不用动作,能用话题和服务解决的,就别上动作。

2.5 参数:节点的「配置开关」

参数说白了就是节点的全局变量。你可以在启动时设置,也可以在运行时动态修改。我个人习惯把所有的可调参数(比如PID系数、传感器频率)都做成参数,而不是硬编码在代码里。

嗯,这里要注意:参数不是线程安全的。如果你在多个回调里同时读写参数,可能会出问题。ROS2提供了参数回调机制,建议用add_on_set_parameters_callback来做参数校验。

// 声明和使用参数
this->declare_parameter("max_speed", 30.0);  // 默认30m/s
double speed = this->get_parameter("max_speed").as_double();

2.6 ROS2通信模型全景

好了,五个概念讲完了。咱们用一张图把它们串起来。下面这张SVG图展示了ROS2的通信模型全貌:

ROS2通信模型全景图 节点A (激光雷达驱动) 节点B (感知融合) 节点C (规划控制) 话题: /pointcloud 话题: /objects 服务客户端 服务服务端 服务: /get_map 请求→响应 动作客户端 动作服务端 动作: /plan_path 目标→反馈→结果 参数服务器 读写参数 读写参数 话题 服务 动作 参数

从这张图你能看到:节点A(激光雷达)通过话题把点云数据发给节点B(感知融合),节点B处理完通过话题把目标列表发给节点C(规划控制)。节点A如果需要地图数据,就通过服务向节点C请求。如果要做路径规划,就用动作来获取带反馈的结果。所有节点都可以读写参数服务器上的配置。

核心总结:

  • 话题:一对多、异步、持续流数据——适合传感器、状态发布
  • 服务:一对一、同步、即时响应——适合查询、触发
  • 动作:带反馈的长期任务——适合规划、导航
  • 参数:全局配置——适合调参、动态配置

我曾经见过一个团队,把所有通信都做成话题,结果系统里飘着几百个话题,调试起来跟大海捞针一样。后来我帮他们梳理,把需要响应的改成服务,把长期任务改成动作,系统瞬间清爽了。所以选型的时候,别偷懒,该用什么就用什么。

嗯,这一章的内容就到这。记住这些概念,后面讲中间件选型的时候,你会发现自己已经能看懂大部分技术文档了。