Opentrons Integration
Overview
Opentrons is a Python-based lab automation platform for Flex and OT-2 robots. Write Protocol API v2 protocols for liquid handling, control hardware modules (heater-shaker, thermocycler), manage labware, for automated pipetting workflows.
When to Use This Skill
This skill should be used when:
- Writing Opentrons Protocol API v2 protocols in Python
- Automating liquid handling workflows on Flex or OT-2 robots
- Controlling hardware modules (temperature, magnetic, heater-shaker, thermocycler)
- Setting up labware configurations and deck layouts
- Implementing complex pipetting operations (serial dilutions, plate replication, PCR setup)
- Managing tip usage and optimizing protocol efficiency
- Working with multi-channel pipettes for 96-well plate operations
- Simulating and testing protocols before robot execution
Core Capabilities
1. Protocol Structure and Metadata
Every Opentrons protocol follows a standard structure:
from opentrons import protocol_api # Metadata metadata = { 'protocolName': 'My Protocol', 'author': 'Name <[email protected]>', 'description': 'Protocol description', 'apiLevel': '2.19' # Use latest available API version } # Requirements (optional) requirements = { 'robotType': 'Flex', # or 'OT-2' 'apiLevel': '2.19' } # Run function def run(protocol: protocol_api.ProtocolContext): # Protocol commands go here pass
Key elements:
- Import
protocol_apifromopentrons - Define
metadatadict with protocolName, author, description, apiLevel - Optional
requirementsdict for robot type and API version - Implement
run()function receivingProtocolContextas parameter - All protocol logic goes inside the
run()function
2. Loading Hardware
Loading Instruments (Pipettes):
def run(protocol: protocol_api.ProtocolContext): # Load pipette on specific mount left_pipette = protocol.load_instrument( 'p1000_single_flex', # Instrument name 'left', # Mount: 'left' or 'right' tip_racks=[tip_rack] # List of tip rack labware objects )
Common pipette names:
- Flex:
p50_single_flex,p1000_single_flex,p50_multi_flex,p1000_multi_flex - OT-2:
p20_single_gen2,p300_single_gen2,p1000_single_gen2,p20_multi_gen2,p300_multi_gen2
Loading Labware:
# Load labware directly on deck plate = protocol.load_labware( 'corning_96_wellplate_360ul_flat', # Labware API name 'D1', # Deck slot (Flex: A1-D3, OT-2: 1-11) label='Sample Plate' # Optional display label ) # Load tip rack tip_rack = protocol.load_labware('opentrons_flex_96_tiprack_1000ul', 'C1') # Load labware on adapter adapter = protocol.load_adapter('opentrons_flex_96_tiprack_adapter', 'B1') tips = adapter.load_labware('opentrons_flex_96_tiprack_200ul')
Loading Modules:
# Temperature module temp_module = protocol.load_module('temperature module gen2', 'D3') temp_plate = temp_module.load_labware('corning_96_wellplate_360ul_flat') # Magnetic module mag_module = protocol.load_module('magnetic module gen2', 'C2') mag_plate = mag_module.load_labware('nest_96_wellplate_100ul_pcr_full_skirt') # Heater-Shaker module hs_module = protocol.load_module('heaterShakerModuleV1', 'D1') hs_plate = hs_module.load_labware('corning_96_wellplate_360ul_flat') # Thermocycler module (takes up specific slots automatically) tc_module = protocol.load_module('thermocyclerModuleV2') tc_plate = tc_module.load_labware('nest_96_wellplate_100ul_pcr_full_skirt')
3. Liquid Handling Operations
Basic Operations:
# Pick up tip pipette.pick_up_tip() # Aspirate (draw liquid in) pipette.aspirate( volume=100, # Volume in µL location=source['A1'] # Well or location object ) # Dispense (expel liquid) pipette.dispense( volume=100, location=dest['B1'] ) # Drop tip pipette.drop_tip() # Return tip to rack pipette.return_tip()
Complex Operations:
# Transfer (combines pick_up, aspirate, dispense, drop_tip) pipette.transfer( volume=100, source=source_plate['A1'], dest=dest_plate['B1'], new_tip='always' # 'always', 'once', or 'never' ) # Distribute (one source to multiple destinations) pipette.distribute( volume=50, source=reservoir['A1'], dest=[plate['A1'], plate['A2'], plate['A3']], new_tip='once' ) # Consolidate (multiple sources to one destination) pipette.consolidate( volume=50, source=[plate['A1'], plate['A2'], plate['A3']], dest=reservoir['A1'], new_tip='once' )
Advanced Techniques:
# Mix (aspirate and dispense in same location) pipette.mix( repetitions=3, volume=50, location=plate['A1'] ) # Air gap (prevent dripping) pipette.aspirate(100, source['A1']) pipette.air_gap(20) # 20µL air gap pipette.dispense(120, dest['A1']) # Blow out (expel remaining liquid) pipette.blow_out(location=dest['A1'].top()) # Touch tip (remove droplets on tip exterior) pipette.touch_tip(location=plate['A1'])
Flow Rate Control:
# Set flow rates (µL/s) pipette.flow_rate.aspirate = 150 pipette.flow_rate.dispense = 300 pipette.flow_rate.blow_out = 400
4. Accessing Wells and Locations
Well Access Methods:
# By name well_a1 = plate['A1'] # By index first_well = plate.wells()[0] # All wells all_wells = plate.wells() # Returns list # By rows rows = plate.rows() # Returns list of lists row_a = plate.rows()[0] # All wells in row A # By columns columns = plate.columns() # Returns list of lists column_1 = plate.columns()[0] # All wells in column 1 # Wells by name (dictionary) wells_dict = plate.wells_by_name() # {'A1': Well, 'A2': Well, ...}
Location Methods:
# Top of well (default: 1mm below top) pipette.aspirate(100, well.top()) pipette.aspirate(100, well.top(z=5)) # 5mm above top # Bottom of well (default: 1mm above bottom) pipette.aspirate(100, well.bottom()) pipette.aspirate(100, well.bottom(z=2)) # 2mm above bottom # Center of well pipette.aspirate(100, well.center())
5. Hardware Module Control
Temperature Module:
# Set temperature temp_module.set_temperature(celsius=4) # Wait for temperature temp_module.await_temperature(celsius=4) # Deactivate temp_module.deactivate() # Check status current_temp = temp_module.temperature # Current temperature target_temp = temp_module.target # Target temperature
Magnetic Module:
# Engage (raise magnets) mag_module.engage(height_from_base=10) # mm from labware base # Disengage (lower magnets) mag_module.disengage() # Check status is_engaged = mag_module.status # 'engaged' or 'disengaged'
Heater-Shaker Module:
# Set temperature hs_module.set_target_temperature(celsius=37) # Wait for temperature hs_module.wait_for_temperature() # Set shake speed hs_module.set_and_wait_for_shake_speed(rpm=500) # Close labware latch hs_module.close_labware_latch() # Open labware latch hs_module.open_labware_latch() # Deactivate heater hs_module.deactivate_heater() # Deactivate shaker hs_module.deactivate_shaker()
Thermocycler Module:
# Open lid tc_module.open_lid() # Close lid tc_module.close_lid() # Set lid temperature tc_module.set_lid_temperature(celsius=105) # Set block temperature tc_module.set_block_temperature( temperature=95, hold_time_seconds=30, hold_time_minutes=0.5, block_max_volume=50 # µL per well ) # Execute profile (PCR cycling) profile = [ {'temperature': 95, 'hold_time_seconds': 30}, {'temperature': 57, 'hold_time_seconds': 30}, {'temperature': 72, 'hold_time_seconds': 60} ] tc_module.execute_profile( steps=profile, repetitions=30, block_max_volume=50 ) # Deactivate tc_module.deactivate_lid() tc_module.deactivate_block()
Absorbance Plate Reader:
# Initialize and read result = plate_reader.read(wavelengths=[450, 650]) # Access readings absorbance_data = result # Dict with wavelength keys
6. Liquid Tracking and Labeling
Define Liquids:
# Define liquid types water = protocol.define_liquid( name='Water', description='Ultrapure water', display_color='#0000FF' # Hex color code ) sample = protocol.define_liquid( name='Sample', description='Cell lysate sample', display_color='#FF0000' )
Load Liquids into Wells:
# Load liquid into specific wells reservoir['A1'].load_liquid(liquid=water, volume=50000) # µL plate['A1'].load_liquid(liquid=sample, volume=100) # Mark wells as empty plate['B1'].load_empty()
7. Protocol Control and Utilities
Execution Control:
# Pause protocol protocol.pause(msg='Replace tip box and resume') # Delay protocol.delay(seconds=60) protocol.delay(minutes=5) # Comment (appears in logs) protocol.comment('Starting serial dilution') # Home robot protocol.home()
Conditional Logic:
# Check if simulating if protocol.is_simulating(): protocol.comment('Running in simulation mode') else: protocol.comment('Running on actual robot')
Rail Lights (Flex only):
# Turn lights on protocol.set_rail_lights(on=True) # Turn lights off protocol.set_rail_lights(on=False)
8. Multi-Channel and 8-Channel Pipetting
When using multi-channel pipettes:
# Load 8-channel pipette multi_pipette = protocol.load_instrument( 'p300_multi_gen2', 'left', tip_racks=[tips] ) # Access entire column with single well reference multi_pipette.transfer( volume=100, source=source_plate['A1'], # Accesses entire column 1 dest=dest_plate['A1'] # Dispenses to entire column 1 ) # Use rows() for row-wise operations for row in plate.rows(): multi_pipette.transfer(100, reservoir['A1'], row[0])
9. Common Protocol Patterns
Serial Dilution:
def run(protocol: protocol_api.ProtocolContext): # Load labware tips = protocol.load_labware('opentrons_flex_96_tiprack_200ul', 'D1') reservoir = protocol.load_labware('nest_12_reservoir_15ml', 'D2') plate = protocol.load_labware('corning_96_wellplate_360ul_flat', 'D3') # Load pipette p300 = protocol.load_instrument('p300_single_flex', 'left', tip_racks=[tips]) # Add diluent to all wells except first p300.transfer(100, reservoir['A1'], plate.rows()[0][1:]) # Serial dilution across row p300.transfer( 100, plate.rows()[0][:11], # Source: wells 0-10 plate.rows()[0][1:], # Dest: wells 1-11 mix_after=(3, 50), # Mix 3x with 50µL after dispense new_tip='always' )
Plate Replication:
def run(protocol: protocol_api.ProtocolContext): # Load labware tips = protocol.load_labware('opentrons_flex_96_tiprack_1000ul', 'C1') source = protocol.load_labware('corning_96_wellplate_360ul_flat', 'D1') dest = protocol.load_labware('corning_96_wellplate_360ul_flat', 'D2') # Load pipette p1000 = protocol.load_instrument('p1000_single_flex', 'left', tip_racks=[tips]) # Transfer from all wells in source to dest p1000.transfer( 100, source.wells(), dest.wells(), new_tip='always' )
PCR Setup:
def run(protocol: protocol_api.ProtocolContext): # Load thermocycler tc_mod = protocol.load_module('thermocyclerModuleV2') tc_plate = tc_mod.load_labware('nest_96_wellplate_100ul_pcr_full_skirt') # Load tips and reagents tips = protocol.load_labware('opentrons_flex_96_tiprack_200ul', 'C1') reagents = protocol.load_labware('opentrons_24_tuberack_nest_1.5ml_snapcap', 'D1') # Load pipette p300 = protocol.load_instrument('p300_single_flex', 'left', tip_racks=[tips]) # Open thermocycler lid tc_mod.open_lid() # Distribute master mix p300.distribute( 20, reagents['A1'], tc_plate.wells(), new_tip='once' ) # Add samples (example for first 8 wells) for i, well in enumerate(tc_plate.wells()[:8]): p300.transfer(5, reagents.wells()[i+1], well, new_tip='always') # Run PCR tc_mod.close_lid() tc_mod.set_lid_temperature(105) # PCR profile tc_mod.set_block_temperature(95, hold_time_seconds=180) profile = [ {'temperature': 95, 'hold_time_seconds': 15}, {'temperature': 60, 'hold_time_seconds': 30}, {'temperature': 72, 'hold_time_seconds': 30} ] tc_mod.execute_profile(steps=profile, repetitions=35, block_max_volume=25) tc_mod.set_block_temperature(72, hold_time_minutes=5) tc_mod.set_block_temperature(4) tc_mod.deactivate_lid() tc_mod.open_lid()
Best Practices
- Always specify API level: Use the latest stable API version in metadata
- Use meaningful labels: Label labware for easier identification in logs
- Check tip availability: Ensure sufficient tips for protocol completion
- Add comments: Use
protocol.comment()for debugging and logging - Simulate first: Always test protocols in simulation before running on robot
- Handle errors gracefully: Add pauses for manual intervention when needed
- Consider timing: Use delays when protocols require incubation periods
- Track liquids: Use liquid tracking for better setup validation
- Optimize tip usage: Use
new_tip='once'when appropriate to save tips - Control flow rates: Adjust flow rates for viscous or volatile liquids
Troubleshooting
Common Issues:
- Out of tips: Verify tip rack capacity matches protocol requirements
- Labware collisions: Check deck layout for spatial conflicts
- Volume errors: Ensure volumes don't exceed well or pipette capacities
- Module not responding: Verify module is properly connected and firmware is updated
- Inaccurate volumes: Calibrate pipettes and check for air bubbles
- Protocol fails in simulation: Check API version compatibility and labware definitions
Resources
For detailed API documentation, see references/api_reference.md in this skill directory.
For example protocol templates, see scripts/ directory.