FTH 4 · Operar um humanoide real|INEMA.CLUB-PRO

Aula 2 · 20 min

Por dentro do software: DDS, SDK, alto e baixo nível

O seu programa não chama uma função do robô: ele publica e assina mensagens num barramento DDS. Nesta aula você monta a bancada do fabricante (simulador, SDK e CycloneDDS 0.10.2), sobe o G1 simulado e lê a primeira mensagem de estado, pelo mesmo caminho que usaria com o robô real.

Começar a aula
Notebook numa bancada ligado por um cabo de rede a um robô humanoide desfocado ao fundo, com um switch de rede de luzes discretas.

Nesta aula você vai

  1. 1O caminho: seu programa → SDK → DDS → robô ou simulador
  2. 2A bancada em ~/fth com versões fixadas, incluindo o CycloneDDS compilado
  3. 3Alto nível (locomoção pronta) × baixo nível (motor a motor)
  4. 4Domínio e interface: o erro que faz o robô parecer morto

Objetivo: montar a bancada do fabricante (simulador, SDK, CycloneDDS 0.10.2) e ler a primeira mensagem de estado do G1 por DDS.

01Do seu programa ao motor

Clique em cada bloco.

Clique em cada bloco do diagrama para ver o que ele faz.

Trocar o simulador pelo robô muda o domínio e a interface, não o seu código.

02Três coisas para saber antes de instalar

Clique em cada uma.

1. Alto × baixo

Alto nível: o controle de locomoção do fabricante anda e você pede velocidade (LocoClient no serviço "sport": SetVelocity, Move, Damp, ZeroTorque).

Baixo nível: você manda em cada motor por rt/lowcmd. Antes, o exemplo oficial libera o controle interno (MotionSwitcherClient, CheckMode e ReleaseMode), senão os dois disputam os motores.

03Montar a bancada (uma vez)

Venv em ~/fth (o mesmo do curso 0, se você fez), simulador e SDK fixados em commits, e o CycloneDDS 0.10.2 compilado: o pip sozinho falha em aarch64 sem a biblioteca C. Precisa de python3.12 (com venv), git, cmake e compilador C.

Terminal · simulador e SDK
F=${FTH:-$HOME/fth}; mkdir -p $F && cd $F
[ -d .venv ] || python3.12 -m venv .venv; . .venv/bin/activate
pip install -q mujoco==3.16.0 numpy pygame==2.6.1 mcap==1.6.0
[ -d operar ] || git clone -q --depth 1 https://github.com/inematds/formacao-fth-operar-v72 operar
[ -d unitree_mujoco ] || git clone -q --filter=blob:none --sparse https://github.com/unitreerobotics/unitree_mujoco
(cd unitree_mujoco && git sparse-checkout set simulate_python unitree_robots/g1 && git checkout -q 1eb6642)
[ -d unitree_sdk2_python ] || git clone -q https://github.com/unitreerobotics/unitree_sdk2_python
(cd unitree_sdk2_python && git checkout -q 814556d)
for r in unitree_mujoco unitree_sdk2_python; do git -C $r log -1 --format="$r %h (%ad)" --date=short; done
Terminal · CycloneDDS 0.10.2 e SDK
F=${FTH:-$HOME/fth}; cd $F && . .venv/bin/activate
[ -d cyclonedds ] || git clone -q --depth 1 -b 0.10.2 https://github.com/eclipse-cyclonedds/cyclonedds 2>/dev/null
cmake -S cyclonedds -B cyclonedds/build -DCMAKE_INSTALL_PREFIX=$F/cyclonedds/install -DBUILD_EXAMPLES=OFF > /dev/null 2>&1
cmake --build cyclonedds/build --target install -j 4 > /dev/null 2>&1 && echo "cyclonedds C 0.10.2 instalado em $F/cyclonedds/install"
export CYCLONEDDS_HOME=$F/cyclonedds/install
pip install -q cyclonedds==0.10.2 && pip install -q -e unitree_sdk2_python
python3 -c "from importlib.metadata import version as v; import unitree_sdk2py; print('cyclonedds', v('cyclonedds'), '· unitree_sdk2py', v('unitree_sdk2py'), '· mujoco', v('mujoco'))"
Exemplo do repositório
unitree_mujoco 1eb6642 (2026-09-07)
unitree_sdk2_python 814556d (2026-09-21)
cyclonedds C 0.10.2 instalado em ~/fth/cyclonedds/install
cyclonedds 0.10.2 · unitree_sdk2py 1.0.1 · mujoco 3.16.0

