LeRobot documentation

Isaac Teleop

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Isaac Teleop

Controla tu robot con NVIDIA Isaac Teleop, un framework de teleoperación multimodal. Isaac Teleop controla una única TeleopSession desde una variedad de dispositivos de entrada: controladores XR (VR), seguimiento de manos, seguimiento corporal completo, guantes Manus, pedales de pie y más.

En LeRobot, Isaac Teleop se incluye como un ejemplo autónomo en examples/isaac_teleop_to_so101/. Cada dispositivo de entrada de Isaac Teleop es su propio subclase de Teleoperator en el paquete isaac_teleop del ejemplo, compartiendo un mismo ciclo de vida de sesión (ver IsaacTeleopTeleoperator). Los dispositivos disponibles hoy son el controlador XR (XRController) y un brazo líder SO-101 con retroalimentación de fuerza (SO101LeaderArm); los guantes Manus y el seguimiento de manos/cuerpo completo son los próximos dispositivos naturales. Esta guía se centra en el controlador XR; el brazo líder SO-101 se resume en Ejecutar el ejemplo.

En esta guía aprenderás:

  • Cómo un dispositivo de Teleop de Isaac controla el objetivo del efector final (EE) de un robot
  • Cómo el clutch (apretar/agarrar en el controlador XR) activa la teleoperación sin dar tirones al brazo
  • Cómo ejecutar el ejemplo de teleoperación SO‑101 y ajustar el movimiento / la pinza / la IK

Instalación

El ejemplo se encuentra en el repositorio de LeRobot (no forma parte del paquete pip lerobot), por lo que debes clonar el repositorio e instalarlo desde el código fuente. La referencia de instalación y uso canónica y siempre actualizada es el README.md del ejemplo; en resumen:

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 se publica en PyPI público (solo Linux). El extra cloudxr incluye las vinculaciones del runtime de CloudXR; retargeters-lite es la ruta del retargeter basado en scipy que se resuelve tanto en x86_64 como en ARM (en aarch64, por ejemplo, un DGX Spark, el extra completo retargeters no se resuelve debido a sus fijaciones de dex-retargeting/nlopt, por lo que no es el predeterminado aquí). En x86_64, además, puedes instalar la pila completa de retargeter:

uv pip install "isaacteleop[retargeters]~=1.3.131"

Configurar CloudXR y conectar un visor

Isaac Teleop transmite el visor a tu máquina a través de NVIDIA CloudXR, que proporciona el runtime de OpenXR al que se conecta la sesión. De forma predeterminada, LeTeleop inicia automáticamente el runtime de CloudXR cuando llamas a teleop_device.connect(); ya no es necesario ejecutar python -m isaacteleop.cloudxr y source cloudxr.env en una shell separada. Solo necesitas un visor compatible conectado y los puertos de firewall de CloudXR abiertos. Sigue el Quick Start de Isaac Teleop para los detalles de emparejamiento del visor y de firewall.

Primera ejecución (EULA). La primera vez que se inicie, debe aceptar la EULA de NVIDIA CloudXR. El inicio automático solicita la aceptación en stdin, por lo que en un equipo sin interfaz gráfica se quedará bloqueado esperando la entrada. Inicialice la EULA una sola vez, de forma interactiva, con:

python -m isaacteleop.cloudxr --accept-eula   # one-time: accept the CloudXR EULA

Después de eso, connect() inicia el runtime de forma no interactiva. La ejecución se bloquea durante ~30s mientras el runtime se inicia.

Configuración. Dos campos de IsaacTeleopConfig (compartidos por cada dispositivo) controlan esto:

  • auto_launch_cloudxr (por defecto True) — si connect() inicia el runtime. Establezca False cuando CloudXR ya esté en ejecución externamente.
  • cloudxr_env_file (valor predeterminado None) — un perfil de dispositivo CloudXR opcional .env que selecciona el transporte del visor (por ejemplo, un perfil de Apple Vision Pro). Esta es la entrada del lanzador; no es el archivo de salida ~/.cloudxr/run/cloudxr.env que el flujo manual antiguo indicaba sourcear. None mantiene el perfil auto-WebRTC predeterminado, aunque el ejemplo SO-101 lo sobrescribe con el default.env que se incluye junto a teleoperate.py, a menos que pases --teleop.cloudxr_env_file.

