opentrons 8.6.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/__init__.py +150 -0
- opentrons/_version.py +34 -0
- opentrons/calibration_storage/__init__.py +54 -0
- opentrons/calibration_storage/deck_configuration.py +62 -0
- opentrons/calibration_storage/encoder_decoder.py +31 -0
- opentrons/calibration_storage/file_operators.py +142 -0
- opentrons/calibration_storage/helpers.py +103 -0
- opentrons/calibration_storage/ot2/__init__.py +34 -0
- opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
- opentrons/calibration_storage/ot2/models/__init__.py +0 -0
- opentrons/calibration_storage/ot2/models/v1.py +149 -0
- opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
- opentrons/calibration_storage/ot2/tip_length.py +281 -0
- opentrons/calibration_storage/ot3/__init__.py +31 -0
- opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
- opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
- opentrons/calibration_storage/ot3/models/__init__.py +0 -0
- opentrons/calibration_storage/ot3/models/v1.py +122 -0
- opentrons/calibration_storage/ot3/module_offset.py +138 -0
- opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
- opentrons/calibration_storage/types.py +45 -0
- opentrons/cli/__init__.py +21 -0
- opentrons/cli/__main__.py +5 -0
- opentrons/cli/analyze.py +501 -0
- opentrons/config/__init__.py +631 -0
- opentrons/config/advanced_settings.py +871 -0
- opentrons/config/defaults_ot2.py +214 -0
- opentrons/config/defaults_ot3.py +499 -0
- opentrons/config/feature_flags.py +86 -0
- opentrons/config/gripper_config.py +55 -0
- opentrons/config/reset.py +203 -0
- opentrons/config/robot_configs.py +187 -0
- opentrons/config/types.py +183 -0
- opentrons/drivers/__init__.py +0 -0
- opentrons/drivers/absorbance_reader/__init__.py +11 -0
- opentrons/drivers/absorbance_reader/abstract.py +72 -0
- opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
- opentrons/drivers/absorbance_reader/driver.py +81 -0
- opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
- opentrons/drivers/absorbance_reader/simulator.py +84 -0
- opentrons/drivers/asyncio/__init__.py +0 -0
- opentrons/drivers/asyncio/communication/__init__.py +22 -0
- opentrons/drivers/asyncio/communication/async_serial.py +183 -0
- opentrons/drivers/asyncio/communication/errors.py +88 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +552 -0
- opentrons/drivers/command_builder.py +102 -0
- opentrons/drivers/flex_stacker/__init__.py +13 -0
- opentrons/drivers/flex_stacker/abstract.py +214 -0
- opentrons/drivers/flex_stacker/driver.py +768 -0
- opentrons/drivers/flex_stacker/errors.py +68 -0
- opentrons/drivers/flex_stacker/simulator.py +309 -0
- opentrons/drivers/flex_stacker/types.py +367 -0
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/drivers/heater_shaker/__init__.py +5 -0
- opentrons/drivers/heater_shaker/abstract.py +76 -0
- opentrons/drivers/heater_shaker/driver.py +204 -0
- opentrons/drivers/heater_shaker/simulator.py +94 -0
- opentrons/drivers/mag_deck/__init__.py +6 -0
- opentrons/drivers/mag_deck/abstract.py +44 -0
- opentrons/drivers/mag_deck/driver.py +208 -0
- opentrons/drivers/mag_deck/simulator.py +63 -0
- opentrons/drivers/rpi_drivers/__init__.py +33 -0
- opentrons/drivers/rpi_drivers/dev_types.py +94 -0
- opentrons/drivers/rpi_drivers/gpio.py +282 -0
- opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
- opentrons/drivers/rpi_drivers/interfaces.py +15 -0
- opentrons/drivers/rpi_drivers/types.py +364 -0
- opentrons/drivers/rpi_drivers/usb.py +102 -0
- opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
- opentrons/drivers/serial_communication.py +151 -0
- opentrons/drivers/smoothie_drivers/__init__.py +4 -0
- opentrons/drivers/smoothie_drivers/connection.py +51 -0
- opentrons/drivers/smoothie_drivers/constants.py +121 -0
- opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
- opentrons/drivers/smoothie_drivers/errors.py +49 -0
- opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
- opentrons/drivers/smoothie_drivers/simulator.py +99 -0
- opentrons/drivers/smoothie_drivers/types.py +16 -0
- opentrons/drivers/temp_deck/__init__.py +10 -0
- opentrons/drivers/temp_deck/abstract.py +54 -0
- opentrons/drivers/temp_deck/driver.py +197 -0
- opentrons/drivers/temp_deck/simulator.py +57 -0
- opentrons/drivers/thermocycler/__init__.py +12 -0
- opentrons/drivers/thermocycler/abstract.py +99 -0
- opentrons/drivers/thermocycler/driver.py +395 -0
- opentrons/drivers/thermocycler/simulator.py +126 -0
- opentrons/drivers/types.py +107 -0
- opentrons/drivers/utils.py +222 -0
- opentrons/execute.py +742 -0
- opentrons/hardware_control/__init__.py +65 -0
- opentrons/hardware_control/__main__.py +77 -0
- opentrons/hardware_control/adapters.py +98 -0
- opentrons/hardware_control/api.py +1347 -0
- opentrons/hardware_control/backends/__init__.py +7 -0
- opentrons/hardware_control/backends/controller.py +400 -0
- opentrons/hardware_control/backends/errors.py +9 -0
- opentrons/hardware_control/backends/estop_state.py +164 -0
- opentrons/hardware_control/backends/flex_protocol.py +497 -0
- opentrons/hardware_control/backends/ot3controller.py +1930 -0
- opentrons/hardware_control/backends/ot3simulator.py +900 -0
- opentrons/hardware_control/backends/ot3utils.py +664 -0
- opentrons/hardware_control/backends/simulator.py +442 -0
- opentrons/hardware_control/backends/status_bar_state.py +240 -0
- opentrons/hardware_control/backends/subsystem_manager.py +431 -0
- opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
- opentrons/hardware_control/backends/types.py +14 -0
- opentrons/hardware_control/constants.py +6 -0
- opentrons/hardware_control/dev_types.py +125 -0
- opentrons/hardware_control/emulation/__init__.py +0 -0
- opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
- opentrons/hardware_control/emulation/app.py +56 -0
- opentrons/hardware_control/emulation/connection_handler.py +38 -0
- opentrons/hardware_control/emulation/heater_shaker.py +150 -0
- opentrons/hardware_control/emulation/magdeck.py +60 -0
- opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
- opentrons/hardware_control/emulation/module_server/client.py +78 -0
- opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
- opentrons/hardware_control/emulation/module_server/models.py +31 -0
- opentrons/hardware_control/emulation/module_server/server.py +110 -0
- opentrons/hardware_control/emulation/parser.py +74 -0
- opentrons/hardware_control/emulation/proxy.py +241 -0
- opentrons/hardware_control/emulation/run_emulator.py +68 -0
- opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
- opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
- opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
- opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
- opentrons/hardware_control/emulation/settings.py +119 -0
- opentrons/hardware_control/emulation/simulations.py +133 -0
- opentrons/hardware_control/emulation/smoothie.py +192 -0
- opentrons/hardware_control/emulation/tempdeck.py +69 -0
- opentrons/hardware_control/emulation/thermocycler.py +128 -0
- opentrons/hardware_control/emulation/types.py +10 -0
- opentrons/hardware_control/emulation/util.py +38 -0
- opentrons/hardware_control/errors.py +43 -0
- opentrons/hardware_control/execution_manager.py +164 -0
- opentrons/hardware_control/instruments/__init__.py +5 -0
- opentrons/hardware_control/instruments/instrument_abc.py +39 -0
- opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
- opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
- opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
- opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
- opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
- opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
- opentrons/hardware_control/module_control.py +332 -0
- opentrons/hardware_control/modules/__init__.py +69 -0
- opentrons/hardware_control/modules/absorbance_reader.py +373 -0
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/flex_stacker.py +948 -0
- opentrons/hardware_control/modules/heater_shaker.py +426 -0
- opentrons/hardware_control/modules/lid_temp_status.py +35 -0
- opentrons/hardware_control/modules/magdeck.py +233 -0
- opentrons/hardware_control/modules/mod_abc.py +245 -0
- opentrons/hardware_control/modules/module_calibration.py +93 -0
- opentrons/hardware_control/modules/plate_temp_status.py +61 -0
- opentrons/hardware_control/modules/tempdeck.py +299 -0
- opentrons/hardware_control/modules/thermocycler.py +731 -0
- opentrons/hardware_control/modules/types.py +417 -0
- opentrons/hardware_control/modules/update.py +255 -0
- opentrons/hardware_control/modules/utils.py +73 -0
- opentrons/hardware_control/motion_utilities.py +318 -0
- opentrons/hardware_control/nozzle_manager.py +422 -0
- opentrons/hardware_control/ot3_calibration.py +1171 -0
- opentrons/hardware_control/ot3api.py +3227 -0
- opentrons/hardware_control/pause_manager.py +31 -0
- opentrons/hardware_control/poller.py +112 -0
- opentrons/hardware_control/protocols/__init__.py +106 -0
- opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
- opentrons/hardware_control/protocols/calibratable.py +45 -0
- opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
- opentrons/hardware_control/protocols/configurable.py +48 -0
- opentrons/hardware_control/protocols/event_sourcer.py +18 -0
- opentrons/hardware_control/protocols/execution_controllable.py +33 -0
- opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
- opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
- opentrons/hardware_control/protocols/gripper_controller.py +55 -0
- opentrons/hardware_control/protocols/hardware_manager.py +51 -0
- opentrons/hardware_control/protocols/identifiable.py +16 -0
- opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
- opentrons/hardware_control/protocols/liquid_handler.py +266 -0
- opentrons/hardware_control/protocols/module_provider.py +16 -0
- opentrons/hardware_control/protocols/motion_controller.py +243 -0
- opentrons/hardware_control/protocols/position_estimator.py +45 -0
- opentrons/hardware_control/protocols/simulatable.py +10 -0
- opentrons/hardware_control/protocols/stoppable.py +9 -0
- opentrons/hardware_control/protocols/types.py +27 -0
- opentrons/hardware_control/robot_calibration.py +224 -0
- opentrons/hardware_control/scripts/README.md +28 -0
- opentrons/hardware_control/scripts/__init__.py +1 -0
- opentrons/hardware_control/scripts/gripper_control.py +208 -0
- opentrons/hardware_control/scripts/ot3gripper +7 -0
- opentrons/hardware_control/scripts/ot3repl +7 -0
- opentrons/hardware_control/scripts/repl.py +187 -0
- opentrons/hardware_control/scripts/tc_control.py +97 -0
- opentrons/hardware_control/simulator_setup.py +260 -0
- opentrons/hardware_control/thread_manager.py +431 -0
- opentrons/hardware_control/threaded_async_lock.py +97 -0
- opentrons/hardware_control/types.py +792 -0
- opentrons/hardware_control/util.py +234 -0
- opentrons/legacy_broker.py +53 -0
- opentrons/legacy_commands/__init__.py +1 -0
- opentrons/legacy_commands/commands.py +483 -0
- opentrons/legacy_commands/helpers.py +153 -0
- opentrons/legacy_commands/module_commands.py +215 -0
- opentrons/legacy_commands/protocol_commands.py +54 -0
- opentrons/legacy_commands/publisher.py +155 -0
- opentrons/legacy_commands/robot_commands.py +51 -0
- opentrons/legacy_commands/types.py +1115 -0
- opentrons/motion_planning/__init__.py +32 -0
- opentrons/motion_planning/adjacent_slots_getters.py +168 -0
- opentrons/motion_planning/deck_conflict.py +396 -0
- opentrons/motion_planning/errors.py +35 -0
- opentrons/motion_planning/types.py +42 -0
- opentrons/motion_planning/waypoints.py +218 -0
- opentrons/ordered_set.py +138 -0
- opentrons/protocol_api/__init__.py +105 -0
- opentrons/protocol_api/_liquid.py +157 -0
- opentrons/protocol_api/_liquid_properties.py +814 -0
- opentrons/protocol_api/_nozzle_layout.py +31 -0
- opentrons/protocol_api/_parameter_context.py +300 -0
- opentrons/protocol_api/_parameters.py +31 -0
- opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
- opentrons/protocol_api/_types.py +43 -0
- opentrons/protocol_api/config.py +23 -0
- opentrons/protocol_api/core/__init__.py +23 -0
- opentrons/protocol_api/core/common.py +33 -0
- opentrons/protocol_api/core/core_map.py +74 -0
- opentrons/protocol_api/core/engine/__init__.py +22 -0
- opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
- opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
- opentrons/protocol_api/core/engine/exceptions.py +19 -0
- opentrons/protocol_api/core/engine/instrument.py +2391 -0
- opentrons/protocol_api/core/engine/labware.py +238 -0
- opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
- opentrons/protocol_api/core/engine/module_core.py +1025 -0
- opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
- opentrons/protocol_api/core/engine/point_calculations.py +64 -0
- opentrons/protocol_api/core/engine/protocol.py +1153 -0
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/stringify.py +74 -0
- opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
- opentrons/protocol_api/core/engine/well.py +241 -0
- opentrons/protocol_api/core/instrument.py +459 -0
- opentrons/protocol_api/core/labware.py +151 -0
- opentrons/protocol_api/core/legacy/__init__.py +11 -0
- opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
- opentrons/protocol_api/core/legacy/deck.py +369 -0
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
- opentrons/protocol_api/core/legacy/load_info.py +67 -0
- opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
- opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
- opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
- opentrons/protocol_api/core/module.py +484 -0
- opentrons/protocol_api/core/protocol.py +311 -0
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/core/well.py +116 -0
- opentrons/protocol_api/core/well_grid.py +45 -0
- opentrons/protocol_api/create_protocol_context.py +177 -0
- opentrons/protocol_api/deck.py +223 -0
- opentrons/protocol_api/disposal_locations.py +244 -0
- opentrons/protocol_api/instrument_context.py +3212 -0
- opentrons/protocol_api/labware.py +1579 -0
- opentrons/protocol_api/module_contexts.py +1425 -0
- opentrons/protocol_api/module_validation_and_errors.py +61 -0
- opentrons/protocol_api/protocol_context.py +1688 -0
- opentrons/protocol_api/robot_context.py +303 -0
- opentrons/protocol_api/validation.py +761 -0
- opentrons/protocol_engine/__init__.py +155 -0
- opentrons/protocol_engine/actions/__init__.py +65 -0
- opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
- opentrons/protocol_engine/actions/action_handler.py +13 -0
- opentrons/protocol_engine/actions/actions.py +302 -0
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/__init__.py +5 -0
- opentrons/protocol_engine/clients/sync_client.py +174 -0
- opentrons/protocol_engine/clients/transports.py +197 -0
- opentrons/protocol_engine/commands/__init__.py +757 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
- opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
- opentrons/protocol_engine/commands/aspirate.py +244 -0
- opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
- opentrons/protocol_engine/commands/blow_out.py +146 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
- opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
- opentrons/protocol_engine/commands/command.py +308 -0
- opentrons/protocol_engine/commands/command_unions.py +974 -0
- opentrons/protocol_engine/commands/comment.py +57 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
- opentrons/protocol_engine/commands/custom.py +67 -0
- opentrons/protocol_engine/commands/dispense.py +194 -0
- opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +232 -0
- opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
- opentrons/protocol_engine/commands/hash_command_params.py +38 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
- opentrons/protocol_engine/commands/home.py +100 -0
- opentrons/protocol_engine/commands/identify_module.py +86 -0
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +464 -0
- opentrons/protocol_engine/commands/load_labware.py +210 -0
- opentrons/protocol_engine/commands/load_lid.py +154 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
- opentrons/protocol_engine/commands/load_liquid.py +95 -0
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +223 -0
- opentrons/protocol_engine/commands/load_pipette.py +167 -0
- opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
- opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
- opentrons/protocol_engine/commands/move_labware.py +546 -0
- opentrons/protocol_engine/commands/move_relative.py +102 -0
- opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
- opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
- opentrons/protocol_engine/commands/move_to_well.py +119 -0
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
- opentrons/protocol_engine/commands/pipetting_common.py +443 -0
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
- opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
- opentrons/protocol_engine/commands/reload_labware.py +90 -0
- opentrons/protocol_engine/commands/retract_axis.py +75 -0
- opentrons/protocol_engine/commands/robot/__init__.py +70 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/save_position.py +109 -0
- opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
- opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
- opentrons/protocol_engine/commands/set_status_bar.py +89 -0
- opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
- opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
- opentrons/protocol_engine/commands/touch_tip.py +189 -0
- opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
- opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
- opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
- opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
- opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
- opentrons/protocol_engine/create_protocol_engine.py +193 -0
- opentrons/protocol_engine/engine_support.py +28 -0
- opentrons/protocol_engine/error_recovery_policy.py +81 -0
- opentrons/protocol_engine/errors/__init__.py +191 -0
- opentrons/protocol_engine/errors/error_occurrence.py +182 -0
- opentrons/protocol_engine/errors/exceptions.py +1308 -0
- opentrons/protocol_engine/execution/__init__.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +216 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
- opentrons/protocol_engine/execution/door_watcher.py +119 -0
- opentrons/protocol_engine/execution/equipment.py +819 -0
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +686 -0
- opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
- opentrons/protocol_engine/execution/labware_movement.py +297 -0
- opentrons/protocol_engine/execution/movement.py +349 -0
- opentrons/protocol_engine/execution/pipetting.py +607 -0
- opentrons/protocol_engine/execution/queue_worker.py +86 -0
- opentrons/protocol_engine/execution/rail_lights.py +25 -0
- opentrons/protocol_engine/execution/run_control.py +33 -0
- opentrons/protocol_engine/execution/status_bar.py +34 -0
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
- opentrons/protocol_engine/execution/tip_handler.py +550 -0
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/notes/__init__.py +17 -0
- opentrons/protocol_engine/notes/notes.py +59 -0
- opentrons/protocol_engine/plugins.py +104 -0
- opentrons/protocol_engine/protocol_engine.py +683 -0
- opentrons/protocol_engine/resources/__init__.py +26 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +58 -0
- opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
- opentrons/protocol_engine/resources/labware_validation.py +73 -0
- opentrons/protocol_engine/resources/model_utils.py +32 -0
- opentrons/protocol_engine/resources/module_data_provider.py +44 -0
- opentrons/protocol_engine/resources/ot3_validation.py +21 -0
- opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
- opentrons/protocol_engine/slot_standardization.py +128 -0
- opentrons/protocol_engine/state/__init__.py +1 -0
- opentrons/protocol_engine/state/_abstract_store.py +27 -0
- opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_move_types.py +83 -0
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +699 -0
- opentrons/protocol_engine/state/command_history.py +309 -0
- opentrons/protocol_engine/state/commands.py +1158 -0
- opentrons/protocol_engine/state/config.py +39 -0
- opentrons/protocol_engine/state/files.py +57 -0
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/geometry.py +2359 -0
- opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
- opentrons/protocol_engine/state/labware.py +1459 -0
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +73 -0
- opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
- opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
- opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
- opentrons/protocol_engine/state/modules.py +1500 -0
- opentrons/protocol_engine/state/motion.py +373 -0
- opentrons/protocol_engine/state/pipettes.py +905 -0
- opentrons/protocol_engine/state/state.py +421 -0
- opentrons/protocol_engine/state/state_summary.py +36 -0
- opentrons/protocol_engine/state/tips.py +420 -0
- opentrons/protocol_engine/state/update_types.py +904 -0
- opentrons/protocol_engine/state/wells.py +290 -0
- opentrons/protocol_engine/types/__init__.py +308 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +81 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +131 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +303 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/__init__.py +37 -0
- opentrons/protocol_reader/extract_labware_definitions.py +66 -0
- opentrons/protocol_reader/file_format_validator.py +152 -0
- opentrons/protocol_reader/file_hasher.py +27 -0
- opentrons/protocol_reader/file_identifier.py +284 -0
- opentrons/protocol_reader/file_reader_writer.py +90 -0
- opentrons/protocol_reader/input_file.py +16 -0
- opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
- opentrons/protocol_reader/protocol_reader.py +188 -0
- opentrons/protocol_reader/protocol_source.py +124 -0
- opentrons/protocol_reader/role_analyzer.py +86 -0
- opentrons/protocol_runner/__init__.py +26 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
- opentrons/protocol_runner/json_file_reader.py +55 -0
- opentrons/protocol_runner/json_translator.py +314 -0
- opentrons/protocol_runner/legacy_command_mapper.py +848 -0
- opentrons/protocol_runner/legacy_context_plugin.py +116 -0
- opentrons/protocol_runner/protocol_runner.py +530 -0
- opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
- opentrons/protocol_runner/run_orchestrator.py +496 -0
- opentrons/protocol_runner/task_queue.py +95 -0
- opentrons/protocols/__init__.py +6 -0
- opentrons/protocols/advanced_control/__init__.py +0 -0
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +60 -0
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +180 -0
- opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
- opentrons/protocols/api_support/__init__.py +0 -0
- opentrons/protocols/api_support/constants.py +8 -0
- opentrons/protocols/api_support/deck_type.py +110 -0
- opentrons/protocols/api_support/definitions.py +18 -0
- opentrons/protocols/api_support/instrument.py +151 -0
- opentrons/protocols/api_support/labware_like.py +233 -0
- opentrons/protocols/api_support/tip_tracker.py +175 -0
- opentrons/protocols/api_support/types.py +32 -0
- opentrons/protocols/api_support/util.py +403 -0
- opentrons/protocols/bundle.py +89 -0
- opentrons/protocols/duration/__init__.py +4 -0
- opentrons/protocols/duration/errors.py +5 -0
- opentrons/protocols/duration/estimator.py +628 -0
- opentrons/protocols/execution/__init__.py +0 -0
- opentrons/protocols/execution/dev_types.py +181 -0
- opentrons/protocols/execution/errors.py +40 -0
- opentrons/protocols/execution/execute.py +84 -0
- opentrons/protocols/execution/execute_json_v3.py +275 -0
- opentrons/protocols/execution/execute_json_v4.py +359 -0
- opentrons/protocols/execution/execute_json_v5.py +28 -0
- opentrons/protocols/execution/execute_python.py +169 -0
- opentrons/protocols/execution/json_dispatchers.py +87 -0
- opentrons/protocols/execution/types.py +7 -0
- opentrons/protocols/geometry/__init__.py +0 -0
- opentrons/protocols/geometry/planning.py +297 -0
- opentrons/protocols/labware.py +312 -0
- opentrons/protocols/models/__init__.py +0 -0
- opentrons/protocols/models/json_protocol.py +679 -0
- opentrons/protocols/parameters/__init__.py +0 -0
- opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
- opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
- opentrons/protocols/parameters/exceptions.py +34 -0
- opentrons/protocols/parameters/parameter_definition.py +272 -0
- opentrons/protocols/parameters/types.py +17 -0
- opentrons/protocols/parameters/validation.py +267 -0
- opentrons/protocols/parse.py +671 -0
- opentrons/protocols/types.py +159 -0
- opentrons/py.typed +0 -0
- opentrons/resources/scripts/lpc21isp +0 -0
- opentrons/resources/smoothie-edge-8414642.hex +23010 -0
- opentrons/simulate.py +1065 -0
- opentrons/system/__init__.py +6 -0
- opentrons/system/camera.py +51 -0
- opentrons/system/log_control.py +59 -0
- opentrons/system/nmcli.py +856 -0
- opentrons/system/resin.py +24 -0
- opentrons/system/smoothie_update.py +15 -0
- opentrons/system/wifi.py +204 -0
- opentrons/tools/__init__.py +0 -0
- opentrons/tools/args_handler.py +22 -0
- opentrons/tools/write_pipette_memory.py +157 -0
- opentrons/types.py +618 -0
- opentrons/util/__init__.py +1 -0
- opentrons/util/async_helpers.py +166 -0
- opentrons/util/broker.py +84 -0
- opentrons/util/change_notifier.py +47 -0
- opentrons/util/entrypoint_util.py +278 -0
- opentrons/util/get_union_elements.py +26 -0
- opentrons/util/helpers.py +6 -0
- opentrons/util/linal.py +178 -0
- opentrons/util/logging_config.py +265 -0
- opentrons/util/logging_queue_handler.py +61 -0
- opentrons/util/performance_helpers.py +157 -0
- opentrons-8.6.0a1.dist-info/METADATA +37 -0
- opentrons-8.6.0a1.dist-info/RECORD +600 -0
- opentrons-8.6.0a1.dist-info/WHEEL +4 -0
- opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
- opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,1500 @@
|
|
|
1
|
+
"""Basic modules data state and store."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import (
|
|
8
|
+
Dict,
|
|
9
|
+
List,
|
|
10
|
+
NamedTuple,
|
|
11
|
+
Optional,
|
|
12
|
+
Sequence,
|
|
13
|
+
Type,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
overload,
|
|
17
|
+
)
|
|
18
|
+
from numpy import array, dot, double as npdouble
|
|
19
|
+
from numpy.typing import NDArray
|
|
20
|
+
|
|
21
|
+
from opentrons.hardware_control.modules.magdeck import (
|
|
22
|
+
OFFSET_TO_LABWARE_BOTTOM as MAGNETIC_MODULE_OFFSET_TO_LABWARE_BOTTOM,
|
|
23
|
+
)
|
|
24
|
+
from opentrons.hardware_control.modules.types import LiveData
|
|
25
|
+
from opentrons.motion_planning.adjacent_slots_getters import (
|
|
26
|
+
get_east_slot,
|
|
27
|
+
get_west_slot,
|
|
28
|
+
get_adjacent_staging_slot,
|
|
29
|
+
)
|
|
30
|
+
from opentrons.protocol_engine.actions.get_state_update import get_state_updates
|
|
31
|
+
from opentrons.protocol_engine.commands.calibration.calibrate_module import (
|
|
32
|
+
CalibrateModuleResult,
|
|
33
|
+
)
|
|
34
|
+
from opentrons.protocol_engine.state import update_types
|
|
35
|
+
from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate import (
|
|
36
|
+
AbsorbanceReaderMeasureMode,
|
|
37
|
+
)
|
|
38
|
+
from opentrons.types import DeckSlotName, MountType, Point, StagingSlotName
|
|
39
|
+
from .update_types import (
|
|
40
|
+
AbsorbanceReaderStateUpdate,
|
|
41
|
+
FlexStackerStateUpdate,
|
|
42
|
+
LoadModuleUpdate,
|
|
43
|
+
)
|
|
44
|
+
from ..errors import ModuleNotConnectedError, AreaNotInDeckConfigurationError
|
|
45
|
+
from ..resources import deck_configuration_provider
|
|
46
|
+
|
|
47
|
+
from ..types import (
|
|
48
|
+
LoadedModule,
|
|
49
|
+
ModuleModel,
|
|
50
|
+
ModuleOffsetVector,
|
|
51
|
+
ModuleOffsetData,
|
|
52
|
+
ModuleType,
|
|
53
|
+
ModuleDefinition,
|
|
54
|
+
DeckSlotLocation,
|
|
55
|
+
ModuleDimensions,
|
|
56
|
+
HeaterShakerLatchStatus,
|
|
57
|
+
HeaterShakerMovementRestrictors,
|
|
58
|
+
DeckType,
|
|
59
|
+
LabwareMovementOffsetData,
|
|
60
|
+
AddressableAreaLocation,
|
|
61
|
+
StackerStoredLabwareGroup,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
from ..resources import DeckFixedLabware
|
|
65
|
+
from .addressable_areas import AddressableAreaView
|
|
66
|
+
from .. import errors
|
|
67
|
+
from ..commands import (
|
|
68
|
+
Command,
|
|
69
|
+
heater_shaker,
|
|
70
|
+
temperature_module,
|
|
71
|
+
thermocycler,
|
|
72
|
+
)
|
|
73
|
+
from ..actions import (
|
|
74
|
+
Action,
|
|
75
|
+
SucceedCommandAction,
|
|
76
|
+
AddModuleAction,
|
|
77
|
+
)
|
|
78
|
+
from ._abstract_store import HasState, HandlesActions
|
|
79
|
+
from .module_substates import (
|
|
80
|
+
MagneticModuleSubState,
|
|
81
|
+
HeaterShakerModuleSubState,
|
|
82
|
+
TemperatureModuleSubState,
|
|
83
|
+
ThermocyclerModuleSubState,
|
|
84
|
+
AbsorbanceReaderSubState,
|
|
85
|
+
FlexStackerSubState,
|
|
86
|
+
MagneticModuleId,
|
|
87
|
+
HeaterShakerModuleId,
|
|
88
|
+
TemperatureModuleId,
|
|
89
|
+
ThermocyclerModuleId,
|
|
90
|
+
AbsorbanceReaderId,
|
|
91
|
+
FlexStackerId,
|
|
92
|
+
MagneticBlockSubState,
|
|
93
|
+
MagneticBlockId,
|
|
94
|
+
ModuleSubStateType,
|
|
95
|
+
)
|
|
96
|
+
from .config import Config
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
ModuleSubStateT = TypeVar("ModuleSubStateT", bound=ModuleSubStateType)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class SlotTransit(NamedTuple):
|
|
103
|
+
"""Class defining starting and ending slots in a pipette movement."""
|
|
104
|
+
|
|
105
|
+
start: DeckSlotName
|
|
106
|
+
end: DeckSlotName
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
_OT2_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE = {
|
|
110
|
+
SlotTransit(start=DeckSlotName.SLOT_1, end=DeckSlotName.FIXED_TRASH),
|
|
111
|
+
SlotTransit(start=DeckSlotName.FIXED_TRASH, end=DeckSlotName.SLOT_1),
|
|
112
|
+
SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.FIXED_TRASH),
|
|
113
|
+
SlotTransit(start=DeckSlotName.FIXED_TRASH, end=DeckSlotName.SLOT_4),
|
|
114
|
+
SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.SLOT_9),
|
|
115
|
+
SlotTransit(start=DeckSlotName.SLOT_9, end=DeckSlotName.SLOT_4),
|
|
116
|
+
SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.SLOT_8),
|
|
117
|
+
SlotTransit(start=DeckSlotName.SLOT_8, end=DeckSlotName.SLOT_4),
|
|
118
|
+
SlotTransit(start=DeckSlotName.SLOT_1, end=DeckSlotName.SLOT_8),
|
|
119
|
+
SlotTransit(start=DeckSlotName.SLOT_8, end=DeckSlotName.SLOT_1),
|
|
120
|
+
SlotTransit(start=DeckSlotName.SLOT_4, end=DeckSlotName.SLOT_11),
|
|
121
|
+
SlotTransit(start=DeckSlotName.SLOT_11, end=DeckSlotName.SLOT_4),
|
|
122
|
+
SlotTransit(start=DeckSlotName.SLOT_1, end=DeckSlotName.SLOT_11),
|
|
123
|
+
SlotTransit(start=DeckSlotName.SLOT_11, end=DeckSlotName.SLOT_1),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_OT3_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE = {
|
|
127
|
+
SlotTransit(start=t.start.to_ot3_equivalent(), end=t.end.to_ot3_equivalent())
|
|
128
|
+
for t in _OT2_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE = (
|
|
132
|
+
_OT2_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE | _OT3_THERMOCYCLER_SLOT_TRANSITS_TO_DODGE
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
_THERMOCYCLER_SLOT = DeckSlotName.SLOT_B1
|
|
136
|
+
_OT2_THERMOCYCLER_ADDITIONAL_SLOTS = [
|
|
137
|
+
DeckSlotName.SLOT_8,
|
|
138
|
+
DeckSlotName.SLOT_10,
|
|
139
|
+
DeckSlotName.SLOT_11,
|
|
140
|
+
]
|
|
141
|
+
_OT3_THERMOCYCLER_ADDITIONAL_SLOTS = [DeckSlotName.SLOT_A1]
|
|
142
|
+
|
|
143
|
+
_COLUMN_4_MODULES = [ModuleModel.FLEX_STACKER_MODULE_V1]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True)
|
|
147
|
+
class HardwareModule:
|
|
148
|
+
"""Data describing an actually connected module."""
|
|
149
|
+
|
|
150
|
+
serial_number: Optional[str]
|
|
151
|
+
definition: ModuleDefinition
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class ModuleState:
|
|
156
|
+
"""The internal data to keep track of loaded modules."""
|
|
157
|
+
|
|
158
|
+
load_location_by_module_id: Dict[str, Optional[str]]
|
|
159
|
+
"""The Cutout ID of the cutout (Flex) or slot (OT-2) that each module has been loaded.
|
|
160
|
+
|
|
161
|
+
This will be None when the module was added via
|
|
162
|
+
ProtocolEngine.use_attached_modules() instead of an explicit loadModule command.
|
|
163
|
+
AddressableAreaLocation is used to represent a literal Deck Slot for OT-2 locations.
|
|
164
|
+
The CutoutID string for a given Cutout that a Module Fixture is loaded into is used
|
|
165
|
+
for Flex. The type distinction is in place for implementation seperation between the two.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
additional_slots_occupied_by_module_id: Dict[str, List[DeckSlotName]]
|
|
169
|
+
"""List of additional slots occupied by each module.
|
|
170
|
+
|
|
171
|
+
The thermocycler (both GENs), occupies multiple slots on both OT-2 and the Flex
|
|
172
|
+
but only one slot is associated with the location of the thermocycler.
|
|
173
|
+
In order to check for deck conflicts with other items, we will keep track of any
|
|
174
|
+
additional slots occupied by a module here.
|
|
175
|
+
|
|
176
|
+
This will be None when a module occupies only one slot.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
requested_model_by_id: Dict[str, Optional[ModuleModel]]
|
|
180
|
+
"""The model by which each loaded module was requested.
|
|
181
|
+
|
|
182
|
+
Becuse of module compatibility, this can differ from the model found through
|
|
183
|
+
hardware_module_by_id. See `ModuleView.get_requested_model()` versus
|
|
184
|
+
`ModuleView.get_connected_model()`.
|
|
185
|
+
|
|
186
|
+
This will be None when the module was added via
|
|
187
|
+
ProtocolEngine.use_attached_modules() instead of an explicit loadModule command.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
hardware_by_module_id: Dict[str, HardwareModule]
|
|
191
|
+
"""Information about each module's physical hardware."""
|
|
192
|
+
|
|
193
|
+
substate_by_module_id: Dict[str, ModuleSubStateType]
|
|
194
|
+
"""Information about each module that's specific to the module type."""
|
|
195
|
+
|
|
196
|
+
module_offset_by_serial: Dict[str, ModuleOffsetData]
|
|
197
|
+
"""Information about each modules offsets."""
|
|
198
|
+
|
|
199
|
+
deck_type: DeckType
|
|
200
|
+
"""Type of deck that the modules are on."""
|
|
201
|
+
|
|
202
|
+
deck_fixed_labware: Sequence[DeckFixedLabware]
|
|
203
|
+
"""Fixed labware from the deck which may be assigned to a module.
|
|
204
|
+
|
|
205
|
+
The Opentrons Plate Reader module makes use of an electronic Lid labware which moves
|
|
206
|
+
between the Reader and Dock positions, and is pre-loaded into the engine as to persist
|
|
207
|
+
even when not in use. For this reason, we inject it here when an appropriate match
|
|
208
|
+
is identified.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ModuleStore(HasState[ModuleState], HandlesActions):
|
|
213
|
+
"""Module state container."""
|
|
214
|
+
|
|
215
|
+
_state: ModuleState
|
|
216
|
+
|
|
217
|
+
def __init__(
|
|
218
|
+
self,
|
|
219
|
+
config: Config,
|
|
220
|
+
deck_fixed_labware: Sequence[DeckFixedLabware],
|
|
221
|
+
module_calibration_offsets: Optional[Dict[str, ModuleOffsetData]] = None,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Initialize a ModuleStore and its state."""
|
|
224
|
+
self._state = ModuleState(
|
|
225
|
+
load_location_by_module_id={},
|
|
226
|
+
additional_slots_occupied_by_module_id={},
|
|
227
|
+
requested_model_by_id={},
|
|
228
|
+
hardware_by_module_id={},
|
|
229
|
+
substate_by_module_id={},
|
|
230
|
+
module_offset_by_serial=module_calibration_offsets or {},
|
|
231
|
+
deck_type=config.deck_type,
|
|
232
|
+
deck_fixed_labware=deck_fixed_labware,
|
|
233
|
+
)
|
|
234
|
+
self._robot_type = config.robot_type
|
|
235
|
+
|
|
236
|
+
def handle_action(self, action: Action) -> None:
|
|
237
|
+
"""Modify state in reaction to an action."""
|
|
238
|
+
if isinstance(action, SucceedCommandAction):
|
|
239
|
+
self._handle_command(action.command)
|
|
240
|
+
|
|
241
|
+
elif isinstance(action, AddModuleAction):
|
|
242
|
+
self._add_module_substate(
|
|
243
|
+
module_id=action.module_id,
|
|
244
|
+
definition=action.definition,
|
|
245
|
+
serial_number=action.serial_number,
|
|
246
|
+
slot_name=None,
|
|
247
|
+
requested_model=None,
|
|
248
|
+
module_live_data=action.module_live_data,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
for state_update in get_state_updates(action):
|
|
252
|
+
self._handle_state_update(state_update)
|
|
253
|
+
|
|
254
|
+
def _handle_command(self, command: Command) -> None:
|
|
255
|
+
# todo(mm, 2024-11-04): Delete this function. Port these isinstance()
|
|
256
|
+
# checks to the update_types.StateUpdate mechanism.
|
|
257
|
+
|
|
258
|
+
if isinstance(command.result, CalibrateModuleResult):
|
|
259
|
+
self._update_module_calibration(
|
|
260
|
+
module_id=command.params.moduleId,
|
|
261
|
+
module_offset=command.result.moduleOffset,
|
|
262
|
+
location=command.result.location,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if isinstance(
|
|
266
|
+
command.result,
|
|
267
|
+
(
|
|
268
|
+
heater_shaker.SetTargetTemperatureResult,
|
|
269
|
+
heater_shaker.DeactivateHeaterResult,
|
|
270
|
+
heater_shaker.SetAndWaitForShakeSpeedResult,
|
|
271
|
+
heater_shaker.DeactivateShakerResult,
|
|
272
|
+
heater_shaker.OpenLabwareLatchResult,
|
|
273
|
+
heater_shaker.CloseLabwareLatchResult,
|
|
274
|
+
),
|
|
275
|
+
):
|
|
276
|
+
self._handle_heater_shaker_commands(command)
|
|
277
|
+
|
|
278
|
+
if isinstance(
|
|
279
|
+
command.result,
|
|
280
|
+
(
|
|
281
|
+
temperature_module.SetTargetTemperatureResult,
|
|
282
|
+
temperature_module.DeactivateTemperatureResult,
|
|
283
|
+
),
|
|
284
|
+
):
|
|
285
|
+
self._handle_temperature_module_commands(command)
|
|
286
|
+
|
|
287
|
+
if isinstance(
|
|
288
|
+
command.result,
|
|
289
|
+
(
|
|
290
|
+
thermocycler.SetTargetBlockTemperatureResult,
|
|
291
|
+
thermocycler.DeactivateBlockResult,
|
|
292
|
+
thermocycler.SetTargetLidTemperatureResult,
|
|
293
|
+
thermocycler.DeactivateLidResult,
|
|
294
|
+
thermocycler.OpenLidResult,
|
|
295
|
+
thermocycler.CloseLidResult,
|
|
296
|
+
),
|
|
297
|
+
):
|
|
298
|
+
self._handle_thermocycler_module_commands(command)
|
|
299
|
+
|
|
300
|
+
def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
|
|
301
|
+
if state_update.loaded_module != update_types.NO_CHANGE:
|
|
302
|
+
self._handle_load_module(state_update.loaded_module)
|
|
303
|
+
|
|
304
|
+
if state_update.absorbance_reader_state_update != update_types.NO_CHANGE:
|
|
305
|
+
self._handle_absorbance_reader_commands(
|
|
306
|
+
state_update.absorbance_reader_state_update
|
|
307
|
+
)
|
|
308
|
+
if state_update.flex_stacker_state_update != update_types.NO_CHANGE:
|
|
309
|
+
self._handle_flex_stacker_commands(state_update.flex_stacker_state_update)
|
|
310
|
+
|
|
311
|
+
def _add_module_substate(
|
|
312
|
+
self,
|
|
313
|
+
module_id: str,
|
|
314
|
+
serial_number: Optional[str],
|
|
315
|
+
definition: ModuleDefinition,
|
|
316
|
+
slot_name: Optional[DeckSlotName],
|
|
317
|
+
requested_model: Optional[ModuleModel],
|
|
318
|
+
module_live_data: Optional[LiveData],
|
|
319
|
+
) -> None:
|
|
320
|
+
# Loading slot name to Cutout ID (Flex)(OT-2) resolution
|
|
321
|
+
load_location: Optional[str]
|
|
322
|
+
if slot_name is not None:
|
|
323
|
+
load_location = deck_configuration_provider.get_cutout_id_by_deck_slot_name(
|
|
324
|
+
slot_name
|
|
325
|
+
)
|
|
326
|
+
else:
|
|
327
|
+
load_location = slot_name
|
|
328
|
+
|
|
329
|
+
actual_model = definition.model
|
|
330
|
+
live_data = module_live_data["data"] if module_live_data else None
|
|
331
|
+
self._state.requested_model_by_id[module_id] = requested_model
|
|
332
|
+
self._state.load_location_by_module_id[module_id] = load_location
|
|
333
|
+
self._state.hardware_by_module_id[module_id] = HardwareModule(
|
|
334
|
+
serial_number=serial_number,
|
|
335
|
+
definition=definition,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if ModuleModel.is_magnetic_module_model(actual_model):
|
|
339
|
+
self._state.substate_by_module_id[module_id] = MagneticModuleSubState(
|
|
340
|
+
module_id=MagneticModuleId(module_id),
|
|
341
|
+
model=actual_model,
|
|
342
|
+
)
|
|
343
|
+
elif ModuleModel.is_heater_shaker_module_model(actual_model):
|
|
344
|
+
self._state.substate_by_module_id[
|
|
345
|
+
module_id
|
|
346
|
+
] = HeaterShakerModuleSubState.from_live_data(
|
|
347
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
348
|
+
data=live_data,
|
|
349
|
+
)
|
|
350
|
+
elif ModuleModel.is_temperature_module_model(actual_model):
|
|
351
|
+
self._state.substate_by_module_id[
|
|
352
|
+
module_id
|
|
353
|
+
] = TemperatureModuleSubState.from_live_data(
|
|
354
|
+
module_id=TemperatureModuleId(module_id),
|
|
355
|
+
data=live_data,
|
|
356
|
+
)
|
|
357
|
+
elif ModuleModel.is_thermocycler_module_model(actual_model):
|
|
358
|
+
self._state.substate_by_module_id[
|
|
359
|
+
module_id
|
|
360
|
+
] = ThermocyclerModuleSubState.from_live_data(
|
|
361
|
+
module_id=ThermocyclerModuleId(module_id), data=live_data
|
|
362
|
+
)
|
|
363
|
+
self._update_additional_slots_occupied_by_thermocycler(
|
|
364
|
+
module_id=module_id, slot_name=slot_name
|
|
365
|
+
)
|
|
366
|
+
elif ModuleModel.is_magnetic_block(actual_model):
|
|
367
|
+
self._state.substate_by_module_id[module_id] = MagneticBlockSubState(
|
|
368
|
+
module_id=MagneticBlockId(module_id)
|
|
369
|
+
)
|
|
370
|
+
elif ModuleModel.is_absorbance_reader(actual_model):
|
|
371
|
+
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
|
|
372
|
+
module_id=AbsorbanceReaderId(module_id),
|
|
373
|
+
configured=False,
|
|
374
|
+
measured=False,
|
|
375
|
+
is_lid_on=True,
|
|
376
|
+
data=None,
|
|
377
|
+
measure_mode=None,
|
|
378
|
+
configured_wavelengths=None,
|
|
379
|
+
reference_wavelength=None,
|
|
380
|
+
)
|
|
381
|
+
elif ModuleModel.is_flex_stacker(actual_model):
|
|
382
|
+
self._state.substate_by_module_id[module_id] = FlexStackerSubState(
|
|
383
|
+
module_id=FlexStackerId(module_id),
|
|
384
|
+
pool_primary_definition=None,
|
|
385
|
+
pool_adapter_definition=None,
|
|
386
|
+
pool_lid_definition=None,
|
|
387
|
+
contained_labware_bottom_first=[],
|
|
388
|
+
max_pool_count=0,
|
|
389
|
+
pool_overlap=0,
|
|
390
|
+
pool_height=0,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def _update_additional_slots_occupied_by_thermocycler(
|
|
394
|
+
self,
|
|
395
|
+
module_id: str,
|
|
396
|
+
slot_name: Optional[
|
|
397
|
+
DeckSlotName
|
|
398
|
+
], # addModuleAction will not have a slot location
|
|
399
|
+
) -> None:
|
|
400
|
+
if slot_name != _THERMOCYCLER_SLOT.to_equivalent_for_robot_type(
|
|
401
|
+
self._robot_type
|
|
402
|
+
):
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
self._state.additional_slots_occupied_by_module_id[module_id] = (
|
|
406
|
+
_OT3_THERMOCYCLER_ADDITIONAL_SLOTS
|
|
407
|
+
if self._state.deck_type == DeckType.OT3_STANDARD
|
|
408
|
+
else _OT2_THERMOCYCLER_ADDITIONAL_SLOTS
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def _update_module_calibration(
|
|
412
|
+
self,
|
|
413
|
+
module_id: str,
|
|
414
|
+
module_offset: ModuleOffsetVector,
|
|
415
|
+
location: DeckSlotLocation,
|
|
416
|
+
) -> None:
|
|
417
|
+
module = self._state.hardware_by_module_id.get(module_id)
|
|
418
|
+
if module:
|
|
419
|
+
module_serial = module.serial_number
|
|
420
|
+
assert (
|
|
421
|
+
module_serial is not None
|
|
422
|
+
), "Expected a module SN and got None instead."
|
|
423
|
+
self._state.module_offset_by_serial[module_serial] = ModuleOffsetData(
|
|
424
|
+
moduleOffsetVector=module_offset,
|
|
425
|
+
location=location,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def _handle_heater_shaker_commands(
|
|
429
|
+
self,
|
|
430
|
+
command: Union[
|
|
431
|
+
heater_shaker.SetTargetTemperature,
|
|
432
|
+
heater_shaker.DeactivateHeater,
|
|
433
|
+
heater_shaker.SetAndWaitForShakeSpeed,
|
|
434
|
+
heater_shaker.DeactivateShaker,
|
|
435
|
+
heater_shaker.OpenLabwareLatch,
|
|
436
|
+
heater_shaker.CloseLabwareLatch,
|
|
437
|
+
],
|
|
438
|
+
) -> None:
|
|
439
|
+
module_id = command.params.moduleId
|
|
440
|
+
hs_substate = self._state.substate_by_module_id[module_id]
|
|
441
|
+
assert isinstance(
|
|
442
|
+
hs_substate, HeaterShakerModuleSubState
|
|
443
|
+
), f"{module_id} is not heater-shaker."
|
|
444
|
+
|
|
445
|
+
# Get current values to preserve target temperature not being set/deactivated
|
|
446
|
+
prev_state: HeaterShakerModuleSubState = hs_substate
|
|
447
|
+
|
|
448
|
+
if isinstance(command.result, heater_shaker.SetTargetTemperatureResult):
|
|
449
|
+
self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
|
|
450
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
451
|
+
labware_latch_status=prev_state.labware_latch_status,
|
|
452
|
+
is_plate_shaking=prev_state.is_plate_shaking,
|
|
453
|
+
plate_target_temperature=command.params.celsius,
|
|
454
|
+
)
|
|
455
|
+
elif isinstance(command.result, heater_shaker.DeactivateHeaterResult):
|
|
456
|
+
self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
|
|
457
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
458
|
+
labware_latch_status=prev_state.labware_latch_status,
|
|
459
|
+
is_plate_shaking=prev_state.is_plate_shaking,
|
|
460
|
+
plate_target_temperature=None,
|
|
461
|
+
)
|
|
462
|
+
elif isinstance(command.result, heater_shaker.SetAndWaitForShakeSpeedResult):
|
|
463
|
+
self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
|
|
464
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
465
|
+
labware_latch_status=prev_state.labware_latch_status,
|
|
466
|
+
is_plate_shaking=True,
|
|
467
|
+
plate_target_temperature=prev_state.plate_target_temperature,
|
|
468
|
+
)
|
|
469
|
+
elif isinstance(command.result, heater_shaker.DeactivateShakerResult):
|
|
470
|
+
self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
|
|
471
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
472
|
+
labware_latch_status=prev_state.labware_latch_status,
|
|
473
|
+
is_plate_shaking=False,
|
|
474
|
+
plate_target_temperature=prev_state.plate_target_temperature,
|
|
475
|
+
)
|
|
476
|
+
elif isinstance(command.result, heater_shaker.OpenLabwareLatchResult):
|
|
477
|
+
self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
|
|
478
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
479
|
+
labware_latch_status=HeaterShakerLatchStatus.OPEN,
|
|
480
|
+
is_plate_shaking=prev_state.is_plate_shaking,
|
|
481
|
+
plate_target_temperature=prev_state.plate_target_temperature,
|
|
482
|
+
)
|
|
483
|
+
elif isinstance(command.result, heater_shaker.CloseLabwareLatchResult):
|
|
484
|
+
self._state.substate_by_module_id[module_id] = HeaterShakerModuleSubState(
|
|
485
|
+
module_id=HeaterShakerModuleId(module_id),
|
|
486
|
+
labware_latch_status=HeaterShakerLatchStatus.CLOSED,
|
|
487
|
+
is_plate_shaking=prev_state.is_plate_shaking,
|
|
488
|
+
plate_target_temperature=prev_state.plate_target_temperature,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def _handle_temperature_module_commands(
|
|
492
|
+
self,
|
|
493
|
+
command: Union[
|
|
494
|
+
temperature_module.SetTargetTemperature,
|
|
495
|
+
temperature_module.DeactivateTemperature,
|
|
496
|
+
],
|
|
497
|
+
) -> None:
|
|
498
|
+
module_id = command.params.moduleId
|
|
499
|
+
assert isinstance(
|
|
500
|
+
self._state.substate_by_module_id[module_id], TemperatureModuleSubState
|
|
501
|
+
), f"{module_id} is not a temperature module."
|
|
502
|
+
|
|
503
|
+
if isinstance(command.result, temperature_module.SetTargetTemperatureResult):
|
|
504
|
+
self._state.substate_by_module_id[module_id] = TemperatureModuleSubState(
|
|
505
|
+
module_id=TemperatureModuleId(module_id),
|
|
506
|
+
plate_target_temperature=command.result.targetTemperature,
|
|
507
|
+
)
|
|
508
|
+
elif isinstance(command.result, temperature_module.DeactivateTemperatureResult):
|
|
509
|
+
self._state.substate_by_module_id[module_id] = TemperatureModuleSubState(
|
|
510
|
+
module_id=TemperatureModuleId(module_id),
|
|
511
|
+
plate_target_temperature=None,
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
def _handle_thermocycler_module_commands(
|
|
515
|
+
self,
|
|
516
|
+
command: Union[
|
|
517
|
+
thermocycler.SetTargetBlockTemperature,
|
|
518
|
+
thermocycler.DeactivateBlock,
|
|
519
|
+
thermocycler.SetTargetLidTemperature,
|
|
520
|
+
thermocycler.DeactivateLid,
|
|
521
|
+
thermocycler.OpenLid,
|
|
522
|
+
thermocycler.CloseLid,
|
|
523
|
+
],
|
|
524
|
+
) -> None:
|
|
525
|
+
module_id = command.params.moduleId
|
|
526
|
+
thermocycler_substate = self._state.substate_by_module_id[module_id]
|
|
527
|
+
assert isinstance(
|
|
528
|
+
thermocycler_substate, ThermocyclerModuleSubState
|
|
529
|
+
), f"{module_id} is not a thermocycler module."
|
|
530
|
+
|
|
531
|
+
# Get current values to preserve target temperature not being set/deactivated
|
|
532
|
+
block_temperature = thermocycler_substate.target_block_temperature
|
|
533
|
+
lid_temperature = thermocycler_substate.target_lid_temperature
|
|
534
|
+
is_lid_open = thermocycler_substate.is_lid_open
|
|
535
|
+
|
|
536
|
+
if isinstance(command.result, thermocycler.SetTargetBlockTemperatureResult):
|
|
537
|
+
self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
|
|
538
|
+
module_id=ThermocyclerModuleId(module_id),
|
|
539
|
+
is_lid_open=is_lid_open,
|
|
540
|
+
target_block_temperature=command.result.targetBlockTemperature,
|
|
541
|
+
target_lid_temperature=lid_temperature,
|
|
542
|
+
)
|
|
543
|
+
elif isinstance(command.result, thermocycler.DeactivateBlockResult):
|
|
544
|
+
self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
|
|
545
|
+
module_id=ThermocyclerModuleId(module_id),
|
|
546
|
+
is_lid_open=is_lid_open,
|
|
547
|
+
target_block_temperature=None,
|
|
548
|
+
target_lid_temperature=lid_temperature,
|
|
549
|
+
)
|
|
550
|
+
elif isinstance(command.result, thermocycler.SetTargetLidTemperatureResult):
|
|
551
|
+
self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
|
|
552
|
+
module_id=ThermocyclerModuleId(module_id),
|
|
553
|
+
is_lid_open=is_lid_open,
|
|
554
|
+
target_block_temperature=block_temperature,
|
|
555
|
+
target_lid_temperature=command.result.targetLidTemperature,
|
|
556
|
+
)
|
|
557
|
+
elif isinstance(command.result, thermocycler.DeactivateLidResult):
|
|
558
|
+
self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
|
|
559
|
+
module_id=ThermocyclerModuleId(module_id),
|
|
560
|
+
is_lid_open=is_lid_open,
|
|
561
|
+
target_block_temperature=block_temperature,
|
|
562
|
+
target_lid_temperature=None,
|
|
563
|
+
)
|
|
564
|
+
elif isinstance(command.result, thermocycler.OpenLidResult):
|
|
565
|
+
self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
|
|
566
|
+
module_id=ThermocyclerModuleId(module_id),
|
|
567
|
+
is_lid_open=True,
|
|
568
|
+
target_block_temperature=block_temperature,
|
|
569
|
+
target_lid_temperature=lid_temperature,
|
|
570
|
+
)
|
|
571
|
+
elif isinstance(command.result, thermocycler.CloseLidResult):
|
|
572
|
+
self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
|
|
573
|
+
module_id=ThermocyclerModuleId(module_id),
|
|
574
|
+
is_lid_open=False,
|
|
575
|
+
target_block_temperature=block_temperature,
|
|
576
|
+
target_lid_temperature=lid_temperature,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
def _handle_load_module(self, load_module_state_update: LoadModuleUpdate) -> None:
|
|
580
|
+
self._add_module_substate(
|
|
581
|
+
module_id=load_module_state_update.module_id,
|
|
582
|
+
definition=load_module_state_update.definition,
|
|
583
|
+
serial_number=load_module_state_update.serial_number,
|
|
584
|
+
slot_name=load_module_state_update.slot_name,
|
|
585
|
+
requested_model=load_module_state_update.requested_model,
|
|
586
|
+
module_live_data=None,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
def _handle_absorbance_reader_commands(
|
|
590
|
+
self, absorbance_reader_state_update: AbsorbanceReaderStateUpdate
|
|
591
|
+
) -> None:
|
|
592
|
+
# Get current values:
|
|
593
|
+
module_id = absorbance_reader_state_update.module_id
|
|
594
|
+
absorbance_reader_substate = self._state.substate_by_module_id[module_id]
|
|
595
|
+
assert isinstance(
|
|
596
|
+
absorbance_reader_substate, AbsorbanceReaderSubState
|
|
597
|
+
), f"{module_id} is not an absorbance plate reader."
|
|
598
|
+
is_lid_on = absorbance_reader_substate.is_lid_on
|
|
599
|
+
measured = True
|
|
600
|
+
configured = absorbance_reader_substate.configured
|
|
601
|
+
measure_mode = absorbance_reader_substate.measure_mode
|
|
602
|
+
configured_wavelengths = absorbance_reader_substate.configured_wavelengths
|
|
603
|
+
reference_wavelength = absorbance_reader_substate.reference_wavelength
|
|
604
|
+
data = absorbance_reader_substate.data
|
|
605
|
+
if (
|
|
606
|
+
absorbance_reader_state_update.absorbance_reader_lid
|
|
607
|
+
!= update_types.NO_CHANGE
|
|
608
|
+
):
|
|
609
|
+
is_lid_on = absorbance_reader_state_update.absorbance_reader_lid.is_lid_on
|
|
610
|
+
elif (
|
|
611
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update
|
|
612
|
+
!= update_types.NO_CHANGE
|
|
613
|
+
):
|
|
614
|
+
configured = True
|
|
615
|
+
measured = False
|
|
616
|
+
is_lid_on = is_lid_on
|
|
617
|
+
measure_mode = AbsorbanceReaderMeasureMode(
|
|
618
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update.measure_mode
|
|
619
|
+
)
|
|
620
|
+
configured_wavelengths = (
|
|
621
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update.sample_wave_lengths
|
|
622
|
+
)
|
|
623
|
+
reference_wavelength = (
|
|
624
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update.reference_wave_length
|
|
625
|
+
)
|
|
626
|
+
data = None
|
|
627
|
+
elif (
|
|
628
|
+
absorbance_reader_state_update.absorbance_reader_data
|
|
629
|
+
!= update_types.NO_CHANGE
|
|
630
|
+
):
|
|
631
|
+
data = absorbance_reader_state_update.absorbance_reader_data.read_result
|
|
632
|
+
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
|
|
633
|
+
module_id=AbsorbanceReaderId(module_id),
|
|
634
|
+
configured=configured,
|
|
635
|
+
measured=measured,
|
|
636
|
+
is_lid_on=is_lid_on,
|
|
637
|
+
measure_mode=measure_mode,
|
|
638
|
+
configured_wavelengths=configured_wavelengths,
|
|
639
|
+
reference_wavelength=reference_wavelength,
|
|
640
|
+
data=data,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
def _handle_flex_stacker_commands(
|
|
644
|
+
self, state_update: FlexStackerStateUpdate
|
|
645
|
+
) -> None:
|
|
646
|
+
"""Handle Flex Stacker state updates."""
|
|
647
|
+
module_id = state_update.module_id
|
|
648
|
+
prev_substate = self._state.substate_by_module_id[module_id]
|
|
649
|
+
assert isinstance(
|
|
650
|
+
prev_substate, FlexStackerSubState
|
|
651
|
+
), f"{module_id} is not a Flex Stacker."
|
|
652
|
+
|
|
653
|
+
self._state.substate_by_module_id[
|
|
654
|
+
module_id
|
|
655
|
+
] = prev_substate.new_from_state_change(state_update)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class ModuleView:
|
|
659
|
+
"""Read-only view of computed module state."""
|
|
660
|
+
|
|
661
|
+
_state: ModuleState
|
|
662
|
+
|
|
663
|
+
def __init__(self, state: ModuleState) -> None:
|
|
664
|
+
"""Initialize the view with its backing state value."""
|
|
665
|
+
self._state = state
|
|
666
|
+
|
|
667
|
+
def get(self, module_id: str) -> LoadedModule:
|
|
668
|
+
"""Get module data by the module's unique identifier."""
|
|
669
|
+
try:
|
|
670
|
+
load_location = self._state.load_location_by_module_id[module_id]
|
|
671
|
+
attached_module = self._state.hardware_by_module_id[module_id]
|
|
672
|
+
|
|
673
|
+
except KeyError as e:
|
|
674
|
+
raise errors.ModuleNotLoadedError(module_id=module_id) from e
|
|
675
|
+
|
|
676
|
+
slot_name = None
|
|
677
|
+
if isinstance(load_location, str):
|
|
678
|
+
slot_name = deck_configuration_provider.get_deck_slot_for_cutout_id(
|
|
679
|
+
load_location
|
|
680
|
+
)
|
|
681
|
+
location = (
|
|
682
|
+
DeckSlotLocation(slotName=slot_name) if slot_name is not None else None
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
return LoadedModule.model_construct(
|
|
686
|
+
id=module_id,
|
|
687
|
+
location=location,
|
|
688
|
+
model=attached_module.definition.model,
|
|
689
|
+
serialNumber=attached_module.serial_number,
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
def get_all(self) -> List[LoadedModule]:
|
|
693
|
+
"""Get a list of all module entries in state."""
|
|
694
|
+
return [
|
|
695
|
+
self.get(mod_id) for mod_id in self._state.load_location_by_module_id.keys()
|
|
696
|
+
]
|
|
697
|
+
|
|
698
|
+
def get_by_slot(
|
|
699
|
+
self,
|
|
700
|
+
slot_name: DeckSlotName,
|
|
701
|
+
) -> Optional[LoadedModule]:
|
|
702
|
+
"""Get the module located in a given slot, if any."""
|
|
703
|
+
locations_by_id = reversed(list(self._state.load_location_by_module_id.items()))
|
|
704
|
+
|
|
705
|
+
for module_id, load_location in locations_by_id:
|
|
706
|
+
module_slot: Optional[DeckSlotName]
|
|
707
|
+
if isinstance(load_location, str):
|
|
708
|
+
module_slot = deck_configuration_provider.get_deck_slot_for_cutout_id(
|
|
709
|
+
load_location
|
|
710
|
+
)
|
|
711
|
+
else:
|
|
712
|
+
module_slot = load_location
|
|
713
|
+
if module_slot == slot_name:
|
|
714
|
+
return self.get(module_id)
|
|
715
|
+
|
|
716
|
+
return None
|
|
717
|
+
|
|
718
|
+
def get_by_addressable_area(
|
|
719
|
+
self, addressable_area_name: str
|
|
720
|
+
) -> Optional[LoadedModule]:
|
|
721
|
+
"""Get the module associated with this addressable area, if any."""
|
|
722
|
+
for module_id in self._state.load_location_by_module_id.keys():
|
|
723
|
+
if addressable_area_name == self.get_provided_addressable_area(module_id):
|
|
724
|
+
return self.get(module_id)
|
|
725
|
+
return None
|
|
726
|
+
|
|
727
|
+
def _get_module_substate(
|
|
728
|
+
self, module_id: str, expected_type: Type[ModuleSubStateT], expected_name: str
|
|
729
|
+
) -> ModuleSubStateT:
|
|
730
|
+
"""Return the specific sub-state of a given module ID.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
module_id: The ID of the module.
|
|
734
|
+
expected_type: The shape of the substate that we expect.
|
|
735
|
+
expected_name: A user-friendly name of the module to put into an
|
|
736
|
+
error message if the substate does not match the expected type.
|
|
737
|
+
|
|
738
|
+
Raises:
|
|
739
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
740
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
741
|
+
but it's not the expected type.
|
|
742
|
+
"""
|
|
743
|
+
try:
|
|
744
|
+
substate = self._state.substate_by_module_id[module_id]
|
|
745
|
+
except KeyError as e:
|
|
746
|
+
raise errors.ModuleNotLoadedError(module_id=module_id) from e
|
|
747
|
+
|
|
748
|
+
if isinstance(substate, expected_type):
|
|
749
|
+
return substate
|
|
750
|
+
|
|
751
|
+
raise errors.WrongModuleTypeError(f"{module_id} is not a {expected_name}.")
|
|
752
|
+
|
|
753
|
+
def get_magnetic_module_substate(self, module_id: str) -> MagneticModuleSubState:
|
|
754
|
+
"""Return a `MagneticModuleSubState` for the given Magnetic Module.
|
|
755
|
+
|
|
756
|
+
Raises:
|
|
757
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
758
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
759
|
+
but it's not a Magnetic Module.
|
|
760
|
+
"""
|
|
761
|
+
return self._get_module_substate(
|
|
762
|
+
module_id=module_id,
|
|
763
|
+
expected_type=MagneticModuleSubState,
|
|
764
|
+
expected_name="Magnetic Module",
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
def get_heater_shaker_module_substate(
|
|
768
|
+
self, module_id: str
|
|
769
|
+
) -> HeaterShakerModuleSubState:
|
|
770
|
+
"""Return a `HeaterShakerModuleSubState` for the given Heater-Shaker Module.
|
|
771
|
+
|
|
772
|
+
Raises:
|
|
773
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
774
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
775
|
+
but it's not a Heater-Shaker Module.
|
|
776
|
+
"""
|
|
777
|
+
return self._get_module_substate(
|
|
778
|
+
module_id=module_id,
|
|
779
|
+
expected_type=HeaterShakerModuleSubState,
|
|
780
|
+
expected_name="Heater-Shaker Module",
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
def get_temperature_module_substate(
|
|
784
|
+
self, module_id: str
|
|
785
|
+
) -> TemperatureModuleSubState:
|
|
786
|
+
"""Return a `TemperatureModuleSubState` for the given Temperature Module.
|
|
787
|
+
|
|
788
|
+
Raises:
|
|
789
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
790
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
791
|
+
but it's not a Temperature Module.
|
|
792
|
+
"""
|
|
793
|
+
return self._get_module_substate(
|
|
794
|
+
module_id=module_id,
|
|
795
|
+
expected_type=TemperatureModuleSubState,
|
|
796
|
+
expected_name="Temperature Module",
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
def get_thermocycler_module_substate(
|
|
800
|
+
self, module_id: str
|
|
801
|
+
) -> ThermocyclerModuleSubState:
|
|
802
|
+
"""Return a `ThermocyclerModuleSubState` for the given Thermocycler Module.
|
|
803
|
+
|
|
804
|
+
Raises:
|
|
805
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
806
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
807
|
+
but it's not a Thermocycler Module.
|
|
808
|
+
"""
|
|
809
|
+
return self._get_module_substate(
|
|
810
|
+
module_id=module_id,
|
|
811
|
+
expected_type=ThermocyclerModuleSubState,
|
|
812
|
+
expected_name="Thermocycler Module",
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
def get_absorbance_reader_substate(
|
|
816
|
+
self, module_id: str
|
|
817
|
+
) -> AbsorbanceReaderSubState:
|
|
818
|
+
"""Return a `AbsorbanceReaderSubState` for the given Absorbance Reader.
|
|
819
|
+
|
|
820
|
+
Raises:
|
|
821
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
822
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
823
|
+
but it's not an Absorbance Reader.
|
|
824
|
+
"""
|
|
825
|
+
return self._get_module_substate(
|
|
826
|
+
module_id=module_id,
|
|
827
|
+
expected_type=AbsorbanceReaderSubState,
|
|
828
|
+
expected_name="Absorbance Reader",
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
def get_flex_stacker_substate(self, module_id: str) -> FlexStackerSubState:
|
|
832
|
+
"""Return a `FlexStackerSubState` for the given Flex Stacker.
|
|
833
|
+
|
|
834
|
+
Raises:
|
|
835
|
+
ModuleNotLoadedError: If module_id has not been loaded.
|
|
836
|
+
WrongModuleTypeError: If module_id has been loaded,
|
|
837
|
+
but it's not a Flex Stacker.
|
|
838
|
+
"""
|
|
839
|
+
return self._get_module_substate(
|
|
840
|
+
module_id=module_id,
|
|
841
|
+
expected_type=FlexStackerSubState,
|
|
842
|
+
expected_name="Flex Stacker",
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
def get_location(self, module_id: str) -> DeckSlotLocation:
|
|
846
|
+
"""Get the slot location of the given module."""
|
|
847
|
+
location = self.get(module_id).location
|
|
848
|
+
if location is None:
|
|
849
|
+
raise errors.ModuleNotOnDeckError(
|
|
850
|
+
f"Module {module_id} is not loaded into a deck slot."
|
|
851
|
+
)
|
|
852
|
+
return location
|
|
853
|
+
|
|
854
|
+
def get_provided_addressable_area(self, module_id: str) -> str:
|
|
855
|
+
"""Get the addressable area provided by this module.
|
|
856
|
+
|
|
857
|
+
If the current deck does not allow modules to provide locations (i.e., is an OT-2 deck)
|
|
858
|
+
then return the addressable area underneath the module.
|
|
859
|
+
"""
|
|
860
|
+
module = self.get(module_id)
|
|
861
|
+
|
|
862
|
+
if isinstance(module.location, DeckSlotLocation):
|
|
863
|
+
location = module.location.slotName
|
|
864
|
+
elif module.model == ModuleModel.THERMOCYCLER_MODULE_V2:
|
|
865
|
+
location = DeckSlotName.SLOT_B1
|
|
866
|
+
else:
|
|
867
|
+
raise ValueError(
|
|
868
|
+
"Module location invalid for nominal module offset calculation."
|
|
869
|
+
)
|
|
870
|
+
if not self.get_deck_supports_module_fixtures():
|
|
871
|
+
return location.value
|
|
872
|
+
return self.ensure_and_convert_module_fixture_location(location, module.model)
|
|
873
|
+
|
|
874
|
+
def get_requested_model(self, module_id: str) -> Optional[ModuleModel]:
|
|
875
|
+
"""Return the model by which this module was requested.
|
|
876
|
+
|
|
877
|
+
Or, if this module was not loaded with an explicit ``loadModule`` command,
|
|
878
|
+
return ``None``.
|
|
879
|
+
|
|
880
|
+
See also `get_connected_model()`.
|
|
881
|
+
"""
|
|
882
|
+
try:
|
|
883
|
+
return self._state.requested_model_by_id[module_id]
|
|
884
|
+
except KeyError as e:
|
|
885
|
+
raise errors.ModuleNotLoadedError(module_id=module_id) from e
|
|
886
|
+
|
|
887
|
+
# TODO(jbl 2023-06-20) rename this method to better reflect it's not just "connected" modules
|
|
888
|
+
def get_connected_model(self, module_id: str) -> ModuleModel:
|
|
889
|
+
"""Return the model of the connected module.
|
|
890
|
+
|
|
891
|
+
NOTE: This method will return the name for any module loaded, not just electronically connected ones.
|
|
892
|
+
This includes the Magnetic Block.
|
|
893
|
+
|
|
894
|
+
This can differ from `get_requested_model()` because of module compatibility.
|
|
895
|
+
For example, a ``loadModule`` command might request a ``temperatureModuleV1``
|
|
896
|
+
but return a ``temperatureModuleV2`` if that's what it finds actually connected
|
|
897
|
+
at run time.
|
|
898
|
+
"""
|
|
899
|
+
return self.get(module_id).model
|
|
900
|
+
|
|
901
|
+
def get_serial_number(self, module_id: str) -> str:
|
|
902
|
+
"""Get the hardware serial number of the given module.
|
|
903
|
+
|
|
904
|
+
If the underlying hardware API is simulating, this will be a dummy value
|
|
905
|
+
provided by the hardware API.
|
|
906
|
+
"""
|
|
907
|
+
module = self.get(module_id)
|
|
908
|
+
if module.serialNumber is None:
|
|
909
|
+
raise ModuleNotConnectedError(
|
|
910
|
+
f"Expected a connected module and got a {module.model.name}"
|
|
911
|
+
)
|
|
912
|
+
return module.serialNumber
|
|
913
|
+
|
|
914
|
+
def get_definition(self, module_id: str) -> ModuleDefinition:
|
|
915
|
+
"""Module definition by ID."""
|
|
916
|
+
try:
|
|
917
|
+
attached_module = self._state.hardware_by_module_id[module_id]
|
|
918
|
+
except KeyError as e:
|
|
919
|
+
raise errors.ModuleNotLoadedError(module_id=module_id) from e
|
|
920
|
+
|
|
921
|
+
return attached_module.definition
|
|
922
|
+
|
|
923
|
+
def get_dimensions(self, module_id: str) -> ModuleDimensions:
|
|
924
|
+
"""Get the specified module's dimensions."""
|
|
925
|
+
return self.get_definition(module_id).dimensions
|
|
926
|
+
|
|
927
|
+
def get_nominal_offset_to_child(
|
|
928
|
+
self,
|
|
929
|
+
module_id: str,
|
|
930
|
+
# todo(mm, 2024-11-07): A method of one view taking a sibling view as an argument
|
|
931
|
+
# is unusual, and may be bug-prone if the order in which the views are updated
|
|
932
|
+
# matters. If we need to compute something that depends on module info and
|
|
933
|
+
# addressable area info, can we do that computation in GeometryView instead of
|
|
934
|
+
# here?
|
|
935
|
+
addressable_areas: AddressableAreaView,
|
|
936
|
+
) -> Point:
|
|
937
|
+
"""Get the nominal offset from a module's location to its child labware's location.
|
|
938
|
+
|
|
939
|
+
Includes the slot-specific transform. Does not include the child's
|
|
940
|
+
Labware Position Check offset.
|
|
941
|
+
"""
|
|
942
|
+
base = self.get_nominal_offset_to_child_from_addressable_area(module_id)
|
|
943
|
+
if self.get_deck_supports_module_fixtures():
|
|
944
|
+
module_addressable_area = self.get_provided_addressable_area(module_id)
|
|
945
|
+
module_addressable_area_position = (
|
|
946
|
+
addressable_areas.get_addressable_area_offsets_from_cutout(
|
|
947
|
+
module_addressable_area
|
|
948
|
+
)
|
|
949
|
+
)
|
|
950
|
+
return base + module_addressable_area_position
|
|
951
|
+
else:
|
|
952
|
+
return base
|
|
953
|
+
|
|
954
|
+
def get_nominal_offset_to_child_from_addressable_area(
|
|
955
|
+
self, module_id: str
|
|
956
|
+
) -> Point:
|
|
957
|
+
"""Get the position offset for a child of this module from the nearest AA.
|
|
958
|
+
|
|
959
|
+
On the Flex, this is always (0, 0, 0); on the OT-2, since modules load on top
|
|
960
|
+
of addressable areas rather than providing addressable areas, the offset is
|
|
961
|
+
the labwareOffset from the module definition, rotated by the module's
|
|
962
|
+
slotTransform if appropriate.
|
|
963
|
+
"""
|
|
964
|
+
if self.get_deck_supports_module_fixtures():
|
|
965
|
+
return Point(0, 0, 0)
|
|
966
|
+
else:
|
|
967
|
+
definition = self.get_definition(module_id)
|
|
968
|
+
slot = self.get_location(module_id).slotName.id
|
|
969
|
+
|
|
970
|
+
pre_transform: NDArray[npdouble] = array(
|
|
971
|
+
(
|
|
972
|
+
definition.labwareOffset.x,
|
|
973
|
+
definition.labwareOffset.y,
|
|
974
|
+
definition.labwareOffset.z,
|
|
975
|
+
1,
|
|
976
|
+
)
|
|
977
|
+
)
|
|
978
|
+
xforms_ser = definition.slotTransforms.get(
|
|
979
|
+
str(self._state.deck_type.value), {}
|
|
980
|
+
).get(
|
|
981
|
+
slot,
|
|
982
|
+
{
|
|
983
|
+
"labwareOffset": [
|
|
984
|
+
[1, 0, 0, 0],
|
|
985
|
+
[0, 1, 0, 0],
|
|
986
|
+
[0, 0, 1, 0],
|
|
987
|
+
[0, 0, 0, 1],
|
|
988
|
+
]
|
|
989
|
+
},
|
|
990
|
+
)
|
|
991
|
+
xforms_ser_offset = xforms_ser["labwareOffset"]
|
|
992
|
+
|
|
993
|
+
# Apply the slot transform, if any
|
|
994
|
+
xform: NDArray[npdouble] = array(xforms_ser_offset)
|
|
995
|
+
xformed = dot(xform, pre_transform)
|
|
996
|
+
return Point(
|
|
997
|
+
x=xformed[0],
|
|
998
|
+
y=xformed[1],
|
|
999
|
+
z=xformed[2],
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
def get_module_calibration_offset(
|
|
1003
|
+
self, module_id: str
|
|
1004
|
+
) -> Optional[ModuleOffsetData]:
|
|
1005
|
+
"""Get the calibration module offset."""
|
|
1006
|
+
module_serial = self.get(module_id).serialNumber
|
|
1007
|
+
if module_serial:
|
|
1008
|
+
return self._state.module_offset_by_serial.get(module_serial)
|
|
1009
|
+
return None
|
|
1010
|
+
|
|
1011
|
+
def get_overall_height(self, module_id: str) -> float:
|
|
1012
|
+
"""Get the height of the module, excluding any labware loaded atop it."""
|
|
1013
|
+
return self.get_dimensions(module_id).bareOverallHeight
|
|
1014
|
+
|
|
1015
|
+
# TODO(mc, 2022-01-19): this method is missing unit test coverage
|
|
1016
|
+
def get_height_over_labware(self, module_id: str) -> float:
|
|
1017
|
+
"""Get the height of module parts above module labware base."""
|
|
1018
|
+
return self.get_dimensions(module_id).overLabwareHeight
|
|
1019
|
+
|
|
1020
|
+
def get_module_highest_z(
|
|
1021
|
+
self, module_id: str, addressable_areas: AddressableAreaView
|
|
1022
|
+
) -> float:
|
|
1023
|
+
"""Get the highest z point of the module, as placed on the robot.
|
|
1024
|
+
|
|
1025
|
+
The highest Z of a module, unlike the bare overall height, depends on
|
|
1026
|
+
the robot it is on. We will calculate this value using the info we already have
|
|
1027
|
+
about the transformation of the module's placement, based on the deck it is on.
|
|
1028
|
+
|
|
1029
|
+
This value is calculated as:
|
|
1030
|
+
highest_z = ( nominal_robot_transformed_labware_offset_z
|
|
1031
|
+
+ z_difference_between_default_labware_offset_point_and_overall_height
|
|
1032
|
+
+ module_calibration_offset_z
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
For OT2, the default_labware_offset point is the same as nominal_robot_transformed_labware_offset_z
|
|
1036
|
+
and hence the highest z will equal to the overall height of the module.
|
|
1037
|
+
|
|
1038
|
+
For Flex, since those two offsets are not the same, the final highest z will be
|
|
1039
|
+
transformed the same amount as the labware offset point is.
|
|
1040
|
+
|
|
1041
|
+
Note: For thermocycler, the lid height is not taken into account.
|
|
1042
|
+
"""
|
|
1043
|
+
module_height = self.get_overall_height(module_id)
|
|
1044
|
+
default_lw_offset_point = self.get_definition(module_id).labwareOffset.z
|
|
1045
|
+
z_difference = module_height - default_lw_offset_point
|
|
1046
|
+
|
|
1047
|
+
nominal_transformed_lw_offset_z = self.get_nominal_offset_to_child(
|
|
1048
|
+
module_id=module_id, addressable_areas=addressable_areas
|
|
1049
|
+
).z
|
|
1050
|
+
calibration_offset = self.get_module_calibration_offset(module_id)
|
|
1051
|
+
return (
|
|
1052
|
+
nominal_transformed_lw_offset_z
|
|
1053
|
+
+ z_difference
|
|
1054
|
+
+ (calibration_offset.moduleOffsetVector.z if calibration_offset else 0)
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
# TODO(mc, 2022-01-19): this method is missing unit test coverage and
|
|
1058
|
+
# is also unused. Remove or add tests.
|
|
1059
|
+
def get_lid_height(self, module_id: str) -> float:
|
|
1060
|
+
"""Get lid height if module is thermocycler."""
|
|
1061
|
+
definition = self.get_definition(module_id)
|
|
1062
|
+
|
|
1063
|
+
if (
|
|
1064
|
+
definition.moduleType == ModuleType.THERMOCYCLER
|
|
1065
|
+
and hasattr(definition.dimensions, "lidHeight")
|
|
1066
|
+
and definition.dimensions.lidHeight is not None
|
|
1067
|
+
):
|
|
1068
|
+
return definition.dimensions.lidHeight
|
|
1069
|
+
else:
|
|
1070
|
+
raise errors.WrongModuleTypeError(
|
|
1071
|
+
f"Cannot get lid height of {definition.moduleType}"
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
@staticmethod
|
|
1075
|
+
def get_magnet_home_to_base_offset(module_model: ModuleModel) -> float:
|
|
1076
|
+
"""Return a Magnetic Module's home offset.
|
|
1077
|
+
|
|
1078
|
+
This is how far a Magnetic Module's magnets have to rise above their
|
|
1079
|
+
home position for their tops to be level with the bottom of the labware.
|
|
1080
|
+
|
|
1081
|
+
The offset is returned in true millimeters,
|
|
1082
|
+
even though GEN1 Magnetic Modules are sometimes controlled in units of
|
|
1083
|
+
half-millimeters ("short mm").
|
|
1084
|
+
"""
|
|
1085
|
+
if module_model == ModuleModel.MAGNETIC_MODULE_V1:
|
|
1086
|
+
offset_in_half_mm = MAGNETIC_MODULE_OFFSET_TO_LABWARE_BOTTOM[
|
|
1087
|
+
"magneticModuleV1"
|
|
1088
|
+
]
|
|
1089
|
+
return offset_in_half_mm / 2
|
|
1090
|
+
elif module_model == ModuleModel.MAGNETIC_MODULE_V2:
|
|
1091
|
+
return MAGNETIC_MODULE_OFFSET_TO_LABWARE_BOTTOM["magneticModuleV2"]
|
|
1092
|
+
else:
|
|
1093
|
+
raise errors.WrongModuleTypeError(
|
|
1094
|
+
f"Can't get magnet offset of {module_model}."
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
@overload
|
|
1098
|
+
@classmethod
|
|
1099
|
+
def calculate_magnet_height(
|
|
1100
|
+
cls,
|
|
1101
|
+
*,
|
|
1102
|
+
module_model: ModuleModel,
|
|
1103
|
+
height_from_home: float,
|
|
1104
|
+
) -> float:
|
|
1105
|
+
pass
|
|
1106
|
+
|
|
1107
|
+
@overload
|
|
1108
|
+
@classmethod
|
|
1109
|
+
def calculate_magnet_height(
|
|
1110
|
+
cls,
|
|
1111
|
+
*,
|
|
1112
|
+
module_model: ModuleModel,
|
|
1113
|
+
height_from_base: float,
|
|
1114
|
+
) -> float:
|
|
1115
|
+
pass
|
|
1116
|
+
|
|
1117
|
+
@overload
|
|
1118
|
+
@classmethod
|
|
1119
|
+
def calculate_magnet_height(
|
|
1120
|
+
cls,
|
|
1121
|
+
*,
|
|
1122
|
+
module_model: ModuleModel,
|
|
1123
|
+
labware_default_height: float,
|
|
1124
|
+
offset_from_labware_default: float,
|
|
1125
|
+
) -> float:
|
|
1126
|
+
pass
|
|
1127
|
+
|
|
1128
|
+
@classmethod
|
|
1129
|
+
def calculate_magnet_height(
|
|
1130
|
+
cls,
|
|
1131
|
+
*,
|
|
1132
|
+
module_model: ModuleModel,
|
|
1133
|
+
height_from_home: Optional[float] = None,
|
|
1134
|
+
height_from_base: Optional[float] = None,
|
|
1135
|
+
labware_default_height: Optional[float] = None,
|
|
1136
|
+
offset_from_labware_default: Optional[float] = None,
|
|
1137
|
+
) -> float:
|
|
1138
|
+
"""Normalize a Magnetic Module engage height to standard units.
|
|
1139
|
+
|
|
1140
|
+
Args:
|
|
1141
|
+
module_model: What kind of Magnetic Module to calculate the height for.
|
|
1142
|
+
height_from_home: A distance above the magnets' home position,
|
|
1143
|
+
in millimeters.
|
|
1144
|
+
height_from_base: A distance above the labware base plane,
|
|
1145
|
+
in millimeters.
|
|
1146
|
+
labware_default_height: A distance above the labware base plane,
|
|
1147
|
+
in millimeters, from a labware definition.
|
|
1148
|
+
offset_from_labware_default: A distance from the
|
|
1149
|
+
``labware_default_height`` argument, in hardware units.
|
|
1150
|
+
|
|
1151
|
+
Negative values are allowed for all arguments, to move down instead of up.
|
|
1152
|
+
|
|
1153
|
+
See the overload signatures for which combinations of parameters are allowed.
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
The same height passed in, converted to be measured in
|
|
1157
|
+
millimeters above the module's labware base plane,
|
|
1158
|
+
suitable as input to a Magnetic Module engage Protocol Engine command.
|
|
1159
|
+
"""
|
|
1160
|
+
if height_from_home is not None:
|
|
1161
|
+
home_to_base = cls.get_magnet_home_to_base_offset(module_model=module_model)
|
|
1162
|
+
return height_from_home - home_to_base
|
|
1163
|
+
|
|
1164
|
+
elif height_from_base is not None:
|
|
1165
|
+
return height_from_base
|
|
1166
|
+
|
|
1167
|
+
else:
|
|
1168
|
+
# Guaranteed statically by overload.
|
|
1169
|
+
assert labware_default_height is not None
|
|
1170
|
+
assert offset_from_labware_default is not None
|
|
1171
|
+
return labware_default_height + offset_from_labware_default
|
|
1172
|
+
|
|
1173
|
+
def should_dodge_thermocycler(
|
|
1174
|
+
self,
|
|
1175
|
+
from_slot: Union[DeckSlotName, StagingSlotName],
|
|
1176
|
+
to_slot: Union[DeckSlotName, StagingSlotName],
|
|
1177
|
+
) -> bool:
|
|
1178
|
+
"""Decide if the requested path would cross the thermocycler, if installed.
|
|
1179
|
+
|
|
1180
|
+
Returns True if we need to dodge, False otherwise.
|
|
1181
|
+
"""
|
|
1182
|
+
all_mods = self.get_all()
|
|
1183
|
+
if any(ModuleModel.is_thermocycler_module_model(mod.model) for mod in all_mods):
|
|
1184
|
+
transit = (from_slot, to_slot)
|
|
1185
|
+
if transit in _THERMOCYCLER_SLOT_TRANSITS_TO_DODGE:
|
|
1186
|
+
return True
|
|
1187
|
+
return False
|
|
1188
|
+
|
|
1189
|
+
def is_edge_move_unsafe(self, mount: MountType, target_slot: DeckSlotName) -> bool:
|
|
1190
|
+
"""Check if the slot next to target contains a module to be avoided, depending on mount."""
|
|
1191
|
+
slot_int = target_slot.as_int()
|
|
1192
|
+
|
|
1193
|
+
if mount is MountType.RIGHT:
|
|
1194
|
+
# Check left of the target
|
|
1195
|
+
neighbor_int = get_west_slot(slot_int)
|
|
1196
|
+
if neighbor_int is None:
|
|
1197
|
+
return False
|
|
1198
|
+
else:
|
|
1199
|
+
neighbor_slot = DeckSlotName.from_primitive(neighbor_int)
|
|
1200
|
+
else:
|
|
1201
|
+
# Check right of the target
|
|
1202
|
+
neighbor_int = get_east_slot(slot_int)
|
|
1203
|
+
if neighbor_int is None:
|
|
1204
|
+
return False
|
|
1205
|
+
else:
|
|
1206
|
+
neighbor_slot = DeckSlotName.from_primitive(neighbor_int)
|
|
1207
|
+
|
|
1208
|
+
# Convert the load location list from addressable areas and cutout IDs to a slot name list
|
|
1209
|
+
load_locations = self._state.load_location_by_module_id.values()
|
|
1210
|
+
module_slots = []
|
|
1211
|
+
for location in load_locations:
|
|
1212
|
+
if isinstance(location, str):
|
|
1213
|
+
module_slots.append(
|
|
1214
|
+
deck_configuration_provider.get_deck_slot_for_cutout_id(location)
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
return neighbor_slot in module_slots
|
|
1218
|
+
|
|
1219
|
+
def select_hardware_module_to_load( # noqa: C901
|
|
1220
|
+
self,
|
|
1221
|
+
model: ModuleModel,
|
|
1222
|
+
location: str,
|
|
1223
|
+
attached_modules: Sequence[HardwareModule],
|
|
1224
|
+
expected_serial_number: Optional[str] = None,
|
|
1225
|
+
) -> HardwareModule:
|
|
1226
|
+
"""Get the next matching hardware module for the given model and location.
|
|
1227
|
+
|
|
1228
|
+
If a "matching" model is found already loaded in state at the requested
|
|
1229
|
+
location, that hardware module will be "reused" and selected. This behavior
|
|
1230
|
+
allows multiple load module commands to be issued while always preserving
|
|
1231
|
+
module hardware instance to deck slot mapping, which is required for
|
|
1232
|
+
multiples-of-a-module functionality.
|
|
1233
|
+
|
|
1234
|
+
Args:
|
|
1235
|
+
model: The requested module model. The selected module may have a
|
|
1236
|
+
different model if the definition lists the model as compatible.
|
|
1237
|
+
location: The location the module will be assigned to.
|
|
1238
|
+
attached_modules: All attached modules as reported by the HardwareAPI,
|
|
1239
|
+
in the order in which they should be used.
|
|
1240
|
+
expected_serial_number: An optional variable containing the serial number
|
|
1241
|
+
expected of the module identified.
|
|
1242
|
+
|
|
1243
|
+
Raises:
|
|
1244
|
+
ModuleNotAttachedError: A not-yet-assigned module matching the requested
|
|
1245
|
+
parameters could not be found in the attached modules list.
|
|
1246
|
+
ModuleAlreadyPresentError: A module of a different type is already
|
|
1247
|
+
assigned to the requested location.
|
|
1248
|
+
"""
|
|
1249
|
+
existing_mod_in_slot = None
|
|
1250
|
+
|
|
1251
|
+
for (
|
|
1252
|
+
mod_id,
|
|
1253
|
+
load_location,
|
|
1254
|
+
) in self._state.load_location_by_module_id.items():
|
|
1255
|
+
if isinstance(load_location, str) and location == load_location:
|
|
1256
|
+
existing_mod_in_slot = self._state.hardware_by_module_id.get(mod_id)
|
|
1257
|
+
|
|
1258
|
+
if existing_mod_in_slot:
|
|
1259
|
+
existing_def = existing_mod_in_slot.definition
|
|
1260
|
+
|
|
1261
|
+
if existing_def.model == model or model in existing_def.compatibleWith:
|
|
1262
|
+
return existing_mod_in_slot
|
|
1263
|
+
|
|
1264
|
+
else:
|
|
1265
|
+
_err = f" present in {location}"
|
|
1266
|
+
raise errors.ModuleAlreadyPresentError(
|
|
1267
|
+
f"A {existing_def.model.value} is already" + _err
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
for m in attached_modules:
|
|
1271
|
+
if m not in self._state.hardware_by_module_id.values():
|
|
1272
|
+
if model == m.definition.model or model in m.definition.compatibleWith:
|
|
1273
|
+
if expected_serial_number is not None:
|
|
1274
|
+
if m.serial_number == expected_serial_number:
|
|
1275
|
+
return m
|
|
1276
|
+
else:
|
|
1277
|
+
return m
|
|
1278
|
+
|
|
1279
|
+
raise errors.ModuleNotAttachedError(
|
|
1280
|
+
f"No available {model.value} with {expected_serial_number or 'any'}"
|
|
1281
|
+
" serial found."
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
def get_heater_shaker_movement_restrictors(
|
|
1285
|
+
self,
|
|
1286
|
+
) -> List[HeaterShakerMovementRestrictors]:
|
|
1287
|
+
"""Get shaking status, latch status, and location for every heater-shaker on deck."""
|
|
1288
|
+
hs_substates = [
|
|
1289
|
+
self.get_heater_shaker_module_substate(module_id=module.id)
|
|
1290
|
+
for module in self.get_all()
|
|
1291
|
+
if module.model == ModuleModel.HEATER_SHAKER_MODULE_V1
|
|
1292
|
+
]
|
|
1293
|
+
hs_restrictors = [
|
|
1294
|
+
HeaterShakerMovementRestrictors(
|
|
1295
|
+
plate_shaking=substate.is_plate_shaking,
|
|
1296
|
+
latch_status=substate.labware_latch_status,
|
|
1297
|
+
deck_slot=self.get_location(substate.module_id).slotName.as_int(),
|
|
1298
|
+
)
|
|
1299
|
+
for substate in hs_substates
|
|
1300
|
+
]
|
|
1301
|
+
return hs_restrictors
|
|
1302
|
+
|
|
1303
|
+
def raise_if_module_in_location(
|
|
1304
|
+
self,
|
|
1305
|
+
location: DeckSlotLocation,
|
|
1306
|
+
) -> None:
|
|
1307
|
+
"""Raise if the given location has a module in it."""
|
|
1308
|
+
for module in self.get_all():
|
|
1309
|
+
if module.model in _COLUMN_4_MODULES and module.location == location:
|
|
1310
|
+
raise errors.LocationIsOccupiedError(
|
|
1311
|
+
f"Module {module.model} is already present at {location.slotName.value[:1]}4."
|
|
1312
|
+
)
|
|
1313
|
+
if module.location == location:
|
|
1314
|
+
raise errors.LocationIsOccupiedError(
|
|
1315
|
+
f"Module {module.model} is already present at {location}."
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
def get_default_gripper_offsets(
|
|
1319
|
+
self, module_id: str
|
|
1320
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
1321
|
+
"""Get the deck's default gripper offsets."""
|
|
1322
|
+
offsets = self.get_definition(module_id).gripperOffsets
|
|
1323
|
+
return offsets.get("default") if offsets else None
|
|
1324
|
+
|
|
1325
|
+
def get_overflowed_module_in_slot(
|
|
1326
|
+
self, slot_name: DeckSlotName
|
|
1327
|
+
) -> Optional[LoadedModule]:
|
|
1328
|
+
"""Get the module that's not loaded in the given slot, but still occupies the slot.
|
|
1329
|
+
|
|
1330
|
+
For example, if there's a thermocycler loaded in B1,
|
|
1331
|
+
`get_overflowed_module_in_slot(DeckSlotName.Slot_A1)` will return the loaded
|
|
1332
|
+
thermocycler module.
|
|
1333
|
+
"""
|
|
1334
|
+
slots_by_id = self._state.additional_slots_occupied_by_module_id
|
|
1335
|
+
|
|
1336
|
+
for module_id, module_slots in slots_by_id.items():
|
|
1337
|
+
if module_slots and slot_name in module_slots:
|
|
1338
|
+
return self.get(module_id)
|
|
1339
|
+
|
|
1340
|
+
return None
|
|
1341
|
+
|
|
1342
|
+
def is_flex_deck_with_thermocycler(self) -> bool:
|
|
1343
|
+
"""Return if this is a Flex deck with a thermocycler loaded in B1-A1 slots."""
|
|
1344
|
+
maybe_module = self.get_by_slot(
|
|
1345
|
+
DeckSlotName.SLOT_A1
|
|
1346
|
+
) or self.get_overflowed_module_in_slot(DeckSlotName.SLOT_A1)
|
|
1347
|
+
if (
|
|
1348
|
+
self._state.deck_type == DeckType.OT3_STANDARD
|
|
1349
|
+
and maybe_module
|
|
1350
|
+
and maybe_module.model == ModuleModel.THERMOCYCLER_MODULE_V2
|
|
1351
|
+
):
|
|
1352
|
+
return True
|
|
1353
|
+
else:
|
|
1354
|
+
return False
|
|
1355
|
+
|
|
1356
|
+
@staticmethod
|
|
1357
|
+
def convert_absorbance_reader_data_points(data: List[float]) -> Dict[str, float]:
|
|
1358
|
+
"""Return the data from the Absorbance Reader module in a map of wells for each read value."""
|
|
1359
|
+
if len(data) == 96:
|
|
1360
|
+
# We have to reverse the reader values because the Opentrons Absorbance Reader is rotated 180 degrees on the deck
|
|
1361
|
+
raw_data = data.copy()
|
|
1362
|
+
raw_data.reverse()
|
|
1363
|
+
well_map: Dict[str, float] = {}
|
|
1364
|
+
for i, value in enumerate(raw_data):
|
|
1365
|
+
row = chr(ord("A") + i // 12) # Convert index to row (A-H)
|
|
1366
|
+
col = (i % 12) + 1 # Convert index to column (1-12)
|
|
1367
|
+
well_key = f"{row}{col}"
|
|
1368
|
+
# Truncate the value to the third decimal place
|
|
1369
|
+
well_map[well_key] = max(0.0, math.floor(value * 1000) / 1000)
|
|
1370
|
+
return well_map
|
|
1371
|
+
else:
|
|
1372
|
+
raise ValueError(
|
|
1373
|
+
"Only readings of 96 Well labware are supported for conversion to map of values by well."
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
def get_deck_supports_module_fixtures(self) -> bool:
|
|
1377
|
+
"""Check if the loaded deck supports modules as fixtures."""
|
|
1378
|
+
deck_type = self._state.deck_type
|
|
1379
|
+
return deck_type not in [DeckType.OT2_STANDARD, DeckType.OT2_SHORT_TRASH]
|
|
1380
|
+
|
|
1381
|
+
def ensure_and_convert_module_fixture_location(
|
|
1382
|
+
self,
|
|
1383
|
+
deck_slot: DeckSlotName,
|
|
1384
|
+
model: ModuleModel,
|
|
1385
|
+
) -> str:
|
|
1386
|
+
"""Ensure module fixture load location is valid.
|
|
1387
|
+
|
|
1388
|
+
Also, convert the deck slot to a valid module fixture addressable area.
|
|
1389
|
+
"""
|
|
1390
|
+
deck_type = self._state.deck_type
|
|
1391
|
+
|
|
1392
|
+
if not self.get_deck_supports_module_fixtures():
|
|
1393
|
+
raise AreaNotInDeckConfigurationError(
|
|
1394
|
+
f"Invalid Deck Type: {deck_type.name} - Does not support modules as fixtures."
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
assert deck_slot in DeckSlotName.ot3_slots()
|
|
1398
|
+
if model == ModuleModel.MAGNETIC_BLOCK_V1:
|
|
1399
|
+
return f"magneticBlockV1{deck_slot.value}"
|
|
1400
|
+
|
|
1401
|
+
elif model == ModuleModel.HEATER_SHAKER_MODULE_V1:
|
|
1402
|
+
# only allowed in column 1 & 3
|
|
1403
|
+
assert deck_slot.value[-1] in ("1", "3")
|
|
1404
|
+
return f"heaterShakerV1{deck_slot.value}"
|
|
1405
|
+
|
|
1406
|
+
elif model == ModuleModel.TEMPERATURE_MODULE_V2:
|
|
1407
|
+
# only allowed in column 1 & 3
|
|
1408
|
+
assert deck_slot.value[-1] in ("1", "3")
|
|
1409
|
+
return f"temperatureModuleV2{deck_slot.value}"
|
|
1410
|
+
|
|
1411
|
+
elif model == ModuleModel.THERMOCYCLER_MODULE_V2:
|
|
1412
|
+
return "thermocyclerModuleV2"
|
|
1413
|
+
|
|
1414
|
+
elif model == ModuleModel.ABSORBANCE_READER_V1:
|
|
1415
|
+
# only allowed in column 3
|
|
1416
|
+
assert deck_slot.value[-1] == "3"
|
|
1417
|
+
return f"absorbanceReaderV1{deck_slot.value}"
|
|
1418
|
+
|
|
1419
|
+
elif model == ModuleModel.FLEX_STACKER_MODULE_V1:
|
|
1420
|
+
# loaded to column 3 but the addressable area is in column 4
|
|
1421
|
+
assert deck_slot.value[-1] == "3"
|
|
1422
|
+
return f"flexStackerModuleV1{deck_slot.value[0]}4"
|
|
1423
|
+
|
|
1424
|
+
raise ValueError(
|
|
1425
|
+
f"Unknown module {model.name} has no addressable areas to provide."
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
def absorbance_reader_dock_location(
|
|
1429
|
+
self, module_id: str
|
|
1430
|
+
) -> AddressableAreaLocation:
|
|
1431
|
+
"""Get the addressable area for the absorbance reader dock."""
|
|
1432
|
+
reader_slot = self.get_location(module_id)
|
|
1433
|
+
lid_doc_slot = get_adjacent_staging_slot(reader_slot.slotName)
|
|
1434
|
+
assert lid_doc_slot is not None
|
|
1435
|
+
lid_dock_area = AddressableAreaLocation(
|
|
1436
|
+
addressableAreaName="absorbanceReaderV1LidDock" + lid_doc_slot.value
|
|
1437
|
+
)
|
|
1438
|
+
return lid_dock_area
|
|
1439
|
+
|
|
1440
|
+
def get_stacker_max_fill_height(self, module_id: str) -> float:
|
|
1441
|
+
"""Get the maximum fill height for the Flex Stacker."""
|
|
1442
|
+
definition = self.get_definition(module_id)
|
|
1443
|
+
|
|
1444
|
+
if (
|
|
1445
|
+
definition.moduleType == ModuleType.FLEX_STACKER
|
|
1446
|
+
and hasattr(definition.dimensions, "maxStackerFillHeight")
|
|
1447
|
+
and definition.dimensions.maxStackerFillHeight is not None
|
|
1448
|
+
):
|
|
1449
|
+
return definition.dimensions.maxStackerFillHeight
|
|
1450
|
+
else:
|
|
1451
|
+
raise errors.WrongModuleTypeError(
|
|
1452
|
+
f"Cannot get max fill height of {definition.moduleType}"
|
|
1453
|
+
)
|
|
1454
|
+
|
|
1455
|
+
def stacker_max_pool_count_by_height(
|
|
1456
|
+
self,
|
|
1457
|
+
module_id: str,
|
|
1458
|
+
pool_height: float,
|
|
1459
|
+
pool_overlap: float,
|
|
1460
|
+
) -> int:
|
|
1461
|
+
"""Get the maximum stack count for the Flex Stacker by stack height."""
|
|
1462
|
+
max_fill_height = self.get_stacker_max_fill_height(module_id)
|
|
1463
|
+
assert max_fill_height > 0
|
|
1464
|
+
# Subtracting the pool overlap from the stack element (pool height) allows us to account for
|
|
1465
|
+
# elements nesting on one-another, and we must subtract from max height to apply starting offset.
|
|
1466
|
+
# Ex: Let H be the total height of the stack; h be the height of a stack element;
|
|
1467
|
+
# d be the stack overlap; and N be the number of labware. Then for N >= 1,
|
|
1468
|
+
# H = Nh - (N-1)d
|
|
1469
|
+
# H = Nh - Nd + d
|
|
1470
|
+
# H - d = N(h-d)
|
|
1471
|
+
# (H-d)/(h-d) = N
|
|
1472
|
+
return math.floor(
|
|
1473
|
+
(max_fill_height - pool_overlap) / (pool_height - pool_overlap)
|
|
1474
|
+
)
|
|
1475
|
+
|
|
1476
|
+
def stacker_contained_labware(
|
|
1477
|
+
self, module_id: str
|
|
1478
|
+
) -> list[StackerStoredLabwareGroup]:
|
|
1479
|
+
"""Get the labware contained in a Flex Stacker."""
|
|
1480
|
+
substate = self.get_flex_stacker_substate(module_id)
|
|
1481
|
+
return substate.get_contained_labware()
|
|
1482
|
+
|
|
1483
|
+
def stacker_max_pool_count(self, module_id: str) -> int | None:
|
|
1484
|
+
"""Get the max stored labware in this stacker configuration."""
|
|
1485
|
+
substate = self.get_flex_stacker_substate(module_id)
|
|
1486
|
+
return substate.get_max_pool_count()
|
|
1487
|
+
|
|
1488
|
+
def validate_stacker_overlap_offset(
|
|
1489
|
+
self,
|
|
1490
|
+
module_id: str,
|
|
1491
|
+
overlap_offset: float,
|
|
1492
|
+
) -> None:
|
|
1493
|
+
"""The overlap offset provided should match the stacker configuration."""
|
|
1494
|
+
substate = self.get_flex_stacker_substate(module_id)
|
|
1495
|
+
configured = substate.get_pool_overlap()
|
|
1496
|
+
if not math.isclose(overlap_offset, configured, rel_tol=1e-9):
|
|
1497
|
+
raise ValueError(
|
|
1498
|
+
f"Provided overlap offset {overlap_offset} does not match "
|
|
1499
|
+
f"configured {configured}."
|
|
1500
|
+
)
|