Skip to content

Hands On Dual Running Blend

konodoki edited this page Aug 2, 2026 · 8 revisions

手把手 5:使用双状态运行混合

Transition 描述从状态机收到切换请求到目标状态正式进入之间,电机帧应该怎样生成。如果两边都是持续运行的模型,直接切换可能让 qpos/kp/kd 突变;只向一个固定进入姿态缓慢靠近,又会丢失两边的动态输出。

running_blend 正是为这种情况准备的:它在过渡期间采样源状态和目标状态的运行帧,再混合两侧 qpos/kp/kd

状态能力

class SinWaveState(RobotControlState, EntryFrameProvider, RunningFrameProvider):
    def get_entry_frame(self, ctx):
        return self._calculate_frame(ctx, 0.0)

    def sample_running_frame(self, ctx, dt, *, advance):
        frame = self._calculate_frame(ctx, self.elapsed)
        if advance:
            self.elapsed += dt
        return frame

    def on_update(self, ctx, dt):
        self._apply_frame(
            ctx, self.sample_running_frame(ctx, dt, advance=True)
        )

advance=False 时不得推进状态内部时间。

系统 profile

transition_profiles:
  dual_running_blend:
    type: running_blend
    duration: 0.3
    curve: smoothstep
    sample_from: true
    sample_to: true
    advance_from: true
    advance_to: true

此共享 profile 位于系统 elf3_state_machine.yaml

当前共享 profile 让来源和目标都在交接期间推进时间、动作历史和步态相位,以混合两端 实时输出。目标状态必须保证过渡完成后的 on_enter() 不会把输出重置到不连续的旧帧。 如果目标需要在正式进入时重置 history,应在该 route 覆盖 advance_to: false,并保证 advance=False 采样完全无副作用。

Mod route

routes:
  - from: com.bxi.basic_actions/normal
    event: activate
    to: sin_wave
    transition:
      profile: dual_running_blend
      advance_to: false

  - from: sin_wave
    event: com.bxi.basic_actions/normal
    to: com.bxi.basic_actions/normal
    transition:
      profile: dual_running_blend
      duration: 1.0
      advance_to: false

SinWaveState.on_enter() 会把 elapsed 重置为 0,所以上面两条 route 都覆盖 advance_to: false。目标侧只预览入口时间点,Transition 完成后的第一帧不会因为时间轴 重置而跳回。

代码主动返回:

ctx.request_state(
    "com.bxi.basic_actions/normal",
    trigger="motion_finished",
    transition={
        "profile": "dual_running_blend",
        "duration": 0.5,
        "sample_from": False,
    },
)

混合和生命周期

from_frame = 源运行帧,或过渡开始前最后电机帧
to_frame   = 目标运行帧,或目标进入帧
alpha      = curve(progress)
output     = from + (to - from) * alpha

内置实现会先把两侧自然布局解析到完整 Robot Layout,再执行上述公式。因此旧 29 关节状态 和新 31 关节状态可以互相混合;新增关节会平滑过渡到平台声明的默认目标。

目标先执行 on_prepare(),Session 再取进入帧。过渡完成才调用源 on_exit() 和目标 on_enter();被中断时调用目标 on_prepare_cancel()

同一个控制周期内会依次采样来源和目标。这里不要求两个模型并行运行;只要两次推理总耗时小于控制周期预算,串行采样具有更简单、确定性更强的状态语义。

常见问题

  • 开启 sample_from 时源必须实现 RunningFrameProvider
  • 开启 sample_to 时目标必须实现 RunningFrameProvider
  • 目标始终需要 EntryFrameProvider
  • 当前共享 profile 使用 advance_from: trueadvance_to: true。若目标的 on_enter() 会重置 history 或时间轴,应对该 route 使用 advance_to: false,并检查 Transition 结束帧与目标第一帧连续。
  • 仅当来源动作已经结束、无法继续生成有效帧时,才使用 sample_from: false,让过渡从开始前最后一个电机帧混合。
  • 突跳时检查进入帧和目标进入后的首帧是否一致。

Clone this wiki locally