-
Notifications
You must be signed in to change notification settings - Fork 12
Custom Transition
konodoki edited this page May 13, 2026
·
12 revisions
过渡行为负责状态切换过程中电机目标怎么输出。常见需求:
- 退出旧状态时保持上一帧电机目标。
- 进入新状态时先对齐新状态第一帧。
-
kp从 0 缓慢增加,避免突然硬切。 - 某些动作需要自定义姿态插值或特殊收尾。
状态机切换时调用顺序:
旧状态.on_exit(ctx)
新状态.on_prepare_enter(ctx, from_state, transition)
过渡期间每个周期:
旧状态.on_exit_transition(ctx, to_state, exit_progress, transition)
新状态.on_enter_transition(ctx, from_state, enter_progress, transition)
过渡结束:
新状态.on_enter(ctx)
注意:进入侧在退出侧之后调用。如果两边都写 ctx.set_motor_target(),进入侧通常会覆盖退出侧。
建议:
- 退出侧负责保存、保持、收尾。
- 进入侧负责真正的过渡目标。
YAML:
transition_profiles:
first_frame_switch:
exit_duration: 0.02
enter_duration: 0.1
exit_behavior: hold_last_motor
enter_behavior: first_frame_ramp_kp
data:
kp_start: zero
kd_start: targetPython 数据结构:
TransitionProfile(
name="first_frame_switch",
duration=0.1,
exit_duration=0.02,
enter_duration=0.1,
exit_behavior="hold_last_motor",
enter_behavior="first_frame_ramp_kp",
data={"kp_start": "zero", "kd_start": "target"},
)data 是行为私有数据。状态机不解释它,只负责合并和传递。
当前 robot_state_base.py 中有:
-
hold_last_motor:保持上一帧电机目标。 -
first_frame_ramp_kp:目标角度使用新状态第一帧,kp/kd随progress增大。
first_frame_ramp_kp 会调用目标状态的:
get_first_frame(ctx)如果目标状态没有第一帧,就退化成保持上一帧目标。
通用行为放在:
src/bxi_example_py_elf3/bxi_example_py_elf3/robot_state_base.py
适合放这里的行为:
- 多个状态都会用。
- 和机器人电机目标结构有关。
- 不依赖某个具体动作的数据文件。
例如新增一个通用 first_frame_blend_pos_and_gain。
只服务某个状态的行为,直接写在状态类里:
from bxi_example_py_elf3.state_machine import StateBehavior, TransitionProfile
def on_enter_transition(
self,
ctx: BxiExample,
from_state: StateBehavior[BxiExample],
progress: float,
transition: TransitionProfile,
) -> None:
if transition.enter_behavior != "sin_wave_blend":
super().on_enter_transition(ctx, from_state, progress, transition)
return
alpha = min(max(float(progress), 0.0), 1.0)
qpos = ctx.joint_nominal_pos.copy()
qpos[self.joint] += self.amplitude * alpha
kp = ctx.joint_kp * alpha
ctx.set_motor_target(qpos, kp, ctx.joint_kd)YAML:
transition_profiles:
sin_wave_blend:
enter_duration: 0.3
enter_behavior: sin_wave_blend不要为了一个行为把专用字段加进 TransitionProfile。把行为参数放到 data。
YAML:
transition_profiles:
sin_wave_blend:
enter_duration: 0.3
enter_behavior: sin_wave_blend
data:
kp_start_ratio: 0.1
target_offset: 0.2状态代码:
kp_start_ratio = float(transition.data.get("kp_start_ratio", 0.0))
target_offset = float(transition.data.get("target_offset", 0.0))好处:
-
state_machine.py不膨胀。 - 不同行为可以有自己的参数。
- YAML 可读。
预设:
transition_profiles:
first_frame_switch:
exit_duration: 0.02
enter_duration: 0.1
exit_behavior: hold_last_motor
enter_behavior: first_frame_ramp_kp
data:
kp_start: zero
kd_start: target某个转移单独覆盖:
recover:
to: recover
transition:
name: recover_long_entry
base: first_frame_switch
enter_duration: 1.0也可以覆盖 data:
transition:
base: first_frame_switch
enter_duration: 0.5
data:
kp_start: current状态机信息话题:
ros2 topic echo /simulation/state_machine_info过渡中会看到:
{
"mode": "transition",
"transition": {
"from": {"name": "normal"},
"to": {"name": "sin_wave"},
"profile": "first_frame_switch",
"progress": 0.5,
"exit_progress": 1.0,
"enter_progress": 0.5,
"enter_behavior": "first_frame_ramp_kp",
"data": {"kp_start": "zero"}
}
}如果过渡效果不对,优先检查:
-
transition是否真的是你期望的 profile。 -
enter_duration是否被 inline 覆盖。 - 目标状态是否实现
get_first_frame()。 - 自定义行为名是否拼写一致。
- 行为是否调用了
super()处理未知 behavior。
- 常用过渡做成 profile。
- 个别状态的小差异用 inline transition。
- 行为专用参数放
data。 - 通用行为放
robot_state_base.py。 - 状态专属行为放状态类。
- 不要在
state_machine.py里写机器人电机细节。