第二章 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里最复杂的通信方式。它结合了话题和服务的特点——可以发送目标、接收反馈、还能取消任务。说白了,就是给机器人派一个长期任务,然后实时看进度。
动作的三个阶段:
- 发送目标:客户端告诉服务端要做什么
- 接收反馈:服务端不断汇报进度
- 获取结果:任务完成或取消时得到最终结果
实际案例:我记得在做一个自动泊车项目时,就用动作来控制车辆从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 它们如何协同工作?
好,现在咱们把五个概念串起来。一个典型的自动驾驶系统是这样工作的:
你看这张图就清楚了:
- 传感器节点通过话题发布原始数据(比如激光雷达的scan数据)
- 感知节点订阅话题,处理数据,再通过服务提供查询接口
- 规划节点通过动作下发导航任务,同时从参数服务器读取配置
- 控制节点执行动作,实时反馈进度
核心要点:这五个概念不是孤立的。在实际项目中,它们像齿轮一样咬合在一起。话题负责高频数据流,服务处理一次性请求,动作管理长期任务,参数提供灵活配置。节点则是这一切的载体。
嗯,说到这里,我想起一个真实的教训。有一次我在集成一个第三方感知模块时,对方把所有功能都塞在一个节点里,用全局变量传数据。结果调试时根本分不清是哪个环节出了问题。后来我花了两天时间把它拆成五个独立节点,每个节点只做一件事,用话题和服务通信。问题一下子就定位到了。
所以,记住这个原则:一个节点一个职责,通信用对的方式。这是ROS2设计的精髓,也是你搭建自动驾驶系统的基础。
| 通信方式 | 适用场景 | 实时性 | 可靠性 |
|---|---|---|---|
| 话题 | 传感器数据、高频状态 | 高 | 中(取决于QoS) |
| 服务 | 查询、一次性操作 | 中 | 高 |
| 动作 | 长期任务、带反馈 | 低 | 高 |
| 参数 | 配置、调优 | - | 高 |
好了,这一章的内容就到这儿。记住这五个概念,后面咱们会反复用到它们。