Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/api/pylabrobot.opentrons.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
pylabrobot.opentrons package
============================

Flex
----

.. autosummary::
:toctree: _autosummary
:nosignatures:
:recursive:

OpentronsRobot
OpentronsFlex
OpentronsError
PipetteInfo

Temperature Module
------------------

Expand Down
229 changes: 229 additions & 0 deletions docs/user_guide/opentrons/flex/hello-world.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "flex-intro",
"metadata": {},
"source": [
"# Opentrons Flex\n",
"\n",
"The Opentrons Flex is a liquid handler with up to two independent pipette mounts (1-, 8-, or 96-channel), an optional gripper, and up to four temperature/heater-shaker/thermocycler modules. PyLabRobot talks to it over the robot's built-in HTTP robot-server API — the same server the Opentrons App uses — rather than through the Python Protocol API.\n",
"\n",
"| Property | Value |\n",
"|---|---|\n",
"| [OEM link](https://opentrons.com/flex) | |\n",
"| [HTTP API reference](https://docs.opentrons.com/v2/) | |\n",
"| Communication | HTTP, robot-server API, default port 31950 |\n",
"| PLR class | `OpentronsFlex` |\n",
"| PLR base class | `OpentronsRobot` (shared with a future OT-2 implementation) |\n",
"\n",
"```{warning}\n",
"This driver has NOT been tested against real hardware in PyLabRobot. The command\n",
"payloads follow the Opentrons HTTP robot-server API as documented and observed\n",
"on other Flex integrations. If you verify it on a Flex, please open a PR to\n",
"remove this warning.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "flex-transport-md",
"metadata": {},
"source": [
"## How it talks\n",
"\n",
"`OpentronsFlex` (and its shared base, `OpentronsRobot`) speaks HTTP to the robot-server running on the Flex itself, not the Python Protocol API. `setup()`:\n",
"\n",
"1. Opens an `httpx.AsyncClient` and checks `GET /health` to confirm the robot-server (not Jupyter/Python API mode) is reachable.\n",
"2. Creates an empty run with `POST /runs` — an empty run (no `protocolId`) lets PLR send interactive setup commands instead of a pre-baked protocol.\n",
"3. Discovers the mounted pipette via `GET /instruments` and loads it into the run (`loadPipette`).\n",
"4. Homes all axes.\n",
"\n",
"Every subsequent operation (`pickUpTip`, `aspirate`, `dispense`, `loadLabware`, ...) is POSTed as a command to that run (`POST /runs/{run_id}/commands`) and polled via `GET` until it succeeds or fails."
]
},
{
"cell_type": "markdown",
"id": "flex-physical-md",
"metadata": {},
"source": [
"## Physical setup / cabling\n",
"\n",
"Connect to the Flex over the network:\n",
"\n",
"- **Ethernet (recommended):** connect the Flex's ethernet port to the same network as your computer, or directly via a USB-C-to-ethernet adapter. Find the robot's IP address on its touchscreen under *Settings → Network*.\n",
"- **Wi-Fi:** configure Wi-Fi on the touchscreen; the IP address is shown in the same network settings screen.\n",
"\n",
"The robot-server listens on port `31950` by default. No USB driver or vendor SDK installation is required on the computer side — just `pip install pylabrobot[http]` (or ensure `httpx` is installed)."
]
},
{
"cell_type": "markdown",
"id": "flex-setup-md",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"Construct `OpentronsFlex` with a `FlexDeck` and the robot's IP address. `setup()` connects, creates a run, and discovers the mounted pipette (`flex.pipette`)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-setup-code",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.opentrons import OpentronsFlex\n",
"from pylabrobot.resources.opentrons import FlexDeck\n",
"\n",
"flex = OpentronsFlex(deck=FlexDeck(), host=\"169.254.99.87\") # replace with your Flex's IP\n",
"await flex.setup()\n",
"\n",
"print(\"Pipette:\", flex.pipette.pipette_name, f\"({flex.pipette.channels} channels)\")"
]
},
{
"cell_type": "markdown",
"id": "flex-deck-md",
"metadata": {},
"source": [
"## Deck layout\n",
"\n",
"Load a Flex 50 uL tip rack and a Corning 96-well plate onto the deck. Corning labware isn't in the built-in Flex load-name map, so set `ot_load_name` explicitly to the Opentrons labware-library name."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-deck-code",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.resources.corning import Cor_96_wellplate_360ul_Fb\n",
"from pylabrobot.resources.opentrons import flex_96_tiprack_50ul\n",
"\n",
"tiprack = flex_96_tiprack_50ul(name=\"tips_01\")\n",
"plate = Cor_96_wellplate_360ul_Fb(name=\"plate_01\")\n",
"plate.ot_load_name = \"corning_96_wellplate_360ul_flat\"\n",
"\n",
"flex.deck.assign_child_at_slot(tiprack, slot=\"C1\")\n",
"flex.deck.assign_child_at_slot(plate, slot=\"D1\")"
]
},
{
"cell_type": "markdown",
"id": "flex-picktips-md",
"metadata": {},
"source": [
"## Picking up tips\n",
"\n",
"`pick_up_tips` takes a list of `TipSpot`s and a matching `use_channels` list — here, channel 0 picks up the tip at well A1 of the rack. The op commits directly to the tip spot's tracker on the resource tree (`tiprack[\"A1\"]` now reports no tip)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-picktips-code",
"metadata": {},
"outputs": [],
"source": [
"await flex.pick_up_tips(tiprack[\"A1\"], use_channels=[0])"
]
},
{
"cell_type": "markdown",
"id": "flex-aspirate-md",
"metadata": {},
"source": [
"## Aspirating\n",
"\n",
"The resource tree's volume tracker needs to know a well has liquid before you can aspirate from it — this driver has no liquid-level probe yet, so set the volume manually. Then aspirate 20 uL from well A1 of the plate on channel 0."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-aspirate-code",
"metadata": {},
"outputs": [],
"source": [
"plate[\"A1\"][0].tracker.set_volume(200.0)\n",
"plate[\"A1\"][0].tracker.commit()\n",
"\n",
"await flex.aspirate(plate[\"A1\"], vols=[20.0], use_channels=[0])"
]
},
{
"cell_type": "markdown",
"id": "flex-dispense-md",
"metadata": {},
"source": [
"## Dispensing\n",
"\n",
"Dispense the 20 uL into well A2 of the same plate, again on channel 0."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-dispense-code",
"metadata": {},
"outputs": [],
"source": [
"await flex.dispense(plate[\"A2\"], vols=[20.0], use_channels=[0])"
]
},
{
"cell_type": "markdown",
"id": "flex-droptips-md",
"metadata": {},
"source": [
"## Dropping tips\n",
"\n",
"Return the tip to its original spot in the rack. Dropping into a `Trash` resource instead (e.g. `flex.deck.get_trash_area()`) routes through the Flex's addressable-area drop-tip commands and does not restore the tracker."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-droptips-code",
"metadata": {},
"outputs": [],
"source": [
"await flex.drop_tips(tiprack[\"A1\"], use_channels=[0])"
]
},
{
"cell_type": "markdown",
"id": "flex-teardown-md",
"metadata": {},
"source": [
"## Teardown"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "flex-teardown-code",
"metadata": {},
"outputs": [],
"source": [
"await flex.stop()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions docs/user_guide/opentrons/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
```{toctree}
:maxdepth: 1

flex/hello-world
temperature_module/hello-world
```
3 changes: 3 additions & 0 deletions pylabrobot/opentrons/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from pylabrobot.opentrons.flex import OpentronsFlex
from pylabrobot.opentrons.robot import OpentronsError, OpentronsRobot, PipetteInfo

from .temperature_module import (
OpentronsTemperatureModuleDriver,
OpentronsTemperatureModuleTemperatureBackend,
Expand Down
Loading
Loading