我们接下来按这条主线学

机器人系统工程师
│
├── ① ROS2基础
│   ├── Workspace / Package
│   ├── Node
│   ├── Topic
│   ├── Service
│   ├── Action
│   ├── Parameter
│   ├── Launch
│   └── rosbag
│
├── ② ROS2机器人基础设施
│   ├── TF / TF2
│   ├── URDF / Xacro
│   ├── RViz
│   └── diagnostics
│
├── ③ C++工程化
│   ├── 类 / 接口
│   ├── RAII
│   ├── 智能指针
│   ├── 多线程
│   ├── Strategy
│   ├── Factory
│   ├── Adapter
│   └── Observer
│
├── ④ 标准机器人驱动
│   ├── CAN / EtherCAT设备
│   ├── 初始化
│   ├── 状态机
│   ├── 心跳
│   ├── 超时
│   ├── 重连
│   └── diagnostics
│
├── ⑤ ros2_control
│   ├── Hardware Interface
│   ├── Controller Manager
│   ├── Joint State
│   └── Controller
│
├── ⑥ SDK产品化
│   ├── 统一接口
│   ├── 配置
│   ├── 插件
│   ├── 错误码
│   ├── API
│   └── 安装发布
│
├── ⑦ 网络与DDS
│   ├── UDP
│   ├── TCP
│   ├── RS485
│   ├── IP / Route / DNS
│   ├── Docker网络
│   └── DDS QoS
│
├── ⑧ 应用层
│   ├── 状态机
│   ├── 任务队列
│   ├── 优先级
│   ├── Cancel / Pause / Resume
│   └── 异常恢复
│
├── ⑨ 软件测试
│   ├── GoogleTest
│   ├── Mock
│   ├── CTest
│   ├── Coverage
│   └── CI/CD
│
└── ⑩ 最终项目
    └── ROS2多设备统一SDK与机器人驱动平台

这条路线的关键是:不要把 ROS2 当作一套命令去背。

你真正要理解的是:

机器人硬件
    ↓
驱动层
    ↓
SDK层
    ↓
ROS2通信层
    ↓
机器人功能模块
    ↓
应用/任务层

ROS2只是贯穿其中的一套“机器人软件基础设施”。


ROS2到底是什么?

我们先不敲代码,先把脑子里的模型建立起来,很多刚学 ROS2 的人会觉得:

ros2 topic list
ros2 node list
ros2 run
ros2 launch
colcon build

命令很多,很乱。其实 ROS2 的核心非常简单:

让很多彼此独立的程序能够方便地交换数据。

例如你现在做人形机器人动捕,可以想成:

动捕程序
    ↓
GMR
    ↓
机器人控制

如果全部写在一个程序:

int main()
{
    read_mocap();
    run_gmr();
    send_robot();
}

开始的时候很舒服,但是以后你想增加:

MuJoCo
日志
RViz
状态监控
录包
机器人真机
调试工具

程序就会越来越大,ROS2的思想是:

Mocap Node
     ↓
    ROS2
     ↓
GMR Node
     ↓
    ROS2
     ↓
Robot Node

每个程序只负责自己的事情,这种独立程序,在 ROS2 里叫:Node,也就是:

节点。


Node到底是什么?

不要把 Node 想复杂。

它本质就是:

一个运行中的程序模块

例如:

xsens_node
gmr_node
robot_driver_node
imu_node
logger_node

每个节点干一件事情。

例如:

xsens_node

职责:
读取动捕数据

然后:

gmr_node

职责:
人体动作
↓
机器人动作

再例如:

robot_driver_node

职责:
机器人指令
↓
CAN / EtherCAT
↓
电机

所以第一条非常重要的工程思想就是:

一个大型机器人程序,不是一个巨大 main(),而是很多职责清晰的模块。


那这些Node怎么交流?

ROS2主要给你四种方式。

Topic
Service
Action
Parameter

暂时先记住这个关系:

持续不断的数据
        ↓
      Topic

问一次,回答一次
        ↓
     Service

执行一个需要时间的任务
        ↓
      Action

程序的配置
        ↓
    Parameter

这是后面非常重要的一张图。


Topic

假设 IMU 一直发送:

姿态
角速度
加速度

50 Hz:

第1帧
第2帧
第3帧
第4帧
...

这就是典型的:

Topic

比如:

/imu/data

IMU节点:

imu_driver
     │
     │ publish
     ↓
 /imu/data

其他节点想要这个数据:

state_estimator
     ↑
     │ subscribe
     │
 /imu/data

因此会看到两个极其重要的词:

Publisher
Subscriber

Publisher:

发布数据。

Subscriber:

接收数据。

你可以把它类比成广播。

电台
 ↓
