Skip to content

feat(calibration): lerobot-calibrate --auto for the SO-101 leader and follower - #4826

Open
stepankandrushin wants to merge 2 commits into
huggingface:mainfrom
stepankandrushin:auto-calibration
Open

stepankandrushin wants to merge 2 commits into
huggingface:mainfrom
stepankandrushin:auto-calibration

Conversation

@stepankandrushin

@stepankandrushin stepankandrushin commented Oct 1, 2026 •

Copy link
Copy Markdown

Summary / Motivation

Related issues

What changed

Along the review of #3282:

  • No separate CLI: --auto=true on lerobot-calibrate. SOFollower and SOLeader get auto_calibrate(), which saves through their own _save_calibration(), so a leader's file lands under calibration/teleoperators.
  • No private retry helpers: the engine (motors/feetech/auto_calibration.py) uses the bus's methods and their num_retry.
  • Every register the run changes (Operating_Mode, Torque_Limit, Acceleration, Goal_Velocity, P_Coefficient; on failure also Homing_Offset and the limits) is restored in a finally, also on Ctrl-C. The changes are written with Lock=1, so they stay in RAM. If a restore fails, the previous calibration is put back and nothing is written.
  • Options under --auto_calibration.*, among them torque_limit and velocity (default 200 steps/s, down from 1000). The leader is documented in so101.mdx.

Changed after running #3282 on hardware (STS3215, firmware 3.10):

  • End stops are found in position mode, with the goal stepped at most 12° ahead of the joint. In velocity mode, firmware 3.10 reports Present_Position without Homing_Offset, and a servo switched back to position mode with torque on can stay in velocity mode while Operating_Mode reads 0.
  • Every move is stepped, with Goal_Velocity 0. A setpoint travels to a far goal at the Goal_Velocity it started with, so a blocked joint otherwise kept pushing at the torque limit for seconds.
  • Goal = present position before torque comes on, written and read back one servo at a time. feat(calibration): add automatic calibration for SO101 leadera and follower w/ Feetech STS servos #3282 enables torque without setting a goal, and after power-on firmware 3.10 reads Goal_Position 0.
  • What moves when is data (robots/so_follower/auto_calibration_plan.py), with one plan for the follower and one for the leader. The leader's plan runs at 80 % torque instead of 50 %, because its servos are geared to be moved by hand. The plan states each joint's unfold direction and assumes a folded start pose, with the gripper or handle hanging down in front. Turned up, the folded arm boxes the wrist in.
  • The run aborts instead of guessing, and writes nothing, on:
  • A wrist roll without a stopper gets 0-4095, as in the manual calibration, and turns back to where it started.

No breaking changes: the manual calibration is unchanged.

How was this tested (or how to run locally)

  • Tests added: tests/motors/test_feetech_auto_calibration.py, 52 tests. They run the plans against a simulated SO-101 whose servos behave as above, and the SO classes and lerobot-calibrate --auto against mocks. Run them with pytest -q tests/motors/test_feetech_auto_calibration.py.

  • Hardware: one SO-101 follower and one SO-101 leader (STS3215, firmware 3.10), both filmed. The feature commit took 121 s on the follower and 125 s on the leader (the commit after it only rewords three comments for --help and adds a test). Every end was found by stall and is in free air in the video frames, and both arms returned to the start pose. A second follower run, from a fresh clone and a new uv environment, took 119 s. Earlier versions of this branch completed four runs on the follower and three on the leader. Across all of them, every register agrees within 5 steps (0.4°) on the follower, and within 3 on the leader except the trigger (8 steps, 0.7°).

    joint follower leader
    shoulder_pan 194.2° 239.0°
    shoulder_lift 213.1° 211.5°
    elbow_flex 197.4° 194.9°
    wrist_flex 207.0° 202.8°
    wrist_roll (stopper to stopper) 342.9° 338.0°
    gripper / trigger 141.3° 113.1°
  • Video, 70 s, sped up: a follower run on an earlier version of this branch, the terminal next to the arm: https://www.youtube.com/watch?v=yAzojrfhfS0. The final version moves the same way, except that each unfold now goes straight in the plan's direction instead of first trying the blocked one.

  • To try it: put the arm in the start pose from so101.mdx ("Automatic calibration"), then lerobot-calibrate --robot.type=so101_follower --robot.port=<port> --robot.id=<id> --auto=true. Keep a hand near the power: Ctrl-C stops the run, and the arm goes limp.

Checklist (required before merge)

Reviewer notes

  • AI use: I wrote this with substantial help from Claude Code. Claude drafted the code, the tests and this description, and ran the hardware tests while I was at the arms.
  • Uncertain, and where input would help:
    • The accepted spans (auto_calibration_plan.py) are about ±10° around what one follower and one leader measured. Other builds may need --auto_calibration.check_ranges=false, and I'd welcome ranges from other arms. The leader's pan accepts 185-250°: ours turns 239°, 45° more than the follower, for reasons I haven't found.
    • SO-100 types reach the same code (SOFollower/SOLeader), but only SO-101 arms have run it.
    • The wrist roll's zero comes out midway between its stopper's two sides. In a manual calibration it is the pose chosen as the middle. So both arms should be calibrated the same way, as the docs say. fix(robots): sweep so_follower/so_leader wrist_roll instead of assuming it spins #4583 would bring the manual calibration closer.
    • The Feetech-specific options sit in the generic CalibrateConfig as auto_calibration; happy to move them.

stepankandrushin and others added 2 commits October 1, 2026 00:34
… follower

