FTH 4 · Operating a real humanoid|INEMA.CLUB-PRO

Lesson 4 · 20 min

Send low-level commands without breaking anything

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
Knee and leg of a humanoid robot suspended on a gantry, with a cable running to a motor driver board on the bench.

In this lesson you will

  1. 1What goes in a MotorCmd_: mode, q, dq, tau, kp, kd
  2. 2Ramp × step: the torque each one asks for
  3. 3Watchdog: a stale command becomes damping
  4. 4CRC and mode_machine, which the real robot checks

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.

01Three protections between the policy and the motor

Click each block.

Click each block in the diagram to see what it does.

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.

02What goes in a command

Click each part.

1. MotorCmd_

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.

Gains from the official exampleLegs 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".

03Ramp, limits and watchdog on the suspended G1

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.

Terminal · simulator + ramp
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
Excerpt · kit/rampa.py
    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]
Terminal · the common mistake: step
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: $?"
wait
Example from the repository
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)
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.

04Practice

The three protections, measured.

E1 · Ramp with watchdog

5 min

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.

E2 · Watch the step fail

5 min

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.

E3 · Read the limit in the MJCF

5 min

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).

05Lesson quiz

The explanation appears after your answer.

Question 1 of 4

Which field does NOT exist in a G1 MotorCmd_?skill

06In three sentences

LimitTarget clipped to the MJCF range; torque capped at the ctrlrange.
RampFrom the current point to the target over seconds, never all at once.
WatchdogA stale command turns into damping and does not turn back on on its own.

Going further

Previous lessonNext lesson