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,1030 @@
|
|
|
1
|
+
"""Shared code for managing pipette configuration and storage."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import logging
|
|
4
|
+
from typing import (
|
|
5
|
+
Dict,
|
|
6
|
+
Optional,
|
|
7
|
+
Tuple,
|
|
8
|
+
Any,
|
|
9
|
+
cast,
|
|
10
|
+
List,
|
|
11
|
+
TypeVar,
|
|
12
|
+
)
|
|
13
|
+
from typing_extensions import Final
|
|
14
|
+
import numpy
|
|
15
|
+
from opentrons_shared_data.pipette.types import UlPerMmAction
|
|
16
|
+
|
|
17
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
18
|
+
CommandPreconditionViolated,
|
|
19
|
+
CommandParameterLimitViolated,
|
|
20
|
+
UnexpectedTipRemovalError,
|
|
21
|
+
UnexpectedTipAttachError,
|
|
22
|
+
)
|
|
23
|
+
from opentrons_shared_data.pipette.pipette_definition import (
|
|
24
|
+
liquid_class_for_volume_between_default_and_defaultlowvolume,
|
|
25
|
+
PressFitPickUpTipConfiguration,
|
|
26
|
+
CamActionPickUpTipConfiguration,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from opentrons import types as top_types
|
|
30
|
+
from opentrons.hardware_control.types import (
|
|
31
|
+
CriticalPoint,
|
|
32
|
+
HardwareAction,
|
|
33
|
+
Axis,
|
|
34
|
+
OT3Mount,
|
|
35
|
+
TipScrapeType,
|
|
36
|
+
)
|
|
37
|
+
from opentrons.hardware_control.constants import (
|
|
38
|
+
SHAKE_OFF_TIPS_SPEED,
|
|
39
|
+
SHAKE_OFF_TIPS_PICKUP_DISTANCE,
|
|
40
|
+
DROP_TIP_RELEASE_DISTANCE,
|
|
41
|
+
SHAKE_OFF_TIPS_DROP_DISTANCE,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
from opentrons.hardware_control.dev_types import PipetteDict
|
|
45
|
+
from .pipette import Pipette
|
|
46
|
+
from .instrument_calibration import (
|
|
47
|
+
PipetteOffsetSummary,
|
|
48
|
+
PipetteOffsetByPipetteMount,
|
|
49
|
+
check_instrument_offset_reasonability,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
MOD_LOG = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
# TODO both pipette handlers should be combined once the pipette configurations
|
|
56
|
+
# are unified AND we separate out the concept of changing pipette state versus static state
|
|
57
|
+
HOME_POSITION: Final[float] = 230.15
|
|
58
|
+
|
|
59
|
+
MountType = TypeVar("MountType", top_types.Mount, OT3Mount)
|
|
60
|
+
InstrumentsByMount = Dict[MountType, Optional[Pipette]]
|
|
61
|
+
PipetteHandlingData = Tuple[Pipette, OT3Mount]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class LiquidActionSpec:
|
|
66
|
+
axis: Axis
|
|
67
|
+
volume: float
|
|
68
|
+
plunger_distance: float
|
|
69
|
+
speed: float
|
|
70
|
+
acceleration: float
|
|
71
|
+
instr: Pipette
|
|
72
|
+
current: float
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class TipActionMoveSpec:
|
|
77
|
+
distance: float
|
|
78
|
+
currents: Optional[Dict[Axis, float]]
|
|
79
|
+
speed: Optional[
|
|
80
|
+
float
|
|
81
|
+
] # allow speed for a movement to default to its axes' speed settings
|
|
82
|
+
scrape_axis: Optional[Axis] = None
|
|
83
|
+
# add a scrape motion in the middle of a tip drop
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class TipActionSpec:
|
|
88
|
+
tip_action_moves: List[TipActionMoveSpec]
|
|
89
|
+
shake_off_moves: List[Tuple[top_types.Point, Optional[float]]]
|
|
90
|
+
z_distance_to_tiprack: Optional[float] = None
|
|
91
|
+
ending_z_retract_distance: Optional[float] = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OT3PipetteHandler:
|
|
95
|
+
IHP_LOG = MOD_LOG.getChild("InstrumentHandler")
|
|
96
|
+
|
|
97
|
+
def __init__(self, attached_instruments: InstrumentsByMount[OT3Mount]):
|
|
98
|
+
assert attached_instruments
|
|
99
|
+
self._attached_instruments: InstrumentsByMount[OT3Mount] = attached_instruments
|
|
100
|
+
self._ihp_log = self.__class__.IHP_LOG
|
|
101
|
+
|
|
102
|
+
def reset_instrument(self, mount: Optional[OT3Mount] = None) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Reset the internal state of a pipette by its mount, without doing
|
|
105
|
+
any lower level reconfiguration. This is useful to make sure that no
|
|
106
|
+
settings changes from a protocol persist.
|
|
107
|
+
|
|
108
|
+
:param mount: If specified, reset that mount. If not specified,
|
|
109
|
+
reset both
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# need to have a reset function on the pipette
|
|
113
|
+
def _reset(m: OT3Mount) -> None:
|
|
114
|
+
self._ihp_log.info(f"Resetting configuration for {m}")
|
|
115
|
+
p = self._attached_instruments[m]
|
|
116
|
+
if not p:
|
|
117
|
+
return
|
|
118
|
+
p.reset_pipette_offset(OT3Mount.from_mount(m), to_default=False)
|
|
119
|
+
p.reload_configurations()
|
|
120
|
+
p.reset_state()
|
|
121
|
+
|
|
122
|
+
if not mount:
|
|
123
|
+
for m in type(list(self._attached_instruments.keys())[0]):
|
|
124
|
+
_reset(m)
|
|
125
|
+
else:
|
|
126
|
+
_reset(mount)
|
|
127
|
+
|
|
128
|
+
def get_instrument_offset(self, mount: OT3Mount) -> Optional[PipetteOffsetSummary]:
|
|
129
|
+
"""Get the specified pipette's offset."""
|
|
130
|
+
assert mount != OT3Mount.GRIPPER, "Wrong mount type to fetch pipette offset"
|
|
131
|
+
try:
|
|
132
|
+
pipette = self.get_pipette(mount)
|
|
133
|
+
except top_types.PipetteNotAttachedError:
|
|
134
|
+
return None
|
|
135
|
+
return self._return_augmented_offset_data(
|
|
136
|
+
pipette, mount, pipette.pipette_offset
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def reset_instrument_offset(self, mount: OT3Mount, to_default: bool) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Temporarily reset the pipette offset to default values.
|
|
142
|
+
:param mount: Modify the given mount.
|
|
143
|
+
"""
|
|
144
|
+
pipette = self.get_pipette(mount)
|
|
145
|
+
pipette.reset_pipette_offset(mount, to_default)
|
|
146
|
+
|
|
147
|
+
def save_instrument_offset(
|
|
148
|
+
self, mount: OT3Mount, delta: top_types.Point
|
|
149
|
+
) -> PipetteOffsetSummary:
|
|
150
|
+
"""
|
|
151
|
+
Save a new instrument offset the pipette offset to a particular value.
|
|
152
|
+
:param mount: Modify the given mount.
|
|
153
|
+
:param delta: The offset to set for the pipette.
|
|
154
|
+
"""
|
|
155
|
+
pipette = self.get_pipette(mount)
|
|
156
|
+
offset_data = pipette.save_pipette_offset(mount, delta)
|
|
157
|
+
return self._return_augmented_offset_data(pipette, mount, offset_data)
|
|
158
|
+
|
|
159
|
+
def _return_augmented_offset_data(
|
|
160
|
+
self,
|
|
161
|
+
pipette: Pipette,
|
|
162
|
+
mount: OT3Mount,
|
|
163
|
+
offset_data: PipetteOffsetByPipetteMount,
|
|
164
|
+
) -> PipetteOffsetSummary:
|
|
165
|
+
if mount == OT3Mount.LEFT:
|
|
166
|
+
other_pipette = self._attached_instruments.get(OT3Mount.RIGHT, None)
|
|
167
|
+
if other_pipette:
|
|
168
|
+
other_offset = other_pipette.pipette_offset.offset
|
|
169
|
+
else:
|
|
170
|
+
other_offset = top_types.Point(0, 0, 0)
|
|
171
|
+
reasonability = check_instrument_offset_reasonability(
|
|
172
|
+
offset_data.offset, other_offset
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
other_pipette = self._attached_instruments.get(OT3Mount.LEFT, None)
|
|
176
|
+
if other_pipette:
|
|
177
|
+
other_offset = other_pipette.pipette_offset.offset
|
|
178
|
+
else:
|
|
179
|
+
other_offset = top_types.Point(0, 0, 0)
|
|
180
|
+
reasonability = check_instrument_offset_reasonability(
|
|
181
|
+
other_offset, offset_data.offset
|
|
182
|
+
)
|
|
183
|
+
return PipetteOffsetSummary(
|
|
184
|
+
offset=offset_data.offset,
|
|
185
|
+
source=offset_data.source,
|
|
186
|
+
status=offset_data.status,
|
|
187
|
+
last_modified=offset_data.last_modified,
|
|
188
|
+
reasonability_check_failures=reasonability,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# TODO(mc, 2022-01-11): change returned map value type to `Optional[PipetteDict]`
|
|
192
|
+
# instead of potentially returning an empty dict
|
|
193
|
+
# For compatibility purposes only right now. We should change this
|
|
194
|
+
# as soon as we can modify the /pipettes endpoint.
|
|
195
|
+
def get_attached_instruments(self) -> Dict[OT3Mount, PipetteDict]:
|
|
196
|
+
"""Get the status dicts of the cached attached instruments.
|
|
197
|
+
|
|
198
|
+
Also available as :py:meth:`get_attached_instruments`.
|
|
199
|
+
|
|
200
|
+
This returns a dictified version of the
|
|
201
|
+
:py:class:`hardware_control.instruments.pipette.Pipette` as a dict keyed by
|
|
202
|
+
the :py:class:`top_types.Mount` to which the pipette is attached.
|
|
203
|
+
If no pipette is attached on a given mount, the mount key will
|
|
204
|
+
still be present but will have the value ``None``.
|
|
205
|
+
|
|
206
|
+
Note that this is only a query of a cached value; to actively scan
|
|
207
|
+
for changes, use :py:meth:`cache_instruments`. This process deactivates
|
|
208
|
+
the motors and should be used sparingly.
|
|
209
|
+
"""
|
|
210
|
+
return {
|
|
211
|
+
m: self.get_attached_instrument(m)
|
|
212
|
+
for m in self._attached_instruments.keys()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# TODO(mc, 2022-01-11): change return type to `Optional[PipetteDict]` instead
|
|
216
|
+
# of potentially returning an empty dict
|
|
217
|
+
def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
|
|
218
|
+
# TODO (lc 12-05-2022) Kill this code ASAP
|
|
219
|
+
instr = self._attached_instruments[mount]
|
|
220
|
+
result: Dict[str, Any] = {}
|
|
221
|
+
if instr:
|
|
222
|
+
configs = [
|
|
223
|
+
"name",
|
|
224
|
+
"aspirate_flow_rate",
|
|
225
|
+
"dispense_flow_rate",
|
|
226
|
+
"pipette_id",
|
|
227
|
+
"current_volume",
|
|
228
|
+
"display_name",
|
|
229
|
+
"tip_length",
|
|
230
|
+
"model",
|
|
231
|
+
"blow_out_flow_rate",
|
|
232
|
+
"working_volume",
|
|
233
|
+
"tip_overlap",
|
|
234
|
+
"versioned_tip_overlap",
|
|
235
|
+
"available_volume",
|
|
236
|
+
"return_tip_height",
|
|
237
|
+
"default_aspirate_flow_rates",
|
|
238
|
+
"default_blow_out_flow_rates",
|
|
239
|
+
"default_dispense_flow_rates",
|
|
240
|
+
"back_compat_names",
|
|
241
|
+
"supported_tips",
|
|
242
|
+
"lld_settings",
|
|
243
|
+
"available_sensors",
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
instr_dict = instr.as_dict()
|
|
247
|
+
# TODO (spp, 2021-08-27): Revisit this logic. Why do we need to build
|
|
248
|
+
# this dict newly every time? Any why only a few items are being updated?
|
|
249
|
+
for key in configs:
|
|
250
|
+
result[key] = instr_dict[key]
|
|
251
|
+
|
|
252
|
+
result["current_nozzle_map"] = instr.nozzle_manager.current_configuration
|
|
253
|
+
result["min_volume"] = instr.liquid_class.min_volume
|
|
254
|
+
result["max_volume"] = instr.liquid_class.max_volume
|
|
255
|
+
result["channels"] = instr._max_channels.value
|
|
256
|
+
result["has_tip"] = instr.has_tip
|
|
257
|
+
result["tip_length"] = instr.current_tip_length
|
|
258
|
+
result["aspirate_speed"] = self.plunger_speed(
|
|
259
|
+
instr, instr.aspirate_flow_rate, "aspirate"
|
|
260
|
+
)
|
|
261
|
+
result["dispense_speed"] = self.plunger_speed(
|
|
262
|
+
instr, instr.dispense_flow_rate, "dispense"
|
|
263
|
+
)
|
|
264
|
+
result["blow_out_speed"] = self.plunger_speed(
|
|
265
|
+
instr, instr.blow_out_flow_rate, "dispense"
|
|
266
|
+
)
|
|
267
|
+
result["ready_to_aspirate"] = instr.ready_to_aspirate
|
|
268
|
+
|
|
269
|
+
result["default_blow_out_speeds"] = {
|
|
270
|
+
alvl: self.plunger_speed(instr, fr, "blowout")
|
|
271
|
+
for alvl, fr in instr.blow_out_flow_rates_lookup.items()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
result["default_dispense_speeds"] = {
|
|
275
|
+
alvl: self.plunger_speed(instr, fr, "dispense")
|
|
276
|
+
for alvl, fr in instr.dispense_flow_rates_lookup.items()
|
|
277
|
+
}
|
|
278
|
+
result["default_aspirate_speeds"] = {
|
|
279
|
+
alvl: self.plunger_speed(instr, fr, "aspirate")
|
|
280
|
+
for alvl, fr in instr.aspirate_flow_rates_lookup.items()
|
|
281
|
+
}
|
|
282
|
+
result[
|
|
283
|
+
"default_push_out_volume"
|
|
284
|
+
] = instr.active_tip_settings.default_push_out_volume
|
|
285
|
+
result[
|
|
286
|
+
"pipette_bounding_box_offsets"
|
|
287
|
+
] = instr.config.pipette_bounding_box_offsets
|
|
288
|
+
result["lld_settings"] = instr.config.lld_settings
|
|
289
|
+
result["plunger_positions"] = {
|
|
290
|
+
"top": instr.plunger_positions.top,
|
|
291
|
+
"bottom": instr.plunger_positions.bottom,
|
|
292
|
+
"blow_out": instr.plunger_positions.blow_out,
|
|
293
|
+
"drop_tip": instr.plunger_positions.drop_tip,
|
|
294
|
+
}
|
|
295
|
+
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
|
|
296
|
+
result["available_sensors"] = instr.config.available_sensors
|
|
297
|
+
return cast(PipetteDict, result)
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def attached_instruments(self) -> Dict[OT3Mount, PipetteDict]:
|
|
301
|
+
return self.get_attached_instruments()
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def hardware_instruments(self) -> InstrumentsByMount[OT3Mount]:
|
|
305
|
+
"""Do not write new code that uses this."""
|
|
306
|
+
return self._attached_instruments
|
|
307
|
+
|
|
308
|
+
def set_current_tiprack_diameter(
|
|
309
|
+
self, mount: OT3Mount, tiprack_diameter: float
|
|
310
|
+
) -> None:
|
|
311
|
+
instr = self.get_pipette(mount)
|
|
312
|
+
self._ihp_log.info(
|
|
313
|
+
"Updating tip rack diameter on pipette mount: "
|
|
314
|
+
f"{mount}, tip diameter: {tiprack_diameter} mm"
|
|
315
|
+
)
|
|
316
|
+
instr.current_tiprack_diameter = tiprack_diameter
|
|
317
|
+
|
|
318
|
+
def set_working_volume(self, mount: OT3Mount, tip_volume: float) -> None:
|
|
319
|
+
instr = self.get_pipette(mount)
|
|
320
|
+
if not instr:
|
|
321
|
+
raise top_types.PipetteNotAttachedError(
|
|
322
|
+
"No pipette attached to {} mount".format(mount.name)
|
|
323
|
+
)
|
|
324
|
+
self._ihp_log.info(
|
|
325
|
+
"Updating working volume on pipette mount:"
|
|
326
|
+
f"{mount}, tip volume: {tip_volume} ul"
|
|
327
|
+
)
|
|
328
|
+
instr.working_volume = tip_volume
|
|
329
|
+
|
|
330
|
+
def calibrate_plunger(
|
|
331
|
+
self,
|
|
332
|
+
mount: OT3Mount,
|
|
333
|
+
top: Optional[float] = None,
|
|
334
|
+
bottom: Optional[float] = None,
|
|
335
|
+
blow_out: Optional[float] = None,
|
|
336
|
+
drop_tip: Optional[float] = None,
|
|
337
|
+
) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Set calibration values for the pipette plunger.
|
|
340
|
+
This can be called multiple times as the user sets each value,
|
|
341
|
+
or you can set them all at once.
|
|
342
|
+
:param top: Touching but not engaging the plunger.
|
|
343
|
+
:param bottom: Must be above the pipette's physical hard-stop, while
|
|
344
|
+
still leaving enough room for 'blow_out'
|
|
345
|
+
:param blow_out: Plunger is pushed down enough to expel all liquids.
|
|
346
|
+
:param drop_tip: Position that causes the tip to be released from the
|
|
347
|
+
pipette
|
|
348
|
+
"""
|
|
349
|
+
instr = self.get_pipette(mount)
|
|
350
|
+
pos_dict: Dict[str, float] = {
|
|
351
|
+
"top": instr.plunger_positions.top,
|
|
352
|
+
"bottom": instr.plunger_positions.bottom,
|
|
353
|
+
"blow_out": instr.plunger_positions.blow_out,
|
|
354
|
+
"drop_tip": instr.plunger_positions.drop_tip,
|
|
355
|
+
}
|
|
356
|
+
if top is not None:
|
|
357
|
+
pos_dict["top"] = top
|
|
358
|
+
if bottom is not None:
|
|
359
|
+
pos_dict["bottom"] = bottom
|
|
360
|
+
if blow_out is not None:
|
|
361
|
+
pos_dict["blow_out"] = blow_out
|
|
362
|
+
if drop_tip is not None:
|
|
363
|
+
pos_dict["drop_tip"] = drop_tip
|
|
364
|
+
instr.update_config_item(pos_dict)
|
|
365
|
+
|
|
366
|
+
def set_flow_rate(
|
|
367
|
+
self,
|
|
368
|
+
mount: OT3Mount,
|
|
369
|
+
aspirate: Optional[float] = None,
|
|
370
|
+
dispense: Optional[float] = None,
|
|
371
|
+
blow_out: Optional[float] = None,
|
|
372
|
+
) -> None:
|
|
373
|
+
this_pipette = self.get_pipette(mount)
|
|
374
|
+
if aspirate:
|
|
375
|
+
this_pipette.aspirate_flow_rate = aspirate
|
|
376
|
+
if dispense:
|
|
377
|
+
this_pipette.dispense_flow_rate = dispense
|
|
378
|
+
if blow_out:
|
|
379
|
+
this_pipette.blow_out_flow_rate = blow_out
|
|
380
|
+
|
|
381
|
+
def set_pipette_speed(
|
|
382
|
+
self,
|
|
383
|
+
mount: OT3Mount,
|
|
384
|
+
aspirate: Optional[float] = None,
|
|
385
|
+
dispense: Optional[float] = None,
|
|
386
|
+
blow_out: Optional[float] = None,
|
|
387
|
+
) -> None:
|
|
388
|
+
this_pipette = self.get_pipette(mount)
|
|
389
|
+
if aspirate:
|
|
390
|
+
this_pipette.aspirate_flow_rate = self.plunger_flowrate(
|
|
391
|
+
this_pipette, aspirate, "aspirate"
|
|
392
|
+
)
|
|
393
|
+
if dispense:
|
|
394
|
+
this_pipette.dispense_flow_rate = self.plunger_flowrate(
|
|
395
|
+
this_pipette, dispense, "dispense"
|
|
396
|
+
)
|
|
397
|
+
if blow_out:
|
|
398
|
+
this_pipette.blow_out_flow_rate = self.plunger_flowrate(
|
|
399
|
+
this_pipette, blow_out, "blowout"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
def instrument_max_height(
|
|
403
|
+
self,
|
|
404
|
+
mount: OT3Mount,
|
|
405
|
+
retract_distance: float,
|
|
406
|
+
critical_point: Optional[CriticalPoint],
|
|
407
|
+
) -> float:
|
|
408
|
+
"""Return max achievable height of the attached instrument
|
|
409
|
+
based on the current critical point
|
|
410
|
+
"""
|
|
411
|
+
cp = self.critical_point_for(mount, critical_point)
|
|
412
|
+
|
|
413
|
+
max_height = HOME_POSITION - retract_distance + cp.z
|
|
414
|
+
|
|
415
|
+
return max_height
|
|
416
|
+
|
|
417
|
+
async def reset(self) -> None:
|
|
418
|
+
self._attached_instruments = {
|
|
419
|
+
k: None for k in self._attached_instruments.keys()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async def update_nozzle_configuration(
|
|
423
|
+
self,
|
|
424
|
+
mount: MountType,
|
|
425
|
+
back_left_nozzle: str,
|
|
426
|
+
front_right_nozzle: str,
|
|
427
|
+
starting_nozzle: Optional[str] = None,
|
|
428
|
+
) -> None:
|
|
429
|
+
instr = self._attached_instruments[OT3Mount.from_mount(mount)]
|
|
430
|
+
if instr:
|
|
431
|
+
instr.update_nozzle_configuration(
|
|
432
|
+
back_left_nozzle, front_right_nozzle, starting_nozzle
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
async def reset_nozzle_configuration(self, mount: OT3Mount) -> None:
|
|
436
|
+
instr = self._attached_instruments[OT3Mount.from_mount(mount)]
|
|
437
|
+
if instr:
|
|
438
|
+
instr.reset_nozzle_configuration()
|
|
439
|
+
|
|
440
|
+
def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
|
|
441
|
+
instr = self._attached_instruments[mount]
|
|
442
|
+
attached = self.attached_instruments
|
|
443
|
+
instr_dict = attached[mount]
|
|
444
|
+
if instr and not instr.has_tip:
|
|
445
|
+
instr.add_tip(tip_length=tip_length)
|
|
446
|
+
# TODO (spp, 2021-08-27): These items are being updated in a local copy
|
|
447
|
+
# of the PipetteDict, which gets thrown away. Fix this.
|
|
448
|
+
instr_dict["has_tip"] = True
|
|
449
|
+
instr_dict["tip_length"] = tip_length
|
|
450
|
+
else:
|
|
451
|
+
self._ihp_log.warning(
|
|
452
|
+
"attach tip called while tip already attached to {instr}"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
def cache_tip(self, mount: OT3Mount, tip_length: float) -> None:
|
|
456
|
+
instrument = self.get_pipette(mount)
|
|
457
|
+
if instrument.has_tip:
|
|
458
|
+
# instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip.
|
|
459
|
+
instrument.remove_tip()
|
|
460
|
+
instrument.add_tip(tip_length=tip_length)
|
|
461
|
+
instrument.set_current_volume(0)
|
|
462
|
+
|
|
463
|
+
def remove_tip(self, mount: OT3Mount) -> None:
|
|
464
|
+
instr = self._attached_instruments[mount]
|
|
465
|
+
attached = self.attached_instruments
|
|
466
|
+
instr_dict = attached[mount]
|
|
467
|
+
if instr and instr.has_tip:
|
|
468
|
+
instr.remove_tip()
|
|
469
|
+
# TODO (spp, 2021-08-27): These items are being updated in a local copy
|
|
470
|
+
# of the PipetteDict, which gets thrown away. Fix this.
|
|
471
|
+
instr_dict["has_tip"] = False
|
|
472
|
+
instr_dict["tip_length"] = 0.0
|
|
473
|
+
else:
|
|
474
|
+
self._ihp_log.warning("detach tip called with no tip")
|
|
475
|
+
|
|
476
|
+
def critical_point_for(
|
|
477
|
+
self, mount: OT3Mount, cp_override: Optional[CriticalPoint] = None
|
|
478
|
+
) -> top_types.Point:
|
|
479
|
+
"""Return the current critical point of the specified mount.
|
|
480
|
+
|
|
481
|
+
The mount's critical point is the position of the mount itself, if no
|
|
482
|
+
pipette is attached, or the pipette's critical point (which depends on
|
|
483
|
+
tip status).
|
|
484
|
+
|
|
485
|
+
If `cp_override` is specified, and that critical point actually exists,
|
|
486
|
+
it will be used instead. Invalid `cp_override`s are ignored.
|
|
487
|
+
"""
|
|
488
|
+
pip = self._attached_instruments[OT3Mount.from_mount(mount)]
|
|
489
|
+
if pip is not None and cp_override != CriticalPoint.MOUNT:
|
|
490
|
+
return pip.critical_point(cp_override)
|
|
491
|
+
else:
|
|
492
|
+
return top_types.Point(0, 0, 0)
|
|
493
|
+
|
|
494
|
+
def ready_for_tip_action(
|
|
495
|
+
self, target: Pipette, action: HardwareAction, mount: OT3Mount
|
|
496
|
+
) -> None:
|
|
497
|
+
if not target.has_tip:
|
|
498
|
+
raise UnexpectedTipRemovalError(str(action), target.name, mount.name)
|
|
499
|
+
if (
|
|
500
|
+
action == HardwareAction.ASPIRATE
|
|
501
|
+
and target.current_volume == 0
|
|
502
|
+
and not target.ready_to_aspirate
|
|
503
|
+
):
|
|
504
|
+
raise RuntimeError("Pipette not ready to aspirate")
|
|
505
|
+
self._ihp_log.debug(f"{action} on {target.name}")
|
|
506
|
+
|
|
507
|
+
def plunger_position(
|
|
508
|
+
self,
|
|
509
|
+
instr: Pipette,
|
|
510
|
+
ul: float,
|
|
511
|
+
action: "UlPerMmAction",
|
|
512
|
+
correction_volume: float = 0.0,
|
|
513
|
+
) -> float:
|
|
514
|
+
if ul == 0:
|
|
515
|
+
position = instr.plunger_positions.bottom
|
|
516
|
+
else:
|
|
517
|
+
multiplier = 1.0 + (correction_volume / ul)
|
|
518
|
+
mm_dist_from_bottom = ul / instr.ul_per_mm(ul, action)
|
|
519
|
+
mm_dist_from_bottom_corrected = mm_dist_from_bottom * multiplier
|
|
520
|
+
position = instr.plunger_positions.bottom - mm_dist_from_bottom_corrected
|
|
521
|
+
return round(position, 6)
|
|
522
|
+
|
|
523
|
+
def plunger_speed(
|
|
524
|
+
self, instr: Pipette, ul_per_s: float, action: "UlPerMmAction"
|
|
525
|
+
) -> float:
|
|
526
|
+
mm_per_s = ul_per_s / instr.ul_per_mm(instr.working_volume, action)
|
|
527
|
+
return round(mm_per_s, 6)
|
|
528
|
+
|
|
529
|
+
def plunger_flowrate(
|
|
530
|
+
self, instr: Pipette, mm_per_s: float, action: "UlPerMmAction"
|
|
531
|
+
) -> float:
|
|
532
|
+
ul_per_s = mm_per_s * instr.ul_per_mm(instr.working_volume, action)
|
|
533
|
+
return round(ul_per_s, 6)
|
|
534
|
+
|
|
535
|
+
def plunger_acceleration(self, instr: Pipette, ul_per_s_per_s: float) -> float:
|
|
536
|
+
# using nominal ul/mm, to make sure accelerations are always the same
|
|
537
|
+
# regardless of volume being aspirated/dispensed
|
|
538
|
+
mm_per_s_per_s = ul_per_s_per_s / instr.config.shaft_ul_per_mm
|
|
539
|
+
return round(mm_per_s_per_s, 6)
|
|
540
|
+
|
|
541
|
+
def plan_check_aspirate(
|
|
542
|
+
self,
|
|
543
|
+
mount: OT3Mount,
|
|
544
|
+
volume: Optional[float],
|
|
545
|
+
rate: float,
|
|
546
|
+
correction_volume: float = 0.0,
|
|
547
|
+
) -> Optional[LiquidActionSpec]:
|
|
548
|
+
"""Check preconditions for aspirate, parse args, and calculate positions.
|
|
549
|
+
|
|
550
|
+
While the mechanics of issuing an aspirate move itself are left to child
|
|
551
|
+
classes, determining things like aspiration volume from the allowed argument
|
|
552
|
+
types is invariant between machines, and this method gathers that functionality.
|
|
553
|
+
|
|
554
|
+
Coalesce
|
|
555
|
+
- Optional volumes
|
|
556
|
+
|
|
557
|
+
Check
|
|
558
|
+
- Aspiration volumes compared to max and remaining
|
|
559
|
+
|
|
560
|
+
Calculate
|
|
561
|
+
- Plunger distances (possibly calling an overridden plunger_volume)
|
|
562
|
+
"""
|
|
563
|
+
instrument = self.get_pipette(mount)
|
|
564
|
+
self.ready_for_tip_action(instrument, HardwareAction.ASPIRATE, mount)
|
|
565
|
+
if volume is None:
|
|
566
|
+
self._ihp_log.debug(
|
|
567
|
+
"No aspirate volume defined. Aspirating up to "
|
|
568
|
+
"max_volume for the pipette"
|
|
569
|
+
)
|
|
570
|
+
asp_vol = instrument.available_volume
|
|
571
|
+
else:
|
|
572
|
+
asp_vol = volume
|
|
573
|
+
|
|
574
|
+
if asp_vol == 0:
|
|
575
|
+
return None
|
|
576
|
+
|
|
577
|
+
assert instrument.ok_to_add_volume(
|
|
578
|
+
asp_vol
|
|
579
|
+
), "Cannot aspirate more than pipette max volume"
|
|
580
|
+
|
|
581
|
+
dist = self.plunger_position(
|
|
582
|
+
instr=instrument,
|
|
583
|
+
ul=instrument.current_volume + asp_vol,
|
|
584
|
+
action="aspirate",
|
|
585
|
+
correction_volume=correction_volume,
|
|
586
|
+
)
|
|
587
|
+
speed = self.plunger_speed(
|
|
588
|
+
instrument, instrument.aspirate_flow_rate * rate, "aspirate"
|
|
589
|
+
)
|
|
590
|
+
acceleration = self.plunger_acceleration(
|
|
591
|
+
instrument, instrument.flow_acceleration
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
return LiquidActionSpec(
|
|
595
|
+
axis=Axis.of_main_tool_actuator(mount),
|
|
596
|
+
volume=asp_vol,
|
|
597
|
+
plunger_distance=dist,
|
|
598
|
+
speed=speed,
|
|
599
|
+
acceleration=acceleration,
|
|
600
|
+
instr=instrument,
|
|
601
|
+
current=instrument.plunger_motor_current.run,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
def plan_check_dispense(
|
|
605
|
+
self,
|
|
606
|
+
mount: OT3Mount,
|
|
607
|
+
volume: Optional[float],
|
|
608
|
+
rate: float,
|
|
609
|
+
push_out: Optional[float],
|
|
610
|
+
is_full_dispense: bool,
|
|
611
|
+
correction_volume: float = 0.0,
|
|
612
|
+
) -> Optional[LiquidActionSpec]:
|
|
613
|
+
"""Check preconditions for dispense, parse args, and calculate positions.
|
|
614
|
+
|
|
615
|
+
While the mechanics of issuing a dispense move itself are left to child
|
|
616
|
+
classes, determining things like dispense volume from the allowed argument
|
|
617
|
+
types is invariant between machines, and this method gathers that functionality.
|
|
618
|
+
|
|
619
|
+
Coalesce
|
|
620
|
+
- Optional volumes
|
|
621
|
+
|
|
622
|
+
Check
|
|
623
|
+
- Dispense volumes compared to max and remaining
|
|
624
|
+
|
|
625
|
+
Calculate
|
|
626
|
+
- Plunger distances (possibly calling an overridden plunger_volume)
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
instrument = self.get_pipette(mount)
|
|
630
|
+
self.ready_for_tip_action(instrument, HardwareAction.DISPENSE, mount)
|
|
631
|
+
|
|
632
|
+
if volume is None:
|
|
633
|
+
disp_vol = instrument.current_volume
|
|
634
|
+
self._ihp_log.debug(
|
|
635
|
+
"No dispense volume specified. Dispensing all "
|
|
636
|
+
"remaining liquid ({}uL) from pipette".format(disp_vol)
|
|
637
|
+
)
|
|
638
|
+
else:
|
|
639
|
+
disp_vol = volume
|
|
640
|
+
|
|
641
|
+
# Ensure we don't dispense more than the current volume.
|
|
642
|
+
#
|
|
643
|
+
# This clamping is inconsistent with plan_check_aspirate(), which asserts
|
|
644
|
+
# that its input is in bounds instead of clamping it. This is to match a quirk
|
|
645
|
+
# of the OT-2 version of this class. Protocol Engine does its own clamping,
|
|
646
|
+
# so we don't expect this to trigger in practice.
|
|
647
|
+
disp_vol = min(instrument.current_volume, disp_vol)
|
|
648
|
+
|
|
649
|
+
# TODO (Ryan): Remove this check in the future.
|
|
650
|
+
# we moved this logic up to protocol_engine but replacing with this check to make sure
|
|
651
|
+
# we don't accidentally call this incorrectly from somewhere else.
|
|
652
|
+
if not is_full_dispense and numpy.isclose(
|
|
653
|
+
instrument.current_volume - disp_vol, 0
|
|
654
|
+
):
|
|
655
|
+
raise CommandPreconditionViolated(
|
|
656
|
+
message="Command created a full-dispense without the full dispense argument",
|
|
657
|
+
detail={
|
|
658
|
+
"command": "dispense",
|
|
659
|
+
"current-volume": str(instrument.current_volume),
|
|
660
|
+
"dispense-volume": str(disp_vol),
|
|
661
|
+
},
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
if disp_vol == 0:
|
|
665
|
+
return None
|
|
666
|
+
|
|
667
|
+
if is_full_dispense:
|
|
668
|
+
disp_vol = instrument.current_volume
|
|
669
|
+
if push_out is None:
|
|
670
|
+
push_out_ul = instrument.push_out_volume
|
|
671
|
+
else:
|
|
672
|
+
push_out_ul = push_out
|
|
673
|
+
else:
|
|
674
|
+
if push_out is not None and push_out != 0:
|
|
675
|
+
raise CommandPreconditionViolated(
|
|
676
|
+
message="Cannot push_out on a dispense that does not leave the pipette empty",
|
|
677
|
+
detail={
|
|
678
|
+
"command": "dispense",
|
|
679
|
+
"remaining-volume": str(instrument.current_volume - disp_vol),
|
|
680
|
+
},
|
|
681
|
+
)
|
|
682
|
+
push_out_ul = 0
|
|
683
|
+
|
|
684
|
+
push_out_dist_mm = push_out_ul / instrument.ul_per_mm(push_out_ul, "blowout")
|
|
685
|
+
|
|
686
|
+
if not instrument.ok_to_push_out(push_out_dist_mm):
|
|
687
|
+
raise CommandParameterLimitViolated(
|
|
688
|
+
command_name="dispense",
|
|
689
|
+
parameter_name="push_out",
|
|
690
|
+
limit_statement="less than pipette max blowout volume",
|
|
691
|
+
actual_value=str(push_out_ul),
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
dist = self.plunger_position(
|
|
695
|
+
instr=instrument,
|
|
696
|
+
ul=instrument.current_volume - disp_vol,
|
|
697
|
+
action="dispense",
|
|
698
|
+
correction_volume=correction_volume,
|
|
699
|
+
)
|
|
700
|
+
speed = self.plunger_speed(
|
|
701
|
+
instrument, instrument.dispense_flow_rate * rate, "dispense"
|
|
702
|
+
)
|
|
703
|
+
acceleration = self.plunger_acceleration(
|
|
704
|
+
instrument, instrument.flow_acceleration
|
|
705
|
+
)
|
|
706
|
+
return LiquidActionSpec(
|
|
707
|
+
axis=Axis.of_main_tool_actuator(mount),
|
|
708
|
+
volume=disp_vol,
|
|
709
|
+
plunger_distance=dist + push_out_dist_mm,
|
|
710
|
+
speed=speed,
|
|
711
|
+
acceleration=acceleration,
|
|
712
|
+
instr=instrument,
|
|
713
|
+
current=instrument.plunger_motor_current.run,
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
def plan_check_blow_out(
|
|
717
|
+
self, mount: OT3Mount, volume: Optional[float] = None
|
|
718
|
+
) -> LiquidActionSpec:
|
|
719
|
+
"""Check preconditions and calculate values for blowout."""
|
|
720
|
+
instrument = self.get_pipette(mount)
|
|
721
|
+
speed = self.plunger_speed(instrument, instrument.blow_out_flow_rate, "blowout")
|
|
722
|
+
acceleration = self.plunger_acceleration(
|
|
723
|
+
instrument, instrument.flow_acceleration
|
|
724
|
+
)
|
|
725
|
+
max_distance = (
|
|
726
|
+
instrument.plunger_positions.blow_out - instrument.plunger_positions.bottom
|
|
727
|
+
)
|
|
728
|
+
if volume is None:
|
|
729
|
+
distance_mm = max_distance
|
|
730
|
+
else:
|
|
731
|
+
ul = volume
|
|
732
|
+
distance_mm = ul / instrument.ul_per_mm(ul, "blowout")
|
|
733
|
+
if distance_mm > max_distance:
|
|
734
|
+
raise CommandParameterLimitViolated(
|
|
735
|
+
command_name="blow_out",
|
|
736
|
+
parameter_name="volume",
|
|
737
|
+
limit_statement="less than the available distance for the plunger to move",
|
|
738
|
+
actual_value=str(volume),
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
return LiquidActionSpec(
|
|
742
|
+
axis=Axis.of_main_tool_actuator(mount),
|
|
743
|
+
volume=0,
|
|
744
|
+
plunger_distance=distance_mm,
|
|
745
|
+
speed=speed,
|
|
746
|
+
acceleration=acceleration,
|
|
747
|
+
instr=instrument,
|
|
748
|
+
current=instrument.plunger_motor_current.run,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
@staticmethod
|
|
752
|
+
def _build_tip_motor_moves(
|
|
753
|
+
prep_move_dist: float,
|
|
754
|
+
clamp_move_dist: float,
|
|
755
|
+
prep_move_speed: float,
|
|
756
|
+
clamp_move_speed: float,
|
|
757
|
+
tip_motor_current: float,
|
|
758
|
+
plunger_current: float,
|
|
759
|
+
) -> List[TipActionMoveSpec]:
|
|
760
|
+
return [
|
|
761
|
+
TipActionMoveSpec(
|
|
762
|
+
distance=prep_move_dist,
|
|
763
|
+
speed=prep_move_speed,
|
|
764
|
+
currents={
|
|
765
|
+
Axis.P_L: plunger_current,
|
|
766
|
+
Axis.Q: tip_motor_current,
|
|
767
|
+
},
|
|
768
|
+
),
|
|
769
|
+
TipActionMoveSpec(
|
|
770
|
+
distance=prep_move_dist + clamp_move_dist,
|
|
771
|
+
speed=clamp_move_speed,
|
|
772
|
+
currents={
|
|
773
|
+
Axis.P_L: plunger_current,
|
|
774
|
+
Axis.Q: tip_motor_current,
|
|
775
|
+
},
|
|
776
|
+
),
|
|
777
|
+
]
|
|
778
|
+
|
|
779
|
+
@staticmethod
|
|
780
|
+
def _build_pickup_shakes(
|
|
781
|
+
instrument: Pipette,
|
|
782
|
+
) -> List[Tuple[top_types.Point, Optional[float]]]:
|
|
783
|
+
def build_one_shake() -> List[Tuple[top_types.Point, Optional[float]]]:
|
|
784
|
+
shake_dist = float(SHAKE_OFF_TIPS_PICKUP_DISTANCE)
|
|
785
|
+
shake_speed = float(SHAKE_OFF_TIPS_SPEED)
|
|
786
|
+
return [
|
|
787
|
+
(top_types.Point(-shake_dist, 0, 0), shake_speed), # left
|
|
788
|
+
(top_types.Point(2 * shake_dist, 0, 0), shake_speed), # right
|
|
789
|
+
(top_types.Point(-shake_dist, 0, 0), shake_speed), # center
|
|
790
|
+
(top_types.Point(0, -shake_dist, 0), shake_speed), # front
|
|
791
|
+
(top_types.Point(0, 2 * shake_dist, 0), shake_speed), # back
|
|
792
|
+
(top_types.Point(0, -shake_dist, 0), shake_speed), # center
|
|
793
|
+
(top_types.Point(0, 0, DROP_TIP_RELEASE_DISTANCE), None), # up
|
|
794
|
+
]
|
|
795
|
+
|
|
796
|
+
return []
|
|
797
|
+
|
|
798
|
+
def plan_ht_pick_up_tip(self, tip_count: int) -> TipActionSpec:
|
|
799
|
+
# Prechecks: ready for pickup tip and press/increment are valid
|
|
800
|
+
mount = OT3Mount.LEFT
|
|
801
|
+
instrument = self.get_pipette(mount)
|
|
802
|
+
if instrument.has_tip:
|
|
803
|
+
raise UnexpectedTipAttachError("pick_up_tip", instrument.name, mount.name)
|
|
804
|
+
self._ihp_log.debug(f"Picking up tip on {mount.name}")
|
|
805
|
+
|
|
806
|
+
pick_up_config = instrument.get_pick_up_configuration()
|
|
807
|
+
if not isinstance(pick_up_config, CamActionPickUpTipConfiguration):
|
|
808
|
+
raise CommandPreconditionViolated(
|
|
809
|
+
f"Low-throughput pick up tip got wrong config for {instrument.name} on {mount.name}"
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
tip_motor_moves = self._build_tip_motor_moves(
|
|
813
|
+
prep_move_dist=pick_up_config.prep_move_distance,
|
|
814
|
+
clamp_move_dist=instrument.get_pick_up_distance_by_configuration(
|
|
815
|
+
pick_up_config
|
|
816
|
+
),
|
|
817
|
+
prep_move_speed=pick_up_config.prep_move_speed,
|
|
818
|
+
clamp_move_speed=instrument.get_pick_up_speed_by_configuration(
|
|
819
|
+
pick_up_config
|
|
820
|
+
),
|
|
821
|
+
plunger_current=instrument.plunger_motor_current.run,
|
|
822
|
+
tip_motor_current=instrument.get_pick_up_current_by_configuration(
|
|
823
|
+
pick_up_config
|
|
824
|
+
),
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
return TipActionSpec(
|
|
828
|
+
tip_action_moves=tip_motor_moves,
|
|
829
|
+
shake_off_moves=[],
|
|
830
|
+
z_distance_to_tiprack=(-1 * pick_up_config.connect_tiprack_distance_mm),
|
|
831
|
+
ending_z_retract_distance=instrument.config.end_tip_action_retract_distance_mm,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
def plan_lt_pick_up_tip(
|
|
835
|
+
self,
|
|
836
|
+
mount: OT3Mount,
|
|
837
|
+
tip_count: int,
|
|
838
|
+
presses: Optional[int],
|
|
839
|
+
increment: Optional[float],
|
|
840
|
+
) -> TipActionSpec:
|
|
841
|
+
# Prechecks: ready for pickup tip and press/increment are valid
|
|
842
|
+
instrument = self.get_pipette(mount)
|
|
843
|
+
if instrument.has_tip:
|
|
844
|
+
raise UnexpectedTipAttachError("pick_up_tip", instrument.name, mount.name)
|
|
845
|
+
self._ihp_log.debug(f"Picking up tip on {mount.name}")
|
|
846
|
+
|
|
847
|
+
pick_up_config = instrument.get_pick_up_configuration()
|
|
848
|
+
if not isinstance(pick_up_config, PressFitPickUpTipConfiguration):
|
|
849
|
+
raise CommandPreconditionViolated(
|
|
850
|
+
f"Low-throughput pick up tip got wrong config for {instrument.name} on {mount.name}"
|
|
851
|
+
)
|
|
852
|
+
if presses is None or presses < 0:
|
|
853
|
+
checked_presses = pick_up_config.presses
|
|
854
|
+
else:
|
|
855
|
+
checked_presses = presses
|
|
856
|
+
|
|
857
|
+
if not increment or increment < 0:
|
|
858
|
+
check_incr = pick_up_config.increment
|
|
859
|
+
else:
|
|
860
|
+
check_incr = increment
|
|
861
|
+
|
|
862
|
+
pick_up_speed = instrument.get_pick_up_speed_by_configuration(pick_up_config)
|
|
863
|
+
|
|
864
|
+
def build_presses() -> List[TipActionMoveSpec]:
|
|
865
|
+
# Press the nozzle into the tip <presses> number of times,
|
|
866
|
+
# moving further by <increment> mm after each press
|
|
867
|
+
press_moves = []
|
|
868
|
+
for i in range(checked_presses):
|
|
869
|
+
# move nozzle down into the tip
|
|
870
|
+
press_dist = (
|
|
871
|
+
-1.0
|
|
872
|
+
* instrument.get_pick_up_distance_by_configuration(pick_up_config)
|
|
873
|
+
+ -1.0 * check_incr * i
|
|
874
|
+
)
|
|
875
|
+
press_moves.append(
|
|
876
|
+
TipActionMoveSpec(
|
|
877
|
+
distance=press_dist,
|
|
878
|
+
speed=pick_up_speed,
|
|
879
|
+
currents={
|
|
880
|
+
Axis.by_mount(
|
|
881
|
+
mount
|
|
882
|
+
): instrument.get_pick_up_current_by_configuration(
|
|
883
|
+
pick_up_config
|
|
884
|
+
)
|
|
885
|
+
},
|
|
886
|
+
)
|
|
887
|
+
)
|
|
888
|
+
# move nozzle back up
|
|
889
|
+
backup_dist = -press_dist
|
|
890
|
+
press_moves.append(
|
|
891
|
+
TipActionMoveSpec(
|
|
892
|
+
distance=backup_dist,
|
|
893
|
+
speed=pick_up_speed,
|
|
894
|
+
currents=None,
|
|
895
|
+
)
|
|
896
|
+
)
|
|
897
|
+
return press_moves
|
|
898
|
+
|
|
899
|
+
return TipActionSpec(
|
|
900
|
+
tip_action_moves=build_presses(),
|
|
901
|
+
shake_off_moves=self._build_pickup_shakes(instrument),
|
|
902
|
+
ending_z_retract_distance=instrument.config.end_tip_action_retract_distance_mm,
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
@staticmethod
|
|
906
|
+
def _shake_off_tips_drop(
|
|
907
|
+
tiprack_diameter: float,
|
|
908
|
+
) -> List[Tuple[top_types.Point, Optional[float]]]:
|
|
909
|
+
# tips don't always fall off, especially if resting against
|
|
910
|
+
# tiprack or other tips below it. To ensure the tip has fallen
|
|
911
|
+
# first, shake the pipette to dislodge partially-sealed tips,
|
|
912
|
+
# then second, raise the pipette so loosened tips have room to fall
|
|
913
|
+
shake_off_dist = SHAKE_OFF_TIPS_DROP_DISTANCE
|
|
914
|
+
if tiprack_diameter > 0.0:
|
|
915
|
+
shake_off_dist = min(shake_off_dist, tiprack_diameter / 4)
|
|
916
|
+
shake_off_dist = max(shake_off_dist, 1.0)
|
|
917
|
+
speed = SHAKE_OFF_TIPS_SPEED
|
|
918
|
+
return [
|
|
919
|
+
(top_types.Point(-shake_off_dist, 0, 0), speed), # left
|
|
920
|
+
(top_types.Point(2 * shake_off_dist, 0, 0), speed), # right
|
|
921
|
+
(top_types.Point(-shake_off_dist, 0, 0), speed), # center
|
|
922
|
+
(top_types.Point(0, 0, DROP_TIP_RELEASE_DISTANCE), None), # top
|
|
923
|
+
]
|
|
924
|
+
|
|
925
|
+
def plan_lt_drop_tip(
|
|
926
|
+
self,
|
|
927
|
+
mount: OT3Mount,
|
|
928
|
+
scrape_tips: TipScrapeType = TipScrapeType.NONE,
|
|
929
|
+
) -> TipActionSpec:
|
|
930
|
+
instrument = self.get_pipette(mount)
|
|
931
|
+
config = instrument.drop_configurations.plunger_eject
|
|
932
|
+
if not config:
|
|
933
|
+
raise CommandPreconditionViolated(
|
|
934
|
+
f"No plunger-eject drop tip configurations for {instrument.name} on {mount.name}"
|
|
935
|
+
)
|
|
936
|
+
scrape_move: Optional[TipActionMoveSpec] = None
|
|
937
|
+
match scrape_tips:
|
|
938
|
+
case TipScrapeType.LEFT_ONE_COL:
|
|
939
|
+
scrape_move = TipActionMoveSpec(
|
|
940
|
+
distance=-11, currents=None, speed=None, scrape_axis=Axis.X
|
|
941
|
+
)
|
|
942
|
+
case TipScrapeType.RIGHT_ONE_COL:
|
|
943
|
+
scrape_move = TipActionMoveSpec(
|
|
944
|
+
distance=11, currents=None, speed=None, scrape_axis=Axis.X
|
|
945
|
+
)
|
|
946
|
+
case TipScrapeType.NONE:
|
|
947
|
+
scrape_move = None
|
|
948
|
+
case _:
|
|
949
|
+
scrape_move = None
|
|
950
|
+
drop_seq = [
|
|
951
|
+
TipActionMoveSpec(
|
|
952
|
+
distance=instrument.plunger_positions.drop_tip,
|
|
953
|
+
speed=config.speed,
|
|
954
|
+
currents={
|
|
955
|
+
Axis.of_main_tool_actuator(mount): config.current,
|
|
956
|
+
},
|
|
957
|
+
),
|
|
958
|
+
TipActionMoveSpec(
|
|
959
|
+
distance=instrument.plunger_positions.bottom,
|
|
960
|
+
speed=None,
|
|
961
|
+
currents={
|
|
962
|
+
Axis.of_main_tool_actuator(
|
|
963
|
+
mount
|
|
964
|
+
): instrument.plunger_motor_current.run,
|
|
965
|
+
},
|
|
966
|
+
),
|
|
967
|
+
]
|
|
968
|
+
if scrape_move:
|
|
969
|
+
# Add the scrape move before the plunger moves back up
|
|
970
|
+
drop_seq.insert(1, scrape_move)
|
|
971
|
+
|
|
972
|
+
return TipActionSpec(
|
|
973
|
+
tip_action_moves=drop_seq,
|
|
974
|
+
shake_off_moves=[],
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def plan_ht_drop_tip(self) -> TipActionSpec:
|
|
978
|
+
mount = OT3Mount.LEFT
|
|
979
|
+
instrument = self.get_pipette(mount)
|
|
980
|
+
config = instrument.drop_configurations.cam_action
|
|
981
|
+
if not config:
|
|
982
|
+
raise CommandPreconditionViolated(
|
|
983
|
+
f"No cam-action drop tip configurations for {instrument.name} on {mount.name}"
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
drop_seq = self._build_tip_motor_moves(
|
|
987
|
+
prep_move_dist=config.prep_move_distance,
|
|
988
|
+
clamp_move_dist=config.distance,
|
|
989
|
+
prep_move_speed=config.prep_move_speed,
|
|
990
|
+
clamp_move_speed=config.speed,
|
|
991
|
+
plunger_current=instrument.plunger_motor_current.run,
|
|
992
|
+
tip_motor_current=config.current,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
return TipActionSpec(
|
|
996
|
+
tip_action_moves=drop_seq,
|
|
997
|
+
shake_off_moves=[],
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
def has_pipette(self, mount: OT3Mount) -> bool:
|
|
1001
|
+
return bool(self._attached_instruments[mount])
|
|
1002
|
+
|
|
1003
|
+
def get_pipette(self, mount: OT3Mount) -> Pipette:
|
|
1004
|
+
pip = self._attached_instruments[mount]
|
|
1005
|
+
if not pip:
|
|
1006
|
+
raise top_types.PipetteNotAttachedError(
|
|
1007
|
+
f"No pipette attached to {mount.name} mount"
|
|
1008
|
+
)
|
|
1009
|
+
return pip
|
|
1010
|
+
|
|
1011
|
+
async def set_liquid_class(self, mount: OT3Mount, liquid_class: str) -> None:
|
|
1012
|
+
pip = self.get_pipette(mount)
|
|
1013
|
+
pip.set_liquid_class_by_name(liquid_class)
|
|
1014
|
+
|
|
1015
|
+
async def configure_for_volume(self, mount: OT3Mount, volume: float) -> None:
|
|
1016
|
+
pip = self.get_pipette(mount)
|
|
1017
|
+
if pip.current_volume > 0:
|
|
1018
|
+
# Switching liquid classes can't happen when there's already liquid
|
|
1019
|
+
return
|
|
1020
|
+
new_class_name = liquid_class_for_volume_between_default_and_defaultlowvolume(
|
|
1021
|
+
volume,
|
|
1022
|
+
pip.liquid_class_name,
|
|
1023
|
+
pip.config.liquid_properties,
|
|
1024
|
+
)
|
|
1025
|
+
pip.set_liquid_class_by_name(new_class_name.name)
|
|
1026
|
+
|
|
1027
|
+
def get_tip_sensor_count(self, mount: OT3Mount) -> int:
|
|
1028
|
+
if not self.has_pipette(mount):
|
|
1029
|
+
return 0
|
|
1030
|
+
return self.get_pipette(mount).tip_presence_responses
|