E1 · Manufacturer's path ready
10 minRun the DDS, SDK and unitree_mujoco setup.
Done when: the block ends with cyclonedds 0.10.2 · unitree_sdk2py ok.
Lesson 6 · 18 min
Before any robot, the policy has to pass in a second simulator and through the same message path the robot uses. It's the official unitree_rl_lab flow: train in Isaac Lab, validate in unitree_mujoco, and only then the G1. In this lesson you build that gate in three stages, with real DDS, and you see the error that most often brings policies down on this crossing.
Start the lesson
Goal: run the policy through a three-stage gate: a different robot model and the manufacturer's DDS message path.
Click each block. Fail one stage, and you don't move up.
A 2026 study with the G1 on construction-site actions evaluated each action in two physics engines before the robot, and only those that passed went to the physical G1 (arXiv 2608.01600, according to robotica-real2sim, checked on 10/14/2026).
Real findings from building this gate (10/19/2026). Click each one.
Isaac Lab orders the joints one way; the robot's SDK, another. The deploy.yaml carries the map (joint_ids_map), and each list comes in its own order: kp and kd in the robot's order, default pose and actions in Isaac Lab's order.
Getting this wrong brings the robot down in 1 s, in both simulators (common error below).
unitree_mujoco comes with a 5 ms step and an Euler integrator. With the wrist kd = 10 (limit of 5 N·m on pitch and yaw), the PD oscillates and the arm stays at the torque limit the whole time. The gait does not suffer; the gate gives a warning, it does not fail.
With 1–2 ms, the warning goes away. It comes from the simulator, not the policy: check it before touching the training.
The lo interface does not do multicast, and without multicast two DDS processes cannot find each other. The kit adds a fixed peer at 127.0.0.1 to the Unitree configuration (s2r.iniciar_dds) only when the interface is lo.
On the robot, the interface is the network card connected to it; the domain and the topics are the same.
The SDK asks for the Python package cyclonedds==0.10.2. On aarch64 there is no wheel: pip fails without the C library. The block builds the releases/0.10.x branch (5041f35) with cmake, in 1–2 min.
The C tag 0.10.2 aborted here with "buffer overflow detected". On x86_64, pip usually finds the wheel (reported, not tested here).
CycloneDDS 0.10.2, unitree_sdk2_python and unitree_mujoco, pinned to the tested commits. You need cmake and a C compiler if pip does not find a wheel.
F=${FTH:-$HOME/fth}; cd $F && . .venv/bin/activate
pip install -q cyclonedds==0.10.2 2>/dev/null || {
[ -d cyclonedds ] || git clone -q --filter=blob:none -b releases/0.10.x https://github.com/eclipse-cyclonedds/cyclonedds
git -C cyclonedds checkout -q 5041f35
cmake -S cyclonedds -B cyclonedds/build -DCMAKE_INSTALL_PREFIX=$F/cyclonedds/install -DBUILD_EXAMPLES=OFF > /dev/null
cmake --build cyclonedds/build --target install -j 4 > /dev/null 2>&1
export CYCLONEDDS_HOME=$F/cyclonedds/install && pip install -q cyclonedds==0.10.2; }
git -C cyclonedds log -1 --format="cyclonedds (C) %H (%ad)" --date=short 2>/dev/null | sed -E 's/([0-9a-f]{7})[0-9a-f]{33}/\1/'
[ -d unitree_sdk2_python ] || git clone -q https://github.com/unitreerobotics/unitree_sdk2_python
git -C unitree_sdk2_python checkout -q 814556d && pip install -q -e unitree_sdk2_python pygame
[ -d unitree_mujoco ] || git clone -q https://github.com/unitreerobotics/unitree_mujoco
git -C unitree_mujoco checkout -q 1eb6642 && git -C unitree_mujoco log -1 --format='unitree_mujoco %h (%ad)' --date=short
python3 -c "import importlib.metadata as m, unitree_sdk2py; print('cyclonedds', m.version('cyclonedds'), '· unitree_sdk2py ok')"cyclonedds (C) 5041f35 (2026-03-22) unitree_mujoco 1eb6642 (2026-09-07) cyclonedds 0.10.2 · unitree_sdk2py ok
Three lines. The first time on this machine (aarch64), building CycloneDDS took 1 to 2 minutes.
Without --dds, steps 1 and 2 (deterministic, ~1 min). With --dds, step 3 in real time. --passo changes the unitree_mujoco step.
F=${FTH:-$HOME/fth}; cd $F/sim2real && . $F/.venv/bin/activate
python3 kit/portao_sim2sim.py; echo "saída: $?"F=${FTH:-$HOME/fth}; cd $F/sim2real && . $F/.venv/bin/activate
python3 kit/portao_sim2sim.py --passo 0.001 --dds 2>/dev/null; echo "saída: $?"F=${FTH:-$HOME/fth}; cd $F/sim2real && . $F/.venv/bin/activate
python3 kit/portao_sim2sim.py --ordem sdk; echo "saída: $?"política: unitree_rl_lab velocity v0 · ordem das juntas: isaac · 0.5 m/s por 10 s
1 Menagerie (MuJoCo, PD no MJCF) passou · andou 4.7 m
2 unitree_mujoco (passo 5 ms) passou · andou 4.8 m
aviso: braços no limite de torque em 99% do tempo
PORTÃO: passou (2 de 2 etapas)
saída: 0It passed, with the arms warning at the 5 ms step.
política: unitree_rl_lab velocity v0 · ordem das juntas: isaac · 0.5 m/s por 10 s 1 Menagerie (MuJoCo, PD no MJCF) passou · andou 4.7 m 2 unitree_mujoco (passo 1 ms) passou · andou 4.7 m 3 unitree_mujoco + DDS (tempo real) passou · andou 5.3 m · política a 50 Hz PORTÃO: passou (3 de 3 etapas) saída: 0
Step 3 runs in real time: the distance and the arms warning change a little from one run to the next. What counts is the last line.
política: unitree_rl_lab velocity v0 · ordem das juntas: sdk · 0.5 m/s por 10 s
1 Menagerie (MuJoCo, PD no MJCF) reprovou · andou -0.1 m e caiu · caiu em t = 1.0 s; pernas/cintura no limite de torque em 3% do tempo (máx. 1 %)
aviso: braços no limite de torque em 6% do tempo
2 unitree_mujoco (passo 5 ms) reprovou · andou -0.3 m e caiu · caiu em t = 1.0 s; pernas/cintura no limite de torque em 6% do tempo (máx. 1 %)
aviso: braços no limite de torque em 98% do tempo
PORTÃO: reprovou — caiu em t = 1.0 s
saída: 1With --ordem sdk, the kit uses the default pose and the actions as if they were already in the robot's order. The G1 falls in 1 s in both models. If your policy falls as soon as it starts, check the map before anything else.
Nothing goes to the robot without passing here.
Run the DDS, SDK and unitree_mujoco setup.
Done when: the block ends with cyclonedds 0.10.2 · unitree_sdk2py ok.
Run the three steps with a 1 ms step.
Done when: python3 kit/portao_sim2sim.py --passo 0.001 --dds 2>/dev/null; echo "saída: $?" → PORTÃO: passou (3 de 3 etapas) and saída: 0.
Run with the wrong joint order and note at which step and how long until it fails.
Done when: python3 kit/portao_sim2sim.py --ordem sdk; echo "saída: $?" → PORTÃO: reprovou — caiu em t = 1.0 s and saída: 1.
The explanation appears after your answer.
Question 1 of 4
Why does step 3 run the robot in a separate process and talk over DDS?skill
Question 2 of 4
The policy falls in 1 s in both models, right at the start. What is the first suspect?
Question 3 of 4
In stage 2 with a 5 ms step, the arms sit at the torque limit, but the gait passes. What do you do?
Question 4 of 4
Why did the lo interface need a fixed peer at 127.0.0.1?
Pick one answer. The explanation appears after you try.