# Unitree G1

<video
  src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/unitree_g1_passing_can.mp4"
  autoplay
  loop
  muted
  playsinline
  style={{ width: "100%" }}
/>

Le humanoïde Unitree G1 est pris en charge dans LeRobot. Vous pouvez le téléopérer, entraîner des politiques de manipulation locomanipulation, tester en simulation, et plus encore. Les variantes à 29 et 23 degrés de liberté sont toutes deux prises en charge.

## Sélection de l'incarnation

La classe `UnitreeG1` sélectionne ses définitions d'articulations via `UnitreeG1Config.embodiment`. La valeur par défaut est `g1_29` ; les configurations existantes conservent donc le comportement G1-29. Sélectionnez la configuration 23-DoF avec `--robot.embodiment=g1_23` ou en Python :

```python
from lerobot.robots.unitree_g1 import UnitreeG1Config

config = UnitreeG1Config(embodiment="g1_23", is_simulation=False)
```

| Capacité                                          | G1-29                         | G1-23                                        |
| --------------------------------------------------- | ----------------------------- | -------------------------------------------- |
| Configuration conjointe et fonctionnalités d'action/observation | 29 articulations du corps, 14 articulations des bras | 23 articulations du corps, 10 articulations des bras                |
| Disposition des commandes/retours DDS                         | 29 slots actifs               | 29 emplacements avec des articulations absentes laissés inactifs    |
| Simulation MuJoCo                                   | Prise en charge                     | Non pris en charge pour le moment ; modèle natif prévu      |
| Contrôleurs de locomotion                              | GR00T et Holosoma            | GR00T et Holosoma                           |
| Contrôleur corps entier SONIC                         | Prise en charge                     | Non pris en charge ; la politique nécessite 29 degrés de liberté    |
| Compensation de la gravité                                | Prise en charge, désactivée par défaut     | Non pris en charge pour le moment ; cinématique native prévue |

Les deux variantes utilisent la même disposition de message DDS à 29 emplacements. G1-29 utilise les 29 emplacements, tandis que G1-23 en utilise 23 et laisse les six emplacements de joints inexistants inactifs. Les vecteurs `kp`, `kd` et `default_positions` restent donc de longueur 29 et sont indexés par emplacement DDS. Les emplacements de bras actifs de G1-23 sont `15..19` et `22..26`. Les emplacements `13, 14, 20, 21, 27, 28` n'ont pas de joint actif ; leurs valeurs de configuration doivent être nulles. Ils sont omis des fonctionnalités d'action/observation, et non renumérotés. Les entrées de commande inactives et réservées sont effacées avant la publication d'un message G1-23. Les remplacements explicites de gains doivent être finis, non négatifs et utiliser la même disposition creuse. Les gains par défaut sont partagés par nom de joint entre les deux variantes, de sorte que les joints physiques identiques utilisent des gains identiques, tandis que les emplacements absents de G1-23 restent nuls.

La simulation MuJoCo native de G1-23 n'est pas encore prise en charge. La sélection de l'incarnation G1-23 avec la simulation activée échoue avant l'initialisation du transport. Un modèle MuJoCo natif pour G1-23 est prévu pour une version future.

---

## Partie 1 : Premiers pas

### Installer le SDK Unitree

