Week 3: Protocol Skeleton (Colab/OT-2)
Prelude to this lab
What this is
A minimal, well-commented Opentrons Python API v2 protocol that mixes five dye “channels” (Red/Yellow/Green/Cyan/Blue) and logs the total consumables.
It will report: tips used and µL per color. It uses OT-2 GEN2 p300, 300 µL tips, a 96-well Corning 360 µL flat plate, and an Eppendorf 1.5 mL 24-tube rack. The log messages appear via
protocol.comment().
Opentrons protocol (copy into a single Colab cell or .py)
# metadata / requirements — declare API level (>=2.15 recommended)
# You can put apiLevel here or in `metadata["apiLevel"]`.
requirements = {"apiLevel": "2.15"} # see docs on versioning
metadata = {
"protocolName": "HTGAA W3 — Gel Art Mix (5 colors) — Skeleton",
"author": "Alireza Hekmati",
"description": "Distribute 5 color channels to a 96-well plate; log tip count and per-color volumes."
}
from opentrons import protocol_api
def run(protocol: protocol_api.ProtocolContext):
# --- Deck / labware -------------------------------------------------------
# Tip rack: 300 µL filtered/unfiltered (OT-2 standard)
tips300 = protocol.load_labware("opentrons_96_tiprack_300ul", "8")
# Plate: Corning 96-well, 360 µL flat bottom
plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "2")
# Tube rack: Eppendorf 1.5 mL Safe-Lock (24 position)
tuberack = protocol.load_labware("opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap", "5")
# Pipette: P300 Single-Channel GEN2 on left
p300 = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips300])
# --- Parameters you may tweak ---------------------------------------------
VOLUME_PER_WELL = 30 # µL to dispense per destination well
N_WELLS_PER_COLOR = 6 # how many wells to fill for each color
MIX_BEFORE_ASPIRATE = (3, 150) # (reps, µL) on source tube
# Map dye sources in tube rack (prepare physical dyes in these tubes)
# A1..A5 will be the 5 color sources.
sources = {
"Red": tuberack["A1"],
"Yellow":tuberack["A2"],
"Green": tuberack["A3"],
"Cyan": tuberack["A4"],
"Blue": tuberack["A5"],
}
# Choose simple destination pattern: first 5 rows (A-E), left to right
row_index = {"A":0, "B":1, "C":2, "D":3, "E":4}
dest_rows = {
"Red": "A",
"Yellow": "B",
"Green": "C",
"Cyan": "D",
"Blue": "E",
}
# --- Accounting: tip count & per-color volumes ----------------------------
tips_used = 0
per_color_uL = {k: 0 for k in sources.keys()}
# Helper to pick up a tip and track it
def get_tip():
nonlocal tips_used
p300.pick_up_tip()
tips_used += 1
# --- Work loop -------------------------------------------------------------
for color, src in sources.items():
# Decide the row and the first N destinations in that row
row = dest_rows[color]
dests = plate.rows()[row_index[row]][:N_WELLS_PER_COLOR]
# Mix the source (optional, helps with homogeneity)
get_tip()
p300.mix(MIX_BEFORE_ASPIRATE[0], MIX_BEFORE_ASPIRATE[1], src)
# Use one tip per color; distribute to N wells
for d in dests:
p300.aspirate(VOLUME_PER_WELL, src) # atomic commands: aspirate/dispense
p300.dispense(VOLUME_PER_WELL, d)
# (optional) touch_tip to minimize droplets
# p300.touch_tip(d)
per_color_uL[color] += VOLUME_PER_WELL
# Final blowout back into source top to clear residual
p300.blow_out(src.top())
p300.drop_tip()
# --- Log summary to run log -----------------------------------------------
protocol.comment("=== HTGAA W3 — RUN SUMMARY ===")
protocol.comment(f"Tips used (P300): {tips_used}")
for c, uL in per_color_uL.items():
protocol.comment(f"{c}: {uL} µL total")
protocol.comment("Per-color wells filled: " + str(N_WELLS_PER_COLOR))
protocol.comment(f"Volume per well: {VOLUME_PER_WELL} µL")
protocol.comment("Plate: corning_96_wellplate_360ul_flat | Tips: opentrons_96_tiprack_300ul")