🤖 roboto_origin_03 Wiki
首页 / 推理子模块 / 中断动作与外部覆盖机制

在具身智能推理系统的实际部署中,单一策略网络往往无法同时兼顾底层平衡控制与高层精细操作。本页文档解析 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.yamlobs_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

安全设计与边界行为

  1. 默认姿态兜底initialize_runtime_state()interrupt_action_ 初始化为 10 维零向量,reset_runtime_state() 进一步将其映射为对应关节的 joint_default_angle。这保证了外部话题延迟或丢失时,双臂不会悬空或抽搐。
  2. 切换瞬态冻结switch_while_paused 在翻转 is_interrupt_ 前后强制暂停/恢复推理,避免观测堆栈中混入新旧模式的混杂帧。
  3. 话题 QoS 隔离/joint_ref_states 采用 KeepLast(1).best_effort().durability_volatile() 的 QoS 策略,与 /joint_states 发布端解耦,允许外部系统以任意频率发布,推理节点始终只消费最新一帧。
  4. 关节限位独立生效:虽然 interrupt_action_ 直接覆盖动作输出,但 RobotInterface 底层的 apply_action() 仍会在电机驱动层执行限位与饱和保护,外部参考值不会绕过硬件安全边界。

Sources: inference_node.cpp [inference_node.hpp#L102-L105]

下一步阅读

中断动作机制与以下系统能力紧密耦合,建议按顺序深入阅读以构建完整知识图谱: