跳转至

ROS 2 简介

1. 什么是 ROS 2?为什么需要它?

ROS 1(Robot Operating System 1,机器人操作系统 1)是机器人软件开发的开创性框架,但随着机器人产业的成熟,其若干根本性局限逐渐暴露:

  • 不支持实时控制:ROS 1 的 TCP/UDP 传输层并非为实时或安全关键系统设计。
  • 单主架构(Single-master):中心化的 roscore 是单点故障。
  • 安全性差:没有内置的身份验证、加密或访问控制。
  • 多机器人支持不足:运行多个机器人需要复杂的命名空间(namespace)变通方案。
  • 跨平台能力弱:主要仅支持 Linux。
  • Python 2 遗留问题:ROS Noetic 最终迁移到了 Python 3,但过渡过程缓慢。

ROS 2 从零开始设计以解决这些问题。它采用 DDS(Data Distribution Service,数据分发服务) 中间件标准作为通信骨干——这是一种经过验证的发布/订阅协议,广泛应用于航空航天、国防和金融系统。

ROS 2 的核心设计目标:

目标 ROS 2 如何实现
实时控制 DDS 支持实时 QoS(Quality of Service,服务质量)策略
多机器人 去中心化发现机制;无需单一主节点
安全性 DDS-Security(SROS2)提供身份验证和加密
嵌入式系统 Micro-ROS 面向微控制器
生产就绪 为商业部署设计,而非仅限研究
跨平台 Linux、macOS、Windows 同等支持

2. ROS 1 与 ROS 2 对比

