# Isaac Teleop

Управляйте своим роботом с помощью [NVIDIA Isaac Teleop](https://github.com/NVIDIA/IsaacTeleop) — мультимодальной системы телеуправления. Isaac Teleop управляет одной сессией `TeleopSession` с использованием различных устройств ввода: контроллеров XR (VR), отслеживания движений рук, отслеживания всего тела, перчаток Manus, педалей и других.

В LeRobot Isaac Teleop поставляется в виде автономного примера в [`examples/isaac_teleop_to_so101/`](https://github.com/huggingface/lerobot/tree/main/examples/isaac_teleop_to_so101). Каждое устройство ввода Isaac Teleop представляет собой собственный подкласс `Teleoperator` в пакете `isaac_teleop` примера, разделяя один жизненный цикл сессии (см. `IsaacTeleopTeleoperator`). Доступные на данный момент устройства — это **XR-контроллер** (`XRController`) и управляемая с обратной связью **рука-лидер SO-101** (`SO101LeaderArm`); перчатки Manus и отслеживание руки/всего тела являются естественными следующими устройствами. Это руководство сосредоточено на XR-контроллере; рука-лидер SO-101 описана кратко в разделе [Запуск примера](#step-3-run-the-example).

**В этом руководстве вы узнаете:**

- Как устройство Isaac Teleop управляет целевым положением конечного эффектора (EE) робота
- Как _clutch_ (сжатие/захват на контроллере XR) активирует телеуправление без рывков в движении руки
- Как запустить пример телеуправления SO‑101 и настроить движение / захват / IK

## Установка

Пример находится в репозитории LeRobot (он не входит в пакет `lerobot` для pip), поэтому клонируйте репозиторий и установите из исходного кода. Канонический, всегда актуальный справочник по установке и использованию — это [`README.md`](https://github.com/huggingface/lerobot/tree/main/examples/isaac_teleop_to_so101/README.md) примера; кратко:

```bash
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` опубликован в публичном PyPI (только для Linux). Дополнительный пакет `cloudxr` предоставляет привязки к рантайму CloudXR; `retargeters-lite` — это путь к ретаргетеру на базе scipy, который устанавливается как на x86_64, так и на ARM (на aarch64 — например, на DGX Spark — полный дополнительный пакет `retargeters` не устанавливается из-за фиксированных версий `dex-retargeting`/`nlopt`, поэтому здесь он не является значением по умолчанию). На x86_64 можно дополнительно установить полный стек ретаргетеров:

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

### Настройка CloudXR и подключение гарнитуры

Isaac Teleop транслирует изображение с гарнитуры на ваш компьютер через **NVIDIA CloudXR**, который предоставляет OpenXR-рантайм, к которому подключается сессия. По умолчанию LeTeleop **автоматически запускает рантайм CloudXR** при вызове `teleop_device.connect()` — вам больше не нужно запускать `python -m isaacteleop.cloudxr` и `source cloudxr.env` в отдельном окне терминала. Достаточно подключить поддерживаемую гарнитуру и открыть порты файрвола CloudXR. Подробности о сопряжении гарнитуры и настройке файрвола описаны в разделе [Quick Start](https://nvidia.github.io/IsaacCapture/main/getting_started/quick_start.html) Isaac Teleop.

**Первый запуск (EULA).** При самом первом запуске необходимо принять EULA NVIDIA CloudXR. Автоматический запуск запрашивает его **на stdin**, поэтому на машине без графического интерфейса он будет зависать в ожидании ввода. Инициализируйте EULA один раз в интерактивном режиме с помощью:

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

После этого `connect()` запускает рантайм в неинтерактивном режиме. Запуск **блокируется примерно на 30 секунд**, пока рантайм не будет запущен.

**Конфигурация.** Два поля в `IsaacTeleopConfig` (общие для всех устройств) управляют этим:

- `auto_launch_cloudxr` (по умолчанию `True`) — определяет, запускает ли `connect()` среду выполнения. Установите `False`, если CloudXR уже запущен извне.
- `cloudxr_env_file` (по умолчанию `None`) — необязательный файл профиля устройства CloudXR `.env`, определяющий транспорт гарнитуры (например, профиль Apple Vision Pro). Это **входной** параметр лаунчера; это не файл **выхода** `~/.cloudxr/run/cloudxr.env`, который старый ручной процесс предлагал `source`ировать. `None` сохраняет профиль auto-WebRTC по умолчанию — хотя пример SO-101 переопределяет его на `default.env`, поставляемый рядом с `teleoperate.py`, если вы не передаёте `--teleop.cloudxr_env_file`.

**Отказ от запуска.** Чтобы пропустить автоматический запуск (если CloudXR уже запущен), установите `auto_launch_cloudxr=False` или экспортируйте:

```bash
export LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1
```

Переменная окружения **имеет приоритет над полем конфигурации**: если задано `LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1`, автоматический запуск пропускается, даже если `auto_launch_cloudxr=True`. Эта переменная **не зависит** от `ISAACLAB_CXR_SKIP_AUTOLAUNCH` в Isaac Lab — установка одной не влияет на другую.

**Один телеоператор на процесс.** Рантайм CloudXR настраивает окружение на уровне всего процесса (синглтон), поэтому запускайте один телеоператор Isaac Teleop на процесс.

**Остановка.** Всегда вызывайте `teleop_device.disconnect()` при завершении — в том числе при Ctrl-C. Оберните цикл телеуправления в `try/finally` и вызовите `disconnect()` в блоке `finally`. Это завершает сессию OpenXR **до** рантайма CloudXR, что является обязательным порядком; хук `atexit` лаунчера лишь завершает рантайм и не вызывает `__exit__` сессии, поэтому без явного вызова `disconnect()` прерванный запуск завершится в неверном порядке.

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

См. [Системные требования](https://nvidia.github.io/IsaacCapture/main/references/requirements.html) для списка поддерживаемых версий ОС / GPU / CloudXR и гарнитур.

## Как это работает

XR-контроллер является одним из устройств **ввода** Isaac Teleop. `XRController` намеренно реализован как тонкий считыватель: он предоставляет **сырые** данные о положении хвата контроллера — уже статически пересчитанные в систему координат базы робота — а также аналоговые значения сжатия и спуска. В нём **нет** ретаргетеров и **нет** собственной логики сцепления. Сцепление (замок включения + пересчёт дельты относительно EE) и отображение захвата находятся ниже по потоку в примере цикла, который затем передаёт данные в существующий замкнутый контур декартовой обратной кинематики LeRobot — тот же самый, что использует оператор с телефоном. Специфичные для устройства компоненты — это `XRController`, `Clutch` цикла и `MapXRControllerActionToRobotAction`; всё, что находится ниже по потоку (`EEBoundsAndSafety`, `InverseKinematicsEEToJoints`), является общим, и будущее устройство (например, перчатки Manus) заменит свой `teleop_<device>.py` + процессор, переиспользуя остальное.

`XRController._build_pipeline` подключает `ControllersSource` Isaac Teleop, который статически пересчитывается в базовую систему координат робота с помощью нативного `ControllerTransform` (`base_T_anchor`), и предоставляет преобразованный поток данных контроллера без изменений. `get_action()` считывает позу захвата, сжатие и спуск непосредственно из этого потока; сессия всегда выполняется в режиме `RUNNING` (нет переключателя для переназначения, который бы ограничивал доступ).

Класс `Clutch` (в файле `examples/isaac_teleop_to_so101/isaac_teleop/clutch.py`, управляемый циклом в `common.py`) повторяет логику класса `SO101ClutchRetargeter` из Isaac Teleop, но выполняется внутри цикла, что позволяет устройству оставаться простым считывателем данных:

- Он фиксирует точку срабатывания на фазе **включения** (кадре, в котором сжатие впервые пересекает `clutch_threshold`) и пересчитывает относительно неё как положение, так и ориентацию, чтобы при включении рука не телепортировалась. `Clutch.rebase` возвращает абсолютную целевую точку в базовой системе координат в виде пары `(pos, quat)`, которую цикл объединяет в 7-мерный `ee_pose`, передаваемый процессору.
- Аналоговый триггер становится значением `closedness` захвата в диапазоне `[0, 1]` (0 = открыт, 1 = закрыт), пропорциональным нажатию триггера, которое `MapXRControllerActionToRobotAction` преобразует в целевое положение челюстей.

См. [интерфейс ретаргетинга](https://nvidia.github.io/IsaacCapture/main/references/retargeting/index.html) и [обзор архитектуры](https://nvidia.github.io/IsaacCapture/main/overview/architecture.html) Isaac Teleop, чтобы узнать, как комбинируются исходные узлы и ретаргетеры.

```text
  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
```

### Сцепление: принадлежит циклу примера

В отличие от телефонного конвейера (где управление сцеплением распределено между `MapPhoneActionToRobotAction` и `EEReferenceAndDelta`), XR-сцепление полностью реализовано в классе `Clutch` цикла примера. Оно выдает **абсолютную** позу ИК, поэтому нет этапа `EEReferenceAndDelta` и накопления дельт в процессоре — `MapXRControllerActionToRobotAction` является чистым, безсостоятельным отображением для каждого кадра.

Механизм сцепления фиксирует исходную точку включения в момент срабатывания **границы включения** (когда значение сжатия пересекает `clutch_threshold`) и управляет EE на основе движения, _относительного_ к этой точке, чтобы рука не телепортировалась при включении. При **каждом** включении — будь то запуск или повторное сцепление в ходе задачи — базовое _положение_ фиксируется по прямой кинематике на основе **измеренных суставов** руки, поэтому базовая точка совпадает с физическим положением руки, даже если она сместилась во время отключения, и включение происходит без скачков. Базовая _ориентация_ сохраняет последнюю заданную поворотную команду: рука с 5 степенями свободы отслеживает ориентацию только мягко, поэтому фиксация измеренной ориентации запястья привела бы к введению её смещения отслеживания в команду при каждом повторном сцеплении.

## Управление

- **Сжатие / захват** — **тормоз** (deadman). Удерживайте его выше `clutch_threshold`, чтобы активировать телеуправление; отпустите, чтобы поставить на паузу. При каждом включении заново фиксируется исходная точка, поэтому вы можете переместить руку во время паузы и снова активировать управление без рывка манипулятора (стиль index/clutch).
- **Спуск** — **захват**, управляемый **аналоговым** сигналом. Пальцы захвата отслеживают спуск пропорционально — нажатие спуска наполовину приводит к закрытию пальцев наполовину — через значение закрытия в диапазоне `[0, 1]` (0 = открыто, 1 = закрыто), которое отображается в абсолютную целевую позицию сустава захвата.
- **Ориентация контроллера** — это **запястье**. Механизм clutch пересчитывает ориентацию контроллера (относительно точки захвата, в базовой системе координат) в мягкую целевую ориентацию для IK, которой запястье следует вместе с позицией. На 5-DOF SO-101 запястье по конструкции лишь частично следует за рукой — см. `orientation_weight` ниже.

## Начало работы

### Шаг 1: Создайте телеоператора

```python
# 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()` возвращает **сырые** данные позы контроллера в базовой системе координат, а не пересчитанную цель с учётом сцепления: `grip_pos` (3,) `[x, y, z]` [м] и `grip_quat` (4,) `[qx, qy, qz, qw]` в базовой системе координат робота, а также скалярные аналоговые значения `squeeze` и `trigger` в диапазоне `[0, 1]`. В цикле примера `Clutch` преобразует их в абсолютную `ee_pose`, а значение squeeze сравнивается в цикле с `clutch_threshold` для активации сцепления.

### Шаг 2: Подключение

Вызов `teleop_device.connect()` сначала автоматически запускает рантайм CloudXR (если вы не отключили эту опцию — см. [Настройка CloudXR и подключение гарнитуры](#set-up-cloudxr-and-connect-a-headset); этот процесс блокируется примерно на 30 секунд, а при первом запуске запрашивает согласие с EULA через stdin), затем запускает [`TeleopSession`](https://nvidia.github.io/IsaacCapture/main/getting_started/teleop_session.html) Isaac Teleop (открывает сессию OpenXR и обнаруживает контроллеры). Контроллеры XR калибруются автоматически, поэтому ручной шаг калибровки не требуется — механизм clutch выполняет повторную центровку каждый раз при активации. Используйте `connect()` в паре с `try/finally`, в котором вызывается `disconnect()`, чтобы сессия корректно завершалась до рантайма при выходе или Ctrl-C.

### Шаг 3: Запустите пример

В примере предполагается, что вы настроили робота (SO‑101 follower) и указали правильный последовательный порт.

При первом запуске **URDF робота и его меши загружаются автоматически**: XR-устройство скачивает URDF SO-101 из [бакета Hugging Face `lerobot/robot-urdfs`](https://huggingface.co/buckets/lerobot/robot-urdfs/tree/so101) в кэш LeRobot (`HF_LEROBOT_HOME/robot-urdfs/so101/`) и далее использует его повторно, поэтому отдельный шаг загрузки не требуется:

```bash
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
```

CLI выполнен в стиле `lerobot-teleoperate` (draccus): `--robot.*` настраивает SO-101 follower, а `--teleop.type` выбирает устройство ввода Isaac (`xr_controller` | `so101_leader`), при этом `--teleop.*` задаёт параметры конкретного устройства. `--teleop.type=xr_controller` запускает сценарий с XR-контроллером, описанный выше. Контракт безопасности при запуске: по умолчанию все суставы плавно переводятся в стандартную позу сброса за `--reset_duration` секунд (`--reset_to_origin=false` оставляет манипулятор в текущем положении), после чего начальное положение сцепки (clutch home) задаётся на основе измеренной позы манипулятора, чтобы первое включение сцепки происходило без рывков; команды на follower отправляются только при включённой сцепке.

**Настройка позы сброса.** Поза сброса поставляется как встроенное значение по умолчанию (удобная средняя поза) и работает сразу — вам **не** нужно ничего записывать. Чтобы адаптировать её под вашу конфигурацию, переведите манипулятор в нужную позу и выполните `python -m examples.isaac_teleop_to_so101.override_reset_pose --id <robot.id>`; команда записывает текущие значения суставов в файл для конкретного манипулятора в кэше LeRobot (`HF_LEROBOT_HOME/reset_poses/<robot.name>/<robot.id>.json`, с ключом, как при калибровке), который затем имеет приоритет над встроенным значением по умолчанию при следующем запуске. Поскольку файл находится в локальном кэше пользователя (а не в репозитории), ваше переопределение остаётся на вашем компьютере, и оба режима `teleoperate` и `record` учитывают его при запуске с тем же `--robot.id`.

Другое устройство, `--teleop.type=so101_leader`, зеркально повторяет движения исполнителя 1:1 с помощью заднего манипулятора SO-101 _leader arm_, суставы которого передаются через нативный плагин `so101_leader` Isaac Teleop (без сцепления, без ИК — манипуляторы leader и follower используют одну кинематику SO-101).

Бинарный файл `so101_leader_plugin` представляет собой плагин на C++, который **не входит** в состав пакета pip `isaacteleop` — его необходимо собрать из исходного кода Isaac Teleop. Следуйте инструкции [Сборка Isaac Teleop из исходного кода](https://nvidia.github.io/IsaacCapture/main/getting_started/build_from_source/index.html) (кратко: из каталога с исходным кодом Isaac Teleop выполните `cmake -B build && cmake --build build --parallel && cmake --install build`); при сборке плагины устанавливаются в каталог `/install/plugins/`, поэтому бинарный файл окажется по пути `install/plugins/so101_leader/so101_leader_plugin` — это путь для параметра `--launch_plugin`, указанный ниже. Подробности о последовательном порту и калибровке см. в файле `README.md` самого плагина (расположен рядом с бинарным файлом).

Укажите в параметре `--teleop.port` последовательный порт физического ведущего манипулятора, а в `--launch_plugin` — путь к бинарнику плагина, чтобы скрипт запустил его после поднятия CloudXR:

```bash
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
```

(Здесь `so101_leader` — это лидер _Isaac_, разрешаемый по реестру устройств Isaac Teleop, в отличие от серийного `so101_leader` в `lerobot-teleoperate`.) Если задан `--teleop.port`, калибровка «тик → радиан» плагина выводится из `--teleop.id` и передается в плагин в качестве третьего позиционного аргумента — это JSON в формате LeRobot по пути `HF_LEROBOT_CALIBRATION/teleoperators/so_leader/<id>.json`, тот же файл, который использует серийный лидер SO-101 (`lerobot-calibrate --teleop.type=so101_leader --teleop.id=<id>`). Если файла нет, скрипт выводит предупреждение, и плагин использует встроенные значения по умолчанию. Все флаги доступны через `python -m examples.isaac_teleop_to_so101.teleoperate --help`. Контракт безопасности при запуске: по умолчанию исполнительный механизм плавно переводится в первое положение лидера за `--align_duration` секунд (`--align=false` для пропуска), чтобы рука не дёргалась при начале зеркалирования, а при устаревании потока данных от лидера исполнительный механизм удерживается в измеренной позе.

Загрузка URDF использует `huggingface_hub` (уже являющийся зависимостью LeRobot) для обращения к публичному бакету `lerobot/robot-urdfs`, поэтому вход не требуется. Данные кэшируются в `HF_LEROBOT_HOME/robot-urdfs/so101/`; удалите эту папку, чтобы принудительно загрузить файлы заново.

Затем в вашем гарнитуре: сожмите и удерживайте рукоятку, чтобы активировать управление, перемещайте контроллер, чтобы управлять рукой, поворачивайте/наклоняйте его, чтобы ориентировать запястье, и нажимайте на спусковой крючок, чтобы закрыть захват (пропорционально — отпустите, чтобы открыть).

Чтобы записать датасет (а не только телеуправлять), используйте `record.py` в той же папке. Он работает по `--teleop.type` (`xr_controller` | `so101_leader`) точно так же, как `teleoperate.py`, поэтому любой из устройств может управлять ведомым манипулятором, а он сохраняет управляющие сигналы для суставов в датасет LeRobot (флаги `--dataset.*` в стиле `lerobot-record`). Полный список CLI и горячие клавиши для записи с клавиатуры описаны в docstring модуля.

## Важные этапы и параметры конвейера

Clutch уже формирует абсолютную позу базовой рамы, поэтому на стороне процессора используется тонкий путь **абсолютной позы** — без переназначения рамы, без накопления дельт и без этапа `EEReferenceAndDelta`.

- `MapXRControllerActionToRobotAction` представляет собой безсостоянное пофреймовое отображение от выходных данных устройства к контракту входа IK. Она записывает абсолютное положение в базовой системе координат, кодирует абсолютную ориентацию как целевой rotvec и инвертирует степень закрытия в целевое значение мотора захвата:

  ```python
  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
  ```

  Полярность захвата (`100 = open, 0 = closed`) — это соглашение аппаратной калибровки в исходном коде; инвертируйте её там, если челюсть открывается, когда должна закрываться.

- `EEBoundsAndSafety` ограничивает EE рамками рабочей области и ограничивает скорость скачков на кадр. Функция «без телепортации» сцепления сохраняет кадры небольшими, поэтому `max_ee_step_m` в основном ловит кратковременные сбои отслеживания контроллера. Нижний предел по z равен `0.0` (плоскость стола), чтобы случайная целевая точка не увела EE под стол; x/y остаются в широкой рамке `[-1, 1]` м. Установите `raise_on_jump=False`, чтобы кадр, превышающий лимит, был **ограничен с предупреждением**, а не вызывал исключение — сбой в середине цикла оставил бы манипулятор без управления:

  ```python
  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)` решает задачу замкнутого IK на базе Placo. Поскольку SO‑101 — это манипулятор с 5 степенями свободы, IK в основном ориентирован на позиционирование; небольшой вес `orientation_weight` позволяет мягко отслеживать целевую ориентацию, переданную в `ee.w*`, чтобы запястье следовало за рукой, при этом недоопределённый roll остаётся частичным по замыслу. Модуля `GripperVelocityToJoint` **нет**: абсолютное значение `ee.gripper_pos` передаётся напрямую в `gripper.pos`. Параметр `initial_guess_current_joints=False` использует в качестве начального приближения для каждого решения **предыдущее решение IK**, а не заново инициализирует его из измеренных углов суставов, благодаря чему траектория суставов остаётся непрерывной от кадра к кадру. Настройте `orientation_weight` на реальном оборудовании — слишком высокое значение будет конфликтовать с отслеживанием позиции, а слишком низкое — игнорировать команду ориентации.

Пример также обеспечивает защиту на уровне цикла: после начального сброса с плавным разгоном (включено по умолчанию — передайте `--reset_to_origin=false`, чтобы оставить манипулятор на месте), он отправляет команды роботу **только при включённом сцеплении**, а при отключённом — повторно передаёт измеренные значения суставов, что позволяет зафиксировать манипулятор в текущем положении при отпускании сцепления.

См. руководство [Процессоры для роботов и телеоператоров](./processors_robots_teleop), чтобы узнать больше о адаптации конвейера под других роботов.

## Устранение неполадок

- **`ModuleNotFoundError: isaacteleop`** — пакет `isaacteleop` не установлен в активном окружении. Повторите команду установки в начале этого руководства: `uv pip install "isaacteleop[cloudxr,retargeters-lite]~=1.3.131"`.
- **Контроллеры не найдены** — убедитесь, что среда выполнения CloudXR запущена, порты файрвола добавлены в белый список, а гарнитура подключена (см. [Настройка CloudXR и подключение гарнитуры](#set-up-cloudxr-and-connect-a-headset) и [Быстрый старт](https://nvidia.github.io/IsaacCapture/main/getting_started/quick_start.html) Isaac Teleop).
- **Автоматический запуск CloudXR не удался** — `connect()` вызывает `RuntimeError`, если среда выполнения не запускается в пределах тайм-аута. Проверьте журналы лаунчера в `~/.cloudxr/logs`. Частые причины: лицензионное соглашение (EULA) не было принято (один раз выполните `python -m isaacteleop.cloudxr --accept-eula` в интерактивном режиме — при автоматическом запуске система запрашивает подтверждение через stdin и зависает в headless-режиме) или среда выполнения уже запущена извне (установите `LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1` или `auto_launch_cloudxr=False`, чтобы пропустить автоматический запуск).
- **Рука не двигается** — сцепление работает как deadman: необходимо удерживать сжатие/захват выше `clutch_threshold`. Если сжатие вашего контроллера регистрируется слабо, уменьшите порог.
- **Движение кажется рассинхронизированным** — проверьте ориентацию гарнитуры/игрового пространства. Поток данных с контроллера пересчитывается в систему координат базы робота с помощью преобразования `base_T_anchor` в `XRControllerConfig` (по умолчанию: стандартная конвенция OpenXR → конвенция осей робота); скорректируйте его, если ваша система координат якоря отличается.

## Подробнее

Документация NVIDIA Isaac Teleop ([главная страница документации](https://nvidia.github.io/IsaacCapture/), [GitHub](https://github.com/NVIDIA/IsaacTeleop)):

- [Быстрый старт](https://nvidia.github.io/IsaacCapture/main/getting_started/quick_start.html) — установка, запуск сервера CloudXR, подключение гарнитуры, запуск примера телеуправления.
- [TeleopSession](https://nvidia.github.io/IsaacCapture/main/getting_started/teleop_session.html) — сессионный API, который оборачивает `XRController`.
- [Интерфейс перенацеливания](https://nvidia.github.io/IsaacCapture/main/references/retargeting/index.html) и [обзор архитектуры](https://nvidia.github.io/IsaacCapture/main/overview/architecture.html) — как исходные узлы и перенацеливатели объединяются в конвейер.
- [Сборка из исходного кода](https://nvidia.github.io/IsaacCapture/main/getting_started/build_from_source/index.html) — сборка `isaacteleop` (и его C++-плагинов, включая плагин `so101_leader`, использованный выше) из локальной копии репозитория.
- [Системные требования](https://nvidia.github.io/IsaacCapture/main/references/requirements.html) и [документация CloudXR SDK](https://docs.nvidia.com/cloudxr-sdk) — поддерживаемые платформы, GPU, версии рантаймов CloudXR/OpenXR и гарнитуры.

