{ "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", "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 OT2\n", "from pylabrobot.resources import OTDeck\n", "\n", "deck = OTDeck()\n", "ot2 = OT2(host=\"ot2.local\", deck=deck)" ] }, { "cell_type": "markdown", "id": "ot2-setup-md", "metadata": {}, "source": [ "## Set up the robot\n", "\n", "`setup()` reads the robot software version and mounted pipettes, creates an Opentrons run, loads the pipettes, and homes the robot.\n", "\n", "For inspection without a run or motion, use `await ot2.connect()` followed by `await ot2.get_health()` or `await ot2.get_mounted_pipettes()`. These queries return fresh readings without changing the driver's session or tracked state. `await ot2.stop()` closes that connection; `await ot2.setup()` can also continue from it." ] }, { "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.num_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 software version recorded during setup." ] }, { "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 }