-
Notifications
You must be signed in to change notification settings - Fork 12
Hands On Sim2Real
konodoki edited this page Jul 23, 2026
·
11 revisions
本页关注把已经能在仿真运行的 policy 接入状态、过渡和真机安全路径。
至少明确:
- 输入关节顺序与单位。
- 四元数顺序。
- 角速度坐标系。
- 模型期望的控制频率。
- 输出是绝对关节位置还是 offset。
-
kps/kds的来源。 - 是否需要速度命令和历史观测。
这些不一致时,状态机和过渡正确也无法保证动作正确。
使用具体类型或联合类型,不要让 _policy() 返回未知类型:
def _policy(self, ctx: BxiExample) -> "MotionPolicy":
return cast("MotionPolicy", getattr(ctx, self.policy_attr))class HardwarePolicyState(
RobotControlState,
EntryFrameProvider,
RunningFrameProvider,
):
def __init__(self, name: str, state_id: int, policy_attr: str):
super().__init__(name, state_id)
self.policy_attr = policy_attr
def _policy(self, ctx: BxiExample) -> "MotionPolicy":
return cast("MotionPolicy", getattr(ctx, self.policy_attr))
def on_prepare(
self,
ctx: BxiExample,
from_state: StateBehavior[BxiExample],
) -> None:
policy = self._policy(ctx)
ctx.preheat_model(
policy,
with_cmd_vel=True,
cmd_vel=self.get_cmd_vel(ctx),
)
def get_entry_frame(self, ctx: BxiExample) -> MotorFrame:
policy = self._policy(ctx)
qpos = getattr(policy, "target_dof_pos", None)
if qpos is None:
qpos = getattr(policy, "default_dof_pos", None)
if qpos is None:
raise ValueError(f"state '{self.name}' policy has no entry position")
return self._motor_frame(qpos, policy.kps, policy.kds)
def sample_running_frame(
self,
ctx: BxiExample,
dt: float,
*,
advance: bool,
) -> MotorFrame | None:
policy = self._policy(ctx)
qpos, _ = policy.inference_step(
ctx.current_q,
ctx.current_dq,
ctx.current_quat_wxyz,
ctx.current_omega,
self.get_cmd_vel(ctx),
)
return self._motor_frame(qpos, policy.kps, policy.kds)
def on_update(self, ctx: BxiExample, dt: float) -> None:
if ctx.is_orientation_unsafe(ctx.current_quat_xyzw):
ctx.request_state("zero_torque", trigger="safety")
return
self._apply_frame(
ctx,
self.sample_running_frame(ctx, dt, advance=True),
)如果 policy 有 timestep 或 history,必须让 advance=False 路径不提交这些变化。
states:
hardware_policy:
behavior: HardwarePolicyState
params:
policy_attr: hardware_policy
speed_profile: normal
transitions:
on_event:
normal_event:
to: normal
transition: soft_switch
zero_torque_event: zero_torque入口第一次验证建议使用稳定的进入过渡:
hardware_policy_event:
to: hardware_policy
transition: first_frame_switch确认进入帧、增益和安全退出无误后,再尝试 dual_running_blend。
- 只加载模型,不切状态,检查输入输出 shape。
- 在零速度下进入状态。
- 使用低增益或受控环境验证进入姿态。
- 验证 normal 和 zero torque 返回边。
- 验证姿态安全条件。
- 验证速度 profile 的缩放和限幅。
- 最后验证运行混合。
- 急停和零力矩事件独立可用。
- 初始姿态与模型进入帧足够接近。
-
qpos/kp/kdshape 与自由度数一致。 - 电机增益没有仿真专用倍率。
- 控制周期与 policy 训练频率关系明确。
- 延迟和推理超时监控已开启。
- 机器人周围留出足够空间。
活动过渡期间状态机仍处理源状态 event,因此 zero torque 可以中断进入过程。目标状态会收到:
def on_prepare_cancel(
self,
ctx: BxiExample,
from_state: StateBehavior[BxiExample],
) -> None:
...如果准备过程申请了 GPU buffer、动作缓存或外部资源,在此释放或恢复。
先使用保守 profile:
speed_profiles:
hardware_safe:
vx_scale: 0.2
vx_min: -0.2
vx_max: 0.2
vy_scale: 0.2
yaw_scale: 0.2确认真机稳定后逐步扩大,不要一开始复用高速 profile。
进入瞬间跳变:
- 检查
get_entry_frame()与模型第一轮正式输出是否一致。 - 检查预热是否使用了当前真机观测。
- 检查增益起点配置。
仿真正常、真机振荡:
- 检查单位、关节顺序、延迟和增益。
- 检查状态估计噪声和滤波。
- 检查推理周期抖动。
过渡中动作提前播放:
- 把
advance_to设为false。 - 检查状态在
advance=False时是否仍推进内部状态。
安全事件没有立即生效:
- 检查源状态是否配置了对应 event 边。
- 检查
remote_events与MotionCommandsslot。 - 检查是否使用了非零 delay。
- 仿真完成进入、运行、退出和中断验证。
- 真机首次使用保守速度与增益。
- 所有模型状态保留 zero torque 路径。
- 状态能力在启动图检查中通过。
- 运行采样严格遵守
advance。 - 准备资源有取消回滚。