第二章:ROS2核心概念与工具

各位同学,欢迎来到第二章。说实话,ROS2的概念体系乍一看有点多,但别慌。我当年刚接触时也觉得眼花缭乱,后来发现,只要抓住几个核心的“通信骨架”,整个系统就清晰了。这一章,我们就来把这些骨架一一拆解。

ROS2 核心体系 通信机制 话题 (Topic) 服务 (Service) 动作 (Action) 工具与配置 参数 (Parameter) Launch文件 rqt工具 tf2 坐标变换 节点 (Node) 是这一切的载体,每个节点都是一个独立的可执行进程 节点 (Node)

2.1 节点:系统的“细胞”

节点,说白了就是一个独立的可执行进程。每个节点只负责一件事,比如读取激光雷达数据、处理IMU信息、或者控制电机。我个人的习惯是,一个传感器对应一个节点,这样调试起来特别方便。

核心要点:节点之间通过话题、服务、动作进行通信,节点本身不存储全局状态。

创建节点很简单,用Python的话几行代码就行:

import rclpy
from rclpy.node import Node

class MySensorNode(Node):
    def __init__(self):
        super().__init__('sensor_node')
        self.get_logger().info('传感器节点已启动!')

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

我的小技巧:节点命名一定要有规律。比如 lidar_front_node、imu_back_node,这样用 ros2 node list 查看时一目了然。

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

话题是ROS2里最常用的通信方式。发布者只管发,订阅者只管收,双方不需要知道对方的存在。我在做多传感器融合时,经常把原始传感器数据发布成话题,然后融合节点去订阅。

举个例子,发布激光雷达数据:

class LidarPublisher(Node):
    def __init__(self):
        super().__init__('lidar_pub')
        self.pub = self.create_publisher(String, 'lidar_data', 10)
        self.timer = self.create_timer(0.1, self.publish_data)
    
    def publish_data(self):
        msg = String()
        msg.data = '激光雷达数据帧'
        self.pub.publish(msg)

避坑指南:我曾经遇到过话题名写错导致数据收不到的情况。建议用 ros2 topic list 确认话题是否存在,再用 ros2 topic echo /话题名 查看数据是否正常。

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

服务适合需要立即得到结果的场景,比如请求当前传感器状态、触发一次校准。和话题不同,服务是同步的——客户端发请求,服务器处理完才返回响应。

你想想看,如果每次获取传感器数据都用服务,那系统效率会很低。所以服务一般用于配置、查询这类低频操作。

通信方式 特点 适用场景
话题 (Topic) 异步、一对多、持续流 传感器数据、控制指令
服务 (Service) 同步、一对一、请求-响应 参数查询、触发动作
动作 (Action) 异步、可取消、有反馈 导航、机械臂运动

2.4 动作:带反馈的“长任务”

动作是ROS2里比较高级的通信方式。它适合执行时间较长的任务,比如让机器人走到某个位置。动作有三个阶段:目标发送、持续反馈、最终结果。

我记得有一次做多传感器标定,需要机器人转一圈采集数据。用动作就很合适——发送“旋转一周”的目标,过程中不断接收当前角度反馈,完成后拿到采集结果。

2.5 参数:节点的“配置旋钮”

参数就是节点的配置项。比如激光雷达的采样频率、IMU的滤波系数。我个人习惯把经常需要调整的值都做成参数,这样不用改代码就能调。

self.declare_parameter('sample_rate', 10)
rate = self.get_parameter('sample_rate').get_parameter_value().integer_value

实用技巧:用 ros2 param list 查看所有参数,用 ros2 param set /节点名 参数名 值 动态修改参数,非常方便。

2.6 Launch文件:一键启动的艺术

当你有十几个节点要启动时,一个个敲命令显然不现实。Launch文件就是用来解决这个问题的。它用Python或XML描述要启动哪些节点、带什么参数。

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(package='sensor_driver', executable='lidar_node'),
        Node(package='sensor_driver', executable='imu_node'),
        Node(package='fusion', executable='fusion_node'),
    ])

2.7 rqt工具:可视化调试利器

rqt是ROS2的图形化工具集。我最常用的是 rqt_graph,它能直观显示节点之间的通信关系。做多传感器融合时,看一眼rqt_graph就知道数据流对不对。

其他好用的还有:

  • rqt_plot:实时绘制数据曲线,看传感器值变化
  • rqt_console:查看日志输出,过滤不同级别的消息
  • rqt_tf_tree:查看tf2坐标树

2.8 tf2:坐标变换的“交通枢纽”

多传感器融合绕不开坐标变换。激光雷达有自己的坐标系,IMU有另一个坐标系,它们都安装在机器人上。tf2就是用来管理这些坐标系之间关系的。

举个例子,激光雷达检测到一个障碍物在 lidar_frame 下坐标是 (1, 0, 0),但你需要知道它在机器人基座 base_link 下的位置。用tf2可以自动完成这个变换。

from tf2_ros import TransformListener, Buffer

self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)

# 获取变换
transform = self.tf_buffer.lookup_transform(
    'base_link', 'lidar_frame', rclpy.time.Time())

我曾经踩过的坑:tf2的坐标系名称大小写敏感,而且必须保证发布频率足够。如果坐标变换超时,所有依赖它的节点都会报错。建议用 tf2_monitor 工具检查发布频率。

嗯,这一章的内容就到这里。这些概念是ROS2的基石,后面的多传感器融合实战都会用到它们。建议你动手敲一遍代码,用 ros2 topic echo 看看数据长什么样,用 rqt_graph 看看节点怎么连的。实践出真知,光看是学不会的。