在具身智能推理系统的实际部署中,单一策略网络往往无法同时兼顾底层平衡控制与高层精细操作。本页文档解析 inference 节点中的中断动作(Interrupt Action)与外部覆盖(External Override)机制:该机制允许在策略网络持续驱动下肢与躯干维持平衡的同时,通过 ROS 话题将双臂关节目标角度从外部系统(如遥操作、动作捕捉或上位机规划器)实时注入,覆盖策略原本的双臂输出。与多策略热切换不同,中断模式保持同一策略在线运行,仅通过观测信号与动作覆写实现局部自由度的外部接管。
Sources: inference_node.hpp inference_interrupt.yaml
架构总览与数据流
中断动作机制由三个核心链路构成:中断观测注入链路让策略网络感知当前是否处于外部接管状态;外部关节参考接收链路通过 /joint_ref_states 话题获取目标关节角;动作覆写链路在推理循环末尾用外部参考替换策略输出的双臂指令。以下数据流图展示了三条链路在双线程架构中的位置与交互关系。
flowchart LR
subgraph External["外部系统"]
ExtOp["遥操作 / 动捕 / 规划器"]
end
subgraph ROS2["ROS 2 交互层"]
Joy["/joy (手柄)"]
JointRef["/joint_ref_states"]
end
subgraph InferenceThread["推理线程 (inference)"]
ObsMgr["观测组装"]
ONNX["ONNX Runtime<br/>policy_interrupt.onnx"]
Override["动作覆写逻辑"]
end
subgraph ControlThread["控制线程 (control)"]
Apply["apply_action()"]
end
subgraph Robot["RobotInterface"]
Motors["CAN/CANFD 电机"]
end
Joy -->|"LB 按钮"| InterruptFlag{"is_interrupt_"}
InterruptFlag -->|"0/1"| ObsMgr
ExtOp -->|"JointState.position[0:9]"| JointRef
JointRef -->|写入 interrupt_action_| InterruptFlag
ObsMgr -->|"ang_vel, gravity_b, cmd_vel,<br/>dof_pos, dof_vel, last_action,<br/>interrupt:1"| ONNX
ONNX -->|"output_buffer[0:22]"| Override
Override -->|"下肢: 策略输出<br/>双臂: interrupt_action_"| Act["act_[0:22]"]
Act --> Apply
Apply --> Motors
Sources: inference_node.cpp ros_interface.cpp
核心概念:Interrupt 信号与动作覆写
Interrupt 观测信号
中断机制首先体现在观测布局中。在 inference_interrupt.yaml 的 obs_layouts 里,显式声明了 interrupt:1 字段,表示向策略网络输入一个额外的二元标量。get_interrupt_obs() 方法读取原子标志 is_interrupt_,将其转换为 0.0f(正常模式)或 1.0f(中断模式)。策略网络在训练阶段即学习该信号语义:当信号为 1 时,网络应主动抑制对双臂关节的决策输出,转而依赖外部提供的关节参考值,从而避免策略输出与外部覆盖之间的冲突。
Sources: obs_manager.cpp inference_interrupt.yaml
外部关节参考通道
外部系统通过发布 ROS 标准消息 sensor_msgs::msg::JointState 到 /joint_ref_states 话题来传递关节目标值。订阅回调 subs_joint_state_callback() 仅在 supports_interrupt() && is_interrupt_.load() 同时满足时才会将消息中的 position 数组写入内部缓冲区 interrupt_action_。这一条件判断确保了外部数据不会被误消费到正常推理流程中,同时也意味着外部系统可以持续发布数据,但只有进入中断模式后才会真正生效。
Sources: ros_interface.cpp [inference_node.hpp#L216-L217]
推理循环中的动作覆写
在推理线程的每次循环末尾,策略网络的原始输出经过 action_scale 缩放和 joint_default_angle 偏置后写入 act_ 数组。随后,系统检查 supports_interrupt() 与 is_interrupt_:若条件成立,则用互斥锁 interrupt_mutex_ 保护下的 interrupt_action_ 覆写 act_ 的最后 10 个元素。从关节索引映射可知,act_ 长度为 23,覆写范围为索引 13 至 22,恰好对应双臂自由度(肩部、肘部、腕部及其附属关节)。下肢与躯干关节(索引 0 至 12)始终保持由策略网络控制,从而确保机器人在双臂执行精细操作期间仍能自适应维持平衡。
Sources: inference_node.cpp
运行时控制与状态切换
手柄 LB 按钮切换逻辑
中断模式通过手柄的 LB 按钮(buttons[4])触发。为了避免在模式切换瞬间出现状态不一致或动作跳变,代码采用了 switch_while_paused 闭包策略:在切换 is_interrupt_ 之前,先将 is_running_ 原子置换为 false 以暂停推理循环,完成标志位翻转后,若此前推理处于运行状态则自动恢复。该机制保证了观测堆栈、动作缓冲区与中断标志始终处于同步状态。
Sources: ros_interface.cpp
状态机与并发安全
中断机制涉及多条并发路径:推理线程读取 is_interrupt_ 并覆写动作;ROS 回调线程写入 interrupt_action_;手柄回调线程翻转 is_interrupt_。代码通过以下锁策略隔离竞争:
| 互斥锁 | 保护资源 | 竞争线程 |
|---|---|---|
mode_mutex_ |
active_policy_idx_、is_interrupt_、is_motion_policy_ |
推理线程、LB/RB 手柄回调 |
interrupt_mutex_ |
interrupt_action_ |
推理线程、/joint_ref_states 回调 |
act_mutex_ |
act_、last_act_ |
推理线程、控制线程 |
is_interrupt_ 本身为 std::atomic<bool>,无需加锁即可保证读写原子性,但在切换策略或重置状态时仍需 mode_mutex_ 配合以维持操作序列的一致性。
Sources: [inference_node.hpp#L215-L216]
配置详解:inference_interrupt.yaml
中断模式的启用完全由 YAML 配置决定,无需修改源码。以下是 inference_interrupt.yaml 与标准配置 inference.yaml 的关键差异对比:
| 配置项 | inference.yaml(标准行走) |
inference_interrupt.yaml(中断模式) |
说明 |
|---|---|---|---|
model_names |
["policy.onnx"] |
["policy_interrupt.onnx"] |
必须使用支持 interrupt 观测输入的专用模型 |
obs_layouts |
ang_vel:3, gravity_b:3, cmd_vel:3, dof_pos:23, dof_vel:23, last_action:23 |
末尾追加 , interrupt:1 |
向网络注入中断二元信号 |
frame_stacks |
[10] |
[10] |
与标准模式保持一致,维持时序感知 |
joint_default_angle 字段在中断模式初始化中承担额外职责:reset_runtime_state() 会将 interrupt_action_ 的初始值设置为对应关节的默认角度,确保在未收到外部话题数据或刚进入中断模式时,双臂不会突变为异常姿态。
Sources: config/inference_interrupt.yaml inference_node.cpp
中断模式与 Motion Policy 模式的本质区别
系统中存在两类“策略切换/覆写”机制,开发者常易混淆。下表从架构意图、实现层与适用场景三个维度进行对比:
| 维度 | 中断动作模式(Interrupt) | Motion Policy 模式(BeyondMimic / GetUp) |
|---|---|---|
| 策略数量 | 单一策略在线运行 | 多个策略(基础策略 + Motion 策略)预加载 |
| 切换对象 | 不切换策略,仅覆写双臂动作输出 | 切换 active_policy_idx_,更换完整观测布局与 ONNX Session |
| 观测差异 | 增加 interrupt:1 标量 |
增加 motion_pos:23, motion_vel:23 参考轨迹 |
| 动作来源 | 下肢:网络输出;双臂:外部话题 | 全身:网络输出(网络已内嵌对参考轨迹的追踪行为) |
| 外部依赖 | 依赖 /joint_ref_states 话题 |
依赖 motion_names 配置的 .npz 文件 |
| 典型应用 | 遥操作双臂、人机协作、精细抓取 | 舞蹈、拳击、起身等全身参考轨迹跟踪 |
从代码层面看,LB 按钮的语义由 supports_interrupt() 与 has_motion_policy() 的返回值动态决定:若配置包含 interrupt 观测源,LB 控制中断开关;否则若存在 motion_names,LB 控制 Motion Policy 的启用与回退。
Sources: ros_interface.cpp inference_node.cpp
安全设计与边界行为
- 默认姿态兜底:
initialize_runtime_state()将interrupt_action_初始化为 10 维零向量,reset_runtime_state()进一步将其映射为对应关节的joint_default_angle。这保证了外部话题延迟或丢失时,双臂不会悬空或抽搐。 - 切换瞬态冻结:
switch_while_paused在翻转is_interrupt_前后强制暂停/恢复推理,避免观测堆栈中混入新旧模式的混杂帧。 - 话题 QoS 隔离:
/joint_ref_states采用KeepLast(1).best_effort().durability_volatile()的 QoS 策略,与/joint_states发布端解耦,允许外部系统以任意频率发布,推理节点始终只消费最新一帧。 - 关节限位独立生效:虽然
interrupt_action_直接覆盖动作输出,但RobotInterface底层的apply_action()仍会在电机驱动层执行限位与饱和保护,外部参考值不会绕过硬件安全边界。
Sources: inference_node.cpp [inference_node.hpp#L102-L105]
下一步阅读
中断动作机制与以下系统能力紧密耦合,建议按顺序深入阅读以构建完整知识图谱:
- 观测布局配置与动态解析:理解
obs_layouts字符串的解析规则与interrupt观测源在obs_source_definitions()中的注册方式。 - 多策略运行时与热切换机制:对比 Motion Policy 的完整策略切换与 Interrupt 的部分动作覆写在实现层面的差异。
- ROS 2 话题与服务接口:了解
/joint_ref_states、/action、/joy等话题的完整接口定义与 QoS 配置。 - 安全监控、限位与故障处理:掌握
gravity_z_upper、joint_limits等安全参数如何与中断模式协同工作。