-
Notifications
You must be signed in to change notification settings - Fork 12
Hands On Custom Transition
konodoki edited this page Jul 23, 2026
·
13 revisions
本课新增 pose_gain_blend:从过渡开始时的电机位置和增益,平滑插值到目标状态提供的进入帧。
新建:
src/bxi_example_py_elf3/bxi_example_py_elf3/transitions/pose_gain_blend.py
不要修改注册表或 transitions/__init__.py。
from __future__ import annotations
from collections.abc import Mapping
from typing import TYPE_CHECKING, Literal
from bxi_example_py_elf3.utils.transition_core import (
ConfigReader,
MotorFrame,
SingleClassTransition,
require_entry_frame_provider,
)
if TYPE_CHECKING:
from bxi_example_py_elf3.bxi_example_demo import BxiExample
from bxi_example_py_elf3.utils.state_machine import StateBehavior
Curve = Literal["linear", "smoothstep"]
class PoseGainBlendTransition(SingleClassTransition):
type_name = "pose_gain_blend"
def __init__(self, name: str, duration: float, curve: Curve):
super().__init__(name, duration)
self._curve = curve
self._start: MotorFrame | None = None
self._target: MotorFrame | None = None
@classmethod
def from_config(
cls,
name: str,
raw: Mapping[str, object],
) -> "PoseGainBlendTransition":
reader = ConfigReader(raw, name)
duration = reader.float("duration", minimum=0.0)
curve = reader.literal(
"curve",
("linear", "smoothstep"),
default="smoothstep",
)
reader.finish()
return cls(name, duration, curve)
def validate_states(
self,
from_state: "StateBehavior[BxiExample]",
to_state: "StateBehavior[BxiExample]",
) -> None:
require_entry_frame_provider(to_state)
def on_start(
self,
ctx: "BxiExample",
from_state: "StateBehavior[BxiExample]",
to_state: "StateBehavior[BxiExample]",
) -> None:
self._start = MotorFrame.create(ctx.pos_last, ctx.kp_last, ctx.kd_last)
provider = require_entry_frame_provider(to_state)
self._target = provider.get_entry_frame(ctx)
def apply(self, ctx: "BxiExample", dt: float, progress: float) -> None:
start = self._start
target = self._target
if start is None or target is None:
raise RuntimeError("pose gain blend transition has not started")
alpha = progress
if self._curve == "smoothstep":
alpha = progress * progress * (3.0 - 2.0 * progress)
ctx.set_motor_target(
start.qpos + (target.qpos - start.qpos) * alpha,
start.kp + (target.kp - start.kp) * alpha,
start.kd + (target.kd - start.kd) * alpha,
)
def config_snapshot(self) -> dict[str, object]:
return {"curve": self._curve}class SinWaveState(RobotControlState, EntryFrameProvider):
def get_entry_frame(self, ctx: BxiExample) -> MotorFrame:
return self._motor_frame(
ctx.joint_nominal_pos,
ctx.joint_kp,
ctx.joint_kd,
)
def on_update(self, ctx: BxiExample, dt: float) -> None:
...其他状态不需要实现这个方法。状态图加载时会验证实际目标状态是否具备能力。
transition_profiles:
pose_blend_switch:
type: pose_gain_blend
duration: 0.5
curve: smoothstepsin_wave_event:
to: sin_wave
transition: pose_blend_switch单次覆盖:
sin_wave_event:
to: sin_wave
transition:
profile: pose_blend_switch
duration: 1.0
curve: linear也可以完全内联:
transition:
type: pose_gain_blend
duration: 0.8
curve: smoothstep启动状态机。成功时 profile 会显示:
{
"name": "pose_blend_switch",
"type": "pose_gain_blend",
"duration": 0.5,
"curve": "smoothstep"
}常见错误:
-
unknown transition type:文件没有安装、type_name拼错,或模块导入失败。 -
unknown fields:YAML 字段没有被ConfigReader读取。 -
must implement EntryFrameProvider:某条边的目标状态没有实现能力。 -
duplicate transition type:两个类用了相同type_name。
如果新过渡需要一个只属于它的函数,在同一个插件文件中声明 @runtime_checkable Protocol,让需要该过渡的状态显式继承并实现。不要把该函数加入 RobotControlState。
完整模式见 自定义过渡 的“状态能力”章节。
- 一个插件类对应一个文件。
-
type_name唯一。 - 配置读取有明确类型和范围检查。
-
reader.finish()已调用。 - 状态能力在图加载时验证。
- Session 数据只在
on_start()初始化。 - YAML 使用
type或profile。
下一课:手把手 5:从零使用双状态运行混合过渡。