-
Notifications
You must be signed in to change notification settings - Fork 12
Hands On Sim2Real
这篇补上完整的 sim2real 流程:先把新的 IsaacLab .onnx 推理器接进 BxiExample,再写一个状态使用这个推理器,先在 MuJoCo 仿真里验证,最后再上 ELF3 真机。
本文用下面名字做例子:
my_walk.onnx
self.my_walk
MyWalkState
my_walk
my_walk_event
实际接模型时,把 my_walk 换成你的模型名。
第一次接新模型时,不要直接改 initial_state 到新状态,更不要直接上真机。推荐顺序是:
放模型文件
-> BxiExample.load_models() 加推理器
-> robot_states.py 加状态
-> elf3_state_machine.yaml 加事件和状态
-> remote_controller/config/xbox_default.yaml 加按键
-> 仿真验证
-> 真机低风险验证
会修改的文件:
src/bxi_example_py_elf3/bxi_example_py_elf3/bxi_example_demo.py
src/bxi_example_py_elf3/bxi_example_py_elf3/robot_states.py
src/bxi_example_py_elf3/config/elf3_state_machine.yaml
src/remote_controller/config/xbox_default.yaml
会新增或替换模型文件:
src/bxi_example_py_elf3/data/isaaclab_model/my_walk.onnx
如果模型是带参考动作的 motion policy,还会有:
src/bxi_example_py_elf3/data/isaaclab_model/my_motion.npz
src/bxi_example_py_elf3/data/isaaclab_model/my_motion.onnx
把 IsaacLab 导出的 ONNX 放到:
src/bxi_example_py_elf3/data/isaaclab_model/my_walk.onnx
当前 package 会把 data/ 下的文件安装到:
install/bxi_example_py_elf3/share/bxi_example_py_elf3/data/
所以新增模型文件后要重新 build:
colcon build --packages-select bxi_example_py_elf3
source install/setup.bash热重载只会监控已经安装后的模型路径。第一次新增文件时,仍然需要 build 一次。
打开:
src/bxi_example_py_elf3/bxi_example_py_elf3/bxi_example_demo.py
找到 load_models(),现在模型是在这里直接声明的。参考现有的 withoutarm:
self.withoutarm: HumanoidGaitPolicyLiteIsaaclab = HumanoidGaitPolicyLiteIsaaclab(
model_file("isaaclab_model/withoutarm.onnx")
)添加你的推理器:
self.my_walk: HumanoidGaitPolicyLiteIsaaclab = HumanoidGaitPolicyLiteIsaaclab(
model_file("isaaclab_model/my_walk.onnx")
)这里有三个要点:
-
model_file("isaaclab_model/my_walk.onnx")是相对data/的路径。 -
self.my_walk后面会作为policy_attr被状态类访问。 - 只改这里,不需要在 launch 里新增
onnx_file_dict;当前工程已经不走旧的 launch 模型字典。
如果你的模型需要 .npz + .onnx,参考 back_flip 或 ballet:
self.my_motion: DanceMotionPolicyGravityIsaaclab = DanceMotionPolicyGravityIsaaclab(
model_file("isaaclab_model/my_motion.npz"),
model_file("isaaclab_model/my_motion.onnx"),
start_frame=0,
)打开:
src/bxi_example_py_elf3/bxi_example_py_elf3/robot_states.py
如果你的模型和 withoutarm、normal、amp_run 一样是速度控制 gait policy,可以先按下面这个状态接入:
class MyWalkState(RobotControlState):
def __init__(self, name: str, state_id: int, policy_attr: str = "my_walk"):
super().__init__(name, state_id)
self.policy_attr = policy_attr
def _policy(self, ctx: BxiExample) -> Any:
return getattr(ctx, self.policy_attr)
def on_prepare_enter(
self,
ctx: BxiExample,
from_state: StateBehavior[BxiExample],
transition: TransitionProfile,
) -> None:
super().on_prepare_enter(ctx, from_state, transition)
policy = self._policy(ctx)
ctx.preheat_model(
policy,
with_cmd_vel=True,
cmd_vel=self.get_cmd_vel(ctx),
)
def get_first_frame(self, ctx: BxiExample) -> Optional[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:
return None
return self._motor_frame(qpos, policy.kps, policy.kds)
def get_motor_frame(
self,
ctx: BxiExample,
dt: float,
on_translation: bool,
) -> Optional[MotorFrame]:
policy = self._policy(ctx)
cmd_vel = self.get_cmd_vel(ctx)
qpos, _vel = policy.inference_step(
ctx.current_q,
ctx.current_dq,
ctx.current_quat_wxyz,
ctx.current_omega,
cmd_vel,
)
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
frame = self.get_motor_frame(ctx, dt, False)
if frame is not None:
ctx.set_motor_target(*frame)这段代码依赖的 Any、Optional、MotorFrame、RobotControlState、StateBehavior、TransitionProfile,当前 robot_states.py 顶部已经有。
如果你的模型不吃速度命令,就不要传 cmd_vel:
qpos = policy.inference_step(
ctx.current_q,
ctx.current_dq,
ctx.current_quat_wxyz,
ctx.current_omega,
)如果你的模型返回的不是 (qpos, vel),按实际返回值改这一行;状态最终只需要输出:
ctx.set_motor_target(qpos, kp, kd)打开:
src/bxi_example_py_elf3/config/elf3_state_machine.yaml
先在 remote_events 里加一个事件。选择一个当前遥控器配置里还没被其他 event 使用的 MotionCommands slot/value:
my_walk_event:
slot: <unused_motion_command_slot>
value: <unused_value>在 states.normal.transitions.on_event 里加入口。第一次验证只建议从 normal 进入新模型:
my_walk_event:
to: my_walk
transition: soft_switch再在 states: 下添加新状态:
my_walk:
manifest:
label: 新步态
index: 14
group: Advanced
icon: directions_walk
confirm: true
confirm_message: 新模型首次验证,请确保机器人周围没有障碍物
behavior: MyWalkState
speed_profile: normal
params:
policy_attr: my_walk
transitions:
on_event:
normal_event:
to: normal
transition: soft_switch
zero_torque_event: zero_torque关键点:
-
behavior: MyWalkState必须和 Python class 名一致。 -
params.policy_attr: my_walk对应BxiExample.load_models()里的self.my_walk。 -
speed_profile: normal表示状态会读取速度输入,并按speed_profiles.normal约束速度。 -
confirm: true适合所有第一次上机的新模型。
打开:
src/remote_controller/config/xbox_default.yaml
键盘 source 里加一个键,例如 0:
keyboard.my_walk: {from: keyboard.key, key: "0"}controls 里加:
keyboard.my_walk_event: {type: bool, inputs: [{source: keyboard.my_walk}]}outputs 的 level: 里加:
- output: <unused_motion_command_slot>=<unused_value>
when:
any:
- [trigger.right_event, button.y_event]
- [keyboard.my_walk_event]这样键盘按 0,或手柄按 右扳机 + Y,会输出上面声明的 MotionCommands slot/value。这个输出会被上一节的 my_walk_event 接住。
重新 build 并 source:
colcon build --packages-select bxi_example_py_elf3 remote_controller
source install/setup.bash终端 1 启动仿真:
ros2 launch bxi_example_py_elf3 example_demo.launch.py终端 2 启动键盘遥控器:
ros2 launch remote_controller remote_controller_keyboard.launch.py终端 3 观察状态机:
ros2 topic echo /simulation/state_machine_info推荐验证顺序:
1. 等待 reset 结束,状态仍是 zero_torque。
2. 进入 pd_brake 或 initial_pos,确认电机目标正常。
3. 切到 normal,确认机器人能稳定站立或行走。
4. 速度输入保持 0,触发 my_walk_event。
5. 观察 /simulation/state_machine_info.current.name 是否变成 my_walk。
6. 观察 qpos、kp、kd 是否连续,机器人是否立即摔倒。
7. 再逐步给很小的 vx、vy、yaw。
8. 用 normal_event 切回 normal。
9. 用 zero_torque_event 验证安全退出仍然有效。
如果仿真一进状态就摔:
- 先检查 ONNX 输入输出维度是否和 29 DOF 一致。
- 检查 IsaacLab 关节顺序是否和
bxi_example_demo.py的joint_name顺序一致。 - 检查模型期望的四元数顺序。当前 IsaacLab policy 调用用的是
ctx.current_quat_wxyz。 - 检查模型输出是不是弧度位置目标,而不是 delta、力矩或归一化 action。
- 检查
policy.kps、policy.kds是否合理。 - 把
speed_profile的速度上限先调小,再验证。
仿真稳定后,再准备真机。上机前至少确认:
仿真能多次进入和退出 my_walk
normal_event 能回 normal
zero_torque_event 能进入 zero_torque
机器人静止速度输入下不会明显抖动或前冲
速度输入从很小值开始不会失控
状态机没有 unknown state / unknown event 报错
真机不要把:
initial_state: my_walk只保持:
initial_state: zero_torque硬件 launch 当前默认:
{"/topic_prefix": "hardware/"}
{"/hot_reload": False}这是正确的。真机首次验证不要开热重载。
在机器人工作区 build 并 source:
colcon build --packages-select bxi_example_py_elf3 remote_controller
source install/setup.bash启动硬件节点:
ros2 launch bxi_example_py_elf3 example_demo_hw.launch.py如果需要单独启动遥控器:
ros2 launch remote_controller remote_controller.launch.py观察硬件状态机:
ros2 topic echo /hardware/state_machine_info第一次上机顺序:
1. 人和机器人保持安全距离,急停和 stop 按键可用。
2. 从 zero_torque 进入 pd_brake 或 initial_pos。
3. 再进入 normal。
4. 不给速度,触发 my_walk_event。
5. 只停留 1-2 秒,立即切回 normal。
6. 多次确认进入和退出都稳定。
7. 再给很小的 vx。
8. 最后再测试 vy 和 yaw。
如果出现明显抖动、前冲、左右脚顺序不对、髋膝方向反了,立即退出到 normal 或 zero_torque,不要继续调速度。
如果这个模型属于内部或高危动作,打开:
src/bxi_example_py_elf3/config/release_protection.yaml
添加:
my_walk:
behavior:
- MyWalkState
model_keys: [my_walk]
files:
- ../data/isaaclab_model/my_walk.onnxmodel_keys 对应 self.my_walk,files 对应实际模型文件。要删除 .onnx 或 .npz,必须写进 files。
1. my_walk.onnx 已放进 data/isaaclab_model/。
2. bxi_example_demo.py 的 load_models() 有 self.my_walk。
3. robot_states.py 有 MyWalkState。
4. elf3_state_machine.yaml 有 my_walk_event。
5. states.normal 能通过 my_walk_event 切到 my_walk。
6. states.my_walk 的 behavior 是 MyWalkState。
7. states.my_walk.params.policy_attr 是 my_walk。
8. remote_controller/config/xbox_default.yaml 能输出上面声明的 MotionCommands slot/value。
9. colcon build 后 install/ 里能找到模型文件。
10. 仿真 /simulation/state_machine_info 能看到 current.name = my_walk。
11. 真机仍然从 zero_torque 启动。
12. 真机 /hardware/state_machine_info 能看到进入和退出 my_walk。