Skip to content

Hands On Custom State

konodoki edited this page Jul 23, 2026 · 26 revisions

手把手 1:从零添加自定义状态 Mod

本课创建 com.example.sin_wave,从基础走路状态进入正弦摆动,再按正常模式键返回。

1. 建立目录

src/bxi_example_py_elf3/mods/com.example.sin_wave/
  mod.yaml
  plugin.py
  state.py

2. 写状态

state.py

from __future__ import annotations

import math
from typing import TYPE_CHECKING

from bxi_example_py_elf3.utils.robot_state_base import RobotControlState
from bxi_example_py_elf3.utils.transition_core import (
    EntryFrameProvider,
    MotorFrame,
    RunningFrameProvider,
)

if TYPE_CHECKING:
    from bxi_example_py_elf3.bxi_example_demo import BxiExample


class SinWaveState(RobotControlState, EntryFrameProvider, RunningFrameProvider):
    def __init__(self, name, state_id, *, amplitude, frequency):
        super().__init__(name, state_id)
        self.amplitude = amplitude
        self.frequency = frequency
        self.elapsed = 0.0

    def on_enter(self, ctx: BxiExample) -> None:
        self.elapsed = 0.0

    def _frame(self, ctx: BxiExample, elapsed: float) -> MotorFrame:
        qpos = ctx.joint_nominal_pos.copy()
        qpos[15] += self.amplitude * math.sin(
            2.0 * math.pi * self.frequency * elapsed
        )
        return self._motor_frame(qpos, ctx.joint_kp, ctx.joint_kd)

    def get_entry_frame(self, ctx: BxiExample) -> MotorFrame:
        return self._frame(ctx, 0.0)

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

    def on_update(self, ctx: BxiExample, dt: float) -> None:
        self._apply_frame(
            ctx, self.sample_running_frame(ctx, dt, advance=True)
        )

3. 写工厂

plugin.py

from bxi_example_py_elf3.utils.mod_system import ModDefinition, ModLoadContext

from .state import SinWaveState


def create_mod(context: ModLoadContext) -> ModDefinition:
    return ModDefinition(
        state_factories={
            "sin_wave": lambda state: SinWaveState(
                state.name,
                state.state_id,
                amplitude=state.float_param("amplitude", 0.25),
                frequency=state.float_param("frequency", 0.5),
            )
        }
    )

4. 写清单

mod.yaml

schema: 1
id: com.example.sin_wave
version: 1.0.0
api: 1
entrypoint: plugin:create_mod

requires:
  - id: com.bxi.basic_actions
    version: ">=1,<2"

events:
  activate: {slot: btn_10, value: 8}

states:
  sin_wave:
    manifest:
      label: 正弦摆动
      index: 20
      group: Customer
      icon: waves
    params:
      amplitude: 0.25
      frequency: 0.5

routes:
  - from: com.bxi.basic_actions/normal
    event: activate
    to: sin_wave
    transition: first_frame_switch

  - from: sin_wave
    event: com.bxi.basic_actions/normal
    to: com.bxi.basic_actions/normal
    transition: dual_running_blend

5. 构建和验证

colcon build --packages-select bxi_example_py_elf3 \
  --symlink-install --merge-install
source install/setup.bash

启动日志应包含:

Mod com.example.sin_wave@1.0.0

状态完整名是 com.example.sin_wave/sin_wave。此时还没有遥控器输出 btn_10=8,可先用测试发布或继续第 3 课完成绑定。

检查清单

  1. 清单状态名与 factory 键完全一致。
  2. 跨 Mod 引用有完整名称和 requires
  3. 参数全部通过 StateBuildContext 消费。
  4. advance=False 不推进 elapsed。
  5. index 与现有状态错开;若冲突会自动调整并告警。

下一课:模型动作状态

Clone this wiki locally