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,2391 @@
|
|
|
1
|
+
"""ProtocolEngine-based InstrumentContext core implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from itertools import dropwhile
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from typing import (
|
|
7
|
+
Optional,
|
|
8
|
+
TYPE_CHECKING,
|
|
9
|
+
cast,
|
|
10
|
+
Union,
|
|
11
|
+
List,
|
|
12
|
+
Sequence,
|
|
13
|
+
Tuple,
|
|
14
|
+
NamedTuple,
|
|
15
|
+
Literal,
|
|
16
|
+
)
|
|
17
|
+
from opentrons.types import (
|
|
18
|
+
Location,
|
|
19
|
+
Mount,
|
|
20
|
+
NozzleConfigurationType,
|
|
21
|
+
NozzleMapInterface,
|
|
22
|
+
MeniscusTrackingTarget,
|
|
23
|
+
)
|
|
24
|
+
from opentrons.hardware_control import SyncHardwareAPI
|
|
25
|
+
from opentrons.hardware_control.dev_types import PipetteDict
|
|
26
|
+
from opentrons.protocols.api_support.util import FlowRates, find_value_for_api_version
|
|
27
|
+
from opentrons.protocols.api_support.types import APIVersion
|
|
28
|
+
from opentrons.protocols.advanced_control.transfers.common import (
|
|
29
|
+
TransferTipPolicyV2,
|
|
30
|
+
NoLiquidClassPropertyError,
|
|
31
|
+
)
|
|
32
|
+
from opentrons.protocols.advanced_control.transfers import common as tx_commons
|
|
33
|
+
from opentrons.protocols.advanced_control.transfers.transfer_liquid_utils import (
|
|
34
|
+
check_current_volume_before_dispensing,
|
|
35
|
+
)
|
|
36
|
+
from opentrons.protocol_engine import commands as cmd
|
|
37
|
+
from opentrons.protocol_engine import (
|
|
38
|
+
DeckPoint,
|
|
39
|
+
DropTipWellLocation,
|
|
40
|
+
DropTipWellOrigin,
|
|
41
|
+
WellLocation,
|
|
42
|
+
WellOrigin,
|
|
43
|
+
WellOffset,
|
|
44
|
+
AllNozzleLayoutConfiguration,
|
|
45
|
+
SingleNozzleLayoutConfiguration,
|
|
46
|
+
RowNozzleLayoutConfiguration,
|
|
47
|
+
ColumnNozzleLayoutConfiguration,
|
|
48
|
+
QuadrantNozzleLayoutConfiguration,
|
|
49
|
+
)
|
|
50
|
+
from opentrons.protocol_engine.types import (
|
|
51
|
+
PRIMARY_NOZZLE_LITERAL,
|
|
52
|
+
NozzleLayoutConfigurationType,
|
|
53
|
+
AddressableOffsetVector,
|
|
54
|
+
LiquidClassRecord,
|
|
55
|
+
NextTipInfo,
|
|
56
|
+
PickUpTipWellLocation,
|
|
57
|
+
LiquidHandlingWellLocation,
|
|
58
|
+
)
|
|
59
|
+
from opentrons.protocol_engine.types import (
|
|
60
|
+
LiquidTrackingType,
|
|
61
|
+
WellLocationFunction,
|
|
62
|
+
)
|
|
63
|
+
from opentrons.protocol_engine.types.automatic_tip_selection import (
|
|
64
|
+
NoTipAvailable,
|
|
65
|
+
NoTipReason,
|
|
66
|
+
)
|
|
67
|
+
from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
|
|
68
|
+
from opentrons.protocol_engine.clients import SyncClient as EngineClient
|
|
69
|
+
from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
|
|
70
|
+
from opentrons_shared_data.pipette.types import (
|
|
71
|
+
PIPETTE_API_NAMES_MAP,
|
|
72
|
+
LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP,
|
|
73
|
+
)
|
|
74
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
75
|
+
UnsupportedHardwareCommand,
|
|
76
|
+
CommandPreconditionViolated,
|
|
77
|
+
)
|
|
78
|
+
from opentrons_shared_data.liquid_classes.liquid_class_definition import BlowoutLocation
|
|
79
|
+
from opentrons.protocol_api._nozzle_layout import NozzleLayout
|
|
80
|
+
from . import overlap_versions, pipette_movement_conflict
|
|
81
|
+
from . import transfer_components_executor as tx_comps_executor
|
|
82
|
+
|
|
83
|
+
from .well import WellCore
|
|
84
|
+
from .labware import LabwareCore
|
|
85
|
+
from ..instrument import AbstractInstrument
|
|
86
|
+
from ...disposal_locations import TrashBin, WasteChute
|
|
87
|
+
|
|
88
|
+
if TYPE_CHECKING:
|
|
89
|
+
from .protocol import ProtocolCore
|
|
90
|
+
from opentrons.protocol_api._liquid import LiquidClass
|
|
91
|
+
from opentrons.protocol_api._liquid_properties import (
|
|
92
|
+
TransferProperties,
|
|
93
|
+
MultiDispenseProperties,
|
|
94
|
+
SingleDispenseProperties,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
_DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
|
|
98
|
+
_RESIN_TIP_DEFAULT_VOLUME = 400
|
|
99
|
+
_RESIN_TIP_DEFAULT_FLOW_RATE = 10.0
|
|
100
|
+
|
|
101
|
+
_FLEX_PIPETTE_NAMES_FIXED_IN = APIVersion(2, 23)
|
|
102
|
+
"""The version after which InstrumentContext.name returns the correct API-specific names of Flex pipettes."""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
106
|
+
"""Instrument API core using a ProtocolEngine.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
pipette_id: ProtocolEngine ID of the loaded instrument.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
pipette_id: str,
|
|
115
|
+
engine_client: EngineClient,
|
|
116
|
+
sync_hardware_api: SyncHardwareAPI,
|
|
117
|
+
protocol_core: ProtocolCore,
|
|
118
|
+
default_movement_speed: float,
|
|
119
|
+
) -> None:
|
|
120
|
+
self._pipette_id = pipette_id
|
|
121
|
+
self._engine_client = engine_client
|
|
122
|
+
self._sync_hardware_api = sync_hardware_api
|
|
123
|
+
self._protocol_core = protocol_core
|
|
124
|
+
|
|
125
|
+
# TODO(jbl 2022-11-03) flow_rates should not live in the cores, and should be moved to the protocol context
|
|
126
|
+
# along with other rate related refactors (for the hardware API)
|
|
127
|
+
flow_rates = self._engine_client.state.pipettes.get_flow_rates(pipette_id)
|
|
128
|
+
self._aspirate_flow_rate = find_value_for_api_version(
|
|
129
|
+
MAX_SUPPORTED_VERSION, flow_rates.default_aspirate
|
|
130
|
+
)
|
|
131
|
+
self._dispense_flow_rate = find_value_for_api_version(
|
|
132
|
+
MAX_SUPPORTED_VERSION, flow_rates.default_dispense
|
|
133
|
+
)
|
|
134
|
+
self._blow_out_flow_rate = find_value_for_api_version(
|
|
135
|
+
MAX_SUPPORTED_VERSION, flow_rates.default_blow_out
|
|
136
|
+
)
|
|
137
|
+
self._flow_rates = FlowRates(self)
|
|
138
|
+
|
|
139
|
+
self.set_default_speed(speed=default_movement_speed)
|
|
140
|
+
self._liquid_presence_detection = bool(
|
|
141
|
+
self._engine_client.state.pipettes.get_liquid_presence_detection(pipette_id)
|
|
142
|
+
)
|
|
143
|
+
if (
|
|
144
|
+
self._liquid_presence_detection
|
|
145
|
+
and not self._pressure_supported_by_pipette()
|
|
146
|
+
):
|
|
147
|
+
raise UnsupportedHardwareCommand(
|
|
148
|
+
"Pressure sensor not available for this pipette"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def pipette_id(self) -> str:
|
|
153
|
+
"""The instrument's unique ProtocolEngine ID."""
|
|
154
|
+
return self._pipette_id
|
|
155
|
+
|
|
156
|
+
def get_default_speed(self) -> float:
|
|
157
|
+
speed = self._engine_client.state.pipettes.get_movement_speed(
|
|
158
|
+
pipette_id=self._pipette_id
|
|
159
|
+
)
|
|
160
|
+
assert speed is not None, "Pipette loading should have set a default speed."
|
|
161
|
+
return speed
|
|
162
|
+
|
|
163
|
+
def set_default_speed(self, speed: float) -> None:
|
|
164
|
+
self._engine_client.set_pipette_movement_speed(
|
|
165
|
+
pipette_id=self._pipette_id, speed=speed
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def air_gap_in_place(
|
|
169
|
+
self, volume: float, flow_rate: float, correction_volume: Optional[float] = None
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Aspirate a given volume of air from the current location of the pipette.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
volume: The volume of air to aspirate, in microliters.
|
|
175
|
+
folw_rate: The flow rate of air into the pipette, in microliters/s
|
|
176
|
+
"""
|
|
177
|
+
self._engine_client.execute_command(
|
|
178
|
+
cmd.AirGapInPlaceParams(
|
|
179
|
+
pipetteId=self._pipette_id,
|
|
180
|
+
volume=volume,
|
|
181
|
+
flowRate=flow_rate,
|
|
182
|
+
correctionVolume=correction_volume,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def aspirate(
|
|
187
|
+
self,
|
|
188
|
+
location: Location,
|
|
189
|
+
well_core: Optional[WellCore],
|
|
190
|
+
volume: float,
|
|
191
|
+
rate: float,
|
|
192
|
+
flow_rate: float,
|
|
193
|
+
in_place: bool,
|
|
194
|
+
meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
|
|
195
|
+
correction_volume: Optional[float] = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Aspirate a given volume of liquid from the specified location.
|
|
198
|
+
Args:
|
|
199
|
+
volume: The volume of liquid to aspirate, in microliters.
|
|
200
|
+
location: The exact location to aspirate from.
|
|
201
|
+
well_core: The well to aspirate from, if applicable.
|
|
202
|
+
rate: Not used in this core.
|
|
203
|
+
flow_rate: The flow rate in µL/s to aspirate at.
|
|
204
|
+
in_place: whether this is a in-place command.
|
|
205
|
+
meniscus_tracking: Optional data about where to aspirate from.
|
|
206
|
+
"""
|
|
207
|
+
if meniscus_tracking == MeniscusTrackingTarget.START:
|
|
208
|
+
raise ValueError("Cannot aspirate at the starting liquid height.")
|
|
209
|
+
if well_core is None:
|
|
210
|
+
if not in_place:
|
|
211
|
+
self._engine_client.execute_command(
|
|
212
|
+
cmd.MoveToCoordinatesParams(
|
|
213
|
+
pipetteId=self._pipette_id,
|
|
214
|
+
coordinates=DeckPoint(
|
|
215
|
+
x=location.point.x, y=location.point.y, z=location.point.z
|
|
216
|
+
),
|
|
217
|
+
minimumZHeight=None,
|
|
218
|
+
forceDirect=False,
|
|
219
|
+
speed=None,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
self._engine_client.execute_command(
|
|
224
|
+
cmd.AspirateInPlaceParams(
|
|
225
|
+
pipetteId=self._pipette_id,
|
|
226
|
+
volume=volume,
|
|
227
|
+
flowRate=flow_rate,
|
|
228
|
+
correctionVolume=correction_volume,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
else:
|
|
233
|
+
well_name = well_core.get_name()
|
|
234
|
+
labware_id = well_core.labware_id
|
|
235
|
+
|
|
236
|
+
(
|
|
237
|
+
well_location,
|
|
238
|
+
dynamic_liquid_tracking,
|
|
239
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
240
|
+
labware_id=labware_id,
|
|
241
|
+
well_name=well_name,
|
|
242
|
+
absolute_point=location.point,
|
|
243
|
+
location_type=WellLocationFunction.LIQUID_HANDLING,
|
|
244
|
+
meniscus_tracking=meniscus_tracking,
|
|
245
|
+
)
|
|
246
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
247
|
+
engine_state=self._engine_client.state,
|
|
248
|
+
pipette_id=self._pipette_id,
|
|
249
|
+
labware_id=labware_id,
|
|
250
|
+
well_name=well_name,
|
|
251
|
+
well_location=well_location,
|
|
252
|
+
)
|
|
253
|
+
assert isinstance(well_location, LiquidHandlingWellLocation)
|
|
254
|
+
if dynamic_liquid_tracking:
|
|
255
|
+
self._engine_client.execute_command(
|
|
256
|
+
cmd.AspirateWhileTrackingParams(
|
|
257
|
+
pipetteId=self._pipette_id,
|
|
258
|
+
labwareId=labware_id,
|
|
259
|
+
wellName=well_name,
|
|
260
|
+
wellLocation=well_location,
|
|
261
|
+
volume=volume,
|
|
262
|
+
flowRate=flow_rate,
|
|
263
|
+
correctionVolume=correction_volume,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
self._engine_client.execute_command(
|
|
268
|
+
cmd.AspirateParams(
|
|
269
|
+
pipetteId=self._pipette_id,
|
|
270
|
+
labwareId=labware_id,
|
|
271
|
+
wellName=well_name,
|
|
272
|
+
wellLocation=well_location,
|
|
273
|
+
volume=volume,
|
|
274
|
+
flowRate=flow_rate,
|
|
275
|
+
correctionVolume=correction_volume,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
280
|
+
|
|
281
|
+
def dispense(
|
|
282
|
+
self,
|
|
283
|
+
location: Union[Location, TrashBin, WasteChute],
|
|
284
|
+
well_core: Optional[WellCore],
|
|
285
|
+
volume: float,
|
|
286
|
+
rate: float,
|
|
287
|
+
flow_rate: float,
|
|
288
|
+
in_place: bool,
|
|
289
|
+
push_out: Optional[float],
|
|
290
|
+
meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
|
|
291
|
+
correction_volume: Optional[float] = None,
|
|
292
|
+
) -> None:
|
|
293
|
+
"""Dispense a given volume of liquid into the specified location.
|
|
294
|
+
Args:
|
|
295
|
+
volume: The volume of liquid to dispense, in microliters.
|
|
296
|
+
location: The exact location to dispense to.
|
|
297
|
+
well_core: The well to dispense to, if applicable.
|
|
298
|
+
rate: Not used in this core.
|
|
299
|
+
flow_rate: The flow rate in µL/s to dispense at.
|
|
300
|
+
in_place: whether this is a in-place command.
|
|
301
|
+
push_out: The amount to push the plunger below bottom position.
|
|
302
|
+
meniscus_tracking: Optional data about where to dispense from.
|
|
303
|
+
"""
|
|
304
|
+
if self._protocol_core.api_version < _DISPENSE_VOLUME_VALIDATION_ADDED_IN:
|
|
305
|
+
# In older API versions, when you try to dispense more than you can,
|
|
306
|
+
# it gets clamped.
|
|
307
|
+
volume = min(volume, self.get_current_volume())
|
|
308
|
+
else:
|
|
309
|
+
# Newer API versions raise an error if you try to dispense more than
|
|
310
|
+
# you can. Let the error come from Protocol Engine's validation.
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
if well_core is None:
|
|
314
|
+
if not in_place:
|
|
315
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
316
|
+
self._move_to_disposal_location(
|
|
317
|
+
disposal_location=location, force_direct=False, speed=None
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
self._engine_client.execute_command(
|
|
321
|
+
cmd.MoveToCoordinatesParams(
|
|
322
|
+
pipetteId=self._pipette_id,
|
|
323
|
+
coordinates=DeckPoint(
|
|
324
|
+
x=location.point.x,
|
|
325
|
+
y=location.point.y,
|
|
326
|
+
z=location.point.z,
|
|
327
|
+
),
|
|
328
|
+
minimumZHeight=None,
|
|
329
|
+
forceDirect=False,
|
|
330
|
+
speed=None,
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
self._engine_client.execute_command(
|
|
335
|
+
cmd.DispenseInPlaceParams(
|
|
336
|
+
pipetteId=self._pipette_id,
|
|
337
|
+
volume=volume,
|
|
338
|
+
flowRate=flow_rate,
|
|
339
|
+
pushOut=push_out,
|
|
340
|
+
correctionVolume=correction_volume,
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
else:
|
|
344
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
345
|
+
raise ValueError("Trash Bin and Waste Chute have no Wells.")
|
|
346
|
+
well_name = well_core.get_name()
|
|
347
|
+
labware_id = well_core.labware_id
|
|
348
|
+
|
|
349
|
+
(
|
|
350
|
+
well_location,
|
|
351
|
+
dynamic_liquid_tracking,
|
|
352
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
353
|
+
labware_id=labware_id,
|
|
354
|
+
well_name=well_name,
|
|
355
|
+
absolute_point=location.point,
|
|
356
|
+
location_type=WellLocationFunction.LIQUID_HANDLING,
|
|
357
|
+
meniscus_tracking=meniscus_tracking,
|
|
358
|
+
)
|
|
359
|
+
assert isinstance(well_location, LiquidHandlingWellLocation)
|
|
360
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
361
|
+
engine_state=self._engine_client.state,
|
|
362
|
+
pipette_id=self._pipette_id,
|
|
363
|
+
labware_id=labware_id,
|
|
364
|
+
well_name=well_name,
|
|
365
|
+
well_location=well_location,
|
|
366
|
+
)
|
|
367
|
+
if dynamic_liquid_tracking:
|
|
368
|
+
self._engine_client.execute_command(
|
|
369
|
+
cmd.DispenseWhileTrackingParams(
|
|
370
|
+
pipetteId=self._pipette_id,
|
|
371
|
+
labwareId=labware_id,
|
|
372
|
+
wellName=well_name,
|
|
373
|
+
wellLocation=well_location,
|
|
374
|
+
volume=volume,
|
|
375
|
+
flowRate=flow_rate,
|
|
376
|
+
pushOut=push_out,
|
|
377
|
+
correctionVolume=correction_volume,
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
self._engine_client.execute_command(
|
|
382
|
+
cmd.DispenseParams(
|
|
383
|
+
pipetteId=self._pipette_id,
|
|
384
|
+
labwareId=labware_id,
|
|
385
|
+
wellName=well_name,
|
|
386
|
+
wellLocation=well_location,
|
|
387
|
+
volume=volume,
|
|
388
|
+
flowRate=flow_rate,
|
|
389
|
+
pushOut=push_out,
|
|
390
|
+
correctionVolume=correction_volume,
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
395
|
+
|
|
396
|
+
def blow_out(
|
|
397
|
+
self,
|
|
398
|
+
location: Union[Location, TrashBin, WasteChute],
|
|
399
|
+
well_core: Optional[WellCore],
|
|
400
|
+
in_place: bool,
|
|
401
|
+
) -> None:
|
|
402
|
+
"""Blow liquid out of the tip.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
location: The location to blow out into.
|
|
406
|
+
well_core: The well to blow out into.
|
|
407
|
+
in_place: whether this is a in-place command.
|
|
408
|
+
"""
|
|
409
|
+
flow_rate = self.get_blow_out_flow_rate(1.0)
|
|
410
|
+
if well_core is None:
|
|
411
|
+
if not in_place:
|
|
412
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
413
|
+
self._move_to_disposal_location(
|
|
414
|
+
disposal_location=location, force_direct=False, speed=None
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
self._engine_client.execute_command(
|
|
418
|
+
cmd.MoveToCoordinatesParams(
|
|
419
|
+
pipetteId=self._pipette_id,
|
|
420
|
+
coordinates=DeckPoint(
|
|
421
|
+
x=location.point.x,
|
|
422
|
+
y=location.point.y,
|
|
423
|
+
z=location.point.z,
|
|
424
|
+
),
|
|
425
|
+
forceDirect=False,
|
|
426
|
+
minimumZHeight=None,
|
|
427
|
+
speed=None,
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
self._engine_client.execute_command(
|
|
432
|
+
cmd.BlowOutInPlaceParams(pipetteId=self._pipette_id, flowRate=flow_rate)
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
436
|
+
raise ValueError("Trash Bin and Waste Chute have no Wells.")
|
|
437
|
+
well_name = well_core.get_name()
|
|
438
|
+
labware_id = well_core.labware_id
|
|
439
|
+
|
|
440
|
+
(
|
|
441
|
+
well_location,
|
|
442
|
+
_,
|
|
443
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
444
|
+
labware_id=labware_id,
|
|
445
|
+
well_name=well_name,
|
|
446
|
+
absolute_point=location.point,
|
|
447
|
+
location_type=WellLocationFunction.BASE,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
451
|
+
engine_state=self._engine_client.state,
|
|
452
|
+
pipette_id=self._pipette_id,
|
|
453
|
+
labware_id=labware_id,
|
|
454
|
+
well_name=well_name,
|
|
455
|
+
well_location=well_location,
|
|
456
|
+
)
|
|
457
|
+
assert isinstance(well_location, WellLocation)
|
|
458
|
+
self._engine_client.execute_command(
|
|
459
|
+
cmd.BlowOutParams(
|
|
460
|
+
pipetteId=self._pipette_id,
|
|
461
|
+
labwareId=labware_id,
|
|
462
|
+
wellName=well_name,
|
|
463
|
+
wellLocation=well_location,
|
|
464
|
+
# TODO(jbl 2022-11-07) PAPIv2 does not have an argument for rate and
|
|
465
|
+
# this also needs to be refactored along with other flow rate related issues
|
|
466
|
+
flowRate=flow_rate,
|
|
467
|
+
)
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
471
|
+
|
|
472
|
+
def touch_tip(
|
|
473
|
+
self,
|
|
474
|
+
location: Location,
|
|
475
|
+
well_core: WellCore,
|
|
476
|
+
radius: float,
|
|
477
|
+
z_offset: float,
|
|
478
|
+
speed: float,
|
|
479
|
+
mm_from_edge: Optional[float] = None,
|
|
480
|
+
) -> None:
|
|
481
|
+
"""Touch pipette tip to edges of the well
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
location: Location moved to, only used for ProtocolCore location cache.
|
|
485
|
+
well_core: The target well for touch tip.
|
|
486
|
+
radius: Percentage modifier for well radius to touch.
|
|
487
|
+
z_offset: Vertical offset for pipette tip during touch tip.
|
|
488
|
+
speed: Speed for the touch tip movements.
|
|
489
|
+
mm_from_edge: Offset from the edge of the well to move to. Requires a radius of 1.
|
|
490
|
+
"""
|
|
491
|
+
if mm_from_edge is not None and radius != 1.0:
|
|
492
|
+
raise ValueError("radius must be set to 1.0 if mm_from_edge is provided.")
|
|
493
|
+
|
|
494
|
+
well_name = well_core.get_name()
|
|
495
|
+
labware_id = well_core.labware_id
|
|
496
|
+
|
|
497
|
+
# Touch tip is always done from the top of the well.
|
|
498
|
+
well_location = WellLocation(
|
|
499
|
+
origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=z_offset)
|
|
500
|
+
)
|
|
501
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
502
|
+
engine_state=self._engine_client.state,
|
|
503
|
+
pipette_id=self._pipette_id,
|
|
504
|
+
labware_id=labware_id,
|
|
505
|
+
well_name=well_name,
|
|
506
|
+
well_location=well_location,
|
|
507
|
+
)
|
|
508
|
+
self._engine_client.execute_command(
|
|
509
|
+
cmd.TouchTipParams(
|
|
510
|
+
pipetteId=self._pipette_id,
|
|
511
|
+
labwareId=labware_id,
|
|
512
|
+
wellName=well_name,
|
|
513
|
+
wellLocation=well_location,
|
|
514
|
+
radius=radius,
|
|
515
|
+
mmFromEdge=mm_from_edge,
|
|
516
|
+
speed=speed,
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
521
|
+
|
|
522
|
+
def pick_up_tip(
|
|
523
|
+
self,
|
|
524
|
+
location: Location,
|
|
525
|
+
well_core: WellCore,
|
|
526
|
+
presses: Optional[int],
|
|
527
|
+
increment: Optional[float],
|
|
528
|
+
prep_after: bool = True,
|
|
529
|
+
) -> None:
|
|
530
|
+
"""Move to and pick up a tip from a given well.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
location: The location of the well we're picking up from.
|
|
534
|
+
Used to calculate the relative well offset for the pick up command.
|
|
535
|
+
well_core: The "well" to pick up from.
|
|
536
|
+
presses: Customize the number of presses the pipette does.
|
|
537
|
+
increment: Customize the movement "distance" of the pipette to press harder.
|
|
538
|
+
prep_after: Not used by this core, pipette preparation will always happen.
|
|
539
|
+
"""
|
|
540
|
+
assert (
|
|
541
|
+
presses is None and increment is None
|
|
542
|
+
), "Tip pick-up with custom presses or increment deprecated"
|
|
543
|
+
|
|
544
|
+
well_name = well_core.get_name()
|
|
545
|
+
labware_id = well_core.labware_id
|
|
546
|
+
|
|
547
|
+
(
|
|
548
|
+
well_location,
|
|
549
|
+
_,
|
|
550
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
551
|
+
labware_id=labware_id,
|
|
552
|
+
well_name=well_name,
|
|
553
|
+
absolute_point=location.point,
|
|
554
|
+
location_type=WellLocationFunction.PICK_UP_TIP,
|
|
555
|
+
)
|
|
556
|
+
pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
|
|
557
|
+
engine_state=self._engine_client.state,
|
|
558
|
+
pipette_id=self._pipette_id,
|
|
559
|
+
labware_id=labware_id,
|
|
560
|
+
)
|
|
561
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
562
|
+
engine_state=self._engine_client.state,
|
|
563
|
+
pipette_id=self._pipette_id,
|
|
564
|
+
labware_id=labware_id,
|
|
565
|
+
well_name=well_name,
|
|
566
|
+
well_location=well_location,
|
|
567
|
+
)
|
|
568
|
+
assert isinstance(well_location, PickUpTipWellLocation)
|
|
569
|
+
self._engine_client.execute_command(
|
|
570
|
+
cmd.PickUpTipParams(
|
|
571
|
+
pipetteId=self._pipette_id,
|
|
572
|
+
labwareId=labware_id,
|
|
573
|
+
wellName=well_name,
|
|
574
|
+
wellLocation=well_location,
|
|
575
|
+
)
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# Set the "last location" unconditionally, even if the command failed
|
|
579
|
+
# and was recovered from and we don't know if the pipette is physically here.
|
|
580
|
+
# This isn't used for path planning, but rather for implicit destination
|
|
581
|
+
# selection like in `pipette.aspirate(location=None)`.
|
|
582
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
583
|
+
|
|
584
|
+
def drop_tip(
|
|
585
|
+
self,
|
|
586
|
+
location: Optional[Location],
|
|
587
|
+
well_core: WellCore,
|
|
588
|
+
home_after: Optional[bool],
|
|
589
|
+
alternate_drop_location: Optional[bool] = False,
|
|
590
|
+
) -> None:
|
|
591
|
+
"""Move to and drop a tip into a given well.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
location: The location of the well we're dropping tip into.
|
|
595
|
+
Used to calculate the relative well offset for the drop command.
|
|
596
|
+
well_core: The well we're dropping into
|
|
597
|
+
home_after: Whether to home the pipette after the tip is dropped.
|
|
598
|
+
alternate_drop_location: Whether to randomize the exact location to drop tip
|
|
599
|
+
within the specified well.
|
|
600
|
+
"""
|
|
601
|
+
well_name = well_core.get_name()
|
|
602
|
+
labware_id = well_core.labware_id
|
|
603
|
+
scrape_tips = False
|
|
604
|
+
|
|
605
|
+
if location is not None:
|
|
606
|
+
(
|
|
607
|
+
relative_well_location,
|
|
608
|
+
_,
|
|
609
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
610
|
+
labware_id=labware_id,
|
|
611
|
+
well_name=well_name,
|
|
612
|
+
absolute_point=location.point,
|
|
613
|
+
location_type=WellLocationFunction.DROP_TIP,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
well_location = DropTipWellLocation(
|
|
617
|
+
origin=DropTipWellOrigin(relative_well_location.origin.value),
|
|
618
|
+
offset=relative_well_location.offset,
|
|
619
|
+
)
|
|
620
|
+
else:
|
|
621
|
+
well_location = DropTipWellLocation()
|
|
622
|
+
|
|
623
|
+
if self._engine_client.state.labware.is_tiprack(labware_id):
|
|
624
|
+
pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
|
|
625
|
+
engine_state=self._engine_client.state,
|
|
626
|
+
pipette_id=self._pipette_id,
|
|
627
|
+
labware_id=labware_id,
|
|
628
|
+
)
|
|
629
|
+
scrape_tips = self.get_channels() <= 8
|
|
630
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
631
|
+
engine_state=self._engine_client.state,
|
|
632
|
+
pipette_id=self._pipette_id,
|
|
633
|
+
labware_id=labware_id,
|
|
634
|
+
well_name=well_name,
|
|
635
|
+
well_location=well_location,
|
|
636
|
+
)
|
|
637
|
+
self._engine_client.execute_command(
|
|
638
|
+
cmd.DropTipParams(
|
|
639
|
+
pipetteId=self._pipette_id,
|
|
640
|
+
labwareId=labware_id,
|
|
641
|
+
wellName=well_name,
|
|
642
|
+
wellLocation=well_location,
|
|
643
|
+
homeAfter=home_after,
|
|
644
|
+
alternateDropLocation=alternate_drop_location,
|
|
645
|
+
scrape_tips=scrape_tips,
|
|
646
|
+
)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
650
|
+
|
|
651
|
+
def drop_tip_in_disposal_location(
|
|
652
|
+
self,
|
|
653
|
+
disposal_location: Union[TrashBin, WasteChute],
|
|
654
|
+
home_after: Optional[bool],
|
|
655
|
+
alternate_tip_drop: bool = False,
|
|
656
|
+
) -> None:
|
|
657
|
+
self._move_to_disposal_location(
|
|
658
|
+
disposal_location,
|
|
659
|
+
force_direct=False,
|
|
660
|
+
speed=None,
|
|
661
|
+
alternate_tip_drop=alternate_tip_drop,
|
|
662
|
+
)
|
|
663
|
+
self._drop_tip_in_place(home_after=home_after)
|
|
664
|
+
self._protocol_core.set_last_location(location=None, mount=self.get_mount())
|
|
665
|
+
|
|
666
|
+
def _move_to_disposal_location(
|
|
667
|
+
self,
|
|
668
|
+
disposal_location: Union[TrashBin, WasteChute],
|
|
669
|
+
force_direct: bool,
|
|
670
|
+
speed: Optional[float],
|
|
671
|
+
alternate_tip_drop: bool = False,
|
|
672
|
+
) -> None:
|
|
673
|
+
# TODO (nd, 2023-11-30): give appropriate offset when finalized
|
|
674
|
+
# https://opentrons.atlassian.net/browse/RSS-391
|
|
675
|
+
|
|
676
|
+
disposal_offset = disposal_location.offset
|
|
677
|
+
offset = AddressableOffsetVector(
|
|
678
|
+
x=disposal_offset.x, y=disposal_offset.y, z=disposal_offset.z
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
if isinstance(disposal_location, TrashBin):
|
|
682
|
+
addressable_area_name = disposal_location.area_name
|
|
683
|
+
self._engine_client.execute_command(
|
|
684
|
+
cmd.MoveToAddressableAreaForDropTipParams(
|
|
685
|
+
pipetteId=self._pipette_id,
|
|
686
|
+
addressableAreaName=addressable_area_name,
|
|
687
|
+
offset=offset,
|
|
688
|
+
forceDirect=force_direct,
|
|
689
|
+
speed=speed,
|
|
690
|
+
minimumZHeight=None,
|
|
691
|
+
alternateDropLocation=alternate_tip_drop,
|
|
692
|
+
ignoreTipConfiguration=True,
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
if isinstance(disposal_location, WasteChute):
|
|
697
|
+
num_channels = self.get_channels()
|
|
698
|
+
addressable_area_name = {
|
|
699
|
+
1: "1ChannelWasteChute",
|
|
700
|
+
8: "8ChannelWasteChute",
|
|
701
|
+
96: "96ChannelWasteChute",
|
|
702
|
+
}[num_channels]
|
|
703
|
+
|
|
704
|
+
self._engine_client.execute_command(
|
|
705
|
+
cmd.MoveToAddressableAreaParams(
|
|
706
|
+
pipetteId=self._pipette_id,
|
|
707
|
+
addressableAreaName=addressable_area_name,
|
|
708
|
+
offset=offset,
|
|
709
|
+
forceDirect=force_direct,
|
|
710
|
+
speed=speed,
|
|
711
|
+
minimumZHeight=None,
|
|
712
|
+
)
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
def _drop_tip_in_place(self, home_after: Optional[bool]) -> None:
|
|
716
|
+
self._engine_client.execute_command(
|
|
717
|
+
cmd.DropTipInPlaceParams(
|
|
718
|
+
pipetteId=self._pipette_id,
|
|
719
|
+
homeAfter=home_after,
|
|
720
|
+
)
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
def home(self) -> None:
|
|
724
|
+
z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id)
|
|
725
|
+
plunger_axis = self._engine_client.state.pipettes.get_plunger_axis(
|
|
726
|
+
self._pipette_id
|
|
727
|
+
)
|
|
728
|
+
self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis, plunger_axis]))
|
|
729
|
+
|
|
730
|
+
def home_plunger(self) -> None:
|
|
731
|
+
plunger_axis = self._engine_client.state.pipettes.get_plunger_axis(
|
|
732
|
+
self._pipette_id
|
|
733
|
+
)
|
|
734
|
+
self._engine_client.execute_command(cmd.HomeParams(axes=[plunger_axis]))
|
|
735
|
+
|
|
736
|
+
def move_to(
|
|
737
|
+
self,
|
|
738
|
+
location: Union[Location, TrashBin, WasteChute],
|
|
739
|
+
well_core: Optional[WellCore],
|
|
740
|
+
force_direct: bool,
|
|
741
|
+
minimum_z_height: Optional[float],
|
|
742
|
+
speed: Optional[float],
|
|
743
|
+
check_for_movement_conflicts: bool = True,
|
|
744
|
+
) -> None:
|
|
745
|
+
if well_core is not None:
|
|
746
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
747
|
+
raise ValueError("Trash Bin and Waste Chute have no Wells.")
|
|
748
|
+
labware_id = well_core.labware_id
|
|
749
|
+
well_name = well_core.get_name()
|
|
750
|
+
(
|
|
751
|
+
well_location,
|
|
752
|
+
_,
|
|
753
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
754
|
+
labware_id=labware_id,
|
|
755
|
+
well_name=well_name,
|
|
756
|
+
absolute_point=location.point,
|
|
757
|
+
location_type=WellLocationFunction.LIQUID_HANDLING,
|
|
758
|
+
meniscus_tracking=location._meniscus_tracking,
|
|
759
|
+
)
|
|
760
|
+
assert isinstance(well_location, LiquidHandlingWellLocation)
|
|
761
|
+
# specifying a static volume offset isn't implemented yet
|
|
762
|
+
# well locations at this point will be default have been assigned a
|
|
763
|
+
# volume offset of operationVolume
|
|
764
|
+
if well_location.volumeOffset:
|
|
765
|
+
if (
|
|
766
|
+
well_location.volumeOffset != 0
|
|
767
|
+
and well_location.volumeOffset != "operationVolume"
|
|
768
|
+
):
|
|
769
|
+
raise ValueError(
|
|
770
|
+
f"volume offset {well_location.volumeOffset} not supported with move_to"
|
|
771
|
+
)
|
|
772
|
+
if check_for_movement_conflicts:
|
|
773
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
774
|
+
engine_state=self._engine_client.state,
|
|
775
|
+
pipette_id=self._pipette_id,
|
|
776
|
+
labware_id=labware_id,
|
|
777
|
+
well_name=well_name,
|
|
778
|
+
well_location=well_location,
|
|
779
|
+
)
|
|
780
|
+
self._engine_client.execute_command(
|
|
781
|
+
cmd.MoveToWellParams(
|
|
782
|
+
pipetteId=self._pipette_id,
|
|
783
|
+
labwareId=labware_id,
|
|
784
|
+
wellName=well_name,
|
|
785
|
+
wellLocation=well_location,
|
|
786
|
+
minimumZHeight=minimum_z_height,
|
|
787
|
+
forceDirect=force_direct,
|
|
788
|
+
speed=speed,
|
|
789
|
+
)
|
|
790
|
+
)
|
|
791
|
+
else:
|
|
792
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
793
|
+
self._move_to_disposal_location(
|
|
794
|
+
disposal_location=location, force_direct=force_direct, speed=speed
|
|
795
|
+
)
|
|
796
|
+
else:
|
|
797
|
+
self._engine_client.execute_command(
|
|
798
|
+
cmd.MoveToCoordinatesParams(
|
|
799
|
+
pipetteId=self._pipette_id,
|
|
800
|
+
coordinates=DeckPoint(
|
|
801
|
+
x=location.point.x, y=location.point.y, z=location.point.z
|
|
802
|
+
),
|
|
803
|
+
minimumZHeight=minimum_z_height,
|
|
804
|
+
forceDirect=force_direct,
|
|
805
|
+
speed=speed,
|
|
806
|
+
)
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
810
|
+
|
|
811
|
+
def resin_tip_seal(
|
|
812
|
+
self, location: Location, well_core: WellCore, in_place: Optional[bool] = False
|
|
813
|
+
) -> None:
|
|
814
|
+
labware_id = well_core.labware_id
|
|
815
|
+
well_name = well_core.get_name()
|
|
816
|
+
(
|
|
817
|
+
well_location,
|
|
818
|
+
_,
|
|
819
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
820
|
+
labware_id=labware_id,
|
|
821
|
+
well_name=well_name,
|
|
822
|
+
absolute_point=location.point,
|
|
823
|
+
location_type=WellLocationFunction.PICK_UP_TIP,
|
|
824
|
+
)
|
|
825
|
+
assert isinstance(well_location, PickUpTipWellLocation)
|
|
826
|
+
self._engine_client.execute_command(
|
|
827
|
+
cmd.SealPipetteToTipParams(
|
|
828
|
+
pipetteId=self._pipette_id,
|
|
829
|
+
labwareId=labware_id,
|
|
830
|
+
wellName=well_name,
|
|
831
|
+
wellLocation=well_location,
|
|
832
|
+
)
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
def resin_tip_unseal(self, location: Location | None, well_core: WellCore) -> None:
|
|
836
|
+
well_name = well_core.get_name()
|
|
837
|
+
labware_id = well_core.labware_id
|
|
838
|
+
|
|
839
|
+
if location is not None:
|
|
840
|
+
(
|
|
841
|
+
relative_well_location,
|
|
842
|
+
_,
|
|
843
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
844
|
+
labware_id=labware_id,
|
|
845
|
+
well_name=well_name,
|
|
846
|
+
absolute_point=location.point,
|
|
847
|
+
location_type=WellLocationFunction.BASE,
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
well_location = DropTipWellLocation(
|
|
851
|
+
origin=DropTipWellOrigin(relative_well_location.origin.value),
|
|
852
|
+
offset=relative_well_location.offset,
|
|
853
|
+
)
|
|
854
|
+
else:
|
|
855
|
+
well_location = DropTipWellLocation()
|
|
856
|
+
|
|
857
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
858
|
+
engine_state=self._engine_client.state,
|
|
859
|
+
pipette_id=self._pipette_id,
|
|
860
|
+
labware_id=labware_id,
|
|
861
|
+
well_name=well_name,
|
|
862
|
+
well_location=well_location,
|
|
863
|
+
)
|
|
864
|
+
self._engine_client.execute_command(
|
|
865
|
+
cmd.UnsealPipetteFromTipParams(
|
|
866
|
+
pipetteId=self._pipette_id,
|
|
867
|
+
labwareId=labware_id,
|
|
868
|
+
wellName=well_name,
|
|
869
|
+
wellLocation=well_location,
|
|
870
|
+
)
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
874
|
+
|
|
875
|
+
def resin_tip_dispense(
|
|
876
|
+
self,
|
|
877
|
+
location: Location,
|
|
878
|
+
well_core: WellCore,
|
|
879
|
+
volume: Optional[float] = None,
|
|
880
|
+
flow_rate: Optional[float] = None,
|
|
881
|
+
) -> None:
|
|
882
|
+
"""
|
|
883
|
+
Args:
|
|
884
|
+
volume: The volume of liquid to dispense, in microliters. Defaults to 400uL.
|
|
885
|
+
location: The exact location to dispense to.
|
|
886
|
+
well_core: The well to dispense to, if applicable.
|
|
887
|
+
flow_rate: The flow rate in µL/s to dispense at. Defaults to 10.0uL/S.
|
|
888
|
+
"""
|
|
889
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
890
|
+
raise ValueError("Trash Bin and Waste Chute have no Wells.")
|
|
891
|
+
well_name = well_core.get_name()
|
|
892
|
+
labware_id = well_core.labware_id
|
|
893
|
+
if volume is None:
|
|
894
|
+
volume = _RESIN_TIP_DEFAULT_VOLUME
|
|
895
|
+
if flow_rate is None:
|
|
896
|
+
flow_rate = _RESIN_TIP_DEFAULT_FLOW_RATE
|
|
897
|
+
|
|
898
|
+
(
|
|
899
|
+
well_location,
|
|
900
|
+
dynamic_tracking,
|
|
901
|
+
) = self._engine_client.state.geometry.get_relative_well_location(
|
|
902
|
+
labware_id=labware_id,
|
|
903
|
+
well_name=well_name,
|
|
904
|
+
absolute_point=location.point,
|
|
905
|
+
location_type=WellLocationFunction.LIQUID_HANDLING,
|
|
906
|
+
)
|
|
907
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
908
|
+
engine_state=self._engine_client.state,
|
|
909
|
+
pipette_id=self._pipette_id,
|
|
910
|
+
labware_id=labware_id,
|
|
911
|
+
well_name=well_name,
|
|
912
|
+
well_location=well_location,
|
|
913
|
+
)
|
|
914
|
+
assert isinstance(well_location, LiquidHandlingWellLocation)
|
|
915
|
+
self._engine_client.execute_command(
|
|
916
|
+
cmd.PressureDispenseParams(
|
|
917
|
+
pipetteId=self._pipette_id,
|
|
918
|
+
labwareId=labware_id,
|
|
919
|
+
wellName=well_name,
|
|
920
|
+
wellLocation=well_location,
|
|
921
|
+
volume=volume,
|
|
922
|
+
flowRate=flow_rate,
|
|
923
|
+
)
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
def get_mount(self) -> Mount:
|
|
927
|
+
"""Get the mount the pipette is attached to."""
|
|
928
|
+
return self._engine_client.state.pipettes.get(
|
|
929
|
+
self._pipette_id
|
|
930
|
+
).mount.to_hw_mount()
|
|
931
|
+
|
|
932
|
+
def get_pipette_name(self) -> str:
|
|
933
|
+
"""Get the pipette's name as a string.
|
|
934
|
+
|
|
935
|
+
Will match the load name of the actually loaded pipette,
|
|
936
|
+
which may differ from the requested load name.
|
|
937
|
+
|
|
938
|
+
From API v2.15 to v2.22, this property returned an internal, engine-specific,
|
|
939
|
+
name for Flex pipettes (eg, "p50_multi_flex" instead of "flex_8channel_50").
|
|
940
|
+
|
|
941
|
+
From API v2.23 onwards, this behavior is fixed so that this property returns
|
|
942
|
+
the API-specific names of Flex pipettes.
|
|
943
|
+
"""
|
|
944
|
+
# TODO (tz, 11-23-22): revert this change when merging
|
|
945
|
+
# https://opentrons.atlassian.net/browse/RLIQ-251
|
|
946
|
+
pipette = self._engine_client.state.pipettes.get(self._pipette_id)
|
|
947
|
+
if self._protocol_core.api_version < _FLEX_PIPETTE_NAMES_FIXED_IN:
|
|
948
|
+
return pipette.pipetteName.value
|
|
949
|
+
else:
|
|
950
|
+
name = next(
|
|
951
|
+
(
|
|
952
|
+
pip_api_name
|
|
953
|
+
for pip_api_name, pip_name in PIPETTE_API_NAMES_MAP.items()
|
|
954
|
+
if pip_name == pipette.pipetteName
|
|
955
|
+
),
|
|
956
|
+
None,
|
|
957
|
+
)
|
|
958
|
+
assert name, "Pipette name not found."
|
|
959
|
+
return name
|
|
960
|
+
|
|
961
|
+
def get_model(self) -> str:
|
|
962
|
+
return self._engine_client.state.pipettes.get_model_name(self._pipette_id)
|
|
963
|
+
|
|
964
|
+
def get_display_name(self) -> str:
|
|
965
|
+
return self._engine_client.state.pipettes.get_display_name(self._pipette_id)
|
|
966
|
+
|
|
967
|
+
def get_min_volume(self) -> float:
|
|
968
|
+
return self._engine_client.state.pipettes.get_minimum_volume(self._pipette_id)
|
|
969
|
+
|
|
970
|
+
def get_max_volume(self) -> float:
|
|
971
|
+
return self._engine_client.state.pipettes.get_maximum_volume(self._pipette_id)
|
|
972
|
+
|
|
973
|
+
def get_working_volume(self) -> float:
|
|
974
|
+
return self._engine_client.state.pipettes.get_working_volume(self._pipette_id)
|
|
975
|
+
|
|
976
|
+
def get_current_volume(self) -> float:
|
|
977
|
+
try:
|
|
978
|
+
current_volume = self._engine_client.state.pipettes.get_aspirated_volume(
|
|
979
|
+
self._pipette_id
|
|
980
|
+
)
|
|
981
|
+
except TipNotAttachedError:
|
|
982
|
+
current_volume = None
|
|
983
|
+
|
|
984
|
+
return current_volume or 0
|
|
985
|
+
|
|
986
|
+
def get_has_clean_tip(self) -> bool:
|
|
987
|
+
try:
|
|
988
|
+
clean_tip = self._engine_client.state.pipettes.get_has_clean_tip(
|
|
989
|
+
self._pipette_id
|
|
990
|
+
)
|
|
991
|
+
except TipNotAttachedError:
|
|
992
|
+
clean_tip = False
|
|
993
|
+
|
|
994
|
+
return clean_tip
|
|
995
|
+
|
|
996
|
+
def get_available_volume(self) -> float:
|
|
997
|
+
try:
|
|
998
|
+
available_volume = self._engine_client.state.pipettes.get_available_volume(
|
|
999
|
+
self._pipette_id
|
|
1000
|
+
)
|
|
1001
|
+
except TipNotAttachedError:
|
|
1002
|
+
available_volume = None
|
|
1003
|
+
|
|
1004
|
+
return available_volume or 0
|
|
1005
|
+
|
|
1006
|
+
def get_hardware_state(self) -> PipetteDict:
|
|
1007
|
+
"""Get the current state of the pipette hardware as a dictionary."""
|
|
1008
|
+
return self._sync_hardware_api.get_attached_instrument(self.get_mount()) # type: ignore[no-any-return]
|
|
1009
|
+
|
|
1010
|
+
def get_channels(self) -> int:
|
|
1011
|
+
return self._engine_client.state.pipettes.get_channels(self._pipette_id)
|
|
1012
|
+
|
|
1013
|
+
def get_active_channels(self) -> int:
|
|
1014
|
+
return self._engine_client.state.pipettes.get_active_channels(self._pipette_id)
|
|
1015
|
+
|
|
1016
|
+
def get_nozzle_map(self) -> NozzleMapInterface:
|
|
1017
|
+
return self._engine_client.state.pipettes.get_nozzle_configuration(
|
|
1018
|
+
self._pipette_id
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
def has_tip(self) -> bool:
|
|
1022
|
+
return (
|
|
1023
|
+
self._engine_client.state.pipettes.get_attached_tip(self._pipette_id)
|
|
1024
|
+
is not None
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
def get_return_height(self) -> float:
|
|
1028
|
+
return self._engine_client.state.pipettes.get_return_tip_scale(self._pipette_id)
|
|
1029
|
+
|
|
1030
|
+
def get_flow_rate(self) -> FlowRates:
|
|
1031
|
+
return self._flow_rates
|
|
1032
|
+
|
|
1033
|
+
def get_aspirate_flow_rate(self, rate: float = 1.0) -> float:
|
|
1034
|
+
return self._aspirate_flow_rate * rate
|
|
1035
|
+
|
|
1036
|
+
def get_dispense_flow_rate(self, rate: float = 1.0) -> float:
|
|
1037
|
+
return self._dispense_flow_rate * rate
|
|
1038
|
+
|
|
1039
|
+
def get_blow_out_flow_rate(self, rate: float = 1.0) -> float:
|
|
1040
|
+
return self._blow_out_flow_rate * rate
|
|
1041
|
+
|
|
1042
|
+
def get_nozzle_configuration(self) -> NozzleConfigurationType:
|
|
1043
|
+
return self._engine_client.state.pipettes.get_nozzle_layout_type(
|
|
1044
|
+
self._pipette_id
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
def get_liquid_presence_detection(self) -> bool:
|
|
1048
|
+
return self._liquid_presence_detection
|
|
1049
|
+
|
|
1050
|
+
def get_tip_origin(
|
|
1051
|
+
self,
|
|
1052
|
+
) -> Optional[Tuple[LabwareCore, WellCore]]:
|
|
1053
|
+
last_tip_pickup_info = (
|
|
1054
|
+
self._engine_client.state.pipettes.get_tip_rack_well_picked_up_from(
|
|
1055
|
+
self._pipette_id
|
|
1056
|
+
)
|
|
1057
|
+
)
|
|
1058
|
+
if last_tip_pickup_info is None:
|
|
1059
|
+
return None
|
|
1060
|
+
else:
|
|
1061
|
+
tip_rack_labware_core = self._protocol_core._labware_cores_by_id[
|
|
1062
|
+
last_tip_pickup_info.labware_id
|
|
1063
|
+
]
|
|
1064
|
+
tip_well_core = tip_rack_labware_core.get_well_core(
|
|
1065
|
+
last_tip_pickup_info.well_name
|
|
1066
|
+
)
|
|
1067
|
+
return tip_rack_labware_core, tip_well_core
|
|
1068
|
+
|
|
1069
|
+
def is_tip_tracking_available(self) -> bool:
|
|
1070
|
+
if self.get_nozzle_configuration() == NozzleConfigurationType.FULL:
|
|
1071
|
+
return True
|
|
1072
|
+
else:
|
|
1073
|
+
if self.get_channels() == 96:
|
|
1074
|
+
return True
|
|
1075
|
+
if self.get_channels() == 8:
|
|
1076
|
+
return True
|
|
1077
|
+
return False
|
|
1078
|
+
|
|
1079
|
+
def set_flow_rate(
|
|
1080
|
+
self,
|
|
1081
|
+
aspirate: Optional[float] = None,
|
|
1082
|
+
dispense: Optional[float] = None,
|
|
1083
|
+
blow_out: Optional[float] = None,
|
|
1084
|
+
) -> None:
|
|
1085
|
+
if aspirate is not None:
|
|
1086
|
+
assert aspirate > 0
|
|
1087
|
+
self._aspirate_flow_rate = aspirate
|
|
1088
|
+
if dispense is not None:
|
|
1089
|
+
assert dispense > 0
|
|
1090
|
+
self._dispense_flow_rate = dispense
|
|
1091
|
+
if blow_out is not None:
|
|
1092
|
+
assert blow_out > 0
|
|
1093
|
+
self._blow_out_flow_rate = blow_out
|
|
1094
|
+
|
|
1095
|
+
def set_liquid_presence_detection(self, enable: bool) -> None:
|
|
1096
|
+
self._liquid_presence_detection = enable
|
|
1097
|
+
|
|
1098
|
+
def configure_for_volume(self, volume: float) -> None:
|
|
1099
|
+
self._engine_client.execute_command(
|
|
1100
|
+
cmd.ConfigureForVolumeParams(
|
|
1101
|
+
pipetteId=self._pipette_id,
|
|
1102
|
+
volume=volume,
|
|
1103
|
+
tipOverlapNotAfterVersion=overlap_versions.overlap_for_api_version(
|
|
1104
|
+
self._protocol_core.api_version
|
|
1105
|
+
),
|
|
1106
|
+
)
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
def prepare_to_aspirate(self) -> None:
|
|
1110
|
+
self._engine_client.execute_command(
|
|
1111
|
+
cmd.PrepareToAspirateParams(pipetteId=self._pipette_id)
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
def configure_nozzle_layout(
|
|
1115
|
+
self,
|
|
1116
|
+
style: NozzleLayout,
|
|
1117
|
+
primary_nozzle: Optional[str],
|
|
1118
|
+
front_right_nozzle: Optional[str],
|
|
1119
|
+
back_left_nozzle: Optional[str],
|
|
1120
|
+
) -> None:
|
|
1121
|
+
if style == NozzleLayout.COLUMN:
|
|
1122
|
+
configuration_model: NozzleLayoutConfigurationType = (
|
|
1123
|
+
ColumnNozzleLayoutConfiguration(
|
|
1124
|
+
primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
|
|
1125
|
+
)
|
|
1126
|
+
)
|
|
1127
|
+
elif style == NozzleLayout.ROW:
|
|
1128
|
+
configuration_model = RowNozzleLayoutConfiguration(
|
|
1129
|
+
primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
|
|
1130
|
+
)
|
|
1131
|
+
elif style == NozzleLayout.QUADRANT or style == NozzleLayout.PARTIAL_COLUMN:
|
|
1132
|
+
assert (
|
|
1133
|
+
# We make sure to set these nozzles in the calling function
|
|
1134
|
+
# if using QUADRANT or PARTIAL_COLUMN. Asserting only for type verification here.
|
|
1135
|
+
front_right_nozzle is not None
|
|
1136
|
+
and back_left_nozzle is not None
|
|
1137
|
+
), f"Both front right and back left nozzles are required for {style} configuration."
|
|
1138
|
+
configuration_model = QuadrantNozzleLayoutConfiguration(
|
|
1139
|
+
primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle),
|
|
1140
|
+
frontRightNozzle=front_right_nozzle,
|
|
1141
|
+
backLeftNozzle=back_left_nozzle,
|
|
1142
|
+
)
|
|
1143
|
+
elif style == NozzleLayout.SINGLE:
|
|
1144
|
+
configuration_model = SingleNozzleLayoutConfiguration(
|
|
1145
|
+
primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
|
|
1146
|
+
)
|
|
1147
|
+
else:
|
|
1148
|
+
configuration_model = AllNozzleLayoutConfiguration()
|
|
1149
|
+
self._engine_client.execute_command(
|
|
1150
|
+
cmd.ConfigureNozzleLayoutParams(
|
|
1151
|
+
pipetteId=self._pipette_id, configurationParams=configuration_model
|
|
1152
|
+
)
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
def load_liquid_class(
|
|
1156
|
+
self,
|
|
1157
|
+
name: str,
|
|
1158
|
+
transfer_properties: TransferProperties,
|
|
1159
|
+
tiprack_uri: str,
|
|
1160
|
+
) -> str:
|
|
1161
|
+
"""Load a liquid class into the engine and return its ID.
|
|
1162
|
+
|
|
1163
|
+
Args:
|
|
1164
|
+
name: Name of the liquid class
|
|
1165
|
+
transfer_properties: Liquid class properties for a specific pipette & tiprack combination
|
|
1166
|
+
tiprack_uri: URI of the tiprack whose transfer properties we will be using.
|
|
1167
|
+
|
|
1168
|
+
Returns:
|
|
1169
|
+
Liquid class record's ID, as generated by the protocol engine.
|
|
1170
|
+
"""
|
|
1171
|
+
liquid_class_record = LiquidClassRecord(
|
|
1172
|
+
liquidClassName=name,
|
|
1173
|
+
pipetteModel=self.get_pipette_name(),
|
|
1174
|
+
tiprack=tiprack_uri,
|
|
1175
|
+
aspirate=transfer_properties.aspirate.as_shared_data_model(),
|
|
1176
|
+
singleDispense=transfer_properties.dispense.as_shared_data_model(),
|
|
1177
|
+
multiDispense=(
|
|
1178
|
+
transfer_properties.multi_dispense.as_shared_data_model()
|
|
1179
|
+
if transfer_properties.multi_dispense
|
|
1180
|
+
else None
|
|
1181
|
+
),
|
|
1182
|
+
)
|
|
1183
|
+
result = self._engine_client.execute_command_without_recovery(
|
|
1184
|
+
cmd.LoadLiquidClassParams(
|
|
1185
|
+
liquidClassRecord=liquid_class_record,
|
|
1186
|
+
)
|
|
1187
|
+
)
|
|
1188
|
+
return result.liquidClassId
|
|
1189
|
+
|
|
1190
|
+
def get_next_tip(
|
|
1191
|
+
self, tip_racks: List[LabwareCore], starting_well: Optional[WellCore]
|
|
1192
|
+
) -> Optional[NextTipInfo]:
|
|
1193
|
+
"""Get the next tip to pick up."""
|
|
1194
|
+
if starting_well is not None:
|
|
1195
|
+
# Drop tip racks until the one with the starting tip is reached (if any)
|
|
1196
|
+
valid_tip_racks = list(
|
|
1197
|
+
dropwhile(
|
|
1198
|
+
lambda tr: starting_well.labware_id != tr.labware_id, tip_racks
|
|
1199
|
+
)
|
|
1200
|
+
)
|
|
1201
|
+
else:
|
|
1202
|
+
valid_tip_racks = tip_racks
|
|
1203
|
+
|
|
1204
|
+
result = self._engine_client.execute_command_without_recovery(
|
|
1205
|
+
cmd.GetNextTipParams(
|
|
1206
|
+
pipetteId=self._pipette_id,
|
|
1207
|
+
labwareIds=[tip_rack.labware_id for tip_rack in valid_tip_racks],
|
|
1208
|
+
startingTipWell=(
|
|
1209
|
+
starting_well.get_name() if starting_well is not None else None
|
|
1210
|
+
),
|
|
1211
|
+
)
|
|
1212
|
+
)
|
|
1213
|
+
next_tip_info = result.nextTipInfo
|
|
1214
|
+
if isinstance(next_tip_info, NoTipAvailable):
|
|
1215
|
+
if next_tip_info.noTipReason == NoTipReason.STARTING_TIP_WITH_PARTIAL:
|
|
1216
|
+
raise CommandPreconditionViolated(
|
|
1217
|
+
"Automatic tip tracking is not available when using a partial pipette"
|
|
1218
|
+
" nozzle configuration and InstrumentContext.starting_tip."
|
|
1219
|
+
" Switch to a full configuration or set starting_tip to None."
|
|
1220
|
+
)
|
|
1221
|
+
return None
|
|
1222
|
+
else:
|
|
1223
|
+
return next_tip_info
|
|
1224
|
+
|
|
1225
|
+
def transfer_with_liquid_class( # noqa: C901
|
|
1226
|
+
self,
|
|
1227
|
+
liquid_class: LiquidClass,
|
|
1228
|
+
volume: float,
|
|
1229
|
+
source: List[Tuple[Location, WellCore]],
|
|
1230
|
+
dest: Union[List[Tuple[Location, WellCore]], TrashBin, WasteChute],
|
|
1231
|
+
new_tip: TransferTipPolicyV2,
|
|
1232
|
+
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1233
|
+
starting_tip: Optional[WellCore],
|
|
1234
|
+
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1235
|
+
return_tip: bool,
|
|
1236
|
+
keep_last_tip: bool,
|
|
1237
|
+
) -> None:
|
|
1238
|
+
"""Execute transfer using liquid class properties.
|
|
1239
|
+
|
|
1240
|
+
Args:
|
|
1241
|
+
liquid_class: The liquid class to use for transfer properties.
|
|
1242
|
+
volume: Volume to transfer per well.
|
|
1243
|
+
source: List of source wells, with each well represented as a tuple of
|
|
1244
|
+
types.Location and WellCore.
|
|
1245
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1246
|
+
dest: List of destination wells, with each well represented as a tuple of
|
|
1247
|
+
types.Location and WellCore.
|
|
1248
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1249
|
+
new_tip: Whether the transfer should use a new tip 'once', 'never', 'always',
|
|
1250
|
+
or 'per source'.
|
|
1251
|
+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
|
|
1252
|
+
as tuples of types.Location and WellCore.
|
|
1253
|
+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
|
|
1254
|
+
up, if the user has set it.
|
|
1255
|
+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
|
|
1256
|
+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
|
|
1257
|
+
otherwise drop in `trash_location`
|
|
1258
|
+
keep_last_tip: When set to `True`, do not drop the final tip used in the transfer.
|
|
1259
|
+
"""
|
|
1260
|
+
if not tip_racks:
|
|
1261
|
+
raise RuntimeError(
|
|
1262
|
+
"No tipracks found for pipette in order to perform transfer"
|
|
1263
|
+
)
|
|
1264
|
+
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1265
|
+
transfer_props = self._get_transfer_properties_for_tip_rack(
|
|
1266
|
+
liquid_class, tiprack_uri_for_transfer_props
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
# TODO: use the ID returned by load_liquid_class in command annotations
|
|
1270
|
+
self.load_liquid_class(
|
|
1271
|
+
name=liquid_class.name,
|
|
1272
|
+
transfer_properties=transfer_props,
|
|
1273
|
+
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1274
|
+
)
|
|
1275
|
+
|
|
1276
|
+
target_destinations: Sequence[
|
|
1277
|
+
Union[Tuple[Location, WellCore], TrashBin, WasteChute]
|
|
1278
|
+
]
|
|
1279
|
+
if isinstance(dest, (TrashBin, WasteChute)):
|
|
1280
|
+
target_destinations = [dest] * len(source)
|
|
1281
|
+
else:
|
|
1282
|
+
target_destinations = dest
|
|
1283
|
+
|
|
1284
|
+
working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
|
|
1285
|
+
|
|
1286
|
+
source_dest_per_volume_step = (
|
|
1287
|
+
tx_commons.get_sources_and_destinations_for_liquid_classes(
|
|
1288
|
+
volumes=[volume for _ in range(len(source))],
|
|
1289
|
+
max_volume=working_volume,
|
|
1290
|
+
targets=zip(source, target_destinations),
|
|
1291
|
+
transfer_properties=transfer_props,
|
|
1292
|
+
)
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
if new_tip == TransferTipPolicyV2.ONCE:
|
|
1296
|
+
self._pick_up_tip_for_liquid_class(
|
|
1297
|
+
tip_racks, starting_tip, tiprack_uri_for_transfer_props
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
prev_src: Optional[Tuple[Location, WellCore]] = None
|
|
1301
|
+
prev_dest: Optional[
|
|
1302
|
+
Union[Tuple[Location, WellCore], TrashBin, WasteChute]
|
|
1303
|
+
] = None
|
|
1304
|
+
post_disp_tip_contents = [
|
|
1305
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1306
|
+
liquid=0,
|
|
1307
|
+
air_gap=0,
|
|
1308
|
+
)
|
|
1309
|
+
]
|
|
1310
|
+
next_step_volume, next_src_dest_combo = next(source_dest_per_volume_step)
|
|
1311
|
+
is_last_step = False
|
|
1312
|
+
while not is_last_step:
|
|
1313
|
+
step_volume = next_step_volume
|
|
1314
|
+
src_dest_combo = next_src_dest_combo
|
|
1315
|
+
step_source, step_destination = src_dest_combo
|
|
1316
|
+
try:
|
|
1317
|
+
next_step_volume, next_src_dest_combo = next(
|
|
1318
|
+
source_dest_per_volume_step
|
|
1319
|
+
)
|
|
1320
|
+
except StopIteration:
|
|
1321
|
+
is_last_step = True
|
|
1322
|
+
|
|
1323
|
+
if (
|
|
1324
|
+
new_tip == TransferTipPolicyV2.ALWAYS
|
|
1325
|
+
or (
|
|
1326
|
+
new_tip == TransferTipPolicyV2.PER_SOURCE
|
|
1327
|
+
and step_source != prev_src
|
|
1328
|
+
)
|
|
1329
|
+
or (
|
|
1330
|
+
new_tip == TransferTipPolicyV2.PER_DESTINATION
|
|
1331
|
+
and step_destination != prev_dest
|
|
1332
|
+
)
|
|
1333
|
+
):
|
|
1334
|
+
if prev_src is not None and prev_dest is not None:
|
|
1335
|
+
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1336
|
+
self._pick_up_tip_for_liquid_class(
|
|
1337
|
+
tip_racks, starting_tip, tiprack_uri_for_transfer_props
|
|
1338
|
+
)
|
|
1339
|
+
post_disp_tip_contents = [
|
|
1340
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1341
|
+
liquid=0,
|
|
1342
|
+
air_gap=0,
|
|
1343
|
+
)
|
|
1344
|
+
]
|
|
1345
|
+
|
|
1346
|
+
post_asp_tip_contents = self.aspirate_liquid_class(
|
|
1347
|
+
volume=step_volume,
|
|
1348
|
+
source=step_source,
|
|
1349
|
+
transfer_properties=transfer_props,
|
|
1350
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
|
|
1351
|
+
tip_contents=post_disp_tip_contents,
|
|
1352
|
+
volume_for_pipette_mode_configuration=step_volume,
|
|
1353
|
+
)
|
|
1354
|
+
post_disp_tip_contents = self.dispense_liquid_class(
|
|
1355
|
+
volume=step_volume,
|
|
1356
|
+
dest=step_destination,
|
|
1357
|
+
source=step_source,
|
|
1358
|
+
transfer_properties=transfer_props,
|
|
1359
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
|
|
1360
|
+
tip_contents=post_asp_tip_contents,
|
|
1361
|
+
add_final_air_gap=(False if is_last_step and keep_last_tip else True),
|
|
1362
|
+
trash_location=trash_location,
|
|
1363
|
+
)
|
|
1364
|
+
prev_src = step_source
|
|
1365
|
+
prev_dest = step_destination
|
|
1366
|
+
|
|
1367
|
+
if not keep_last_tip:
|
|
1368
|
+
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1369
|
+
|
|
1370
|
+
def distribute_with_liquid_class( # noqa: C901
|
|
1371
|
+
self,
|
|
1372
|
+
liquid_class: LiquidClass,
|
|
1373
|
+
volume: float,
|
|
1374
|
+
source: Tuple[Location, WellCore],
|
|
1375
|
+
dest: List[Tuple[Location, WellCore]],
|
|
1376
|
+
new_tip: Literal[
|
|
1377
|
+
TransferTipPolicyV2.NEVER,
|
|
1378
|
+
TransferTipPolicyV2.ONCE,
|
|
1379
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1380
|
+
],
|
|
1381
|
+
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1382
|
+
starting_tip: Optional[WellCore],
|
|
1383
|
+
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1384
|
+
return_tip: bool,
|
|
1385
|
+
keep_last_tip: bool,
|
|
1386
|
+
) -> None:
|
|
1387
|
+
"""Execute a distribution using liquid class properties.
|
|
1388
|
+
|
|
1389
|
+
Args:
|
|
1390
|
+
liquid_class: The liquid class to use for transfer properties.
|
|
1391
|
+
volume: The amount of liquid in uL, to dispense into each destination well.
|
|
1392
|
+
source: Source well represented as a tuple of types.Location and WellCore.
|
|
1393
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1394
|
+
dest: List of destination wells, with each well represented as a tuple of
|
|
1395
|
+
types.Location and WellCore.
|
|
1396
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1397
|
+
new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
|
|
1398
|
+
'never': the transfer will never pick up a new tip
|
|
1399
|
+
'once': the transfer will pick up a new tip once at the start of transfer
|
|
1400
|
+
'always': the transfer will pick up a new tip before every aspirate
|
|
1401
|
+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
|
|
1402
|
+
as tuples of types.Location and WellCore.
|
|
1403
|
+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
|
|
1404
|
+
up, if the user has set it.
|
|
1405
|
+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
|
|
1406
|
+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
|
|
1407
|
+
otherwise drop in `trash_location`
|
|
1408
|
+
keep_last_tip: When set to `True`, do not drop the final tip used in the distribute.
|
|
1409
|
+
|
|
1410
|
+
This method distributes the liquid in the source well into multiple destinations.
|
|
1411
|
+
It can accomplish this by either doing a multi-dispense (aspirate once and then
|
|
1412
|
+
dispense multiple times consecutively) or by doing multiple single-dispenses
|
|
1413
|
+
(going back to aspirate after each dispense). Whether it does a multi-dispense or
|
|
1414
|
+
multiple single dispenses is determined by whether multi-dispense properties
|
|
1415
|
+
are available in the liquid class and whether the tip in use can hold multiple
|
|
1416
|
+
volumes to be dispensed without having to refill.
|
|
1417
|
+
"""
|
|
1418
|
+
if not tip_racks:
|
|
1419
|
+
raise RuntimeError(
|
|
1420
|
+
"No tipracks found for pipette in order to perform transfer"
|
|
1421
|
+
)
|
|
1422
|
+
assert new_tip in [
|
|
1423
|
+
TransferTipPolicyV2.NEVER,
|
|
1424
|
+
TransferTipPolicyV2.ONCE,
|
|
1425
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1426
|
+
]
|
|
1427
|
+
|
|
1428
|
+
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1429
|
+
transfer_props = self._get_transfer_properties_for_tip_rack(
|
|
1430
|
+
liquid_class, tiprack_uri_for_transfer_props
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
# If the volume to dispense into a well is less than threshold for low volume mode,
|
|
1434
|
+
# then set the max working volume to the max volume of low volume mode.
|
|
1435
|
+
# NOTE: this logic will need to be updated once we support list of volumes
|
|
1436
|
+
# TODO (spp): refactor this to use the volume thresholds from shared data
|
|
1437
|
+
has_low_volume_mode = self.get_pipette_name() in [
|
|
1438
|
+
"flex_1channel_50",
|
|
1439
|
+
"flex_8channel_50",
|
|
1440
|
+
]
|
|
1441
|
+
working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
|
|
1442
|
+
if has_low_volume_mode and volume < 5:
|
|
1443
|
+
working_volume = 30
|
|
1444
|
+
# If there are no multi-dispense properties or if the volume to distribute
|
|
1445
|
+
# per destination well is so large that the tip cannot hold enough liquid
|
|
1446
|
+
# to consecutively distribute to at least two wells, then we resort to using
|
|
1447
|
+
# a regular, one-to-one transfer to carry out the distribution.
|
|
1448
|
+
min_asp_vol_for_multi_dispense = 2 * volume
|
|
1449
|
+
if (
|
|
1450
|
+
transfer_props.multi_dispense is None
|
|
1451
|
+
or not self._tip_can_hold_volume_for_multi_dispensing(
|
|
1452
|
+
transfer_volume=min_asp_vol_for_multi_dispense,
|
|
1453
|
+
multi_dispense_properties=transfer_props.multi_dispense,
|
|
1454
|
+
tip_working_volume=working_volume,
|
|
1455
|
+
)
|
|
1456
|
+
):
|
|
1457
|
+
return self.transfer_with_liquid_class(
|
|
1458
|
+
liquid_class=liquid_class,
|
|
1459
|
+
volume=volume,
|
|
1460
|
+
source=[source for _ in range(len(dest))],
|
|
1461
|
+
dest=dest,
|
|
1462
|
+
new_tip=new_tip,
|
|
1463
|
+
tip_racks=tip_racks,
|
|
1464
|
+
starting_tip=starting_tip,
|
|
1465
|
+
trash_location=trash_location,
|
|
1466
|
+
return_tip=return_tip,
|
|
1467
|
+
keep_last_tip=keep_last_tip,
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
# TODO: use the ID returned by load_liquid_class in command annotations
|
|
1471
|
+
self.load_liquid_class(
|
|
1472
|
+
name=liquid_class.name,
|
|
1473
|
+
transfer_properties=transfer_props,
|
|
1474
|
+
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1475
|
+
)
|
|
1476
|
+
|
|
1477
|
+
# This will return a generator that provides pairs of destination well and
|
|
1478
|
+
# the volume to dispense into it
|
|
1479
|
+
dest_per_volume_step = (
|
|
1480
|
+
tx_commons.get_sources_and_destinations_for_liquid_classes(
|
|
1481
|
+
volumes=[volume for _ in range(len(dest))],
|
|
1482
|
+
max_volume=working_volume,
|
|
1483
|
+
targets=dest,
|
|
1484
|
+
transfer_properties=transfer_props,
|
|
1485
|
+
is_multi_dispense=True,
|
|
1486
|
+
)
|
|
1487
|
+
)
|
|
1488
|
+
|
|
1489
|
+
if new_tip != TransferTipPolicyV2.NEVER:
|
|
1490
|
+
self._pick_up_tip_for_liquid_class(
|
|
1491
|
+
tip_racks, starting_tip, tiprack_uri_for_transfer_props
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
tip_contents = [
|
|
1495
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1496
|
+
liquid=0,
|
|
1497
|
+
air_gap=0,
|
|
1498
|
+
)
|
|
1499
|
+
]
|
|
1500
|
+
next_step_volume, next_dest = next(dest_per_volume_step)
|
|
1501
|
+
is_last_step = False
|
|
1502
|
+
is_first_step = True
|
|
1503
|
+
|
|
1504
|
+
# This loop will run until the last step has been executed
|
|
1505
|
+
while not is_last_step:
|
|
1506
|
+
total_aspirate_volume = 0.0
|
|
1507
|
+
vol_dest_combo = []
|
|
1508
|
+
|
|
1509
|
+
# This loop looks at the next volumes to dispense and calculates how many
|
|
1510
|
+
# dispense volumes plus their conditioning & disposal volumes can fit into
|
|
1511
|
+
# the tip. It then collects these volumes and their destinations in a list.
|
|
1512
|
+
while not is_last_step and self._tip_can_hold_volume_for_multi_dispensing(
|
|
1513
|
+
transfer_volume=total_aspirate_volume + next_step_volume,
|
|
1514
|
+
multi_dispense_properties=transfer_props.multi_dispense,
|
|
1515
|
+
tip_working_volume=working_volume,
|
|
1516
|
+
):
|
|
1517
|
+
total_aspirate_volume += next_step_volume
|
|
1518
|
+
vol_dest_combo.append((next_step_volume, next_dest))
|
|
1519
|
+
try:
|
|
1520
|
+
next_step_volume, next_dest = next(dest_per_volume_step)
|
|
1521
|
+
except StopIteration:
|
|
1522
|
+
is_last_step = True
|
|
1523
|
+
|
|
1524
|
+
conditioning_vol = (
|
|
1525
|
+
transfer_props.multi_dispense.conditioning_by_volume.get_for_volume(
|
|
1526
|
+
total_aspirate_volume
|
|
1527
|
+
)
|
|
1528
|
+
)
|
|
1529
|
+
disposal_vol = (
|
|
1530
|
+
transfer_props.multi_dispense.disposal_by_volume.get_for_volume(
|
|
1531
|
+
total_aspirate_volume
|
|
1532
|
+
)
|
|
1533
|
+
)
|
|
1534
|
+
|
|
1535
|
+
use_single_dispense = False
|
|
1536
|
+
if total_aspirate_volume == volume and len(vol_dest_combo) == 1:
|
|
1537
|
+
# We are only doing a single transfer. Either because this is the last
|
|
1538
|
+
# remaining volume to dispense or, once this function accepts a list of
|
|
1539
|
+
# volumes, the next pair of volumes is too large to be multi-dispensed.
|
|
1540
|
+
# So we won't use conditioning volume or disposal volume
|
|
1541
|
+
conditioning_vol = 0
|
|
1542
|
+
disposal_vol = 0
|
|
1543
|
+
use_single_dispense = True
|
|
1544
|
+
|
|
1545
|
+
if (
|
|
1546
|
+
not use_single_dispense
|
|
1547
|
+
and disposal_vol > 0
|
|
1548
|
+
and not transfer_props.multi_dispense.retract.blowout.enabled
|
|
1549
|
+
):
|
|
1550
|
+
raise RuntimeError(
|
|
1551
|
+
"Distribute uses a disposal volume but location for disposing of"
|
|
1552
|
+
" the disposal volume cannot be found when blowout is disabled."
|
|
1553
|
+
" Specify a blowout location and enable blowout when using a disposal volume."
|
|
1554
|
+
)
|
|
1555
|
+
|
|
1556
|
+
if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
|
|
1557
|
+
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1558
|
+
self._pick_up_tip_for_liquid_class(
|
|
1559
|
+
tip_racks, starting_tip, tiprack_uri_for_transfer_props
|
|
1560
|
+
)
|
|
1561
|
+
tip_contents = [
|
|
1562
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1563
|
+
liquid=0,
|
|
1564
|
+
air_gap=0,
|
|
1565
|
+
)
|
|
1566
|
+
]
|
|
1567
|
+
# Aspirate the total volume determined by the loop above
|
|
1568
|
+
tip_contents = self.aspirate_liquid_class(
|
|
1569
|
+
volume=total_aspirate_volume + conditioning_vol + disposal_vol,
|
|
1570
|
+
source=source,
|
|
1571
|
+
transfer_properties=transfer_props,
|
|
1572
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
|
|
1573
|
+
tip_contents=tip_contents,
|
|
1574
|
+
# We configure the mode based on the last dispense volume and disposal volume
|
|
1575
|
+
# since the mode is only used to determine the dispense push out volume
|
|
1576
|
+
# and we can do a push out only at the last dispense, that too if there is no disposal volume.
|
|
1577
|
+
volume_for_pipette_mode_configuration=vol_dest_combo[-1][0],
|
|
1578
|
+
conditioning_volume=conditioning_vol,
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
# If the tip has volumes corresponding to multiple destinations, then
|
|
1582
|
+
# multi-dispense in those destinations.
|
|
1583
|
+
# If the tip has a volume corresponding to a single destination, then
|
|
1584
|
+
# do a single-dispense into that destination.
|
|
1585
|
+
for idx, (dispense_vol, dispense_dest) in enumerate(vol_dest_combo):
|
|
1586
|
+
if use_single_dispense:
|
|
1587
|
+
tip_contents = self.dispense_liquid_class(
|
|
1588
|
+
volume=dispense_vol,
|
|
1589
|
+
dest=dispense_dest,
|
|
1590
|
+
source=source,
|
|
1591
|
+
transfer_properties=transfer_props,
|
|
1592
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
|
|
1593
|
+
tip_contents=tip_contents,
|
|
1594
|
+
add_final_air_gap=(
|
|
1595
|
+
False if is_last_step and keep_last_tip else True
|
|
1596
|
+
),
|
|
1597
|
+
trash_location=trash_location,
|
|
1598
|
+
)
|
|
1599
|
+
else:
|
|
1600
|
+
tip_contents = self.dispense_liquid_class_during_multi_dispense(
|
|
1601
|
+
volume=dispense_vol,
|
|
1602
|
+
dest=dispense_dest,
|
|
1603
|
+
source=source,
|
|
1604
|
+
transfer_properties=transfer_props,
|
|
1605
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
|
|
1606
|
+
tip_contents=tip_contents,
|
|
1607
|
+
add_final_air_gap=(
|
|
1608
|
+
False if is_last_step and keep_last_tip else True
|
|
1609
|
+
),
|
|
1610
|
+
trash_location=trash_location,
|
|
1611
|
+
conditioning_volume=conditioning_vol,
|
|
1612
|
+
disposal_volume=disposal_vol,
|
|
1613
|
+
is_last_dispense_in_tip=(idx == len(vol_dest_combo) - 1),
|
|
1614
|
+
)
|
|
1615
|
+
is_first_step = False
|
|
1616
|
+
|
|
1617
|
+
if not keep_last_tip:
|
|
1618
|
+
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1619
|
+
|
|
1620
|
+
def _tip_can_hold_volume_for_multi_dispensing(
|
|
1621
|
+
self,
|
|
1622
|
+
transfer_volume: float,
|
|
1623
|
+
multi_dispense_properties: MultiDispenseProperties,
|
|
1624
|
+
tip_working_volume: float,
|
|
1625
|
+
) -> bool:
|
|
1626
|
+
"""
|
|
1627
|
+
Whether the tip can hold the volume plus the conditioning and disposal volumes
|
|
1628
|
+
required for multi-dispensing.
|
|
1629
|
+
"""
|
|
1630
|
+
return (
|
|
1631
|
+
transfer_volume
|
|
1632
|
+
+ multi_dispense_properties.conditioning_by_volume.get_for_volume(
|
|
1633
|
+
transfer_volume
|
|
1634
|
+
)
|
|
1635
|
+
+ multi_dispense_properties.disposal_by_volume.get_for_volume(
|
|
1636
|
+
transfer_volume
|
|
1637
|
+
)
|
|
1638
|
+
<= tip_working_volume
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
def consolidate_with_liquid_class( # noqa: C901
|
|
1642
|
+
self,
|
|
1643
|
+
liquid_class: LiquidClass,
|
|
1644
|
+
volume: float,
|
|
1645
|
+
source: List[Tuple[Location, WellCore]],
|
|
1646
|
+
dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
|
|
1647
|
+
new_tip: Literal[
|
|
1648
|
+
TransferTipPolicyV2.NEVER,
|
|
1649
|
+
TransferTipPolicyV2.ONCE,
|
|
1650
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1651
|
+
],
|
|
1652
|
+
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1653
|
+
starting_tip: Optional[WellCore],
|
|
1654
|
+
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1655
|
+
return_tip: bool,
|
|
1656
|
+
keep_last_tip: bool,
|
|
1657
|
+
) -> None:
|
|
1658
|
+
"""Execute consolidate using liquid class properties.
|
|
1659
|
+
|
|
1660
|
+
Args:
|
|
1661
|
+
liquid_class: The liquid class to use for transfer properties.
|
|
1662
|
+
volume: Volume to transfer per well.
|
|
1663
|
+
source: List of source wells, with each well represented as a tuple of
|
|
1664
|
+
types.Location and WellCore.
|
|
1665
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1666
|
+
dest: List of destination wells, with each well represented as a tuple of
|
|
1667
|
+
types.Location and WellCore.
|
|
1668
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1669
|
+
new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
|
|
1670
|
+
'never': the transfer will never pick up a new tip
|
|
1671
|
+
'once': the transfer will pick up a new tip once at the start of transfer
|
|
1672
|
+
'always': the transfer will pick up a new tip after every dispense
|
|
1673
|
+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
|
|
1674
|
+
as tuples of types.Location and WellCore.
|
|
1675
|
+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
|
|
1676
|
+
up, if the user has set it.
|
|
1677
|
+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
|
|
1678
|
+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
|
|
1679
|
+
otherwise drop in `trash_location`
|
|
1680
|
+
keep_last_tip: When set to `True`, do not drop the final tip used in the consolidate.
|
|
1681
|
+
"""
|
|
1682
|
+
if not tip_racks:
|
|
1683
|
+
raise RuntimeError(
|
|
1684
|
+
"No tipracks found for pipette in order to perform transfer"
|
|
1685
|
+
)
|
|
1686
|
+
# NOTE: Tip option of "always" in consolidate is equivalent to "after every dispense",
|
|
1687
|
+
# or more specifically, "before the next chunk of aspirates".
|
|
1688
|
+
assert new_tip in [
|
|
1689
|
+
TransferTipPolicyV2.NEVER,
|
|
1690
|
+
TransferTipPolicyV2.ONCE,
|
|
1691
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1692
|
+
]
|
|
1693
|
+
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1694
|
+
transfer_props = self._get_transfer_properties_for_tip_rack(
|
|
1695
|
+
liquid_class, tiprack_uri_for_transfer_props
|
|
1696
|
+
)
|
|
1697
|
+
|
|
1698
|
+
blow_out_properties = transfer_props.dispense.retract.blowout
|
|
1699
|
+
if (
|
|
1700
|
+
blow_out_properties.enabled
|
|
1701
|
+
and blow_out_properties.location == BlowoutLocation.SOURCE
|
|
1702
|
+
):
|
|
1703
|
+
raise RuntimeError(
|
|
1704
|
+
'Blowout location "source" incompatible with consolidate liquid.'
|
|
1705
|
+
' Please choose "destination" or "trash".'
|
|
1706
|
+
)
|
|
1707
|
+
|
|
1708
|
+
# TODO: use the ID returned by load_liquid_class in command annotations
|
|
1709
|
+
self.load_liquid_class(
|
|
1710
|
+
name=liquid_class.name,
|
|
1711
|
+
transfer_properties=transfer_props,
|
|
1712
|
+
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1713
|
+
)
|
|
1714
|
+
|
|
1715
|
+
working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
|
|
1716
|
+
|
|
1717
|
+
source_per_volume_step = (
|
|
1718
|
+
tx_commons.get_sources_and_destinations_for_liquid_classes(
|
|
1719
|
+
volumes=[volume for _ in range(len(source))],
|
|
1720
|
+
max_volume=working_volume,
|
|
1721
|
+
targets=source,
|
|
1722
|
+
transfer_properties=transfer_props,
|
|
1723
|
+
)
|
|
1724
|
+
)
|
|
1725
|
+
|
|
1726
|
+
if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
|
|
1727
|
+
self._pick_up_tip_for_liquid_class(
|
|
1728
|
+
tip_racks, starting_tip, tiprack_uri_for_transfer_props
|
|
1729
|
+
)
|
|
1730
|
+
|
|
1731
|
+
aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
|
|
1732
|
+
tip_contents = [
|
|
1733
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1734
|
+
liquid=0,
|
|
1735
|
+
air_gap=0,
|
|
1736
|
+
)
|
|
1737
|
+
]
|
|
1738
|
+
next_step_volume, next_source = next(source_per_volume_step)
|
|
1739
|
+
is_first_step = True
|
|
1740
|
+
is_last_step = False
|
|
1741
|
+
while not is_last_step:
|
|
1742
|
+
total_dispense_volume = 0.0
|
|
1743
|
+
vol_aspirate_combo = []
|
|
1744
|
+
air_gap = aspirate_air_gap_by_volume.get_for_volume(next_step_volume)
|
|
1745
|
+
# Take air gap into account because there will be a final air gap before the dispense
|
|
1746
|
+
while total_dispense_volume + next_step_volume <= working_volume - air_gap:
|
|
1747
|
+
total_dispense_volume += next_step_volume
|
|
1748
|
+
vol_aspirate_combo.append((next_step_volume, next_source))
|
|
1749
|
+
try:
|
|
1750
|
+
next_step_volume, next_source = next(source_per_volume_step)
|
|
1751
|
+
air_gap = aspirate_air_gap_by_volume.get_for_volume(
|
|
1752
|
+
next_step_volume + total_dispense_volume
|
|
1753
|
+
)
|
|
1754
|
+
except StopIteration:
|
|
1755
|
+
is_last_step = True
|
|
1756
|
+
break
|
|
1757
|
+
|
|
1758
|
+
if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
|
|
1759
|
+
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1760
|
+
self._pick_up_tip_for_liquid_class(
|
|
1761
|
+
tip_racks, starting_tip, tiprack_uri_for_transfer_props
|
|
1762
|
+
)
|
|
1763
|
+
tip_contents = [
|
|
1764
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1765
|
+
liquid=0,
|
|
1766
|
+
air_gap=0,
|
|
1767
|
+
)
|
|
1768
|
+
]
|
|
1769
|
+
|
|
1770
|
+
total_aspirated_volume = 0.0
|
|
1771
|
+
for step_num, (step_volume, step_source) in enumerate(vol_aspirate_combo):
|
|
1772
|
+
tip_contents = self.aspirate_liquid_class(
|
|
1773
|
+
volume=step_volume,
|
|
1774
|
+
source=step_source,
|
|
1775
|
+
transfer_properties=transfer_props,
|
|
1776
|
+
transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
|
|
1777
|
+
tip_contents=tip_contents,
|
|
1778
|
+
volume_for_pipette_mode_configuration=(
|
|
1779
|
+
total_dispense_volume if step_num == 0 else None
|
|
1780
|
+
),
|
|
1781
|
+
current_volume=total_aspirated_volume,
|
|
1782
|
+
)
|
|
1783
|
+
total_aspirated_volume += step_volume
|
|
1784
|
+
is_first_step = False
|
|
1785
|
+
tip_contents = self.dispense_liquid_class(
|
|
1786
|
+
volume=total_dispense_volume,
|
|
1787
|
+
dest=dest,
|
|
1788
|
+
source=None, # Cannot have source as location for blowout so hardcoded to None
|
|
1789
|
+
transfer_properties=transfer_props,
|
|
1790
|
+
transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
|
|
1791
|
+
tip_contents=tip_contents,
|
|
1792
|
+
add_final_air_gap=(False if is_last_step and keep_last_tip else True),
|
|
1793
|
+
trash_location=trash_location,
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
if not keep_last_tip:
|
|
1797
|
+
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1798
|
+
|
|
1799
|
+
def _get_location_and_well_core_from_next_tip_info(
|
|
1800
|
+
self,
|
|
1801
|
+
tip_info: NextTipInfo,
|
|
1802
|
+
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1803
|
+
) -> _TipInfo:
|
|
1804
|
+
tiprack_labware_core = self._protocol_core._labware_cores_by_id[
|
|
1805
|
+
tip_info.labwareId
|
|
1806
|
+
]
|
|
1807
|
+
tip_well = tiprack_labware_core.get_well_core(tip_info.tipStartingWell)
|
|
1808
|
+
|
|
1809
|
+
tiprack_loc = [
|
|
1810
|
+
loc for loc, lw_core in tip_racks if lw_core == tiprack_labware_core
|
|
1811
|
+
]
|
|
1812
|
+
|
|
1813
|
+
return _TipInfo(
|
|
1814
|
+
Location(tip_well.get_top(0), tiprack_loc[0].labware),
|
|
1815
|
+
tiprack_labware_core.get_uri(),
|
|
1816
|
+
tip_well,
|
|
1817
|
+
)
|
|
1818
|
+
|
|
1819
|
+
def _get_transfer_properties_for_tip_rack(
|
|
1820
|
+
self, liquid_class: LiquidClass, tip_rack_uri: str
|
|
1821
|
+
) -> TransferProperties:
|
|
1822
|
+
try:
|
|
1823
|
+
return liquid_class.get_for(
|
|
1824
|
+
pipette=self.get_pipette_name(), tip_rack=tip_rack_uri
|
|
1825
|
+
)
|
|
1826
|
+
except NoLiquidClassPropertyError:
|
|
1827
|
+
if self._protocol_core.robot_type == "OT-2 Standard":
|
|
1828
|
+
raise NoLiquidClassPropertyError(
|
|
1829
|
+
"Default liquid classes are not supported with OT-2 pipettes and tip racks."
|
|
1830
|
+
) from None
|
|
1831
|
+
raise
|
|
1832
|
+
|
|
1833
|
+
def get_working_volume_for_tip_rack(self, tip_rack: LabwareCore) -> float:
|
|
1834
|
+
"""Given a tip rack, return the maximum allowed volume for the pipette."""
|
|
1835
|
+
return min(
|
|
1836
|
+
self.get_max_volume(),
|
|
1837
|
+
self._engine_client.state.geometry.get_nominal_tip_geometry(
|
|
1838
|
+
pipette_id=self.pipette_id,
|
|
1839
|
+
labware_id=tip_rack.labware_id,
|
|
1840
|
+
well_name=None,
|
|
1841
|
+
).volume,
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
def _pick_up_tip_for_liquid_class(
|
|
1845
|
+
self,
|
|
1846
|
+
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1847
|
+
starting_tip: Optional[WellCore],
|
|
1848
|
+
tiprack_uri_for_transfer_props: str,
|
|
1849
|
+
) -> None:
|
|
1850
|
+
"""Resolve next tip and pick it up, for use in liquid class transfer code."""
|
|
1851
|
+
next_tip = self.get_next_tip(
|
|
1852
|
+
tip_racks=[core for loc, core in tip_racks],
|
|
1853
|
+
starting_well=starting_tip,
|
|
1854
|
+
)
|
|
1855
|
+
if next_tip is None:
|
|
1856
|
+
raise RuntimeError(
|
|
1857
|
+
f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
|
|
1858
|
+
f" {[f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
|
|
1859
|
+
)
|
|
1860
|
+
(
|
|
1861
|
+
tiprack_loc,
|
|
1862
|
+
tiprack_uri,
|
|
1863
|
+
tip_well,
|
|
1864
|
+
) = self._get_location_and_well_core_from_next_tip_info(next_tip, tip_racks)
|
|
1865
|
+
if tiprack_uri != tiprack_uri_for_transfer_props:
|
|
1866
|
+
raise RuntimeError(
|
|
1867
|
+
f"Tiprack {tiprack_uri} does not match the tiprack designated "
|
|
1868
|
+
f"for this transfer- {tiprack_uri_for_transfer_props}."
|
|
1869
|
+
)
|
|
1870
|
+
self.pick_up_tip(
|
|
1871
|
+
location=tiprack_loc,
|
|
1872
|
+
well_core=tip_well,
|
|
1873
|
+
presses=None,
|
|
1874
|
+
increment=None,
|
|
1875
|
+
)
|
|
1876
|
+
|
|
1877
|
+
def _drop_tip_for_liquid_class(
|
|
1878
|
+
self,
|
|
1879
|
+
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1880
|
+
return_tip: bool,
|
|
1881
|
+
) -> None:
|
|
1882
|
+
"""Drop or return tip for usage in liquid class transfers."""
|
|
1883
|
+
if return_tip:
|
|
1884
|
+
last_tip = self.get_tip_origin()
|
|
1885
|
+
assert last_tip is not None
|
|
1886
|
+
_, tip_well = last_tip
|
|
1887
|
+
self.drop_tip(
|
|
1888
|
+
location=None,
|
|
1889
|
+
well_core=tip_well,
|
|
1890
|
+
home_after=False,
|
|
1891
|
+
alternate_drop_location=False,
|
|
1892
|
+
)
|
|
1893
|
+
elif isinstance(trash_location, (TrashBin, WasteChute)):
|
|
1894
|
+
self.drop_tip_in_disposal_location(
|
|
1895
|
+
disposal_location=trash_location,
|
|
1896
|
+
home_after=False,
|
|
1897
|
+
alternate_tip_drop=True,
|
|
1898
|
+
)
|
|
1899
|
+
elif isinstance(trash_location, Location):
|
|
1900
|
+
self.drop_tip(
|
|
1901
|
+
location=trash_location,
|
|
1902
|
+
well_core=trash_location.labware.as_well()._core, # type: ignore[arg-type]
|
|
1903
|
+
home_after=False,
|
|
1904
|
+
alternate_drop_location=True,
|
|
1905
|
+
)
|
|
1906
|
+
|
|
1907
|
+
def aspirate_liquid_class(
|
|
1908
|
+
self,
|
|
1909
|
+
volume: float,
|
|
1910
|
+
source: Tuple[Location, WellCore],
|
|
1911
|
+
transfer_properties: TransferProperties,
|
|
1912
|
+
transfer_type: tx_comps_executor.TransferType,
|
|
1913
|
+
tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
|
|
1914
|
+
volume_for_pipette_mode_configuration: Optional[float],
|
|
1915
|
+
conditioning_volume: Optional[float] = None,
|
|
1916
|
+
current_volume: float = 0.0,
|
|
1917
|
+
) -> List[tx_comps_executor.LiquidAndAirGapPair]:
|
|
1918
|
+
"""Execute aspiration steps.
|
|
1919
|
+
|
|
1920
|
+
1. Submerge
|
|
1921
|
+
2. Mix
|
|
1922
|
+
3. pre-wet
|
|
1923
|
+
4. Aspirate
|
|
1924
|
+
5. Delay- wait inside the liquid
|
|
1925
|
+
6. Aspirate retract
|
|
1926
|
+
|
|
1927
|
+
Return: List of liquid and air gap pairs in tip.
|
|
1928
|
+
"""
|
|
1929
|
+
aspirate_props = transfer_properties.aspirate
|
|
1930
|
+
volume_for_air_gap = aspirate_props.retract.air_gap_by_volume.get_for_volume(
|
|
1931
|
+
volume + current_volume
|
|
1932
|
+
)
|
|
1933
|
+
tx_commons.check_valid_liquid_class_volume_parameters(
|
|
1934
|
+
aspirate_volume=volume,
|
|
1935
|
+
air_gap=volume_for_air_gap if conditioning_volume is None else 0,
|
|
1936
|
+
max_volume=self.get_working_volume(),
|
|
1937
|
+
current_volume=current_volume,
|
|
1938
|
+
)
|
|
1939
|
+
source_loc, source_well = source
|
|
1940
|
+
last_liquid_and_airgap_in_tip = (
|
|
1941
|
+
deepcopy(tip_contents[-1]) # don't modify caller's object
|
|
1942
|
+
if tip_contents
|
|
1943
|
+
else tx_comps_executor.LiquidAndAirGapPair(
|
|
1944
|
+
liquid=0,
|
|
1945
|
+
air_gap=0,
|
|
1946
|
+
)
|
|
1947
|
+
)
|
|
1948
|
+
if volume_for_pipette_mode_configuration is not None:
|
|
1949
|
+
prep_location = Location(
|
|
1950
|
+
point=source_well.get_top(LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP.z),
|
|
1951
|
+
labware=source_loc.labware,
|
|
1952
|
+
)
|
|
1953
|
+
self.move_to(
|
|
1954
|
+
location=prep_location,
|
|
1955
|
+
well_core=source_well,
|
|
1956
|
+
force_direct=False,
|
|
1957
|
+
minimum_z_height=None,
|
|
1958
|
+
speed=None,
|
|
1959
|
+
)
|
|
1960
|
+
self.remove_air_gap_during_transfer_with_liquid_class(
|
|
1961
|
+
last_air_gap=last_liquid_and_airgap_in_tip.air_gap,
|
|
1962
|
+
dispense_props=transfer_properties.dispense,
|
|
1963
|
+
location=prep_location,
|
|
1964
|
+
)
|
|
1965
|
+
last_liquid_and_airgap_in_tip.air_gap = 0
|
|
1966
|
+
# TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
|
|
1967
|
+
self.configure_for_volume(volume_for_pipette_mode_configuration)
|
|
1968
|
+
self.prepare_to_aspirate()
|
|
1969
|
+
|
|
1970
|
+
aspirate_point = (
|
|
1971
|
+
tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
1972
|
+
well=source_well,
|
|
1973
|
+
well_volume_difference=-volume,
|
|
1974
|
+
position_reference=aspirate_props.aspirate_position.position_reference,
|
|
1975
|
+
offset=aspirate_props.aspirate_position.offset,
|
|
1976
|
+
mount=self.get_mount(),
|
|
1977
|
+
)
|
|
1978
|
+
)
|
|
1979
|
+
aspirate_location = Location(aspirate_point, labware=source_loc.labware)
|
|
1980
|
+
|
|
1981
|
+
components_executor = tx_comps_executor.TransferComponentsExecutor(
|
|
1982
|
+
instrument_core=self,
|
|
1983
|
+
transfer_properties=transfer_properties,
|
|
1984
|
+
target_location=aspirate_location,
|
|
1985
|
+
target_well=source_well,
|
|
1986
|
+
transfer_type=transfer_type,
|
|
1987
|
+
tip_state=tx_comps_executor.TipState(
|
|
1988
|
+
last_liquid_and_air_gap_in_tip=last_liquid_and_airgap_in_tip
|
|
1989
|
+
),
|
|
1990
|
+
)
|
|
1991
|
+
components_executor.submerge(
|
|
1992
|
+
submerge_properties=aspirate_props.submerge, post_submerge_action="aspirate"
|
|
1993
|
+
)
|
|
1994
|
+
# Do not do a pre-aspirate mix or pre-wet if consolidating
|
|
1995
|
+
if transfer_type != tx_comps_executor.TransferType.MANY_TO_ONE:
|
|
1996
|
+
# TODO: check if we want to do a mix only once when we're splitting a transfer
|
|
1997
|
+
# and coming back to the source multiple times.
|
|
1998
|
+
# We will have to do pre-wet always even for split volumes
|
|
1999
|
+
components_executor.mix(
|
|
2000
|
+
mix_properties=aspirate_props.mix, last_dispense_push_out=False
|
|
2001
|
+
)
|
|
2002
|
+
# TODO: check if pre-wet needs to be enabled for first well of consolidate
|
|
2003
|
+
components_executor.pre_wet(
|
|
2004
|
+
volume=volume,
|
|
2005
|
+
)
|
|
2006
|
+
components_executor.aspirate_and_wait(volume=volume)
|
|
2007
|
+
if (
|
|
2008
|
+
transfer_type == tx_comps_executor.TransferType.ONE_TO_MANY
|
|
2009
|
+
and conditioning_volume not in [None, 0.0]
|
|
2010
|
+
and transfer_properties.multi_dispense is not None
|
|
2011
|
+
):
|
|
2012
|
+
# Dispense the conditioning volume
|
|
2013
|
+
components_executor.dispense_and_wait(
|
|
2014
|
+
dispense_properties=transfer_properties.multi_dispense,
|
|
2015
|
+
volume=conditioning_volume or 0.0,
|
|
2016
|
+
push_out_override=0,
|
|
2017
|
+
)
|
|
2018
|
+
components_executor.retract_after_aspiration(
|
|
2019
|
+
volume=volume, add_air_gap=False
|
|
2020
|
+
)
|
|
2021
|
+
else:
|
|
2022
|
+
components_executor.retract_after_aspiration(
|
|
2023
|
+
volume=volume, add_air_gap=True
|
|
2024
|
+
)
|
|
2025
|
+
|
|
2026
|
+
# return copy of tip_contents with last entry replaced by tip state from executor
|
|
2027
|
+
last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
|
|
2028
|
+
new_tip_contents = tip_contents[0:-1] + [last_contents]
|
|
2029
|
+
return new_tip_contents
|
|
2030
|
+
|
|
2031
|
+
def remove_air_gap_during_transfer_with_liquid_class(
|
|
2032
|
+
self,
|
|
2033
|
+
last_air_gap: float,
|
|
2034
|
+
dispense_props: SingleDispenseProperties,
|
|
2035
|
+
location: Union[Location, TrashBin, WasteChute],
|
|
2036
|
+
) -> None:
|
|
2037
|
+
"""Remove an air gap that was previously added during a transfer."""
|
|
2038
|
+
if last_air_gap == 0:
|
|
2039
|
+
return
|
|
2040
|
+
current_vol = self.get_current_volume()
|
|
2041
|
+
check_current_volume_before_dispensing(
|
|
2042
|
+
current_volume=current_vol, dispense_volume=last_air_gap
|
|
2043
|
+
)
|
|
2044
|
+
correction_volume = dispense_props.correction_by_volume.get_for_volume(
|
|
2045
|
+
current_vol - last_air_gap
|
|
2046
|
+
)
|
|
2047
|
+
# The minimum flow rate should be air_gap_volume per second
|
|
2048
|
+
flow_rate = max(
|
|
2049
|
+
dispense_props.flow_rate_by_volume.get_for_volume(last_air_gap),
|
|
2050
|
+
last_air_gap,
|
|
2051
|
+
)
|
|
2052
|
+
self.dispense(
|
|
2053
|
+
location=location,
|
|
2054
|
+
well_core=None,
|
|
2055
|
+
volume=last_air_gap,
|
|
2056
|
+
rate=1,
|
|
2057
|
+
flow_rate=flow_rate,
|
|
2058
|
+
in_place=True,
|
|
2059
|
+
push_out=0,
|
|
2060
|
+
correction_volume=correction_volume,
|
|
2061
|
+
)
|
|
2062
|
+
dispense_delay = dispense_props.delay
|
|
2063
|
+
if dispense_delay.enabled and dispense_delay.duration:
|
|
2064
|
+
self.delay(dispense_delay.duration)
|
|
2065
|
+
|
|
2066
|
+
def dispense_liquid_class(
|
|
2067
|
+
self,
|
|
2068
|
+
volume: float,
|
|
2069
|
+
dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
|
|
2070
|
+
source: Optional[Tuple[Location, WellCore]],
|
|
2071
|
+
transfer_properties: TransferProperties,
|
|
2072
|
+
transfer_type: tx_comps_executor.TransferType,
|
|
2073
|
+
tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
|
|
2074
|
+
add_final_air_gap: bool,
|
|
2075
|
+
trash_location: Union[Location, TrashBin, WasteChute],
|
|
2076
|
+
) -> List[tx_comps_executor.LiquidAndAirGapPair]:
|
|
2077
|
+
"""Execute single-dispense steps.
|
|
2078
|
+
1. Move pipette to the ‘submerge’ position with normal speed.
|
|
2079
|
+
- The pipette will move in an arc- move to max z height of labware
|
|
2080
|
+
(if asp & disp are in same labware)
|
|
2081
|
+
or max z height of all labware (if asp & disp are in separate labware)
|
|
2082
|
+
2. Air gap removal:
|
|
2083
|
+
- If dispense location is above the meniscus, DO NOT remove air gap
|
|
2084
|
+
(it will be dispensed along with rest of the liquid later).
|
|
2085
|
+
All other scenarios, remove the air gap by doing a dispense
|
|
2086
|
+
- Flow rate = min(dispenseFlowRate, (airGapByVolume)/sec)
|
|
2087
|
+
- Use the post-dispense delay
|
|
2088
|
+
4. Move to the dispense position at the specified ‘submerge’ speed
|
|
2089
|
+
(even if we might not be moving into the liquid)
|
|
2090
|
+
- Do a delay (submerge delay)
|
|
2091
|
+
6. Dispense:
|
|
2092
|
+
- Dispense at the specified flow rate.
|
|
2093
|
+
- Do a push out as specified ONLY IF there is no mix following the dispense AND the tip is empty.
|
|
2094
|
+
Volume for push out is the volume being dispensed. So if we are dispensing 50uL, use pushOutByVolume[50] as push out volume.
|
|
2095
|
+
7. Delay
|
|
2096
|
+
8. Mix using the same flow rate and delays as specified for asp+disp,
|
|
2097
|
+
with the volume and the number of repetitions specified. Use the delays in asp & disp.
|
|
2098
|
+
- If the dispense position is outside the liquid, then raise error if mix is enabled.
|
|
2099
|
+
Can only be checked if using liquid level detection/ meniscus-based positioning.
|
|
2100
|
+
- If the user wants to perform a mix then they should specify a dispense position that’s inside the liquid OR do mix() on the wells after transfer.
|
|
2101
|
+
- Do push out at the last dispense.
|
|
2102
|
+
9. Retract
|
|
2103
|
+
|
|
2104
|
+
Return:
|
|
2105
|
+
List of liquid and air gap pairs in tip.
|
|
2106
|
+
"""
|
|
2107
|
+
dispense_props = transfer_properties.dispense
|
|
2108
|
+
dispense_location: Union[Location, TrashBin, WasteChute]
|
|
2109
|
+
if isinstance(dest, tuple):
|
|
2110
|
+
dest_loc, dest_well = dest
|
|
2111
|
+
dispense_point = tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
2112
|
+
well=dest_well,
|
|
2113
|
+
well_volume_difference=volume,
|
|
2114
|
+
position_reference=dispense_props.dispense_position.position_reference,
|
|
2115
|
+
offset=dispense_props.dispense_position.offset,
|
|
2116
|
+
mount=self.get_mount(),
|
|
2117
|
+
)
|
|
2118
|
+
dispense_location = Location(dispense_point, labware=dest_loc.labware)
|
|
2119
|
+
else:
|
|
2120
|
+
dispense_location = dest
|
|
2121
|
+
dest_well = None
|
|
2122
|
+
|
|
2123
|
+
last_liquid_and_airgap_in_tip = (
|
|
2124
|
+
tip_contents[-1]
|
|
2125
|
+
if tip_contents
|
|
2126
|
+
else tx_comps_executor.LiquidAndAirGapPair(
|
|
2127
|
+
liquid=0,
|
|
2128
|
+
air_gap=0,
|
|
2129
|
+
)
|
|
2130
|
+
)
|
|
2131
|
+
components_executor = tx_comps_executor.TransferComponentsExecutor(
|
|
2132
|
+
instrument_core=self,
|
|
2133
|
+
transfer_properties=transfer_properties,
|
|
2134
|
+
target_location=dispense_location,
|
|
2135
|
+
target_well=dest_well,
|
|
2136
|
+
transfer_type=transfer_type,
|
|
2137
|
+
tip_state=tx_comps_executor.TipState(
|
|
2138
|
+
last_liquid_and_air_gap_in_tip=last_liquid_and_airgap_in_tip
|
|
2139
|
+
),
|
|
2140
|
+
)
|
|
2141
|
+
components_executor.submerge(
|
|
2142
|
+
submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
|
|
2143
|
+
)
|
|
2144
|
+
push_out_vol = (
|
|
2145
|
+
0.0
|
|
2146
|
+
if dispense_props.mix.enabled
|
|
2147
|
+
else dispense_props.push_out_by_volume.get_for_volume(volume)
|
|
2148
|
+
)
|
|
2149
|
+
components_executor.dispense_and_wait(
|
|
2150
|
+
dispense_properties=dispense_props,
|
|
2151
|
+
volume=volume,
|
|
2152
|
+
push_out_override=push_out_vol,
|
|
2153
|
+
)
|
|
2154
|
+
components_executor.mix(
|
|
2155
|
+
mix_properties=dispense_props.mix,
|
|
2156
|
+
last_dispense_push_out=True,
|
|
2157
|
+
)
|
|
2158
|
+
components_executor.retract_after_dispensing(
|
|
2159
|
+
trash_location=trash_location,
|
|
2160
|
+
source_location=source[0] if source else None,
|
|
2161
|
+
source_well=source[1] if source else None,
|
|
2162
|
+
add_final_air_gap=add_final_air_gap,
|
|
2163
|
+
)
|
|
2164
|
+
last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
|
|
2165
|
+
new_tip_contents = tip_contents[0:-1] + [last_contents]
|
|
2166
|
+
return new_tip_contents
|
|
2167
|
+
|
|
2168
|
+
def dispense_liquid_class_during_multi_dispense(
|
|
2169
|
+
self,
|
|
2170
|
+
volume: float,
|
|
2171
|
+
dest: Tuple[Location, WellCore],
|
|
2172
|
+
source: Optional[Tuple[Location, WellCore]],
|
|
2173
|
+
transfer_properties: TransferProperties,
|
|
2174
|
+
transfer_type: tx_comps_executor.TransferType,
|
|
2175
|
+
tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
|
|
2176
|
+
add_final_air_gap: bool,
|
|
2177
|
+
trash_location: Union[Location, TrashBin, WasteChute],
|
|
2178
|
+
conditioning_volume: float,
|
|
2179
|
+
disposal_volume: float,
|
|
2180
|
+
is_last_dispense_in_tip: bool,
|
|
2181
|
+
) -> List[tx_comps_executor.LiquidAndAirGapPair]:
|
|
2182
|
+
"""Execute a dispense step that's part of a multi-dispense.
|
|
2183
|
+
|
|
2184
|
+
This executes a dispense step very similar to a single dispense except that:
|
|
2185
|
+
- it uses the multi-dispense properties from the liquid class
|
|
2186
|
+
- handles push-out based on disposal volume in addition to the existing conditions
|
|
2187
|
+
- delegates the retraction steps to a different, multi-dispense retract function
|
|
2188
|
+
|
|
2189
|
+
Return:
|
|
2190
|
+
List of liquid and air gap pairs in tip.
|
|
2191
|
+
"""
|
|
2192
|
+
assert transfer_properties.multi_dispense is not None
|
|
2193
|
+
dispense_props = transfer_properties.multi_dispense
|
|
2194
|
+
|
|
2195
|
+
dest_loc, dest_well = dest
|
|
2196
|
+
dispense_point = (
|
|
2197
|
+
tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
2198
|
+
well=dest_well,
|
|
2199
|
+
well_volume_difference=volume,
|
|
2200
|
+
position_reference=dispense_props.dispense_position.position_reference,
|
|
2201
|
+
offset=dispense_props.dispense_position.offset,
|
|
2202
|
+
mount=self.get_mount(),
|
|
2203
|
+
)
|
|
2204
|
+
)
|
|
2205
|
+
dispense_location = Location(dispense_point, labware=dest_loc.labware)
|
|
2206
|
+
last_liquid_and_airgap_in_tip = (
|
|
2207
|
+
tip_contents[-1]
|
|
2208
|
+
if tip_contents
|
|
2209
|
+
else tx_comps_executor.LiquidAndAirGapPair(
|
|
2210
|
+
liquid=0,
|
|
2211
|
+
air_gap=0,
|
|
2212
|
+
)
|
|
2213
|
+
)
|
|
2214
|
+
components_executor = tx_comps_executor.TransferComponentsExecutor(
|
|
2215
|
+
instrument_core=self,
|
|
2216
|
+
transfer_properties=transfer_properties,
|
|
2217
|
+
target_location=dispense_location,
|
|
2218
|
+
target_well=dest_well,
|
|
2219
|
+
transfer_type=transfer_type,
|
|
2220
|
+
tip_state=tx_comps_executor.TipState(
|
|
2221
|
+
last_liquid_and_air_gap_in_tip=last_liquid_and_airgap_in_tip
|
|
2222
|
+
),
|
|
2223
|
+
)
|
|
2224
|
+
components_executor.submerge(
|
|
2225
|
+
submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
|
|
2226
|
+
)
|
|
2227
|
+
is_last_dispense_without_disposal_vol = (
|
|
2228
|
+
disposal_volume == 0 and is_last_dispense_in_tip
|
|
2229
|
+
)
|
|
2230
|
+
push_out_vol = (
|
|
2231
|
+
# TODO (spp): verify if it's okay to use push_out_by_volume of single dispense
|
|
2232
|
+
transfer_properties.dispense.push_out_by_volume.get_for_volume(volume)
|
|
2233
|
+
if is_last_dispense_without_disposal_vol
|
|
2234
|
+
else 0.0
|
|
2235
|
+
)
|
|
2236
|
+
|
|
2237
|
+
components_executor.dispense_and_wait(
|
|
2238
|
+
dispense_properties=dispense_props,
|
|
2239
|
+
volume=volume,
|
|
2240
|
+
push_out_override=push_out_vol,
|
|
2241
|
+
)
|
|
2242
|
+
components_executor.retract_during_multi_dispensing(
|
|
2243
|
+
trash_location=trash_location,
|
|
2244
|
+
source_location=source[0] if source else None,
|
|
2245
|
+
source_well=source[1] if source else None,
|
|
2246
|
+
conditioning_volume=conditioning_volume,
|
|
2247
|
+
add_final_air_gap=add_final_air_gap,
|
|
2248
|
+
is_last_retract=is_last_dispense_in_tip,
|
|
2249
|
+
)
|
|
2250
|
+
last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
|
|
2251
|
+
new_tip_contents = tip_contents[0:-1] + [last_contents]
|
|
2252
|
+
return new_tip_contents
|
|
2253
|
+
|
|
2254
|
+
def retract(self) -> None:
|
|
2255
|
+
"""Retract this instrument to the top of the gantry."""
|
|
2256
|
+
z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id)
|
|
2257
|
+
self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis]))
|
|
2258
|
+
|
|
2259
|
+
def _pressure_supported_by_pipette(self) -> bool:
|
|
2260
|
+
return self._engine_client.state.pipettes.get_pipette_supports_pressure(
|
|
2261
|
+
self.pipette_id
|
|
2262
|
+
)
|
|
2263
|
+
|
|
2264
|
+
def detect_liquid_presence(self, well_core: WellCore, loc: Location) -> bool:
|
|
2265
|
+
labware_id = well_core.labware_id
|
|
2266
|
+
well_name = well_core.get_name()
|
|
2267
|
+
offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
|
|
2268
|
+
well_location = WellLocation(
|
|
2269
|
+
origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
|
|
2270
|
+
)
|
|
2271
|
+
|
|
2272
|
+
# The error handling here is a bit nuanced and also a bit broken:
|
|
2273
|
+
#
|
|
2274
|
+
# - If the hardware detects liquid, the `tryLiquidProbe` engine command will
|
|
2275
|
+
# succeed and return a height, which we'll convert to a `True` return.
|
|
2276
|
+
# Okay so far.
|
|
2277
|
+
#
|
|
2278
|
+
# - If the hardware detects no liquid, the `tryLiquidProbe` engine command will
|
|
2279
|
+
# succeed and return `None`, which we'll convert to a `False` return.
|
|
2280
|
+
# Still okay so far.
|
|
2281
|
+
#
|
|
2282
|
+
# - If there is any other error within the `tryLiquidProbe` command, things get
|
|
2283
|
+
# messy. It may kick the run into recovery mode. At that point, all bets are
|
|
2284
|
+
# off--we lose our guarantee of having a `tryLiquidProbe` command whose
|
|
2285
|
+
# `result` we can inspect. We don't know how to deal with that here, so we
|
|
2286
|
+
# currently propagate the exception up, which will quickly kill the protocol,
|
|
2287
|
+
# after a potential split second of recovery mode. It's unclear what would
|
|
2288
|
+
# be good user-facing behavior here, but it's unfortunate to kill the protocol
|
|
2289
|
+
# for an error that the engine thinks should be recoverable.
|
|
2290
|
+
result = self._engine_client.execute_command_without_recovery(
|
|
2291
|
+
cmd.TryLiquidProbeParams(
|
|
2292
|
+
labwareId=labware_id,
|
|
2293
|
+
wellName=well_name,
|
|
2294
|
+
wellLocation=well_location,
|
|
2295
|
+
pipetteId=self.pipette_id,
|
|
2296
|
+
)
|
|
2297
|
+
)
|
|
2298
|
+
|
|
2299
|
+
self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
|
|
2300
|
+
|
|
2301
|
+
return result.z_position is not None
|
|
2302
|
+
|
|
2303
|
+
def get_minimum_liquid_sense_height(self) -> float:
|
|
2304
|
+
attached_tip = self._engine_client.state.pipettes.get_attached_tip(
|
|
2305
|
+
self._pipette_id
|
|
2306
|
+
)
|
|
2307
|
+
if attached_tip:
|
|
2308
|
+
tip_volume = attached_tip.volume
|
|
2309
|
+
else:
|
|
2310
|
+
raise TipNotAttachedError(
|
|
2311
|
+
"Need to have a tip attached for liquid-sense operations."
|
|
2312
|
+
)
|
|
2313
|
+
lld_settings = self._engine_client.state.pipettes.get_pipette_lld_settings(
|
|
2314
|
+
pipette_id=self.pipette_id
|
|
2315
|
+
)
|
|
2316
|
+
if lld_settings:
|
|
2317
|
+
lld_min_height_for_tip_attached = lld_settings[f"t{tip_volume}"][
|
|
2318
|
+
"minHeight"
|
|
2319
|
+
]
|
|
2320
|
+
return lld_min_height_for_tip_attached
|
|
2321
|
+
else:
|
|
2322
|
+
raise ValueError("liquid-level detection settings not found.")
|
|
2323
|
+
|
|
2324
|
+
def liquid_probe_with_recovery(self, well_core: WellCore, loc: Location) -> None:
|
|
2325
|
+
labware_id = well_core.labware_id
|
|
2326
|
+
well_name = well_core.get_name()
|
|
2327
|
+
offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
|
|
2328
|
+
well_location = WellLocation(
|
|
2329
|
+
origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
|
|
2330
|
+
)
|
|
2331
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
2332
|
+
engine_state=self._engine_client.state,
|
|
2333
|
+
pipette_id=self._pipette_id,
|
|
2334
|
+
labware_id=labware_id,
|
|
2335
|
+
well_name=well_name,
|
|
2336
|
+
well_location=well_location,
|
|
2337
|
+
)
|
|
2338
|
+
self._engine_client.execute_command(
|
|
2339
|
+
cmd.LiquidProbeParams(
|
|
2340
|
+
labwareId=labware_id,
|
|
2341
|
+
wellName=well_name,
|
|
2342
|
+
wellLocation=well_location,
|
|
2343
|
+
pipetteId=self.pipette_id,
|
|
2344
|
+
)
|
|
2345
|
+
)
|
|
2346
|
+
|
|
2347
|
+
self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
|
|
2348
|
+
|
|
2349
|
+
def liquid_probe_without_recovery(
|
|
2350
|
+
self, well_core: WellCore, loc: Location
|
|
2351
|
+
) -> LiquidTrackingType:
|
|
2352
|
+
labware_id = well_core.labware_id
|
|
2353
|
+
well_name = well_core.get_name()
|
|
2354
|
+
offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
|
|
2355
|
+
well_location = WellLocation(
|
|
2356
|
+
origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
|
|
2357
|
+
)
|
|
2358
|
+
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
2359
|
+
engine_state=self._engine_client.state,
|
|
2360
|
+
pipette_id=self._pipette_id,
|
|
2361
|
+
labware_id=labware_id,
|
|
2362
|
+
well_name=well_name,
|
|
2363
|
+
well_location=well_location,
|
|
2364
|
+
)
|
|
2365
|
+
result = self._engine_client.execute_command_without_recovery(
|
|
2366
|
+
cmd.LiquidProbeParams(
|
|
2367
|
+
labwareId=labware_id,
|
|
2368
|
+
wellName=well_name,
|
|
2369
|
+
wellLocation=well_location,
|
|
2370
|
+
pipetteId=self.pipette_id,
|
|
2371
|
+
)
|
|
2372
|
+
)
|
|
2373
|
+
|
|
2374
|
+
self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
|
|
2375
|
+
return result.z_position
|
|
2376
|
+
|
|
2377
|
+
def nozzle_configuration_valid_for_lld(self) -> bool:
|
|
2378
|
+
"""Check if the nozzle configuration currently supports LLD."""
|
|
2379
|
+
return self._engine_client.state.pipettes.get_nozzle_configuration_supports_lld(
|
|
2380
|
+
self.pipette_id
|
|
2381
|
+
)
|
|
2382
|
+
|
|
2383
|
+
def delay(self, seconds: float) -> None:
|
|
2384
|
+
"""Call a protocol delay."""
|
|
2385
|
+
self._protocol_core.delay(seconds=seconds, msg=None)
|
|
2386
|
+
|
|
2387
|
+
|
|
2388
|
+
class _TipInfo(NamedTuple):
|
|
2389
|
+
tiprack_location: Location
|
|
2390
|
+
tiprack_uri: str
|
|
2391
|
+
tip_well: WellCore
|