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

# LeRobot adapters

> Two subclasses that wrap a LeRobot Robot and Teleoperator, plus the SO-101 calibration helper.

Two ready made subclasses, one per machine. Pass your LeRobot robot or
teleoperator to the matching one and you are done: there is no `descriptor()`,
`read_joints()` or `write_joints()` to write.

```python theme={null}
from zeroruntime.teleops.adapters.lerobot import (SO101FollowerAdapter,
                                              SO101LeaderAdapter)
```

## SO101FollowerAdapter

```python theme={null}
SO101FollowerAdapter(meeting_id=None, *, robot, units="norm", control_hz=50,
                     camera_fps=30, observation_features=None, **kw)
```

<ParamField path="robot" type="Robot" required>
  An already constructed LeRobot `Robot`, with its cameras configured on it.
</ParamField>

<ParamField path="units" type="str" default="norm">
  What the arm's numbers mean: `norm`, `deg`, `rad` or `m`. Match what your
  LeRobot object reports.
</ParamField>

<ParamField path="control_hz" type="int" default="50">
  The loop rate, and the rate announced to the leader.
</ParamField>

<ParamField path="camera_fps" type="int" default="30">
  The frame rate declared for the cameras. LeRobot does the capturing.
</ParamField>

<ParamField path="**kw" type="any">
  Everything from [`Follower`](/robotics/api-reference/python/follower): `token`,
  `safety`, `record`, `observation_hz` and the rest.
</ParamField>

<Warning>
  `limits()` returns `{}` on these adapters, so there is **no position
  clamping**. `slew` is the only thing holding the arm back, so set it on real
  hardware.
</Warning>

## SO101LeaderAdapter

```python theme={null}
SO101LeaderAdapter(meeting_id=None, *, teleop=None, motors=(), units="norm",
                   control_hz=50, **kw)
```

<ParamField path="teleop" type="Teleoperator">
  An already constructed LeRobot `Teleoperator`, the arm a person moves.
</ParamField>

<ParamField path="motors" type="tuple[str, ...]" default="()">
  Joint names, for when there is no local arm to read them from. See
  [Recording without an arm](#recording-without-an-arm).
</ParamField>

<ParamField path="units" type="str" default="norm">
  Must match the follower's.
</ParamField>

<ParamField path="control_hz" type="int" default="50">
  The rate commands are sent at, and the rate announced to the follower.
</ParamField>

<ParamField path="**kw" type="any">
  Everything from [`Leader`](/robotics/api-reference/python/leader):
  `sync_buffer_ms`, `max_misalignment` and the rest.
</ParamField>

### Recording without an arm

Pass `motors` and no `teleop` to get a leader that watches a session and records
it, without being able to move anything.

```python theme={null}
recorder = SO101LeaderAdapter(MEETING_ID, motors=(
    "shoulder_pan", "shoulder_lift", "elbow_flex",
    "wrist_flex", "wrist_roll", "gripper"))
```

It never claims control and never sends, since there is no local arm to read.

## Calibration

```python theme={null}
from zeroruntime.teleops import so101_calibration as calib

calib.load_for(robot_id, *, leader=False)      # read LeRobot's calibration
calib.radian_ranges(cal)                       # {joint: (min_rad, max_rad)}
calib.verify_against_bus(cal, homing_offsets)  # check it against the servos
```

Calibrate each arm with
[`lerobot-calibrate`](https://huggingface.co/docs/lerobot/so101#calibrate)
first. `load_for` then reads that file back by the same `id` you gave it, and
`leader=True` looks under the teleoperator folder instead of the robot one.

<Warning>
  Check the file against the arm before you run. Both arms have the same joint
  names, so loading the leader's calibration on the follower gives numbers that
  look fine and an arm that moves wrongly, and nothing else catches it.
</Warning>

### Where you use it

Confirm the file matches the arm in front of you, before anything moves:

```python theme={null}
cal = calib.load_for("my_follower")

arm.connect(calibrate=False)
problems = calib.verify_against_bus(
    cal, arm.bus.sync_read("Homing_Offset", normalize=False))
if problems:
    raise SystemExit("\n".join(problems))
```

Or turn it into real joint limits, if you are writing your own subclass rather
than using the adapters above:

```python theme={null}
class MyArm(Follower):
    def limits(self):
        return calib.radian_ranges(cal)      # measured travel, per joint
```

## Related

<CardGroup cols={2}>
  <Card title="Follower" icon="robot" href="/robotics/api-reference/python/follower">
    The base class these subclass, and everything they inherit.
  </Card>

  <Card title="SO-101" icon="gears" href="/robotics/integrations/lerobot/so101">
    Calibrating the arms, and setting slew for degrees and gripper units.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.