第2章:ROS2基础与通信机制

各位同学,欢迎来到第二章。上一章我们搭好了Jetson的环境,这一章咱们来啃ROS2的通信机制。说实话,ROS2的通信是整个机器人开发的命脉,传感器数据融合能不能做好,全看你对这套机制的理解深度。

我个人习惯把ROS2比作一个「消息中转站」。各个传感器、算法模块、执行器之间怎么说话?怎么保证数据不丢、不乱、不打架?这就是ROS2要解决的核心问题。

2.1 ROS2架构:四个核心通信原语

ROS2的通信模型,说白了就四种方式:话题(Topic)服务(Service)动作(Action),以及底层支撑的节点(Node)。我刚开始接触时也容易搞混,后来用一句话记住了:

  • 话题:广播式,一个发多个收,适合传感器数据流(比如IMU、激光雷达)
  • 服务:一问一答,适合临时请求(比如「打开摄像头」)
  • 动作:带反馈的长任务,适合「导航到A点」这种需要中途取消或看进度的操作
  • 节点:每个可执行程序就是一个节点,它们通过话题/服务/动作互相通信

我的经验之谈:在Jetson上做传感器融合,90%的场景用话题就够了。服务和动作更多用于上层决策。别一上来就想着用服务,那会增加系统耦合度。

2.2 工作空间创建:你的代码仓库

ROS2的工作空间(Workspace)就是你的项目文件夹。我习惯叫它「代码的工地」——所有源码、编译产物、配置文件都放在这里。

创建步骤很简单,但有个坑我踩过:路径不能有中文,否则编译会报一些莫名其妙的错误。

# 创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build

# 每次打开新终端都要source一下
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc

嗯,这里要注意:colcon build 默认是Debug模式,如果你在Jetson上编译,建议加个 --cmake-args -DCMAKE_BUILD_TYPE=Release,能省不少时间。我曾经在Jetson Nano上编译一个包,Debug模式花了15分钟,Release模式只用了6分钟。

2.3 发布者与订阅者:最核心的编程实战

咱们直接上手写代码。我以IMU数据发布为例,这是传感器融合中最常见的场景。

2.3.1 创建功能包

cd ~/ros2_ws/src
ros2 pkg create imu_publisher --build-type ament_python --dependencies rclpy sensor_msgs
ros2 pkg create imu_subscriber --build-type ament_python --dependencies rclpy sensor_msgs

为什么分两个包?我个人习惯把发布者和订阅者分开,这样后期调试时,可以单独替换其中一个而不影响另一个。你想想看,如果IMU坏了,你只需要改发布者,订阅者那边完全不用动。

2.3.2 编写发布者(Publisher)

打开 imu_publisher/imu_publisher/imu_publisher_node.py

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Imu
import random

class ImuPublisher(Node):
    def __init__(self):
        super().__init__('imu_publisher')
        self.publisher_ = self.create_publisher(Imu, 'imu_data', 10)
        self.timer = self.create_timer(0.01, self.timer_callback)  # 100Hz
        self.get_logger().info('IMU发布者已启动')

    def timer_callback(self):
        msg = Imu()
        msg.header.stamp = self.get_clock().now().to_msg()
        msg.header.frame_id = 'imu_link'
        # 模拟IMU数据(实际项目中从硬件读取)
        msg.linear_acceleration.x = random.uniform(-1.0, 1.0)
        msg.angular_velocity.z = random.uniform(-0.5, 0.5)
        self.publisher_.publish(msg)

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

if __name__ == '__main__':
    main()

避坑指南:我曾经把 create_publisher 的队列大小设成了1,结果数据频繁丢失,IMU融合出来的姿态一直在跳。后来改成10就稳定了。对于高频传感器(比如IMU 100Hz以上),队列大小建议10-20;低频传感器(比如GPS 10Hz),5就够了。

2.3.3 编写订阅者(Subscriber)

打开 imu_subscriber/imu_subscriber/imu_subscriber_node.py

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Imu

class ImuSubscriber(Node):
    def __init__(self):
        super().__init__('imu_subscriber')
        self.subscription = self.create_subscription(
            Imu, 'imu_data', self.listener_callback, 10)
        self.get_logger().info('IMU订阅者已启动')

    def listener_callback(self, msg):
        accel = msg.linear_acceleration
        gyro = msg.angular_velocity
        self.get_logger().info(
            f'收到IMU数据: accel=({accel.x:.2f}, {accel.y:.2f}, {accel.z:.2f}) '
            f'gyro=({gyro.x:.2f}, {gyro.y:.2f}, {gyro.z:.2f})')

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

if __name__ == '__main__':
    main()

2.3.4 编译与运行

cd ~/ros2_ws
colcon build --packages-select imu_publisher imu_subscriber
source install/setup.bash

# 开两个终端
# 终端1:运行发布者
ros2 run imu_publisher imu_publisher_node

# 终端2:运行订阅者
ros2 run imu_subscriber imu_subscriber_node

你会看到订阅者终端不断打印IMU数据。如果没看到,先检查一下话题名是否一致。我遇到过最蠢的错误:发布者写的是 imu_data,订阅者写的是 imu/data,找了半小时才发现。

2.4 常用调试命令

ROS2提供了很多命令行工具,我几乎每天都会用:

命令 用途 我的使用频率
ros2 node list 查看所有运行中的节点 每天10次+
ros2 topic list 查看所有话题 每天20次+
ros2 topic echo /topic_name 实时打印话题数据 调试时一直开着
ros2 topic hz /topic_name 查看话题发布频率 检查传感器是否掉帧
ros2 interface show sensor_msgs/msg/Imu 查看消息结构 写代码前必看

重要提醒:在Jetson上调试时,ros2 topic echo 会占用大量CPU。如果你发现系统卡顿,先关掉echo,或者用 --once 参数只打印一次。我曾经在调试激光雷达时开着echo没关,结果Jetson直接过热降频了。

2.5 本章小结

这一章我们走通了ROS2最核心的通信机制。你学会了:

  • 四种通信原语的区别与选择
  • 工作空间的创建与编译
  • 发布者与订阅者的完整编程流程
  • 常用的调试命令

下一章,我们将把这些知识应用到实际的传感器数据采集上。你会看到,同样的发布-订阅模式,如何驱动真正的IMU和激光雷达硬件。嗯,到时候你会感谢今天打下的基础。

课后练习:试着修改发布者的频率,从100Hz改成50Hz,看看订阅者的输出有什么变化。再用 ros2 topic hz /imu_data 验证一下实际频率是否和设置的一致。这个小实验能帮你理解ROS2的定时器机制。

公众号:蓝海资料掘金营,微信deep3321