特性 ROS 1 (Noetic) ROS 2 (Humble+)
中间件 自定义 TCP/UDP(roscomm) DDS(Data Distribution Service)
发现机制 中心化 roscore(单主节点) 去中心化(DDS 发现)
实时性 不支持 通过 DDS QoS 策略支持
安全性 无内置安全 DDS-Security(SROS2)
多机器人 手动命名空间方案 原生支持
构建系统 catkin colcon + ament(CMake / Python setuptools)
启动文件 XML(.launch Python(.launch.py
语言支持 C++(roscpp)、Python(rospy) C++(rclcpp)、Python(rclpy)
参数 全局参数服务器 每个节点独立参数
动作服务器 actionlib rclpy/rclcpp 内置 Action
生命周期管理 托管节点生命周期(LifecycleNode)
消息 IDL .msg 文件 相同的 .msg + .srv + .action 文件
平台 Linux(主要) Linux、macOS、Windows
DDS 实现 Fast DDS、Cyclone DDS、Connext 等

3. ROS 2 架构

ROS 2 采用分层架构,通过 RMW(ROS Middleware Interface,ROS 中间件接口)抽象中间件层。

+-------------------------------------------------------+
|                  用户应用程序                           |
|           (你的 ROS 2 节点、启动文件)                 |
+-------------------------------------------------------+
|             RCL(ROS 客户端库)                        |
|         rclcpp(C++)  /  rclpy(Python)              |
+-------------------------------------------------------+
|           RMW(ROS 中间件接口)                        |
|      rmw_fastrtps / rmw_cyclonedds / rmw_connext      |
+-------------------------------------------------------+
|                DDS 实现层                              |
|       eProsima Fast DDS / Eclipse Cyclone DDS          |
+-------------------------------------------------------+
|              操作系统网络栈(UDP/IP)                   |
+-------------------------------------------------------+

各层说明:

  • 应用层:你的节点(Node)、话题(Topic)、服务(Service)、动作(Action)。
  • RCL(ROS Client Library,ROS 客户端库):语言特定的 API。rclcpp 用于 C++,rclpy 用于 Python。提供熟悉的 ROS API(NodePublisherSubscriber 等)。
  • RMW(ROS Middleware Interface,ROS 中间件接口):抽象层,允许在不修改应用代码的情况下切换 DDS 实现。可选择 rmw_fastrtps(默认)、rmw_cyclonedds 或其他实现。
  • DDS 层:处理实际的发布/订阅传输、发现、序列化、QoS 以及(可选的)安全性。

DDS 服务质量(QoS)策略

QoS 策略让你可以调整通信行为:

+---------------------+------------------------------------------+
| 策略                | 选项                                      |
+---------------------+------------------------------------------+
| 可靠性 Reliability  | RELIABLE(可靠) / BEST_EFFORT(尽力而为)  |
| 持久性 Durability   | TRANSIENT_LOCAL / VOLATILE                |
| 历史记录 History    | KEEP_LAST(n) / KEEP_ALL                   |
| 截止时间 Deadline   | (周期)                                   |
| 生命周期 Lifespan   | (持续时间)                                |
| 活跃性 Liveliness   | AUTOMATIC / MANUAL_BY_TOPIC               |
+---------------------+------------------------------------------+

4. 核心概念

4.1 节点(Node)

**节点**是计算的基本单元。每个节点执行一个明确定义的功能(如相机驱动、路径规划器)。

import rclpy
from rclpy.node import Node

class MyNode(Node):
    def __init__(self):
        super().__init__('my_node')
        self.get_logger().info('Hello from ROS 2!')

4.2 话题(Topic)

**话题**是用于异步发布/订阅通信的命名总线。多个发布者和订阅者可以连接到同一个话题。

  +----------+         /camera/image         +------------+
  | 发布者    | -------> Topic -------> | 订阅者    |
  | (camera)  |                          | (detector) |
  +----------+                          +------------+
                                               |
                                          +------------+
                                          | 订阅者    |
                                          | (display)  |
                                          +------------+

4.3 服务(Service)

**服务**是同步的请求/响应通信(类似 RPC)。客户端发送请求并阻塞直到收到响应。

  +----------+    request    +----------+    response
  | 客户端   | ------------> |  服务端   | ----------->
  +----------+               +----------+

4.4 动作(Action)

**动作**结合了目标(goal)、反馈(feedback)和结果(result)。适用于需要周期性进度更新的长时间运行任务(如"导航到航点")。

  +----------+   goal    +----------+   feedback(周期性)
  |  客户端   | --------> |  服务端   | ------------------->
  +----------+           +----------+
       ^                      |
       |     result(结果)    |
       +----------------------+

4.5 参数(Parameter)

**参数**是节点级别的配置值(整数、字符串、布尔值等),可在运行时查询或设置。

4.6 启动(Launch)

**启动文件**用于同时启动多个节点,支持可配置的参数、话题重映射和条件判断。ROS 2 使用基于 Python 的启动文件(.launch.py)。

5. ROS 2 命令行工具 vs ROS 1

任务 ROS 1 ROS 2
运行节点 rosrun pkg node ros2 run pkg node
列出节点 rosnode list ros2 node list
列出话题 rostopic list ros2 topic list
回显话题 rostopic echo /topic ros2 topic echo /topic
发布话题 rostopic pub /topic ... ros2 topic pub /topic ...
查看消息类型 rosmsg show MsgType ros2 interface show MsgType
调用服务 rosservice call /srv ... ros2 service call /srv ...
列出参数 rosparam list ros2 param list
获取参数 rosparam get /p ros2 param get /node p
列出功能包 rospack list ros2 pkg list
创建功能包 catkin_create_pkg ros2 pkg create --build-type ament_python
构建工作空间 catkin_make colcon build
启动文件 roslaunch pkg file.launch ros2 launch pkg file.launch.py
录制 bag rosbag record -a ros2 bag record -a
回放 bag rosbag play file.bag ros2 bag play file/

6. ROS 2 发行版

发行版 Ubuntu 版本 停止支持日期 说明
Humble Hawksbill 22.04 2027 年 5 月 LTS(长期支持);截至 2025 年使用最广泛
Iron Irwini 22.04 2024 年 11 月 非 LTS;已停止支持
Jazzy Jalisco 24.04 2029 年 5 月 LTS;最新稳定版
Rolling 最新版 持续交付;始终保持最新

建议:生产环境使用 Humble(Ubuntu 22.04)或 Jazzy(Ubuntu 24.04)。如需最新功能且能接受偶尔的兼容性问题,可使用 Rolling

7. 用 ROS 2 Python 编写简单的发布者/订阅者

7.1 创建功能包

# 加载 ROS 2 环境
source /opt/ros/humble/setup.bash

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

# 创建 Python 功能包
ros2 pkg create --build-type ament_python my_pubsub --dependencies rclpy std_msgs

7.2 发布者(Publisher)(my_publisher.py

创建文件 my_pubsub/my_pubsub/my_publisher.py

import rclpy
from rclpy.node import Node
from std_msgs.msg import String


class MinimalPublisher(Node):
    """一个最小的 ROS 2 发布者,发送 'Hello World' 消息。"""

    def __init__(self):
        super().__init__('minimal_publisher')
        # 在 'chatter' 话题上创建发布者,队列大小为 10
        self.publisher_ = self.create_publisher(String, 'chatter', 10)
        # 创建定时器,每 0.5 秒调用一次 publish_callback
        self.timer = self.create_timer(0.5, self.publish_callback)
        self.count = 0

    def publish_callback(self):
        msg = String()
        msg.data = f'Hello World: {self.count}'
        self.publisher_.publish(msg)
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.count += 1


def main(args=None):
    rclpy.init(args=args)
    node = MinimalPublisher()
    try:
        rclpy.spin(node)  # 保持节点运行
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

7.3 订阅者(Subscriber)(my_subscriber.py

创建文件 my_pubsub/my_pubsub/my_subscriber.py

import rclpy
from rclpy.node import Node
from std_msgs.msg import String


class MinimalSubscriber(Node):
    """一个最小的 ROS 2 订阅者,监听 'chatter' 话题。"""

    def __init__(self):
        super().__init__('minimal_subscriber')
        # 订阅 'chatter' 话题,每收到消息调用 listener_callback
        self.subscription = self.create_subscription(
            String,
            'chatter',
            self.listener_callback,
            10  # QoS 队列深度
        )

    def listener_callback(self, msg: String):
        self.get_logger().info(f'I heard: "{msg.data}"')


def main(args=None):
    rclpy.init(args=args)
    node = MinimalSubscriber()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

7.4 注册入口点

编辑 setup.py 添加控制台脚本入口点:

entry_points={
    'console_scripts': [
        'talker = my_pubsub.my_publisher:main',
        'listener = my_pubsub.my_subscriber:main',
    ],
},

7.5 构建与运行

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

# 终端 1:运行发布者
ros2 run my_pubsub talker

# 终端 2:运行订阅者
ros2 run my_pubsub listener

预期输出:

# 终端 1(talker)
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
...

# 终端 2(listener)
[INFO] [minimal_subscriber]: I heard: "Hello World: 0"
[INFO] [minimal_subscriber]: I heard: "Hello World: 1"
...

8. ROS 2 启动文件(基于 Python)

ROS 2 的启动文件使用 Python 编写。创建 my_pubsub/launch/demo.launch.py

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_pubsub',
            executable='talker',
            name='my_talker',
            output='screen',
            parameters=[],       # 参数 YAML 文件或字典列表
            remappings=[],       # 话题重映射
        ),
        Node(
            package='my_pubsub',
            executable='listener',
            name='my_listener',
            output='screen',
        ),
    ])

运行启动文件:

ros2 launch my_pubsub demo.launch.py

高级启动特性

ROS 2 启动文件支持:

  • 参数声明DeclareLaunchArgument('robot_name', default_value='robot1')
  • 条件执行IfCondition(LaunchConfiguration('use_camera'))
  • 变量替换${} 风格的变量展开
  • 包含其他启动文件IncludeLaunchDescription(...)
  • 分组动作GroupAction([...]) 支持命名空间和重映射

9. 从 ROS 1 迁移的建议

如果你要将现有的 ROS 1 代码迁移到 ROS 2,以下是实用指南:

  1. 消息类型基本相同.msg.srv.action 文件语法一致。功能包名称可能不同(如 geometry_msgs 在两个版本中均可用)。

  2. rospy 替换为 rclpy

  3. rospy.init_node()rclpy.init() + Node('name')
  4. rospy.Publisher()node.create_publisher()
  5. rospy.Subscriber()node.create_subscription()
  6. rospy.spin()rclpy.spin(node)

  7. rosrun 替换为 ros2 runroslaunch 替换为 ros2 launch

  8. 将 catkin 替换为 colcon

    # 旧
    catkin_make
    # 新
    colcon build
    

  9. 移植启动文件:将 XML 格式的 .launch 转换为 Python 格式的 .launch.py。虽然 ros2 launch 也支持旧格式,但 Python 是标准方式。

  10. 参数:ROS 2 的参数是每个节点独立的(没有全局参数服务器)。使用 ros2 param 命令行工具或在代码中声明参数:self.declare_parameter('name', default_value)

  11. 日志:将 rospy.loginfo() 替换为 self.get_logger().info()

  12. 时间:将 rospy.Time.now() 替换为 node.get_clock().now()

  13. 使用 ros1_bridge:在混合使用 ROS 1 和 ROS 2 的过渡期,ros1_bridge 包可以桥接话题和服务。

  14. 渐进式迁移:不必一次性移植所有内容。使用 ros1_bridge 连接新旧节点,逐步完成迁移。

10. 参考资料