Repository navigation
fix: make the homing tick the kinematic zero, not the recorded-range midpoint - #49
Merged
Merged
Conversation
The calibration workflow's setHomingPosition already writes each servo's position_offset so the pose the user homed at reads back as raw tick 2047. But NormModeDegrees defined 0 degrees as (RangeMin+RangeMax)/2 -- the midpoint of the recorded travel range -- which drifts off that homed pose whenever the recorded range is asymmetric around 2047. Measured on hardware as ~6.9 degrees on an elbow joint and ~13mm at the gripper TCP. Introduce servo.HomingTick (2047) as the one named constant for this value, used as the center in NormModeDegrees's Normalize/Denormalize, and have workflow.go's targetCenter reference it so the two can't drift apart again. RangeMin/RangeMax keep clamping Denormalize's output; only the zero moved. NormModeRange100 (gripper), NormModeRangeM100, and NormModeRaw are untouched. components/arm/motion.go's calculateJointLimits independently re-derives degrees from RangeMin/RangeMax using the old midpoint-centered math (not MotorCalibration.Normalize), so the arm's joint limits still center on the range midpoint rather than HomingTick -- left alone as out of scope for this change. DefaultSO101FullCalibration's arm-joint 500/3500 defaults (midpoint 2000) are similarly untouched; they now imply a zero offset by 47 ticks from a real calibration file's homing tick until a machine is actually calibrated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qv1ZbWg7v4RH7HcR6xUgAi
…ing-tick zero calculateJointLimits normalized RangeMin/RangeMax against the range midpoint, which made every joint's limits exactly [-pi, pi] regardless of calibration: (min - mid) / halfRange is always -1. It also used a different zero than the joint positions now do. Run both ends of the recorded range through MotorCalibration.Normalize instead, so clampPositions and manual mode bound the arm to its real recorded travel in the same frame the positions report in. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qv1ZbWg7v4RH7HcR6xUgAi
…e, document the zero Review follow-ups. jointLimitsFor takes the full calibration and the servo id list, so a partial servo_ids config indexes calibration by id instead of by position (the old code panicked on fewer than five). clampPositions emits one warning per move naming every clamped joint, and none on the streamed path, where teleop would otherwise log at 100 Hz. Denormalize rounds in degrees mode like its siblings. Docs state where 0 degrees is and the angle shift an existing calibration file sees on upgrade. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qv1ZbWg7v4RH7HcR6xUgAi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With the gripper physically touching the table, the
follower-gripperTCP frame read z ≈ −13 mm at a mid pose and −25 mm at full stretch. Frame config, served URDF, TCP constant, and viewer all checked out; the error was angular and grew with reach.Root cause: two different zeros.
set_homingcenters each servo'sposition_offsetso the homed pose reads tick 2047, butNormModeDegreesdefined 0° as(RangeMin+RangeMax)/2of the later-recorded travel range. The printed stops aren't symmetric about the homed pose, so the kinematic zero drifted off it. On this arm the gap was 6.9° on the elbow, 2.3° shoulder-lift, 1.8° wrist-flex, 9.9° pan.Hardware measurement at all-zeros: shoulder-lift and elbow horn heights matched the model (119 / 232 mm); the wrist horn was 13 mm high (250 vs 237). Only the forearm sits between them, so the elbow zero carried the error.
Fix
internal/servo: newHomingTick = 2047; the degrees branch ofNormalize/Denormalizecenters on it. Range min/max keep their clamping role. Other norm modes (gripper) untouched.Denormalizenow rounds like its siblings. The calibration workflow's homing target references the same constant so the two can't drift again.components/arm:calculateJointLimitswas a latent no-op ((min−mid)/halfRangeis identically ±1, so every joint got ±180°) and panicked on a partialservo_ids. Replaced byjointLimitsFor, which runs both range ends through the sameNormalizeand indexes calibration by servo id.clampPositionsnow genuinely binds: it warns once per move naming every clamped joint, and stays silent on the streamed (teleop) path.docs/arm.md"Where 0° is",docs/calibration.mdnote that the homing pose is the arm's zero, CLAUDE.md gotcha.Verified on hardware
Deployed to the live follower arm with its existing calibration file: TCP reads z = 0.3 mm with the tip on the table, approach axis plumb, at both the mid pose and full stretch.
Behaviour changes to know about
(2047 − midpoint) × 360/4095degrees. Recorded trajectories and saved joint poses from before this change replay shifted by that amount. The gripper is unaffected.save_calibration.Why LeRobot does the same thing
LeRobot's
MotorNormMode.DEGREESuses the identical midpoint formula. Investigated: the mid-range zero was a deliberate 2025 redesign decision (drop ±180° wrap-around), but the DEGREES formula landed in the same commit as a thin rescaling of the percentage mode, undocumented in its PR, and its kinematics apply no compensating offset. Open issues #4442 and #3587 ask for exactly this. A maintainer's own description ("the position you start calibration with will be the 0") matches this PR, not their formula.🤖 Generated with Claude Code