2. ROS 2 基础回顾
好,咱们直接进入正题。这一章我打算带大家快速过一遍 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 知识体系总览
下面这张图是我自己画的,把本章的核心知识点串起来了。你可以把它当作一个快速索引,后面遇到问题随时回来查。
好了,这一章的内容就到这里。记住:节点是骨架,通信是血脉,工具链是双手。后面做多机器人仿真时,你会反复用到这些知识。
本章核心总结:
- 话题:一对多、异步、持续数据流
- 服务:一对一、同步、偶尔触发
- 动作:带反馈的长时间任务
- Launch:用 Python 或 XML 一键启动
- Colcon:编译、测试、打包一条龙
可可精品资料库