opentrons 8.6.0__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 +557 -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 +187 -0
- opentrons/drivers/asyncio/communication/errors.py +88 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +557 -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/scripts/update_module_fw.py +274 -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 +276 -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 +1186 -0
- opentrons/motion_planning/__init__.py +32 -0
- opentrons/motion_planning/adjacent_slots_getters.py +168 -0
- opentrons/motion_planning/deck_conflict.py +501 -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 +400 -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 +1027 -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 +1006 -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 +3272 -0
- opentrons/protocol_api/labware.py +1579 -0
- opentrons/protocol_api/module_contexts.py +1447 -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 +339 -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 +350 -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 +68 -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 +1164 -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 +2408 -0
- opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
- opentrons/protocol_engine/state/labware.py +1432 -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 +1515 -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 +310 -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 +310 -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 +852 -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.0.dist-info/METADATA +37 -0
- opentrons-8.6.0.dist-info/RECORD +601 -0
- opentrons-8.6.0.dist-info/WHEEL +4 -0
- opentrons-8.6.0.dist-info/entry_points.txt +3 -0
- opentrons-8.6.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
"""Pipetting command handling."""
|
|
2
|
+
from typing import Optional, Iterator, Tuple
|
|
3
|
+
from typing_extensions import Protocol as TypingProtocol
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from opentrons.hardware_control import HardwareControlAPI
|
|
7
|
+
|
|
8
|
+
from ..state.state import StateView
|
|
9
|
+
from ..state.pipettes import HardwarePipette
|
|
10
|
+
from ..notes import CommandNoteAdder, CommandNote
|
|
11
|
+
from ..errors.exceptions import (
|
|
12
|
+
TipNotAttachedError,
|
|
13
|
+
InvalidAspirateVolumeError,
|
|
14
|
+
InvalidPushOutVolumeError,
|
|
15
|
+
InvalidDispenseVolumeError,
|
|
16
|
+
InvalidLiquidHeightFound,
|
|
17
|
+
)
|
|
18
|
+
from opentrons.protocol_engine.types import WellLocation
|
|
19
|
+
from opentrons.protocol_engine.types.liquid_level_detection import (
|
|
20
|
+
SimulatedProbeResult,
|
|
21
|
+
LiquidTrackingType,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# 1e-9 µL (1 femtoliter!) is a good value because:
|
|
25
|
+
# * It's large relative to rounding errors that occur in practice in protocols. For
|
|
26
|
+
# example, https://opentrons.atlassian.net/browse/RESC-182 shows a rounding error
|
|
27
|
+
# on the order of 1e-15 µL.
|
|
28
|
+
# * It's small relative to volumes that our users might actually care about and
|
|
29
|
+
# expect the robot to execute faithfully.
|
|
30
|
+
# * It's the default absolute tolerance for math.isclose(), where it apparently works
|
|
31
|
+
# well in general.
|
|
32
|
+
_VOLUME_ROUNDING_ERROR_TOLERANCE = 1e-9
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PipettingHandler(TypingProtocol):
|
|
36
|
+
"""Liquid handling commands."""
|
|
37
|
+
|
|
38
|
+
def get_state_view(self) -> StateView:
|
|
39
|
+
"""Get the stateview associated with this handler."""
|
|
40
|
+
|
|
41
|
+
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
42
|
+
"""Get whether a pipette is ready to aspirate."""
|
|
43
|
+
|
|
44
|
+
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
45
|
+
"""Prepare for pipette aspiration."""
|
|
46
|
+
|
|
47
|
+
async def aspirate_in_place(
|
|
48
|
+
self,
|
|
49
|
+
pipette_id: str,
|
|
50
|
+
volume: float,
|
|
51
|
+
flow_rate: float,
|
|
52
|
+
command_note_adder: CommandNoteAdder,
|
|
53
|
+
correction_volume: float = 0.0,
|
|
54
|
+
) -> float:
|
|
55
|
+
"""Set flow-rate and aspirate."""
|
|
56
|
+
|
|
57
|
+
async def aspirate_while_tracking(
|
|
58
|
+
self,
|
|
59
|
+
pipette_id: str,
|
|
60
|
+
labware_id: str,
|
|
61
|
+
well_name: str,
|
|
62
|
+
volume: float,
|
|
63
|
+
flow_rate: float,
|
|
64
|
+
command_note_adder: CommandNoteAdder,
|
|
65
|
+
) -> float:
|
|
66
|
+
"""Set flow-rate and aspirate while tracking."""
|
|
67
|
+
|
|
68
|
+
async def dispense_while_tracking(
|
|
69
|
+
self,
|
|
70
|
+
pipette_id: str,
|
|
71
|
+
labware_id: str,
|
|
72
|
+
well_name: str,
|
|
73
|
+
volume: float,
|
|
74
|
+
flow_rate: float,
|
|
75
|
+
push_out: Optional[float],
|
|
76
|
+
is_full_dispense: bool = False,
|
|
77
|
+
) -> float:
|
|
78
|
+
"""Set flow-rate and dispense while tracking."""
|
|
79
|
+
|
|
80
|
+
async def dispense_in_place(
|
|
81
|
+
self,
|
|
82
|
+
pipette_id: str,
|
|
83
|
+
volume: float,
|
|
84
|
+
flow_rate: float,
|
|
85
|
+
push_out: Optional[float],
|
|
86
|
+
is_full_dispense: bool,
|
|
87
|
+
correction_volume: float = 0.0,
|
|
88
|
+
) -> float:
|
|
89
|
+
"""Set flow-rate and dispense."""
|
|
90
|
+
|
|
91
|
+
async def blow_out_in_place(
|
|
92
|
+
self,
|
|
93
|
+
pipette_id: str,
|
|
94
|
+
flow_rate: float,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Set flow rate and blow-out."""
|
|
97
|
+
|
|
98
|
+
async def liquid_probe_in_place(
|
|
99
|
+
self,
|
|
100
|
+
pipette_id: str,
|
|
101
|
+
labware_id: str,
|
|
102
|
+
well_name: str,
|
|
103
|
+
well_location: WellLocation,
|
|
104
|
+
) -> LiquidTrackingType:
|
|
105
|
+
"""Detect liquid level."""
|
|
106
|
+
|
|
107
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
108
|
+
"""Increase evo tip dispense action count."""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class HardwarePipettingHandler(PipettingHandler):
|
|
112
|
+
"""Liquid handling, using the Hardware API."""
|
|
113
|
+
|
|
114
|
+
def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> None:
|
|
115
|
+
"""Initialize a PipettingHandler instance."""
|
|
116
|
+
self._state_view = state_view
|
|
117
|
+
self._hardware_api = hardware_api
|
|
118
|
+
|
|
119
|
+
def get_state_view(self) -> StateView:
|
|
120
|
+
"""Get the stateview associated with this handler."""
|
|
121
|
+
return self._state_view
|
|
122
|
+
|
|
123
|
+
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
124
|
+
"""Get whether a pipette is ready to aspirate."""
|
|
125
|
+
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
126
|
+
pipette_id=pipette_id,
|
|
127
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
128
|
+
)
|
|
129
|
+
return (
|
|
130
|
+
self._state_view.pipettes.get_aspirated_volume(pipette_id) is not None
|
|
131
|
+
and hw_pipette.config["ready_to_aspirate"]
|
|
132
|
+
and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
136
|
+
"""Prepare for pipette aspiration.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
140
|
+
"""
|
|
141
|
+
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
|
|
142
|
+
await self._hardware_api.prepare_for_aspirate(mount=hw_mount)
|
|
143
|
+
|
|
144
|
+
def get_hw_aspirate_params(
|
|
145
|
+
self,
|
|
146
|
+
pipette_id: str,
|
|
147
|
+
volume: float,
|
|
148
|
+
command_note_adder: CommandNoteAdder,
|
|
149
|
+
) -> Tuple[HardwarePipette, float]:
|
|
150
|
+
"""Get params for hardware aspirate."""
|
|
151
|
+
_adjusted_volume = _validate_aspirate_volume(
|
|
152
|
+
state_view=self._state_view,
|
|
153
|
+
pipette_id=pipette_id,
|
|
154
|
+
aspirate_volume=volume,
|
|
155
|
+
command_note_adder=command_note_adder,
|
|
156
|
+
)
|
|
157
|
+
_hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
158
|
+
pipette_id=pipette_id,
|
|
159
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
160
|
+
)
|
|
161
|
+
return _hw_pipette, _adjusted_volume
|
|
162
|
+
|
|
163
|
+
def get_hw_dispense_params(
|
|
164
|
+
self,
|
|
165
|
+
pipette_id: str,
|
|
166
|
+
volume: float,
|
|
167
|
+
) -> Tuple[HardwarePipette, float]:
|
|
168
|
+
"""Get params for hardware dispense."""
|
|
169
|
+
_adjusted_volume = _validate_dispense_volume(
|
|
170
|
+
state_view=self._state_view,
|
|
171
|
+
pipette_id=pipette_id,
|
|
172
|
+
dispense_volume=volume,
|
|
173
|
+
)
|
|
174
|
+
_hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
175
|
+
pipette_id=pipette_id,
|
|
176
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
177
|
+
)
|
|
178
|
+
return _hw_pipette, _adjusted_volume
|
|
179
|
+
|
|
180
|
+
async def aspirate_while_tracking(
|
|
181
|
+
self,
|
|
182
|
+
pipette_id: str,
|
|
183
|
+
labware_id: str,
|
|
184
|
+
well_name: str,
|
|
185
|
+
volume: float,
|
|
186
|
+
flow_rate: float,
|
|
187
|
+
command_note_adder: CommandNoteAdder,
|
|
188
|
+
) -> float:
|
|
189
|
+
"""Set flow-rate and aspirate.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
193
|
+
"""
|
|
194
|
+
# get mount and config data from state and hardware controller
|
|
195
|
+
hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
|
|
196
|
+
pipette_id, volume, command_note_adder
|
|
197
|
+
)
|
|
198
|
+
aspirate_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
|
|
199
|
+
labware_id=labware_id,
|
|
200
|
+
well_name=well_name,
|
|
201
|
+
operation_volume=volume * -1,
|
|
202
|
+
pipette_id=pipette_id,
|
|
203
|
+
)
|
|
204
|
+
if isinstance(aspirate_z_distance, SimulatedProbeResult):
|
|
205
|
+
raise InvalidLiquidHeightFound(
|
|
206
|
+
"Aspirate distance must be a float in Hardware pipetting handler."
|
|
207
|
+
)
|
|
208
|
+
with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
|
|
209
|
+
await self._hardware_api.aspirate_while_tracking(
|
|
210
|
+
mount=hw_pipette.mount,
|
|
211
|
+
z_distance=aspirate_z_distance,
|
|
212
|
+
flow_rate=flow_rate,
|
|
213
|
+
volume=adjusted_volume,
|
|
214
|
+
)
|
|
215
|
+
return adjusted_volume
|
|
216
|
+
|
|
217
|
+
async def dispense_while_tracking(
|
|
218
|
+
self,
|
|
219
|
+
pipette_id: str,
|
|
220
|
+
labware_id: str,
|
|
221
|
+
well_name: str,
|
|
222
|
+
volume: float,
|
|
223
|
+
flow_rate: float,
|
|
224
|
+
push_out: Optional[float],
|
|
225
|
+
is_full_dispense: bool = False,
|
|
226
|
+
) -> float:
|
|
227
|
+
"""Set flow-rate and dispense.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
231
|
+
"""
|
|
232
|
+
# get mount and config data from state and hardware controller
|
|
233
|
+
hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
|
|
234
|
+
dispense_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
|
|
235
|
+
labware_id=labware_id,
|
|
236
|
+
well_name=well_name,
|
|
237
|
+
operation_volume=volume,
|
|
238
|
+
pipette_id=pipette_id,
|
|
239
|
+
)
|
|
240
|
+
if isinstance(dispense_z_distance, SimulatedProbeResult):
|
|
241
|
+
raise InvalidLiquidHeightFound(
|
|
242
|
+
"Dispense distance must be a float in Hardware pipetting handler."
|
|
243
|
+
)
|
|
244
|
+
with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
|
|
245
|
+
await self._hardware_api.dispense_while_tracking(
|
|
246
|
+
mount=hw_pipette.mount,
|
|
247
|
+
z_distance=dispense_z_distance,
|
|
248
|
+
flow_rate=flow_rate,
|
|
249
|
+
volume=adjusted_volume,
|
|
250
|
+
push_out=push_out,
|
|
251
|
+
is_full_dispense=is_full_dispense,
|
|
252
|
+
)
|
|
253
|
+
return adjusted_volume
|
|
254
|
+
|
|
255
|
+
async def aspirate_in_place(
|
|
256
|
+
self,
|
|
257
|
+
pipette_id: str,
|
|
258
|
+
volume: float,
|
|
259
|
+
flow_rate: float,
|
|
260
|
+
command_note_adder: CommandNoteAdder,
|
|
261
|
+
correction_volume: float = 0.0,
|
|
262
|
+
) -> float:
|
|
263
|
+
"""Set flow-rate and aspirate.
|
|
264
|
+
|
|
265
|
+
Raises:
|
|
266
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
267
|
+
"""
|
|
268
|
+
# get mount and config data from state and hardware controller
|
|
269
|
+
hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
|
|
270
|
+
pipette_id, volume, command_note_adder
|
|
271
|
+
)
|
|
272
|
+
with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
|
|
273
|
+
await self._hardware_api.aspirate(
|
|
274
|
+
mount=hw_pipette.mount,
|
|
275
|
+
volume=adjusted_volume,
|
|
276
|
+
correction_volume=correction_volume,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return adjusted_volume
|
|
280
|
+
|
|
281
|
+
async def dispense_in_place(
|
|
282
|
+
self,
|
|
283
|
+
pipette_id: str,
|
|
284
|
+
volume: float,
|
|
285
|
+
flow_rate: float,
|
|
286
|
+
push_out: Optional[float],
|
|
287
|
+
is_full_dispense: bool,
|
|
288
|
+
correction_volume: float = 0.0,
|
|
289
|
+
) -> float:
|
|
290
|
+
"""Dispense liquid without moving the pipette."""
|
|
291
|
+
hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
|
|
292
|
+
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
293
|
+
if push_out and push_out < 0:
|
|
294
|
+
raise InvalidPushOutVolumeError(
|
|
295
|
+
"push out value cannot have a negative value."
|
|
296
|
+
)
|
|
297
|
+
with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
|
|
298
|
+
await self._hardware_api.dispense(
|
|
299
|
+
mount=hw_pipette.mount,
|
|
300
|
+
volume=adjusted_volume,
|
|
301
|
+
push_out=push_out,
|
|
302
|
+
correction_volume=correction_volume,
|
|
303
|
+
is_full_dispense=is_full_dispense,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return adjusted_volume
|
|
307
|
+
|
|
308
|
+
async def blow_out_in_place(
|
|
309
|
+
self,
|
|
310
|
+
pipette_id: str,
|
|
311
|
+
flow_rate: float,
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Set flow rate and blow-out."""
|
|
314
|
+
# get mount and config data from state and hardware controller
|
|
315
|
+
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
316
|
+
pipette_id=pipette_id,
|
|
317
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
318
|
+
)
|
|
319
|
+
with self._set_flow_rate(pipette=hw_pipette, blow_out_flow_rate=flow_rate):
|
|
320
|
+
await self._hardware_api.blow_out(mount=hw_pipette.mount)
|
|
321
|
+
|
|
322
|
+
async def liquid_probe_in_place(
|
|
323
|
+
self,
|
|
324
|
+
pipette_id: str,
|
|
325
|
+
labware_id: str,
|
|
326
|
+
well_name: str,
|
|
327
|
+
well_location: WellLocation,
|
|
328
|
+
) -> LiquidTrackingType:
|
|
329
|
+
"""Return liquid level relative to the bottom of the well."""
|
|
330
|
+
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
331
|
+
pipette_id=pipette_id,
|
|
332
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
333
|
+
)
|
|
334
|
+
well_def = self._state_view.labware.get_well_definition(labware_id, well_name)
|
|
335
|
+
well_depth = well_def.depth
|
|
336
|
+
lld_min_height = self._state_view.pipettes.get_current_tip_lld_settings(
|
|
337
|
+
pipette_id=pipette_id
|
|
338
|
+
)
|
|
339
|
+
z_pos = await self._hardware_api.liquid_probe(
|
|
340
|
+
mount=hw_pipette.mount,
|
|
341
|
+
max_z_dist=well_depth - lld_min_height + well_location.offset.z,
|
|
342
|
+
)
|
|
343
|
+
labware_pos = self._state_view.geometry.get_labware_position(labware_id)
|
|
344
|
+
relative_height = z_pos - labware_pos.z - well_def.z
|
|
345
|
+
return float(relative_height)
|
|
346
|
+
|
|
347
|
+
@contextmanager
|
|
348
|
+
def _set_flow_rate(
|
|
349
|
+
self,
|
|
350
|
+
pipette: HardwarePipette,
|
|
351
|
+
aspirate_flow_rate: Optional[float] = None,
|
|
352
|
+
dispense_flow_rate: Optional[float] = None,
|
|
353
|
+
blow_out_flow_rate: Optional[float] = None,
|
|
354
|
+
) -> Iterator[None]:
|
|
355
|
+
"""Context manager for setting flow rate before calling aspirate, dispense, or blowout."""
|
|
356
|
+
original_aspirate_rate = pipette.config["aspirate_flow_rate"]
|
|
357
|
+
original_dispense_rate = pipette.config["dispense_flow_rate"]
|
|
358
|
+
original_blow_out_rate = pipette.config["blow_out_flow_rate"]
|
|
359
|
+
self._hardware_api.set_flow_rate(
|
|
360
|
+
pipette.mount,
|
|
361
|
+
aspirate=aspirate_flow_rate,
|
|
362
|
+
dispense=dispense_flow_rate,
|
|
363
|
+
blow_out=blow_out_flow_rate,
|
|
364
|
+
)
|
|
365
|
+
try:
|
|
366
|
+
yield
|
|
367
|
+
finally:
|
|
368
|
+
self._hardware_api.set_flow_rate(
|
|
369
|
+
pipette.mount,
|
|
370
|
+
aspirate=original_aspirate_rate,
|
|
371
|
+
dispense=original_dispense_rate,
|
|
372
|
+
blow_out=original_blow_out_rate,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
376
|
+
"""Increase evo tip dispense action count."""
|
|
377
|
+
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
378
|
+
pipette_id=pipette_id,
|
|
379
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
380
|
+
)
|
|
381
|
+
await self._hardware_api.increase_evo_disp_count(mount=hw_pipette.mount)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class VirtualPipettingHandler(PipettingHandler):
|
|
385
|
+
"""Liquid handling, using the virtual pipettes.""" ""
|
|
386
|
+
|
|
387
|
+
_state_view: StateView
|
|
388
|
+
|
|
389
|
+
def __init__(
|
|
390
|
+
self,
|
|
391
|
+
state_view: StateView,
|
|
392
|
+
) -> None:
|
|
393
|
+
"""Initialize a PipettingHandler instance."""
|
|
394
|
+
self._state_view = state_view
|
|
395
|
+
|
|
396
|
+
def get_state_view(self) -> StateView:
|
|
397
|
+
"""Get the stateview associated with this handler."""
|
|
398
|
+
return self._state_view
|
|
399
|
+
|
|
400
|
+
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
401
|
+
"""Get whether a pipette is ready to aspirate."""
|
|
402
|
+
return self._state_view.pipettes.get_aspirated_volume(
|
|
403
|
+
pipette_id
|
|
404
|
+
) is not None and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
|
|
405
|
+
|
|
406
|
+
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
407
|
+
"""Virtually prepare to aspirate (no-op)."""
|
|
408
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
409
|
+
|
|
410
|
+
async def aspirate_in_place(
|
|
411
|
+
self,
|
|
412
|
+
pipette_id: str,
|
|
413
|
+
volume: float,
|
|
414
|
+
flow_rate: float,
|
|
415
|
+
command_note_adder: CommandNoteAdder,
|
|
416
|
+
correction_volume: float = 0.0,
|
|
417
|
+
) -> float:
|
|
418
|
+
"""Virtually aspirate (no-op)."""
|
|
419
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
420
|
+
return _validate_aspirate_volume(
|
|
421
|
+
state_view=self._state_view,
|
|
422
|
+
pipette_id=pipette_id,
|
|
423
|
+
aspirate_volume=volume,
|
|
424
|
+
command_note_adder=command_note_adder,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
async def dispense_in_place(
|
|
428
|
+
self,
|
|
429
|
+
pipette_id: str,
|
|
430
|
+
volume: float,
|
|
431
|
+
flow_rate: float,
|
|
432
|
+
push_out: Optional[float],
|
|
433
|
+
is_full_dispense: bool,
|
|
434
|
+
correction_volume: float = 0.0,
|
|
435
|
+
) -> float:
|
|
436
|
+
"""Virtually dispense (no-op)."""
|
|
437
|
+
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
438
|
+
if push_out and push_out < 0:
|
|
439
|
+
raise InvalidPushOutVolumeError(
|
|
440
|
+
"push out value cannot have a negative value."
|
|
441
|
+
)
|
|
442
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="dispense")
|
|
443
|
+
return _validate_dispense_volume(
|
|
444
|
+
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
async def blow_out_in_place(
|
|
448
|
+
self,
|
|
449
|
+
pipette_id: str,
|
|
450
|
+
flow_rate: float,
|
|
451
|
+
) -> None:
|
|
452
|
+
"""Virtually blow out (no-op)."""
|
|
453
|
+
|
|
454
|
+
async def liquid_probe_in_place(
|
|
455
|
+
self,
|
|
456
|
+
pipette_id: str,
|
|
457
|
+
labware_id: str,
|
|
458
|
+
well_name: str,
|
|
459
|
+
well_location: WellLocation,
|
|
460
|
+
) -> LiquidTrackingType:
|
|
461
|
+
"""Detect liquid level."""
|
|
462
|
+
return SimulatedProbeResult()
|
|
463
|
+
|
|
464
|
+
def _validate_tip_attached(self, pipette_id: str, command_name: str) -> None:
|
|
465
|
+
"""Validate if there is a tip attached."""
|
|
466
|
+
tip_geometry = self._state_view.pipettes.get_attached_tip(pipette_id)
|
|
467
|
+
if not tip_geometry:
|
|
468
|
+
raise TipNotAttachedError(
|
|
469
|
+
f"Cannot perform {command_name} without a tip attached"
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
async def aspirate_while_tracking(
|
|
473
|
+
self,
|
|
474
|
+
pipette_id: str,
|
|
475
|
+
labware_id: str,
|
|
476
|
+
well_name: str,
|
|
477
|
+
volume: float,
|
|
478
|
+
flow_rate: float,
|
|
479
|
+
command_note_adder: CommandNoteAdder,
|
|
480
|
+
) -> float:
|
|
481
|
+
"""Virtually aspirate (no-op)."""
|
|
482
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
483
|
+
|
|
484
|
+
return _validate_aspirate_volume(
|
|
485
|
+
state_view=self._state_view,
|
|
486
|
+
pipette_id=pipette_id,
|
|
487
|
+
aspirate_volume=volume,
|
|
488
|
+
command_note_adder=command_note_adder,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
async def dispense_while_tracking(
|
|
492
|
+
self,
|
|
493
|
+
pipette_id: str,
|
|
494
|
+
labware_id: str,
|
|
495
|
+
well_name: str,
|
|
496
|
+
volume: float,
|
|
497
|
+
flow_rate: float,
|
|
498
|
+
push_out: Optional[float],
|
|
499
|
+
is_full_dispense: bool = False,
|
|
500
|
+
) -> float:
|
|
501
|
+
"""Virtually dispense (no-op)."""
|
|
502
|
+
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
503
|
+
if push_out and push_out < 0:
|
|
504
|
+
raise InvalidPushOutVolumeError(
|
|
505
|
+
"push out value cannot have a negative value."
|
|
506
|
+
)
|
|
507
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="dispense")
|
|
508
|
+
return _validate_dispense_volume(
|
|
509
|
+
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
513
|
+
"""Increase evo tip dispense action count."""
|
|
514
|
+
pass
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def create_pipetting_handler(
|
|
518
|
+
state_view: StateView, hardware_api: HardwareControlAPI
|
|
519
|
+
) -> PipettingHandler:
|
|
520
|
+
"""Create a pipetting handler."""
|
|
521
|
+
return (
|
|
522
|
+
HardwarePipettingHandler(state_view=state_view, hardware_api=hardware_api)
|
|
523
|
+
if state_view.config.use_virtual_pipettes is False
|
|
524
|
+
else VirtualPipettingHandler(state_view=state_view)
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _validate_aspirate_volume(
|
|
529
|
+
state_view: StateView,
|
|
530
|
+
pipette_id: str,
|
|
531
|
+
aspirate_volume: float,
|
|
532
|
+
command_note_adder: CommandNoteAdder,
|
|
533
|
+
) -> float:
|
|
534
|
+
"""Get whether the given volume is valid to aspirate right now.
|
|
535
|
+
|
|
536
|
+
Return the volume to aspirate, possibly clamped, or raise an
|
|
537
|
+
InvalidAspirateVolumeError.
|
|
538
|
+
"""
|
|
539
|
+
working_volume = state_view.pipettes.get_working_volume(pipette_id=pipette_id)
|
|
540
|
+
|
|
541
|
+
current_volume = (
|
|
542
|
+
state_view.pipettes.get_aspirated_volume(pipette_id=pipette_id) or 0
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# TODO(mm, 2024-01-11): We should probably just use
|
|
546
|
+
# state_view.pipettes.get_available_volume()? Its whole `None` return vs. exception
|
|
547
|
+
# raising thing is confusing me.
|
|
548
|
+
available_volume = working_volume - current_volume
|
|
549
|
+
available_volume_with_tolerance = (
|
|
550
|
+
available_volume + _VOLUME_ROUNDING_ERROR_TOLERANCE
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
if aspirate_volume > available_volume_with_tolerance:
|
|
554
|
+
raise InvalidAspirateVolumeError(
|
|
555
|
+
attempted_aspirate_volume=aspirate_volume,
|
|
556
|
+
available_volume=available_volume,
|
|
557
|
+
max_pipette_volume=state_view.pipettes.get_maximum_volume(
|
|
558
|
+
pipette_id=pipette_id
|
|
559
|
+
),
|
|
560
|
+
max_tip_volume=_get_max_tip_volume(
|
|
561
|
+
state_view=state_view, pipette_id=pipette_id
|
|
562
|
+
),
|
|
563
|
+
)
|
|
564
|
+
else:
|
|
565
|
+
volume_to_aspirate = min(aspirate_volume, available_volume)
|
|
566
|
+
if volume_to_aspirate < aspirate_volume:
|
|
567
|
+
command_note_adder(
|
|
568
|
+
CommandNote(
|
|
569
|
+
noteKind="warning",
|
|
570
|
+
shortMessage=f"Aspirate clamped to {available_volume} µL",
|
|
571
|
+
longMessage=(
|
|
572
|
+
f"Command requested to aspirate {aspirate_volume} µL but only"
|
|
573
|
+
f" {available_volume} µL were available in the pipette. This is"
|
|
574
|
+
" probably a floating point artifact."
|
|
575
|
+
),
|
|
576
|
+
source="execution",
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
return volume_to_aspirate
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _validate_dispense_volume(
|
|
583
|
+
state_view: StateView, pipette_id: str, dispense_volume: float
|
|
584
|
+
) -> float:
|
|
585
|
+
"""Get whether the given volume is valid to dispense right now.
|
|
586
|
+
|
|
587
|
+
Return the volume to dispense, possibly clamped, or raise an
|
|
588
|
+
InvalidDispenseVolumeError.
|
|
589
|
+
"""
|
|
590
|
+
aspirated_volume = state_view.pipettes.get_aspirated_volume(pipette_id)
|
|
591
|
+
if aspirated_volume is None:
|
|
592
|
+
raise InvalidDispenseVolumeError(
|
|
593
|
+
"Cannot perform a dispense if there is no volume in attached tip."
|
|
594
|
+
)
|
|
595
|
+
else:
|
|
596
|
+
remaining = aspirated_volume - dispense_volume
|
|
597
|
+
if remaining < -_VOLUME_ROUNDING_ERROR_TOLERANCE:
|
|
598
|
+
raise InvalidDispenseVolumeError(
|
|
599
|
+
f"Cannot dispense {dispense_volume} µL when only {aspirated_volume} µL has been aspirated."
|
|
600
|
+
)
|
|
601
|
+
else:
|
|
602
|
+
return min(dispense_volume, aspirated_volume)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _get_max_tip_volume(state_view: StateView, pipette_id: str) -> Optional[float]:
|
|
606
|
+
attached_tip = state_view.pipettes.get_attached_tip(pipette_id=pipette_id)
|
|
607
|
+
return None if attached_tip is None else attached_tip.volume
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Command queue execution worker module."""
|
|
2
|
+
import asyncio
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from typing import Optional, AsyncGenerator, Callable
|
|
5
|
+
|
|
6
|
+
from ..state.state import StateStore
|
|
7
|
+
from .command_executor import CommandExecutor
|
|
8
|
+
|
|
9
|
+
log = getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QueueWorker:
|
|
13
|
+
"""Handle and track execution of commands queued in ProtocolEngine state."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
state_store: StateStore,
|
|
18
|
+
command_executor: CommandExecutor,
|
|
19
|
+
command_generator: Callable[[], AsyncGenerator[str, None]],
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Initialize the queue worker's dependencies and state.
|
|
22
|
+
|
|
23
|
+
Arguments:
|
|
24
|
+
state_store: The source of truth for protocol state, including
|
|
25
|
+
all queued commands.
|
|
26
|
+
command_executor: Interface used to execute and update commands.
|
|
27
|
+
command_generator: Command generator to get the next command to execute.
|
|
28
|
+
"""
|
|
29
|
+
self._state_store: StateStore = state_store
|
|
30
|
+
self._command_executor: CommandExecutor = command_executor
|
|
31
|
+
self._command_generator = command_generator
|
|
32
|
+
self._worker_task: Optional["asyncio.Task[None]"] = None
|
|
33
|
+
|
|
34
|
+
def start(self) -> None:
|
|
35
|
+
"""Start processing jobs.
|
|
36
|
+
|
|
37
|
+
This method will no-op if the worker is already running.
|
|
38
|
+
"""
|
|
39
|
+
if self._worker_task is None:
|
|
40
|
+
self._worker_task = asyncio.create_task(self._run_commands())
|
|
41
|
+
|
|
42
|
+
def cancel(self) -> None:
|
|
43
|
+
"""Cancel any in-progress commands.
|
|
44
|
+
|
|
45
|
+
This method is synchronous to allow synchronous callers to
|
|
46
|
+
cancel the ongoing background task in situations where it
|
|
47
|
+
needs to happen immediately.
|
|
48
|
+
|
|
49
|
+
You should call `join` after calling `cancel` to clean up and
|
|
50
|
+
propagate errors.
|
|
51
|
+
"""
|
|
52
|
+
if self._worker_task:
|
|
53
|
+
self._worker_task.cancel()
|
|
54
|
+
|
|
55
|
+
async def join(self) -> None:
|
|
56
|
+
"""Wait for the worker to finish, propagating any errors."""
|
|
57
|
+
worker_task = self._worker_task
|
|
58
|
+
|
|
59
|
+
if worker_task:
|
|
60
|
+
self._worker_task = None
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
await worker_task
|
|
64
|
+
except asyncio.CancelledError: # From self.cancel().
|
|
65
|
+
pass
|
|
66
|
+
except Exception as e:
|
|
67
|
+
log.error("Unhandled exception in QueueWorker job", exc_info=e)
|
|
68
|
+
raise e
|
|
69
|
+
|
|
70
|
+
async def _run_commands(self) -> None:
|
|
71
|
+
async for command_id in self._command_generator():
|
|
72
|
+
try:
|
|
73
|
+
await self._command_executor.execute(command_id=command_id)
|
|
74
|
+
except BaseException:
|
|
75
|
+
log.exception(
|
|
76
|
+
# The state can tear if e.g. we've finished updating PipetteStore,
|
|
77
|
+
# but the exception came before we could update LabwareStore. Or
|
|
78
|
+
# the exception could have interrupted updating a single store.
|
|
79
|
+
"Unhandled failure in command executor."
|
|
80
|
+
" This is a bug in opentrons.protocol_engine"
|
|
81
|
+
" and has probably left the ProtocolEngine in a torn state."
|
|
82
|
+
)
|
|
83
|
+
raise
|
|
84
|
+
# Yield to the event loop in case we're executing a long sequence of commands
|
|
85
|
+
# that never yields internally. For example, a long sequence of comment commands.
|
|
86
|
+
await asyncio.sleep(0)
|