/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
摘要
启元 Q1 SDK 的示例是双语言交付:18 个 Python 之外还有 18 个 C++。跑 C++ 路线要把 OpenCV 4.13.0 的 60MB 源码和 ruckig 轨迹规划库一起从源码编起。本文在 20 核 WSL2 环境实测从零全量编译仅 79 秒左右,并解锁依赖 ruckig 的 joint_control(500Hz 轨迹环零超限,示例通过记录 14/18);随后按官方"方式 A"手写第一个 C++ 节点 touch_reaction(摸头→开心表情+呼吸灯,含两个真实编译坑的修复)。全部实战对着配套开源的机器人模拟器进行,DDS 消息层语言无关,同一个"机器人替身"两门语言通用,旗舰速度示例实测位移 1.118 米。附 C++/Python 选型决策表与 4 坑速查。 AtomGit仓库地址: https://atomgit.com/user3401/q1_simulator

一、为什么 C++ 这半边值得单独写一篇
三个现实理由:
本篇无真机,实战环节用配套仓库里的开源模拟器 q1_simulator 充当机器人:一个 ROS2 节点,按官方《接口说明》实现机器人侧的全部角色(16 个服务端 + 8 个话题,含输入源优先级仲裁与动作状态机),部署三步:
git clone https://atomgit.com/user3401/q1_simulator.git ~/workspace/q1_simulator
cd ~/workspace/q1_simulator && colcon build && source install/setup.bash
ros2 run q1_simulator simulator # 它就是你的"机器人"
它给本文的独特优势:**机器人模拟器在 DDS 消息层工作,与客户端语言无关。**C++ 示例发出的服务调用与 Python 发出的字节流一模一样,同一个"替身"两门语言通用。
二、全量编译到底在编什么
先用一张图看清 colcon 的构建依赖图(原创绘制):

colcon 视角下的 SDK 构建图,aimdk_msgs 是一切的基础;aimdk_opencv 从源码构建 OpenCV 4.13.0;ruckig_for_primebot 同时产出 C++ 库与 nanobind Python 模块;aimdk_examples_cpp 链接前三者,foreach 循环把 18 个示例统一注册为可执行目标。
为什么 SDK 不直接给预编译库?官方指南 §2.1.7-B 说得直白:预编译 .so 无法适配用户自己的 CPU 架构、glibc、C++ ABI 与 FFmpeg 环境,源码交付是跨平台稳妥解。代价就是首次全量编译的时间成本,这也是本篇要量化的东西。
三个源码包的角色:
| examples/opencv/ | OpenCV 4.13.0 完整源码(ament 化为 aimdk_opencv) | 60MB 源码 | 支撑 get_video_stream 的 RTSP 解码(components:core/imgproc/imgcodecs/videoio) |
| examples/ruckig_for_primebot/ | Ruckig 轨迹规划库 + nanobind 包装 | ~1MB 源码 | joint_control 的实时关节轨迹规划(C++ 库 + Python 模块双产物) |
| examples/cpp/ | 18 个示例节点 | — | foreach 自动注册,缺源文件直接 FATAL_ERROR |
三、编译实录:依赖、命令与耗时拆解
3.1 依赖清单
按官方指南 §2.1.7-B,C++ 侧在 Python 侧依赖(colcon/rosidl/python3-dev)之外还需要:
sudo apt install -y git cmake build-essential pkg-config \\
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \\
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \\
libyaml-cpp-dev ffmpeg
python3 -m pip install –user nanobind # ruckig Python 模块构建需要
(FFmpeg/GStreamer 开发库是 OpenCV videoio 后端的依赖;不需要装系统 libopencv-dev,SDK 用自己源码构建的版本。)
3.2 从零全量编译
cd ~/workspace/sdk_q1
rm -rf build install log # 从零统计耗时
source /opt/ros/humble/setup.bash
time colcon build –packages-up-to aimdk_examples_cpp –symlink-install
实测结果(20 核 / 15GB 内存分给 WSL 的环境,一次通过、零报错):

| aimdk_msgs | 23.7s | 消息协议包(Python 路线只编它) |
| aimdk_opencv | 1min 5s | OpenCV 4.13.0 源码构建,最大头 |
| ruckig_for_primebot | 3.95s | C++ 库 + nanobind Python 模块双产物 |
| aimdk_examples_cpp | 11.6s | 18 个示例可执行目标 |
| 合计 | 79 秒左右(colcon 汇总 1min 17s) | 4 packages finished |
**"全量编译要几十分钟"的印象可以更新了,**前提说清楚:官方对 OpenCV 做过模块裁剪(只构建 core/imgproc/imgcodecs/videoio 四个组件,不含 world 与附加模块),加上 20 核并行,才有这个成绩。低核数机器上会线性变慢(4 核预计 5~8 分钟量级),但"几十分钟"的恐惧感确实可以放下了。
从零全量编译实测:4 包共 79 秒左右一次通过,OpenCV 源码构建占 1 分 10 秒。
四、C++ 示例实战:对着模拟器直接开跑
4.1 为什么机器人模拟器不用改一行代码
DDS 是二进制中间件:C++ 客户端 rclcpp 与 Python 客户端 rclpy 走的是同一个 FastDDS 层,序列化后的字节流完全一致。机器人模拟器应答的每一个服务、发布的每一条状态,C++ 示例照单全收,语言切换发生在客户端,"机器人"毫无感知。
这条经验值得记下:测试替身做在消息协议层而不是 API 层,换客户端语言时它一行都不用改。
4.2 实战三连
三个 C++ 示例对机器人模拟器直接开跑(ros2 run aimdk_examples_cpp <示例名>)。
模拟机器人终端

