--- name: opentrons-integration description: "Lab automation platform for Flex/OT-2 robots. Write Protocol API v2 protocols, liquid handling, hardware modules (heater-shaker, thermocycler), labware management, for automated pipetting workflows." --- # 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: ```python from opentrons import protocol_api # Metadata metadata = { 'protocolName': 'My Protocol', 'author': 'Name ', '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_api` from `opentrons` - Define `metadata` dict with protocolName, author, description, apiLevel - Optional `requirements` dict for robot type and API version - Implement `run()` function receiving `ProtocolContext` as parameter - All protocol logic goes inside the `run()` function ### 2. Loading Hardware **Loading Instruments (Pipettes):** ```python 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # 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:** ```python # Check if simulating if protocol.is_simulating(): protocol.comment('Running in simulation mode') else: protocol.comment('Running on actual robot') ``` **Rail Lights (Flex only):** ```python # 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: ```python # 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:** ```python 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:** ```python 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:** ```python 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 1. **Always specify API level**: Use the latest stable API version in metadata 2. **Use meaningful labels**: Label labware for easier identification in logs 3. **Check tip availability**: Ensure sufficient tips for protocol completion 4. **Add comments**: Use `protocol.comment()` for debugging and logging 5. **Simulate first**: Always test protocols in simulation before running on robot 6. **Handle errors gracefully**: Add pauses for manual intervention when needed 7. **Consider timing**: Use delays when protocols require incubation periods 8. **Track liquids**: Use liquid tracking for better setup validation 9. **Optimize tip usage**: Use `new_tip='once'` when appropriate to save tips 10. **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.