Excluirse. Para omitir el inicio automático (si CloudXR ya está en ejecución), establezca auto_launch_cloudxr=False o exporte:

export LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1

La variable de entorno tiene prioridad sobre el campo de configuración: si se establece LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1, se omite el inicio automático incluso cuando auto_launch_cloudxr=True. Esta variable es independiente de ISAACLAB_CXR_SKIP_AUTOLAUNCH de Isaac Lab: establecer una no afecta a la otra.

Un teleoperador por proceso. El runtime de CloudXR configura el entorno a nivel de proceso (un singleton), por lo que debe ejecutar un único teleoperador de Isaac Teleop por proceso.

Apagando. Llame siempre a teleop_device.disconnect() al salir, incluso en caso de Ctrl-C. Envuelva su bucle de teleoperación en try/finally y llame a disconnect() en el finally. Esto desmonta la sesión de OpenXR antes que el runtime de CloudXR, que es el orden requerido; el hook de atexit del launcher solo recoge el runtime y no ejecuta el __exit__ de la sesión, por lo que, sin una llamada explícita a disconnect(), una ejecución interrumpida se apaga en el orden incorrecto.

teleop_device.connect()
try:
    while True:
        action = teleop_device.get_action()
        # ... drive the robot ...
finally:
    teleop_device.disconnect()

Consulte Requisitos del sistema para conocer las versiones compatibles de SO / GPU / CloudXR y los visores.

Cómo funciona

El controlador XR es un dispositivo de entrada de Isaac Teleop. XRController es un lector deliberadamente delgado: expone la pose de agarre cruda del controlador, que ya se ha rebaseado estáticamente al marco base del robot, junto con los valores analógicos de squeeze y trigger. No tiene retargeters ni lógica de clutch propia. El clutch (enganche + rebaseado de delta sobre el EE) y el mapeo del agarre residen aguas abajo en el bucle de ejemplo, que luego alimenta el pipeline de IK cartesiano en lazo cerrado existente de LeRobot, el mismo que utiliza el teleoperador con el teléfono. Las partes específicas del dispositivo son XRController, el Clutch del bucle y MapXRControllerActionToRobotAction; todo lo que está aguas abajo (EEBoundsAndSafety, InverseKinematicsEEToJoints) es compartido, y un dispositivo futuro (p. ej. guantes Manus) sustituiría su propio teleop_<device>.py + procesador reutilizando el resto.

XRController._build_pipeline conecta el ControllersSource de Isaac Teleop, que se rebasea estáticamente al marco base del robot mediante el ControllerTransform nativo (base_T_anchor), y expone el flujo de controlador transformado tal cual. get_action() lee la pose del agarre, el apretón y el gatillo directamente de él; la sesión siempre se avanza en RUNNING (no hay ningún retargeter de embrague que lo controle).

La clase Clutch (en examples/isaac_teleop_to_so101/isaac_teleop/clutch.py, controlada por el bucle en common.py) replica el SO101ClutchRetargeter de Isaac Teleop, pero se ejecuta dentro del bucle para que el dispositivo pueda mantenerse como un lector ligero:

  • Fija su origen de activación en el borde de activación del apretón (el fotograma en el que el apretón cruza por primera vez clutch_threshold) y rebasea tanto la posición como la orientación en torno a él, de modo que la activación no teletransporte el brazo. Clutch.rebase devuelve el objetivo absoluto del marco base como un par (pos, quat), que el bucle concatena en el ee_pose de 7D que se alimenta al procesador.
  • El disparador analógico se convierte en un closedness de la pinza en [0, 1] (0 = abierto, 1 = cerrado), proporcional a la presión del disparador, que MapXRControllerActionToRobotAction mapea a un objetivo de mandíbula.

Consulte la interfaz de retargeting y el resumen de la arquitectura de Isaac Teleop para ver cómo se componen los nodos de origen y los retargeters.

  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 targets

El embrague: propiedad del bucle de ejemplo

A diferencia de la tubería de teléfono (que divide el clutch entre MapPhoneActionToRobotAction y EEReferenceAndDelta), el clutch de XR reside por completo en la clase Clutch del bucle de ejemplo. Emite una pose EE absoluta, por lo que no hay una etapa EEReferenceAndDelta ni acumulación de deltas en el procesador: MapXRControllerActionToRobotAction es un mapeo puro y sin estado por cuadro.

