Skip to content

Custom State

konodoki edited this page Jul 23, 2026 · 27 revisions

自定义状态

自定义状态必须由某个 Mod 提供。最小组成只有状态类和 mod.yaml;共享资源或注册高级扩展时再增加 plugin.py。完整渐进路线见 从低地板到高天花板实战

最低地板:PoseState

id: com.example.hold
version: 1.0.0
states:
  hold:
    factory: state:HoldState
    label: 保持姿态
    index: 20
from bxi_example_py_elf3.utils.state_library import PoseState


class HoldState(PoseState):
    def target_position(self, ctx):
        return ctx.pos_last.copy()

这里不需要 plugin.pyPoseState 已提供进入帧、运行帧和正常更新;时间轨迹改用 ProceduralState,策略推理改用 PolicyState,固定动作回放可直接复用 MotionReplayState

完整控制:RobotControlState

from __future__ import annotations

from typing import TYPE_CHECKING

from bxi_example_py_elf3.utils.robot_state_base import RobotControlState

if TYPE_CHECKING:
    from bxi_example_py_elf3.bxi_example_demo import BxiExample


class HoldState(RobotControlState):
    def on_update(self, ctx: BxiExample, dt: float) -> None:
        ctx.set_motor_target(ctx.joint_nominal_pos, ctx.joint_kp, ctx.joint_kd)

构造函数若没有额外依赖,可以直接继承基类的 (name, state_id)

显式工厂

from bxi_example_py_elf3.utils.mod_system import ModDefinition, ModLoadContext

from .state import HoldState


def create_mod(context: ModLoadContext) -> ModDefinition:
    return ModDefinition(
        state_factories={
            "hold": lambda state: HoldState(state.name, state.state_id),
        }
    )

框架不再扫描所有 RobotControlState 子类。工厂键必须与清单的本地状态名完全一致。

清单声明

schema: 1
id: com.example.hold
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:
  hold:
    manifest:
      label: 保持姿态
      index: 20
      group: Customer
      icon: pause

routes:
  - {from: com.bxi.basic_actions/normal, event: activate, to: hold}
  - {from: hold, event: com.bxi.basic_actions/normal, to: com.bxi.basic_actions/normal}

运行时完整状态名是 com.example.hold/hold

生命周期

方法 时机 用途
on_bind(ctx) 状态构建后一次 创建订阅、client、timer 等 ROS 资源
on_prepare(ctx, from_state) 过渡 Session 创建前 预热模型、选择动作、准备缓存;不要输出电机
on_prepare_cancel(...) 准备后的过渡被中断 撤销准备副作用
on_enter(ctx) 成为当前状态后 重置播放进度和状态私有变量
on_update(ctx, dt) 当前状态每个控制周期 生成电机输出
on_action(ctx, name) route 指定 action 时 执行动作并返回是否处理
on_exit(ctx) 成功切出时 默认保存上一个状态电机帧
on_unbind(ctx) 热重载或节点关闭 释放 on_bind 创建的资源

不要在 __init__() 中访问 ROS node 上下文;构造阶段只有工厂依赖和清单参数。

强类型参数

简单 Mod 推荐 dataclass:

from dataclasses import dataclass

from bxi_example_py_elf3.utils.state_library import PoseState

@dataclass(frozen=True)
class HoldParams:
    gain_scale: float = 0.5
    allow_motion: bool = False

class HoldState(PoseState[HoldParams]):
    Params = HoldParams

约定 factory 会自动构造参数对象,保留默认值并拒绝未知字段。显式 plugin.py 工厂仍可使用下面的逐字段 API:

states:
  hold:
    params:
      gain_scale: 0.5
      allow_motion: false
"hold": lambda state: HoldState(
    state.name,
    state.state_id,
    gain_scale=state.float_param("gain_scale", 1.0),
    allow_motion=state.bool_param("allow_motion", False),
)

支持 int_paramfloat_paramstring_parambool_param 和通用 param。未知参数会在构建时失败。

MotorFrame 和过渡能力

需要进入帧的状态实现 EntryFrameProvider

class HoldState(RobotControlState, EntryFrameProvider):
    def get_entry_frame(self, ctx: BxiExample) -> MotorFrame:
        return self._motor_frame(
            ctx.joint_nominal_pos,
            ctx.joint_kp,
            ctx.joint_kd,
        )

需要 running_blend 动态采样时再实现 RunningFrameProvider

def sample_running_frame(self, ctx, dt, *, advance):
    qpos = self._calculate(ctx, dt, advance=advance)
    return self._motor_frame(qpos, ctx.joint_kp, ctx.joint_kd)

advance=False 必须是观察操作,不应推进 timestep 或 history。普通 on_update() 可用 _apply_frame() 发布采样结果。

速度输入

清单给状态指定 speed_profile 后,状态调用 self.get_cmd_vel(ctx)。如需额外滤波,覆盖 process_cmd_vel()。不要直接读取遥控器按钮或绕过 profile。

主动切换和 action

ctx.request_state(
    "com.bxi.basic_actions/normal",
    trigger="motion_finished",
    transition={"profile": "dual_running_blend", "duration": 0.5},
)
def on_action(self, ctx, action_name):
    if action_name != "toggle_pause":
        return False
    self.playing = not self.playing
    return True

状态 id 与界面 index

  • states.<local>.id:运行时 int32 id;默认由完整状态名稳定计算。
  • states.<local>.manifest.index:界面排序号。

重复的界面 index 会自动移动到未占用值并告警;非法 index 或真正 state id 冲突仍会阻止加载。

完整可运行步骤见 手把手自定义状态

Clone this wiki locally