2. ROS 2 基础回顾:节点、话题、服务、动作的通信机制,launch文件编写,colcon编译工具链

好,咱们直接进入正题。这一章我打算带大家快速过一遍 ROS 2 的核心通信机制,以及日常开发绕不开的 launch 文件和 colcon 工具链。这些东西是后面做多机器人仿真的地基,地基打不牢,后面跑起来全是坑。

我个人习惯是,不管学什么新框架,先搞清楚它怎么“说话”的。ROS 2 里节点之间怎么交换数据?说白了就是四种方式:话题、服务、动作,外加一个参数。今天咱们把这四个讲透。

2.1 节点:一切功能的载体

节点是 ROS 2 里最小的执行单元。你可以把它理解成一个独立的小程序,专门干一件事。比如一个节点负责读取激光雷达数据,另一个节点负责规划路径。

我在项目中遇到过最头疼的事,就是节点命名冲突。两个节点都叫 lidar_node,结果启动后只有一个能正常工作。所以我的建议是:节点名一定要有命名空间,比如 /robot1/lidar_node 和 /robot2/lidar_node。

核心要点:

  • 每个节点有唯一的名称
  • 节点通过 DDS 进行通信
  • 一个进程可以包含多个节点(但我不推荐,调试起来很麻烦)

2.2 话题:最常用的发布/订阅模式

话题是 ROS 2 里最基础的通信方式。一个节点发布消息,其他节点订阅这个消息。发布者和订阅者互不知道对方的存在,这就是解耦。

嗯,这里要注意:话题通信是单向的、异步的。适合传输传感器数据、状态信息这种持续更新的数据。

# 发布者示例
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.pub = self.create_publisher(String, 'chatter', 10)
        self.timer = self.create_timer(1.0, self.timer_callback)

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello, ROS 2!'
        self.pub.publish(msg)
        self.get_logger().info('发布: "%s"' % msg.data)

你想想看,如果我要让两个机器人交换位置信息,用话题就非常合适。每个机器人发布自己的位姿,其他机器人订阅就行。

避坑指南:我曾经在调试多机器人系统时,发现某个话题的数据总是延迟。查了半天,原来是 QoS 设置不匹配。发布者用的是“可靠传输”,订阅者用的是“尽力传输”,结果数据包经常丢。记住:发布者和订阅者的 QoS 必须一致。

2.3 服务:请求/响应的同步通信

话题适合“一直发”,但有些场景你需要“问一句,答一句”。比如让机器人去某个位置,你得先问它“当前位置是多少?”,它回答你,你再下发指令。这就是服务。

服务是同步的、双向的。客户端发送请求,服务器处理并返回响应。适合偶尔触发的操作,比如获取参数、触发某个动作。

特性 话题 服务
通信模式 发布/订阅 请求/响应
同步/异步 异步 同步
适用场景 持续数据流 偶尔触发的操作
典型例子 激光雷达数据 获取机器人位姿
# 服务端示例
class ServiceNode(Node):
    def __init__(self):
        super().__init__('service_node')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)

    def add_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info('收到请求: %d + %d = %d' % (request.a, request.b, response.sum))
        return response

2.4 动作:带反馈的长时间任务

动作是 ROS 2 里最复杂的通信方式,但也是最强大的。它结合了话题和服务的特点:你可以发送一个目标(类似服务请求),然后持续收到反馈(类似话题),最后得到结果。

为什么需要动作?你想想看,如果让机器人导航到某个点,这个过程可能持续几十秒。用服务的话,客户端会一直阻塞等待,这显然不合理。用动作,你可以随时取消任务、查看进度。

动作的三要素:

  • 目标:告诉机器人要做什么
  • 反馈:机器人告诉你做到哪一步了
  • 结果:任务完成后的最终状态

我记得第一次用动作做多机器人协同搬运时,就踩了个大坑。两个机器人同时执行动作,结果反馈话题冲突了。后来我给每个动作服务器都加了唯一的命名空间,问题才解决。

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

手动启动几十个节点?那是不可能的。Launch 文件就是用来解决这个问题的。它让你用 Python 或 XML 描述要启动哪些节点、传什么参数、放在哪个命名空间。

我个人强烈推荐用 Python 格式的 launch 文件。为什么?因为 Python 是图灵完备的,你可以写条件判断、循环,甚至从外部文件读取配置。

# launch/example.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_package',
            executable='talker',
            name='talker_node',
            namespace='robot1',
            parameters=[{'param1': 'value1'}]
        ),
        Node(
            package='my_package',
            executable='listener',
            name='listener_node',
            namespace='robot1'
        ),
    ])

注意:Launch 文件里的 executable 是你在 setup.py 里注册的可执行文件名称,不是 Python 文件名。我见过太多人搞混这个,结果启动时报错“找不到可执行文件”。

2.6 Colcon 编译工具链

最后说说编译。ROS 2 用的是 colcon,它是 catkin 的升级版。说白了,colcon 就是帮你把 C++ 和 Python 代码编译成可执行文件。

常用的命令就这几个:

# 编译整个工作空间
colcon build

# 只编译某个包
colcon build --packages-select my_package

# 编译并运行测试
colcon test

# 编译时显示详细信息
colcon build --event-handlers console_direct+

嗯,这里有个小技巧:每次修改代码后,如果只改了 Python 文件,其实不需要重新编译。但如果你改了 C++ 头文件或者接口定义,那就必须 colcon build 一下。

我的习惯:每次编译前先 colcon build --packages-select 只编译我改过的包,这样能省不少时间。全量编译一般只在第一次或者改动了公共接口时才做。

2.7 知识体系总览

下面这张图是我自己画的,把本章的核心知识点串起来了。你可以把它当作一个快速索引,后面遇到问题随时回来查。

ROS 2 基础通信与工具链 ROS 2 核心 话题 (Topic) 服务 (Service) 动作 (Action) 参数 Colcon 编译工具链 Launch 文件 colcon build colcon test Python Launch XML Launch 节点是基础,话题/服务/动作是通信方式,colcon和launch是开发工具 发布/订阅 → 异步 | 请求/响应 → 同步 | 目标/反馈/结果 → 长时间任务

好了,这一章的内容就到这里。记住:节点是骨架,通信是血脉,工具链是双手。后面做多机器人仿真时,你会反复用到这些知识。

本章核心总结:

  • 话题:一对多、异步、持续数据流
  • 服务:一对一、同步、偶尔触发
  • 动作:带反馈的长时间任务
  • Launch:用 Python 或 XML 一键启动
  • Colcon:编译、测试、打包一条龙

可可精品资料库