--l / --r | Arm side (required) |
--channel IFACE | SocketCAN interface override for setups without the Axol hub adapter |
--joint JOINT | shoulder_1, shoulder_2, shoulder_3, elbow, wrist_1, wrist_2, wrist_3 (required) |
--kp KP [KP ...] | Proportional gain candidate(s) (default: the joint’s configured kp) |
--kd KD [KD ...] | Derivative gain candidate(s) (default: the joint’s configured kd) |
--ff {full,gravity,friction,none} | Feedforward during the test (default: full — matches production) |
--host-kd KD | Host-side damping (Nm·s/rad) via t_ff — matches the production kd_host term (default: the joint’s configured kd_host) |
--host-kd-hz HZ | Band-pass centre (Hz) confining the host damping to the joint’s resonance band — matches the production kd_host_hz; aim it at the ring frequency the step test reports (default: the joint’s configured value, else the shared 3.2 Hz) |
--host-kd-q Q | Quality factor of that band-pass (bandwidth = centre/q) — matches the production kd_host_q (default: the joint’s configured value, else the shared 0.8). At 0.8 the band is an octave wide either side and, with a low centre, reaches into the sub-1.5 Hz intentional-motion band where the damping drags the final approach (the step never settles). With the centre on a measured ring, q 2–3 damps the ring only |
--mode {sine,step} | sine = sinusoidal tracking (default); step = step response |
--amp DEG | Motion amplitude in degrees (default: 10°, clamped to joint headroom) |
--center DEG | Start/centre angle of the probe in joint-frame degrees (0 = rest). Step frames the step around it (direction picks the side with more headroom); sine centres the wave there. Gains that look fine hanging at rest can misbehave under gravity load, so probe 45, -45, … too. Default: step starts at the joint’s current position, sine at the joint midpoint. shoulder_2 may start at 0 but must swing outboard (the robot base is inboard); wrist_2 runs with the elbow raised to its midpoint, which clears the base through its full range. Incompatible with --pose-by-hand |
--pose JOINT=DEG | Hold another joint at this joint-frame angle during the test, e.g. --pose elbow=90 --pose wrist_1=45 (repeatable; the workbench takes them space-separated in one field). Reflected inertia and gravity load change with pose — a gain set that is stable hanging at rest can ring with the elbow extended — so probe the worst case. Posed joints ramp back to their pre-test positions afterwards. Safety: shoulder_2 may only be posed outboard, and wrist_2 may only be posed in its inboard half when the pose also bends the elbow ≥ 30° (the elbow ramps first / returns last so the gripper never crosses the base with the elbow straight) |
--freq FLOAT | [sine] Frequency in Hz (default: 1.0) |
--duration FLOAT | [sine] Duration in seconds (default: 5.0) |
--hold FLOAT | [step] Hold time per phase in seconds (default: 2.0) — also the length of the unscored settle at the center before the step, so arrival/gain-change ringing dies out before anything is measured |
--rate FLOAT | Command rate in Hz (default: 240.0 — the production control rate, so gains see the same host-damping transport delay teleop gives them). The loop runs open-throttle when CAN round trips exceed the cycle budget, so the report prints the rate actually achieved and warns when it saturates below the request |
--save | Save the best candidate’s Kp/Kd to ~/.almond/calibration.json |
--no-imu | Skip the wrist IMU (recorded by default: each candidate gets an imu shake score — 1–15 Hz displacement p2p in mm at the gripper, see tune.motion) |
--save-run | Persist each candidate’s full time series and metrics as a tuning-run artifact (~/.almond/diagnostics/tuning/) for charting and A/B comparison in the diagnostics UI |
--dump-csv [PATH] | Write per-sample (kp, kd, t, target, actual, error) rows to a CSV for offline plotting. Pass without a value to auto-name as logs/pid_<side>_<joint>_<timestamp>.csv |