第二章 ROS2核心概念:节点、话题、服务、动作、参数

好,咱们直接进入正题。ROS2这五个核心概念——节点、话题、服务、动作、参数——说白了就是自动驾驶系统里最基础的通信工具。你想想看,一辆自动驾驶车上几十个传感器、十几个控制器,它们之间怎么说话?就是靠这些东西。

我个人习惯把ROS2比作一个公司。节点就是员工,话题就是广播喇叭,服务就是打电话问事情,动作就是派一个长期任务,参数就是贴在墙上的规章制度。这么一想,是不是好理解多了?

2.1 节点:系统的“细胞”

节点是ROS2里最小的执行单元。每个节点负责一件事,比如一个节点专门读激光雷达数据,另一个节点专门做路径规划。

节点有什么特点?

  • 独立运行:每个节点是一个独立的进程。一个挂了,不影响其他节点。
  • 可组合:多个节点可以组合成一个复杂系统。
  • 命名唯一:同一个网络里,节点名不能重复。

重要提醒:我在项目中遇到过,有人把所有功能写在一个节点里,结果调试时改一个功能就得重启整个系统。千万别这么干!一个节点只做一件事,这是铁律。

// 创建一个节点的示例(C++)
#include "rclcpp/rclcpp.hpp"

class MyNode : public rclcpp::Node
{
public:
  MyNode() : Node("lidar_processor")
  {
    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里最常用的通信方式。它就像广播电台——一个节点发消息,多个节点可以同时收。发送方叫发布者,接收方叫订阅者。

话题的特点:

  • 异步通信:发布者只管发,不管有没有人收。
  • 一对多:一个话题可以有多个订阅者。
  • 数据类型固定:每个话题有固定的消息类型。

我的经验:我曾经调试一个感知模块,发现话题数据延迟特别大。查了半天,原来是发布频率太高,订阅者处理不过来。后来我加了个队列长度限制,问题就解决了。嗯,这里要注意:话题的QoS设置很关键,后面我们会专门讲。

// 发布者示例
auto publisher = this->create_publisher<sensor_msgs::msg::LaserScan>("scan", 10);

// 订阅者示例
auto subscription = this->create_subscription<sensor_msgs::msg::LaserScan>(
  "scan", 10, [this](const sensor_msgs::msg::LaserScan::SharedPtr msg) {
    RCLCPP_INFO(this->get_logger(), "收到激光数据,角度范围: %f", msg->angle_max);
  });

2.3 服务:一问一答的同步通信

服务跟话题不一样。它是同步的——客户端发请求,服务端处理完再回复。就像你打电话问客服:“现在电量还剩多少?”客服查一下告诉你。

什么时候用服务?

  • 需要立即得到结果时(比如查询状态)
  • 执行一次性操作(比如启动某个模块)
  • 请求-响应模式很明确时

避坑指南:我曾经在项目中用服务来传输大量点云数据,结果导致整个系统卡死。因为服务是同步的,数据量大时会阻塞调用方。记住:大数据量用话题,小数据量用服务。

// 服务端示例
auto service = this->create_service<std_srvs::srv::Trigger>(
  "start_mapping", 
  [this](const std::shared_ptr<std_srvs::srv::Trigger::Request> request,
         std::shared_ptr<std_srvs::srv::Trigger::Response> response) {
    response->success = true;
    response->message = "建图任务已启动";
  });

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

动作是ROS2里最复杂的通信方式。它结合了话题和服务的特点——可以发送目标、接收反馈、还能取消任务。说白了,就是给机器人派一个长期任务,然后实时看进度。

动作的三个阶段:

  1. 发送目标:客户端告诉服务端要做什么
  2. 接收反馈:服务端不断汇报进度
  3. 获取结果:任务完成或取消时得到最终结果

实际案例:我记得在做一个自动泊车项目时,就用动作来控制车辆从A点开到B点。动作可以实时反馈当前位置,如果发现障碍物还能随时取消任务。这种场景用话题或服务都不合适。

// 动作服务端关键代码
rclcpp_action::Server<nav2_msgs::action::NavigateToPose>::GoalHandle::SharedPtr goal_handle;

// 处理目标
auto handle_goal = [this](const rclcpp_action::GoalUUID & uuid,
    std::shared_ptr<const nav2_msgs::action::NavigateToPose::Goal> goal) {
  RCLCPP_INFO(this->get_logger(), "收到导航目标: x=%f, y=%f", 
              goal->pose.pose.position.x, goal->pose.pose.position.y);
  return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
};

2.5 参数:系统的配置中心

参数就是节点的配置项。比如激光雷达的扫描频率、控制器的PID系数、路径规划的避障距离等等。参数可以在运行时动态修改,不用重启节点。

参数的使用场景:

  • 系统配置(传感器参数、算法参数)
  • 运行时调优(PID参数在线调整)
  • 模块开关(启用/禁用某个功能)

我的习惯:我一般把所有参数放在一个YAML文件里,启动时一次性加载。调试时再用命令行动态修改。这样既方便又灵活。

// 声明和获取参数
this->declare_parameter("scan_frequency", 10.0);
this->declare_parameter("enable_debug", false);

double freq = this->get_parameter("scan_frequency").as_double();
bool debug = this->get_parameter("enable_debug").as_bool();

// 动态修改参数(命令行)
// ros2 param set /lidar_node scan_frequency 20.0

2.6 它们如何协同工作?

好,现在咱们把五个概念串起来。一个典型的自动驾驶系统是这样工作的:

ROS2核心概念协同工作示意图 传感器节点 发布话题数据 感知节点 订阅话题+服务 规划节点 动作+参数 控制节点 执行动作 话题(scan) 话题(障碍物) 服务(查询状态) 动作(导航) 参数服务器 参数 传感器发布激光雷达数据 感知节点订阅并处理 规划节点通过动作下发路径 控制节点执行并反馈进度

你看这张图就清楚了:

  1. 传感器节点通过话题发布原始数据(比如激光雷达的scan数据)
  2. 感知节点订阅话题,处理数据,再通过服务提供查询接口
  3. 规划节点通过动作下发导航任务,同时从参数服务器读取配置
  4. 控制节点执行动作,实时反馈进度

核心要点:这五个概念不是孤立的。在实际项目中,它们像齿轮一样咬合在一起。话题负责高频数据流,服务处理一次性请求,动作管理长期任务,参数提供灵活配置。节点则是这一切的载体。

嗯,说到这里,我想起一个真实的教训。有一次我在集成一个第三方感知模块时,对方把所有功能都塞在一个节点里,用全局变量传数据。结果调试时根本分不清是哪个环节出了问题。后来我花了两天时间把它拆成五个独立节点,每个节点只做一件事,用话题和服务通信。问题一下子就定位到了。

所以,记住这个原则:一个节点一个职责,通信用对的方式。这是ROS2设计的精髓,也是你搭建自动驾驶系统的基础。

通信方式 适用场景 实时性 可靠性
话题 传感器数据、高频状态 高 中(取决于QoS)
服务 查询、一次性操作 中 高
动作 长期任务、带反馈 低 高
参数 配置、调优 - 高

好了,这一章的内容就到这儿。记住这五个概念,后面咱们会反复用到它们。

可可精品资料库