欢迎光临
我们一直在努力

Python 之外:启元 Q1 SDK 的 C++ 编译、示例与手写节点全实录

/* 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++ 这半边值得单独写一篇

三个现实理由:

  • 官方是双语言交付。SDK 的 examples/ 下 Python 与 C++ 示例严格一一对应(各 18 个),《快速入门与集成指南》§3.2 专门给了 C++ 的两种集成路径(SDK 内部新增节点 / FetchContent 独立工程)。但社区文章目前几乎全在讲 Python,C++ 这半边没人趟过。
  • C++ 才是机器人开发的主战场。运控高频环节(500Hz 关节指令)、视觉流处理(RTSP 解码)对时延敏感,真机部署还要交叉编译 ARM;这些场景最终都会落到 C++。这些活最后基本都得 C++ 来干,Python 更适合在前头把逻辑快速验证掉。
  • Python 跑通 ≠ C++ 链路通。Python 示例只需要编译消息包 aimdk_msgs(21.7 秒);C++ 路线会把 OpenCV 4.13.0、ruckig 轨迹规划库全部从源码构建一遍,C++ 路线会把 OpenCV 4.13.0、ruckig 全部从源码构建一遍。
  • 本篇无真机,实战环节用配套仓库里的开源模拟器 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 的实际行为,值得记录:

  • rclcpp::Duration::from_milliseconds() 不存在——Humble 的 Duration 静态构造只有 from_seconds / from_nanoseconds。节流判断改用 Duration::from_seconds(kThrottleMs / 1000.0)。
  • SharedFuture 的作用域在 Client 而非服务类型——异步回调参数要写 rclcpp::Client<aimdk_msgs::srv::PlayEmotion>::SharedFuture,写成 aimdk_msgs::srv::PlayEmotion::SharedFuture 会连出一串模板推导错误,最底下那行 no type named 'type' in enable_if 才是真线索。
  • 修完增量编译 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++:一张决策表

    维度Python (rclpy)C++ (rclcpp)
    环境成本 只需编 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。

    参考资料

  • 启元机器人开发者社区:https://dev.primebot.com/
  • 《PrimeBot SDK 快速入门与集成指南》v1.0.0.0 §2.1.7 / §3.2,SDK 包内文档
  • 本文配套仓库 q1_simulator:https://atomgit.com/user3401/q1_simulator
  • 赞(0)
    未经允许不得转载:171主机测评 » Python 之外:启元 Q1 SDK 的 C++ 编译、示例与手写节点全实录
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址