Skip to content

STARBackend: bound X-arm and channel moves to firmware-resolved x_range#1181

Merged
BioCam merged 2 commits into
PyLabRobot:mainfrom
BioCam:pr2-x-range
Jul 22, 2026
Merged

STARBackend: bound X-arm and channel moves to firmware-resolved x_range#1181
BioCam merged 2 commits into
PyLabRobot:mainfrom
BioCam:pr2-x-range

Conversation

@BioCam

@BioCam BioCam commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

experimental_x_arm_move and the PIP path in move_channel_x bounded the X target against the fixed literals 90.0 <= x <= 1350.0, identical for every machine. Now that #1167 resolves each installed X-drive's x_range at setup, a move can be bounded against the arm's actual reachable travel instead of a one-size constant - which genuinely differs by machine, since a STAR reaches farther in X than a STARLet.

This is the first PR of the epic "Safety Systems: Tracking STAR Capabilities" building on #1167.

  • Adds _check_x_arm_reachable(x, position), which reads the resolved x_range off extended_conf.left_x_drive / right_x_drive and raises ValueError when x falls outside it, or when no arm is installed on that rail.
  • experimental_x_arm_move and the PIP path in move_channel_x now call it, replacing the 90.0 <= x <= 1350.0 literal.
  • STARChatterboxBackend derives its simulated X reach from the deck width rather than the last rail, so a simulated move to the right of the deck is not spuriously rejected. The hardware backend is unaffected - it reads the true drive travel from the RU query (firmware-fed on device, estimated only in simulation).

The bound is the drive travel range (x_range), not the wider workspace_range that #1167 also resolves.
x is the arm's position at its reference point (its center for a dual-rail arm, the right edge for a single-rail arm), and the channels sit off that reference point, so the reachable workspace extends past where the reference point itself can travel.
Scope: this bounds the arm-level (experimental_x_arm_move) and single-channel (move_channel_x) X primitives.
Validating each per-channel x on the high-level pipetting ops (pick_up_tips / drop_tips / aspirate / dispense) against x_range is a follow-up - it belongs in the shared _ops_to_fw_positions chokepoint and should report every non-compliant channel at once, so it is sequenced with the per-channel tracking work rather than bolted on here (marked with a TODO at that chokepoint).

Behaviour: this replaces the fixed 90.0 <= x <= 1350.0 literal with the machine's resolved x_range, which is marginally tighter at both ends (dual-rail left resolves to roughly [95.0, 1340]).
So it is a deliberate tightening, not purely additive: an x in those margins that the old constant accepted now raises rather than reaching the wire, because the fixed bound was never the real per-machine limit.
An out-of-range move raises a clear ValueError client-side, naming the arm and its range, instead of relying on the fixed literal, and a target valid on a STAR but past a STARLet's reach is now correctly rejected on the STARLet.

Tests: TestXArmRangeEnforcement (a target past either end of x_range is rejected and no command is sent, while an in-range target reaches the wire) and TestXArmReachByDeck (a STAR resolves a larger reach than a STARLet, and an X past the STARLet's right edge is accepted on a STAR but rejected on a STARLet). ruff format, ruff check --select I, ruff check, and mypy pylabrobot --check-untyped-defs are clean; the STAR suite passes.

@BioCam BioCam changed the title STARBackend: bound X-arm and channel moves to the resolved x_range STARBackend: bound X-arm and channel moves to firmware-resolved x_range Jul 22, 2026
@BioCam
BioCam marked this pull request as ready for review July 22, 2026 09:30
@BioCam
BioCam force-pushed the pr2-x-range branch 8 times, most recently from 9d6533d to 13171f3 Compare July 22, 2026 11:16
Enforce the per-arm drive range from XArmInformation on the two X-move entry
points. experimental_x_arm_move and move_channel_x now call _check_x_arm_reachable,
which reads the x_arm_information property (raising if setup has not resolved the
ranges yet) and rejects a target outside the arm's x_range with a clear ValueError
before any wire command is sent. Replaces the hardcoded 90-1350mm literal in
experimental_x_arm_move, and drops the STARChatterboxBackend move_channel_x stub so
the bounds check runs in simulation too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The simulated X reach used the last rail's position, which stops short of the deck's right edge, so once moves are bounded to the resolved x_range a simulated move to the right of the deck was spuriously rejected. Derive it from the deck width instead. The hardware backend is unaffected: it reads the true drive travel from the RU query.
@BioCam
BioCam merged commit 1be17e2 into PyLabRobot:main Jul 22, 2026
21 checks passed
@BioCam
BioCam deleted the pr2-x-range branch July 22, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant