feat(opentrons): Opentrons Flex liquid handler (plain-class, mount-addressed heads) - #1184
feat(opentrons): Opentrons Flex liquid handler (plain-class, mount-addressed heads)#1184vcjdeboer wants to merge 1 commit into
Conversation
6af085c to
1ae9dc6
Compare
|
Downstream report: bridged this driver into an agent-facing protocol (simulation only) I maintain the labwire bridge that wraps PyLabRobot for AI-agent control (discussed in the earlier thread about cancellation on the Flex). Since the plain-class redesign will eventually reach us, I ran an experiment against this PR to see how a protocol adapter fares in the new architecture. Everything below is observation from that exercise, in case any of it is useful mid-redesign. Nothing here needs action on my account. Setup: pinned this PR's head commit (6ee378e), drove OpentronsFlex through our protocol layer, and exercised it against a simulation of the robot-server command layer (mock HTTP for /health, /runs, /runs/{id}/commands, /instruments), which I understood to be how the PR itself was validated. No hardware involved on my side either. What ported cleanly:
Things I hit that may be worth knowing:
The driver held up well under a fairly adversarial consumer. Happy to share the simulation harness or the full compatibility notes if either is ever useful. |
|
thank you @vcjdeboer ! could you please rebase this onto main? |
6ee378e to
06971f2
Compare
06971f2 to
5166ba4
Compare
…dressed heads) Add the Opentrons Flex as a plain-class device (post-capability architecture). - OpentronsRobot(abc.ABC): shared base owning the robot-server HTTP transport (behind a swappable OpentronsTransport Protocol, with an httpx transport and an offline recording transport for dry runs), the run/command lifecycle, and instrument discovery. - OpentronsFlex(OpentronsRobot): the device. setup() discovers the mounted pipette(s) and composes a head sub-object per mount (flex.left / flex.right), or flex.head96 for the 96-channel head. stop() drops any mounted tips to the trash, homes the gantry, then cancels the run and disconnects. - FlexHead1 / FlexHead8 / FlexHead96: mount-addressed fixed heads. Each op sends ONE robot-server command anchored at the reference well; the hardware fans it out to the head's N nozzles. Tip/volume state commits to the resource tree (TipSpot.tracker / Well.tracker), only for actuated channels (None-skip) and only via a transactional stage -> wire -> verify -> commit/rollback. The Flex hardware tip-presence sensor is the authority for tip presence: pickups are verified against it (rolling back on a missed pickup) and get_mounted_tips() is reconciled against it. Aspirate/dispense default to 1 mm above the well bottom and auto-issue prepareToAspirate before the first aspirate after a pickup. - Labware is name-based: the robot owns the authoritative geometry, resolved from the Opentrons load name (ot_load_name); PLR resources carry a nominal SBS grid for tracking/addressing only. FlexDeck models slots as ResourceHolders; name-based tip-rack and plate factories. - Docs: a Head8 hello-world notebook and API reference, wired into the docs toctrees. FlexHead8 is verified on real Opentrons Flex hardware (robot-server API 8.8: setup, homing, and column tip pickup against the tip-presence sensor). FlexHead1 and FlexHead96 are implemented but not yet hardware-verified and emit a one-time warning on first use. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5166ba4 to
5f3869c
Compare
|
@Silousr thanks for the test. The PR has been reworked since the commit you pinned. It is now a mount-addressed head model (flex.left / flex.right / flex.head96, with FlexHead1 / FlexHead8 / FlexHead96), so two of your points are addressed:
The import artifact was a v1b1-base issue and clears on the current main rebase. FlexHead8 is verified on a real Flex; Head1/Head96 are coded but not yet hardware-tested. Also, the bigger reason was the abstraction: the Flex only has fixed heads (1/8/96-channel across two gantry mounts), so a flat single-channel class with a use_channels list was modelling independently-addressable channels the machine doesn't have (I think use_channels is a Hamilton STAR concept). The head model matches the real hardware, and it lets the robot stay the authority for the things it owns anyway (labware geometry via load names, tip presence via the tipDetected sensor). So this build doesn't include a reach-geometry model like the OT2RobotGeometry that @BioCam added for the OT-2 (in resources/opentrons/, though nothing wires it into a device yet I think). Worth deciding whether the Flex should use the same approach. |
Opentrons Flex liquid handler (plain-class, mount-addressed heads)
Adds the Opentrons Flex as a plain-class device, following the post-capability architecture. It drives the on-robot robot-server HTTP API (Protocol Engine:
/runs,/commands,/instruments) directly.Architecture
OpentronsRobot(abc.ABC)— shared base owning the transport, the run/command lifecycle, and instrument discovery. The transport sits behind a smallOpentronsTransportProtocol with two implementations: anhttpxtransport for real hardware and an offline recording transport for dry runs (so the whole device is testable without a robot).OpentronsFlex(OpentronsRobot)— the device.setup()discovers the mounted pipette(s) and composes a head sub-object per gantry mount (flex.left/flex.right), orflex.head96for the 96-channel head.stop()drops any mounted tips to the trash, homes the gantry, then cancels the run and disconnects.FlexHead1/FlexHead8/FlexHead96— mount-addressed fixed heads. Each operation sends one robot-server command anchored at the reference well; the fixed head fans it out to its N nozzles (a column for Head8, the whole plate for Head96, a single well for Head1). Head8 also supports single/partial pickup viaconfigureNozzleLayout.Behaviour
TipSpot.tracker/Well.tracker, only for the channels actually actuated (None-skip), and only through a transactional stage → wire → verify → commit/rollback flow — so an infeasible operation raises before any hardware motion, and a failed wire command leaves no partial state. Gated by the globaldoes_tip_tracking()/does_volume_tracking()switches.tipDetectedsensor is the ground truth for tip presence: pickups are verified against it and rolled back on a missed pickup, andget_mounted_tips()(public per-channel telemetry) is reconciled against it.prepareToAspiratebefore the first aspirate after a pickup.ot_load_name); the PLR resources carry a nominal SBS grid for tracking/addressing only.FlexDeckmodels slots asResourceHolders; name-based tip-rack and plate factories are included.Hardware status
FlexHead8is verified on real Opentrons Flex hardware (robot-server API 8.8: setup, homing, and column tip pickup confirmed against the tip-presence sensor).FlexHead1andFlexHead96are implemented but not yet hardware-verified (they need 1-channel / 96-channel pipettes) and emit a one-time warning on first use.Docs & tests
docs/user_guide/opentrons/flex/hello-world.ipynb) and an API reference page, wired into the docs toctrees.Follow-ups (not in this PR)
FlexRobotGeometrynative reach checks (per-mount / nozzle-envelope), folding a grounded operating-envelope model.FlexDeckdeserialization guard (reject labware assigned directly to the deck).OpentronsRobotbase (pending verification that the two share enough at the wire level).