El embrague fija su origen de acoplamiento en el borde de acoplamiento de la presión (el momento en que la presión cruza clutch_threshold) y controla el EE desde el movimiento relativo a ese origen, de modo que el brazo no se teletransporta al acoplarse. En cada acoplamiento —ya sea al inicio o un re-acoplamiento a mitad de la tarea—, la posición de origen se fija a partir de la cinemática directa sobre las articulaciones medidas del brazo, por lo que el origen coincide con la posición física real del brazo incluso si se movió mientras estaba desacoplado, y el acoplamiento es libre de saltos. La orientación de origen conserva la última rotación comandada: el brazo de 5 GDL solo sigue la orientación de forma suave, por lo que fijar la orientación medida de la muñeca inyectaría su desfase de seguimiento en el comando en cada re-acoplamiento.

Controles

  • Apriete / agarre — el clutch (deadman). Manténgalo presionado más allá de clutch_threshold para activar la teleoperación; suéltelo para pausar. Cada activación vuelve a capturar el origen, por lo que puede reposicionar su mano mientras está en pausa y volver a activar sin que el brazo salte (estilo índice/clutch).
  • Gatillo — el agarre, controlado de forma analógica. La mandíbula sigue el gatillo de manera proporcional: un gatillo a medio pulsar deja la mandíbula a medio cerrar — mediante un valor de cierre en [0, 1] (0 = abierto, 1 = cerrado) que se mapea a un objetivo absoluto de la articulación del agarre.
  • Orientación del controlador — la muñeca. El clutch rebasea la orientación del controlador (engage‑relative, base‑frame) en un objetivo de orientación suave de IK que la muñeca sigue junto con la posición. En el 5‑DOF SO‑101, la muñeca sigue la mano solo parcialmente por diseño — consulte orientation_weight a continuación.

Comenzar

Paso 1: Crear el teleoperador

# 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() devuelve la pose del controlador en el marco base sin procesar, no un objetivo rebaseado con clutch: grip_pos (3,) [x, y, z] [m] y grip_quat (4,) [qx, qy, qz, qw] en el marco base del robot, además de los valores analógicos escalares squeeze y trigger en [0, 1]. El Clutch del bucle de ejemplo convierte estos valores en la ee_pose absoluta, y el bucle aplica un umbral al squeeze en comparación con clutch_threshold para activarlo.

Paso 2: Conectar

Al llamar a teleop_device.connect() primero se inicia automáticamente el runtime de CloudXR (a menos que hayas optado por no hacerlo; consulta Configurar CloudXR y conectar un visor; esto bloquea durante ~30 s y, en la primera ejecución, solicita la EULA en stdin), y luego se inicia la TeleopSession de Isaac Teleop (abre la sesión de OpenXR y detecta los controladores). Los controladores de XR se autocalibran, por lo que no hay un paso de calibración manual: el clutch se encarga de recentrar cada vez que lo activas. Combina connect() con un try/finally que llame a disconnect() para que la sesión se cierre antes de que el runtime termine al salir o con Ctrl-C.

Paso 3: Ejecutar el ejemplo

El ejemplo asume que configuraste tu robot (SO‑101 follower) y estableciste el puerto serie correcto.

El URDF del robot y sus mallas se obtienen automáticamente en la primera ejecución: el dispositivo XR descarga el URDF del SO-101 desde el bucket de Hugging Face lerobot/robot-urdfs en la caché de LeRobot (HF_LEROBOT_HOME/robot-urdfs/so101/) y lo reutiliza después, por lo que no hay ningún paso de descarga separado:

python -m examples.isaac_teleop_to_so101.teleoperate --robot.type=so101_follower --robot.port=/dev/ttyACM0 \
    --robot.id=so101_follower_arm --teleop.type=xr_controller

La CLI es de estilo lerobot-teleoperate (draccus): --robot.* configura el seguidor SO-101 y --teleop.type selecciona el dispositivo de entrada de Isaac (xr_controller | so101_leader), con --teleop.* como sus ajustes de dispositivo. --teleop.type=xr_controller ejecuta la ruta del controlador XR descrita anteriormente. El contrato de seguridad de inicio: de forma predeterminada, mueve todos los articulaciones a una pose de reinicio predeterminada durante --reset_duration segundos (--reset_to_origin=false mantiene el brazo en su posición actual), y luego establece la posición inicial del embrague a partir de la pose medida del brazo para que el primer acoplamiento sea sin saltos; el seguidor solo se comanda mientras el embrague está acoplado.

