LeRobot documentation
Isaac Teleop
Isaac Teleop
Pilotez votre robot avec NVIDIA Isaac Teleop, un
framework de téléopération multimodal. Isaac Teleop pilote une seule TeleopSession à partir d'une
gamme de périphériques d'entrée — manettes XR (VR), suivi des mains, suivi du corps entier, gants Manus, pédales
de pied, et plus encore.
Dans LeRobot, Isaac Teleop est fourni en tant qu’exemple autonome sous examples/isaac_teleop_to_so101/.
Chaque périphérique d’entrée Isaac Teleop est sa propre sous-classe de Teleoperator dans le package isaac_teleop de l’exemple, partageant un même cycle de vie de session (voir IsaacTeleopTeleoperator). Les
périphériques disponibles aujourd’hui sont le contrôleur XR (XRController) et un bras leader SO-101 rétro-conductible (SO101LeaderArm) ; les gants Manus et le suivi de la main/corps entier sont les
périphériques naturels suivants. Ce guide se concentre sur le contrôleur XR ; le leader SO-101 est résumée
sous Exécuter l’exemple.
Dans ce guide, vous apprendrez :
- Comment un appareil de téléopération Isaac pilote la cible de l'effecteur terminal (EE) d'un robot
- Comment le clutch (serrage/prise sur le contrôleur XR) active la téléopération sans à-coups du bras
- Comment exécuter l'exemple de téléopération SO‑101 et régler le mouvement / la pince / l'IK
Installation
L’exemple se trouve dans le dépôt LeRobot (il ne fait pas partie du paquet pip lerobot), il faut donc cloner le dépôt et l’installer à partir du code source. La référence d’installation et d’utilisation officielle, toujours à jour, est le README.md de l’exemple ; en résumé :
git clone https://github.com/huggingface/lerobot.git
cd lerobot
uv pip install -e ".[feetech,kinematics,dataset]" "huggingface_hub>=1.5"
uv pip install "isaacteleop[cloudxr,retargeters-lite]~=1.3.131" "scipy>=1.14"isaacteleop est publié sur PyPI public (Linux uniquement). L’extra cloudxr apporte les bindings du runtime CloudXR ; retargeters-lite est le chemin du retargeter basé sur scipy qui se résout à la fois sur x86_64 et ARM (sur aarch64 — par exemple un DGX Spark — l’extra complet retargeters ne se résout pas en raison de ses pins dex-retargeting/nlopt, c’est pourquoi il n’est pas la valeur par défaut ici). Sur x86_64, vous pouvez en outre installer la pile complète de retargeting :
uv pip install "isaacteleop[retargeters]~=1.3.131"Configurer CloudXR et connecter un casque
Isaac Teleop diffuse le casque vers votre machine via NVIDIA CloudXR, qui fournit le runtime OpenXR auquel la session se connecte. Par défaut, LeTeleop démarre automatiquement le runtime CloudXR pour vous lorsque vous appelez teleop_device.connect() — vous n'avez plus besoin d'exécuter python -m isaacteleop.cloudxr et source cloudxr.env dans un shell séparé. Il vous suffit d'avoir un casque pris en charge connecté et les ports du pare-feu CloudXR ouverts. Suivez le démarrage rapide d'Isaac Teleop pour les détails sur l'appairage du casque et le pare-feu.
Première exécution (EULA). Le tout premier lancement doit accepter l'EULA NVIDIA CloudXR. Le lancement automatique le demande sur stdin, de sorte qu'une machine sans interface graphique restera bloquée en attente d'une entrée. Initialisez l'EULA une fois, de manière interactive, avec :
python -m isaacteleop.cloudxr --accept-eula # one-time: accept the CloudXR EULAEnsuite, connect() lance le runtime de manière non interactive. Le lancement bloque pendant environ 30 s pendant que le runtime démarre.
Configuration. Deux champs de IsaacTeleopConfig (partagés par tous les périphériques) contrôlent cela :
auto_launch_cloudxr(par défautTrue) — indique siconnect()démarre le runtime. DéfinissezFalsesi CloudXR est déjà en cours d’exécution en externe.cloudxr_env_file(par défautNone) — un fichier.envde profil d’appareil CloudXR facultatif qui sélectionne le transport du casque (par exemple, un profil Apple Vision Pro). Il s’agit d’une entrée du lanceur ; ce n’est pas le fichier de sortie~/.cloudxr/run/cloudxr.envque l’ancien flux manuel vous indiquait desource.Noneconserve le profil auto-WebRTC par défaut — bien que l’exemple SO-101 le remplace par ledefault.envfourni à côté deteleoperate.py, sauf si vous passez--teleop.cloudxr_env_file.
Refuser. Pour ignorer le lancement automatique (CloudXR est déjà en cours d’exécution), définissez auto_launch_cloudxr=False ou exportez :
export LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1La variable d’environnement prend le pas sur le champ de configuration : si LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1 est définie, le lancement automatique est ignoré même lorsque auto_launch_cloudxr=True. Cette variable est indépendante de ISAACLAB_CXR_SKIP_AUTOLAUNCH d’Isaac Lab — définir l’une n’affecte pas l’autre.
Un téléopérateur par processus. Le runtime CloudXR configure l’environnement à l’échelle du processus (singleton), il faut donc exécuter un seul téléopérateur Isaac Teleop par processus.
Arrêt en cours. Appelez toujours teleop_device.disconnect() à la sortie — y compris en cas de Ctrl-C. Enveloppez votre boucle de téléopération dans un try/finally et appelez disconnect() dans le finally. Cela démonte la session OpenXR avant le runtime CloudXR, ce qui est l’ordre requis ; le hook atexit du lanceur ne récupère que le runtime et n’exécute pas le __exit__ de la session, de sorte qu’sans un disconnect() explicite, une exécution interrompue s’arrête dans le mauvais ordre.
teleop_device.connect()
try:
while True:
action = teleop_device.get_action()
# ... drive the robot ...
finally:
teleop_device.disconnect()Consultez Exigences système pour connaître les versions prises en charge des systèmes d’exploitation, des GPU et de CloudXR, ainsi que les casques compatibles.
Fonctionnement
The XR controller is one Isaac Teleop input device. XRController is a deliberately thin
reader: it exposes the raw controller grip pose — already statically rebased into the robot
base frame — plus the squeeze and trigger analog values. It has no retargeters and no clutch logic of its own. The clutch (engage latch + delta rebasing onto the EE) and the gripper
mapping live downstream in the example loop, which then feeds LeRobot’s existing closed‑loop
Cartesian IK pipeline — the same one the phone teleoperator uses. The device‑specific pieces are XRController, the loop’s Clutch, and MapXRControllerActionToRobotAction; everything downstream
(EEBoundsAndSafety, InverseKinematicsEEToJoints) is shared, and a future device (e.g. Manus
gloves) would swap in its own teleop_<device>.py + processor while reusing the rest.
XRController._build_pipeline connecte la source ControllersSource d’Isaac Teleop — repositionnée statiquement dans le repère de base du robot par le ControllerTransform natif (base_T_anchor) — et expose le flux de contrôleur transformé tel quel. get_action() lit directement la pose de la poignée, la pression et la gâchette ; la session est toujours avancée en RUNNING (il n’y a pas de réorienteur à embrayage pour la conditionner).
La classe Clutch (dans examples/isaac_teleop_to_so101/isaac_teleop/clutch.py, pilotée par la
boucle de common.py) reproduit le SO101ClutchRetargeter d’Isaac Teleop, mais est intégrée dans la boucle afin que
l’appareil reste un simple lecteur :
- Il verrouille son origine d’engagement sur le bord d’engagement de la pince (la frame où la pince franchit pour la première fois
clutch_threshold) et réinitialise à la fois la position et l’orientation autour de celle-ci, de sorte que l’engagement ne téléporte pas le bras.Clutch.rebaserenvoie la cible absolue dans le repère de base sous forme d’un couple(pos, quat), que la boucle concatène dans leee_pose7D transmis au processeur. - Le déclencheur analogique devient une valeur de
closednessde la pince dans[0, 1](0 = ouvert, 1 = fermé), proportionnelle à la course du déclencheur, queMapXRControllerActionToRobotActionconvertit en cible de mâchoire.
Consultez l'interface de retargeting et l'aperçu de l'architecture d'Isaac Teleop pour comprendre comment les nœuds sources et les retargeters sont composés.
VR controller (OpenXR)
│
▼
XRController.get_action() ── raw base-frame grip_pos / grip_quat + squeeze + trigger
│ (TeleopSession always stepped RUNNING; clutch lives downstream)
▼
Clutch.rebase(grip_pos, grip_quat) ── engage-relative delta applied to the EE home (pos + orient)
│ ee_pose (7) / closedness → absolute ee_pose; closedness = trigger
▼
MapXRControllerActionToRobotAction ── absolute ee.x/y/z; ee.w* = orientation rotvec target;
│ ee.x/y/z / ee.w* / ee.gripper_pos ee.gripper_pos = (1 - closedness) * 100
▼
EEBoundsAndSafety ── workspace clip + per-frame step clamp (clamp+warn)
│
▼
InverseKinematicsEEToJoints ── closed-loop Placo IK; position + soft-orientation
│ (orientation_weight=0.01) (passes ee.gripper_pos → gripper.pos)
▼
SO-101 follower joint targetsLa pince : détenue par la boucle d'exemple
Contrairement au pipeline téléphone (qui répartit le clutch entre MapPhoneActionToRobotAction et EEReferenceAndDelta), le clutch XR réside entièrement dans la classe Clutch de la boucle d’exemple. Il émet une pose EE absolue, il n’y a donc ni étape EEReferenceAndDelta ni accumulation de delta dans le processeur — MapXRControllerActionToRobotAction est une pure correspondance par image, sans état.
L’embrayage verrouille son origine d’engagement sur le bord d’engagement de la pression (le moment où la pression franchit clutch_threshold) et pilote l’EE à partir du mouvement relatif à cette origine, de sorte que le bras ne téléporte pas lors de l’engagement. À chaque engagement — qu’il s’agisse du démarrage ou d’un ré‑embrayage en cours de tâche —, la position de référence est verrouillée à partir de la cinématique directe sur les articulations mesurées du bras, de sorte que la position de référence correspond à l’endroit où le bras se trouve physiquement, même s’il a bougé pendant qu’il était désengagé, et que l’engagement est sans saut. L’orientation de référence conserve la dernière rotation commandée : le bras à 5 degrés de liberté ne suit l’orientation que de manière souple, de sorte que verrouiller l’orientation mesurée du poignet injecterait son écart de suivi dans la commande à chaque ré‑embrayage.
Contrôles
- Serrer / saisir — le clutch (frein à main). Maintenez-le au-delà de
clutch_thresholdpour activer la téléopération ; relâchez-le pour mettre en pause. Chaque activation recapture l'origine, ce qui vous permet de repositionner votre main pendant la pause et de réactiver sans que le bras ne saute (style index/clutch). - Déclencheur — le gripper, contrôlé en analogique. La mâchoire suit le déclencheur de manière proportionnelle — un déclencheur à moitié enfoncé laisse la mâchoie à moitié fermée — via une valeur de fermeture dans
[0, 1](0 = ouvert, 1 = fermé) qui correspond à une cible absolue pour l'articulation du gripper. - Orientation du contrôleur — le poignet. Le clutch rebase l'orientation du contrôleur
(engage‑relative, base‑frame) en une cible d'orientation IK douce que le poignet suit en parallèle
de la position. Sur le 5‑DOF SO‑101, le poignet ne suit la main que partiellement par conception — voir
orientation_weightci‑dessous.
Commencer
Étape 1 : Créer le téléopérateur
# Run from the repo root so the `examples` package is importable.
from examples.isaac_teleop_to_so101.isaac_teleop import XRController, XRControllerConfig
teleop_config = XRControllerConfig(
hand_side="right", # "left" or "right" controller
clutch_threshold=0.5, # squeeze value above which the clutch engages
)
teleop_device = XRController(teleop_config)XRController.get_action() renvoie la pose du contrôleur dans le repère de base brut, et non une cible re‑ancrée par le clutch : grip_pos (3,) [x, y, z] [m] et grip_quat (4,) [qx, qy, qz, qw] dans le repère de base du robot, ainsi que les valeurs analogiques scalaires squeeze et trigger comprises dans [0, 1]. La boucle d’exemple utilise Clutch pour convertir ces données en la pose absolue ee_pose, et le squeeze est seuillé par la boucle par rapport à clutch_threshold pour l’engager.
Étape 2 : Connecter
Appeler teleop_device.connect() lance d’abord automatiquement le runtime CloudXR (sauf si vous avez désactivé cette option —
voir Configurer CloudXR et connecter un casque ; cela bloque pendant
environ 30 s et, lors de la première exécution, demande l’acceptation du EULA sur l’entrée standard), puis démarre la session TeleopSession d’Isaac Teleop (ouvre la session OpenXR et détecte les manettes). Les manettes XR sont auto‑calibrantes, il n’y a donc aucune étape de calibration manuelle — le mécanisme de verrouillage (clutch) recentre la position à chaque fois que vous l’engagez. Associez connect() à un bloc try/finally qui appelle disconnect() afin que la session soit correctement fermée avant le runtime lors de la sortie ou d’un Ctrl‑C.
Étape 3 : Exécuter l’exemple
L'exemple suppose que vous avez configuré votre robot (SO‑101 follower) et défini le port série correct.
Le URDF du robot et ses maillages sont récupérés automatiquement lors de la première exécution : l’appareil XR télécharge
l’URDF du SO-101 depuis le bucket Hugging Face lerobot/robot-urdfs vers le cache LeRobot (HF_LEROBOT_HOME/robot-urdfs/so101/) et le réutilise ensuite, il n’y a donc pas
d’étape de téléchargement distincte :
python -m examples.isaac_teleop_to_so101.teleoperate --robot.type=so101_follower --robot.port=/dev/ttyACM0 \
--robot.id=so101_follower_arm --teleop.type=xr_controllerL’interface en ligne de commande est de type lerobot-teleoperate (draccus) : --robot.* configure le suiveur SO-101 et --teleop.type sélectionne le périphérique d’entrée Isaac (xr_controller | so101_leader), avec --teleop.* pour ses réglages. --teleop.type=xr_controller exécute le chemin du contrôleur XR décrit
ci-dessus. Le contrat de sécurité au démarrage : par défaut, il amène tous les articulations vers une pose de réinitialisation par défaut sur --reset_duration secondes (--reset_to_origin=false laisse le bras en position), puis initialise la position de référence de l’embrayage à partir de la pose mesurée du bras afin que la première mise en prise soit sans à-coup ; le suiveur n’est commandé que lorsque l’embrayage est engagé.
Personnaliser la pose de réinitialisation. La pose de réinitialisation est fournie par défaut (une pose intermédiaire confortable) et fonctionne immédiatement — vous n'avez pas besoin d'enregistrer quoi que ce soit. Pour l'adapter à votre configuration, amenez le bras à la pose souhaitée et exécutez python -m examples.isaac_teleop_to_so101.override_reset_pose --id <robot.id> ; cela écrit les articulations actuelles dans un fichier par bras dans le cache LeRobot
(HF_LEROBOT_HOME/reset_poses/<robot.name>/<robot.id>.json, indexé comme la calibration), qui prend alors le pas sur la valeur par défaut intégrée lors de l'exécution suivante. Comme ce fichier se trouve dans le cache local de l'utilisateur (et non dans le dépôt), votre surcharge reste sur votre machine, et les commandes teleoperate et record la respectent
lorsqu'elles sont lancées avec le même --robot.id.
L’autre périphérique, --teleop.type=so101_leader, reproduit le suiveur 1:1 à partir d’un bras leader SO-101 rétro-conductible dont les articulations sont diffusées par le plugin natif so101_leader d’Isaac Teleop (sans embrayage, sans IK — le leader et le suiveur partagent la cinématique du SO-101).
Le binaire so101_leader_plugin est un plugin C++ qui ne fait pas partie du paquet pip isaacteleop — vous le compilez à partir de l’arborescence source d’Isaac Teleop. Suivez Compiler Isaac Teleop à partir du code source (en résumé, depuis votre clone d’Isaac Teleop : cmake -B build && cmake --build build --parallel && cmake --install build) ; la compilation installe les plugins sous <IsaacTeleop>/install/plugins/, de sorte que le binaire se trouve à install/plugins/so101_leader/so101_leader_plugin — le chemin de --launch_plugin ci-dessous. Consultez le README.md du plugin (à côté du binaire) pour les détails sur le port série et l’étalonnage.
Pointez --teleop.port vers le port série du leader physique et --launch_plugin vers le binaire de ce plugin afin que le script le démarre une fois CloudXR opérationnel :
python -m examples.isaac_teleop_to_so101.teleoperate --robot.type=so101_follower --robot.port=/dev/ttyACM0 \
--robot.id=so101_follower_arm --teleop.type=so101_leader \
--teleop.port=/dev/ttyACM1 --teleop.id=so101_leader_arm \
--launch_plugin=/code/Teleop/install/plugins/so101_leader/so101_leader_plugin(Note so101_leader désigne ici le leader Isaac, résolu par rapport au registre des appareils Isaac Teleop, distinct du port série so101_leader de lerobot-teleoperate.) Lorsqu’un --teleop.port est défini, l’étalonnage tick→radian du plugin est déduit de --teleop.id et transmis au plugin en tant que troisième argument positionnel — le fichier JSON au format LeRobot situé à HF_LEROBOT_CALIBRATION/teleoperators/so_leader/<id>.json, le même fichier utilisé par le leader SO-101 en série (lerobot-calibrate --teleop.type=so101_leader --teleop.id=<id>). S’il est manquant, le script émet un avertissement et le plugin utilise les valeurs par défaut intégrées. Exécutez python -m examples.isaac_teleop_to_so101.teleoperate --help pour afficher tous les drapeaux. Son contrat de sécurité au démarrage : par défaut, le suiveur est amené vers la première lecture du leader sur --align_duration secondes (--align=false pour ignorer) afin que le bras ne se bloque pas brutalement au démarrage du miroir, et tant que le flux du leader est obsolète, le suiveur est maintenu dans sa pose mesurée.
La récupération du URDF utilise huggingface_hub (déjà une dépendance de LeRobot) sur le bucket public lerobot/robot-urdfs, il n’est donc pas nécessaire de se connecter. Le contenu est mis en cache sous HF_LEROBOT_HOME/robot-urdfs/so101/ ; supprimez ce dossier pour forcer une nouvelle téléchargement.
Ensuite, sur votre casque : pressez et maintenez la poignée pour activer, déplacez la manette pour piloter le bras, pivotez/inclinez-la pour orienter le poignet, et appuyez sur la gâchette pour fermer le pince (de manière proportionnelle — relâchez pour l’ouvrir).
Pour enregistrer un jeu de données (et pas seulement téléopérer), utilisez record.py dans le même dossier. Il se base sur --teleop.type (xr_controller | so101_leader) exactement comme teleoperate.py, de sorte que n'importe quel appareil peut piloter le suiveur, et il enregistre les articulations commandées dans un jeu de données LeRobot (drapeaux --dataset.* de type lerobot-record). Consultez la docstring de son module pour la CLI complète et les raccourcis clavier d'enregistrement.
Étapes et options importantes du pipeline
La pince produit déjà une pose absolue du repère de base, le côté processeur est donc un chemin pose absolue mince — il n'y a ni remappage de repère, ni accumulation d'incrément, ni étape EEReferenceAndDelta.
MapXRControllerActionToRobotActionest une mapping sans état, par image, de la sortie de l’appareil vers le contrat d’entrée IK. Il écrit la position absolue dans le repère de base, encode l’orientation absolue comme cible rotvec et inverse la fermeture pour obtenir la cible du moteur de la pince :action["ee.x"], action["ee.y"], action["ee.z"] = ee_pose[:3] # absolute, base frame [m] action["ee.wx"], action["ee.wy"], action["ee.wz"] = orient_rotvec # orientation target (rotvec) action["ee.gripper_pos"] = (1 - closedness) * 100 # motor units; SO-101 calibrates 100 = openLa polarité de la pince (
100 = open, 0 = closed) est une convention de calibration matérielle dans la source — inversez-la là-bas si la mâchoire s'ouvre alors qu'elle devrait se fermer.EEBoundsAndSafetyborne l’EE dans un espace de travail et limite le taux des sauts par image. L’embrayage sans téléportation maintient les images petites, de sorte quemax_ee_step_mdétecte principalement les anomalies transitoires de suivi du contrôleur. Le plancher z est0.0(le plan de la table) afin qu’une cible erratique ne puisse pas faire descendre l’EE sous la table ; x/y restent dans la boîte lâche[-1, 1]m. Définissezraise_on_jump=Falsepour qu’une image dépassant la limite soit bornée et signalée plutôt que de lever une exception — un plantage en cours de boucle laisserait le bras sans contrôle :EEBoundsAndSafety( end_effector_bounds={"min": [-1.0, -1.0, 0.0], "max": [1.0, 1.0, 1.0]}, max_ee_step_m=0.10, raise_on_jump=False, )InverseKinematicsEEToJoints(initial_guess_current_joints=False, orientation_weight=0.01)résout l’IK Placo en boucle fermée. Le SO‑101 est un bras à 5‑DOF, donc l’IK est dominée par la position ; le petitorientation_weightlui permet de suivre doucement la cible d’orientation portée paree.w*afin que le poignet suive la main, tandis que le roulis sous‑déterminé reste partiel par conception. Il n’y a pas deGripperVelocityToJoint: la position absolueee.gripper_posest transmise directement àgripper.pos.initial_guess_current_joints=Falseinitialise chaque résolution à partir de la solution IK précédente plutôt que de réinitialiser à partir des articulations mesurées, ce qui maintient la trajectoire des articulations continue d’une image à l’autre. Ajustezorientation_weightsur le matériel — trop élevé, il entre en conflit avec le suivi de position ; trop bas, il ignore la consigne d’orientation.
L’exemple applique également une sécurité au niveau de la boucle : après la rampe de réinitialisation au démarrage (activée par défaut —
passez --reset_to_origin=false pour laisser le bras en place), il commande le robot uniquement lorsque l’embrayage est engagé, et renvoie les articulations mesurées lorsqu’il est désengagé, de sorte que le désengagement de l’embrayage fige le bras sur place.
Consultez le guide Processors for Robots and Teleoperators pour en savoir plus sur l’adaptation du pipeline à d’autres robots.
Dépannage
ModuleNotFoundError: isaacteleop— le packageisaacteleopn'est pas installé dans l'environnement actif. Relancez la commande d'installation en haut de ce guide :uv pip install "isaacteleop[cloudxr,retargeters-lite]~=1.3.131".- Aucun contrôleur trouvé — assurez-vous que le runtime CloudXR est en cours d’exécution, que les ports du pare-feu sont autorisés et que le casque est connecté (voir Configurer CloudXR et connecter un casque et le Quick Start d’Isaac Teleop).
- Échec du lancement automatique de CloudXR —
connect()lève uneRuntimeErrorsi le runtime ne démarre pas dans le délai de démarrage. Consultez les journaux du lanceur sous~/.cloudxr/logs. Causes courantes : le EULA n'a jamais été accepté (exécutezpython -m isaacteleop.cloudxr --accept-eulaune fois, en mode interactif — le lancement automatique demande une confirmation sur stdin et reste bloqué en mode sans interface), ou le runtime est déjà en cours d'exécution en externe (définissezLEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1ouauto_launch_cloudxr=Falsepour ignorer le lancement automatique). - Le bras ne bouge pas — l’embrayage est un bouton de sécurité : vous devez maintenir la pression/la poignée au-delà de
clutch_threshold. Baissez le seuil si la pression de votre contrôleur est détectée faiblement. - Le mouvement semble désaligné — vérifiez l’orientation du casque/de l’espace de jeu. Le flux des manettes est recalé dans le repère de base du robot par la transformation
base_T_anchordeXRControllerConfig(par défaut : convention OpenXR standard → repère robot) ; ajustez-la si votre repère d’ancrage diffère.
En savoir plus
Documentation NVIDIA Isaac Teleop (accueil de la documentation, GitHub) :
- Démarrage rapide — installer, exécuter le serveur CloudXR, connecter un casque, exécuter un exemple de téléopération.
- TeleopSession —
l’API de session encapsulée par
XRController. - Interface de retargeting et aperçu de l'architecture — comment les nœuds sources et les retargeters se composent en pipeline.
- Compiler à partir du code source —
construisez
isaacteleop(et ses plugins C++, y compris le pluginso101_leaderutilisé ci-dessus) à partir d'un clone local. - Exigences système et la documentation du SDK CloudXR — plateformes prises en charge, GPU, versions du runtime CloudXR/OpenXR et casques.