Skip to content

fix: symmetric calibration for wrist_roll motors in SO-101#3320

Closed
cwzhong627-source wants to merge 2 commits intohuggingface:mainfrom
cwzhong627-source:fix/wrist-roll-calibration
Closed

fix: symmetric calibration for wrist_roll motors in SO-101#3320
cwzhong627-source wants to merge 2 commits intohuggingface:mainfrom
cwzhong627-source:fix/wrist-roll-calibration

Conversation

@cwzhong627-source
Copy link
Copy Markdown

@cwzhong627-source cwzhong627-source commented Apr 8, 2026

fix(robots): ensure symmetric wrist_roll calibration for SO-101

Type / Scope

  • Type: Bug
  • Scope: lerobot/robots/so_follower, lerobot/teleoperators/so_leader

Summary / Motivation

Current calibration for wrist_roll (full-turn motors) in SO-101 robots relies on a hardcoded range or an asymmetric logic, which causes the software "zero" point to deviate from the physical orientation set by the user during the calibration process.

This PR implements a symmetric range strategy. By calculating a safe_delta centered at the current physical position, we ensure that the software center ((min + max) / 2) perfectly aligns with the user-defined physical zero. This fixes the "offset wrist" issue and ensures consistent range of motion in both directions.

Related issues

Fixes / Closes: #3193

What changed

  • Modified so_follower.py and so_leader.py to read current_pos of the full_turn_motor before recording other joints.
  • mplemented safe_delta = min(current_pos, 4095 - current_pos, 2048) to calculate strictly symmetric range_min and range_max.
  • Breaking changes: None. This improves calibration accuracy for existing SO-101 users.

How was this tested (or how to run locally)

  • Hardware: Tested on a physical SO-101 follower arm with STS3215 motors.
  • Manual Verification:
    1. Ran lerobot-calibrate and set the wrist to a specific orientation.
    2. Checked the generated calibration.json.
    3. Confirmed that range_min and range_max are symmetric around the set position (e.g., current_pos=2028 resulted in min=0, max=4056).
    4. Verified that the wrist no longer "jumps" or offsets when teleoperation starts.

Bash

# To reproduce:
lerobot-calibrate --robot.type=so101_follower --robot.id=test_arm

Checklist (required before merge)

  • Linting/formatting run (pre-commit run -a) - Note: If you haven't run this, you can check if CI passes.
  • All tests pass locally (pytest)
  • Documentation updated - N/A
  • CI is green

Reviewer notes

The core logic uses safe_delta to prevent the range from exceeding the physical limits (0-4095) while maintaining symmetry. This is crucial because motors_bus.py uses the arithmetic mean of the range to define the normalized zero point.

@github-actions github-actions bot added the robots Issues concerning robots HW interfaces label Apr 8, 2026
@cwzhong627-source cwzhong627-source marked this pull request as draft April 8, 2026 13:27
@cwzhong627-source cwzhong627-source marked this pull request as ready for review April 8, 2026 13:27
@Maximellerbach Maximellerbach self-requested a review April 10, 2026 10:06
@Maximellerbach
Copy link
Copy Markdown
Member

Hey, I'm currently looking into this PR.
After calibrating both the leader and follower, I found during teleop the range of motion of the wrist to be limited to +90 / -90 so that means we are loosing ~180 degrees on this joint.
Is this expected ? This seems to me to be a pretty big change

@cwzhong627-source
Copy link
Copy Markdown
Author

cwzhong627-source commented Apr 10, 2026

Hey, I'm currently looking into this PR. After calibrating both the leader and follower, I found during teleop the range of motion of the wrist to be limited to +90 / -90 so that means we are loosing ~180 degrees on this joint. Is this expected ? This seems to me to be a pretty big change

hi@Maximellerbach,

I've realized the issue and will close this PR. You are right—the 180° loss is a significant regression caused by the way I forced symmetry.

Here is the "Math" behind the problem:
My current code uses: safe_delta = min(current_pos, 4095 - current_pos, 2048).

Example:

If a user calibrates the wrist at 1024 (90° off the physical center 2048):

safe_delta becomes 1024.

The range is restricted to [0, 2048], which is only 180°.

The remaining 180° on the other side is "sacrificed" just to maintain a symmetric software zero-point.

Why wrist_roll is unique:
I now understand that wrist_roll is fundamentally different from other joints. Other joints are linear (they have physical stops), but wrist_roll is circular (continuous 360°).

For linear joints, shrinking the range to find the center works fine.

For circular joints, it creates a "topological break" where the robot suddenly lose half its motion.

Next Step:
I'm closing this PR now. I have two better solutions in mind and would love to hear which one fits the LeRobot philosophy:

Option A (Automatic Offset): Keep the full [0, 4095] range and use homing_offset to align the zero-point in software. This is the most user-friendly.

Option B (Validation): Simply warn/block the user during calibration if the wrist is too far from the physical center, forcing a manual mechanical adjustment.

I'm leaning toward Option A. Let me know what you think, and I'll follow up with a fresh PR!

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

Labels

robots Issues concerning robots HW interfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

so101 wrist_roll calibration broken in new version: leader/follower zero points don't correspond

2 participants