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,1347 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
from functools import partial
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
from typing import (
|
|
9
|
+
Callable,
|
|
10
|
+
Dict,
|
|
11
|
+
Union,
|
|
12
|
+
List,
|
|
13
|
+
Optional,
|
|
14
|
+
Tuple,
|
|
15
|
+
Sequence,
|
|
16
|
+
Set,
|
|
17
|
+
Any,
|
|
18
|
+
TypeVar,
|
|
19
|
+
Mapping,
|
|
20
|
+
cast,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
24
|
+
PositionUnknownError,
|
|
25
|
+
UnsupportedHardwareCommand,
|
|
26
|
+
)
|
|
27
|
+
from opentrons_shared_data.pipette import (
|
|
28
|
+
pipette_load_name_conversions as pipette_load_name,
|
|
29
|
+
)
|
|
30
|
+
from opentrons_shared_data.pipette.types import PipetteName
|
|
31
|
+
from opentrons_shared_data.robot.types import RobotType
|
|
32
|
+
from opentrons import types as top_types
|
|
33
|
+
from opentrons.config import robot_configs
|
|
34
|
+
from opentrons.config.types import RobotConfig, OT3Config
|
|
35
|
+
from opentrons.drivers.rpi_drivers.types import USBPort, PortGroup
|
|
36
|
+
|
|
37
|
+
from .util import use_or_initialize_loop, check_motion_bounds, ot2_axis_to_string
|
|
38
|
+
from .instruments.ot2.pipette import (
|
|
39
|
+
generate_hardware_configs,
|
|
40
|
+
load_from_config_and_check_skip,
|
|
41
|
+
)
|
|
42
|
+
from .backends import Controller, Simulator
|
|
43
|
+
from .execution_manager import ExecutionManagerProvider
|
|
44
|
+
from .pause_manager import PauseManager
|
|
45
|
+
from .module_control import AttachedModulesControl
|
|
46
|
+
from .types import (
|
|
47
|
+
Axis,
|
|
48
|
+
CriticalPoint,
|
|
49
|
+
DoorState,
|
|
50
|
+
DoorStateNotification,
|
|
51
|
+
ErrorMessageNotification,
|
|
52
|
+
HardwareEventHandler,
|
|
53
|
+
HardwareAction,
|
|
54
|
+
MotionChecks,
|
|
55
|
+
PauseType,
|
|
56
|
+
StatusBarState,
|
|
57
|
+
EstopState,
|
|
58
|
+
SubSystem,
|
|
59
|
+
SubSystemState,
|
|
60
|
+
HardwareFeatureFlags,
|
|
61
|
+
TipScrapeType,
|
|
62
|
+
)
|
|
63
|
+
from . import modules
|
|
64
|
+
from .robot_calibration import (
|
|
65
|
+
RobotCalibrationProvider,
|
|
66
|
+
RobotCalibration,
|
|
67
|
+
)
|
|
68
|
+
from .protocols import HardwareControlInterface
|
|
69
|
+
from .instruments.ot2.pipette_handler import PipetteHandlerProvider
|
|
70
|
+
from .instruments.ot2.instrument_calibration import load_pipette_offset
|
|
71
|
+
from .motion_utilities import (
|
|
72
|
+
target_position_from_absolute,
|
|
73
|
+
target_position_from_relative,
|
|
74
|
+
target_position_from_plunger,
|
|
75
|
+
deck_from_machine,
|
|
76
|
+
machine_from_deck,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
mod_log = logging.getLogger(__name__)
|
|
81
|
+
|
|
82
|
+
AttachedModuleSpec = Dict[str, List[Union[str, Tuple[str, str]]]]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class API(
|
|
86
|
+
ExecutionManagerProvider,
|
|
87
|
+
RobotCalibrationProvider,
|
|
88
|
+
PipetteHandlerProvider[top_types.Mount],
|
|
89
|
+
# This MUST be kept last in the inheritance list so that it is
|
|
90
|
+
# deprioritized in the method resolution order; otherwise, invocations
|
|
91
|
+
# of methods that are present in the protocol will call the (empty,
|
|
92
|
+
# do-nothing) methods in the protocol. This will happily make all the
|
|
93
|
+
# tests fail.
|
|
94
|
+
HardwareControlInterface[RobotCalibration, top_types.Mount, RobotConfig],
|
|
95
|
+
):
|
|
96
|
+
"""This API is the primary interface to the hardware controller.
|
|
97
|
+
|
|
98
|
+
Because the hardware manager controls access to the system's hardware
|
|
99
|
+
as a whole, it is designed as a class of which only one should be
|
|
100
|
+
instantiated at a time. This class's methods should be the only method
|
|
101
|
+
of external access to the hardware. Each method may be minimal - it may
|
|
102
|
+
only delegate the call to another submodule of the hardware manager -
|
|
103
|
+
but its purpose is to be gathered here to provide a single interface.
|
|
104
|
+
|
|
105
|
+
This implements the protocols in opentrons.hardware_control.protocols,
|
|
106
|
+
and longer method docstrings may be found there. Docstrings for the
|
|
107
|
+
methods in this class only note where their behavior is different or
|
|
108
|
+
extended from that described in the protocol.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
CLS_LOG = mod_log.getChild("API")
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
backend: Union[Controller, Simulator],
|
|
116
|
+
loop: asyncio.AbstractEventLoop,
|
|
117
|
+
config: RobotConfig,
|
|
118
|
+
feature_flags: Optional[HardwareFeatureFlags] = None,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Initialize an API instance.
|
|
121
|
+
|
|
122
|
+
This should rarely be explicitly invoked by an external user; instead,
|
|
123
|
+
one of the factory methods build_hardware_controller or
|
|
124
|
+
build_hardware_simulator should be used.
|
|
125
|
+
"""
|
|
126
|
+
self._log = self.CLS_LOG.getChild(str(id(self)))
|
|
127
|
+
self._config = config
|
|
128
|
+
self._backend = backend
|
|
129
|
+
self._loop = loop
|
|
130
|
+
# If no feature flag set is defined, we will use the default values
|
|
131
|
+
self._feature_flags = feature_flags or HardwareFeatureFlags()
|
|
132
|
+
|
|
133
|
+
self._callbacks: Set[HardwareEventHandler] = set()
|
|
134
|
+
# {'X': 0.0, 'Y': 0.0, 'Z': 0.0, 'A': 0.0, 'B': 0.0, 'C': 0.0}
|
|
135
|
+
self._current_position: Dict[Axis, float] = {}
|
|
136
|
+
|
|
137
|
+
self._last_moved_mount: Optional[top_types.Mount] = None
|
|
138
|
+
# The motion lock synchronizes calls to long-running physical tasks
|
|
139
|
+
# involved in motion. This fixes issue where for instance a move()
|
|
140
|
+
# or home() call is in flight and something else calls
|
|
141
|
+
# current_position(), which will not be updated until the move() or
|
|
142
|
+
# home() call succeeds or fails.
|
|
143
|
+
self._motion_lock = asyncio.Lock()
|
|
144
|
+
self._door_state = DoorState.CLOSED
|
|
145
|
+
self._pause_manager = PauseManager()
|
|
146
|
+
ExecutionManagerProvider.__init__(self, isinstance(backend, Simulator))
|
|
147
|
+
RobotCalibrationProvider.__init__(self)
|
|
148
|
+
PipetteHandlerProvider.__init__(
|
|
149
|
+
self, {top_types.Mount.LEFT: None, top_types.Mount.RIGHT: None}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def door_state(self) -> DoorState:
|
|
154
|
+
return self._door_state
|
|
155
|
+
|
|
156
|
+
@door_state.setter
|
|
157
|
+
def door_state(self, door_state: DoorState) -> None:
|
|
158
|
+
self._door_state = door_state
|
|
159
|
+
|
|
160
|
+
def _update_door_state(self, door_state: DoorState) -> None:
|
|
161
|
+
mod_log.info(f"Updating the window switch status: {door_state}")
|
|
162
|
+
self.door_state = door_state
|
|
163
|
+
for cb in self._callbacks:
|
|
164
|
+
hw_event = DoorStateNotification(new_state=door_state)
|
|
165
|
+
try:
|
|
166
|
+
cb(hw_event)
|
|
167
|
+
except Exception:
|
|
168
|
+
mod_log.exception("Errored during door state event callback")
|
|
169
|
+
|
|
170
|
+
def _reset_last_mount(self) -> None:
|
|
171
|
+
self._last_moved_mount = None
|
|
172
|
+
|
|
173
|
+
def get_deck_from_machine(
|
|
174
|
+
self, machine_pos: Dict[Axis, float]
|
|
175
|
+
) -> Dict[Axis, float]:
|
|
176
|
+
return deck_from_machine(
|
|
177
|
+
machine_pos=machine_pos,
|
|
178
|
+
attitude=self._robot_calibration.deck_calibration.attitude,
|
|
179
|
+
offset=top_types.Point(0, 0, 0),
|
|
180
|
+
robot_type=cast(RobotType, "OT-2 Standard"),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
async def build_hardware_controller( # noqa: C901
|
|
185
|
+
cls,
|
|
186
|
+
config: Union[RobotConfig, OT3Config, None] = None,
|
|
187
|
+
port: Optional[str] = None,
|
|
188
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
189
|
+
firmware: Optional[Tuple[pathlib.Path, str]] = None,
|
|
190
|
+
feature_flags: Optional[HardwareFeatureFlags] = None,
|
|
191
|
+
) -> "API":
|
|
192
|
+
"""Build a hardware controller that will actually talk to hardware.
|
|
193
|
+
|
|
194
|
+
This method should not be used outside of a real robot, and on a
|
|
195
|
+
real robot only one true hardware controller may be active at one
|
|
196
|
+
time.
|
|
197
|
+
|
|
198
|
+
:param config: A config to preload. If not specified, load the default.
|
|
199
|
+
:param port: A port to connect to. If not specified, the default port
|
|
200
|
+
(found by scanning for connected FT232Rs).
|
|
201
|
+
:param loop: An event loop to use. If not specified, use the result of
|
|
202
|
+
:py:meth:`asyncio.get_event_loop`.
|
|
203
|
+
"""
|
|
204
|
+
checked_loop = use_or_initialize_loop(loop)
|
|
205
|
+
if isinstance(config, RobotConfig):
|
|
206
|
+
checked_config = config
|
|
207
|
+
else:
|
|
208
|
+
checked_config = robot_configs.load_ot2()
|
|
209
|
+
backend = await Controller.build(checked_config)
|
|
210
|
+
backend.set_lights(button=None, rails=False)
|
|
211
|
+
|
|
212
|
+
async def blink() -> None:
|
|
213
|
+
while True:
|
|
214
|
+
backend.set_lights(button=True, rails=None)
|
|
215
|
+
await asyncio.sleep(0.5)
|
|
216
|
+
backend.set_lights(button=False, rails=None)
|
|
217
|
+
await asyncio.sleep(0.5)
|
|
218
|
+
|
|
219
|
+
blink_task = checked_loop.create_task(blink())
|
|
220
|
+
try:
|
|
221
|
+
try:
|
|
222
|
+
await backend.connect(port)
|
|
223
|
+
fw_version = backend.fw_version
|
|
224
|
+
except Exception:
|
|
225
|
+
mod_log.exception(
|
|
226
|
+
"Motor driver could not connect, reprogramming if possible"
|
|
227
|
+
)
|
|
228
|
+
fw_version = None
|
|
229
|
+
|
|
230
|
+
if firmware is not None:
|
|
231
|
+
if fw_version != firmware[1]:
|
|
232
|
+
await backend.update_firmware(str(firmware[0]), checked_loop, True)
|
|
233
|
+
await backend.connect(port)
|
|
234
|
+
elif firmware is None and fw_version is None:
|
|
235
|
+
msg = (
|
|
236
|
+
"Motor controller could not be connected and no "
|
|
237
|
+
"firmware was provided for (re)programming"
|
|
238
|
+
)
|
|
239
|
+
mod_log.error(msg)
|
|
240
|
+
raise RuntimeError(msg)
|
|
241
|
+
|
|
242
|
+
api_instance = cls(
|
|
243
|
+
backend,
|
|
244
|
+
loop=checked_loop,
|
|
245
|
+
config=checked_config,
|
|
246
|
+
feature_flags=feature_flags,
|
|
247
|
+
)
|
|
248
|
+
await api_instance.cache_instruments()
|
|
249
|
+
module_controls = await AttachedModulesControl.build(
|
|
250
|
+
api_instance, board_revision=backend.board_revision
|
|
251
|
+
)
|
|
252
|
+
backend.module_controls = module_controls
|
|
253
|
+
checked_loop.create_task(backend.watch(loop=checked_loop))
|
|
254
|
+
backend.start_gpio_door_watcher(
|
|
255
|
+
loop=checked_loop, update_door_state=api_instance._update_door_state
|
|
256
|
+
)
|
|
257
|
+
return api_instance
|
|
258
|
+
finally:
|
|
259
|
+
blink_task.cancel()
|
|
260
|
+
try:
|
|
261
|
+
await blink_task
|
|
262
|
+
except asyncio.CancelledError:
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
async def build_hardware_simulator(
|
|
267
|
+
cls,
|
|
268
|
+
attached_instruments: Optional[
|
|
269
|
+
Dict[top_types.Mount, Dict[str, Optional[str]]]
|
|
270
|
+
] = None,
|
|
271
|
+
attached_modules: Optional[Dict[str, List[modules.SimulatingModule]]] = None,
|
|
272
|
+
config: Optional[Union[RobotConfig, OT3Config]] = None,
|
|
273
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
274
|
+
strict_attached_instruments: bool = True,
|
|
275
|
+
feature_flags: Optional[HardwareFeatureFlags] = None,
|
|
276
|
+
) -> "API":
|
|
277
|
+
"""Build a simulating hardware controller.
|
|
278
|
+
|
|
279
|
+
This method may be used both on a real robot and on dev machines.
|
|
280
|
+
Multiple simulating hardware controllers may be active at one time.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
if None is attached_instruments:
|
|
284
|
+
attached_instruments = {}
|
|
285
|
+
|
|
286
|
+
if None is attached_modules:
|
|
287
|
+
attached_modules = {}
|
|
288
|
+
|
|
289
|
+
checked_loop = use_or_initialize_loop(loop)
|
|
290
|
+
if isinstance(config, RobotConfig):
|
|
291
|
+
checked_config = config
|
|
292
|
+
else:
|
|
293
|
+
checked_config = robot_configs.load_ot2()
|
|
294
|
+
backend = await Simulator.build(
|
|
295
|
+
attached_instruments,
|
|
296
|
+
attached_modules,
|
|
297
|
+
checked_config,
|
|
298
|
+
checked_loop,
|
|
299
|
+
strict_attached_instruments,
|
|
300
|
+
)
|
|
301
|
+
api_instance = cls(
|
|
302
|
+
backend,
|
|
303
|
+
loop=checked_loop,
|
|
304
|
+
config=checked_config,
|
|
305
|
+
feature_flags=feature_flags,
|
|
306
|
+
)
|
|
307
|
+
await api_instance.cache_instruments()
|
|
308
|
+
module_controls = await AttachedModulesControl.build(
|
|
309
|
+
api_instance, board_revision=backend.board_revision
|
|
310
|
+
)
|
|
311
|
+
backend.module_controls = module_controls
|
|
312
|
+
await backend.watch()
|
|
313
|
+
return api_instance
|
|
314
|
+
|
|
315
|
+
def __repr__(self) -> str:
|
|
316
|
+
return "<{} using backend {}>".format(type(self), type(self._backend))
|
|
317
|
+
|
|
318
|
+
async def get_serial_number(self) -> Optional[str]:
|
|
319
|
+
return await self._backend.get_serial_number()
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def loop(self) -> asyncio.AbstractEventLoop:
|
|
323
|
+
"""The event loop used by this instance."""
|
|
324
|
+
return self._loop
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def is_simulator(self) -> bool:
|
|
328
|
+
"""`True` if this is a simulator; `False` otherwise."""
|
|
329
|
+
return isinstance(self._backend, Simulator)
|
|
330
|
+
|
|
331
|
+
def register_callback(self, cb: HardwareEventHandler) -> Callable[[], None]:
|
|
332
|
+
"""Allows the caller to register a callback, and returns a closure
|
|
333
|
+
that can be used to unregister the provided callback
|
|
334
|
+
"""
|
|
335
|
+
self._callbacks.add(cb)
|
|
336
|
+
|
|
337
|
+
def unregister() -> None:
|
|
338
|
+
self._callbacks.remove(cb)
|
|
339
|
+
|
|
340
|
+
return unregister
|
|
341
|
+
|
|
342
|
+
def get_fw_version(self) -> str:
|
|
343
|
+
"""
|
|
344
|
+
Return the firmware version of the connected motor control board.
|
|
345
|
+
|
|
346
|
+
The version is a string retrieved directly from the attached hardware
|
|
347
|
+
(or possibly simulator).
|
|
348
|
+
"""
|
|
349
|
+
from_backend = self._backend.fw_version
|
|
350
|
+
if from_backend is None:
|
|
351
|
+
return "unknown"
|
|
352
|
+
else:
|
|
353
|
+
return from_backend
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def fw_version(self) -> str:
|
|
357
|
+
return self.get_fw_version()
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def board_revision(self) -> str:
|
|
361
|
+
return str(self._backend.board_revision)
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def attached_subsystems(self) -> Dict[SubSystem, SubSystemState]:
|
|
365
|
+
return {}
|
|
366
|
+
|
|
367
|
+
# Incidentals (i.e. not motion) API
|
|
368
|
+
|
|
369
|
+
async def set_lights(
|
|
370
|
+
self, button: Optional[bool] = None, rails: Optional[bool] = None
|
|
371
|
+
) -> None:
|
|
372
|
+
"""Control the robot lights."""
|
|
373
|
+
self._backend.set_lights(button, rails)
|
|
374
|
+
|
|
375
|
+
async def get_lights(self) -> Dict[str, bool]:
|
|
376
|
+
"""Return the current status of the robot lights.
|
|
377
|
+
|
|
378
|
+
:returns: A dict of the lights: `{'button': bool, 'rails': bool}`
|
|
379
|
+
"""
|
|
380
|
+
return self._backend.get_lights()
|
|
381
|
+
|
|
382
|
+
async def identify(self, duration_s: int = 5) -> None:
|
|
383
|
+
"""Blink the button light to identify the robot."""
|
|
384
|
+
count = duration_s * 4
|
|
385
|
+
on = False
|
|
386
|
+
for sec in range(count):
|
|
387
|
+
then = self._loop.time()
|
|
388
|
+
await self.set_lights(button=on)
|
|
389
|
+
on = not on
|
|
390
|
+
now = self._loop.time()
|
|
391
|
+
await asyncio.sleep(max(0, 0.25 - (now - then)))
|
|
392
|
+
await self.set_lights(button=True)
|
|
393
|
+
|
|
394
|
+
async def set_status_bar_state(self, state: StatusBarState) -> None:
|
|
395
|
+
"""The status bar does not exist on OT-2!"""
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
async def set_status_bar_enabled(self, enabled: bool) -> None:
|
|
399
|
+
"""The status bar does not exist on OT-2!"""
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
def get_status_bar_enabled(self) -> bool:
|
|
403
|
+
"""There is no status bar on OT-2, return False at all times."""
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
def get_status_bar_state(self) -> StatusBarState:
|
|
407
|
+
"""There is no status bar on OT-2, return IDLE at all times."""
|
|
408
|
+
return StatusBarState.IDLE
|
|
409
|
+
|
|
410
|
+
@ExecutionManagerProvider.wait_for_running
|
|
411
|
+
async def delay(self, duration_s: float) -> None:
|
|
412
|
+
"""Delay execution by pausing and sleeping."""
|
|
413
|
+
self.pause(PauseType.DELAY)
|
|
414
|
+
try:
|
|
415
|
+
await self.do_delay(duration_s)
|
|
416
|
+
finally:
|
|
417
|
+
self.resume(PauseType.DELAY)
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def attached_modules(self) -> List[modules.AbstractModule]:
|
|
421
|
+
return self._backend.module_controls.available_modules
|
|
422
|
+
|
|
423
|
+
async def update_firmware(
|
|
424
|
+
self,
|
|
425
|
+
firmware_file: str,
|
|
426
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
427
|
+
explicit_modeset: bool = True,
|
|
428
|
+
) -> str:
|
|
429
|
+
"""Update the firmware on the motor controller board.
|
|
430
|
+
|
|
431
|
+
:param firmware_file: The path to the firmware file.
|
|
432
|
+
:param explicit_modeset: `True` to force the smoothie into programming
|
|
433
|
+
mode; `False` to assume it is already in
|
|
434
|
+
programming mode.
|
|
435
|
+
:param loop: An asyncio event loop to use; if not specified, the one
|
|
436
|
+
associated with this instance will be used.
|
|
437
|
+
:returns: The stdout of the tool used to update the smoothie
|
|
438
|
+
"""
|
|
439
|
+
if None is loop:
|
|
440
|
+
checked_loop = self._loop
|
|
441
|
+
else:
|
|
442
|
+
checked_loop = loop
|
|
443
|
+
return await self._backend.update_firmware(
|
|
444
|
+
firmware_file, checked_loop, explicit_modeset
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def has_gripper(self) -> bool:
|
|
448
|
+
return False
|
|
449
|
+
|
|
450
|
+
async def cache_instruments(
|
|
451
|
+
self,
|
|
452
|
+
require: Optional[Dict[top_types.Mount, PipetteName]] = None,
|
|
453
|
+
skip_if_would_block: bool = False,
|
|
454
|
+
) -> None:
|
|
455
|
+
"""
|
|
456
|
+
Scan the attached instruments, take necessary configuration actions,
|
|
457
|
+
and set up hardware controller internal state if necessary.
|
|
458
|
+
"""
|
|
459
|
+
self._log.info("Updating instrument model cache")
|
|
460
|
+
checked_require = require or {}
|
|
461
|
+
for mount, name in checked_require.items():
|
|
462
|
+
if not pipette_load_name.supported_pipette(name):
|
|
463
|
+
raise RuntimeError(f"{name} is not a valid pipette name")
|
|
464
|
+
async with self._motion_lock:
|
|
465
|
+
found = await self._backend.get_attached_instruments(checked_require)
|
|
466
|
+
for mount, instrument_data in found.items():
|
|
467
|
+
config = instrument_data.get("config")
|
|
468
|
+
req_instr = checked_require.get(mount, None)
|
|
469
|
+
pip_id = instrument_data.get("id")
|
|
470
|
+
pip_offset_cal = load_pipette_offset(pip_id, mount)
|
|
471
|
+
p, may_skip = load_from_config_and_check_skip(
|
|
472
|
+
config,
|
|
473
|
+
self._attached_instruments[mount],
|
|
474
|
+
req_instr,
|
|
475
|
+
pip_id,
|
|
476
|
+
pip_offset_cal,
|
|
477
|
+
self._feature_flags.use_old_aspiration_functions,
|
|
478
|
+
)
|
|
479
|
+
self._attached_instruments[mount] = p
|
|
480
|
+
if req_instr and p:
|
|
481
|
+
converted_name = pipette_load_name.convert_to_pipette_name_type(
|
|
482
|
+
req_instr
|
|
483
|
+
)
|
|
484
|
+
p.act_as(converted_name)
|
|
485
|
+
|
|
486
|
+
if may_skip:
|
|
487
|
+
self._log.info(f"Skipping configuration on {mount.name}")
|
|
488
|
+
continue
|
|
489
|
+
|
|
490
|
+
self._log.info(f"Doing full configuration on {mount.name}")
|
|
491
|
+
hw_config = generate_hardware_configs(
|
|
492
|
+
p, self._config, self._backend.board_revision
|
|
493
|
+
)
|
|
494
|
+
await self._backend.configure_mount(mount, hw_config)
|
|
495
|
+
self._log.info("Instruments found: {}".format(self._attached_instruments))
|
|
496
|
+
|
|
497
|
+
# Global actions API
|
|
498
|
+
def pause(self, pause_type: PauseType) -> None:
|
|
499
|
+
"""
|
|
500
|
+
Pause motion of the robot after a current motion concludes.
|
|
501
|
+
"""
|
|
502
|
+
self._pause_manager.pause(pause_type)
|
|
503
|
+
|
|
504
|
+
async def _chained_calls() -> None:
|
|
505
|
+
await self._execution_manager.pause()
|
|
506
|
+
self._backend.pause()
|
|
507
|
+
|
|
508
|
+
asyncio.run_coroutine_threadsafe(_chained_calls(), self._loop)
|
|
509
|
+
|
|
510
|
+
def pause_with_message(self, message: str) -> None:
|
|
511
|
+
"""As pause, but providing a message to registered callbacks."""
|
|
512
|
+
self._log.warning(f"Pause with message: {message}")
|
|
513
|
+
notification = ErrorMessageNotification(message=message)
|
|
514
|
+
for cb in self._callbacks:
|
|
515
|
+
cb(notification)
|
|
516
|
+
self.pause(PauseType.PAUSE)
|
|
517
|
+
|
|
518
|
+
def resume(self, pause_type: PauseType) -> None:
|
|
519
|
+
"""Resume motion after a call to pause."""
|
|
520
|
+
self._pause_manager.resume(pause_type)
|
|
521
|
+
|
|
522
|
+
if self._pause_manager.should_pause:
|
|
523
|
+
return
|
|
524
|
+
|
|
525
|
+
# Resume must be called immediately to awaken thread running hardware
|
|
526
|
+
# methods (ThreadManager)
|
|
527
|
+
self._backend.resume()
|
|
528
|
+
|
|
529
|
+
async def _chained_calls() -> None:
|
|
530
|
+
# mirror what happens API.pause.
|
|
531
|
+
await self._execution_manager.resume()
|
|
532
|
+
self._backend.resume()
|
|
533
|
+
|
|
534
|
+
asyncio.run_coroutine_threadsafe(_chained_calls(), self._loop)
|
|
535
|
+
|
|
536
|
+
async def halt(self, disengage_before_stopping: bool = False) -> None:
|
|
537
|
+
"""Immediately stop motion, cancel execution manager and cancel running tasks.
|
|
538
|
+
|
|
539
|
+
After this call, the smoothie will be in a bad state until a call to
|
|
540
|
+
:py:meth:`stop`.
|
|
541
|
+
"""
|
|
542
|
+
if disengage_before_stopping:
|
|
543
|
+
await self._backend.hard_halt()
|
|
544
|
+
await self._backend.halt()
|
|
545
|
+
|
|
546
|
+
async def stop(self, home_after: bool = True) -> None:
|
|
547
|
+
"""
|
|
548
|
+
Stop motion as soon as possible, reset, and optionally home.
|
|
549
|
+
|
|
550
|
+
This will cancel motion (after the current call to :py:meth:`move`;
|
|
551
|
+
see :py:meth:`pause` for more detail), then home and reset the
|
|
552
|
+
robot. After this call, no further recovery is necessary.
|
|
553
|
+
"""
|
|
554
|
+
await self._backend.halt() # calls smoothie_driver.kill()
|
|
555
|
+
await self.cancel_execution_and_running_tasks()
|
|
556
|
+
self._log.info("Recovering from halt")
|
|
557
|
+
await self.reset()
|
|
558
|
+
await self.cache_instruments()
|
|
559
|
+
|
|
560
|
+
if home_after:
|
|
561
|
+
await self.home()
|
|
562
|
+
|
|
563
|
+
def is_movement_execution_taskified(self) -> bool:
|
|
564
|
+
return self.taskify_movement_execution
|
|
565
|
+
|
|
566
|
+
def should_taskify_movement_execution(self, taskify: bool) -> None:
|
|
567
|
+
self.taskify_movement_execution = taskify
|
|
568
|
+
|
|
569
|
+
async def cancel_execution_and_running_tasks(self) -> None:
|
|
570
|
+
await self._execution_manager.cancel()
|
|
571
|
+
|
|
572
|
+
async def reset(self) -> None:
|
|
573
|
+
"""Reset the stored state of the system."""
|
|
574
|
+
self._pause_manager.reset()
|
|
575
|
+
await self._execution_manager.reset()
|
|
576
|
+
await PipetteHandlerProvider.reset(self)
|
|
577
|
+
|
|
578
|
+
# Gantry/frame (i.e. not pipette) action API
|
|
579
|
+
async def home_z(
|
|
580
|
+
self,
|
|
581
|
+
mount: Optional[top_types.Mount] = None,
|
|
582
|
+
allow_home_other: bool = True,
|
|
583
|
+
) -> None:
|
|
584
|
+
"""Home the Z-stage(s) of the instrument mounts.
|
|
585
|
+
|
|
586
|
+
If given a mount, will try to only home that mount.
|
|
587
|
+
However, if the other mount is currently extended,
|
|
588
|
+
both mounts will be homed, unless `allow_home_other`
|
|
589
|
+
is explicitly set to `False`.
|
|
590
|
+
|
|
591
|
+
Setting `allow_home_other` to `False` is a bad idea,
|
|
592
|
+
but the option exists for strict backwards compatibility.
|
|
593
|
+
"""
|
|
594
|
+
if mount is not None and (
|
|
595
|
+
self._last_moved_mount in [mount, None] or allow_home_other is False
|
|
596
|
+
):
|
|
597
|
+
axes = [Axis.by_mount(mount)]
|
|
598
|
+
else:
|
|
599
|
+
axes = [Axis.Z, Axis.A]
|
|
600
|
+
|
|
601
|
+
await self.home(axes)
|
|
602
|
+
|
|
603
|
+
async def _do_plunger_home(
|
|
604
|
+
self,
|
|
605
|
+
axis: Optional[Axis] = None,
|
|
606
|
+
mount: Optional[top_types.Mount] = None,
|
|
607
|
+
acquire_lock: bool = True,
|
|
608
|
+
) -> None:
|
|
609
|
+
assert (axis is not None) ^ (mount is not None), "specify either axis or mount"
|
|
610
|
+
if axis:
|
|
611
|
+
checked_axis = axis
|
|
612
|
+
checked_mount = Axis.to_ot2_mount(checked_axis)
|
|
613
|
+
if mount:
|
|
614
|
+
checked_mount = mount
|
|
615
|
+
checked_axis = Axis.of_plunger(checked_mount)
|
|
616
|
+
instr = self.hardware_instruments[checked_mount]
|
|
617
|
+
if not instr:
|
|
618
|
+
return
|
|
619
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
620
|
+
if acquire_lock:
|
|
621
|
+
await stack.enter_async_context(self._motion_lock)
|
|
622
|
+
with self._backend.save_current():
|
|
623
|
+
self._backend.set_active_current(
|
|
624
|
+
{checked_axis: instr.plunger_motor_current.run}
|
|
625
|
+
)
|
|
626
|
+
await self._backend.home([ot2_axis_to_string(checked_axis)])
|
|
627
|
+
# either we were passed False for our acquire_lock and we
|
|
628
|
+
# should pass it on, or we acquired the lock above and
|
|
629
|
+
# shouldn't do it again
|
|
630
|
+
target_pos = target_position_from_plunger(
|
|
631
|
+
checked_mount,
|
|
632
|
+
instr.plunger_positions.bottom,
|
|
633
|
+
self._current_position,
|
|
634
|
+
)
|
|
635
|
+
await self._move(
|
|
636
|
+
target_pos,
|
|
637
|
+
acquire_lock=False,
|
|
638
|
+
home_flagged_axes=False,
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
@ExecutionManagerProvider.wait_for_running
|
|
642
|
+
async def home_plunger(self, mount: top_types.Mount) -> None:
|
|
643
|
+
"""
|
|
644
|
+
Home the plunger motor for a mount, and then return it to the 'bottom'
|
|
645
|
+
position.
|
|
646
|
+
"""
|
|
647
|
+
await self.current_position(mount=mount, refresh=True)
|
|
648
|
+
await self._do_plunger_home(mount=mount, acquire_lock=True)
|
|
649
|
+
|
|
650
|
+
@ExecutionManagerProvider.wait_for_running
|
|
651
|
+
async def home(self, axes: Optional[List[Axis]] = None) -> None:
|
|
652
|
+
"""Home the entire robot and initialize current position."""
|
|
653
|
+
# Should we assert/ raise an error or just remove non-ot2 axes and log warning?
|
|
654
|
+
# No internal code passes OT3 axes as arguments on an OT2. But a user/ client
|
|
655
|
+
# can still explicitly specify an OT3 axis even when working on an OT2.
|
|
656
|
+
# Adding this check in order to prevent misuse of axes types.
|
|
657
|
+
if axes:
|
|
658
|
+
unsupported = list(axis not in Axis.ot2_axes() for axis in axes)
|
|
659
|
+
if any(unsupported):
|
|
660
|
+
raise UnsupportedHardwareCommand(
|
|
661
|
+
message=f"At least one axis in {axes} is not supported on the OT2.",
|
|
662
|
+
detail={"unsupported_axes": str(unsupported)},
|
|
663
|
+
)
|
|
664
|
+
self._reset_last_mount()
|
|
665
|
+
# Initialize/update current_position
|
|
666
|
+
checked_axes = axes or [ax for ax in Axis.ot2_axes()]
|
|
667
|
+
gantry = [ax for ax in checked_axes if ax in Axis.gantry_axes()]
|
|
668
|
+
smoothie_gantry = [ot2_axis_to_string(ax) for ax in gantry]
|
|
669
|
+
smoothie_pos = {}
|
|
670
|
+
plungers = [ax for ax in checked_axes if ax not in Axis.gantry_axes()]
|
|
671
|
+
|
|
672
|
+
async with self._motion_lock:
|
|
673
|
+
if smoothie_gantry:
|
|
674
|
+
smoothie_pos.update(await self._backend.home(smoothie_gantry))
|
|
675
|
+
self._current_position = self.get_deck_from_machine(
|
|
676
|
+
self._axis_map_from_string_map(smoothie_pos)
|
|
677
|
+
)
|
|
678
|
+
for plunger in plungers:
|
|
679
|
+
await self._do_plunger_home(axis=plunger, acquire_lock=False)
|
|
680
|
+
|
|
681
|
+
async def current_position(
|
|
682
|
+
self,
|
|
683
|
+
mount: top_types.Mount,
|
|
684
|
+
critical_point: Optional[CriticalPoint] = None,
|
|
685
|
+
refresh: bool = False,
|
|
686
|
+
# TODO(mc, 2021-11-15): combine with `refresh` for more reliable
|
|
687
|
+
# position reporting when motors are not homed
|
|
688
|
+
fail_on_not_homed: bool = False,
|
|
689
|
+
) -> Dict[Axis, float]:
|
|
690
|
+
"""Return the postion (in deck coords) of the critical point of the
|
|
691
|
+
specified mount.
|
|
692
|
+
"""
|
|
693
|
+
z_ax = Axis.by_mount(mount)
|
|
694
|
+
plunger_ax = Axis.of_plunger(mount)
|
|
695
|
+
position_axes = [Axis.X, Axis.Y, z_ax, plunger_ax]
|
|
696
|
+
|
|
697
|
+
if fail_on_not_homed:
|
|
698
|
+
if not self._current_position:
|
|
699
|
+
raise PositionUnknownError(
|
|
700
|
+
message=f"Current position of {str(mount)} pipette is unknown,"
|
|
701
|
+
" please home.",
|
|
702
|
+
detail={"mount": str(mount), "missing_axes": str(position_axes)},
|
|
703
|
+
)
|
|
704
|
+
axes_str = [ot2_axis_to_string(a) for a in position_axes]
|
|
705
|
+
if not self._backend.is_homed(axes_str):
|
|
706
|
+
unhomed = self._backend._unhomed_axes(axes_str)
|
|
707
|
+
raise PositionUnknownError(
|
|
708
|
+
message=f"{str(mount)} pipette axes ({unhomed}) must be homed.",
|
|
709
|
+
detail={"mount": str(mount), "unhomed_axes": str(unhomed)},
|
|
710
|
+
)
|
|
711
|
+
elif not self._current_position and not refresh:
|
|
712
|
+
raise PositionUnknownError(
|
|
713
|
+
message="Current position is unknown; please home motors."
|
|
714
|
+
)
|
|
715
|
+
async with self._motion_lock:
|
|
716
|
+
if refresh:
|
|
717
|
+
smoothie_pos = await self._backend.update_position()
|
|
718
|
+
self._current_position = self.get_deck_from_machine(
|
|
719
|
+
self._axis_map_from_string_map(smoothie_pos)
|
|
720
|
+
)
|
|
721
|
+
if mount == top_types.Mount.RIGHT:
|
|
722
|
+
offset = top_types.Point(0, 0, 0)
|
|
723
|
+
else:
|
|
724
|
+
offset = top_types.Point(*self._config.left_mount_offset)
|
|
725
|
+
|
|
726
|
+
cp = self.critical_point_for(mount, critical_point)
|
|
727
|
+
return {
|
|
728
|
+
Axis.X: self._current_position[Axis.X] + offset[0] + cp.x,
|
|
729
|
+
Axis.Y: self._current_position[Axis.Y] + offset[1] + cp.y,
|
|
730
|
+
z_ax: self._current_position[z_ax] + offset[2] + cp.z,
|
|
731
|
+
plunger_ax: self._current_position[plunger_ax],
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async def gantry_position(
|
|
735
|
+
self,
|
|
736
|
+
mount: top_types.Mount,
|
|
737
|
+
critical_point: Optional[CriticalPoint] = None,
|
|
738
|
+
refresh: bool = False,
|
|
739
|
+
# TODO(mc, 2021-11-15): combine with `refresh` for more reliable
|
|
740
|
+
# position reporting when motors are not homed
|
|
741
|
+
fail_on_not_homed: bool = False,
|
|
742
|
+
) -> top_types.Point:
|
|
743
|
+
"""Return the position of the critical point for only gantry axes."""
|
|
744
|
+
cur_pos = await self.current_position(
|
|
745
|
+
mount,
|
|
746
|
+
critical_point,
|
|
747
|
+
refresh,
|
|
748
|
+
fail_on_not_homed,
|
|
749
|
+
)
|
|
750
|
+
return top_types.Point(
|
|
751
|
+
x=cur_pos[Axis.X], y=cur_pos[Axis.Y], z=cur_pos[Axis.by_mount(mount)]
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# TODO(mc, 2022-05-13): return resulting gantry position
|
|
755
|
+
async def move_to(
|
|
756
|
+
self,
|
|
757
|
+
mount: top_types.Mount,
|
|
758
|
+
abs_position: top_types.Point,
|
|
759
|
+
speed: Optional[float] = None,
|
|
760
|
+
critical_point: Optional[CriticalPoint] = None,
|
|
761
|
+
max_speeds: Optional[Dict[Axis, float]] = None,
|
|
762
|
+
) -> None:
|
|
763
|
+
"""
|
|
764
|
+
Move the critical point of the specified mount to a location
|
|
765
|
+
relative to the deck, at the specified speed.
|
|
766
|
+
"""
|
|
767
|
+
if not self._current_position:
|
|
768
|
+
await self.home()
|
|
769
|
+
|
|
770
|
+
target_position = target_position_from_absolute(
|
|
771
|
+
mount,
|
|
772
|
+
abs_position,
|
|
773
|
+
partial(self.critical_point_for, cp_override=critical_point),
|
|
774
|
+
top_types.Point(*self._config.left_mount_offset),
|
|
775
|
+
top_types.Point(0, 0, 0),
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
await self.prepare_for_mount_movement(mount)
|
|
779
|
+
await self._move(target_position, speed=speed, max_speeds=max_speeds)
|
|
780
|
+
|
|
781
|
+
async def move_axes(
|
|
782
|
+
self,
|
|
783
|
+
position: Mapping[Axis, float],
|
|
784
|
+
speed: Optional[float] = None,
|
|
785
|
+
max_speeds: Optional[Dict[Axis, float]] = None,
|
|
786
|
+
expect_stalls: bool = False,
|
|
787
|
+
) -> None:
|
|
788
|
+
"""Moves the effectors of the specified axis to the specified position.
|
|
789
|
+
The effector of the x,y axis is the center of the carriage.
|
|
790
|
+
The effector of the pipette mount axis are the mount critical points but only in z.
|
|
791
|
+
"""
|
|
792
|
+
raise UnsupportedHardwareCommand(
|
|
793
|
+
message="move_axes is not supported on the OT-2.",
|
|
794
|
+
detail={"axes_commanded": str(list(position.keys()))},
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
async def move_rel(
|
|
798
|
+
self,
|
|
799
|
+
mount: top_types.Mount,
|
|
800
|
+
delta: top_types.Point,
|
|
801
|
+
speed: Optional[float] = None,
|
|
802
|
+
max_speeds: Optional[Dict[Axis, float]] = None,
|
|
803
|
+
check_bounds: MotionChecks = MotionChecks.NONE,
|
|
804
|
+
fail_on_not_homed: bool = False,
|
|
805
|
+
) -> None:
|
|
806
|
+
"""Move the critical point of the specified mount by a specified
|
|
807
|
+
displacement in a specified direction, at the specified speed.
|
|
808
|
+
"""
|
|
809
|
+
|
|
810
|
+
# TODO: Remove the fail_on_not_homed and make this the behavior all the time.
|
|
811
|
+
# Having the optional arg makes the bug stick around in existing code and we
|
|
812
|
+
# really want to fix it when we're not gearing up for a release.
|
|
813
|
+
if not self._current_position:
|
|
814
|
+
if fail_on_not_homed:
|
|
815
|
+
raise PositionUnknownError(
|
|
816
|
+
message="Cannot make a relative move because absolute position"
|
|
817
|
+
" is unknown.",
|
|
818
|
+
detail={
|
|
819
|
+
"mount": str(mount),
|
|
820
|
+
"fail_on_not_homed": str(fail_on_not_homed),
|
|
821
|
+
},
|
|
822
|
+
)
|
|
823
|
+
else:
|
|
824
|
+
await self.home()
|
|
825
|
+
|
|
826
|
+
target_position = target_position_from_relative(
|
|
827
|
+
mount, delta, self._current_position
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
axes_moving = [Axis.X, Axis.Y, Axis.by_mount(mount)]
|
|
831
|
+
axes_str = [ot2_axis_to_string(a) for a in axes_moving]
|
|
832
|
+
if fail_on_not_homed and not self._backend.is_homed(axes_str):
|
|
833
|
+
unhomed = self._backend._unhomed_axes(axes_str)
|
|
834
|
+
raise PositionUnknownError(
|
|
835
|
+
message=f"{str(mount)} pipette axes ({unhomed}) must be homed.",
|
|
836
|
+
detail={"mount": str(mount), "unhomed_axes": str(unhomed)},
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
await self.prepare_for_mount_movement(mount)
|
|
840
|
+
await self._move(
|
|
841
|
+
target_position,
|
|
842
|
+
speed=speed,
|
|
843
|
+
max_speeds=max_speeds,
|
|
844
|
+
check_bounds=check_bounds,
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
async def _cache_and_maybe_retract_mount(self, mount: top_types.Mount) -> None:
|
|
848
|
+
"""Retract the 'other' mount if necessary
|
|
849
|
+
|
|
850
|
+
If `mount` does not match the value in :py:attr:`_last_moved_mount`
|
|
851
|
+
(and :py:attr:`_last_moved_mount` exists) then retract the mount
|
|
852
|
+
in :py:attr:`_last_moved_mount`. Also unconditionally update
|
|
853
|
+
:py:attr:`_last_moved_mount` to contain `mount`.
|
|
854
|
+
"""
|
|
855
|
+
if mount != self._last_moved_mount and self._last_moved_mount:
|
|
856
|
+
await self.retract(self._last_moved_mount, 10)
|
|
857
|
+
self._last_moved_mount = mount
|
|
858
|
+
|
|
859
|
+
async def prepare_for_mount_movement(self, mount: top_types.Mount) -> None:
|
|
860
|
+
await self._cache_and_maybe_retract_mount(mount)
|
|
861
|
+
|
|
862
|
+
@ExecutionManagerProvider.wait_for_running
|
|
863
|
+
async def _move(
|
|
864
|
+
self,
|
|
865
|
+
target_position: "OrderedDict[Axis, float]",
|
|
866
|
+
speed: Optional[float] = None,
|
|
867
|
+
home_flagged_axes: bool = True,
|
|
868
|
+
max_speeds: Optional[Dict[Axis, float]] = None,
|
|
869
|
+
acquire_lock: bool = True,
|
|
870
|
+
check_bounds: MotionChecks = MotionChecks.NONE,
|
|
871
|
+
) -> None:
|
|
872
|
+
"""Worker function to apply robot motion.
|
|
873
|
+
|
|
874
|
+
Robot motion means the kind of motions that are relevant to the robot,
|
|
875
|
+
i.e. only one pipette plunger and mount move at the same time, and an
|
|
876
|
+
XYZ move in the coordinate frame of one of the pipettes.
|
|
877
|
+
|
|
878
|
+
``target_position`` should be an ordered dict (ordered by XYZABC)
|
|
879
|
+
of deck calibrated values, containing any specified XY motion and
|
|
880
|
+
at most one of a ZA or BC components. The frame in which to move
|
|
881
|
+
is identified by the presence of (ZA) or (BC).
|
|
882
|
+
"""
|
|
883
|
+
machine_pos = self._string_map_from_axis_map(
|
|
884
|
+
machine_from_deck(
|
|
885
|
+
deck_pos=target_position,
|
|
886
|
+
attitude=self._robot_calibration.deck_calibration.attitude,
|
|
887
|
+
offset=top_types.Point(0, 0, 0),
|
|
888
|
+
robot_type=cast(RobotType, "OT-2 Standard"),
|
|
889
|
+
)
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
bounds = self._backend.axis_bounds
|
|
893
|
+
to_check = {
|
|
894
|
+
ax: machine_pos[ot2_axis_to_string(ax)]
|
|
895
|
+
for idx, ax in enumerate(target_position.keys())
|
|
896
|
+
if ax in Axis.gantry_axes()
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
check_motion_bounds(to_check, target_position, bounds, check_bounds)
|
|
900
|
+
checked_maxes = max_speeds or {}
|
|
901
|
+
str_maxes = {ot2_axis_to_string(ax): val for ax, val in checked_maxes.items()}
|
|
902
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
903
|
+
if acquire_lock:
|
|
904
|
+
await stack.enter_async_context(self._motion_lock)
|
|
905
|
+
try:
|
|
906
|
+
await self._backend.move(
|
|
907
|
+
machine_pos,
|
|
908
|
+
speed=speed,
|
|
909
|
+
home_flagged_axes=home_flagged_axes,
|
|
910
|
+
axis_max_speeds=str_maxes,
|
|
911
|
+
)
|
|
912
|
+
except Exception:
|
|
913
|
+
self._log.exception("Move failed")
|
|
914
|
+
self._current_position.clear()
|
|
915
|
+
raise
|
|
916
|
+
else:
|
|
917
|
+
self._current_position.update(target_position)
|
|
918
|
+
|
|
919
|
+
def get_engaged_axes(self) -> Dict[Axis, bool]:
|
|
920
|
+
"""Which axes are engaged and holding."""
|
|
921
|
+
return {Axis[ax]: eng for ax, eng in self._backend.engaged_axes().items()}
|
|
922
|
+
|
|
923
|
+
@property
|
|
924
|
+
def engaged_axes(self) -> Dict[Axis, bool]:
|
|
925
|
+
return self.get_engaged_axes()
|
|
926
|
+
|
|
927
|
+
async def disengage_axes(self, which: List[Axis]) -> None:
|
|
928
|
+
await self._backend.disengage_axes([ot2_axis_to_string(ax) for ax in which])
|
|
929
|
+
|
|
930
|
+
def axis_is_present(self, axis: Axis) -> bool:
|
|
931
|
+
is_ot2 = axis in Axis.ot2_axes()
|
|
932
|
+
if not is_ot2:
|
|
933
|
+
return False
|
|
934
|
+
if axis in Axis.pipette_axes():
|
|
935
|
+
mount = Axis.to_ot2_mount(axis)
|
|
936
|
+
if self.attached_pipettes.get(mount) is None:
|
|
937
|
+
return False
|
|
938
|
+
return True
|
|
939
|
+
|
|
940
|
+
@ExecutionManagerProvider.wait_for_running
|
|
941
|
+
async def _fast_home(self, axes: Sequence[str], margin: float) -> Dict[str, float]:
|
|
942
|
+
converted_axes = "".join(axes)
|
|
943
|
+
return await self._backend.fast_home(converted_axes, margin)
|
|
944
|
+
|
|
945
|
+
async def retract(self, mount: top_types.Mount, margin: float = 10) -> None:
|
|
946
|
+
"""Pull the specified mount up to its home position.
|
|
947
|
+
|
|
948
|
+
Works regardless of critical point or home status.
|
|
949
|
+
"""
|
|
950
|
+
await self.retract_axis(Axis.by_mount(mount), margin)
|
|
951
|
+
|
|
952
|
+
async def retract_axis(self, axis: Axis, margin: float = 10) -> None:
|
|
953
|
+
"""Pull the specified axis up to its home position.
|
|
954
|
+
|
|
955
|
+
Works regardless of critical point or home status.
|
|
956
|
+
"""
|
|
957
|
+
smoothie_ax = (ot2_axis_to_string(axis),)
|
|
958
|
+
|
|
959
|
+
async with self._motion_lock:
|
|
960
|
+
smoothie_pos = await self._fast_home(smoothie_ax, margin)
|
|
961
|
+
self._current_position = self.get_deck_from_machine(
|
|
962
|
+
self._axis_map_from_string_map(smoothie_pos)
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
# Gantry/frame (i.e. not pipette) config API
|
|
966
|
+
@property
|
|
967
|
+
def config(self) -> RobotConfig:
|
|
968
|
+
"""Get the robot's configuration object.
|
|
969
|
+
|
|
970
|
+
:returns .RobotConfig: The object.
|
|
971
|
+
"""
|
|
972
|
+
return self._config
|
|
973
|
+
|
|
974
|
+
@config.setter
|
|
975
|
+
def config(self, config: Union[RobotConfig, OT3Config]) -> None:
|
|
976
|
+
"""Replace the currently-loaded config"""
|
|
977
|
+
if isinstance(config, RobotConfig):
|
|
978
|
+
self._config = config
|
|
979
|
+
else:
|
|
980
|
+
self._log.error("Cannot use an OT-3 config on an OT-2")
|
|
981
|
+
|
|
982
|
+
def get_config(self) -> RobotConfig:
|
|
983
|
+
"""
|
|
984
|
+
Get the robot's configuration object.
|
|
985
|
+
|
|
986
|
+
:returns .RobotConfig: The object.
|
|
987
|
+
"""
|
|
988
|
+
return self.config
|
|
989
|
+
|
|
990
|
+
def set_config(self, config: Union[OT3Config, RobotConfig]) -> None:
|
|
991
|
+
"""Replace the currently-loaded config"""
|
|
992
|
+
if isinstance(config, RobotConfig):
|
|
993
|
+
self.config = config
|
|
994
|
+
else:
|
|
995
|
+
self._log.error("Cannot use an OT-3 config on an OT-2")
|
|
996
|
+
|
|
997
|
+
async def update_config(self, **kwargs: Any) -> None:
|
|
998
|
+
"""Update values of the robot's configuration.
|
|
999
|
+
|
|
1000
|
+
`kwargs` should contain keys of the robot's configuration. For
|
|
1001
|
+
instance, `update_config(log_level='debug)` would change the API
|
|
1002
|
+
server log level to :py:attr:`logging.DEBUG`.
|
|
1003
|
+
|
|
1004
|
+
Documentation on keys can be found in the documentation for
|
|
1005
|
+
:py:class:`.RobotConfig`.
|
|
1006
|
+
"""
|
|
1007
|
+
self._config = replace(self._config, **kwargs)
|
|
1008
|
+
|
|
1009
|
+
@property
|
|
1010
|
+
def hardware_feature_flags(self) -> HardwareFeatureFlags:
|
|
1011
|
+
return self._feature_flags
|
|
1012
|
+
|
|
1013
|
+
@hardware_feature_flags.setter
|
|
1014
|
+
def hardware_feature_flags(self, feature_flags: HardwareFeatureFlags) -> None:
|
|
1015
|
+
self._feature_flags = feature_flags
|
|
1016
|
+
|
|
1017
|
+
async def update_deck_calibration(self, new_transform: RobotCalibration) -> None:
|
|
1018
|
+
pass
|
|
1019
|
+
|
|
1020
|
+
# Pipette action API
|
|
1021
|
+
async def prepare_for_aspirate(
|
|
1022
|
+
self, mount: top_types.Mount, rate: float = 1.0
|
|
1023
|
+
) -> None:
|
|
1024
|
+
"""
|
|
1025
|
+
Prepare the pipette for aspiration.
|
|
1026
|
+
"""
|
|
1027
|
+
instrument = self.get_pipette(mount)
|
|
1028
|
+
self.ready_for_tip_action(instrument, HardwareAction.PREPARE_ASPIRATE, mount)
|
|
1029
|
+
|
|
1030
|
+
if instrument.current_volume == 0:
|
|
1031
|
+
speed = self.plunger_speed(
|
|
1032
|
+
instrument, instrument.blow_out_flow_rate, "aspirate"
|
|
1033
|
+
)
|
|
1034
|
+
bottom = instrument.plunger_positions.bottom
|
|
1035
|
+
target = target_position_from_plunger(mount, bottom, self._current_position)
|
|
1036
|
+
await self._move(
|
|
1037
|
+
target,
|
|
1038
|
+
speed=(speed * rate),
|
|
1039
|
+
home_flagged_axes=False,
|
|
1040
|
+
)
|
|
1041
|
+
instrument.ready_to_aspirate = True
|
|
1042
|
+
|
|
1043
|
+
async def aspirate(
|
|
1044
|
+
self,
|
|
1045
|
+
mount: top_types.Mount,
|
|
1046
|
+
volume: Optional[float] = None,
|
|
1047
|
+
rate: float = 1.0,
|
|
1048
|
+
correction_volume: float = 0.0,
|
|
1049
|
+
) -> None:
|
|
1050
|
+
"""
|
|
1051
|
+
Aspirate a volume of liquid (in microliters/uL) using this pipette.
|
|
1052
|
+
"""
|
|
1053
|
+
aspirate_spec = self.plan_check_aspirate(mount, volume, rate)
|
|
1054
|
+
if not aspirate_spec:
|
|
1055
|
+
return
|
|
1056
|
+
target_pos = target_position_from_plunger(
|
|
1057
|
+
mount,
|
|
1058
|
+
aspirate_spec.plunger_distance,
|
|
1059
|
+
self._current_position,
|
|
1060
|
+
)
|
|
1061
|
+
try:
|
|
1062
|
+
self._backend.set_active_current(
|
|
1063
|
+
{aspirate_spec.axis: aspirate_spec.current}
|
|
1064
|
+
)
|
|
1065
|
+
await self._move(
|
|
1066
|
+
target_pos,
|
|
1067
|
+
speed=aspirate_spec.speed,
|
|
1068
|
+
home_flagged_axes=False,
|
|
1069
|
+
)
|
|
1070
|
+
except Exception:
|
|
1071
|
+
self._log.exception("Aspirate failed")
|
|
1072
|
+
aspirate_spec.instr.set_current_volume(0)
|
|
1073
|
+
raise
|
|
1074
|
+
else:
|
|
1075
|
+
aspirate_spec.instr.add_current_volume(aspirate_spec.volume)
|
|
1076
|
+
|
|
1077
|
+
async def dispense(
|
|
1078
|
+
self,
|
|
1079
|
+
mount: top_types.Mount,
|
|
1080
|
+
volume: Optional[float] = None,
|
|
1081
|
+
rate: float = 1.0,
|
|
1082
|
+
push_out: Optional[float] = None,
|
|
1083
|
+
correction_volume: float = 0.0,
|
|
1084
|
+
is_full_dispense: bool = False,
|
|
1085
|
+
) -> None:
|
|
1086
|
+
"""
|
|
1087
|
+
Dispense a volume of liquid in microliters(uL) using this pipette.
|
|
1088
|
+
"""
|
|
1089
|
+
|
|
1090
|
+
dispense_spec = self.plan_check_dispense(mount, volume, rate, push_out)
|
|
1091
|
+
if not dispense_spec:
|
|
1092
|
+
return
|
|
1093
|
+
target_pos = target_position_from_plunger(
|
|
1094
|
+
mount,
|
|
1095
|
+
dispense_spec.plunger_distance,
|
|
1096
|
+
self._current_position,
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
try:
|
|
1100
|
+
self._backend.set_active_current(
|
|
1101
|
+
{dispense_spec.axis: dispense_spec.current}
|
|
1102
|
+
)
|
|
1103
|
+
await self._move(
|
|
1104
|
+
target_pos,
|
|
1105
|
+
speed=dispense_spec.speed,
|
|
1106
|
+
home_flagged_axes=False,
|
|
1107
|
+
)
|
|
1108
|
+
except Exception:
|
|
1109
|
+
self._log.exception("Dispense failed")
|
|
1110
|
+
dispense_spec.instr.set_current_volume(0)
|
|
1111
|
+
raise
|
|
1112
|
+
else:
|
|
1113
|
+
dispense_spec.instr.remove_current_volume(dispense_spec.volume)
|
|
1114
|
+
|
|
1115
|
+
async def blow_out(
|
|
1116
|
+
self, mount: top_types.Mount, volume: Optional[float] = None
|
|
1117
|
+
) -> None:
|
|
1118
|
+
"""
|
|
1119
|
+
Force any remaining liquid to dispense. The liquid will be dispensed at
|
|
1120
|
+
the current location of pipette
|
|
1121
|
+
"""
|
|
1122
|
+
blowout_spec = self.plan_check_blow_out(mount)
|
|
1123
|
+
self._backend.set_active_current({blowout_spec.axis: blowout_spec.current})
|
|
1124
|
+
target_pos = target_position_from_plunger(
|
|
1125
|
+
mount,
|
|
1126
|
+
blowout_spec.plunger_distance,
|
|
1127
|
+
self._current_position,
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
try:
|
|
1131
|
+
await self._move(
|
|
1132
|
+
target_pos,
|
|
1133
|
+
speed=blowout_spec.speed,
|
|
1134
|
+
home_flagged_axes=False,
|
|
1135
|
+
)
|
|
1136
|
+
except Exception:
|
|
1137
|
+
self._log.exception("Blow out failed")
|
|
1138
|
+
raise
|
|
1139
|
+
finally:
|
|
1140
|
+
blowout_spec.instr.set_current_volume(0)
|
|
1141
|
+
blowout_spec.instr.ready_to_aspirate = False
|
|
1142
|
+
|
|
1143
|
+
async def update_nozzle_configuration_for_mount(
|
|
1144
|
+
self,
|
|
1145
|
+
mount: top_types.Mount,
|
|
1146
|
+
back_left_nozzle: Optional[str],
|
|
1147
|
+
front_right_nozzle: Optional[str],
|
|
1148
|
+
starting_nozzle: Optional[str] = None,
|
|
1149
|
+
) -> None:
|
|
1150
|
+
"""
|
|
1151
|
+
Update a nozzle configuration for a given pipette.
|
|
1152
|
+
|
|
1153
|
+
The expectation of this function is that the back_left_nozzle/front_right_nozzle are the two corners
|
|
1154
|
+
of a rectangle of nozzles. A call to this function that does not follow that schema will result
|
|
1155
|
+
in an error.
|
|
1156
|
+
|
|
1157
|
+
:param mount: A robot mount that the instrument is on.
|
|
1158
|
+
:param back_left_nozzle: A string representing a nozzle name of the form <LETTER><NUMBER> such as 'A1'.
|
|
1159
|
+
:param front_right_nozzle: A string representing a nozzle name of the form <LETTER><NUMBER> such as 'A1'.
|
|
1160
|
+
:param starting_nozzle: A string representing the starting nozzle which will be used as the critical point
|
|
1161
|
+
of the pipette nozzle configuration. By default, the back left nozzle will be the starting nozzle if
|
|
1162
|
+
none is provided.
|
|
1163
|
+
:return: None.
|
|
1164
|
+
|
|
1165
|
+
If none of the nozzle parameters are provided, the nozzle configuration will be reset to default.
|
|
1166
|
+
"""
|
|
1167
|
+
if not back_left_nozzle and not front_right_nozzle and not starting_nozzle:
|
|
1168
|
+
await self.reset_nozzle_configuration(mount)
|
|
1169
|
+
else:
|
|
1170
|
+
assert back_left_nozzle and front_right_nozzle
|
|
1171
|
+
await self.update_nozzle_configuration(
|
|
1172
|
+
mount, back_left_nozzle, front_right_nozzle, starting_nozzle
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
async def tip_pickup_moves(
|
|
1176
|
+
self,
|
|
1177
|
+
mount: top_types.Mount,
|
|
1178
|
+
presses: Optional[int] = None,
|
|
1179
|
+
increment: Optional[float] = None,
|
|
1180
|
+
) -> None:
|
|
1181
|
+
spec, _ = self.plan_check_pick_up_tip(
|
|
1182
|
+
mount=mount, presses=presses, increment=increment
|
|
1183
|
+
)
|
|
1184
|
+
self._backend.set_active_current(spec.plunger_currents)
|
|
1185
|
+
target_absolute = target_position_from_plunger(
|
|
1186
|
+
mount, spec.plunger_prep_pos, self._current_position
|
|
1187
|
+
)
|
|
1188
|
+
await self._move(
|
|
1189
|
+
target_absolute,
|
|
1190
|
+
home_flagged_axes=False,
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
for press in spec.presses:
|
|
1194
|
+
with self._backend.save_current():
|
|
1195
|
+
self._backend.set_active_current(press.current)
|
|
1196
|
+
target_down = target_position_from_relative(
|
|
1197
|
+
mount, press.relative_down, self._current_position
|
|
1198
|
+
)
|
|
1199
|
+
await self._move(target_down, speed=press.speed)
|
|
1200
|
+
target_up = target_position_from_relative(
|
|
1201
|
+
mount, press.relative_up, self._current_position
|
|
1202
|
+
)
|
|
1203
|
+
await self._move(target_up)
|
|
1204
|
+
# neighboring tips tend to get stuck in the space between
|
|
1205
|
+
# the volume chamber and the drop-tip sleeve on p1000.
|
|
1206
|
+
# This extra shake ensures those tips are removed
|
|
1207
|
+
for rel_point, speed in spec.shake_off_list:
|
|
1208
|
+
await self.move_rel(mount, rel_point, speed=speed)
|
|
1209
|
+
|
|
1210
|
+
await self.retract(mount, spec.retract_target)
|
|
1211
|
+
|
|
1212
|
+
async def pick_up_tip(
|
|
1213
|
+
self,
|
|
1214
|
+
mount: top_types.Mount,
|
|
1215
|
+
tip_length: float,
|
|
1216
|
+
presses: Optional[int] = None,
|
|
1217
|
+
increment: Optional[float] = None,
|
|
1218
|
+
prep_after: bool = True,
|
|
1219
|
+
) -> None:
|
|
1220
|
+
"""
|
|
1221
|
+
Pick up tip from current location.
|
|
1222
|
+
"""
|
|
1223
|
+
|
|
1224
|
+
spec, _add_tip_to_instrs = self.plan_check_pick_up_tip(
|
|
1225
|
+
mount=mount, presses=presses, increment=increment, tip_length=tip_length
|
|
1226
|
+
)
|
|
1227
|
+
self._backend.set_active_current(spec.plunger_currents)
|
|
1228
|
+
target_absolute = target_position_from_plunger(
|
|
1229
|
+
mount, spec.plunger_prep_pos, self._current_position
|
|
1230
|
+
)
|
|
1231
|
+
await self._move(
|
|
1232
|
+
target_absolute,
|
|
1233
|
+
home_flagged_axes=False,
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1236
|
+
for press in spec.presses:
|
|
1237
|
+
with self._backend.save_current():
|
|
1238
|
+
self._backend.set_active_current(press.current)
|
|
1239
|
+
target_down = target_position_from_relative(
|
|
1240
|
+
mount, press.relative_down, self._current_position
|
|
1241
|
+
)
|
|
1242
|
+
await self._move(target_down, speed=press.speed)
|
|
1243
|
+
target_up = target_position_from_relative(
|
|
1244
|
+
mount, press.relative_up, self._current_position
|
|
1245
|
+
)
|
|
1246
|
+
await self._move(target_up)
|
|
1247
|
+
# neighboring tips tend to get stuck in the space between
|
|
1248
|
+
# the volume chamber and the drop-tip sleeve on p1000.
|
|
1249
|
+
# This extra shake ensures those tips are removed
|
|
1250
|
+
for rel_point, speed in spec.shake_off_list:
|
|
1251
|
+
await self.move_rel(mount, rel_point, speed=speed)
|
|
1252
|
+
|
|
1253
|
+
await self.retract(mount, spec.retract_target)
|
|
1254
|
+
_add_tip_to_instrs()
|
|
1255
|
+
|
|
1256
|
+
if prep_after:
|
|
1257
|
+
await self.prepare_for_aspirate(mount)
|
|
1258
|
+
|
|
1259
|
+
async def tip_drop_moves(
|
|
1260
|
+
self,
|
|
1261
|
+
mount: top_types.Mount,
|
|
1262
|
+
home_after: bool = True,
|
|
1263
|
+
ignore_plunger: bool = False,
|
|
1264
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
1265
|
+
) -> None:
|
|
1266
|
+
spec, _ = self.plan_check_drop_tip(mount, home_after)
|
|
1267
|
+
|
|
1268
|
+
for move in spec.drop_moves:
|
|
1269
|
+
self._backend.set_active_current(move.current)
|
|
1270
|
+
target_pos = target_position_from_plunger(
|
|
1271
|
+
mount, move.target_position, self._current_position
|
|
1272
|
+
)
|
|
1273
|
+
await self._move(
|
|
1274
|
+
target_pos,
|
|
1275
|
+
speed=move.speed,
|
|
1276
|
+
home_flagged_axes=False,
|
|
1277
|
+
)
|
|
1278
|
+
if move.home_after:
|
|
1279
|
+
smoothie_pos = await self._fast_home(
|
|
1280
|
+
axes=[ot2_axis_to_string(ax) for ax in move.home_axes],
|
|
1281
|
+
margin=move.home_after_safety_margin,
|
|
1282
|
+
)
|
|
1283
|
+
self._current_position = self.get_deck_from_machine(
|
|
1284
|
+
self._axis_map_from_string_map(smoothie_pos)
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
for shake in spec.shake_moves:
|
|
1288
|
+
await self.move_rel(mount, shake[0], speed=shake[1])
|
|
1289
|
+
|
|
1290
|
+
self._backend.set_active_current(spec.ending_current)
|
|
1291
|
+
|
|
1292
|
+
async def drop_tip(self, mount: top_types.Mount, home_after: bool = True) -> None:
|
|
1293
|
+
"""Drop tip at the current location."""
|
|
1294
|
+
await self.tip_drop_moves(mount, home_after)
|
|
1295
|
+
|
|
1296
|
+
# todo(mm, 2024-10-17): Ideally, callers would be able to replicate the behavior
|
|
1297
|
+
# of this method via self.drop_tip_moves() plus other public methods. This
|
|
1298
|
+
# currently prevents that: there is no public equivalent for
|
|
1299
|
+
# instrument.set_current_volume().
|
|
1300
|
+
instrument = self.get_pipette(mount)
|
|
1301
|
+
instrument.set_current_volume(0)
|
|
1302
|
+
|
|
1303
|
+
self.set_current_tiprack_diameter(mount, 0.0)
|
|
1304
|
+
self.remove_tip(mount)
|
|
1305
|
+
|
|
1306
|
+
async def create_simulating_module(
|
|
1307
|
+
self,
|
|
1308
|
+
model: modules.types.ModuleModel,
|
|
1309
|
+
) -> modules.AbstractModule:
|
|
1310
|
+
"""Get a simulating module hardware API interface for the given model."""
|
|
1311
|
+
assert (
|
|
1312
|
+
self.is_simulator
|
|
1313
|
+
), "Cannot build simulating module from non-simulating hardware control API"
|
|
1314
|
+
|
|
1315
|
+
return await self._backend.module_controls.build_module(
|
|
1316
|
+
port="",
|
|
1317
|
+
usb_port=USBPort(name="", port_number=1, port_group=PortGroup.MAIN),
|
|
1318
|
+
type=modules.ModuleType.from_model(model),
|
|
1319
|
+
sim_model=model.value,
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
def get_instrument_max_height(
|
|
1323
|
+
self, mount: top_types.Mount, critical_point: Optional[CriticalPoint] = None
|
|
1324
|
+
) -> float:
|
|
1325
|
+
return PipetteHandlerProvider.instrument_max_height(
|
|
1326
|
+
self, mount, self._config.z_retract_distance, critical_point
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
async def clean_up(self) -> None:
|
|
1330
|
+
"""Get the API ready to stop cleanly."""
|
|
1331
|
+
await self._backend.clean_up()
|
|
1332
|
+
|
|
1333
|
+
MapPayload = TypeVar("MapPayload")
|
|
1334
|
+
|
|
1335
|
+
@staticmethod
|
|
1336
|
+
def _axis_map_from_string_map(
|
|
1337
|
+
input_map: Dict[str, "API.MapPayload"]
|
|
1338
|
+
) -> Dict[Axis, "API.MapPayload"]:
|
|
1339
|
+
return {Axis[k]: v for k, v in input_map.items()}
|
|
1340
|
+
|
|
1341
|
+
def _string_map_from_axis_map(
|
|
1342
|
+
self, input_map: Dict[Axis, "API.MapPayload"]
|
|
1343
|
+
) -> Dict[str, "API.MapPayload"]:
|
|
1344
|
+
return {ot2_axis_to_string(k): v for k, v in input_map.items()}
|
|
1345
|
+
|
|
1346
|
+
def get_estop_state(self) -> EstopState:
|
|
1347
|
+
return EstopState.DISENGAGED
|