LeRobot documentation
Isaac Teleop
Isaac Teleop
Controle seu robô com o NVIDIA Isaac Teleop, um
framework de teleoperação multimodal. O Isaac Teleop opera uma única TeleopSession a partir de uma
variedade de dispositivos de entrada — controladores XR (VR), rastreamento de mãos, rastreamento corporal completo, luvas Manus, pedais de
pé, entre outros.
No LeRobot, o Isaac Teleop é fornecido como um exemplo autossuficiente em examples/isaac_teleop_to_so101/.
Cada dispositivo de entrada do Isaac Teleop é um subclasse de Teleoperator no pacote isaac_teleop do exemplo, compartilhando um único ciclo de vida de sessão (veja IsaacTeleopTeleoperator). Os
dispositivos disponíveis hoje são o controlador XR (XRController) e um braço líder SO-101 com retroação de força (SO101LeaderArm); luvas Manus e rastreamento de mão/corpo inteiro são os
próximos dispositivos naturais. Este guia foca no controlador XR; o líder SO-101 é resumido
em Executar o exemplo.
Neste guia, você aprenderá:
- Como um dispositivo de Teleop do Isaac controla o alvo do efetuor final (EE) de um robô
- Como o clutch (apertar/segurar no controlador do XR) ativa a teleoperação sem dar solavancos no braço
- Como executar o exemplo de teleoperação do SO‑101 e ajustar movimento / garra / IK
Instalação
O exemplo está no repositório LeRobot (não faz parte do pacote pip lerobot), portanto clone o repositório e instale a partir do código-fonte. A referência de instalação e uso canônica e sempre atualizada é o README.md do exemplo; em resumo:
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 é publicado no PyPI público (somente Linux). O extra cloudxr traz os bindings do runtime CloudXR; retargeters-lite é o caminho do retargeter baseado em scipy que resolve tanto em x86_64 quanto em ARM (em aarch64 — por exemplo, um DGX Spark — o extra completo retargeters não resolve devido aos seus pins de dex-retargeting/nlopt, motivo pelo qual ele não é o padrão aqui). Em x86_64, você pode instalar adicionalmente a pilha completa de retargeters:
uv pip install "isaacteleop[retargeters]~=1.3.131"Configure o CloudXR e conecte um headset
O Isaac Teleop transmite o headset para sua máquina por meio do NVIDIA CloudXR, que fornece o runtime OpenXR ao qual a sessão se conecta. Por padrão, o LeTeleop inicia automaticamente o runtime do CloudXR para você quando você chama teleop_device.connect() — você não precisa mais executar python -m isaacteleop.cloudxr e source cloudxr.env em um shell separado. Tudo o que você precisa é de um headset compatível conectado e das portas de firewall do CloudXR abertas. Siga o Quick Start do Isaac Teleop para os detalhes de pareamento do headset e de firewall.
Primeira execução (EULA). A primeira inicialização deve aceitar a EULA do NVIDIA CloudXR. O auto-launch solicita isso no stdin, portanto, em uma máquina headless, ele ficará pendurado aguardando entrada. Inicialize a EULA uma vez, de forma interativa, com:
python -m isaacteleop.cloudxr --accept-eula # one-time: accept the CloudXR EULADepois disso, connect() inicia o runtime de forma não interativa. O início bloqueia por ~30s enquanto o runtime é iniciado.
Configuração. Dois campos em IsaacTeleopConfig (compartilhados por todos os dispositivos) controlam isso:
auto_launch_cloudxr(padrãoTrue) — seconnect()inicia o runtime. DefinaFalsequando o CloudXR já estiver em execução externamente.cloudxr_env_file(padrãoNone) — um perfil de dispositivo CloudXR opcional.envque seleciona o transporte do headset (por exemplo, um perfil do Apple Vision Pro). Esta é a entrada do launcher; não é o arquivo de saída~/.cloudxr/run/cloudxr.envque o fluxo manual antigo indicava parasource.Nonemantém o perfil auto-WebRTC padrão — embora o exemplo SO-101 o substitua pelodefault.envdistribuído ao lado deteleoperate.py, a menos que você passe--teleop.cloudxr_env_file.
Desativar. Para pular o lançamento automático (CloudXR já em execução), defina auto_launch_cloudxr=False ou exporte:
export LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1A variável de ambiente tem precedência sobre o campo de configuração: se LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1 estiver
definida, o auto-launch é ignorado mesmo quando auto_launch_cloudxr=True. Esta variável é independente da ISAACLAB_CXR_SKIP_AUTOLAUNCH do Isaac Lab — definir uma não afeta a
outra.
Um teleoperador por processo. O runtime do CloudXR configura o ambiente em nível de processo (um singleton), portanto, execute um único teleoperador Isaac Teleop por processo.
Encerrando. Sempre chame teleop_device.disconnect() na saída — inclusive em Ctrl-C. Envolva
seu loop de teleoperação em try/finally e chame disconnect() no finally. Isso derruba
a sessão OpenXR antes do runtime CloudXR, que é a ordem necessária; o hook atexit do launcher apenas coleta o runtime e não executa o __exit__ da sessão, então sem um
disconnect() explícito, uma execução interrompida encerra na ordem errada.
teleop_device.connect()
try:
while True:
action = teleop_device.get_action()
# ... drive the robot ...
finally:
teleop_device.disconnect()Consulte Requisitos do sistema para as versões suportadas de SO / GPU / CloudXR e headsets.
Como funciona
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 conecta o ControllersSource do Isaac Teleop — rebaseado estaticamente no referencial da base do robô pelo ControllerTransform nativo (base_T_anchor) — e expõe o fluxo do controlador transformado sem alterações. get_action() lê a pose do grip, o squeeze e o trigger diretamente dele; a sessão é sempre executada em RUNNING (não há um retargeter de clutch para atuar como gate).
A classe Clutch (em examples/isaac_teleop_to_so101/isaac_teleop/clutch.py, controlada pelo
loop em common.py) espelha o SO101ClutchRetargeter do Isaac Teleop, mas fica dentro do loop para que o
dispositivo possa permanecer como um leitor simples:
- Ele fixa sua origem de engajamento na borda de engajamento do squeeze (o quadro em que o squeeze cruza
clutch_thresholdpela primeira vez) e rebaseia tanto a posição quanto a orientação em torno dela, de modo que o engajamento não teleporte o braço.Clutch.rebaseretorna o alvo absoluto do quadro de base como um par(pos, quat), que o loop concatena noee_posede 7D enviado ao processador. - O gatilho analógico se torna a
closedness(fechamento) da garra em[0, 1](0 = aberto, 1 = fechado), proporcional à pressão do gatilho, que oMapXRControllerActionToRobotActionmapeia para um alvo da mandíbula.
Veja a interface de retargeting e a visão geral da arquitetura do Isaac Teleop para entender como os nós de origem e os retargeters se combinam.
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 targetsO clutch: pertencente ao loop de exemplo
Diferente do pipeline do telefone (que divide o clutch entre MapPhoneActionToRobotAction e EEReferenceAndDelta), o clutch do XR está inteiramente na classe Clutch do loop de exemplo. Ele emite uma pose absoluta do EE, portanto não há um estágio EEReferenceAndDelta nem acúmulo de delta no processador — MapXRControllerActionToRobotAction é um mapeamento puro e sem estado por quadro.
O clutch fixa sua origem de engate na borda de engate do squeeze (o momento em que o squeeze cruza clutch_threshold) e dirige o EE a partir do movimento relativo a essa origem, para que o braço não teleporte no engate. Em todo engate — seja na inicialização ou em um re-clutch durante a tarefa —, a posição de referência é fixada a partir da cinemática direta nos juntos medidos do braço, de modo que a posição de referência corresponde a onde o braço está fisicamente, mesmo que ele tenha se movido enquanto estava desengatado, e o engate ocorre sem saltos. A orientação de referência mantém a última rotação comandada: o braço de 5-DOF rastreia a orientação apenas de forma suave, portanto fixar a orientação do punho medida injetaria seu offset de rastreamento no comando a cada re-clutch.
Controles
- Apertar / segurar — o clutch (deadman). Segure-o além de
clutch_thresholdpara ativar a teleoperação; solte para pausar. Cada ativação recaptura a origem, permitindo reposicionar a mão enquanto está pausado e reativar sem que o braço salte (estilo índice/clutch). - Gatilho — o gripper, controlado de forma analógica. A mandíbula acompanha o gatilho proporcionalmente — um gatilho pressionado pela metade deixa a mandíbula meio fechada — por meio de um grau de fechamento em
[0, 1](0 = aberto, 1 = fechado) que mapeia para um alvo absoluto da junta do gripper. - Orientação do controlador — o pulso. O clutch rebaseia a orientação do controlador
(relativa ao engajamento, no referencial base) em um alvo suave de orientação IK que o pulso acompanha junto com a
posição. No SO‑101 de 5‑DOF, o pulso segue a mão apenas parcialmente por design — veja
orientation_weightabaixo.
Comece
Etapa 1: Crie o 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() retorna a pose do controlador no raw base‑frame, e não um alvo rebaseado por clutch: grip_pos (3,) [x, y, z] [m] e grip_quat (4,) [qx, qy, qz, qw] no base frame do robô, além dos valores analógicos escalares squeeze e trigger em [0, 1]. O Clutch do loop de exemplo converte esses valores na ee_pose absoluta, e o squeeze é limiarado pelo loop em relação a clutch_threshold para engatar.
Etapa 2: Conectar
Ao chamar teleop_device.connect() primeiro, o runtime do CloudXR é iniciado automaticamente (a menos que você tenha optado por não fazer isso —
veja Configurar o CloudXR e conectar um headset; isso bloqueia por
~30s e, na primeira execução, solicita a EULA no stdin), em seguida, inicia a sessão de teleoperação do Isaac TeleopSession (abre a sessão OpenXR e detecta os controladores). Os controladores XR são autocalibrantes, portanto
não há etapa de calibração manual — o clutch trata a recentralização cada vez que você engata. Combine connect() com um try/finally que chama disconnect() para que a sessão seja encerrada antes do
runtime na saída/Ctrl-C.
Etapa 3: Execute o exemplo
O exemplo pressupõe que você configurou seu robô (SO‑101 follower) e definiu a porta serial correta.
O URDF do robô e suas malhas são obtidos automaticamente na primeira execução: o dispositivo XR baixa
o URDF do SO-101 do bucket lerobot/robot-urdfs do Hugging Face para o cache do LeRobot (HF_LEROBOT_HOME/robot-urdfs/so101/) e o reutiliza em seguida, de modo que não há
etapa de download separada:
python -m examples.isaac_teleop_to_so101.teleoperate --robot.type=so101_follower --robot.port=/dev/ttyACM0 \
--robot.id=so101_follower_arm --teleop.type=xr_controllerA CLI é no estilo lerobot-teleoperate (draccus): --robot.* configura o seguidor SO-101 e --teleop.type seleciona o dispositivo de entrada Isaac (xr_controller | so101_leader), com --teleop.* definindo os parâmetros do dispositivo. --teleop.type=xr_controller executa o caminho do controlador XR descrito acima. O contrato de segurança na inicialização: por padrão, ele move todos os articulações para uma pose de reset padrão ao longo de --reset_duration segundos (--reset_to_origin=false mantém o braço onde está), em seguida, define a posição inicial do embreagem a partir da pose medida do braço para que o primeiro engate seja sem saltos; o seguidor é comandado apenas enquanto o embreagem está engatado.
Personalizando a pose de reset. A pose de reset é fornecida como padrão embutido (uma pose confortável de faixa média) e funciona imediatamente — você não precisa gravar nada. Para adaptá-la ao seu setup, mova o braço para a pose desejada e execute python -m examples.isaac_teleop_to_so101.override_reset_pose --id <robot.id>; isso grava as juntas atuais em um arquivo por braço no cache do LeRobot
(HF_LEROBOT_HOME/reset_poses/<robot.name>/<robot.id>.json, com chave similar à calibração), que passa a ter
prioridade sobre o padrão embutido na próxima execução. Como fica no cache local do usuário (não
no repositório), sua personalização permanece na sua máquina, e tanto teleoperate quanto record a respeitam
quando lançados com o mesmo --robot.id.
O outro dispositivo, --teleop.type=so101_leader, espelha o seguidor 1:1 a partir de um braço líder SO-101 leader arm reversível, cujas juntas são transmitidas pelo plugin nativo so101_leader do Isaac Teleop (sem embreagem, sem IK — o líder e o seguidor compartilham a cinemática do SO-101).
O binário so101_leader_plugin é um plugin em C++ que não faz parte do pacote pip isaacteleop — ele é compilado a partir da árvore de fontes do Isaac Teleop. Siga as instruções de Build Isaac Teleop from source (em resumo, a partir do seu checkout do Isaac Teleop: cmake -B build && cmake --build build --parallel && cmake --install build); a compilação instala os plugins em <IsaacTeleop>/install/plugins/, de modo que o binário fica em install/plugins/so101_leader/so101_leader_plugin — o caminho do --launch_plugin abaixo. Consulte o próprio README.md do plugin (localizado ao lado do binário) para os detalhes de serial/calibração.
Aponte --teleop.port para a porta serial do líder físico e --launch_plugin para o binário desse plugin para que o script o inicie após o CloudXR estar ativo:
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(Observe que so101_leader aqui é o líder Isaac, resolvido em relação ao registro de dispositivos de teleoperação Isaac, distinto do serial so101_leader do lerobot-teleoperate.) Quando um --teleop.port é definido, a calibração de tick→radiano do plugin é inferida a partir de --teleop.id e passada ao plugin como seu terceiro argumento posicional — o JSON no formato LeRobot em HF_LEROBOT_CALIBRATION/teleoperators/so_leader/<id>.json, o mesmo arquivo usado pelo líder SO-101 serial (lerobot-calibrate --teleop.type=so101_leader --teleop.id=<id>). Se ele estiver ausente, o script emite um aviso e o plugin usa padrões embutidos. Execute python -m examples.isaac_teleop_to_so101.teleoperate --help para ver todas as flags. Seu contrato de segurança de inicialização: por padrão, o seguidor é movido suavemente para a primeira leitura do líder ao longo de --align_duration segundos (--align=false para pular), para que o braço não se desloque bruscamente quando o espelhamento começa, e enquanto o fluxo do líder estiver obsoleto, o seguidor é mantido em sua pose medida.
O fetch do URDF usa huggingface_hub (já uma dependência do LeRobot) contra o bucket público lerobot/robot-urdfs, portanto não requer login. Ele é armazenado em cache em HF_LEROBOT_HOME/robot-urdfs/so101/; exclua essa pasta para forçar um novo download.
Em seguida, no seu headset: aperte e segure o gatilho lateral para ativar, mova o controle para dirigir o braço, gire/incline-o para orientar o pulso e pressione o gatilho para fechar o garra (proporcionalmente — solte para abrir).
Para gravar um dataset (não apenas teleoperar), use record.py na mesma pasta. Ele faz o despacho com base em --teleop.type (xr_controller | so101_leader) exatamente como teleoperate.py, de modo que qualquer dispositivo pode controlar o seguidor, e ele salva as juntas comandadas em um dataset LeRobot (flags --dataset.* no estilo lerobot-record). Consulte a docstring do módulo para a CLI completa e os atalhos de teclado para gravação.
Etapas e opções importantes do pipeline
O clutch já produz uma pose absoluta do base-frame, portanto o lado do processador é um caminho fino de pose absoluta — não há remapeamento de frame, nem acúmulo de delta, nem estágio EEReferenceAndDelta.
MapXRControllerActionToRobotActioné um mapeamento sem estado, por quadro, da saída do dispositivo para o contrato de entrada do IK. Ele escreve a posição absoluta no referencial base, codifica a orientação absoluta como um alvo rotvec e inverte o fechamento para um alvo de pinça 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 = openA polaridade da garra (
100 = open, 0 = closed) é uma convenção de calibração de hardware na origem — inverta-a lá se a mandíbula abrir quando deveria fechar.EEBoundsAndSafetylimita o EE a um espaço de trabalho e aplica limite de taxa aos saltos por quadro. O modo sem teleporte do clutch mantém os quadros pequenos, de modo quemax_ee_step_mcaptura principalmente falhas transitórias de rastreamento do controlador. O piso em z é0.0(o plano da mesa), para que um alvo fora do lugar não leve o EE abaixo da mesa; x/y permanecem na caixa solta de[-1, 1]m. Definaraise_on_jump=Falsepara que um quadro acima do limite seja limitado e alertado em vez de gerar uma exceção — uma falha no meio do loop deixaria o braço sem controle: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)resolve o IK Placo em laço fechado. O SO‑101 é um braço de 5‑DOF, portanto o IK é dominado pela posição; o pequenoorientation_weightpermite que ele acompanhe suavemente o alvo de orientação contido emee.w*, fazendo com que o pulso siga a mão, enquanto o roll subdeterminado permanece parcial por design. Não há nenhumGripperVelocityToJoint: o valor absoluto deee.gripper_posé passado diretamente paragripper.pos.initial_guess_current_joints=Falseinicia cada resolução a partir da solução IK anterior, em vez de redefinir a semente a partir das juntas medidas, de modo que a trajetória das juntas permaneça contínua de um frame para o outro. Ajusteorientation_weightno hardware — um valor muito alto compromete o rastreamento da posição, enquanto um valor muito baixo ignora o comando de orientação.
O exemplo também aplica um controle de segurança no nível do loop: após o slew de reset na inicialização (ativado por padrão —
passar --reset_to_origin=false para manter o braço onde está), ele comanda o robô apenas enquanto
o clutch está engatado e reenvia as juntas medidas enquanto estiver desengatado, de modo que soltar o
clutch congela o braço no lugar.
Consulte o guia Processadores para Robôs e Teleoperadores para mais informações sobre a adaptação do pipeline a outros robôs.
Solução de problemas
ModuleNotFoundError: isaacteleop— o pacoteisaacteleopnão está instalado no ambiente ativo. Execute novamente o comando de instalação no início deste guia:uv pip install "isaacteleop[cloudxr,retargeters-lite]~=1.3.131".- Nenhum controlador encontrado — verifique se o runtime do CloudXR está em execução, se as portas do firewall estão na lista de permissões e se o headset está conectado (consulte Configurar o CloudXR e conectar um headset e o Quick Start do Isaac Teleop).
- Falha no auto-início do CloudXR —
connect()levanta umRuntimeErrorse o runtime não iniciar dentro do tempo limite de inicialização. Verifique os logs do launcher em~/.cloudxr/logs. Causas comuns: o EULA nunca foi aceito (executepython -m isaacteleop.cloudxr --accept-eulauma vez, interativamente — o auto-início solicita confirmação no stdin e fica travado em modo headless), ou o runtime já está em execução externamente (definaLEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1ouauto_launch_cloudxr=Falsepara pular o auto-início). - O braço não se move — a embreagem é um deadman: você deve manter a pressão/aperto acima de
clutch_threshold. Reduza o limite se o aperto do seu controlador for reportado de forma suave. - O movimento parece desalinhado — confirme a orientação do headset/área de jogo. O fluxo do controle é rebaseado no referencial da base do robô pela transformação
base_T_anchoremXRControllerConfig(padrão: convenção de eixos OpenXR → robô); ajuste-a se o seu referencial de âncora for diferente.
Saiba mais
Documentação do NVIDIA Isaac Teleop (página inicial da documentação, GitHub):
- Início rápido — instale, execute o servidor CloudXR, conecte um headset e execute um exemplo de teleoperação.
- TeleopSession —
a API de sessão que o
XRControllerencapsula. - Interface de retargeting e visão geral da arquitetura — como os nós de origem e os retargeters se compõem em um pipeline.
- Compilar a partir do código-fonte —
compile o
isaacteleop(e seus plugins em C++, incluindo o pluginso101_leaderusado acima) a partir de um checkout local. - Requisitos do sistema e a documentação do SDK do CloudXR — plataformas, GPUs, versões do runtime CloudXR/OpenXR e headsets suportados.