第二章:ROS2核心概念与通信机制

各位同学,欢迎来到第二章。说实话,这一章是整个自动驾驶参数配置优化的地基。你想想看,如果连节点怎么通信、参数怎么传递都搞不清楚,后面那些高级优化技巧根本无从下手。我个人习惯把这一章叫做“ROS2的骨架”,因为所有功能都长在这些核心概念之上。

本章核心脉络:节点(Node)→ 话题(Topic)→ 服务(Service)→ 动作(Action)→ 参数(Parameter)→ DDS中间件。这六个概念,一个都不能少。

2.1 节点:ROS2的最小执行单元

节点是什么?说白了,就是一个独立的可执行进程。每个节点只负责一件事,比如读取激光雷达数据、控制电机、处理图像。我刚开始接触ROS2时犯过一个错误:把所有功能塞进一个节点里。结果代码耦合严重,改一个地方崩一片。后来我学乖了——一个节点只干一件事。

创建节点其实很简单。看下面这个例子:

import rclpy
from rclpy.node import Node

class LidarDriverNode(Node):
    def __init__(self):
        super().__init__('lidar_driver')
        self.get_logger().info('激光雷达驱动节点已启动')

def main(args=None):
    rclpy.init(args=args)
    node = LidarDriverNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

嗯,这里要注意:rclpy.spin()会让节点一直运行,直到你按Ctrl+C。我在项目中遇到过一个问题——忘记调用destroy_node(),结果节点退出时资源没释放,内存泄漏了。所以记得养成好习惯。

2.2 话题:异步通信的“广播站”

话题是ROS2里最常用的通信方式。它就像广播站——发布者只管发,订阅者只管收,双方不需要知道对方的存在。这种松耦合设计,正是自动驾驶系统需要的。

为什么会这样?因为自动驾驶里传感器数据量太大了。激光雷达每秒几十万点云,摄像头每秒30帧图像。如果用同步通信,系统早就卡死了。

通信方式 特点 适用场景
话题(Topic) 异步、一对多、无反馈 传感器数据、状态信息
服务(Service) 同步、一对一、有反馈 请求-响应模式
动作(Action) 异步、可取消、有反馈 长时间任务

发布话题的代码也很直观:

class SpeedPublisher(Node):
    def __init__(self):
        super().__init__('speed_publisher')
        self.publisher_ = self.create_publisher(Float32, 'vehicle_speed', 10)
        self.timer = self.create_timer(0.1, self.publish_speed)

    def publish_speed(self):
        msg = Float32()
        msg.data = 30.5  # 当前车速 km/h
        self.publisher_.publish(msg)

避坑指南:我曾经把话题的QoS设置成“尽力而为”,结果在信号不好的路段丢包严重,车速数据突然跳变。后来改成“可靠传输”才稳定。QoS设置一定要根据场景来选。

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

服务适合那些需要立即得到结果的场景。比如“请给我当前GPS坐标”、“请执行自检程序”。它是一对一的,客户端发请求,服务端回响应,中间不能打断。

你想想看,如果让车辆“启动引擎”用话题来发,你发完就不知道引擎到底启动了没有。但用服务,你能拿到返回值——成功还是失败。这就是服务的价值。

class SelfCheckService(Node):
    def __init__(self):
        super().__init__('self_check_service')
        self.srv = self.create_service(Trigger, 'self_check', self.check_callback)

    def check_callback(self, request, response):
        self.get_logger().info('收到自检请求')
        # 执行自检逻辑
        response.success = True
        response.message = '所有系统正常'
        return response

2.4 动作:长时间任务的“指挥官”

动作是我个人最喜欢的一个机制。它结合了话题和服务的特点——你可以发起一个长时间任务(比如“变道”、“泊车”),过程中能收到实时反馈,还能随时取消。

我记得在做一个自动泊车项目时,一开始用服务来实现。结果泊车过程要30秒,服务一直阻塞着,其他节点都卡死了。换成动作后,问题迎刃而解。

动作有三个通信通道:

  • Goal:发送目标(比如“泊入3号车位”)
  • Feedback:实时反馈(比如“已完成30%”)
  • Result:最终结果(比如“泊车成功”)

2.5 参数:节点的“可调旋钮”

参数就是节点的配置项。你可以动态修改,不用重启节点。这对自动驾驶太重要了——你总不能在高速上为了调个PID参数就停车重启吧?

我习惯把参数分为三类:

  • 静态参数:启动时设置,运行中不改(比如车型ID)
  • 动态参数:运行时可以调(比如PID系数、速度限制)
  • 标定参数:传感器标定值(比如相机内参)
class ParamConfigNode(Node):
    def __init__(self):
        super().__init__('param_config')
        self.declare_parameter('max_speed', 60.0)
        self.declare_parameter('pid_kp', 1.5)
        
        # 动态获取参数
        max_speed = self.get_parameter('max_speed').value
        self.get_logger().info(f'最大速度设置为: {max_speed} km/h')

警告:动态修改参数时,一定要做合法性校验。我曾经在测试时把最大速度设成了-10,结果车辆直接倒车...嗯,从那以后我每次改参数都加了个范围检查。

2.6 DDS中间件原理:ROS2的“隐形高速公路”

DDS(数据分发服务)是ROS2底层真正的通信引擎。ROS1用的是自研的TCPROS,而ROS2换成了工业级的DDS。为什么?因为自动驾驶对实时性、可靠性要求太高了。

DDS的核心机制包括:

  • 全局数据空间:所有节点共享一个虚拟的“数据黑板”
  • QoS策略:你可以控制可靠性、持久性、时效性
  • 自动发现:节点上线后自动找到彼此,不用手动配置

我个人觉得,理解DDS的关键在于QoS。你想想看,激光雷达的点云数据丢了就丢了,下一帧马上来——所以用“尽力而为”。但刹车指令丢了会出事故——必须用“可靠传输”。

下面这张图展示了ROS2的通信架构:

ROS2通信架构图 应用层(Application Layer) 节点(Node)| 话题(Topic)| 服务(Service)| 动作(Action)| 参数(Parameter) RCL层(ROS Client Library) rclpy / rclcpp / rcljava RMW层(ROS Middleware Interface) Fast DDS | Cyclone DDS | RTI Connext DDS层(Data Distribution Service) 全局数据空间 | QoS策略 | 自动发现 | 实时传输 抽象程度递减 实时性递增

从这张图你能看到,ROS2的通信是分层的。上层(应用层)你只管写业务逻辑,底层(DDS层)帮你搞定网络传输、数据序列化、节点发现这些脏活累活。

核心要点:ROS2之所以比ROS1更适合自动驾驶,就是因为DDS。它提供了真正的分布式通信、丰富的QoS策略、以及工业级的可靠性。你写代码时可能感觉不到DDS的存在,但它一直在背后默默工作。

好了,这一章的内容就到这里。六个核心概念——节点、话题、服务、动作、参数、DDS——它们构成了ROS2的通信骨架。下一章我们会深入参数配置的具体优化技巧,到时候见。

可可精品资料库