Personalización de la pose de reinicio. La pose de reinicio se incluye como valor predeterminado integrado (una pose cómoda de rango medio) y funciona sin configuración adicional: no es necesario grabar nada. Para adaptarla a tu configuración, mueve el brazo a la pose deseada y ejecuta python -m examples.isaac_teleop_to_so101.override_reset_pose --id <robot.id>; esto escribe las articulaciones actuales en un archivo por brazo en la caché de LeRobot (HF_LEROBOT_HOME/reset_poses/<robot.name>/<robot.id>.json, con la misma clave que la calibración), que luego tiene prioridad sobre el valor predeterminado integrado en la siguiente ejecución. Dado que se almacena en la caché local del usuario (no en el repositorio), tu personalización permanece en tu máquina, y tanto teleoperate como record la respetan cuando se lanzan con el mismo --robot.id.

El otro dispositivo, --teleop.type=so101_leader, replica al seguidor 1:1 desde un brazo líder SO-101 retroaccionable cuyos articulaciones se transmiten mediante el plugin nativo so101_leader de Isaac Teleop (sin embrague, sin IK: el líder y el seguidor comparten la cinemática del SO-101).

El binario so101_leader_plugin es un plugin de C++ que no forma parte del paquete pip isaacteleop: se compila desde el árbol de fuentes de Isaac Teleop. Siga las instrucciones de Compilar Isaac Teleop desde el código fuente (en resumen, desde su clon de Isaac Teleop: cmake -B build && cmake --build build --parallel && cmake --install build); la compilación instala los plugins en <IsaacTeleop>/install/plugins/, por lo que el binario se encuentra en install/plugins/so101_leader/so101_leader_plugin, que es la ruta de --launch_plugin indicada a continuación. Consulte el propio README.md del plugin (junto al binario) para conocer los detalles de su serie/calibración.

Apunta --teleop.port al puerto serie del líder físico y --launch_plugin al binario de ese plugin para que el script lo inicie después de que CloudXR esté en marcha:

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

(Nota: so101_leader aquí es el líder de Isaac, resuelto contra el registro de dispositivos de teleoperación de Isaac, distinto del serial so101_leader de lerobot-teleoperate.) Cuando se establece un --teleop.port, la calibración de tick a radianes del plugin se infiere a partir de --teleop.id y se pasa al plugin como su tercer argumento posicional: el JSON en formato LeRobot ubicado en HF_LEROBOT_CALIBRATION/teleoperators/so_leader/<id>.json, el mismo archivo que utiliza el líder SO-101 por serial (lerobot-calibrate --teleop.type=so101_leader --teleop.id=<id>). Si falta, el script emite una advertencia y el plugin utiliza valores predeterminados integrados. Ejecute python -m examples.isaac_teleop_to_so101.teleoperate --help para ver todas las banderas. Su contrato de seguridad de inicio: de forma predeterminada, el seguidor se desplaza hacia la primera lectura del líder a lo largo de --align_duration segundos (--align=false para omitirlo) para que el brazo no se mueva bruscamente cuando comienza el espejo, y mientras el flujo del líder está desactualizado, el seguidor se mantiene en su pose medida.

La descarga del URDF utiliza huggingface_hub (ya una dependencia de LeRobot) contra el bucket público lerobot/robot-urdfs, por lo que no requiere inicio de sesión. Se almacena en caché en HF_LEROBOT_HOME/robot-urdfs/so101/; elimina esa carpeta para forzar una nueva descarga.

Luego, en su visor: presione y mantenga el agarre para activarlo, mueva el controlador para dirigir el brazo, gírelo/inclínelo para orientar la muñeca y presione el gatillo para cerrar el actuador (de forma proporcional: suéltelo para abrirlo).

Para grabar un conjunto de datos (no solo teleoperar), use record.py en la misma carpeta. Se despacha según --teleop.type (xr_controller | so101_leader) exactamente como teleoperate.py, por lo que cualquier dispositivo puede controlar al seguidor, y guarda las articulaciones comandadas en un conjunto de datos de LeRobot (banderas --dataset.* de estilo lerobot-record). Consulte la docstring de su módulo para la CLI completa y los atajos de teclado para la grabación.

Pasos y opciones importantes del pipeline

