Initial commit
This commit is contained in:
567
skills/opentrons-integration/SKILL.md
Normal file
567
skills/opentrons-integration/SKILL.md
Normal file
@@ -0,0 +1,567 @@
|
||||
---
|
||||
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 <email@example.com>',
|
||||
'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.
|
||||
Reference in New Issue
Block a user