> ## Documentation Index
> Fetch the complete documentation index at: https://docs.almond.bot/llms.txt
> Use this file to discover all available pages before exploring further.

# teleop

> Launch a VR teleoperation session on the robot or simulator.

Launches a VR teleoperation session. When started, the hostname (`.local`) and local IP address are printed — enter either of these in the VR app at [axol.almond.bot](https://axol.almond.bot) to connect.

Runs on the **real robot by default**; pass `--sim` for the browser visualizer. The full robot config is exposed via draccus, so every gain, stiffness, and gripper limit is reachable from the CLI or a config file — see [Command configuration](/cli/configuration).

<Tip>
  For a guided, step-by-step walkthrough including the VR controller layout, see the [Teleoperation guide](/operations/teleop).
</Tip>

<Warning>
  **Before opening the VR app**, accept the self-signed HTTPS certificate in the VR browser by navigating to `https://<hostname>.local:8000` or `https://<local-ip>:8000` and proceeding past the security warning.
</Warning>

<Note>
  **Network tip:** If VR tracking feels jittery or packets arrive in bursts, configure the following on your router/access point:

  * **DTIM interval** → `1`
  * **Beacon interval** → `100` ms
  * **WMM APSD (U-APSD)** → disabled

  These settings prevent the AP from buffering packets between beacon intervals, which causes intermittent delivery delays that are especially noticeable for latency-sensitive VR traffic.
</Note>

| Flag                                                                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--sim`                                                                                 | Use the software visualizer instead of the real robot. Bare `--sim` works (or `--sim true`). Omit to run on real hardware (the default).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `--cart_only`                                                                           | Drive **only** the [powered cart](#powered-cart) — the arms are never constructed and the Axol hub CAN channels are never opened, so no arm hardware need be connected. A cart is implied (`--cart.enabled` isn't consulted). Mutually exclusive with `--sim`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--left_channel <ch\|null>`                                                             | CAN channel for the left arm; `null` disables it. Default: `CAN_LEFT`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `--right_channel <ch\|null>`                                                            | CAN channel for the right arm; `null` disables it. Default: `CAN_RIGHT`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `--cart.enabled BOOL`                                                                   | This robot has the [powered cart](#powered-cart) (x-drive base + telescoping lift). When set, the headset thumbsticks drive the base/lift (operator mobility only — never recorded, never policy-controlled). Default: `false`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--cart.channel <ch\|null>`                                                             | SocketCAN interface for the cart's wheel motors; `null` disables the wheels (lift-only). Default: `can_alm_axol_b`, pinned by [`can.setup`](/cli/can-setup).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `--cart.lift BOOL`                                                                      | The telescoping lift is present (the jelly\_legs controller on the chest bus). The chest bus being down only disables the lift with a warning — the cart still drives. Default: `true`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--cart.lift_channel <ch>`                                                              | SocketCAN interface of the chest bus carrying the lift controller. Default: `can_alm_axol_c`, pinned by [`can.setup`](/cli/can-setup).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `--cart.imu BOOL`                                                                       | Feed the carrier board's IMU into the cart's straight-line heading hold as its yaw reference (independent of the cameras). Default: `false`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `--cart.<field> VALUE`                                                                  | Any other cart tunable — peak wheel speed (`--cart.max_speed`), turn rate, command slew, stick deadzone, park-hold stiffness, … The full set is the `CartConfig` dataclass, and each field also appears under **Advanced → Cart** in the [control panel](/guides/control-panel#settings).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `--axol.left_stiffness S`                                                               | Compliance↔stiffness blend for the left arm in `[0, 1]`. Scalar (`0.8`) or a 7-element list (`[0.8,0.8,0.5,0.5,0.2,0.2,0.0]`, one per arm joint in `Joint` enum order). `0` = fully compliant; `1` = pre-tuning industrial gains; `0.5` (default) is the geometric mean. See [`AxolConfig.left_stiffness`](/api/robot).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--axol.right_stiffness S`                                                              | Same, for the right arm.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `--axol.left.gripper.torque_limit FLOAT`                                                | Max torque (Nm) for the left gripper in POSITION\_FORCE mode (default: 0.5). Use `right` for the right gripper.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--axol.<side>.<joint>.<gain> VALUE`                                                    | Override any per-joint gain, e.g. `--axol.left.elbow.kp 60`. `<side>` is `left`/`right`; `<joint>` is `shoulder_1…wrist_3`; `<gain>` is `kp`/`kd`/etc.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `--teleop.position_multiplier FLOAT`                                                    | Scale factor on hand **position** (not orientation): the end-effector target moves this many times as far as your hand. `1.0` (default) is 1:1; `2.0` moves the arm twice as far, helping cover the robot's full reach when the arm is longer than the operator's.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `--teleop.rotation_multiplier FLOAT`                                                    | Scale factor on hand **orientation** (not position): the end-effector rotates this many times as far as your wrist. `1.0` (default) is 1:1; `2.0` rotates the arm twice as far.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--teleop.reset_torque_threshold FLOAT`                                                 | Contact watchdog for every return-to-rest (X reset, Y-exit return, startup move; hardware only — default: 4.0 Nm). A joint torque residual (measured minus modeled gravity) sustained above this means the move hit something, so it stops and the arms drop into a limp **gravity-compensation hold** — free them by hand, then press **X** to replan home from wherever they are. `0` disables the watchdog.                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--teleop.reset_gravity_comp_kd FLOAT`                                                  | Velocity damping (Nm·s/rad) for that contact-fallback hold (default: 0.25); same semantics as [`gravity-comp`](/cli/gravity-comp) `--kd`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `--teleop.teleop_torque_threshold FLOAT`                                                | Contact watchdog for the **tracking** phase — while you're actively driving the arms — separate from the always-on return-to-rest guard above (hardware only). **Off by default** (`0`), since teleop pushes on the scene on purpose; set a threshold (the control panel suggests `16.0`) and a sustained torque residual past it **disengages tracking** and drops the arms into the limp gravity-comp hold (press **X** to return to rest, then re-engage). Shared with [`collect-data`](/cli/collect-data).                                                                                                                                                                                                                                                                                                               |
| `--teleop.hold_to_engage BOOL`                                                          | Grip scheme (default: `false`). `false` is the toggle scheme — both grips from rest engage, then each grip toggles its own arm between tracking and frozen. `true` is a **dead-man** scheme: hold both grips to start, release one to freeze that arm, keep holding to keep it going.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `--teleop.ik_alpha FLOAT`, `--teleop.pose_min_cutoff FLOAT`, `--teleop.pose_beta FLOAT` | The pose/IK smoothing filters — the smoothness↔latency trade. See [Latency tuning](/operations/teleop#latency-tuning) for what each costs and the recommended adjustment order. Defaults: `0.3` / `0.8` / `2.0`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `--vr_server.interp_smooth_window_s FLOAT`, `--vr_server.interp_max_delay_s FLOAT`      | The VR pose stream's fixed-lag smoothing window (glitch rejection; adds half the window as latency — default `0.12` ≈ +60 ms) and the adaptive jitter-buffer cap (only spent while the network is actually bursty — default `0.15`). See [Latency tuning](/operations/teleop#latency-tuning).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `--cameras DICT`                                                                        | Map camera slots to the serial numbers of locally attached ZED cameras, e.g. `--cameras "{overhead: 41234567, left_arm: 41234568}"` (any of `overhead` / `left_arm` / `right_arm`). When set, teleop opens those cameras and relays them to the headset over WebRTC (overhead + both wrist views shown at once, each freely moved and resized). Stereo ZED X cameras are detected automatically from their serial: a stereo overhead is relayed as a single side-by-side track, `overhead_sbs` (both eyes packed into one stream), rendered per-lens on the headset, while a stereo wrist streams a single eye under its slot name (one feed, like a mono camera). Requires `pyzed` ([`zed.install`](/cli/zed-install)) and the GStreamer NVENC stack ([`gst.install`](/cli/gst-install)). Default: `{}` (no headset video). |
| `--camera_eyes DICT`                                                                    | Override which eye(s) of a **stereo** slot are streamed to the headset, keyed by slot: `--camera_eyes "{overhead: both, left_arm: left}"` (`both` / `left` / `right`). Unset slots fall back to the default policy — overhead streams both eyes (packed side-by-side, rendered per-lens), wrists stream their left eye. Ignored for mono slots. Default: `{}`.                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--resolution {SVGA,HD1080,HD1200}`                                                     | Capture resolution for all cameras. Default `null`: each camera's SDK default (HD1200 on GMSL).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--kinematics.<field> VALUE`                                                            | Override an IK solver cost weight or solver parameter, e.g. `--kinematics.pos_weight 100` or `--kinematics.max_joint_delta 0.02`. See [`almond_axol.kinematics`](/api/kinematics) for every field and its default.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `--vr_server.port INT`                                                                  | Port for the VR teleop WebSocket server (default: 8000). The TLS cert/key paths live here too (`--vr_server.certfile`, `--vr_server.keyfile`); `null` (the default) uses the auto-generated cert.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `--log_level {DEBUG,INFO,WARNING,ERROR}`                                                | Default: `INFO`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--config_path PATH`                                                                    | Load a whole-config JSON/YAML file; CLI overrides layer on top. See [Command configuration](/cli/configuration).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

```bash theme={null}
axol teleop                                       # real robot
axol teleop --sim                                 # browser visualizer
axol teleop --axol.left_stiffness 0.8
axol teleop --axol.left.elbow.kp 60 --axol.right.gripper.torque_limit 0.7
axol teleop --teleop.position_multiplier 2.0      # scale hand motion 2x
axol teleop --teleop.rotation_multiplier 2.0      # scale hand rotation 2x
axol teleop --left_channel null                   # disable the left arm
axol teleop --cart.enabled true                   # drive the powered cart from the thumbsticks
axol teleop --cart_only                            # drive only the cart (no arms, no hub)
axol teleop --cameras "{overhead: 41234567, left_arm: 41234568, right_arm: 41234569}"
axol teleop --cameras "{overhead: 41234567}"      # stereo overhead auto-detected, per-lens
axol teleop --config_path my_teleop.json          # whole-config file
```

## Camera streaming to the headset

With `--cameras` set, teleop opens the locally attached ZED cameras by serial number and relays them to the headset over WebRTC, so the operator sees the robot's cameras instead of (or alongside) passthrough. All streamed feeds show at once and can be moved and resized with the controllers — see the [VR Interface guide](/guides/vr-interface#camera-views) for the layout and controls. Requires `pyzed` ([`zed.install`](/cli/zed-install)) and the GStreamer NVENC stack ([`gst.install`](/cli/gst-install) — the hosted installer runs it).

Teleop and [`collect-data`](/cli/collect-data) share one **GPU-resident** video path (`almond_axol.video.gst_zed`): the zed-gstreamer source elements (`zedxonesrc` mono / `zedsrc` stereo) grab and **NVENC**-encode (`nvvidconv` → `nvv4l2h264enc`) entirely on the GPU, and Python only ever sees the encoded H.264 access units — which **aiortc** forwards as pre-encoded packets (RTP/SRTP/ICE + SDP signaling), with no Python encode step. Teleop asks the cameras for 60 fps capture (falling back to the camera default when unsupported). Stereo ZED X cameras are auto-detected from their serial: a **stereo overhead** grabs both eyes on one pass and is relayed as a single side-by-side source, `overhead_sbs` (both eyes packed into one track — one decoder session on the headset), rendered per-lens, while a **stereo wrist** is relayed as a single source under its slot name (one feed, like a mono camera). When the gst stack is unavailable the path falls back to the ZED SDK grab (`ZedCamera`) plus in-Python NVENC (`almond_axol.video.hw_video`), which streams the overhead as the two per-eye sources `overhead_left` / `overhead_right` instead.

The whole relay — camera grab/encode plus WebRTC sending — runs in a **dedicated subprocess** (`almond_axol.video.video_proc`), the same isolation pattern used for the IK solver: it keeps the camera pump off the teleop process so the control loops stay at full rate. Only SDP signaling crosses the process boundary.

For low-latency encode the NVENC/VIC engine clocks must be pinned to their maximum (the default Jetson devfreq governor scales them for throughput, not latency, which roughly triples per-frame encode time). This is handled by [`axol jetson.setup`](/cli/jetson-setup), run at boot by the installer's systemd unit — not at teleop startup. If no GStreamer hardware encoder is present at all, aiortc falls back to software H.264 — `SVGA` is then the recommended resolution.

## Powered cart

Robots fitted with the **powered Axol Cart** (x-drive omni base + telescoping lift) let the operator drive the whole robot from the headset thumbsticks — left stick translates, right stick rotates, the stick clicks run the lift. Enable it with `--cart.enabled true` (or the control panel's **Powered cart** toggle); the cart's own parameters live on the nested `cart` config (`--cart.max_speed`, `--cart.channel`, `--cart.imu`, …). Cart motion is **operator mobility only** — it's never recorded into a dataset and a policy never drives it.

`--cart_only` runs *just* the cart: the arms are never constructed and the Axol hub CAN bus is never opened, so you can drive the cart around without the arm hardware connected. The cart brings its own CAN interface up on start ([`can.setup`](/cli/can-setup) pins it as `can_alm_axol_b`), so no separate [`can.enable`](/cli/can-enable) step is needed. For the full control mapping see the [VR interface guide](/guides/vr-interface#powered-cart).

<Note>
  This command takes its config via draccus, not fixed `argparse` flags. Field names use underscores, nested fields are dot-separated, `None` is `null`, and lists use `[a,b,c]` — see [Command configuration](/cli/configuration) for the full model.
</Note>
