skills/opentrons-integration/references/modules_and_deck.md
Module compatibility is both an API question and a physical hardware question. Confirm the robot model, module generation, caddy or adapter, firmware, deck location, and labware before authoring commands.
| Hardware | Flex | OT-2 | Protocol API load name |
|---|---|---|---|
| Absorbance Plate Reader | Yes | No | absorbanceReaderV1 |
| Flex Stacker | Yes | No | flexStackerModuleV1 |
| Heater-Shaker GEN1 | Yes | Yes | heaterShakerModuleV1 |
| Magnetic Block GEN1 | Yes | No | magneticBlockV1 |
| Magnetic Module GEN1/GEN2 | No | Yes | magnetic module / magnetic module gen2 |
| Temperature Module GEN2 | Yes | Yes | temperature module gen2 |
| Thermocycler GEN2 | Yes | Yes | thermocyclerModuleV2 |
Notes:
See the current Flex module list and OT-2 module list before relying on this snapshot.
protocol.load_waste_chute() and occupies D3.trash = protocol.load_trash_bin("A3")
chute = protocol.load_waste_chute() # D3 only; do not load both into D3
Load only the fixtures physically installed for the run.
load_trash_bin() for the fixed trash.heater_shaker = protocol.load_module(
module_name="heaterShakerModuleV1",
location="D1",
)
Loading a powered module makes it a run requirement. The App will prevent the run from starting unless the expected connected module is available.
Load labware through the module:
plate = heater_shaker.load_labware(
"corning_96_wellplate_360ul_flat",
label="Mixing Plate",
)
For a separate adapter:
temperature_module = protocol.load_module(
"temperature module gen2",
"D3",
)
adapter = temperature_module.load_adapter(
"opentrons_96_well_aluminum_block"
)
plate = adapter.load_labware(
"opentrons_96_wellplate_200ul_pcr_full_skirt"
)
The API generally cannot prove that every unusual module, adapter, and labware combination is physically valid. Check the official compatibility list.
temperature_module.set_temperature(celsius=4)
# Pipetting or incubation commands...
temperature_module.deactivate()
set_temperature() blocks until the target is reached. In API 2.27+,
start_set_temperature() can run concurrently and returns a task. Wait for the
task before relying on the target temperature.
Operational checks:
Typical sequence:
heater_shaker.close_labware_latch()
heater_shaker.set_and_wait_for_temperature(37)
heater_shaker.set_and_wait_for_shake_speed(1000)
protocol.delay(minutes=5)
heater_shaker.deactivate_shaker()
heater_shaker.deactivate_heater()
The exact temperature method name depends on API behavior. Current context
methods include set_target_temperature(),
set_and_wait_for_temperature(), wait_for_temperature(),
set_and_wait_for_shake_speed(), deactivate_shaker(), and
deactivate_heater().
Always:
API 2.27+ provides nonblocking shake and temperature operations for concurrent work. Keep task handles and wait before a dependent step.
thermocycler = protocol.load_module("thermocyclerModuleV2")
plate = thermocycler.load_labware(
"opentrons_96_wellplate_200ul_pcr_full_skirt"
)
thermocycler.open_lid()
# Pipette into plate.
thermocycler.close_lid()
thermocycler.set_lid_temperature(temperature=105)
thermocycler.set_block_temperature(
temperature=95,
hold_time_seconds=180,
block_max_volume=25,
)
thermocycler.execute_profile(
steps=[
{"temperature": 95, "hold_time_seconds": 15},
{"temperature": 60, "hold_time_seconds": 30},
{"temperature": 72, "hold_time_seconds": 30},
],
repetitions=35,
block_max_volume=25,
)
thermocycler.set_block_temperature(
temperature=4,
block_max_volume=25,
)
thermocycler.deactivate_lid()
API 2.28 adds an optional ramp_rate to block-temperature commands.
Checks:
block_max_volume matches the actual per-well reaction volume.The Magnetic Block is passive. It has no engage or disengage methods. Separation happens by moving compatible labware onto and off the block:
magnetic_block = protocol.load_module("magneticBlockV1", "D1")
protocol.move_labware(
labware=plate,
new_location=magnetic_block,
use_gripper=True,
)
protocol.delay(minutes=5)
protocol.move_labware(
labware=plate,
new_location="C1",
use_gripper=True,
)
Confirm gripper compatibility and plate orientation. Avoid aspirating beads by validating settle time, aspiration side, bottom clearance, and flow rate.
The powered Magnetic Module is OT-2-only:
magnetic_module = protocol.load_module(
"magnetic module gen2",
"1",
)
plate = magnetic_module.load_labware(
"nest_96_wellplate_2ml_deep"
)
magnetic_module.engage(height_from_base=6.5)
protocol.delay(minutes=5)
magnetic_module.disengage()
Engage height is labware- and assay-specific. Do not copy an arbitrary height from another plate. The Magnetic Module is discontinued but remains supported for existing OT-2 hardware.
The reader is Flex-only and loads in A3–D3. Its caddy also uses the corresponding column-4 location for lid travel.
Required workflow:
reader = protocol.load_module(
module_name="absorbanceReaderV1",
location="D3",
)
plate = protocol.load_labware(
"corning_96_wellplate_360ul_flat",
"C2",
)
reader.close_lid()
reader.initialize(
mode="multi",
wavelengths=[450, 562, 600],
)
reader.open_lid()
protocol.move_labware(
labware=plate,
new_location=reader,
use_gripper=True,
)
reader.close_lid()
data = reader.read(export_filename="plate_data")
Default hardware wavelengths are 450, 562, 600, and 650 nm.
read() returns:
dict[int, dict[str, float]]
The first key is wavelength; the second is well name. In simulation every measurement is zero, and no output file is written. Guard calculations that would divide by a reading:
if not protocol.is_simulating():
normalized = data[450]["A1"] / data[450]["H12"]
Do not move the plate-reader lid manually.
Up to four Stackers attach to the right side of Flex. Each shuttle is addressed as a column-4 location:
stacker = protocol.load_module(
module_name="flexStackerModuleV1",
location="A4",
)
Configure one labware type per Stacker:
stacker.set_stored_labware(
load_name="opentrons_flex_96_tiprack_200ul",
count=5,
lid="opentrons_flex_tiprack_lid",
)
Retrieve and move:
tip_rack = stacker.retrieve()
protocol.move_labware(
labware=tip_rack,
new_location="B2",
use_gripper=True,
)
Store:
protocol.move_labware(
labware=plate,
new_location=stacker,
use_gripper=True,
)
stacker.store()
fill() and empty() pause for manual loading or unloading.
Constraints:
retrieve() or store().Flex automated move:
protocol.move_labware(
labware=plate,
new_location="C2",
use_gripper=True,
)
Manual move:
protocol.move_labware(
labware=plate,
new_location="C2",
use_gripper=False,
)
A manual move pauses for the operator. Make sure the message and run setup make the source and destination unambiguous.
API 2.23+ supports lid stacks and move_lid(). Confirm lid and labware
compatibility, orientation, stack quantity, and disposal location.
Concurrent methods can overlap long module actions with independent pipetting:
start_set_temperature().start_set_block_temperature(),
start_set_lid_temperature(), and start_execute_profile().Pattern:
Do not create concurrency merely to shorten runtime. Check deck access, vibration, thermal dependencies, and collision risk.