从0到1独立搭建机器人软件系统
我们接下来按这条主线学
机器人系统工程师
│
├── ① 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 任务。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)