第 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.xmlformat 3 的四类依赖标签各有语义,Python 包无编译步骤可只写<exec_depend>;接口定义必须走 ament_cmake。ros2 doctor与ros2 daemon是环境自检的第一手工具;rmw 多实现共存时显式指定RMW_IMPLEMENTATION避免通信不一致。