Skip to content

Advanced Recipes

konodoki edited this page Jul 23, 2026 · 19 revisions

高级配置范式

本页展示这套框架的上限写法。建议先读完基础页面:

1. 最小状态切换

目标:键盘 0 切到 sin_wave

遥控器:

sources:
  keyboard:
    type: keyboard
    signals:
      keyboard.sin_wave: {from: keyboard.key, key: "0"}

controls:
  keyboard.sin_wave: {type: bool, inputs: [{source: keyboard.sin_wave}]}

outputs:
  edge:
    - output: btn_10=5
      when: [keyboard.sin_wave]

状态机:

remote_events:
  sin_wave:
    slot: btn_10
    value: 5

states:
  normal:
    transitions:
      on_event:
        sin_wave:
          to: sin_wave
          transition: soft_switch

  sin_wave:
    behavior: SinWaveState

2. 同一事件支持键盘和手柄

outputs:
  edge:
    - output: btn_10=5
      when:
        any:
          - [trigger.right, button.west]
          - [keyboard.sin_wave]

适合只用一次的组合键。

3. 把复杂组合键提升为 command

controls:
  command.sin_wave:
    type: bool
    inputs:
      - when:
          any:
            - [trigger.right, button.west]
            - [keyboard.sin_wave]
        value: true
        - [switch.mode=high, button.south]

outputs:
  edge:
    - output: btn_10=5
      when: [command.sin_wave]

适合复用、调试、统一命名。

4. 游戏手柄和键盘混合控制速度

controls:
  move.vx:
    type: analog
    mix: max_abs
    inputs:
      - source: gamepad.left_y
        direction: -1
        curve: stick
      - source: keyboard.vx
    deadzone: 0.03
    min: -1.0
    max: 1.0
    alpha: 0.03

outputs:
  analog:
    vel_des.x: move.vx

max_abs 表示哪个输入更大用哪个,适合手柄和键盘共存。

状态机侧还需要给会使用速度的状态声明 speed_profile

speed_profiles:
  normal:
    vx_scale: 1.0
    vy_scale: 0.5
    yaw_scale: 1.0

states:
  normal:
    behavior: NormalState
    speed_profile: normal

状态代码中通过 self.get_cmd_vel(ctx) 读取处理后的速度:

cmd_vel = self.get_cmd_vel(ctx)

状态需要额外滤波或屏蔽某个方向时,重写 process_cmd_vel(ctx, cmd_vel)ctx.current_cmd_vel 的写入由基类完成。

没有 speed_profile 的状态会得到零速度,因此静态动作、起身动作、高危动作默认不会被摇杆速度影响。

5. 自动驾驶和遥控器抢占

假设另一个 driver 或节点写入 auto.vx

controls:
  auto.vx:
    type: analog
    inputs: [{source: auto.vx}]

outputs:
  analog:
    vel_des.x:
      controls: [move.vx, auto.vx]
      mix: first_active

含义:

  • move.vx 有输入时用遥控器。
  • 否则使用 auto.vx

也可以反过来让自动优先。

6. 用 CRSF 按键组选择模式

默认配置将 CH8 解为 crsf.button_group_b enum。它的值分别是 backstartdpad_upidledpad_downdpad_leftdpad_right;默认左右方向键用于 yaw, 因此这里使用上方向键作为模式入口:

使用:

outputs:
  edge:
    - output: btn_10=1
      when: [crsf.button_group_b=dpad_up]

更安全的写法是组合确认:

when:
  any:
    - [crsf.button_group_b=dpad_up, button.west]

7. level 保持和 edge 脉冲组合

持续按住进入某个旧兼容按钮:

level:
  - output: btn_1=1
    when: [keyboard.normal]

一次性切换状态:

edge:
  - output: btn_10=5
    when: [keyboard.sin_wave]

判断原则:

  • 需要持续有效:level
  • 只触发一次:edge

8. 带延迟的按键切换

states:
  normal:
    transitions:
      on_event:
        recover:
          to: recover
          delay: 0.5
          transition: first_frame_switch

收到 event 后先进入 pending,0.5 秒后开始 transition。

9. 自动播放 3 秒后返回

states:
  sin_wave:
    behavior: SinWaveState
    transitions:
      after:
        - seconds: 3.0
          to: normal
          transition: soft_switch

如果状态内部也可能提前结束,可以在代码里:

ctx.request_state("normal", trigger="finished", transition="soft_switch")

