PyLabRobot Integration
PyLabRobot (PLR) is the open-source Python library Orca Framework uses as its hardware abstraction layer. It is where the shape of a 96-well plate comes from, and where the code that drives a liquid handler lives, so the plate you name in a template and the arm that carries it are PLR objects underneath. Read this page if a labware definition you need is missing, if you are importing PLR types directly, or if you need to know which of two PLR object trees a piece of state lives in. Writing a workflow needs none of it.
PLR provides:
- Resource definitions: geometry for plates, tip racks, troughs, lids.
- Device backends: drivers for liquid handlers, plate readers, arms and other instruments.
- Chatterbox simulators: driver-level simulators that exercise the real driver code paths without touching hardware.
PLR is a dependency of cheshire-drivers, not of orca. orca imports pylabrobot zero times; it holds cheshire-drivers' adapters and interfaces (IPlate, ITipRack, ITrough) instead. Everything below goes through cheshire_drivers.
The two entrances
PLR is used as two different things, with the same class names. This is the most confusing part of the integration, so here it is plainly (see docs/plr-two-entrances.md in the orca repository).
- PLR as a definitions library, inside the Orca labware instance.
PlateTemplate.create_instance()resolves a factory and builds a real PLRPlate. That object lives inside the OrcaPlateInstance. - PLR as a driver, inside the liquid handler. The PLR liquid-handler wrapper builds a separate PLR
Deckand its ownPlateobjects from the deck layout config.
So one labware instance can correspond to two distinct PLR Plate objects: one in the Orca instance, one in the driver's deck. They are linked only by name, never by object identity, and the linking name is the instance name (<template>-<8 hex>), not the template name. The driver resolves a command against its own deck and never sees the Orca instance's plate.
State crosses that name link by push and pull. configure_deck seeds initial well volumes onto the driver's plate. The driver does its own per-op arithmetic on it. Every op returns a labware state, which Orca projects onto its own bookkeeping when trust_driver_state is on and the driver advertises provides_state. Placement and pick push single arrivals and departures. A full reconcile pushes the whole ledger-side occupancy, and is for a driver world whose occupancy is unknown (a fresh deck, a rebuilt session, a boot), never for a change concerning one labware.
The catalog is the integration point
Orca templates do not take PLR objects. A PlateTemplate names a catalog labware_type; the runtime resolves it against the labware catalog, and PLR sits one layer further down, backing the catalog rows:
PLR definition -> catalog row (keyed by labware_type) -> template (names it) -> instance
| Orca concept | What backs it |
|---|---|
PlateTemplate(name, labware_type=...) | A catalog row whose geometry came from a PLR Plate definition. |
TipRackTemplate(name, labware_type=..., with_tips=...) | A catalog row backed by a PLR TipRack definition. |
TroughTemplate(name, labware_type=...) | A catalog row backed by a PLR Trough definition. |
LiquidHandler deck modeling | A PLR-backed deck tree inside the driver. |
| Pure simulation of a liquid handler | PLR Chatterbox backends. The other device kinds run cheshire-drivers' own sim drivers, which are not PLR. |
See Labware for the three-layer model.
How a labware_type becomes an object
cheshire_drivers.plr.plates holds the curated, pre-wrapped factories: each one wraps a raw PLR factory so it returns IPlate / ITipRack / ITrough rather than a concrete PLR type. resolve_plr_factory looks the labware_type up there by name.
from orca.sdk.labware import PlateTemplate
sample_plate = PlateTemplate("sample_plate", labware_type="Cor_96_wellplate_360ul_Fb")
Two failure modes to recognise:
- A
labware_typethe seed catalog names butcheshire_drivers.plr.platesdoes not expose raisesRuntimeError. Either the wrapper is missing or the seed is wrong. - An operator-custom catalog row, added through the labware catalog with geometry but no PLR class behind it, cannot be built through the template path at all.
resolve_plr_factoryraisesNotImplementedError, and aregisternaming it raisesLabwareHasNoModelbefore anything is written. A catalog row is not on its own evidence you can place one.
Adding a labware definition PLR does not have
- Contribute upstream: submit a PR to PyLabRobot. Best long term.
- Wrap it locally:
cheshire_drivers.plr.labwareexposesplr_plate_factory,plr_tip_rack_factoryandplr_trough_factoryas the escape hatch. Wrap a PLR factory with one of them and add it tocheshire_drivers.plr.platesunder the name the catalog uses, soresolve_plr_factorycan find it.
Browse pylabrobot/resources/ to see what PLR already ships before adding your own.
Import PLR through cheshire-drivers
Anything that needs PLR types or backends imports them from cheshire_drivers.plr, which keeps PLR a transitive dependency:
| What you need | Import from cheshire_drivers.plr |
|---|---|
| PLR concrete types | Plate, TipRack, Trough, Resource |
| A backend class by name | get_plr_backend_class |
| Liquid handler drivers | ChatterboxLiquidHandlerDriver, ChatterboxLiquidHandlerWithProtocolDriver, OT2LiquidHandlerDriver, STARLiquidHandlerDriver, FlexLiquidHandlerDriver |
| Arm drivers and wrappers | PreciseFlexTransporterDriver, PLRTransporterBackendWrapper, PLRArmBackend, transporter_driver, convert_cartesian_to_plr_coord, convert_joint_to_plr_dict |
| Other Chatterbox sims | ChatterboxShakerDriver, ChatterboxCentrifugeDriver, ChatterboxThermocyclerDriver, ChatterboxHeatingShakerDriver, ChatterboxReaderDriver, ChatterboxStorageDriver |
| Real device drivers | A4SSealerDriver, VSpinCentrifugeDriver |
| Labware adapters | PLRPlateAdapter, PLRWellAdapter, PLRTipRackAdapter, PLRTipSpotAdapter, PLRTroughAdapter |
The arm symbols in particular moved here. PLRArmBackend is PLR's PreciseFlex re-exported. The SCARA pick and place is composed in cheshire-drivers out of the arm driver's own primitives rather than using the controller's built-in routine, so the route and the geometry live next to the parameters that describe them. See Move and pipetting parameters.
Liquid handler drivers
You do not pass a driver to LiquidHandler. The device class calls resolve_drivers(...) in its constructor, and the device factory context in scope picks the pair. With no factory bound, a deck-modeling LiquidHandler falls back to ChatterboxLiquidHandlerWithProtocolDriver, which owns its own deck tree. The maintained examples bind a small local factory so the handler falls back to ChatterboxLiquidHandlerDriver instead, which tracks tips, well volumes and deck placement. When the same topology is deployed behind a gateway, the remote factory is already bound and the local default is bypassed. See Sim hierarchy.
Calling liquid-handler methods inside actions
Once you have narrowed ctx.device(ILiquidHandler), call the interface methods, which wrap PLR underneath:
from cheshire_drivers.pipetting import MixParams, PipettingProfile
@orca.action(device=flex_1, inputs=[plate, tips])
async def transfer(ctx: ActionContext):
lh = ctx.device(ILiquidHandler)
plate = ctx.plate("plate")
column = [plate.well(f"{row}1") for row in "ABCDEFGH"]
await lh.pick_up_tips(await ctx.next_tips("tips", 8))
await lh.aspirate(column, [50.0] * 8, flow_rates=[100.0] * 8)
await lh.dispense(column, [50.0] * 8, flow_rates=[150.0] * 8,
technique=PipettingProfile(
mix=MixParams(volume=40.0, repetitions=3, flow_rate=150.0)))
await lh.discard_tips()
ctx.plate(name), ctx.tip_rack(name) and ctx.trough(name) return the underlying resource behind the IPlate / ITipRack / ITrough interfaces, so you can address wells and tip spots. ctx.next_tips(name, count) picks the next tips that are actually there. You pass those to the ILiquidHandler interface methods, never to a PLR backend directly.
See also
- Devices: Orca's device wrappers and how driver capabilities are decided.
- Labware: templates, catalog rows and instance identity.
- Sim hierarchy: which driver you get in which run mode.
- Move parameters: the grip and approach numbers the arm drivers read.
- PyLabRobot docs: full PLR reference.