Liquid handling as code
A liquid handler only moves to positions it can compute. Before any command, the SDK needs to know which labware exists, where it sits on the deck, and which device the deck belongs to. Every liquid handling script follows that order: labware, deck, device, commands. The script below walks it once against a simulated Hamilton STAR, so you need no hardware.
You need uv and access to the UniteLabs package registry (see Set up your development machine). Then create a project:
uv init first-transfer && cd first-transfer
uv add unitelabs-labware unitelabs-liquid-handling
import asyncio
from unitelabs.labware import PredefinedLiquids, Standard96Plate, StandardTrough
from unitelabs.labware.hamilton import (
PLT_CAR_L5MD_A00,
TIP_CAR_480_A00,
HamiltonTip_300,
HamiltonTipRack_300,
LiquidClass,
)
from unitelabs.liquid_handling.testing import MicrolabSTARMock
async def main():
# 1. Labware: every object the robot will touch, with a stable identifier
plate = Standard96Plate(identifier="destination_plate")
trough = StandardTrough(identifier="water_source")
trough.containers[0].add_liquid(PredefinedLiquids.WATER, 150_000) # µL
tips = HamiltonTipRack_300(filled_with=HamiltonTip_300)
# 2. Carriers hold labware, the way they sit on the real deck
tip_carrier = TIP_CAR_480_A00(identifier="tip_carrier")
plate_carrier = PLT_CAR_L5MD_A00(identifier="plate_carrier")
tip_carrier[0] = tips
plate_carrier[0] = plate
plate_carrier[1] = trough
# 3. Device: a simulated Hamilton STAR, then its deck
lh = MicrolabSTARMock()
await lh.configure()
await lh.initialize()
lh.deck.add(tip_carrier, track=7)
lh.deck.add(plate_carrier, track=1)
# 4. Commands: 100 µL from the trough into column 1, eight channels at once
water = LiquidClass.HamiltonTip_300_Water_DispenseJet_Empty()
await lh.pipettes.pick_up_tips_from(channels=range(8), rack=tips)
try:
await lh.pipettes.aspirate(
source=trough, channels=range(8), volume=100, liquid_class=water
)
await lh.pipettes.dispense(
target=plate["A1":"H1"],
channels=range(8),
volume=100,
liquid_class=water,
)
finally:
# 5. Safe state: drop the tips even if a command failed
await lh.pipettes.discard_tips(channels=range(8))
# The simulator tracked every volume
print("Trough:", trough.containers[0].volume, "µL")
print("A1:", plate["A1"].container.volume, "µL")
asyncio.run(main())
Run it with uv run transfer.py. It prints Trough: 149200 µL and A1: 100 µL: eight channels each took 100 µL from the trough and put it into column 1. The simulator tracks every volume, so it would also catch an overfilled well or an empty trough before real samples are at stake.
Three things in the script carry over to every real one:
| Part | Why it matters |
|---|---|
| Identifiers | A stable identifier lets you save the deck and load it again on the next run. Without one, names change every run. |
| The deck | Commands find wells through the chain deck → carrier → labware → well. Place something wrongly and the simulator raises before anything moves. |
try / finally | A run can fail halfway. The finally block puts the robot back into a safe state, here without tips, so the next run doesn't start with tips loaded. |
To run the same script on a real STAR, replace MicrolabSTARMock() with MicrolabSTAR(name="Microlab STAR", client=AsyncApiClient()), imported from unitelabs.liquid_handling.hamilton and unitelabs.sdk. Use the name your instrument has in GroundControl.
Next steps
- Basic pipetting: the same transfer on a Hamilton or a Bravo, with more control over liquid classes and tips.
- Building a deck: save this deck to JSON and load it in every run.
- Error handling: failure patterns and a safe-state checklist.
- Automate your workflows: turn a script like this into a workflow the platform runs and records.
Last updated
Control a device with Python
One script that finds a connected QInstruments BioShake, lists what it can do, reads a value, starts heating and streams the temperature.
Automate your workflows
One file that defines a workflow with a phase and a step, runs it locally, and shows what the platform adds when you deploy it.