PyLabRobot Integration
Orca Framework uses PyLabRobot (PLR) as its hardware abstraction layer. PLR provides:
- Resource definitions — geometry for plates, tip racks, troughs, lids.
- Device backends — drivers for liquid handlers, plate readers, and other instruments.
- Chatterbox simulators — driver-level simulators that exercise the real driver code paths without touching hardware.
Orca wraps PLR resources and drivers in a workflow-aware layer so they can participate in scheduling, labware tracking, and event flow.
The catalog is the integration point
Orca templates do not take PLR objects. A PlateTemplate names a catalog slug string (labware_type); the runtime resolves that slug against the labware catalog at build time, and PLR sits one layer further down, backing the catalog rows. So the flow is:
PLR definition → catalog row (keyed by slug) → template (names the slug) → instance
| Orca concept | What backs it |
|---|---|
PlateTemplate(name, "slug") | A catalog row whose geometry came from a PLR Plate definition. |
TipRackTemplate(name, "slug", with_tips=...) | A catalog row backed by a PLR TipRack definition. |
TroughTemplate(name, "slug") | A catalog row backed by a PLR Trough definition. |
LiquidHandler deck modeling | PLR-backed deck tree; the sim path uses ChatterboxLiquidHandlerWithProtocolDriver. |
| Simulation | PLR Chatterbox backends. |
See Labware for the three-layer model (catalog → template → instance).
How PLR definitions reach the catalog
cheshire_drivers.plr exposes factory callables (e.g. plr_plate_factory, plr_tip_rack_factory, plr_trough_factory, defined in cheshire_drivers.plr.labware) that wrap PLR resource definitions. These seed catalog rows under string slugs. Once a row exists, a template binds to it by slug:
from orca.sdk.labware import PlateTemplate
sample_plate = PlateTemplate("sample_plate", "Cor_96_wellplate_360ul_Fb")
PLR ships definitions for most common labware. Browse pylabrobot/resources/ to see what is available before adding your own.
Adding a labware definition PLR doesn't have
If your lab uses a plate PLR doesn't ship a definition for, you have two options:
- Contribute upstream — submit a PR to PyLabRobot. Best long-term.
- Define locally — register a custom catalog row (via the
plr_plate_factory/catalog custom-row path) and reference it by slug from your template. You never pass a PLR object intoPlateTemplate.
TODO: walkthrough of registering a custom catalog row from PLR primitives.
Liquid handler drivers
You do not pass a driver to LiquidHandler. The device class calls resolve_drivers(...) in its constructor; the device factory context in scope picks the driver, selected by run mode. The deck-modeling sim used by LiquidHandler is ChatterboxLiquidHandlerWithProtocolDriver (the handler owns its deck tree), not a driver you construct and pass in.
TODO: full factory-context walkthrough covering binding a real STAR/Vantage backend vs the Chatterbox sim.
Calling liquid-handler methods inside actions
Once you've narrowed ctx.device(ILiquidHandler), you call the interface methods, which wrap PLR underneath:
@orca.action(device=liquid_handler, inputs=[plate, tips])
async def transfer(ctx: ActionContext):
lh = ctx.device(ILiquidHandler)
rack = ctx.tip_rack("tips")
plate = ctx.plate("plate")
await lh.pick_up_tips([rack.tip_spot("A1")])
await lh.aspirate([plate.well("A1")], [50.0], flow_rates=[10.0])
await lh.dispense([plate.well("B1")], [50.0], flow_rates=[15.0])
await lh.drop_tips([rack.tip_spot("A1")])
The ctx.plate(name), ctx.tip_rack(name), and ctx.trough(name) accessors return the underlying PLR resource so you can address wells and tip spots (plate.well("A1"), rack.tip_spot("A1")). You pass those addresses to the ILiquidHandler interface methods (aspirate, dispense, pick_up_tips, ...), which wrap PLR. You call the interface, not the PLR backend directly.
Where to go next
- Devices — Orca's device wrappers around PLR backends.
- Labware — defining labware templates that bind to catalog slugs.
- PyLabRobot docs — full PLR reference.