Skip to content
Draft
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
15 changes: 15 additions & 0 deletions docs/api/pylabrobot.opentrons.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.. currentmodule:: pylabrobot.opentrons

pylabrobot.opentrons package
=============================

.. currentmodule:: pylabrobot.opentrons.ot2

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

OpentronsOT2
OT2Pipette
OpentronsOT2Error
1 change: 1 addition & 0 deletions docs/api/pylabrobot.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Manufacturers
pylabrobot.kbiosystems
pylabrobot.mettler_toledo
pylabrobot.molecular_devices
pylabrobot.opentrons
pylabrobot.qinstruments
pylabrobot.sartorius
pylabrobot.thermo_fisher
1 change: 1 addition & 0 deletions docs/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ kbioscience/index
kbiosystems/index
mettler_toledo/index
molecular_devices/index
opentrons/index
qinstruments/index
sartorius/index
thermo_fisher/index
Expand Down
7 changes: 7 additions & 0 deletions docs/user_guide/opentrons/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Opentrons

```{toctree}
:maxdepth: 1

ot2/hello-world
```
354 changes: 354 additions & 0 deletions docs/user_guide/opentrons/ot2/hello-world.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ot2-title",
"metadata": {},
"source": [
"# Opentrons OT-2\n",
"\n",
"The OT-2 is a two-mount liquid-handling robot. PyLabRobot discovers the pipette on each mount and exposes it as a real object, so operations read as `pipette.pick_up_tip(...)`, `pipette.aspirate(...)`, and `pipette.dispense(...)`.\n",
"\n",
"| Property | Value |\n",
"|---|---|\n",
"| Communication | JSON over HTTP |\n",
"| Default address | Robot hostname or IP, port `31950` |\n",
"| Pipette mounts | Left and right |\n",
"| Supported liquid operations | Single-channel GEN1 and GEN2 pipettes |\n",
"| Deck | 12 slots; slot 12 contains fixed trash by default |\n",
"\n",
"```{warning}\n",
"This new-architecture driver has NOT been tested against hardware in PyLabRobot. `setup()` logs a warning to that effect. Keep clear of the deck whenever the robot can move. If you verify it on your OT-2, please open a PR to remove the warning.\n",
"```\n",
"\n",
"The OT-2 exposes a run-command HTTP API. PyLabRobot creates a run during `setup()`, sends one command at a time, and waits for each command to succeed or fail before continuing."
]
},
{
"cell_type": "markdown",
"id": "ot2-physical",
"metadata": {},
"source": [
"## Physical setup\n",
"\n",
"1. Install the pipettes and remove any tips already attached to their nozzles.\n",
"2. In the Opentrons App, complete deck calibration, pipette-offset calibration, and tip-length calibration for the exact Opentrons tip rack you will use.\n",
"3. Put the computer and OT-2 on the same network.\n",
"4. Find the robot's hostname or IP in the Opentrons App. A hostname such as `ot2.local` may also work on your network.\n",
"5. Keep the deck clear until the labware layout below matches the physical deck."
]
},
{
"cell_type": "markdown",
"id": "ot2-create-md",
"metadata": {},
"source": [
"## Create the robot\n",
"\n",
"Create the deck first and pass it to the robot. Replace `ot2.local` with your robot's hostname or IP address, without `http://`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-create",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.opentrons import OpentronsOT2\n",
"from pylabrobot.resources import OTDeck\n",
"\n",
"deck = OTDeck()\n",
"ot2 = OpentronsOT2(host=\"ot2.local\", deck=deck)"
]
},
{
"cell_type": "markdown",
"id": "ot2-setup-md",
"metadata": {},
"source": [
"## Connect\n",
"\n",
"`setup()` creates an Opentrons run, discovers the mounted pipettes, reads the robot API version, and homes the robot."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-setup",
"metadata": {},
"outputs": [],
"source": [
"await ot2.setup()"
]
},
{
"cell_type": "markdown",
"id": "ot2-pipettes-md",
"metadata": {},
"source": [
"## Inspect the pipettes\n",
"\n",
"The left and right mount are either an `OT2Pipette` or `None`. This notebook uses the first mounted single-channel pipette."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-pipettes",
"metadata": {},
"outputs": [],
"source": [
"print(\"Left:\", ot2.left_pipette.name if ot2.left_pipette else None)\n",
"print(\"Right:\", ot2.right_pipette.name if ot2.right_pipette else None)\n",
"\n",
"pipette = next((p for p in ot2.pipettes if p.channels == 1), None)\n",
"assert pipette is not None, \"This example needs a mounted single-channel pipette\""
]
},
{
"cell_type": "markdown",
"id": "ot2-deck-md",
"metadata": {},
"source": [
"## Model the physical deck\n",
"\n",
"Choose a tip rack that exactly matches the physical rack and discovered pipette, place it in slot 1, and place the plate in slot 2. Make the physical deck match this layout before continuing. The standard Opentrons rack definitions below preserve the rack identity used by the robot's tip-length calibration. Tracking is enabled so PyLabRobot checks tip and liquid state around each robot command."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-deck",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.resources import set_tip_tracking, set_volume_tracking\n",
"from pylabrobot.resources.celltreat import celltreat_96_wellplate_350uL_Fb\n",
"from pylabrobot.resources.opentrons import (\n",
" opentrons_96_filtertiprack_10ul,\n",
" opentrons_96_filtertiprack_20ul,\n",
" opentrons_96_filtertiprack_200ul,\n",
" opentrons_96_filtertiprack_1000ul,\n",
" opentrons_96_tiprack_300ul,\n",
")\n",
"\n",
"set_tip_tracking(True)\n",
"set_volume_tracking(True)\n",
"\n",
"tip_rack_factory = {\n",
" 10: opentrons_96_filtertiprack_10ul,\n",
" 20: opentrons_96_filtertiprack_20ul,\n",
" 50: opentrons_96_filtertiprack_200ul,\n",
" 300: opentrons_96_tiprack_300ul,\n",
" 1000: opentrons_96_filtertiprack_1000ul,\n",
"}[pipette.maximum_volume]\n",
"\n",
"tips = tip_rack_factory(name=\"tips\")\n",
"plate = celltreat_96_wellplate_350uL_Fb(name=\"plate\")\n",
"deck.assign_child_at_slot(tips, slot=1)\n",
"deck.assign_child_at_slot(plate, slot=2)\n",
"\n",
"transfer_volume = max(pipette.minimum_volume, min(20, pipette.maximum_volume))\n",
"test_liquid_volume = max(100, transfer_volume * 2)\n",
"plate.get_well(\"A1\").tracker.set_volume(test_liquid_volume)\n",
"print(f\"Before continuing, manually add {test_liquid_volume:g} µL of water to plate well A1.\")"
]
},
{
"cell_type": "markdown",
"id": "ot2-pickup-md",
"metadata": {},
"source": [
"## Pick up a tip\n",
"\n",
"Pause here and add the printed amount of water to plate well A1. Verify that the matching tip rack is physically in slot 1, the plate is in slot 2, and tip A1 is present. This is the first operation after homing that approaches labware."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-pickup",
"metadata": {},
"outputs": [],
"source": [
"await pipette.pick_up_tip(tips.get_item(\"A1\"))"
]
},
{
"cell_type": "markdown",
"id": "ot2-mix-md",
"metadata": {},
"source": [
"## Mix\n",
"\n",
"`mix()` moves to 1 mm above the well bottom, performs the requested aspiration/dispense cycles client-side, then returns to traversal height."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-mix",
"metadata": {},
"outputs": [],
"source": [
"await pipette.mix(\n",
" plate.get_well(\"A1\"), volume=transfer_volume, repetitions=3, liquid_height=1\n",
")"
]
},
{
"cell_type": "markdown",
"id": "ot2-aspirate-md",
"metadata": {},
"source": [
"## Aspirate\n",
"\n",
"Aspirate from the cavity bottom plus `liquid_height`. You can also pass a `Coordinate` offset to compensate for a carefully measured positional calibration difference."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-aspirate",
"metadata": {},
"outputs": [],
"source": [
"await pipette.aspirate(plate.get_well(\"A1\"), volume=transfer_volume, liquid_height=1)"
]
},
{
"cell_type": "markdown",
"id": "ot2-dispense-md",
"metadata": {},
"source": [
"## Dispense\n",
"\n",
"Dispense the tracked liquid into another well. The pipette returns to the configured traversal height after the operation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-dispense",
"metadata": {},
"outputs": [],
"source": [
"await pipette.dispense(plate.get_well(\"B1\"), volume=transfer_volume, liquid_height=1)"
]
},
{
"cell_type": "markdown",
"id": "ot2-return-md",
"metadata": {},
"source": [
"## Return the tip\n",
"\n",
"`return_tip()` uses the recorded pickup origin and restores the tip-rack tracker after the robot command succeeds."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-return",
"metadata": {},
"outputs": [],
"source": [
"await pipette.return_tip()"
]
},
{
"cell_type": "markdown",
"id": "ot2-pickup-two-md",
"metadata": {},
"source": [
"## Pick up another tip\n",
"\n",
"Pick up a fresh tip to demonstrate disposal in the fixed trash."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-pickup-two",
"metadata": {},
"outputs": [],
"source": [
"await pipette.pick_up_tip(tips.get_item(\"A2\"))"
]
},
{
"cell_type": "markdown",
"id": "ot2-discard-md",
"metadata": {},
"source": [
"## Discard the tip\n",
"\n",
"`discard_tip()` uses the fixed-trash command sequence appropriate for the robot's reported HTTP API version."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-discard",
"metadata": {},
"outputs": [],
"source": [
"await pipette.discard_tip()"
]
},
{
"cell_type": "markdown",
"id": "ot2-home-md",
"metadata": {},
"source": [
"## Home\n",
"\n",
"Home the gantry and pipette axes when you need to return the robot to its reference state."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-home",
"metadata": {},
"outputs": [],
"source": [
"await ot2.home()"
]
},
{
"cell_type": "markdown",
"id": "ot2-stop-md",
"metadata": {},
"source": [
"## Teardown\n",
"\n",
"Always run `stop()`, including after an error. It stops the active Opentrons run, making the robot available to the Opentrons App again, and closes the HTTP transport."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ot2-stop",
"metadata": {},
"outputs": [],
"source": [
"await ot2.stop()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading