跳转至

第 1 章 ROS 2 生态与工程基线

ROS 2 是对 ROS 1 架构级重写而非渐进升级:去掉中心化的 master 节点、以 DDS 作为通信中间件、用 ament 替代 catkin 构建体系。理解这三处断裂,才能解释为什么 ROS 1 教程里的命令与概念在 ROS 2 环境中大面积失效。本章覆盖从安装到第一个包编译通过的完整工程基线,是后续九章所有示例的前提。

1. ROS 1 到 ROS 2 的架构断裂

ROS 2(Robot Operating System 2)不是 ROS 1 的版本号递增,而是同一设计目标下的第二套实现。三处核心差异决定了几乎所有命令和概念的不兼容:

维度 ROS 1 ROS 2 工程影响
节点发现 中心化 roscore(master) 去中心 DDS multicast discovery 无"先启动 master"的前置步骤;网络内多机自动组网
通信层 TCPROS/UDPROS 自研协议 DDS(Data Distribution Service)QoS 体系 QoS 策略可逐 topic 调优;不兼容时静默丢消息而非报错
构建系统 catkin(CMake 宏包装) ament(colcon 调度 + 原生 CMake/Python) 工作空间不再 catkin_init_workspace;改用 colcon build

因此,ROS 1 教程中的 rosrun、roslaunch、catkin_make 等关键字在 ROS 2 环境中没有直接对应命令——不是路径变了,而是底层机制已完全不同。

去中心化发现的实际含义:启动任何节点时不依赖全局 master 进程,节点间通过 DDS 的组播(multicast)自动完成服务发现。网络内新增一台机器、加入同一个 ROS_DOMAIN_ID,节点即可互相可见,无需额外配置中心节点。

两代实现的结构差别:ROS 1 的全部交互经过中心 master,ROS 2 的节点直接两两组网、无须中心进程。

flowchart LR
    subgraph r1["ROS 1:中心化"]
        m1(("roscore<br/>master"))
        a1["节点 A"] --- m1
        b1["节点 B"] --- m1
        c1["节点 C"] --- m1
    end
    subgraph r2["ROS 2:DDS 去中心"]
        a2["节点 A"] --- b2["节点 B"]
        b2 --- c2["节点 C"]
        a2 --- c2
    end
    r1 ~~~ r2

2. 发行版与平台配对

REP 2000(ROS Enhancement Proposal 2000)定义了 ROS 发行版与操作系统的版本配对关系。版本基线:

ROS 2 发行版 代号 配对 Ubuntu 支持截止 LTS
Humble Humble Hawksbill 22.04 (Jammy) 2027-05 是
Iron Iron Irwini 22.04 2024-11 否
Jazzy Jazzy Jalisco 24.04 (Noble) 2029-05 是
Rolling Rolling Ridley 最新开发版 滚动 否

实操基线选 Humble(搭配 Ubuntu 22.04)。选版原则:生产与长期维护用 LTS,尝鲜与新硬件支持用最新非 LTS,避免在 Rolling 上开发依赖不稳定 API 的项目。 ROS 1 Noetic 已于 2025-05 EOL。遗留项目若仍需 ROS 1 接口,可通过 ros1_bridge 在有限条件下与 ROS 2 节点互通——两套系统共用话题名、消息类型一致时可桥接,但 QoS 控制面与参数系统无法映射。

3. 安装路径与 rosdep

3.1 apt 安装(推荐)

Ubuntu 22.04 上通过 apt 源安装 Humble:

## 添加 ROS 2 apt 源(需要管理员权限;ros.key 取自 rosdistro 仓库)
sudo apt update && sudo apt install -y software-properties-common curl
sudo add-apt-repository universe
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
  -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
  http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | \
  sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
sudo apt update

## 安装桌面版(含 RViz2、演示包);机器人版不带 GUI
sudo apt install -y ros-humble-desktop

安装完成后 source /opt/ros/humble/setup.bash 即可使用 ros2 命令。将此行加入 ~/.bashrc 可省去每次手动 source。

3.2 源码编译安装

需要自定义 rmw 实现、修改核心包源码或目标平台无预编译包时,从源码构建:

## 先装 vcs 命令行工具(包名 python3-vcstool,安装后可执行命令是 vcs)
sudo apt install -y python3-vcstool
## 在 ~/ros2_humble 下初始化工作空间
mkdir -p ~/ros2_humble/src
cd ~/ros2_humble
vcs import src < <(curl -sL \
  https://raw.githubusercontent.com/ros2/ros2/humble/ros2.repos)
## --skip-keys 跳过已手工安装、不希望 rosdep 接管的依赖键
rosdep install --from-paths src --ignore-src -yr --skip-keys rviz_python
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release

--symlink-install 使修改 Python 文件后不需重新 build;--cmake-args 透传给底层 CMake。

3.3 rosdep:系统依赖解析器

rosdep 读取 package.xml 中的 <depend> 声明,将其映射到当前平台的 apt 包名并批量安装。安装工具本身:

sudo apt install -y python3-rosdep
sudo rosdep init    # 写入 /etc/rosdep/sources.list.d/,只需一次
rosdep update       # 拉取索引,无需 sudo

典型用法——在 colcon build 前自动补齐编译依赖:

cd ~/ros2_ws
rosdep install --from-paths src --ignore-src -r -y

--ignore-src 排除本地 src 中已有的包(避免把自开发包当外部依赖去 apt 搜);-r 跳过 apt 源中不存在的条目而非中断。

判据:rosdep 管的是系统依赖

rosdep 解析的是 package.xml 里声明的系统级依赖(apt 包);Python 包与工具链仍由 pip / conda 各自管——两者不要互相替代。

3.4 社区一键脚本(鱼香ROS)

国内网络访问 packages.ros.org 较慢时,可以用社区维护的一键脚本省去逐项换源与安装。鱼香ROS(fishros)的安装脚本把「换国内镜像源 + 安装 ROS + 常用工具」打包成菜单式向导:

wget http://fishros.com/install -O fishros && . fishros   # 菜单式:选一键安装 / 换源 / Docker 等

按提示选发行版(Humble)与安装选项即可,结束后仍用 source /opt/ros/humble/setup.bash 进入环境。它只是把 3.1–3.3 的手工步骤自动化,不改变安装结果的结构——包依旧落在 /opt/ros/humble,rosdep 与 colcon 的用法不变。

三条边界:脚本由社区维护,内容、镜像源与附带工具随维护者更新,执行前应通读一遍或至少确认它改了哪些文件(常见是 /etc/apt/sources.list.d/ 与 pip 源);换源会覆盖机器上既有的 apt 源配置,多 ROS 版本共存或已有自定义源的机器,优先走 3.1 的手工路径、只把 packages.ros.org 换为镜像;装完用第 8 节的 ros2 doctor 与 printenv | grep ROS 复核 ROS_DISTRO、RMW_IMPLEMENTATION 是否符合预期。

4. Overlay 与 setup.bash 加载链

ROS 2 的环境加载机制是多层的,理解加载顺序可解释"为什么自己编译的包覆盖了系统包"这类问题。

underlay 是系统安装的 ROS 2 基础环境(/opt/ros/humble/setup.bash);overlay 是在 underlay 之上叠加的工作空间环境(~/ros2_ws/install/setup.bash)。source 的顺序决定优先级:

## 典型加载顺序——先 base,再工作空间
source /opt/ros/humble/setup.bash           # underlay:系统 ROS 2
source ~/ros2_ws/install/setup.bash          # overlay:本地工作空间,同名包覆盖 underlay

每个 setup.bash 并不逐个执行 shell 脚本,而是用 _local_setup_util_sh.py 汇总各级 prefix 下 share/<pkg>/hook/*.dsv(path.dsv、pythonpath.dsv、env.dsv 等),按累积顺序设置 PATH、PYTHONPATH、AMENT_PREFIX_PATH、CMAKE_PREFIX_PATH 等变量。RMW_IMPLEMENTATION 不在加载链里——中间件实现由启动时的环境变量或节点的 --rmw-implementation 参数决定。

加载链的效果:ros2 pkg list 会列出 overlay 中新增的包;若 overlay 中编译了与 underlay 同名的包版本,运行时加载 overlay 版本——这就是修改核心包源码后"局部替换而不影响系统"的方式。

移除一个 overlay 只需重开终端,或按逆序重新 source 前一层 setup.bash。ROS 的 setup 脚本不提供 deactivate 函数——该命令属于 Python 虚拟环境(virtualenv/conda),两者常同时使用,容易混淆。

坑:setup.bash 叠加顺序反了

underlay 必须在前、工作空间在后;顺序颠倒时 ROS_DISTRO 与包路径会被旧环境覆盖,表现为“编译过了但 ros2 run 找不到包”。

5. colcon 构建与测试

colcon 是 ROS 2 的构建调度工具,取代 ROS 1 的 catkin_make / catkin build。它自动发现工作空间内的包、按拓扑序编译、处理并行与日志。

5.1 基本命令

mkdir -p ~/ros2_ws/src       # 工作空间根目录
cd ~/ros2_ws
colcon build                  # 编译 src/ 下全部包
colcon build --packages-select my_pkg   # 只编译指定包
colcon build --packages-up-to my_pkg    # 编译 my_pkg 及其依赖链
colcon test                   # 运行全部包的测试
colcon test-result --verbose  # 汇总测试结果

构建产物默认输出到 build/、install/、log/ 三个目录。install/ 是 source 的目标,--merge-install 选项可让各包安装到同一前缀而非子目录。

5.2 与 CMake/Python 后端的衔接

colcon 本身只负责调度——实际编译由每个包的 setup.py(ament_python)或 CMakeLists.txt(ament_cmake)执行。colcon build --cmake-clean-cache 可清除 CMake 缓存解决陈旧配置问题。--event-handlers console_direct+ 让编译输出直接打到终端而非日志文件,适合调试单包编译错误。

6. package.xml 与依赖声明

package.xml(format 3)是包的身份与依赖声明文件。四类依赖语义:

标签 含义 典型场景
<buildtool_depend> 编译时需要(构建工具自身) ament_cmake, ament_python
<depend> 编译 + 运行都需要的通用依赖 rclcpp, std_msgs, sensor_msgs
<exec_depend> 仅运行时需要 Python 包的 launch, 调试工具
<test_depend> 仅测试时需要 ament_lint_auto, pytest

一个最小 Python 包的 package.xml:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>my_py_pkg</name>
  <version>0.1.0</version>
  <description>示例 Python 包</description>
  <maintainer email="dev@example.com">dev</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_python</buildtool_depend>  <!-- 指定使用 ament_python 后端 -->

  <exec_depend>rclpy</exec_depend>                    <!-- 运行时依赖 rclpy -->
  <exec_depend>std_msgs</exec_depend>

  <test_depend>ament_copyright</test_depend>          <!-- 代码合规测试 -->
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>             <!-- 构建类型标记 -->
  </export>
</package>

<exec_depend> vs <depend>:C++ 包的依赖通常在编译期需要头文件,写 <depend> 一步到位;纯 Python 包没有编译步骤,依赖只给 <exec_depend> 即可。

7. ament_cmake 与 ament_python

两种构建后端按语言主线分工:

维度 ament_cmake ament_python
适用语言 C++(rclcpp)、混合 C++/Python 纯 Python(rclpy)
构建文件 CMakeLists.txt setup.py
安装产物 可执行文件 + CMake config + Python 模块 Python egg
接口生成 原生支持 rosidl_generate_interfaces() 不支持消息/服务定义(需单独建 ament_cmake 包)
定义位置 find_package(ament_cmake REQUIRED) from setuptools import setup

两后端的差别不止构建文件:安装产物位置、接口生成能力与下游发现方式都不同,选错后端往往要到 ros2 run 或 find_package 阶段才暴露。

7.1 ament_cmake:C++ 与混合包

CMakeLists.txt 骨架:

cmake_minimum_required(VERSION 3.8)
project(my_cpp_pkg)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/talker.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS talker DESTINATION lib/${PROJECT_NAME})
ament_package()

ament_target_dependencies 把 find_package 解析到的头文件路径、链接库与编译定义绑定到 target。四处位置决定这个包能不能被找到:

  • find_package 与 ament_target_dependencies 成对出现:每加一个依赖两边各加一行,少一边就是编译期找不到头文件、或链接期找不到库。
  • install(TARGETS ... DESTINATION lib/${PROJECT_NAME}):ros2 run 只在 lib/<包名>/ 里找可执行文件,装到别处等于没装。
  • ament_package() 必须是最后一句:它写入包索引与 CMake config,漏掉时 ros2 pkg list 找不到本包。
  • 导出面:下游要对本包 find_package 并链接其中的库时,还要用 ament_export_dependencies 与 ament_export_targets 声明导出;纯可执行包不需要。

7.2 ament_python:纯 Python 包

setup.py 骨架(与上面 CMake 骨架对应同一组能力):

from setuptools import find_packages, setup

package_name = "my_py_pkg"

setup(
    name=package_name,
    version="0.0.1",
    packages=find_packages(exclude=["test"]),
    data_files=[
        # ament 索引标记(空文件)与元数据:缺 resource/<pkg> 时 ros2 pkg list 找不到本包
        ("share/ament_index/resource_index/packages", ["resource/" + package_name]),
        ("share/" + package_name, ["package.xml"]),
        ("share/" + package_name + "/launch", ["launch/demo.launch.py"]),
    ],
    install_requires=["setuptools"],
    zip_safe=True,
    maintainer="your name",
    maintainer_email="you@example.com",
    description="Minimal ament_python package",
    license="Apache-2.0",
    tests_require=["pytest"],
    entry_points={
        # 生成命令:ros2 run my_py_pkg talker 即调用 talker.py 的 main()
        "console_scripts": ["talker = my_py_pkg.talker:main"],
    },
)

配套的 setup.cfg 只有四行,但它决定 ros2 run 能否找到命令:

[develop]
script_dir=$base/lib/my_py_pkg
[install]
install_scripts=$base/lib/my_py_pkg

三个字段各管一件事:entry_points 把 模块:函数 注册成可执行命令;setup.cfg 把脚本装到 lib/<包名>/(ros2 run 只在这个目录里找);data_files 把 package.xml 与 launch/ 拷进 share/<包名>/,get_package_share_directory() 才取得到。改完这些文件重新 colcon build 即生效;开发期用 --symlink-install 可免去“改 Python 就重装”。

ament_python 不能生成接口:需要自定义 .msg / .srv / .action 时另建一个 ament_cmake 接口包(第 3 章给出接口包模板),Python 包只声明对它的依赖——这就是“Python 包生成不了接口”的根因所在。包目录也要满足 find_packages() 的约定——my_py_pkg/__init__.py 与空的 resource/my_py_pkg 都要在,缺任一个 ros2 pkg list 与 ros2 run 都找不到本包。

8. ros2 doctor 与环境诊断

ros2 doctor 是内置环境检查工具,一次运行可检测常见配置问题:

ros2 doctor --report

输出覆盖:ROS 2 版本与架构、已加载的 rmw 实现、DDS 中间件版本、ROS_DOMAIN_ID 当前值、网络多播可达性、package.xml 解析异常、重复包名、rosdep 数据库状态。

典型排查场景——安装了多个 rmw 实现导致通信异常:

## 查看当前生效的 rmw
ros2 doctor --report | grep -i "middleware\|RMW"
## 显式指定(Humble 默认 rmw_fastrtps_cpp)
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

CycloneDDS 的配置文件形态:RMW_IMPLEMENTATION 只换实现,接口级参数(网卡白名单、多播开关、域映射)走 cyclonedds.xml,用 CYCLONEDDS_URI=file:///path/cyclonedds.xml 指给进程;多机部署常再用 ROS_DOMAIN_ID 做逻辑隔离,并在 XML 里固定网卡避免选错接口。脚本化交付时这两个变量与 RMW_IMPLEMENTATION 一起在启动脚本里注入(source agent_dds_env.sh 一类),比让使用者手工 export 可靠——环境变量的生命周期跟进程走,漏一处就退回默认网卡与默认域。

ros2 daemon 是 CLI 查询的后台缓存进程。ros2 node list、ros2 topic list 等命令由 daemon 代理 DDS discovery,避免每次 CLI 调用都触发一次完整的网络发现。daemon 挂掉或缓存过期时的修复:

ros2 daemon stop && ros2 daemon start

9. 要点

  • ROS 2 的三处架构断裂(去中心发现、DDS 通信、ament 构建)是 ROS 1 经验大面积失效的根因,后续命令与概念都建立在这三条之上。
  • 版本配对遵循 REP 2000;当前基线 Humble + Ubuntu 22.04,EOL 2027-05。
  • setup.bash 的加载顺序决定包的可见性与优先级;overlay 覆盖 underlay 是自定义修改的标准做法。
  • package.xml format 3 的四类依赖标签各有语义,Python 包无编译步骤可只写 <exec_depend>;接口定义必须走 ament_cmake。
  • ros2 doctor 与 ros2 daemon 是环境自检的第一手工具;rmw 多实现共存时显式指定 RMW_IMPLEMENTATION 避免通信不一致。

参考文献与延伸阅读

  • ROS 2. Humble 文档. 文档
  • ROS Enhancement Proposals. REP 2000: ROS 2 Releases and Target Platforms. 规范
  • ROS 2 Design. 设计文档. 文档