本文基于启元机器人官方Q1 SDK v1.0.0.0包撰写。文中所有接口名称、枚举编号、频率与参数取自该包内的
aimdk_msgs消息定义、examples/示例代码与随包文档(《PrimeBot_SDK快速入门与集成指南》《接口说明》),引用处标注了文件路径,可直接对照。
拿到一个机器人 SDK,最值得先看的不是示例怎么跑,而是它把"能力"切成了什么形状。这个 SDK 包的顶层很简单:一个aimdk_msgs消息包、一个examples/例程目录、两份 Markdown 文档。但把这三样东西拼起来,整套机器人的技术架构就完整地暴露了出来——它没有发明私有协议,而是把全部能力做成了标准 ROS2 接口。本文不写环境搭建教程,而是顺着源码,逐层讲清:硬件如何组织、消息如何分层、控制权如何仲裁、运动如何安全地下发到关节。
一、整体架构:一条 ROS2 总线上的双板系统
快速入门指南第一节给出了官方拓扑(见《PrimeBot_SDK快速入门与集成指南》):系统由三类节点组成,全部通过局域网 FastDDS 互联。
图 1:启元 Q1 的双板去中心化拓扑。三类节点地位平等,没有中心代理;开发 PC、运控板、大脑板上的程序都通过同一条 ROS2 总线透明访问全量接口。
运控板(10.1.1.100):运动控制大脑,负责底层硬件抽象 HAL、运控算法 MC,以及表情、语音等基础交互服务;
大脑板(10.1.1.101):高阶感知与导航大脑,运行核心 AI 算法、感知、规划和复杂供应商程序;
开发 PC:推荐 Ubuntu 22.04 配 ROS2 Humble,有线直连,静态 IP 配为
10.1.1.99,与两块板处于同一 /24 子网。
README 里有一句话点明了设计取向:"统一通过 ROS2 接口即可控制机器人全部能力,无需关心底层协议细节;开发程序可灵活部署在 PC、运控板或大脑板上,透明访问全量机器人能力接口。"这意味着程序部署在哪台机器上不影响它能调用的接口,差异只在网络延迟和实时性。
把这套系统按软件职责再切一层,可以归纳为六层:硬件本体、双板系统、ROS2 通信层、消息接口层(aimdk_msgs)、业务节点层、以及板载自启动与构建工具。开发者真正写代码的位置是第四、第五层,下面三层由固件维护,SDK 只通过文档和配置文件暴露约定。
二、开发者模式:开放之前,先有一道物理开关
理解架构,要先理解"开放"是如何被管控的。文档 写得很明确:机器人默认处于标准生产模式,该模式下"外部设备无法直接发现机器人节点,网口也无法连接"。也就是说,开发者模式不是一个软件层面的 API 开关,而是在手机 APP 里切换、保存后重新上下电才生效的系统状态。启用须知中还明确提示:开启二次开发功能后将不再享受维保服务。
开发者模式本身又分五个维度(文档 §5),构成一套分层权限模型:
| 层级 | 维度 | 选项 | 含义 |
|---|---|---|---|
| 第一层 | 运行模式 | Standard / Develop | 标准模式为全闭环、只运行官方认证组件;开发模式才开放外部接入 |
| 第二层 | 开发方式 | API / ROS2 | 官方称 RESTful/gRPC 方式暂未支持;当前默认且仅支持 ROS2 |
| 第三层 | 开发层级 | Basic / Advanced | Basic 为"无损能力增强",原厂业务与安全策略完整、外部指令并发注入;Advanced 可关闭特定官方模块,独占传感器裸流、底层运控等核心资源 |
| 第四层 | 领域设置 | 功能领域 | 可选择性接管运动控制、语音交互、作业规划、传感器数据 |
| 第五层 | 能力配置 | 原子开关 | 按勾选自动编排底层配置 |
文档同时如实标注了 v1.0.0.0 的现状:第四层"领域设置"与第五层"能力配置"暂予合并,目前仅生效"运动控制—低层运控开发"一项,该授权允许开发者对全身关节直接下发指令,但要求手动关闭本体高层运动控制;交互、作业、传感器领域的原子能力标注为后续开放。这种"先告诉你哪里能做、再明确告诉你哪里还不能做"的写法,在接口文档里是少见的坦诚。
三、通信底座:ROS2 Humble + FastDDS,参数都是调过的
SDK 没有提供私有 SDK 库,而是固定使用 ROS2 Humble,RMW 实现固定为rmw_fastrtps_cpp(FastDDS)。消息包、服务、节点、QoS、命令行工具全部是标准 ROS2 概念,验证通信甚至不需要编译 SDK:ros2 node list能看到/hal_camera、/hal_audio、/mc_ros2_node等官方节点,ros2 topic list能看到全量话题。
FastDDS 配置直接以源码形式放在aimdk_msgs/config/fastdds_profiles.xml,并通过 colcon 的ament_environment_hooks机制在source install/setup.bash时自动注入RMW_IMPLEMENTATION和FASTRTPS_DEFAULT_PROFILES_FILE环境变量。文件里的数值不是默认值,是为机器人调过的:
UDP 传输:关闭 FastDDS 内置传输,自定义
UDPv4描述符,sendBufferSize和receiveBufferSize均为20971520(20MB);文档 §6.7 要求开发机内核参数net.core.rmem_max/wmem_max不小于该值,并给出了 24MB(25165824)的持久化配置范例;共享内存传输:文件中定义了一个
SHM描述符,segment_size为 200MB,但默认 participant 只启用 UDP,SHM 留给同机多进程场景按需自行开启;网卡绑定:
interfaceWhiteList默认注释。文档 §6.1 特别警告多网卡问题——网线直连时如果还连着 WiFi,DDS 发现报文可能绑定到错误网卡,导致节点发现失败,排查建议就是直连时临时禁用无关网卡。
这几个数值背后是很实际的问题:视频流和高频关节指令属于高频、大包数据,socket 缓冲区不足时会静默丢帧,表现为画面卡顿或"时通时不通"。文档把这一故障的排查路径也写全了:先看ping链路、再看防火墙(DDS 数据传输用随机大端口,ufw默认可能拦截)、再看缓冲区与内核参数。
QoS 策略也遵循数据性质:官方 Python 示例订阅 BMS 这类传感器话题时直接使用 ROS2 标准的qos_profile_sensor_data(BEST_EFFORT+VOLATILE+KEEP_LAST(5)),允许丢帧、不保留历史;而动作切换等服务调用走可靠通道,一次都不能丢。多台机器人共处一个局域网时,用标准的ROS_DOMAIN_ID做隔离。
四、消息接口层:aimdk_msgs 的六个域
aimdk_msgs按职责分成六个子目录,每个目录下再分msg与srv:
| 子模块 | 职责 | 代表性消息/服务 |
|---|---|---|
hal | 硬件抽象 | BMS、关节状态/指令、触摸、JPEG 图像、音频设备、灯带 |
mc | 运动控制 | 动作状态机、输入源、走跑速度、预设动作、自定义关节 |
interaction | 交互 | TTS 播报、表情播放、任务关闭 |
agent | 智能体 | ASR 语音识别结果AsrResult |
config | 配置 | FastDDS profile 与环境钩子 |
common | 公共类型 | 请求头、响应头、通用状态、任务响应 |
能力以三种标准 ROS2 原语暴露:持续数据流走 Topic(如/aima/hal/bms/state),一次性指令走 Service(服务名统一挂载在/aimdk_5Fmsgs/srv/下,aimdk_5Fmsgs是包名aimdk_msgs的 ROS2 转义形式),公共约定走消息头。请求带RequestHeader(时间戳、来源 source),响应带ResponseHeader(状态码 code)。
以电池订阅为例,官方examples/python/get_bms_state.py就是一个标准节点:
# examples/python/get_bms_state.py(节选) from rclpy.qos import qos_profile_sensor_data from aimdk_msgs.msg import Bms self.subscription = self.create_subscription( Bms, "/aima/hal/bms/state", self.callback, qos_profile_sensor_data, )而hal/msg/Bms.msg的字段密度超出一般消费级产品的开放程度:除了电压(mV)、电流(mA,充电为正)、输出功率(mW)、温度、剩余容量(mAh)、循环次数、累计充放电容量,还有厂家/序列号/协议与硬件版本信息,以及一个uint32 status状态字,逐位标注了充电过流、放电过流、短路、电芯过压/欠压、MOSFET 禁止充放电、温度越限、无线充电接入、关机触发等 29 个标志位。它给的不是"还剩 70% 电"这种黑盒结果,而是电池管理系统的原始状态。
关节状态同样原始。hal/msg/JointState.msg每个关节带position、velocity、effort(位置、速度、力矩),以及线圈温度、电机温度、电机电压三个诊断字段——这意味着开发者可以在自己的程序里做电机级的温度监控。相比之下,下发指令用的JointCommand.msg则是position、velocity、effort加上stiffness(刚度)和damping(阻尼),把准直驱关节的柔顺特性也做成了可配参数。
服务返回的状态码体系集中在common/CommonState.msg:SUCCESS=1、FAILURE=2、ABORTED=3、TIMEOUT=4、INVALID=5,运行中为RUNNING=400;另有一个reason枚举,列出动作被拒绝的具体原因——低电量、充电中、动作不在白名单、当前形态不支持、前后左右上方有障碍物、其他任务正在运行、已是目标状态、当前姿态下无转移路径等共 31 种(REASON_SUCCESS=0 至 REASON_TRANSFORM_CONDITION=30)。这是一个容易被忽略但很重要的设计:机器拒绝执行时给出的是可据以处理的原因码,而不是一个笼统的失败。
五、动作状态机:机器人不能"想切就切"
人形机器人的行为之间存在拓扑约束:不能从折叠收纳直接进入行走,必须先过渡到地面姿态、再起身;全身控制、上肢自定义等模式也只在特定状态下开放。Q1 把这套规则做成了显式状态机。
mc/action/McAction.msg用一个文件枚举了全部机型的动作状态:急停段(1 被动、2 阻尼、3 四足躺下)、形态切换段(10 四足转双足、20 反向,含常规、带旋转、前空翻)、四足段 100–199(101 站立、102 走跑、103 越障、104 仿生等)、双足段 200–303(201 站立、202 走跑、300 全身控制、302 上肢录制),以及 Q 系列专用的 1001–1019 段(1001 折叠收纳、1002 走跑、1005 全身控制、1007 站起、1014 二开上肢控制、1015 跑步等)。Q1 与 T1 共用同一套消息定义,差异只在各自使用的编号段——这也是理解 T1 时最省力的部分。
值得注意的是官方没有让开发者自己处理切换顺序。examples/python/common/mc_action_switcher.py(C++ 侧有对应的examples/cpp/common/mc_action_switcher.hpp)内置了一张状态有向图_ACTION_RULES,客户端用 BFS 自动规划从当前状态到目标状态的路径,并逐步下发、逐步等待。下图是按_ACTION_RULES简化的状态拓扑:
图 2:Q1 动作状态有向图(据
mc_action_switcher.py的_ACTION_RULES简化)。图中只画了边,不是所有边都双向;例如收纳只能先到地面姿态,起身由地面姿态触发并自动进入走跑。
例如从折叠收纳到走跑,BFS 规划的结果是:
STORE_DEFAULT → BIPED_GROUND_POSE → STAND_UP(执行后自动进入 BIPED_WALK_RUN)
这里有两个细节。其一,状态机里区分了"桥接状态"与"跳过状态":STAND_UP被记入_AUTOMATIC_NEXT_ACTIONS,下发后机器人会自动进入BIPED_WALK_RUN,而走跑等桥接状态本身不作为需要等待的目标。其二,等待不是固定延时:每一步下发后,切换器以 0.5 秒间隔轮询GetMcAction,直到返回的info.action_desc与目标一致且status非IDLE(0)。McActionStatus只有三个值:IDLE=0、RUNNING=100、TRANSITION=200;服务响应级别的成功与失败则用前面提到的CommonState的 1/2 判定。
频率约束同样是硬编码的:McActionSwitchOptions里minimum_set_action_interval=0.5,每次请求前还另有 0.5 秒前置等待,整个切换有total_timeout总超时(走跑示例传 30 秒)。服务调用本身带 3 秒超时、最多重试 3 次;一旦服务端返回非成功状态,示例不做盲目重试,而是直接交还上层状态机处理。
六、控制权:输入源仲裁与超时脱锁
机器人可能同时被语音 Agent、手机 APP、SDK 节点三方控制,必须有仲裁。Q1 的做法是输入源注册。mc/motion/McInputSource.msg只有三个字段:
string name # 输入源名称(注释中列出内置源:rc/vr/app_proxy/interaction/pnc) int32 priority # 优先级 0-100 int32 timeout # 超时时间(ms),默认 1000任何节点下发运动指令前,先调SetMcInputSource服务注册。McInputAction.msg定义了五种操作:ADD=1001、MODIFY=1002、DELETE=1003、ENABLE=2001、DISABLE=2002;还可以通过GetCurrentInputSource查询当前持有控制权的源。注册之后,速度消息里的source字段必须与注册名一致,否则指令无效。
官方examples/python/mc_locomotion_velocity.py把这套流程写得非常完整,其中有多处教科书式的安全处理:
图 3:输入源注册与释放时序。超时脱锁是最后的安全网:节点崩溃或网线断开时,机器人不会继续执行最后一条速度指令。
具体看代码,工程细节有四处:一是注册前先DELETE一次同名输入源,防止上一次运行未正常释放导致残留;二是发布定时器为 0.02 秒,即50Hz 心跳,超过 1000ms 没有新指令即自动脱锁;三是速度设了死区,输入绝对值小于 0.005 归 0,介于 0.005 和 0.2 之间直接拒绝并退出(前进、侧移阈值 0.2m/s,转向阈值 0.2rad/s),避免噪声被放大成抖动;四是程序专门覆盖了SIGINT处理,阻止 Ctrl+C 触发rclpy.shutdown(),以保证finally块里仍能完成零速度发布和输入源释放。
七、三级运动控制:从"播一个动作"到"写每个关节"
沿着抽象层级往下,Q1 对运动能力的开放分三级。
第一级:预设动作。进入全身控制状态后,调SetMcPresetMotion服务播放内置动作。McPresetMotion.msg同样是 Q 与 T1 共用一份枚举:T1 系列在 1001–2026 段(挥手、握手、比心、拍照姿势、说话动作),Q 系列在 3001–3048 段——3001 招手、3002 握手、3003 碰拳、3004 挥手、3005 开心、3007 比心、3018 拥抱、3046–3048 三支跳舞。一行服务调用即可,轨迹由官方保证。
第二级:速度控制。进入BIPED_WALK_RUN后,向/aima/mc/locomotion/velocity发布McLocomotionVelocity,字段为前进、侧移、转向、弯腰四个速度加level,如前所述以 50Hz 发布并受死区约束。
第三级:关节级控制,又分两个深度。进入BIPED_CUSTOM_UPPER(1014)后,向/aima/mc/custom/joint/command发布McCustomJointCommand(内含JointCommand数组,按关节名自动匹配),控制上肢 10 个关节——腰 2 个加双臂各 4 个。官方examples/python/upper_body_control.py中发布频率为100Hz,刚度默认 20.0、阻尼默认 3.0,机器人在站立状态下即可动上肢。
更深一层是 HAL 直控。examples/python/joint_control.py通过/aima/hal/joint/command直接控制全身关节,文件开头的警告直接说明了代价:
# 注意:在使用本脚本前,需首先关闭机器人本体运控模块并将机器人平躺或悬挂, # 否则可能出现不可预期的异常行为!
该示例引入随包附带的Ruckig轨迹库源码(examples/ruckig_for_primebot),在control_period_s=0.002(即500Hz)周期下做带速度、加速度、加加速度约束的在线轨迹规划,默认max_velocity=3.0rad/s、max_acceleration=10.0、max_jerk=25.0,刚度默认 20、阻尼默认 2,避免阶跃指令打坏关节。MC 上肢话题与 HAL 关节话题的区别正在于此:前者运行在运动模型之下,机器人可以边站边动;后者绕过运动模型,所以必须关闭运控并把机器人平躺或悬挂。
McAction.msg中还保留了一个BIPED_RECORD_UPPER=302(双足控制上肢录制模式),为示教录制预留了状态入口;v1.0.0.0 中与之配套的完整录制接口尚未在本 SDK 包内开放,示例层未提供例程,此处不做推断。
八、感知与交互:给的是原始数据,不是识别结论
沿接口清单扫一遍,HAL 与交互层的能力边界很清楚。
视觉。连续视频流走 RTSP 而非 DDS:examples/python/get_video_stream.py中的地址是rtsp://{ip}:2554/live_{camera_id},用 OpenCV 直接取流解码并可存 MP4;单帧抓图则走CaptureJpegImage服务,返回JpegImage。这是典型的按数据形态选型——连续大流量用专用流媒体协议,一次性请求用 ROS2 服务。
触觉。hal/msg/TouchState.msg给出传感器编号(Q1 为头部传感器 id=1)和 10 种事件类型:单击、双击直至六击、多击、向左/向右滑动。官方还演示了用命令行ros2 topic pub模拟一次单击事件、另一终端echo接收的原生调试方法。
音频。麦克风采集、机器人端扬声器播放、PC 端音频推流(play_audio_from_pc.py)、设备列表查询、独立的音量与静音服务,全部以 HAL 音频接口提供;agent/msg/AsrResult.msg输出语音识别结果。
表达。PlayTts服务的请求体PlayTtsRequest.msg字段完整:文本、优先级(默认INTERACTION_L6)、0–99 的加权参数、调用方来源domain、请求追踪trace_id、以及是否打断同等优先级播报的is_interrupted(不打断则进入队列)。优先级在TtsPriorityLevel.msg中分 6 个层级:L1 后台服务、L2 主动服务、L4 任务执行、L6 交互响应、L8 危险预警、L10 生命安全,数值越大越优先。此外还有表情播放PlayEmotion、头部灯带LedStripCommand、颈灯SetNeckLight,以及系统控制类的ShutdownTask关机服务。
可以看出一个一致的取舍:开放的是原始数据(图像、音频流、触摸事件、关节力矩),而非识别好的语义。ASR 给了结果流,但唤醒词与对话模型不可配置;相机给了图,但没有自带检测模型。能力边界和接口粒度一样,都是明确的。
九、部署与工程化:进程管理、自启动与构建工具
程序最终要跑在板上,这部分能力同样用的是成熟方案。
板载自启动。文档 §4 给出了完整接入流程:模块部署必须放在/robot/software/<your_module>/下,提供带SIGTERM处理的启动脚本;随后修改process_manager的 YAML 配置(如q1_v2d_soc0_config.yaml),在apps段登记模块的启动脚本绝对路径、是否以 root 运行、日志路径和环境变量,再把模块名加入startup_stages第二阶段的应用列表。开机自启分两个阶段:第一阶段先起sys_guard、mc等基础服务并延时 1.5 秒,第二阶段才起app_proxy、abox及业务应用,延时 8 秒。运行态用yamo em doctor查看所有模块的 PID 与运行状态,SSH 登录则需要向售后索取密钥(旧版可用密码)。
构建与依赖。构建统一用 colcon,SDK 支持"在 SDK 内部新增节点"和"作为第三方依赖集成进自有工作区"两种方式,Python 与 C++ 均有完整示例(18 个 Python 示例与 18 个对应的 C++ 示例,C++ 侧提供完整 CMakeLists 与 package.xml)。examples/opencv直接附带了OpenCV 4.13.0源码包,examples/ruckig_for_primebot附带 Ruckig 源码,版本钉死,避免"我本地能跑、板上编译不过"的环境漂移。文档还推荐使用 Docker 编译环境,并明确提示 SDK 路径不能含中文或空格。
十、开放边界:我认为的几个客观判断
通读这个 SDK 包,可以客观给出几个判断。
第一,开放建立在标准协议之上。ROS2 + FastDDS、标准 Topic/Service、标准 QoS、RTSP、colcon、Docker、OpenCV,没有一个环节是私有封闭格式。接口粒度到达关节力矩、刚度、电机温度、电池 29 位状态字这一级,属于面向开发者的真正开放,而非几个封装好的动作按钮。
第二,安全内建在协议与流程里,而不是靠文档提醒。标准模式网口物理关闭、开发者模式切换后重新上下电、输入源优先级与超时脱锁、状态机合法路径与最小切换间隔、速度死区、关节级 Ruckig 加加速度约束、HAL 直控要求平躺并关闭运控、TTS 按生命安全分层级,这些机制叠加起来,让一段失控的代码很难造成严重后果。
第三,失败语义可被程序化处理。30 种拒绝原因码、三级任务状态、动作切换的就绪标志,让上层程序能够区分"低电量""有障碍""模式不支持""已有任务在跑",而不是面对一个布尔失败值做无意义重试。
边界同样需要如实指出。文档没有回避:API 方式(RESTful/gRPC)暂未开放,当前只有 ROS2;开发者模式的领域级原子开关当前仅"低层运控开发"一项生效;HAL 视频是原始流、ASR 是识别结果,导航、SLAM、目标检测这类高层能力不在本 SDK 中;开发环境仅限 Ubuntu 22.04,不支持 Mac/Windows 直接开发。对一个 v1.0.0.0 的版本号来说,这些限制的存在本身并不奇怪,真正值得肯定的是它们被逐条写进文档,而不是藏在售后问答里。
从两块板子、一条 ROS2 总线,到一个关节指令的 500Hz 周期,这套架构始终在回答同一个工程问题:如何让外部代码安全地变成物理动作。启元给出的答案是去中心化拓扑、显式状态机、仲裁式控制权和按风险分层的接口。对熟悉 ROS2 的开发者,这套答案几乎没有学习成本;接口的开放深度和安全机制的密度,是它在 v1.0.0.0 阶段最有说服力的部分。
启元机器人开发者社区 :启元机器人开发者社区 · 一站式具身智能研发平台
附:
| 引用内容 | 文件路径(相对 SDK 根目录) |
|---|---|
| 双板拓扑、开发者模式、权限模型、FastDDS 配置、自启动、排障 | PrimeBot_SDK快速入门与集成指南.md |
| 全量接口定义与参数 | 接口说明.md |
| 全部动作枚举 | aimdk_msgs/mc/action/McAction.msg |
| 动作状态码 | aimdk_msgs/mc/action/McActionStatus.msg、McActionInfo.msg |
| 通用状态与拒绝原因 | aimdk_msgs/common/CommonState.msg |
| 输入源与操作 | aimdk_msgs/mc/motion/McInputSource.msg、McInputAction.msg |
| 速度/预设/关节指令 | aimdk_msgs/mc/motion/McLocomotionVelocity.msg、McPresetMotion.msg、McCustomJointCommand.msg |
| 电池/关节/触摸/关节指令 | aimdk_msgs/hal/msg/Bms.msg、JointState.msg、JointCommand.msg、TouchState.msg |
| TTS | aimdk_msgs/interaction/msg/PlayTtsRequest.msg、TtsPriorityLevel.msg |
| FastDDS 实际配置 | aimdk_msgs/config/fastdds_profiles.xml |
| 状态机 BFS 切换 | examples/python/common/mc_action_switcher.py |
| 速度/上肢/关节控制 | examples/python/mc_locomotion_velocity.py、upper_body_control.py、joint_control.py |
| 电池/视频/其余例程 | examples/python/get_bms_state.py、get_video_stream.py等 18 个示例(C++ 对应examples/cpp/src/) |