10. 单独覆盖某次过渡时长

states:
  zero_torque:
    transitions:
      on_event:
        recover:
          to: recover
          transition:
            profile: dual_running_blend
            duration: 1.0

这样不需要为了一次特殊过渡新增全局 profile。

11. 使用插件字段配置过渡行为

transition:
  type: entry_gain_ramp
  duration: 0.5
  kp_from: current
  kd_from: target

插件通过 ConfigReader 强类型读取:

kp_from = reader.literal(
    "kp_from",
    ("current", "zero", "target"),
    default="current",
)

适合把行为参数留在 YAML,而不是把字段写死到 utils/state_machine.py

12. 状态内部 action

状态配置:

states:
  dance:
    behavior: DanceState
    transitions:
      on_event:
        toggle_dance_pause:
          action: toggle_dance_pause

状态类:

def on_action(self, ctx: BxiExample, action_name: str) -> bool:
    if action_name != "toggle_dance_pause":
        return False
    self.playing = not self.playing
    return True

适合暂停、切换子模式、重置内部计数器。

13. 一个状态根据姿态自动安全退出

def on_update(self, ctx: BxiExample, dt: float) -> None:
    if ctx.is_orientation_unsafe(ctx.current_quat_xyzw):
        ctx.request_state("zero_torque", trigger="safety")
        return

    # 正常动作

安全逻辑放状态内部,比只靠外部按键更可靠。

14. 新增 MotionCommands 字段适配

如果底层 MotionCommands 未来新增字段,比如:

body_pitch_des

推荐只改:

src/remote_controller/include/remote_controller/motion_commands_adapter.hpp
src/remote_controller/src/motion_commands_adapter.cpp

增加:

bool is_motion_command_field_supported(const std::string &field)
{
    return field == "body_pitch_des" || ...;
}

和:

if (field == "body_pitch_des") {
    message.body_pitch_des = float_value;
    return true;
}

然后 YAML 直接写:

outputs:
  analog:
    body_pitch_des: body.pitch

这样字段适配集中在 adapter,不污染业务层。

15. 多候选 driver 复用同一套 controls

手柄:

sources:
  gamepad:
    type: joystick
    priority: 50
    signals:
      gamepad.left_y: {from: js.axis.3}

CRSF:

sources:
  crsf:
    type: crsf
    priority: 100
    signals:
      crsf.left_y: {from: crsf.channel.2}

统一 control:

controls:
  move.vx:
    type: analog
    mix: max_abs
    inputs:
      - source: gamepad.left_y
        direction: -1
      - source: crsf.left_y
        direction: -1

上层状态和 outputs 不需要知道输入来自哪个设备。

候选设备严格独占:同一个 control 可以列出多个 source,但只有活动 driver 会更新 signal,因此 max_abs 不会把手柄和 CRSF 同时混合。设备仲裁应写在 priority,不要依赖 source mix。

16. 自定义 driver 的高上限方案

设计 driver 时,把所有物理输入标准化成 raw source:

crsf.channel.1
crsf.channel.2
crsf.channel.3
crsf.channel.4
crsf.channel.5
crsf.link_quality
crsf.rssi

YAML 再解释:

controls:
  link.good:
    type: bool
    threshold: 0.5
    inputs: [{source: crsf.link_quality}]

  command.enable_high_risk:
    type: bool
    inputs:
      - when:
          all: [link.good, switch.mode=high]
        value: true
        - trigger.left
        - button.west

这样 driver 不需要内置任何业务策略。

17. 保护状态发布

内部状态:

states:
  <state_name>:
    behavior: <StateClassName>

保护清单:

protected_states:
  <state_name>:
    behavior:
      - <StateClassName>
    model_keys: [<model_member_name>]
    files:
      - ../data/<model_or_motion_file>

公开版脚本会删除状态、事件、模型 key、初始化代码和文件。

18. 高级配置 checklist

1. 物理输入只出现在 sources。
2. 业务判断只引用 controls。
3. 复杂组合键优先提炼成 command.*。
4. 状态切换用 edge。
5. 持续兼容按钮用 level。
6. 运行时安全判断放状态代码。
7. 常用过渡放 transition_profiles。
8. 个别过渡差异用 inline transition。
9. 行为参数由插件直接声明并用 ConfigReader 验证。
10. 自定义 driver 不写业务状态名。
11. MotionCommands 字段适配集中在 adapter。
12. 高危状态用 release_protection.yaml 管。

Clone this wiki locally