E1 · Ramp with watchdog
5 minRun the ramp block and find in the output the moment the watchdog acts.
Done when: python3 kit/rampa.py; echo "saída: $?" → juntas que pediram acima do limite: 0, watchdog: amortecimento ativado and saída: 0.
Lesson 4 · 20 min
Sending a target to 29 motors is easy; the hard part is what happens when the target is far away, out of range, or stops arriving. In this lesson you command the suspended G1 with three protections (MJCF limit, ramp, and watchdog) and see, in the same script, the step ask for more torque than the motor has.
Start the lesson
Goal: command the G1's 29 joints with a limit, a ramp, and a watchdog, and see the step command ask for torque above the limit.
Click each block.
The kit's watchdog is yours, in your controller. What the robot's firmware does with no command is not documented in the SDK: don't count on it. And it runs in the same process as the sender: if the process dies, what holds the robot is the physical stop and the gantry.
Click each part.
mode (1 enables, 0 disables), q (target), dq (target velocity), tau (added torque), kp and kd. There is no Ki and no global "position" or "torque" mode.
Stiff = high kp; torque only = kp and kd zero with tau filled in; damping = kp zero and a small kd.
Legs 60/60/60/100/40/40, waist 60/40/40, arms 40; kd 1 (knee 2). A starting point from the example, not a "robot default".
mode_pr = 0 (PR: pitch/roll in series), mode_machine copied from the first LowState_, and crc = CRC().Crc(cmd) before publishing.
The Python simulator does not check the CRC and sends mode_machine = 0; the kit fills in both anyway, like the official example, so the code works the same on the robot.
The official G1 example sends every 2 ms (control_dt_ = 0.002). The policy can be slower (50 Hz): the fast loop resends the last target with the limits.
This is the loop where the watchdog lives: it sees when the target gets old.
6 s of command: a 3 s ramp to the working pose (knees and elbows bent), hold, and at t = 4.5 s the "policy" freezes on purpose.
F=${FTH:-$HOME/fth}; cd $F/operar && . $F/.venv/bin/activate
python3 kit/sim.py --s 9 --suspenso &
python3 kit/rampa.py; echo "saída: $?"
wait idade = t_ciclo - comp["carimbo"]
if not amortecendo and idade > IDADE_MAX:
amortecendo = True
print(f"política sem resposta desde t = {a.trava_em:.1f} s")
print(f"watchdog: comando velho há mais de {IDADE_MAX * 1000:.0f} ms → amortecimento ativado (kp=0, kd={KD_AMORTECER:g})")
cmd.mode_pr, cmd.mode_machine = 0, modo
for i in range(op.N):
m_ = cmd.motor_cmd[i]
m_.mode, m_.dq, m_.tau = 1, 0.0, 0.0
if amortecendo:
m_.q, m_.kp, m_.kd = 0.0, 0.0, KD_AMORTECER
else:
m_.q, m_.kp, m_.kd = comp["alvo"][i], op.KP[i], op.KD[i]F=${FTH:-$HOME/fth}; cd $F/operar && . $F/.venv/bin/activate
python3 kit/sim.py --s 9 --suspenso &
python3 kit/rampa.py --sem-rampa; echo "saída: $?"
waitsimulador: G1 29 motores · DDS domínio 1 em lo · 9 s · suspenso: sim estado recebido: 29 juntas · pose inicial lida · mode_machine = 0 limites: 29/29 alvos já dentro da faixa do MJCF (margem 0.05 rad), 0 recortados política sem resposta desde t = 4.5 s watchdog: comando velho há mais de 100 ms → amortecimento ativado (kp=0, kd=2) rampa de 3 s · todas as juntas pediram menos de 50 % do torque máximo juntas que pediram acima do limite: 0 ciclo de envio: 2 ms · watchdog: amortecimento ativado saída: 0 simulador: fim em t = 9.0 s · robô suspenso (pés fora do chão)
With a ramp, every joint stays below half the ceiling. The policy freezes, the watchdog sees the command get old and damps.
simulador: G1 29 motores · DDS domínio 1 em lo · 9 s · suspenso: sim estado recebido: 29 juntas · pose inicial lida · mode_machine = 0 limites: 29/29 alvos já dentro da faixa do MJCF (margem 0.05 rad), 0 recortados política sem resposta desde t = 4.5 s watchdog: comando velho há mais de 100 ms → amortecimento ativado (kp=0, kd=2) degrau · junta mais perto do limite: left_wrist_pitch (9 de 5 N·m, ≈ 170 %) juntas que pediram acima do limite: 4 ciclo de envio: 2 ms · watchdog: amortecimento ativado saída: 1 simulador: fim em t = 9.0 s · robô suspenso (pés fora do chão)
The same target all at once: four joints ask for more than the ceiling, and the wrist asks for almost double what it has. On the real robot this means a jolt and a saturated motor; the script exits with code 1.
The three protections, measured.
Run the ramp block and find in the output the moment the watchdog acts.
Done when: python3 kit/rampa.py; echo "saída: $?" → juntas que pediram acima do limite: 0, watchdog: amortecimento ativado and saída: 0.
Run the common-mistake block and say which joint went furthest past the limit and why that one.
Done when: python3 kit/rampa.py --sem-rampa; echo "saída: $?" → juntas que pediram acima do limite: 4 and saída: 1.
Find in the simulator's MJCF the torque ceiling of the wrist that blew past it in the step.
Done when: grep -c 'ctrlrange="-5 5"' ~/fth/unitree_mujoco/unitree_robots/g1/g1_29dof.xml → 4 (pitch and yaw of both wrists, 5 N·m cap).
The explanation appears after your answer.
Question 1 of 4
Which field does NOT exist in a G1 MotorCmd_?skill
Question 2 of 4
The policy stopped sending targets. What does the kit's watchdog do?
Question 3 of 4
Why did the step ask for more torque than the ramp, with the same target?
Question 4 of 4
The simulator does not check the CRC. Why does the kit fill it in anyway?
Choose an option. The explanation appears after your attempt.