广播频道
 ↓
无数收音机

ROS2:

Publisher
 ↓
Topic
 ↓
Subscriber
Subscriber
Subscriber

这也是为什么 Topic 特别适合:

IMU数据
关节状态
激光雷达
相机
机器人状态
动捕数据
控制指令

Service

但是有些东西不应该不停发送。

例如:

“机器人现在是否已经初始化?”

你问一次:

请求
↓
机器人驱动
↓
回答

类似:

Client
   │
   │ Request
   ↓
Server
   │
   │ Response
   ↓
Client

这就是:

Service

例如以后我们的驱动可以有:

/reset_motor
/enable_motor
/clear_error

你发送:

enable = true

机器人回答:

success = true

所以:

Topic = 流式通信
Service = 请求/响应


Action

Action刚开始最容易搞不懂。

比如:

“让机器人走到目标位置。”

这个任务可能需要:

10秒

这期间你希望知道:

现在走到哪了?

甚至:

取消任务。

于是:

发送Goal
   ↓
机器人执行
   ↓
Feedback
   ↓
Feedback
   ↓
Feedback
   ↓
Result

而且可以:

Cancel

这就是 Action。

因此以后做机器人任务层:

回零
导航
机械臂移动
执行动作
抓取

Action非常常见。


Parameter

再比如你的机器人程序里面有:

double max_speed = 1.0;
double timeout = 0.5;
std::string can_interface = "can0";

如果全写死:

max_speed = 1.0;

每次修改都要:

改代码
↓
重新编译
↓
重新运行

很麻烦。

ROS2允许:

max_speed: 1.0
timeout: 0.5
can_interface: can0

这些就是:

Parameter

以后甚至可以配置:

robot:
  can_interface: can0
  baudrate: 1000000
  timeout_ms: 100

motor:
  max_velocity: 3.0
  max_torque: 20.0

这就开始有“产品化软件”的味道了。


把今天这几个概念放进真实机器人

假设以后我们自己做:

ROS2多设备统一SDK与机器人驱动平台

最初架构可能长这样:

            ROS2 Application
                   │
          ┌────────┴────────┐
          │                 │
       Service            Action
          │                 │
          ↓                 ↓
        Robot Driver Node
          ↑              ↓
       Topic            Topic
          │              │
     command          joint_state
          │              │
          └──────┬───────┘
                 ↓
            Device SDK
                 ↓
        ┌────────┴────────┐
        ↓                 ↓
     CAN Motor       Sim Sensor

看到这里,你应该开始发现:我们实际上要做的是把:

原来:

C++程序
↓
CAN
↓
设备

升级成:

ROS2
↓
Driver
↓
SDK
↓
CAN / EtherCAT
↓
设备

这正是机器人系统工程师的重要能力。


Workspace、Package、colcon 到底是什么?

这几个词刚接触 ROS2 时特别容易混,先不看定义,直接看最终我们的项目:

humanoid_ros2_system/
├── mocap_driver
├── gmr_retarget
├── robot_output
├── humanoid_description
├── mujoco_bridge
├── ethercat_driver
├── ethercat_ros2_control
└── system_tests

你以后不会把这么多东西写进一个 C++ 工程。

ROS2 会把它们组织成:

Workspace
    │
    └── src/
         │
         ├── Package A
         │
         ├── Package B
         │
         ├── Package C
         │
         └── Package D

先记一句:

Workspace 是整个开发工作区,Package 是里面一个个相对独立的软件模块。


一、Workspace 是什么?

假设我们建立:

~/humanoid_ros2_ws

这个就是一个:ROS2 Workspace。你可以把它理解成:

一个“大项目文件夹”。

以后你的动捕、GMR、MuJoCo、EtherCAT,都可以放在这个 Workspace 里面。

比如:

humanoid_ros2_ws/
│
├── src/
│   ├── mocap_driver/
│   ├── gmr_retarget/
│   ├── mujoco_bridge/
│   └── ethercat_ros2_control/
│
├── build/
├── install/
└── log/

这四个目录非常重要。


二、src 是干什么的?

src 最容易理解:

src = source

也就是:

你的源代码放这里。

比如:

src/
├── mocap_driver
├── gmr_retarget
└── robot_output

这些目录通常就是一个个:

ROS2 Package

官方 ROS2 教程也建议 Workspace 下使用 src 目录组织各个 Package。(Cyberlab FTP)


三、Package 又是什么?

Package 可以理解成:

ROS2 项目中的一个功能模块。

例如:

mocap_driver

负责:

PNLink / Xsens / Axis
        ↓
    读取人体姿态
        ↓
发布 ROS2 Topic

那么我们就把它做成一个 Package。

另一个:

gmr_retarget

负责:

人体姿态
   ↓
GMR
   ↓
机器人关节目标

它又是一个 Package。

所以:

Workspace
│
├── Package:mocap_driver
│
├── Package:gmr_retarget
│
└── Package:robot_output

非常像:

一家公司
│
├── 动捕部门
├── GMR部门
└── 输出部门

Workspace 是公司。

Package 是部门。

Node 则可以理解成部门里面实际干活的人。


四、Package 和 Node 不一样

这是新手特别容易混淆的地方。

比如:

mocap_driver

这是:

Package

里面可能有:

mocap_driver_node
mocap_monitor_node
mocap_test_node

三个 Node。

关系是:

Package
│
├── Node
├── Node
└── Node

所以:

Package 是代码组织单位。

而:

Node 是程序运行单位。

这一点非常重要。


五、那 colcon 是什么?

现在我们已经写了:

mocap_driver
gmr_retarget
robot_output

这些都是 C++ 代码,C++ 代码不能直接运行,必须经历:

.cpp
 ↓
编译
 ↓
可执行程序

普通 C++ 项目可能使用:

cmake
make

ROS2 项目一般使用:

colcon build

你可以先把:

colcon

理解成:

ROS2 工作空间的统一编译工具。

官方 ROS2 教程也是使用 colcon 构建整个 Workspace。(ROS 2 Repository)


六、colcon 不等于 CMake

这里顺手把一个以后经常遇到的问题讲清楚。

你会同时看到:

CMake
ament_cmake
colcon

很容易乱。

先建立这个关系:

           colcon
              ↓
       找到所有 Package
              ↓
        调用构建系统
              ↓
         ament_cmake
              ↓
            CMake
              ↓
            g++
              ↓
          C++程序

先不用深究内部机制,可以粗略理解:CMake,负责告诉编译器:

有哪些.cpp
链接哪些库
生成什么程序

ament_cmake

ROS2 在 CMake 上面加的一套 ROS 工程规则,例如:

find_package(rclcpp REQUIRED)

告诉系统:

我要使用 ROS2 的 C++ 库。

官方 ROS2 C++ Package 使用 ament_cmake 作为常见构建类型,而 colcon 用于构建工作空间。(Cyberlab FTP)


colcon

站得更高,假设:

src/
├── A
├── B
├── C
├── D
├── E
└── F

你不用进入每一个目录:

cd A
cmake ...
make

cd B
cmake ...
make

而是在 Workspace 根目录:

colcon build

它帮你组织构建,所以记:

CMake
↓
构建一个C++工程

ament_cmake
↓
让CMake适应ROS2 Package

colcon
↓
管理、构建整个ROS2 Workspace

现在知道这个程度完全够。


七、我们现在真正创建自己的 Workspace

因为真机环境是 Ubuntu 22.04,我们这条实战主线优先按:

Ubuntu 22.04
+
ROS2 Humble
+
C++

来做,先打开终端,检查 ROS2:

echo $ROS_DISTRO

理想情况下应该看到:

humble

如果没有输出,可以:

source /opt/ros/humble/setup.bash

再执行:

echo $ROS_DISTRO

八、创建我们的正式学习工作区

不要叫官方教程里的:

dev_ws

我们直接为以后项目准备:

mkdir -p ~/humanoid_ros2_ws/src

进入:

cd ~/humanoid_ros2_ws

看看:

tree

如果没有 tree

sudo apt install tree

应该类似:

humanoid_ros2_ws/
└── src/

现在这里还什么都没有。


九、创建第一个 Package

进入:

cd ~/humanoid_ros2_ws/src

执行:

ros2 pkg create --build-type ament_cmake mocap_demo --dependencies rclcpp std_msgs

这条命令暂时不用背。

拆开看。

ros2 pkg create

意思:

创建 ROS2 Package。


--build-type ament_cmake

意思:

我要写 C++ ROS2 Package。


mocap_demo

Package 名字。


--dependencies rclcpp std_msgs

告诉它:我的项目依赖:

rclcpp
std_msgs

其中:

rclcpp

非常重要,它就是:

ROS2 的 C++ 客户端库。

以后我们写:

#include <rclcpp/rclcpp.hpp>

基本都会碰到它。


十、现在看看 Package 长什么样

执行:

cd ~/humanoid_ros2_ws
tree src

应该类似:

src/
└── mocap_demo
    ├── CMakeLists.txt
    ├── include
    │   └── mocap_demo
    ├── package.xml
    └── src

这里先认识两个文件。

package.xml

可以理解成:

这个 Package 的身份证。

里面写:

我叫什么
版本多少
作者是谁
依赖什么

比如:

<name>mocap_demo</name>

<depend>rclcpp</depend>
<depend>std_msgs</depend>

