Add FK-based STARBackend.iswap_request_pose() + iswap_request_joint_state()#1041
Open
BioCam wants to merge 2 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a forward-kinematics-based iSWAP pose accessor on STARBackend as a more reliable alternative to the legacy request_iswap_position() (C0 QG) firmware call, which empirically returns rotation-drive position rather than the grip center in most states. The new path queries each joint encoder and computes the grip center via a 2-link SCARA FK using per-machine EEPROM-calibrated link lengths and STRAIGHT angle. The legacy method is left untouched.
Changes:
- Introduce
STARBackend.iSWAPAxisIntEnum (X/Y/Z/ROTATION/WRIST/GRIPPER +is_in_kinematic_chain/is_revolutepredicates), and cache_iswap_link_1_length/_iswap_link_2_lengthfrom EEPROM inset_up_iswap(). - Add three entry points: pure static
_iswap_fk(...), asynciswap_request_joint_state(), and asynciswap_request_pose()returningCartesianCoords. - Add unit tests (
TestiSWAPForwardKinematics,TestiSWAPAxisPredicates,TestiSWAPRequestJointState,TestiSWAPRequestPose) covering canonical FK configurations, axis predicates, joint composition, and the full mocked I/O→FK path.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | Adds nested iSWAPAxis enum, EEPROM link-length caching at setup, static _iswap_fk SCARA forward-kinematics, and async iswap_request_joint_state/iswap_request_pose methods. |
| pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py | Adds four TestiSWAP* test classes covering FK math at canonical configurations, axis predicates, and mocked end-to-end joint/pose retrieval. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Adds a forward-kinematics-based iSWAP grip-center accessor as an alternative to the legacy
request_iswap_position()(C0 QG), which is empirically broken in 9 of 13 tested machine states. The new path queries each joint encoder directly and computes the grip center from per-machine EEPROM calibration — no firmware-state dependency.Three new entry points on
STARBackend:The legacy
request_iswap_position()is untouched — no deprecation, no behavior change. Callers can migrate at their own pace.Why
request_iswap_position()sendsC0 QG, which on our test STAR (iSWAP-equipped) returns the rotation drive position rather than the grip center in most states. Confirmed on hardware:request_iswap_positioniswap_request_pose.locationFailure modes: C0 QG returns sub-mm-correct values only when both W and T joints are at named EEPROM stops and the kinematic configuration is one of a few specific arrangements. Off-stop W angles, parked states, and certain W+T combinations all degrade silently to "return rotation drive position." Worse, the failures are deterministic (repeatable across 5 consecutive calls in Phase 8) — so any "is the value stable?" check passes while the value itself is 195+ mm wrong.
The "0.5 mm" residual in the agreement scenarios is also informative: even when C0 QG works, it's using nominal Hamilton defaults (L=138, ±45°) rather than the EEPROM-stored per-machine values (L1=137.8, L2=137.7, STRAIGHT=−45.01°, LEFT=+45.94° on this machine). The FK is per-machine calibrated; C0 QG is not.
How
iswap_request_poseis two operations:iswap_request_joint_state()composes the six existing per-axis read methods (iswap_rotation_drive_request_x/y/z/angle,iswap_wrist_drive_request_angle,iswap_gripper_request_width) into aDict[iSWAPAxis, float]._iswap_fk(joints, l1, l2, wrist_straight)is a static, pure function that runs a 2-link SCARA forward kinematics:Sign convention follows right-hand rule about +z (CCW positive looking down) — same phrasing PF400 and KX2 modules use on v1b1.
L₁,L₂, and the wrist STRAIGHT calibration are read from EEPROM once duringsetup()and cached on the backend; subsequentiswap_request_posecalls don't re-query EEPROM.API surface added
STARBackend.iSWAPAxis(IntEnum)is_in_kinematic_chainandis_revolutepropertiesSTARBackend._iswap_fk(...)STARBackend.iswap_request_joint_state()Dict[int, float]STARBackend.iswap_request_pose()pylabrobot.arms.standard.CartesianCoordsSTARBackend._iswap_link_1_length,_iswap_link_2_lengthCartesianCoordsalready exists on main (pylabrobot/arms/standard.py). Setup-time caching is added inside the existingset_up_iswap()block.Testing
Unit tests (
STAR_tests.py::TestiSWAP*, 7 tests):TestiSWAPForwardKinematics(4): hand-computed expected poses vs_iswap_fkoutput for FRONT, LEFT, FRONT+RIGHT, REVERSE configurations.TestiSWAPAxisPredicates(1): the kinematic-chain partition (X/Y/Z/ROTATION/WRIST in, GRIPPER out).TestiSWAPRequestJointState(1): mocked per-axis methods → verified dict shape.TestiSWAPRequestPose(1): full mocked E2E path joints → FK → pose, including EEPROM-STRAIGHT drift propagation.No tautological tests (e.g. "raises when condition X" mirroring
if condition X: raise). Every test independently computes its expected output.Empirical (real STAR, log + notebook attached):
_dev/test_iswap_fk_pose.ipynband_dev/logs/2026-05-15_15-58-08_iswap_fk_testing.login the cb-protocol-library sister repo.Portability to v1b1
Drop-in for v1b1's arm-capability framework:
request_gripper_pose() -> CartesianPose(from_BaseArmBackend)iswap_request_pose() -> CartesianCoordsCartesianCoords→CartesianPose)request_joint_position() -> JointPose=Dict[int,float](fromHasJoints)iswap_request_joint_state() -> Dict[int, float]Axis(IntEnum)(cf. KX2paa/kx2/config.py::Axis)STARBackend.iSWAPAxisAxisiniswap/subpackagefk(joints, config) -> CartesianPose(cf. PF400brooks/kinematics.py, KX2paa/kx2/kinematics.py)STARBackend._iswap_fk(positional params)iswap/kinematics.py; bundle params intoiSWAPConfigcapabilities/arms/kinematics.py:gripper_speed,sample_gripper_speed_along_trajectory, ...)Callable[[Dict[K,float]], Coordinate]_iswap_fkalready matches that signature — drop-inNo FK math change at port time. No test math change. Mostly find-and-replace.
Follow-ups (separate PRs)
iSWAPInformationdataclass — bundles the 6+ scattered_iswap_*cached attrs (link lengths, predefined increments, X offset, parking Y, fw version) into one dataclass, parallel to the existingHead96Information. ~150–250 LOC; enablesSTAR_chatterbox.pyto populate a single_DEFAULT_ISWAP_INFORMATIONinstead of 6+ assignments.iswap_request_pose()working against the chatterbox.Test plan / checklist
_iswap_fkagainst hand-computed expected values at 4 canonical configurationsrotation.x = y = 0)make format,make lint,make typecheck, fullSTAR_tests.py(76/76) all passFiles
pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py(+150) —iSWAPAxisenum, cached attrs, setup population,_iswap_fk, two new async methods.pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py(+152) —TestiSWAP*(4 classes, 7 tests).