`lerobot-calibrate --robot.type=so101_follower --robot.port=... --robot.id=... --auto=true` (or
--teleop.type=so101_leader ...) calibrates an arm without moving it by hand: every joint is driven into its two
mechanical end stops at a limited torque, and Homing_Offset and the position limits come from where it stalls.
The result goes to the same calibration file and servo registers as a manual calibration.

A rework of the automatic calibration in huggingface#3282, along its review:

- No separate CLI: SOFollower and SOLeader get auto_calibrate(), which saves through their own
  _save_calibration(), so a leader's file lands under calibration/teleoperators.
- The engine (motors/feetech/auto_calibration.py) uses the bus's read/write/sync_read/sync_write and their
  retries. What moves when is data (robots/so_follower/auto_calibration_plan.py): a plan for the follower and
  one for the leader, whose servos are geared to be moved by hand and run at 80 % torque instead of 50 %.
- Every register the run changes is written with Lock=1 (RAM only) and restored in a finally, also on Ctrl-C;
  a restore that fails leaves the previous calibration in place and writes nothing.
- Options under --auto_calibration.*: velocity (default 200 steps/s, was 1000), torque_limit, the checks.

Changed from huggingface#3282 after runs on hardware (STS3215, firmware 3.10):

- End stops are found in position mode, the goal stepped at most 12 degrees ahead of the joint. In velocity
  mode, firmware 3.10 reports Present_Position without Homing_Offset, and a servo switched back to position
  mode with torque on can stay in velocity mode while Operating_Mode reads 0. A setpoint travels to a far goal
  at the Goal_Velocity it started with and keeps a blocked joint pushing, so every move is stepped, with
  Goal_Velocity 0.
- Goal = present position, written and read back one servo at a time, before torque comes on; a joint that
  moves at torque-on ends the run.
- The plan states each joint's unfold direction instead of guessing it, and assumes and checks a folded start
  pose (gripper hanging down: turned up, the folded arm boxes the wrist in).
- Abort instead of guessing: a timeout, a joint that cannot unfold, a range more than about 10 degrees off an
  SO-101's, or a load change on a held joint (the arm pressing on the table or a clamp). Nothing is written.
- A wrist roll without a stopper gets 0-4095, as in the manual calibration, and turns back to where it started.

tests/motors/test_feetech_auto_calibration.py runs the plans against a simulated SO-101 arm whose servos behave
as above, and the SO classes and lerobot-calibrate --auto against mocks. Docs in docs/source/so101.mdx.

Co-authored-by: Isaac Sin <sinweitengisaac@gmail.com>
…options

draccus turns the comments of AutoCalibrationConfig into argparse help, and argparse
%-formats help text: the "% o" of "in 0.1 % of the servo's maximum torque" made
`lerobot-calibrate --help` raise "TypeError: %o format: an integer is required, not dict".
The comments say "tenths of a percent" now; a test runs --help and looks for the options.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 07:40
@github-actions github-actions Bot added documentation Improvements or fixes to the project’s docs tests Problems with test coverage, failures, or improvements to testing robots Issues concerning robots HW interfaces labels Oct 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It is a large, intricate feature that physically drives a robot arm into mechanical end stops under torque, with behavior tuned to specific hardware/firmware and author-acknowledged uncertainties, so it needs human review and on-hardware validation.

Review effort: Balanced
Findings: None

What changed in this PR

This PR adds opt-in automatic calibration for SO-101 leader and follower arms via lerobot-calibrate ... --auto=true. Instead of a person hand-moving each joint, the arm drives every joint into its two mechanical end stops at a limited torque, and Homing_Offset plus the position limits are derived from where each joint stalls. It is a reworked continuation of #3282, re-engineered around observed STS3215 firmware 3.10 behavior (position-mode-only end-stop search, stepped goals, goal-before-torque, full register restore on any abort/Ctrl-C). The result flows through each device's existing _save_calibration()/write_calibration() path, so a leader's file lands under calibration/teleoperators, with no changes to the manual calibration flow.

Changes:

  • New calibration engine (motors/feetech/auto_calibration.py) with a data-driven AutoCalibrationPlan, FeetechAutoCalibrator, stall/contact/range-plausibility checks, and guaranteed register restoration.
  • SO-101 plan data (robots/so_follower/auto_calibration_plan.py) for follower and leader (leader at higher torque), plus auto_calibrate() methods on SOFollower/SOLeader and an --auto/--auto_calibration.* CLI path in lerobot_calibrate.py.
  • Documentation in so101.mdx and a 52-test simulation/mocks suite.
File Description
src/​lerobot/​motors/​feetech/​auto_calibration.py New engine: plan types, decision helpers, stall detection, end-stop driving, and fail-safe register restore.
src/​lerobot/​robots/​so_follower/​auto_calibration_plan.py Follower and leader choreography/range data for the self-calibration.
src/​lerobot/​robots/​so_follower/​so_follower.py Adds auto_calibrate() that prompts for start pose and persists the result like manual calibration.
src/​lerobot/​teleoperators/​so_leader/​so_leader.py Adds auto_calibrate() using SO_LEADER_PLAN, saving under teleoperators.
src/​lerobot/​scripts/​lerobot_calibrate.py Adds --auto and --auto_calibration config and dispatch to auto_calibrate().
docs/​source/​so101.mdx User-facing "Automatic calibration" section with warnings, flow, and options table.
tests/​motors/​test_feetech_auto_calibration.py Simulated-arm and mock-based tests of the engine, plans, SO classes, and CLI.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or fixes to the project’s docs robots Issues concerning robots HW interfaces tests Problems with test coverage, failures, or improvements to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants