UniteLabs
Integrate

Calling a Connector

Discover a connector's modules and actions, then run aspirate and dispense against an Agilent Bravo.

Pre-requisite: Follow the steps in Importing a connector to install the SDK and authenticate against the platform.

This tutorial uses an Agilent Bravo to show how to discover what a connector exposes and then call real actions on it. The pattern is the same for every connector (thermocyclers, balances, readers, liquid handlers); only the module and action names change.

Understanding Actions

Actions are the callable methods on a module: the things you invoke to read a value or trigger behavior on the instrument. Every action has typed parameters and responses, and either returns a single result or streams updates over time. See Action for the full model.

Connect to the Bravo

from unitelabs.sdk import AsyncApiClient
from unitelabs.liquid_handling.agilent import Bravo

client = AsyncApiClient()

bravo = Bravo(name="Bravo", client=client)
await bravo.configure()
await bravo.initialize()
await bravo.activate()

Three calls bring the Bravo online: configure reads the deck and module configuration from the connector, initialize opens the control loop to the physical device, and activate arms the pipette head for motion.

Discover available actions

The Bravo's pipette head exposes every liquid-handling action you need. Ask the module what actions it has:

print(bravo.pipette_head.actions.keys())

The same callables appear in the platform UI under the pipette head module, grouped by type into Properties, Sensors, and Controls. See the Terminology table for the cross-surface mapping.

Inspect an action's parameters

Before calling an action you do not already know, ask it what arguments it expects:

print(await bravo.pipette_head.aspirate.parameters)

The result is a typed schema: parameter names, data types, and constraints (units, min / max, enumerations). The SDK uses this to serialize your arguments correctly; the platform UI uses the same schema to render a parameter form.

Place a plate on the deck

Before any liquid moves, the SDK needs to know what is on the deck. For a minimal example we place one 96-well plate and pre-fill it with water, plus a rack of tips the pipette head will use:

from unitelabs.labware.agilent.tips import AgilentTip_250, AgilentTipRack_250
from unitelabs.labware.plates import Standard96Plate
from unitelabs.labware.liquids import Liquid, PredefinedLiquids

# Tips
tip_rack = AgilentTipRack_250(identifier="TipRack")
tip_rack.fill(AgilentTip_250)

# A plate pre-filled with water
plate = Standard96Plate(identifier="DemoPlate")
plate.container.add_liquid(PredefinedLiquids.WATER, 20_000.0)

# Place both on the Bravo deck
bravo.deck.add(tip_rack, location=1)
bravo.deck.add(plate, location=5)

See Labware for how the plate's wells are modelled and how the SDK tracks liquid volume through every subsequent action.

Aspirate and dispense

Pick up tips, aspirate 100 µL from the plate, and dispense back into it:

# Pick up tips
await bravo.pipette_head.pick_up_tips_from(rack=tip_rack, press_depth=5.8)

# Aspirate 100 µL
await bravo.pipette_head.aspirate(plate=plate, volume=100)

# Dispense 100 µL
await bravo.pipette_head.dispense(plate=plate, volume=100)

# Return the tips
await bravo.pipette_head.discard_tips(bravo.deck[3])

These are the two actions you introspected in the previous section. Now you have discovered them on the connector, asked for their parameter schemas, and executed them against a real instrument.

Next steps

This tutorial stays focused on calling actions on one module. For a full end-to-end protocol that wires deck building, labware, device connection, and error handling into a single runnable script, see Your First Protocol. For deeper pipetting patterns and vendor-specific options (Hamilton, Bravo), see Basic Pipetting.