前置:第 02 篇(环境已通、能跑自己的节点)。
一、控制模块的七个接口 控制模块是 AIMDK 的核心。七个子模块按使用频率排列如下:
控制模块的七个子模块与依赖关系
控制模块下辖运动模式切换、走跑控制、MC 控制信号、预设动作、灵创动作、末端执行器与关节控制七个子模块;其中走跑控制依赖 MC 控制信号,灵创动作依赖运动模式切换,关节控制可能需要关闭 MC。
控制模块 · 七个子模块与依赖关系
控制模块
运动模式切换
查询 / 设置
走跑控制
速度级控制
MC 控制信号
输入源管理 / 仲裁
预设动作控制
灵创动作
动作资源播放
末端执行器控制
夹爪 / 灵巧手
关节控制
关节级控制与状态
依赖
依赖
可能需关闭
实线:从属关系 虚线:依赖关系(箭头指向被依赖的模块)
虚线标出的是真实存在的依赖关系 ,也是新手容易忽略的部分:走跑控制依赖 MC 输入源注册;动作播放依赖运动模式正确;关节控制有时需要临时关闭 MC。
下面按”用得多 → 用得少”展开。
1.1 运动模式切换 机器人存在一个运动模式 状态,并非任何时候都能接受任意指令。标准流程是:查询当前模式 → 确认可操作 → 设置到目标模式。
需要特别注意 v0.8 起的状态码变更:零力矩、软急停、关节冻结、跑步等旧模式码已不对二开开放,必须替换为 PASSIVE_DEFAULT / DAMPING_DEFAULT / JOINT_DEFAULT / STAND_DEFAULT / LOCOMOTION_DEFAULT。
经验 :任何运动相关程序,第一步都应该查模式。跳过这一步直接下发速度指令,是端侧调试中最常见的翻车点——失败时往往没有明确报错。
1.2 走跑控制 走跑控制通过话题 下发速度指令(话题前缀 /aima/),是持续性的速度流,而非一次性请求。
关键机制是多输入源管理 :机器人可能同时收到手柄、APP、你的程序三类指令,必须先注册一个输入源,你的指令才会被 MC 采纳。
1.3 MC 控制信号与输入源仲裁 MC(Motion Control)模块负责输入源配置与仲裁 :
输入源配置 :声明身份并注册到 MC 模块
仲裁流程 :多个输入源同时请求控制时,由 MC 决定优先级
配套有专门的输入源管理服务做注册 / 注销。
取舍 :做”语音控制 + 手柄接管”这类多来源控制时,仲裁逻辑绕不开。一个常见设计是让手柄持有最高优先级,程序侧只在中低优先级区间竞争。
1.4 预设动作控制 官方提供一批预设动作 ,通过预设动作控制服务传入动作 ID 即可播放,不需要自己上传资源。
经验 :验证”动作链路是否通”时,先用预设动作,不要一上来就调灵创资源。前者排除了资源匹配、resource_key 解析等变量,能快速区分是”链路问题”还是”资源问题”。
1.5 灵创动作 灵创动作 是部署在机器人上的动作资源,包含全身动作(如舞蹈)与手臂动作。这是二开中最常用的动作能力,由两个服务配合完成。
灵创动作播放流程
play_action_by_keyword 先拉取资源列表,按名称做精确与包含两级匹配,再按 resource_key 是否含 onnx 判定全身或手臂动作,最后调用 ExecuteActionResource 播放并校验返回码。
灵创动作播放 · play_action_by_keyword(name)
play_action_by_keyword(name)
GetRobotResources 拉取全部资源
拉取成功?
返回 False
按 name 匹配资源
精确匹配命中?
包含匹配命中?
未找到,返回 False
取 resource_key / version
resource_key 含 onnx?
meta: BODY_MONTION
全身动作
meta: ARM_MONTION
手臂动作
ExecuteActionResource 播放
header.header.code == 0?
播放成功
播放失败
否
是
否
是
否
是
是
否
是
否
关键规则 :resource_key 中含 onnx 的是全身动作 (BODY_MONTION),否则是手臂动作 (ARM_MONTION)。
完整实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 """灵创动作播放器:按关键词匹配并播放""" import jsonimport rclpyfrom rclpy.node import Nodefrom aimdk_msgs.srv import GetRobotResources, ExecuteActionResourceSVC_GET_RES = "/aimdk_5Fmsgs/srv/GetRobotResources" SVC_PLAY_ACT = "/aimdk_5Fmsgs/srv/ExecuteActionResource" class LinkCraftPlayer (Node ): def __init__ (self ): super ().__init__("linkcraft_player" ) self .srv_get_res = self .create_client(GetRobotResources, SVC_GET_RES) self .srv_play_act = self .create_client(ExecuteActionResource, SVC_PLAY_ACT) self .resource_list = [] def refresh_all_resources (self ) -> bool : if not self .srv_get_res.wait_for_service(timeout_sec=1.0 ): self .get_logger().warn("灵创资源服务未就绪" ) return False future = self .srv_get_res.call_async(GetRobotResources.Request()) rclpy.spin_until_future_complete(self , future, timeout_sec=3.0 ) res = future.result() if res is None or res.header.header.code != 0 : self .get_logger().error( f"获取资源失败:{res.header.message if res else '无响应' } " ) return False self .resource_list = res.robot_resources self .get_logger().info(f"已加载 {len (self.resource_list)} 个动作资源" ) return True def _match (self, name: str ): """两级匹配:精确优先,其次包含。 只做包含匹配会出现 '爱你' 误命中 '新版爱你' 这类问题。 """ for item in self .resource_list: if item.current_version.name == name: return item for item in self .resource_list: if name in item.current_version.name: return item return None def play_action_by_keyword (self, name: str ) -> bool : if not self .refresh_all_resources(): return False target = self ._match (name) if target is None : self .get_logger().warn(f"未找到动作:{name} " ) return False req = ExecuteActionResource.Request() req.resource_key = target.resource_key req.resource_version = target.current_version.version req.slaves = [] resource_type = ( "BODY_MONTION" if "onnx" in req.resource_key else "ARM_MONTION" ) req.meta = json.dumps({"resource_type" : resource_type}) future = self .srv_play_act.call_async(req) rclpy.spin_until_future_complete(self , future, timeout_sec=5.0 ) resp = future.result() if resp is not None and resp.header.header.code == 0 : self .get_logger().info(f"播放成功:{target.current_version.name} " ) return True self .get_logger().error( f"播放失败:{resp.header.message if resp else '服务无返回' } " ) return False
两点设计说明:
两级匹配 。只做包含匹配会导致 爱你 误命中 新版爱你。精确匹配优先、包含匹配兜底,是成本最低的改进。
onnx 判类型属于脆弱依赖 。它是官方约定,但依赖资源命名规范。生产环境建议在拉列表时就把 resource_key → resource_type 的映射缓存下来,避免每次播放都重新推断。
1.6 末端执行器控制 控制夹爪与灵巧手,通过话题下发指令,另有手部状态信息话题与查询手部类型服务。
1.7 关节控制 提供关节级控制(话题下发)与关节状态查询(服务读取)。
⚠️ 官方安全提示 :某些关节控制示例需临时关闭 MC 模块 。这类操作风险较高,必须先在仿真环境验证,并明确恢复方式。
二、交互模块 2.1 语音控制 三块能力:ASR (语音转文本)、TTS (文本转语音)、MIC 音频流采集 (直接订阅麦克风音频流,含 VAD 相关能力)。
⚠️ 语音合成存在优先级限制 ,需避免同时播放多条语音。做”边播报边执行动作”时,必须考虑 TTS 的排队与打断策略。
取舍 :自研语音系统需要先关闭内置交互(切到 only_voice)以释放音频流。但关闭后端侧 VAD、唤醒、播报排队都需要自行补齐——除非确实需要定制语音链路,否则优先复用内置能力。
2.2 屏幕控制 通过 /face_ui_proxy/ 前缀的服务控制屏幕显示(表情、UI 元素)。
表情播放会占用显示资源。多个表情源并发时会出现互相覆盖,需要在应用层做互斥。
2.3 灯带控制 控制灯带颜色与效果。用于情感表达和状态指示,例如”导航中”用呼吸灯、”等待唤醒”用低亮度常亮。
三、感知模块与其他
模块
状态
注意
感知模块
选装
写依赖视觉的代码前,先确认目标机器是否装配
硬件抽象模块
可用
底层硬件统一抽象层
故障与系统管理
待发布
当前不可用
开发者模式
可用
开发者相关配置
四、动手:动作序列编排 把前面的能力串起来。真实实现中,走跑控制涉及”查模式 → 注册输入源 → 发布速度 → 零速停止”四步,下面给出编排骨架。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 """端侧组合动作:走一段 → 播灵创动作 → 说一句话""" import jsonimport timeimport rclpyfrom rclpy.node import Nodefrom aimdk_msgs.srv import GetRobotResources, ExecuteActionResourceclass ActionSequence (Node ): def __init__ (self ): super ().__init__("action_sequence" ) self .get_res = self .create_client( GetRobotResources, "/aimdk_5Fmsgs/srv/GetRobotResources" ) self .play_act = self .create_client( ExecuteActionResource, "/aimdk_5Fmsgs/srv/ExecuteActionResource" ) def walk (self, duration_sec: float = 2.0 ) -> bool : """走一段。真实实现四步: ① 查询运动模式,确认可运动 ② 向 MC 模块注册输入源 ③ 向走跑控制话题持续发布速度指令 ④ 显式发送零速停止 具体服务 / 话题名以官方接口文档为准。 """ self .get_logger().info("① 走一段" ) time.sleep(duration_sec) self .get_logger().info(" 已停止" ) return True def play_action (self, keyword: str ) -> bool : self .get_logger().info(f"② 播放灵创动作:{keyword} " ) if not self .get_res.wait_for_service(timeout_sec=1.0 ): self .get_logger().warn(" 资源服务未就绪,跳过动作" ) return False future = self .get_res.call_async(GetRobotResources.Request()) rclpy.spin_until_future_complete(self , future, timeout_sec=3.0 ) res = future.result() if res is None or res.header.header.code != 0 : self .get_logger().warn(" 资源拉取失败,跳过动作" ) return False target = next ( (it for it in res.robot_resources if keyword in it.current_version.name), None ) if target is None : self .get_logger().warn(f" 未找到动作 {keyword} ,跳过" ) return False req = ExecuteActionResource.Request() req.resource_key = target.resource_key req.resource_version = target.current_version.version req.slaves = [] req.meta = json.dumps({ "resource_type" : "BODY_MONTION" if "onnx" in req.resource_key else "ARM_MONTION" }) future = self .play_act.call_async(req) rclpy.spin_until_future_complete(self , future, timeout_sec=5.0 ) resp = future.result() ok = resp is not None and resp.header.header.code == 0 self .get_logger().info(" 播放成功" if ok else " 播放失败" ) return ok def speak (self, text: str ): self .get_logger().info(f"③ 语音播报:{text} " ) def main (): rclpy.init() node = ActionSequence() try : node.walk(duration_sec=2.0 ) node.play_action("新年快乐" ) node.speak("大家好,我是灵犀 X2" ) finally : node.destroy_node() rclpy.shutdown() if __name__ == "__main__" : main()
动作序列编排的调用时序
main 依次驱动 ActionSequence 完成走一段、播灵创动作与语音播报;走一段内部包含查模式、注册输入源、发布速度与零速停止,动作播放包含拉取资源与下发播放。
main
ActionSequence
AIMDK 端侧
walk(2.0)
查运动模式 → 注册输入源 → 发布速度
零速停止
play_action("新年快乐")
GetRobotResources
ExecuteActionResource
speak("大家好")
语音合成
持续执行
resource_key / version
code == 0
显式停止,不依赖超时
受 TTS 优先级限制
编排的三条原则 :
失败要降级 。动作找不到时跳过并记录,而不是让整个序列抛异常中断——演示场景下”少一个动作”远比”整体崩掉”可接受。
动作之间留间隔 。机器人执行动作需要物理时间,连续下发会互相打断。
运动必须显式停止 。不要依赖”超时自动停”这类隐式行为。
五、上真机前的检查项
检查项
判据
运动类程序已注册输入源
注册服务返回成功,且机器人能响应速度指令
运动前已查询运动模式
日志中能看到模式查询结果
未使用 v0.8 废弃状态码
代码中不出现已废弃的旧模式常量
关节控制已知悉 MC 开关方式
明确知道如何关闭、如何恢复
速度 / 力矩参数有依据
不是试出来的数值
遥控手柄在手边
能随时接管
已在仿真环境验证
有验证记录
运控计算单元(PC1)无二开程序
ps 检查确认
结论
控制模块七个接口:模式切换 / 走跑 / MC 输入源 / 预设动作 / 灵创动作 / 末端执行器 / 关节控制 。
灵创动作 = GetRobotResources 拉列表 + ExecuteActionResource 播放;resource_key 含 onnx 即全身动作。
走跑控制依赖 MC 输入源注册 ——未注册的失败现象是”无报错、机器人不动”。
交互模块三类:语音(ASR / TTS / MIC)、屏幕、灯带;TTS 有优先级限制,表情有资源互斥。
编排动作序列:失败降级、留间隔、显式停止 。
验证动作链路时先用预设动作,排除资源匹配带来的干扰变量。
下一篇转到云端:创建灵心应用、完成 v1.4.0 的验签,写出第一个”文本进 → 技能 + LLM 回复出”的云端 Agent。
声明
本文首发智元AIMA开放平台 ,并在博客同步