Skip to content

lerobot

tested on simulator

Tier Tier 2
Protocol Hugging Face LeRobot Robot API
Covers SO-100 and SO-101 arms, Koch and OMX arms, other LeRobot arms with joint goals
Hazard default high
Conformance full
Tested with a faithful fake of LeRobot's SOFollower, with each assumption checked in the hardware test plan

Estimated reach. A dozen robot types, thousands of hobby and lab arms built from them. LeRobot registers about a dozen robot types, and the SO-100 and SO-101 kits are among the most built open source arms. The driver reads joint positions and sends joint goals through LeRobot's Robot interface, so it covers robots whose actions are joint positions. Mobile bases and hands with other action types need a map. The count is a model estimate.

The lerobot driver controls Hugging Face LeRobot arms, such as the SO-100, SO-101 and Koch followers. Joint positions are reads, goal positions are channels with per-joint limits and rates, and torque off is the native safe state.

Driver namelerobot
LibraryLeRobot 0.6.1 (Apache-2.0), installed separately
Extranone. Install LeRobot with pip install 'lerobot[feetech]', or the extra for your motors
Hazard defaulthigh
Conformancefull
Safe statenative. Torque off on every motor
Self-describeyes, from the arm's joints and where they are now

Because LeRobot pulls in PyTorch, which is several gigabytes and has no wheels for some machines such as Intel Macs, LeRobot is not an OpenHAL extra. The driver imports LeRobot only when a device connects and, when LeRobot is missing, explains how to install it.

What it supports

Channel nameKindLeRobot call
<joint>, for example shoulder_panread, deg (or -100 to 100), gripper in percentget_observation()["<joint>.pos"]
torqueread, boolbus.sync_read("Torque_Enable") on every motor
<joint>_goal, for example elbow_flex_goalset, same unit as the jointsend_action({"<joint>.pos": goal})
torque_off, torque_onactionbus.disable_torque(), bus.enable_torque()
move (one argument per joint)actionone send_action() for several joints
MapMeaning
{ joint: wrist_flex }A joint under another channel name
{ joint: x, unit: m/s }The unit LeRobot uses for that key, for robots that aren't arms
{ torque: true }The torque state
{ command: move, joints: { pan: shoulder_pan, lift: shoulder_lift } }A move action, each argument naming its joint

Body joints are in degrees with use_degrees: true, LeRobot's default, and from -100 to 100 otherwise. Grippers are 0 to 100 percent open. Angles and plain numbers are never mixed, even though both count as dimensionless to the unit library.

How it works

  • connect() builds the robot from its LeRobot type and calls connect(calibrate=False). Because calibration moves the arm by hand and prompts at the keyboard, the driver never runs calibration, and an uncalibrated arm is refused with the exact lerobot-calibrate command to run.
  • Connecting turns torque on, so the arm holds its position and nothing moves.
  • Because the servo holds the joint's present position, a goal channel reads that position until a goal is sent, so the kernel's max_change_per_s is measured from where the joint really is.
  • A goal reads back as the value LeRobot actually wrote, after its own max_relative_target clipping.
  • torque_on sends every present position as the goal first, so no joint jumps to an old goal.
  • Every LeRobot call blocks on the serial bus, so each call runs in a worker thread with a deadline.

Safe state

A stop turns torque off on every motor, so the arm goes limp and falls under gravity. Rest the arm over something soft while testing, and keep the power switch within reach. The device file's own safe state steps run afterwards, and with torque off those steps only set registers.

close() turns torque off too, LeRobot's disable_torque_on_disconnect, unless torque_off_on_close: false.

Setup

  1. Install LeRobot and its motor extra, for example pip install 'lerobot[feetech]' in the hub's environment.
  2. Calibrate the arm once with lerobot-calibrate --robot.type=so101_follower --robot.port=<port> --robot.id=<id>.
  3. Start from openhal/drivers/lerobot/examples/so101-follower.hal.yaml. Set the port and the id you calibrated with, and keep the goal limits narrow.
  4. Run openhal file validate, then confirm the limits by hand.

Parameters

KeyDefaultMeaning
robotrequiredLeRobot robot type, for example so101_follower or koch_follower
portrequiredSerial port of the motor bus
robot_idrequiredThe id the arm was calibrated under
use_degreestrueDegrees for body joints, or -100 to 100
max_relative_targetnoneLeRobot's own clip on how far one goal may move from the present position, in degrees
torque_off_on_closetrueTurn torque off when the hub closes the device
timeout2 sPer call
action_wait8 sHow long move waits for the joints to arrive before returning started
observation_age50 msReuse one observation across reads this close together
tolerance2How close, in the joint's unit, counts as arrived for move

Example device file

device: SO-101 follower arm
id: so101
connect: { lerobot: so101_follower, port: /dev/ttyACM0, robot_id: my_follower, max_relative_target: 10 }
read:
  shoulder_pan: { unit: deg }
  torque: { type: bool }
write:
  shoulder_pan_goal: { unit: deg, min: -30, max: 30, max_change_per_s: 20, confirm: confirm-each-time }
actions:
  torque_off: {}
hazard: high

Known limitations

  • Cameras in the observation are ignored.
  • The fake in simulator.py stands in for LeRobot in tests, and the fake's assumptions are listed in its docstring and checked step by step in the hardware test plan.
  • Kinematics and Cartesian moves are not exposed, so goals are per joint.
  • move arguments get their limits from the action, not from the goal channels, so keep both the same.

Hazard defaults

The default hazard is high, because arms can pinch and strike and fall when torque goes off, so every change and action waits for a person's approval.

Sources