Os dois commits e as três versões. A compilação leva alguns minutos; o aviso do compilador fica escondido no /dev/null.

04Subir o G1 simulado e ler o estado

O kit/sim.py roda a ponte do unitree_mujoco sem janela, em segundo plano, por 8 s; o ler_estado.py é outro processo que só assina rt/lowstate. O G1 está pendurado no elástico (o pórtico do simulador).

Terminal · simulador + leitura
F=${FTH:-$HOME/fth}; cd $F/operar && . $F/.venv/bin/activate
python3 kit/sim.py --s 8 --suspenso &
python3 kit/ler_estado.py; echo "saída: $?"
wait
Trecho · kit/op.py
class Estado:
    """Assina rt/lowstate e guarda a última mensagem e quantas chegaram."""

    def __init__(self):
        from unitree_sdk2py.core.channel import ChannelSubscriber
        from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_
        self.ultima, self.n, self.t_ultima = None, 0, 0.0
        self.sub = ChannelSubscriber("rt/lowstate", LowState_)
        self.sub.Init(self._chegou, 10)
Mostrado · só com o robô real, depois do checklist
# Trecho de example/g1/low_level/g1_low_level_example.py (unitree_sdk2_python 814556d)
ChannelFactoryInitialize(0, sys.argv[1])   # domínio 0 + nome da placa ligada ao robô
self.msc = MotionSwitcherClient()
self.msc.SetTimeout(5.0)
self.msc.Init()
status, result = self.msc.CheckMode()
while result['name']:                      # libera o controle interno antes do baixo nível
    self.msc.ReleaseMode()
    status, result = self.msc.CheckMode()
    time.sleep(1)
Exemplo do repositório
simulador: G1 29 motores · DDS domínio 1 em lo · 8 s · suspenso: sim
tópico: rt/lowstate · tipo: unitree_hg LowState_
IMU quatérnio (w, x, y, z): [1.0, 0.0, 0.1, 0.0] · tronco em pé (inclinação < 20°): sim
junta 0 (left_hip_pitch): q = 0.0 rad · torque estimado = 0 N·m
motores no pacote: 35 (o G1 de 29 GDL usa os índices 0 a 28)
saída: 0
simulador: fim em t = 8.0 s · robô suspenso (pés fora do chão)

O pacote tem 35 posições de motor; o G1 de 29 GDL usa as 29 primeiras. Motores soltos: torque zero.

05Prática

Bancada pronta e primeira mensagem lida.

E1 · Montar a bancada

15 min

Rode os dois blocos de preparação. Se o cmake reclamar, instale cmake e um compilador C pelo gerenciador do seu sistema e rode o segundo bloco de novo.

Pronto quando: a saída mostra cyclonedds 0.10.2 · unitree_sdk2py 1.0.1 · mujoco 3.16.0.

E2 · Ler o G1

5 min

Rode o bloco do simulador com a leitura e explique por que o pacote tem 35 motores se o G1 tem 29.

Pronto quando: python3 kit/ler_estado.py; echo "saída: $?" (com o sim.py rodando) → motores no pacote: 35 (o G1 de 29 GDL usa os índices 0 a 28) e saída: 0.

E3 · Ver o erro do domínio

3 min

Rode só o ler_estado.py, sem o simulador, e espere os 10 s.

Pronto quando: ERRO: nenhuma mensagem em rt/lowstate em 10 s e saída: 1.

06Teste da aula

A explicação aparece depois da sua resposta.

Pergunta 1 de 4

O ler_estado.py ficou 10 s esperando e deu erro. Qual a causa mais provável?competência

07Em três frases

BarramentoPrograma e robô conversam por DDS: rt/lowstate e rt/lowcmd.
DomínioSimulador no domínio 1 em lo; robô no 0 na placa de rede.
Bancadaunitree_mujoco 1eb6642, SDK 814556d e CycloneDDS 0.10.2.

Para ir além

Aula anteriorPróxima aula