feat(arm): manual (hand-guided) teach mode#27
Merged
Conversation
Guard MoveToPosition, MoveToJointPositions, MoveThroughJointPositions, and Close with exitManualLocked so any powered motion command (or arm shutdown) tears down an active manual-mode session and restores full stiffness before proceeding. GoToInputs delegates to MoveThroughJointPositions so it is covered transitively.
GetStatus serializes the arm's Status() via structpb.NewStruct, which
rejects a typed []manualJointStatus slice ("proto: invalid type"). Emit
the joints as []interface{} of map[string]interface{} instead. Adds a
regression test that runs structpb.NewStruct over the Status output.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg
…racterization Read-only diagnostic to characterize the STS3215 present_load signal (sign + magnitude at rest vs under a gentle hand push, per joint) that manual mode's admittance law depends on. Writes no goals and does not change torque; refuses to run while manual mode is active. Used to debug manual-mode runaway. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg
Bench testing showed present_load saturates on the gentlest push and doesn't encode direction, causing runaway. The servo now stays in position mode holding a goal; when backdriven past a small angular deadband the goal follows the hand, trailing by the deadband so a restoring force still holds gravity. Load becomes telemetry only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg
The manual-mode Status regression test imports structpb directly, so go mod tidy moves protobuf from the indirect to the direct require block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg
Read-only readback of p_gain/torque_limit/max_torque/torque_enable per arm servo (works while manual mode is active), plus a direct torque_limit writer with readback to test in isolation whether register 48 governs holding torque on this firmware. Used to debug manual-mode compliance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg
…gnostics Keep set_torque_limit (the register-write-with-readback tuning aid); drop the load/position sampler and register readback now that manual-mode tuning is settled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg
Manual mode now works out of the box with no manual_mode config: p_gain 8,
torque_limit 50, pos_deadband_deg 0.5 — the settings validated on the bench as
the lightest feel that still holds a fully extended arm against gravity.
Previously p_gain/torque_limit defaulted to 0 ("leave the register unchanged"),
which meant the default experience was a position-mode arm too stiff to
backdrive. A config zero is indistinguishable from omitted, so an explicit 0
inline override to enter_manual_mode is now the escape hatch for leaving the
compliance registers untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QqF98UsXVbDDXQgcWq9DWq
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.
Manual (hand-guided) "teach" mode for
devrel:so101:armAdds a live jog-by-hand mode: an operator can push the arm around by hand and it follows, then holds whatever pose it's left in — without the arm ever going limp. Entered/exited via
DoCommand, state exposed viaStatus(), tuned via amanual_modeconfig object.How it works (and how we got here)
The servos stay in position mode the whole time (so the arm always holds against gravity — never limp). Two ideas make it hand-guidable, both arrived at through bench iteration on the real hardware:
pos_deadband_deg), the goal follows the hand, trailing by the deadband so a small restoring force still holds gravity. Release → it holds where left.present_loadregister saturates on the gentlest push and doesn't encode push direction, so it ran away on hardware. Position is clean, low-noise, and in the same calibrated frame as the goal.torque_limitcompliance. Position mode at full torque is too stiff to backdrive by hand. Capping the servo'storque_limit(RAM reg 48) just above the gravity-hold load makes a hand easily overpower it while it still holds the arm's weight. Bench-validated config:torque_limit ≈ 50,pos_deadband_deg ≈ 0.5,p_gain ≈ 8gave easy guiding that still held a fully-extended arm.API
enter_manual_mode(accepts inline tuning overrides; re-entering while active re-applies the new params for live tuning),exit_manual_mode. Plusset_torque_limit— a diagnostic that writestorque_limitdirectly (with readback) to find the lowest value that still holds.Status(): returnsmode("manual"/"auto") plus live per-joint telemetry (actual_deg,goal_deg,dev_deg,load) for tuning. Emitted as structpb-native types (guarded by a serialization regression test).manual_modeobject —pos_deadband_deg,loop_hz,p_gain,torque_limit(all optional; validated).Safety
MoveToPosition,MoveToJointPositions,MoveThroughJointPositions, motion-service planning viaGoToInputs) andStop/Closeauto-exit manual mode first and re-stiffen at the current pose. Deadlock-freedom independently verified (the control loop never takes the arm mutex).p_gain/torque_limit) capture-before-write and restore fail-safe.torque_limitset below a joint's gravity-hold load makes it sag, which past the deadband re-triggers following — so it's operator-tuned (watchdev_degat rest ≈ 0), and defaults to 0 (unchanged/safe).Testing
-race.Status()wire format.🤖 Generated with Claude Code
https://claude.ai/code/session_01CuMdoGZLt6murwFTxr8nZg