# Outbox — feetech_calib

## 2026-07-11 — Bootstrap + first deliverable (servo-zeroing fixture)

### Inherited servo knowledge (mined from cad's cloned context)
- **Servo family:** Feetech STS3215, 12-bit absolute encoder, 4096 ticks/rev, 2048 = center.
  Scale is a HARDWARE CONSTANT: 651.9 ticks/rad. No hardware direction-reversal register — direction
  is a software `signs` array (starting guess `[-1,1,1,1,1,1]` for the 6-DOF).
- **Comm:** `scservo_sdk` (pip `feetech-servo-sdk`) + local `scservo_patch.py` monkey-patch (auto-injects
  portHandler). Default `/dev/tty.usbserial-0001` @ 115200. `COMM_RX_TIMEOUT=-6` = wrong baud / not connected.
- **Robots needing calibration:** smith300 6-servo arm (assembled 2026-07-10, joints output_0..5, all
  revolute, axis local Y, zero = URDF q=0 of two_servo_beauty.urdf); v2 gripper ×2 (jaw revolute, zero =
  jaws closed; wand's servo runs UNPOWERED as a back-drive encoder). IDs currently ad-hoc → my scheme.

### Controller repo (rescued from the Mac before it closed)
`our_feetech_controller` → now at **VPS `/data/cameron/our_feetech_controller`** (git repo, committed;
Cameron confirmed code lives here now — no lab mirror). Scripts:
- `which_motors.py` (bus scan), `set_motor_id.py` (EEPROM ID reassign, one servo on bus),
  `calibrate_motors.py` (INST_OFSCAL → current pos becomes 2048), `feetech_read_motor.py` /
  `read_and_vis_motors.py` (live diagnostics: pos/speed/load/volt/temp/current), `release_torques.py`,
  `write_motors_from_goals.py` (MuJoCo-in-the-loop position control w/ temp + tick-range safety rails),
  `diag_motor_wobble.py`, double-servo read/write tests.

### Calibration approach (decided with Cameron)
**Single-point zero, NOT min/max endstop mapping** — the encoder scale is a known hardware constant, so
only the offset + direction sign are unknown per joint; one good reference pose fully determines it.
Sim min/max = SAFETY tick-clamps only (URDF currently has placeholder ±1.74 rad on all 6). Reference pose
made repeatable via a printed fixture (below), zeroed with INST_OFSCAL.

### DELIVERED: `servo_zero_fixture.stl` (universal STS3215 q=0 zeroing jig)
Built it myself (Cameron's call — calibration-scoped, one-shot). Silhouette pocket of the servo+yoke at
q=0; the assembly only seats when the limp yoke is at zero (angular key ≈ ±0.5°). One fixture zeroes all 6
servos + absorbs horn re-clocking. Verified: seats at 0.00 cm³ interference, rejects ±8°/±15°. 56.6 cm³,
75×57×30 mm. Source of record: `our_feetech_controller/fixtures/`. Generator + outputs also in the cad tree
(reads cad's meshes). Shipped STL to Mac `stl_transfers/calib_fixtures/`. No URDF/joint change (checked a
suspected stale offset — it's fine: yoke visual origin +0.0255 cancels joint origin −0.0255, gen_two_servo.py:40).
Viewer URLs (dir=/home/cameronsmith/cad_recovery_mnt):
- fixture:  `...&file=servo_zero_fixture.urdf`
- fit demo: `...&file=servo_zero_fixture_demo.urdf`
- install/explode slider: `...&file=servo_zero_fixture_install.urdf`

### Next (pending Cameron)
- Build `calibrate_arm.py`: ping 6 → seat each in fixture → INST_OFSCAL → determine signs → write
  `arm_calib.json` (per-joint offset/sign/soft-limits) that `write_motors_from_goals.py` reads.
- Assign the arm's servo IDs (scheme TBD — likely output_0..5 → IDs 1..6, gripper jaw its own ID).