状态:

全部一次通过:
① get_bms_state——订阅电池状态,输出与 Python 版逐字节一致(同一台"机器人"嘛):voltage: 50.479 V、current: -1.240 A。

② play_emotion——同样打出 Detected ID 10 on Q1, switching to 3003 的机型补丁日志,受理成功 code=0, status=1。

③ mc_locomotion_velocity(旗舰)——完整链路实录:

终端响应:

虚拟机器人:


机器人模拟器侧同步记录 [速度] source=node v=(0.30, 0.00) w=0.00,速度积分后 tf2_echo odom pelvis 报出 Translation: [1.118, 0.000, 0.000]——0.3 m/s × 5 秒的理论值 1.5 米,实测 1.118 米(积分从首条指令的保鲜窗口起算,含指令间隙),量级完全对账。
4.3 意外之喜:joint_control 解锁,通过记录 14/18
若走"只编消息包"的最小路线(跑 Python 示例的常见选择),joint_control 会因缺少 ruckig 轨迹规划模块而缺席——这正是 C++ 全量编译的价值:OpenCV 与 ruckig 两个源码包一次构建,from ruckig_for_primebot import Ruckig 直接可用,当场解锁:
cd ~/workspace/sdk_q1/examples/python
python3 joint_control.py –ros-args \\
-p joint_names:="[left_shoulder_pitch_joint, left_elbow_joint]" \\
-p target_positions:="[-1.0, 2.0]"
实测输出(对着机器人模拟器的关节反馈流):

Trajectory initialized from current=[0.017234, 1.018674] to target=[-1.000000, 2.000000]
Trajectory completed. planned_duration=1.091969 s, runtime=1.092532 s,
cycles=546, actual_hz=499.76, avg_cycle_ms=0.153, max_cycle_ms=0.469, overruns=0
joint_control 随全量编译解锁:500Hz 轨迹环在 WSL2 里零超限,规划与实测误差 0.5ms。
有三个细节值得注意:
\\1. current=[0.017234, 1.018674] 正是机器人模拟器按官方站立位发布的关节值,示例从"机器人"反馈流里读到初值,规划起点对得上;
\\2. 计划 1.0920 秒 vs 实际 1.0925 秒,误差 0.5 毫秒;
\\3. 500Hz 控制环(2ms 周期)在 WSL2 里 零超限、平均周期 0.153ms,"WSL2 跑不了控制"的粗糙印象可以修正为:“对 2ms 周期的软实时任务完全够用”(真机 1kHz 硬实时是另一回事,别混为一谈)。示例源码头部那条"使用前需关闭运控模块并将机器人平躺悬挂"的警告是真机安全要求,对着机器人模拟器练手则无此顾虑。
五、手写第一个 C++ 节点:摸头会"开心"的 touch_reaction
示例跑通只能算会用,往 SDK 里加自己的节点才算开始二开。这一节按官方指南 §3.2-方式 A 的路径,从零写一个 touch_reaction 节点:**订阅头部触摸事件 → 自动播放开心表情(3003)+ 点亮天蓝呼吸灯,3 秒节流。**一个"摸头会开心"的小机器人行为,顺带练到触摸订阅、两个服务调用、异步回调三件事。
5.1 注册:CMakeLists 加一行
SDK 的 examples/cpp/CMakeLists.txt 用 foreach 遍历 EXAMPLE_TARGETS 列表,自动为每个目标找同名 .cpp 编译链接——所以注册只需:
set(EXAMPLE_TARGETS
…
upper_body_control
touch_reaction # ← 加这一行
)

5.2 节点骨架与两个真实的编译坑
核心逻辑三十来行(完整源码在仓库 examples/touch_reaction.cpp):
touch_sub_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
"/aima/hal/touch/state", 1,
[this](aimdk_msgs::msg::TouchState::ConstSharedPtr msg) {
if (msg->event_type ==
aimdk_msgs::msg::TouchState::TOUCH_EVENT_SINGLE_CLICK) {
on_head_pat(); // 节流后 → PlayEmotion(3003) + LedStripCommand
}
});
第一遍编译我撞了两个 API 坑,都是 Humble 版 rclcpp 的实际行为,值得记录:
修完增量编译 5.4 秒,改一次代码重编 5.4 秒,比 Python 的改完即跑多一步,但类型错误在编译期就拦下来了。
5.3 实战:节流精确生效
对机器人模拟器跑 11 秒(每 3 秒被摸一次),双端日志完全对账:

