Skip to main content
LeRobot-compatible wrappers for the Axol hardware. They need the lerobot extra. These classes implement the LeRobot Robot, Teleoperator and Camera interfaces, so Axol works with any LeRobot training or data-collection pipeline. The collect-data and run-policy commands are built on them.
Treat policy checkpoints and LeRobot datasets as trusted input. LeRobot reads dataset metadata and loads model files without a sandbox. Only load checkpoints and datasets you created or got from a source you trust, especially on a computer connected to the robot.

Use with the stock LeRobot CLIs

The lerobot_robot_axol plugin on PyPI registers these wrappers with LeRobot, so the stock LeRobot CLIs (lerobot-teleoperate, lerobot-record, lerobot-replay and others) can drive Axol. Installing the plugin also installs almond-axol[lerobot]. Then select the devices by type:
Teleoperation still uses the VR headset: open the VR app and connect to the robot’s computer. For the best data collection performance, use collect-data and run-policy instead. They add ZED capture in a separate process, NVENC dataset encoding on the Jetson, and video streaming to the headset.
The plugin needs LeRobot 0.6.1, Linux and Python 3.12 or later on the robot’s computer. The hosted installer includes Python 3.13. The zed camera also needs pyzed (zed.install). To add the plugin to an axol environment from the one-command installer, see the plugin README.
If you install the plugin from PyPI into the hosted Axol environment, Axol updates from the installer or the control panel keep the same plugin version. If you installed it any other way, such as from a local path or a Git URL, the hosted update stops before it changes anything. Update that environment by hand, from the same source.

AxolRobot

LeRobot Robot that wraps the async Axol hardware driver. A background thread runs an asyncio event loop, so motor telemetry keeps streaming while the synchronous get_observation() and send_action() calls block the calling thread.

AxolRobotConfig fields

Key methods

Frame and state timing

get_observation() picks the camera frames first, then the robot state that matches them. It never waits for a new exposure:
  1. It finds the newest exposure time that every camera has already delivered.
  2. For each camera, it takes the stored frame nearest that time. Shared-memory readers keep a short history of frames for this (latest_capture_ts(), read_nearest(target, tolerance_s)). An in-process reader gives its newest frame.
  3. From the realtime core’s 240 Hz state history, it takes the joint state nearest the frames’ median exposure time. FK, if enabled, runs after this.
It never returns the same frame set twice, so a loop faster than the cameras waits at most one frame period. It raises an error instead of returning mismatched data when:
  • a camera has gone silent (its newest frame is a couple of frame periods old),
  • no state samples fall on both sides of the frame time,
  • the cameras and the state are too far apart in time.

AxolVRTeleop

LeRobot Teleoperator that wraps VRTeleop. It runs the VR WebSocket server and the IK process on a background thread, so get_action() doesn’t block and is safe to call from any thread.

AxolVRTeleopConfig fields

Key methods

ZedCamera

LeRobot Camera for a ZED camera attached to this computer. It opens the camera by serial number through the ZED SDK and grabs frames on a background thread. It needs pyzed (zed.install).
Each frame carries two timestamps on the local perf_counter clock:
  • capture_perf_ts: when the sensor exposed the frame (from TIME_REFERENCE.IMAGE).
  • receive_perf_ts: when this process retrieved it.
On connect(), the camera measures the gap between the two and logs a warning if the average is outside the expected range.

ZedStereoCamera

Wrapper for a stereo ZED X. It opens one SDK connection and gets both eyes from each grab. The eyes are exposed as left_view and right_view, which have the same read methods as ZedCamera, so the rest of the pipeline treats them as ordinary cameras.
When an AxolRobot camera has stereo=True, its eyes setting decides the observation keys (see AxolRobotConfig.observation_cameras()):
  • "both": camera X becomes X_left and X_right, and the dataset records both eyes. This is the default for the overhead camera.
  • "left" or "right": that eye only, under the plain name X, so it looks like a mono camera. This is the default for wrist cameras.
You rarely set stereo or eyes by hand. collect-data and run-policy call AxolRobotConfig.apply_detected_stereo(), which detects a stereo ZED X from its serial and sets eyes to "both" for the overhead camera and "left" for the others. stream_eyes sets which eyes go to the headset, separately from what is recorded. For example, stream "both" for depth during teleop while recording only "left".

ZedCameraConfig fields