-
Notifications
You must be signed in to change notification settings - Fork 12
Custom State
konodoki edited this page Jul 23, 2026
·
27 revisions
自定义状态必须由某个 Mod 显式提供。最小组成是状态类、plugin.py 中的工厂和 mod.yaml 中的声明。
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 上下文;构造阶段只有工厂依赖和清单参数。
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_param、float_param、string_param、bool_param 和通用 param。未知参数会在构建时失败。
需要进入帧的状态实现 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。
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-
states.<local>.id:运行时 int32 id;默认由完整状态名稳定计算。 -
states.<local>.manifest.index:界面排序号。
重复的界面 index 会自动移动到未占用值并告警;非法 index 或真正 state id 冲突仍会阻止加载。
完整可运行步骤见 手把手自定义状态。