mode=254 是灯带枚举里的 LED_CUSTOM_BREATH(自定义呼吸),r/g/b 与周期仅在自定义模式下生效,这类"字段条件生效"的约定,正是读原始 .srv 定义比读示例代码更能获得的信息。
5.4 顺手聊聊:同一份逻辑的两种语言味道
C++ 旗舰示例与 Python 版的逻辑逐行对应(同样的 DELETE 残留→ADD 注册→50Hz 发布→持锁查询→释放),工程风格却是两种味道,C++ 版用模板函数统一了三种服务的重试逻辑:
// 节选自 examples/cpp/src/mc_locomotion_velocity.cpp
template <typename ServiceT>
typename rclcpp::Client<ServiceT>::SharedFuture
call_service_with_retry(
typename rclcpp::Client<ServiceT>::SharedPtr client,
typename rclcpp::ServiceT::Request::SharedPtr request, …) {
for (int i = 0; i < max_retries; ++i) {
auto future = client->async_send_request(request);
if (rclcpp::spin_until_future_complete(
this->shared_from_this(), future, timeout)
== rclcpp::FutureReturnCode::SUCCESS) {
return future.future.share();
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
…
}
对照 Python 版的同名方法,能看到 SDK 团队在两种语言里,两边的写法是照着同一套思路做的,读懂其中一个,另一个看起来就只是换了个语法。
六、Python 还是 C++:一张决策表
| 环境成本 | 只需编 aimdk_msgs(21.7s) | 全量编译 86s(20 核实测)+ 一批系统依赖 |
| 迭代速度 | 改完即跑 | 每次改动需重新编译(增量秒级) |
| 高频控制 | 500Hz 关节环勉强 | 稳(真机标称路径) |
| 视觉流 | opencv-python 够用 | 源码构建 OpenCV + FFmpeg 后端 |
| 真机部署 | PC 侧脚本 | 运控板/大脑板部署、ARM 交叉编译 |
| 适用阶段 | 原型验证、逻辑联调 | 性能敏感环节、产品化部署 |
本篇的建议与官方一致:验证期用 Python 快跑,定型后把性能敏感部分迁 C++;两者共享同一套 aimdk_msgs 契约,迁移成本主要是语言本身而非协议。
七、排坑实录:4 个坑一张表
全量编译本身零报错,依赖装齐后 colcon build 一路绿灯;坑都藏在实战环节:
| 1 | –ros-args -p forward_velocity:=0.3 传参无效,示例报 Forward 0.00 m/s | C++ 示例与 Python 版一样走 stdin 交互(源码头注释里的 “Parameters” 指交互输入而非 ROS 参数,有误导性)。自动化用管道喂数:`printf “0.3\\n0.0\\n0.0\\n” |
| 2 | 改了 .cpp 源码重跑还是旧行为 | –symlink-install 只软链 Python 侧资源,C++ 目标仍需重新 colcon build(增量 5.4s 实测);确认产物更新再 ros2 run |
| 3 | 编译报 'from_milliseconds' is not a member of 'rclcpp::Duration' | Humble 的 Duration 静态构造只有 from_seconds/from_nanoseconds,毫秒节流写 from_seconds(ms/1000.0) |
| 4 | 编译报 'SharedFuture' is not a member of 'aimdk_msgs::srv::…' | 异步回调参数类型在 Client 侧:rclcpp::Client<ServiceT>::SharedFuture;报错串最底部的 enable_if 行才是真线索 |
两个环境前提:nanobind 缺失会让 ruckig 在配置阶段报错(pip3 install –user nanobind);OpenCV 的 videoio 依赖 FFmpeg/GStreamer 开发库,少装会在链接期报 undefined reference,按 3.1 清单一次装齐最省心。
八、总结
回头看这几个数:从零全量编译 79 秒,“C++ 路线很重”这个顾虑可以放下了;joint_control 解锁后官方示例过了 14 个;C++ 旗舰示例驱动机器人模拟器走了 1.118 米,说明模拟器做在 DDS 消息层确实省事,换语言不用换“机器人”,以后接真机也只是把它换成真的运控板和大脑板;自己写节点改一次代码重编 5.4 秒,日常迭代不憋屈。
选型建议维持官方口径:验证期 Python 快跑,性能敏感环节与真机部署迁 C++。后续计划补齐 RTSP 合成流(凑 15/18),真机到货后做模拟与实测数据对照。
仓库地址: https://atomgit.com/user3401/q1_simulator。
参考资料