以后安装依赖、发布 Package 都会用它。


十一、CMakeLists.txt 是什么?

它是:

告诉 CMake 怎么编译这个 Package。

以后我们会逐渐看到:

find_package(rclcpp REQUIRED)

add_executable(pose_publisher src/pose_publisher.cpp)

ament_target_dependencies(
  pose_publisher
  rclcpp
)

install(
  TARGETS pose_publisher
  DESTINATION lib/${PROJECT_NAME}
)

现在先不要背。

你只要知道:

.cpp代码
   ↓
CMakeLists.txt
   ↓
告诉编译器怎么编
   ↓
生成程序

即可。


十二、第一次 colcon build

现在:

cd ~/humanoid_ros2_ws

注意:

不要在 src 里面执行。

执行:

colcon build

正常情况下会出现类似:

Starting >>> mocap_demo
Finished <<< mocap_demo

Summary: 1 package finished

然后再执行:

tree -L 1

你会突然发现:

humanoid_ros2_ws/
├── build
├── install
├── log
└── src

这就是为什么我前面先给你看这四个目录。


十三、build / install / log 是什么?

build

build/

可以理解成:

编译过程中产生的中间文件。

一般不要自己改。


install

install/

非常重要。

最终编译好的:

程序
库
配置
环境文件

很多东西会被安装到这里。


log

log/

记录:

colcon构建日志

编译失败时这里可以帮助排查。


所以:

src
= 你写的东西

build
= 编译中间产物

install
= 编译后的可用结果

log
= 构建日志

这一组一定要理解。


十四、为什么 build 完还要 source?

这是 ROS2 新手的又一个大坑。

你编译:

colcon build

之后,并不代表当前终端马上就认识你新编译的软件。

通常还要:

source install/setup.bash

于是整个流程变成:

修改代码
   ↓
colcon build
   ↓
source install/setup.bash
   ↓
ros2 run ...

以后你会执行这个流程无数次。


十五、为什么还要 source /opt/ros/humble/setup.bash?

这里其实有两层环境。

第一层:

source /opt/ros/humble/setup.bash

意思类似:

把系统安装的 ROS2 Humble 环境加载进来。

这叫:

Underlay

然后:

source ~/humanoid_ros2_ws/install/setup.bash

意思:

再把我自己写的项目环境叠上去。

这叫:

Overlay

直观来看:

Ubuntu
  ↓
ROS2 Humble
  ↓
你的 humanoid_ros2_ws

所以以后你的 GMR、动捕、EtherCAT Package 都相当于建立在 ROS2 Humble 之上。


十六、把它和你原来的 C++ 项目对应起来

你以前可能是:

gmr_project/
├── src/
├── include/
└── CMakeLists.txt

然后:

mkdir build
cd build
cmake ..
make

现在变成 ROS2:

humanoid_ros2_ws
│
└── src
    │
    ├── mocap_driver
    │   ├── src
    │   ├── include
    │   ├── CMakeLists.txt
    │   └── package.xml
    │
    ├── gmr_retarget
    │
    └── robot_output

然后你不再分别:

编译 mocap
编译 GMR
编译 robot_output

而是:

cd ~/humanoid_ros2_ws

colcon build

这就是 ROS2 工程组织方式开始给你带来的价值。


这一课真正需要记住的只有5句话

Workspace
= 整个ROS2开发工作区
Package
= 一个相对独立的软件功能模块
Node
= Package里面运行起来的功能程序
ament_cmake
= ROS2 C++ Package常用构建体系
colcon build
= 构建整个Workspace

再加上最重要的目录:

humanoid_ros2_ws/
│
├── src       ← 我写的
├── build     ← 编译中间文件
├── install   ← 编译结果
└── log       ← 编译日志

今天你的实际练习

在 Ubuntu 22.04 机器上依次执行:

source /opt/ros/humble/setup.bash

mkdir -p ~/humanoid_ros2_ws/src

cd ~/humanoid_ros2_ws/src

ros2 pkg create \
  --build-type ament_cmake \
  mocap_demo \
  --dependencies rclcpp std_msgs

cd ~/humanoid_ros2_ws

colcon build

source install/setup.bash

tree -L 3

如果这一步成功,下一课我们就不再讲概念了,直接写第一个真正属于你项目的 C++ ROS2 节点:

pose_publisher
       │
       │ 20ms
       ↓
/mocap/human_pose

然后再写:

/mocap/human_pose
       ↓
pose_subscriber
       ↓
帧号
频率
延迟

到那里你会第一次真正把:

C++ → Node → Publisher → Topic → Subscriber

整条链跑起来。

如果你想按学习计划稳定推进,我也可以每天提醒你继续当天的 ROS2 任务。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