Suivez le [guide d’installation de unitree_sdk2_python](https://github.com/unitreerobotics/unitree_sdk2_python#installation). Testé avec `unitree_sdk2py==1.0.1` et `cyclonedds==0.10.2` :

```bash
conda create -y -n lerobot python=3.12
conda activate lerobot
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git
cd unitree_sdk2_python
pip install -e .
cd ..
```

### Installer LeRobot

```bash
conda install ffmpeg -c conda-forge
conda install -c conda-forge "pinocchio>=3.0.0,<4.0.0"
git clone https://github.com/huggingface/lerobot.git
cd lerobot
pip install -e '.[unitree_g1]'
```

  Pour le moment, pinocchio doit être installé via conda-forge (et non pip) afin d'inclure les bindings CasADi nécessaires pour la cinématique inverse des bras.

### Tester l’installation (simulation)

L'environnement de simulation possède ses propres dépendances. Consultez les dépendances de l'environnement de simulation : [Unitree G1 Mujoco EnvHub](https://huggingface.co/lerobot/unitree-g1-mujoco/tree/main).

```bash
pip install mujoco loguru msgpack msgpack-numpy
```

```bash
lerobot-teleoperate \
  --robot.type=unitree_g1 \
  --robot.is_simulation=true \
  --teleop.type=unitree_g1 \
  --teleop.id=wbc_unitree \
  --robot.cameras='{"global_view": {"type": "zmq", "server_address": "localhost", "port": 5555, "camera_name": "head_camera", "width": 640, "height": 480, "fps": 30, "warmup_s": 5}}' \
  --display_data=true \
  --robot.controller=GrootLocomotionController
```

Cela lancera une [instance de simulation MuJoCo](https://huggingface.co/lerobot/unitree-g1-mujoco/tree/main) pour le G1. Vous pouvez connecter une manette à votre machine avant le lancement afin de contrôler la locomotion du robot dans la simulation. Nous prenons en charge à la fois [HolosomaLocomotionController](https://github.com/amazon-far/holosoma) et [GrootLocomotionController](https://github.com/NVlabs/GR00T-WholeBodyControl) via `--robot.controller`.

- Appuyez sur `9` pour libérer le robot
- Appuyez sur `7` / `8` pour augmenter / diminuer la hauteur de la taille

#### Choisir l'effecteur terminal

`--robot.end_effector` indique ce que les bras portent : `dex1` pour les pinces parallèles (par défaut), `dex3` pour les mains à trois doigts, ou `dummy` pour des poignets nus. En simulation, il sélectionne le modèle MuJoCo, qui détermine les actionneurs des doigts et les caméras présentes dans la scène — les caméras de poignet sont montées sur l’effecteur terminal, de sorte que `dummy` ne possède que `head_camera`.

```bash
lerobot-teleoperate \
  --robot.type=unitree_g1 \
  --robot.is_simulation=true \
  --robot.end_effector=dex3 \
  --teleop.id=wbc_unitree \
  --robot.controller=GrootLocomotionController
```

Le simulateur publie les caméras du modèle via ZMQ sur `--robot.sim_camera_port` (5555 par défaut), c’est là que les caméras `zmq` de `--robot.cameras` les lisent. Passez `--robot.sim_publish_images=false` pour ouvrir la fenêtre interactive de MuJoCo à la place : le simulateur rend soit ses caméras, soit sa fenêtre, jamais les deux, car les contextes hors écran appartiennent au thread qui les crée et non au visualiseur. `--robot.sim_onscreen` nomme explicitement ce choix au lieu de le laisser aux caméras, et demander les deux est refusé ; définissez-le et `--robot.sim_publish_images` tous deux à false pour ne rien rendre, ce qu’une machine sans affichage peut faire.

### Se connecter au robot physique

L'adresse IP Ethernet du G1 est fixée à `192.168.123.164`. Votre machine doit avoir une IP statique sur le même sous-réseau : `192.168.123.x` où `x ≠ 164`.

```bash
# Replace 'enp131s0' with your ethernet interface name (check with `ip a`)
sudo ip addr flush dev enp131s0
sudo ip addr add 192.168.123.200/24 dev enp131s0
sudo ip link set enp131s0 up
```

### Se connecter au robot via SSH

```bash
ssh unitree@192.168.123.164
# Password: 123
```

### Partager Internet via Ethernet

Le G1 a besoin d'un accès à Internet pour cloner des dépôts et installer des paquets. Partagez la connexion de votre ordinateur portable via Ethernet :

**Sur votre ordinateur portable :**

```bash
sudo sysctl -w net.ipv4.ip_forward=1

# Replace wlp132s0f0 with your WiFi interface name
sudo iptables -t nat -A POSTROUTING -o wlp132s0f0 -s 192.168.123.0/24 -j MASQUERADE
sudo iptables -A FORWARD -i wlp132s0f0 -o enp131s0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i enp131s0 -o wlp132s0f0 -j ACCEPT
```

**Sur le G1 :**

```bash
sudo ip route del default 2>/dev/null || true
sudo ip route add default via 192.168.123.200 dev eth0
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf

# Verify
ping -c 3 8.8.8.8
```

### Installer le SDK Unitree sur le G1

Suivez le [guide d’installation de unitree_sdk2_python](https://github.com/unitreerobotics/unitree_sdk2_python#installation) :

```bash
conda create -y -n lerobot python=3.12
conda activate lerobot
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git
cd unitree_sdk2_python
python -m pip install -e .
cd ..
```

### Installer LeRobot sur le G1

```bash
git clone https://github.com/huggingface/lerobot.git
cd lerobot
conda install -c conda-forge "pinocchio>=3.0.0,<4.0.0"
python -m pip install -e '.[unitree_g1]'
```

  Pour le moment, pinocchio doit être installé via conda-forge (et non pip) afin d'inclure les bindings CasADi nécessaires pour la cinématique inverse des bras.

### (Facultatif) Activer le WiFi sur le robot

Pour un accès SSH sans fil, vous pouvez activer le WiFi sur le G1 (il est bloqué par défaut) :

```bash
sudo rfkill unblock all
sudo ip link set wlan0 up
sudo nmcli radio wifi on
sudo nmcli device set wlan0 managed yes
sudo systemctl restart NetworkManager
```

**Se connecter à un réseau WiFi :**

```bash
nmcli device wifi list

sudo nmcli connection add type wifi ifname wlan0 con-name "YourNetwork" ssid "YourNetwork"
sudo nmcli connection modify "YourNetwork" wifi-sec.key-mgmt wpa-psk
sudo nmcli connection modify "YourNetwork" wifi-sec.psk "YourPassword"
sudo nmcli connection modify "YourNetwork" connection.autoconnect yes
sudo nmcli connection up "YourNetwork"

ip a show wlan0
```

Vous pouvez ensuite vous connecter en SSH via le Wi-Fi plutôt que l’Ethernet :

```bash
ssh unitree@<ROBOT_WIFI_IP>
# Password: 123
```

---

## Partie 2 : Téléopération & Locomotion

### Lancer le serveur Robot

Sur le robot (depuis `~/lerobot`) :

```bash
cd ~/lerobot
python src/lerobot/robots/unitree_g1/run_g1_server.py --camera
```

### Exécuter la politique de locomotion

Vous pouvez exécuter le client de téléopération depuis votre ordinateur portable via Ethernet, via WiFi (expérimental) ou directement sur le robot lui-même. Tenez compte de la latence potentielle introduite par votre réseau.

**Depuis votre ordinateur portable :**

```bash
lerobot-teleoperate \
  --robot.type=unitree_g1 \
  --robot.is_simulation=false \
  --robot.robot_ip=<ROBOT_IP> \
  --teleop.type=unitree_g1 \
  --teleop.id=wbc_unitree \
  --robot.cameras='{"global_view": {"type": "zmq", "server_address": "<ROBOT_IP>", "port": 5555, "camera_name": "head_camera", "width": 640, "height": 480, "fps": 30}}' \
  --display_data=true \
  --robot.controller=HolosomaLocomotionController
```

Nous prenons en charge à la fois [GrootLocomotionController](https://github.com/NVlabs/GR00T-WholeBodyControl) et [HolosomaLocomotionController](https://github.com/amazon-far/holosoma) via `--robot.controller`.

---

## Partie 3 : Loco-manipulation avec l'exosquelette Homunculus

Nous proposons une solution de loco-manipulation via l'exosquelette Homunculus — un exosquelette open source à 7 degrés de liberté pour le contrôle du corps entier. Découvrez-le [ici](https://github.com/nepyope/hmc_exo).

### Calibrer

```bash
lerobot-calibrate \
  --teleop.type=unitree_g1 \
  --teleop.left_arm_config.port=/dev/ttyACM1 \
  --teleop.right_arm_config.port=/dev/ttyACM0 \
  --teleop.id=exo
```

Pendant l'étalonnage, faites parcourir à chaque articulation toute sa plage de mouvement. Après l'ajustement, placez l'articulation dans une position neutre et appuyez sur `n` pour passer à la suite.

### Enregistrer un jeu de données

```bash
lerobot-record \
  --robot.type=unitree_g1 \
  --robot.is_simulation=true \
  --robot.cameras='{"global_view": {"type": "zmq", "server_address": "localhost", "port": 5555, "camera_name": "head_camera", "width": 640, "height": 480, "fps": 30}}' \
  --teleop.type=unitree_g1 \
  --teleop.left_arm_config.port=/dev/ttyACM1 \
  --teleop.right_arm_config.port=/dev/ttyACM0 \
  --teleop.id=exo \
  --dataset.repo_id=your-username/dataset-name \
  --dataset.single_task="Test" \
  --dataset.num_episodes=2 \
  --dataset.episode_time_s=5 \
  --dataset.reset_time_s=5 \
  --dataset.push_to_hub=true \
  --dataset.streaming_encoding=true \
  --dataset.encoder_threads=2
```

> **Remarque :** Omettez `--teleop.left_arm_config.port` et `--teleop.right_arm_config.port` si vous utilisez uniquement la manette.

Exemple de jeu de données : [nepyope/unitree_box_move_blue_full](https://huggingface.co/datasets/nepyope/unitree_box_move_blue_full)

---

## Partie 4 : Entraînement et inférence

### Entraînement

```bash
python src/lerobot/scripts/lerobot_train.py \
  --dataset.repo_id=your-username/dataset-name  \
  --policy.type=pi05 \
  --output_dir=./outputs/pi05_training \
  --job_name=pi05_training \
  --policy.repo_id=your-username/your-repo-id \
  --policy.pretrained_path=lerobot/pi05_base \
  --policy.compile_model=true \
  --policy.gradient_checkpointing=true \
  --trackio.enable=true \
  --policy.dtype=bfloat16 \
  --policy.freeze_vision_encoder=false \
  --policy.train_expert_only=false \
  --steps=3000 \
  --policy.device=cuda \
  --batch_size=32
```

### Inférence avec RTC

Une politique entraînée compatible peut utiliser le RTC d’inférence local :

```bash
lerobot-rollout \
  --strategy.type=base \
  --policy.path=your-username/your-repo-id \
  --policy.device=cuda \
  --robot.type=unitree_g1 \
  --robot.is_simulation=false \
  --robot.controller=HolosomaLocomotionController \
  --robot.cameras='{"global_view": {"type": "zmq", "server_address": "<ROBOT_IP>", "port": 5555, "camera_name": "head_camera", "width": 640, "height": 480, "fps": 30}}' \
  --task="task_description" \
  --duration=1000 \
  --fps=30 \
  --inference.type=rtc
```

Les contrôleurs de locomotion G1 ne déclarent pas le contrat de cible conservée utilisé par l'attente récupérable de la rollout. Une fois le mouvement initié, l'épuisement de leur tampon d'actions provoque donc une défaillance terminale plutôt qu'une attente de période de grâce. Les délais de demande et les limites d'âge des observations s'appliquent également. Validez que le chunk utilisable de la politique couvre la latence d'inférence avant d'utiliser cette recette ; voir [attente et budgets RTC locaux](./inference#inference-backends). SONIC possède un contrat de jeton conservé distinct, décrit ci-dessous.

---

## Partie 5 : Contrôle du corps entier SONIC (politiques de jetons latents)

LeRobot intègre désormais la moitié **décodeur** de la pile de déploiement du corps entier [SONIC](https://nvlabs.github.io/GEAR-SONIC/) de NVIDIA sous la forme du `SonicWholeBodyController`. Au lieu de commander les articulations, la politique émet à chaque tick un **token latent de mouvement** de dimension 64 (`motion_token.{i}.pos`) ; le décodeur SONIC associe ce token à l’historique récent de la proprioception pour produire une action résiduelle, mise à l’échelle et ajoutée à la pose debout, générant ainsi des cibles de position articulaire à 50 Hz pour les 29 degrés de liberté.

Le contrôleur charge son décodeur ONNX et toutes les constantes de déploiement (gains PD, `default_angles`, `action_scale` et le jeton neutre) depuis [`lerobot/sonic_decoder`](https://huggingface.co/lerobot/sonic_decoder). Le dépôt inclut également un décodeur distillé à faible latence, sélectionnable via l'argument `policy_type="low_latency"` du contrôleur (valeur par défaut : `"default"`).

### Test en simulation

Toute politique de sortie de jetons entraînée sur des jetons de mouvement SONIC (par exemple [`nepyope/sonic_walk`](https://huggingface.co/nepyope/sonic_walk)) peut la piloter :

```bash
lerobot-rollout \
  --strategy.type=base \
  --policy.path=nepyope/sonic_walk \
  --policy.device=cuda \
  --robot.type=unitree_g1 \
  --robot.is_simulation=true \
  --robot.controller=SonicWholeBodyController \
  --robot.cameras='{"ego_view": {"type": "zmq", "server_address": "localhost", "port": 5555, "camera_name": "head_camera", "width": 640, "height": 480, "fps": 30, "warmup_s": 15}}'
```

### Exécuter sur le robot physique

Exécutez le serveur de politique [remote inference](./remote_inference) sur la machine GPU et conservez le pont robot, le décodeur SONIC et le client de rollout sur le G1 Jetson. Seuls des blocs de jetons de 64 dimensions traversent le réseau ; le décodeur et la boucle `rt/lowcmd` à 50 Hz restent à bord.

Installez l'extra distant sur les deux machines. Conservez l'extra Unitree sur le G1 :

```bash
# GPU machine
python -m pip install -e '.[remote]'

# G1 Jetson
python -m pip install -e '.[unitree_g1,remote]'
```

Sur la machine GPU, démarrez le serveur de politique depuis la racine du dépôt. Le profil maintenu correspond à `nepyope/sonic_walk` ; pour une autre politique, copiez et mettez à jour son checkpoint et son contrat de fonctionnalités ensemble.

```bash
lerobot-policy-server \
  --config_path=examples/remote_inference/unitree_g1_sonic.yaml
```

Sur le G1, gardez le pont et la caméra en fonctionnement :

```bash
python src/lerobot/robots/unitree_g1/run_g1_server.py --camera
```

Puis démarrez le client de rollout dans un second terminal G1, en remplaçant `<GPU_MACHINE_IP>` :

```bash
lerobot-rollout \
  --strategy.type=base \
  --inference.type=remote \
  --inference.endpoint=tcp/<GPU_MACHINE_IP>:7447 \
  --inference.deployment=g1-sonic \
  --inference.semantics=unitree-g1-sonic-motion-token-v1 \
  --inference.encoding=jpeg \
  --inference.action_starvation_grace_s=1.0 \
  --robot.type=unitree_g1 \
  --robot.is_simulation=false \
  --robot.robot_ip=127.0.0.1 \
  --robot.controller=SonicWholeBodyController \
  --robot.cameras='{"ego_view": {"type": "zmq", "server_address": "127.0.0.1", "port": 5555, "camera_name": "head_camera", "width": 640, "height": 480, "fps": 30, "warmup_s": 5}}' \
  --task='walk forward' \
  --fps=30 \
  --return_to_initial_position=false \
  --interactive=true
```

Tapez `/start` uniquement une fois que le G1 est prêt à se déplacer. SONIC déclare un support de l’attente, car la réémission du dernier jeton complet permet au décodeur embarqué de maintenir son équilibre. Avant le premier résultat de politique, le déroulement envoie le jeton neutre du décodeur. Si le tampon d’actions se vide, il répète le dernier jeton appliqué pendant au plus `action_starvation_grace_s` (une seconde ci-dessus) ; si aucune nouvelle prédiction n’arrive, le déroulement déclenche une erreur et suit son chemin d’arrêt/déconnexion configuré. Les contrôleurs de locomotion du G1 ne sont toujours pas pris en charge, car leurs vitesses de joystick ne peuvent pas être conservées de cette manière.

La recette désactive le retour à la position initiale : l’interpolation à travers l’espace des jetons latents de SONIC ne constitue pas une trajectoire de retour physique validée. Évitez `/reset` pour la même raison, car cela ramènerait le jeton à sa valeur de connexion. `/stop` ferme toujours la session distante et déconnecte le robot via le chemin d’arrêt normal du contrôleur.

Avec `SonicWholeBodyController`, l'état d'observation est l'écho de jeton 64-D plutôt que les 29 positions articulaires, de sorte qu'un point de contrôle compatible doit être entraîné sur cet état. À la connexion, le robot amène en douceur les articulations vers les `default_angles` du décodeur avant que le contrôleur ne prenne le relais.

---

## Ressources supplémentaires

- [Documentation du SDK Unitree](https://github.com/unitreerobotics/unitree_sdk2_python)
- [GR00T-WholeBodyControl](https://github.com/NVlabs/GR00T-WholeBodyControl)
- [Holosoma](https://github.com/amazon-far/holosoma)
- [Documentation LeRobot](https://github.com/huggingface/lerobot)
- [Unitree IL LeRobot](https://github.com/unitreerobotics/unitree_IL_lerobot)

---

_Dernière mise à jour : juillet 2026_

