一、ROS2核心概念与生态:理解DDS、节点、话题、服务、动作的核心机制
各位同学,欢迎来到这门课的第一章。说实话,每次带新人入门ROS2,我都会先问一个问题:你为什么要从ROS1迁移过来?或者,你为什么要直接学ROS2?
答案其实很简单——因为ROS2解决了ROS1里那些让人头疼的“硬伤”。比如节点一多就掉线、实时性没法保证、跨机器通信配置繁琐……这些坑,我当年在项目里都踩过。今天我们就从最核心的概念讲起,把ROS2的“骨架”搭清楚。
1.1 DDS:ROS2的通信基石
ROS2和ROS1最大的区别,就是底层通信换成了DDS(Data Distribution Service)。说白了,DDS是一个“去中心化”的实时数据分发标准。它不像ROS1那样需要一个Master节点来管着所有人,而是每个节点自己就能发现对方、建立连接。
核心优势:
- 去中心化:没有单点故障,一个节点挂了不影响全局
- QoS策略:你可以控制消息的可靠性、历史记录、生命周期
- 跨平台:Windows、Linux、嵌入式系统都能跑
我个人习惯把DDS理解成一个“智能邮局”。你寄信时,可以选“挂号信”(可靠传输)或者“平信”(尽力传输),可以选“保留最近10封”(KEEP_LAST历史策略),甚至可以选“信过了3秒就作废”(LIFESPAN)。这些在ROS1里根本做不到。
避坑指南:我曾经在一个项目中,因为没设置QoS的reliability策略,导致控制指令偶尔丢失,车子在测试时突然急停。后来改成RELIABLE模式,问题就解决了。记住:默认的QoS不一定适合你的场景。
1.2 节点:ROS2的最小执行单元
节点就是ROS2里的“工人”。每个节点负责一个具体的任务,比如读取激光雷达数据、处理图像、控制电机。节点之间通过话题、服务、动作来通信。
在ROS2里,创建一个节点非常简单:
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
super().__init__('my_node')
self.get_logger().info('节点已启动!')
def main(args=None):
rclpy.init(args=args)
node = MyNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
你想想看,这段代码里最核心的就是rclpy.spin()——它让节点一直“活着”,等待处理消息。如果忘了这行,节点跑完就退出了,啥也干不了。
1.3 话题:发布-订阅模式
话题是ROS2里最常用的通信方式。一个节点发布消息到某个话题,其他节点订阅这个话题就能收到消息。这是“一对多”的异步通信。
举个例子,激光雷达节点发布/scan话题,定位节点订阅它来获取环境数据。这种模式的好处是解耦——发布者和订阅者互不知道对方的存在。
代码示例:发布者
class PublisherNode(Node):
def __init__(self):
super().__init__('publisher')
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, ROS2!'
self.pub.publish(msg)
这里有个细节:create_publisher的第三个参数是QoS深度(10),表示缓存10条消息。如果订阅者处理不过来,旧消息会被丢弃。嗯,这里要注意,如果你的系统对实时性要求高,建议用rclpy.qos.QoSProfile来精细控制。
1.4 服务:请求-响应模式
话题是“发了就不管”,服务则是“你问我答”。客户端发送请求,服务端处理并返回响应。适合那些需要确认结果的场景,比如“请帮我规划一条路径”或者“请设置目标速度”。
服务定义使用.srv文件:
# AddTwoInts.srv
int64 a
int64 b
---
int64 sum
服务端实现:
class ServiceNode(Node):
def __init__(self):
super().__init__('service_server')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
def add_callback(self, request, response):
response.sum = request.a + request.b
return response
我在项目中遇到过一个问题:服务调用如果超时,客户端会一直阻塞。所以建议给服务调用加上超时时间,比如client.call_async()配合asyncio.wait_for。
1.5 动作:带反馈的长时间任务
动作是服务的“升级版”。它适合那些需要长时间执行、并且需要中途反馈的任务,比如“导航到目标点”——你可以实时收到当前位置、剩余距离等反馈。
动作定义使用.action文件:
# NavigateToPose.action
# 目标
geometry_msgs/PoseStamped target_pose
---
# 结果
bool success
---
# 反馈
geometry_msgs/PoseStamped current_pose
float32 distance_remaining
动作客户端可以取消任务、接收反馈,这是服务做不到的。我记得有一次做自动泊车,用服务来执行泊车动作,结果车子卡在半路没法取消,只能重启系统。后来改成动作,加了个“紧急停止”的取消逻辑,问题就解决了。
1.6 ROS2与ROS1的核心差异
| 特性 | ROS1 | ROS2 |
|---|---|---|
| 通信中间件 | 自定义TCPROS/UDPROS | DDS(如Fast DDS、Cyclone DDS) |
| 节点发现 | 依赖Master节点 | 去中心化,自动发现 |
| QoS支持 | 无 | 丰富的QoS策略 |
| 实时性 | 不支持 | 支持(需配置) |
| 跨平台 | 主要Linux | Windows、macOS、嵌入式 |
| Python版本 | Python 2 | Python 3 |
说白了,ROS2就是ROS1的“现代化重构”。它保留了ROS1的编程模型(节点、话题、服务、动作),但底层换成了DDS,解决了ROS1在实时性、可靠性、跨平台上的短板。
1.7 搭建完整的开发环境
我个人推荐使用Ubuntu 22.04 + ROS2 Humble。安装步骤很简单:
# 设置locale
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
# 添加ROS2源
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
# 安装ROS2 Humble
sudo apt update
sudo apt install ros-humble-desktop
# 设置环境变量
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
注意:如果你用的是Windows或macOS,建议用Docker或者虚拟机。我试过在Windows上直接编译ROS2,折腾了两天才搞定,后来老老实实用WSL2了。
安装完成后,运行一个简单的测试:
# 终端1:启动talker
ros2 run demo_nodes_cpp talker
# 终端2:启动listener
ros2 run demo_nodes_py listener
如果能看到“Hello World”在终端里来回打印,恭喜你,环境搭建成功了!
知识体系总览
下面这张图总结了本章的核心内容,你可以把它当作ROS2的“地图”:
这张图里,DDS在最底层,支撑着节点、话题、服务、动作这四大核心。节点是执行单元,话题用于异步通信,服务用于同步请求,动作用于长时间任务。而QoS策略贯穿其中,让你能精细控制通信行为。
我的建议:刚开始学ROS2,不要急着看源码。先动手跑几个demo,感受一下节点怎么启动、话题怎么收发。等你有感觉了,再深入DDS和QoS的细节。我当年就是先跑通了talker-listener,才真正理解了“发布-订阅”是怎么回事。
好了,这一章的内容就到这里。记住:ROS2的核心不是代码,而是通信模型。理解了DDS、节点、话题、服务、动作,你就掌握了ROS2的“魂”。