El clutch ya produce una pose absoluta del marco base, por lo que el lado del procesador es un camino de pose absoluta delgado: no hay remapeo de marcos, no hay acumulación de deltas y no hay una etapa EEReferenceAndDelta.

  • MapXRControllerActionToRobotAction es un mapeo sin estado por cuadro desde la salida del dispositivo al contrato de entrada de IK. Escribe la posición absoluta en el marco base, codifica la orientación absoluta como un objetivo rotvec e invierte el cierre para obtener un objetivo de pinza de motor:

    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 = open

    La polaridad de la pinza (100 = open, 0 = closed) es una convención de calibración de hardware en la fuente; invértala allí si la mandíbula se abre cuando debería cerrarse.

  • EEBoundsAndSafety limita el EE a un espacio de trabajo y aplica un límite de velocidad a los saltos por cuadro. El embrague sin teletransporte mantiene los cuadros pequeños, por lo que max_ee_step_m detecta principalmente fallos transitorios de seguimiento del controlador. El límite inferior de z es 0.0 (el plano de la mesa) para que un objetivo errático no lleve el EE por debajo de la mesa; x/y se mantienen en el suelto recuadro de [-1, 1] m. Establece raise_on_jump=False para que un cuadro que supere el límite se recorte y se emita una advertencia en lugar de lanzar una excepción: un error en medio del bucle dejaría el brazo sin control:

    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) resuelve la IK de Placo en bucle cerrado. SO‑101 es un brazo de 5 GDL, por lo que la IK está dominada por la posición; el pequeño orientation_weight le permite seguir suavemente el objetivo de orientación contenido en ee.w* para que la muñeca siga a la mano, mientras que el giro indeterminado permanece parcial por diseño. No existe ningún GripperVelocityToJoint: la posición absoluta ee.gripper_pos se pasa directamente a gripper.pos. initial_guess_current_joints=False inicia cada resolución desde la solución IK anterior en lugar de reiniciar desde las articulaciones medidas, de modo que la trayectoria de las articulaciones se mantiene continua entre fotogramas. Ajusta orientation_weight en el hardware: un valor demasiado alto compite con el seguimiento de posición, y uno demasiado bajo ignora la orden de orientación.

El ejemplo también aplica un control de seguridad a nivel de bucle: después de la rampa de reinicio al inicio (activada por defecto; pase --reset_to_origin=false para mantener el brazo en su posición), envía comandos al robot solo mientras el embrague esté acoplado y reenvía las articulaciones medidas mientras está desacoplado, de modo que al soltar el embrague el brazo se queda congelado en su posición.

Consulte la guía Procesadores para robots y teleoperadores para obtener más información sobre cómo adaptar la pipeline a otros robots.

Solución de problemas

  • ModuleNotFoundError: isaacteleop — el paquete isaacteleop no está instalado en el entorno activo. Vuelve a ejecutar el comando de instalación al inicio de esta guía: uv pip install "isaacteleop[cloudxr,retargeters-lite]~=1.3.131".
  • No se encontraron controladores — asegúrese de que el runtime de CloudXR esté en ejecución, de que los puertos del firewall estén en la lista blanca y de que el visor esté conectado (consulte Configurar CloudXR y conectar un visor y el Quick Start de Isaac Teleop).
  • Fallo al iniciar CloudXR automáticamente — connect() lanza un RuntimeError si el runtime no se inicia dentro del tiempo de espera de arranque. Comprueba los registros del lanzador en ~/.cloudxr/logs. Causas comunes: el EULA nunca se aceptó (ejecuta python -m isaacteleop.cloudxr --accept-eula una vez, de forma interactiva: el inicio automático solicita confirmación en stdin y se queda bloqueado en modo headless), o el runtime ya está en ejecución externamente (establece LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1 o auto_launch_cloudxr=False para omitir el inicio automático).
  • El brazo no se mueve — el embrague es de tipo *deadman*: debes mantener la presión de apretar/agarrar por encima de clutch_threshold. Reduce el umbral si la presión de tu controlador se registra con poca intensidad.
  • El movimiento parece desalineado — confirma la orientación del visor/espacio de juego. El flujo del controlador se rebasea en el marco de referencia de la base del robot mediante la transformación base_T_anchor en XRControllerConfig (predeterminado: convención estándar OpenXR → ejes del robot); ajústala si tu marco de anclaje es diferente.

Más información

Documentación de NVIDIA Isaac Teleop (inicio de la documentación, GitHub):

Update on GitHub