opentrons 8.6.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/__init__.py +150 -0
- opentrons/_version.py +34 -0
- opentrons/calibration_storage/__init__.py +54 -0
- opentrons/calibration_storage/deck_configuration.py +62 -0
- opentrons/calibration_storage/encoder_decoder.py +31 -0
- opentrons/calibration_storage/file_operators.py +142 -0
- opentrons/calibration_storage/helpers.py +103 -0
- opentrons/calibration_storage/ot2/__init__.py +34 -0
- opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
- opentrons/calibration_storage/ot2/models/__init__.py +0 -0
- opentrons/calibration_storage/ot2/models/v1.py +149 -0
- opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
- opentrons/calibration_storage/ot2/tip_length.py +281 -0
- opentrons/calibration_storage/ot3/__init__.py +31 -0
- opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
- opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
- opentrons/calibration_storage/ot3/models/__init__.py +0 -0
- opentrons/calibration_storage/ot3/models/v1.py +122 -0
- opentrons/calibration_storage/ot3/module_offset.py +138 -0
- opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
- opentrons/calibration_storage/types.py +45 -0
- opentrons/cli/__init__.py +21 -0
- opentrons/cli/__main__.py +5 -0
- opentrons/cli/analyze.py +501 -0
- opentrons/config/__init__.py +631 -0
- opentrons/config/advanced_settings.py +871 -0
- opentrons/config/defaults_ot2.py +214 -0
- opentrons/config/defaults_ot3.py +499 -0
- opentrons/config/feature_flags.py +86 -0
- opentrons/config/gripper_config.py +55 -0
- opentrons/config/reset.py +203 -0
- opentrons/config/robot_configs.py +187 -0
- opentrons/config/types.py +183 -0
- opentrons/drivers/__init__.py +0 -0
- opentrons/drivers/absorbance_reader/__init__.py +11 -0
- opentrons/drivers/absorbance_reader/abstract.py +72 -0
- opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
- opentrons/drivers/absorbance_reader/driver.py +81 -0
- opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
- opentrons/drivers/absorbance_reader/simulator.py +84 -0
- opentrons/drivers/asyncio/__init__.py +0 -0
- opentrons/drivers/asyncio/communication/__init__.py +22 -0
- opentrons/drivers/asyncio/communication/async_serial.py +183 -0
- opentrons/drivers/asyncio/communication/errors.py +88 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +552 -0
- opentrons/drivers/command_builder.py +102 -0
- opentrons/drivers/flex_stacker/__init__.py +13 -0
- opentrons/drivers/flex_stacker/abstract.py +214 -0
- opentrons/drivers/flex_stacker/driver.py +768 -0
- opentrons/drivers/flex_stacker/errors.py +68 -0
- opentrons/drivers/flex_stacker/simulator.py +309 -0
- opentrons/drivers/flex_stacker/types.py +367 -0
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/drivers/heater_shaker/__init__.py +5 -0
- opentrons/drivers/heater_shaker/abstract.py +76 -0
- opentrons/drivers/heater_shaker/driver.py +204 -0
- opentrons/drivers/heater_shaker/simulator.py +94 -0
- opentrons/drivers/mag_deck/__init__.py +6 -0
- opentrons/drivers/mag_deck/abstract.py +44 -0
- opentrons/drivers/mag_deck/driver.py +208 -0
- opentrons/drivers/mag_deck/simulator.py +63 -0
- opentrons/drivers/rpi_drivers/__init__.py +33 -0
- opentrons/drivers/rpi_drivers/dev_types.py +94 -0
- opentrons/drivers/rpi_drivers/gpio.py +282 -0
- opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
- opentrons/drivers/rpi_drivers/interfaces.py +15 -0
- opentrons/drivers/rpi_drivers/types.py +364 -0
- opentrons/drivers/rpi_drivers/usb.py +102 -0
- opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
- opentrons/drivers/serial_communication.py +151 -0
- opentrons/drivers/smoothie_drivers/__init__.py +4 -0
- opentrons/drivers/smoothie_drivers/connection.py +51 -0
- opentrons/drivers/smoothie_drivers/constants.py +121 -0
- opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
- opentrons/drivers/smoothie_drivers/errors.py +49 -0
- opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
- opentrons/drivers/smoothie_drivers/simulator.py +99 -0
- opentrons/drivers/smoothie_drivers/types.py +16 -0
- opentrons/drivers/temp_deck/__init__.py +10 -0
- opentrons/drivers/temp_deck/abstract.py +54 -0
- opentrons/drivers/temp_deck/driver.py +197 -0
- opentrons/drivers/temp_deck/simulator.py +57 -0
- opentrons/drivers/thermocycler/__init__.py +12 -0
- opentrons/drivers/thermocycler/abstract.py +99 -0
- opentrons/drivers/thermocycler/driver.py +395 -0
- opentrons/drivers/thermocycler/simulator.py +126 -0
- opentrons/drivers/types.py +107 -0
- opentrons/drivers/utils.py +222 -0
- opentrons/execute.py +742 -0
- opentrons/hardware_control/__init__.py +65 -0
- opentrons/hardware_control/__main__.py +77 -0
- opentrons/hardware_control/adapters.py +98 -0
- opentrons/hardware_control/api.py +1347 -0
- opentrons/hardware_control/backends/__init__.py +7 -0
- opentrons/hardware_control/backends/controller.py +400 -0
- opentrons/hardware_control/backends/errors.py +9 -0
- opentrons/hardware_control/backends/estop_state.py +164 -0
- opentrons/hardware_control/backends/flex_protocol.py +497 -0
- opentrons/hardware_control/backends/ot3controller.py +1930 -0
- opentrons/hardware_control/backends/ot3simulator.py +900 -0
- opentrons/hardware_control/backends/ot3utils.py +664 -0
- opentrons/hardware_control/backends/simulator.py +442 -0
- opentrons/hardware_control/backends/status_bar_state.py +240 -0
- opentrons/hardware_control/backends/subsystem_manager.py +431 -0
- opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
- opentrons/hardware_control/backends/types.py +14 -0
- opentrons/hardware_control/constants.py +6 -0
- opentrons/hardware_control/dev_types.py +125 -0
- opentrons/hardware_control/emulation/__init__.py +0 -0
- opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
- opentrons/hardware_control/emulation/app.py +56 -0
- opentrons/hardware_control/emulation/connection_handler.py +38 -0
- opentrons/hardware_control/emulation/heater_shaker.py +150 -0
- opentrons/hardware_control/emulation/magdeck.py +60 -0
- opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
- opentrons/hardware_control/emulation/module_server/client.py +78 -0
- opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
- opentrons/hardware_control/emulation/module_server/models.py +31 -0
- opentrons/hardware_control/emulation/module_server/server.py +110 -0
- opentrons/hardware_control/emulation/parser.py +74 -0
- opentrons/hardware_control/emulation/proxy.py +241 -0
- opentrons/hardware_control/emulation/run_emulator.py +68 -0
- opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
- opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
- opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
- opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
- opentrons/hardware_control/emulation/settings.py +119 -0
- opentrons/hardware_control/emulation/simulations.py +133 -0
- opentrons/hardware_control/emulation/smoothie.py +192 -0
- opentrons/hardware_control/emulation/tempdeck.py +69 -0
- opentrons/hardware_control/emulation/thermocycler.py +128 -0
- opentrons/hardware_control/emulation/types.py +10 -0
- opentrons/hardware_control/emulation/util.py +38 -0
- opentrons/hardware_control/errors.py +43 -0
- opentrons/hardware_control/execution_manager.py +164 -0
- opentrons/hardware_control/instruments/__init__.py +5 -0
- opentrons/hardware_control/instruments/instrument_abc.py +39 -0
- opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
- opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
- opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
- opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
- opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
- opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
- opentrons/hardware_control/module_control.py +332 -0
- opentrons/hardware_control/modules/__init__.py +69 -0
- opentrons/hardware_control/modules/absorbance_reader.py +373 -0
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/flex_stacker.py +948 -0
- opentrons/hardware_control/modules/heater_shaker.py +426 -0
- opentrons/hardware_control/modules/lid_temp_status.py +35 -0
- opentrons/hardware_control/modules/magdeck.py +233 -0
- opentrons/hardware_control/modules/mod_abc.py +245 -0
- opentrons/hardware_control/modules/module_calibration.py +93 -0
- opentrons/hardware_control/modules/plate_temp_status.py +61 -0
- opentrons/hardware_control/modules/tempdeck.py +299 -0
- opentrons/hardware_control/modules/thermocycler.py +731 -0
- opentrons/hardware_control/modules/types.py +417 -0
- opentrons/hardware_control/modules/update.py +255 -0
- opentrons/hardware_control/modules/utils.py +73 -0
- opentrons/hardware_control/motion_utilities.py +318 -0
- opentrons/hardware_control/nozzle_manager.py +422 -0
- opentrons/hardware_control/ot3_calibration.py +1171 -0
- opentrons/hardware_control/ot3api.py +3227 -0
- opentrons/hardware_control/pause_manager.py +31 -0
- opentrons/hardware_control/poller.py +112 -0
- opentrons/hardware_control/protocols/__init__.py +106 -0
- opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
- opentrons/hardware_control/protocols/calibratable.py +45 -0
- opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
- opentrons/hardware_control/protocols/configurable.py +48 -0
- opentrons/hardware_control/protocols/event_sourcer.py +18 -0
- opentrons/hardware_control/protocols/execution_controllable.py +33 -0
- opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
- opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
- opentrons/hardware_control/protocols/gripper_controller.py +55 -0
- opentrons/hardware_control/protocols/hardware_manager.py +51 -0
- opentrons/hardware_control/protocols/identifiable.py +16 -0
- opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
- opentrons/hardware_control/protocols/liquid_handler.py +266 -0
- opentrons/hardware_control/protocols/module_provider.py +16 -0
- opentrons/hardware_control/protocols/motion_controller.py +243 -0
- opentrons/hardware_control/protocols/position_estimator.py +45 -0
- opentrons/hardware_control/protocols/simulatable.py +10 -0
- opentrons/hardware_control/protocols/stoppable.py +9 -0
- opentrons/hardware_control/protocols/types.py +27 -0
- opentrons/hardware_control/robot_calibration.py +224 -0
- opentrons/hardware_control/scripts/README.md +28 -0
- opentrons/hardware_control/scripts/__init__.py +1 -0
- opentrons/hardware_control/scripts/gripper_control.py +208 -0
- opentrons/hardware_control/scripts/ot3gripper +7 -0
- opentrons/hardware_control/scripts/ot3repl +7 -0
- opentrons/hardware_control/scripts/repl.py +187 -0
- opentrons/hardware_control/scripts/tc_control.py +97 -0
- opentrons/hardware_control/simulator_setup.py +260 -0
- opentrons/hardware_control/thread_manager.py +431 -0
- opentrons/hardware_control/threaded_async_lock.py +97 -0
- opentrons/hardware_control/types.py +792 -0
- opentrons/hardware_control/util.py +234 -0
- opentrons/legacy_broker.py +53 -0
- opentrons/legacy_commands/__init__.py +1 -0
- opentrons/legacy_commands/commands.py +483 -0
- opentrons/legacy_commands/helpers.py +153 -0
- opentrons/legacy_commands/module_commands.py +215 -0
- opentrons/legacy_commands/protocol_commands.py +54 -0
- opentrons/legacy_commands/publisher.py +155 -0
- opentrons/legacy_commands/robot_commands.py +51 -0
- opentrons/legacy_commands/types.py +1115 -0
- opentrons/motion_planning/__init__.py +32 -0
- opentrons/motion_planning/adjacent_slots_getters.py +168 -0
- opentrons/motion_planning/deck_conflict.py +396 -0
- opentrons/motion_planning/errors.py +35 -0
- opentrons/motion_planning/types.py +42 -0
- opentrons/motion_planning/waypoints.py +218 -0
- opentrons/ordered_set.py +138 -0
- opentrons/protocol_api/__init__.py +105 -0
- opentrons/protocol_api/_liquid.py +157 -0
- opentrons/protocol_api/_liquid_properties.py +814 -0
- opentrons/protocol_api/_nozzle_layout.py +31 -0
- opentrons/protocol_api/_parameter_context.py +300 -0
- opentrons/protocol_api/_parameters.py +31 -0
- opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
- opentrons/protocol_api/_types.py +43 -0
- opentrons/protocol_api/config.py +23 -0
- opentrons/protocol_api/core/__init__.py +23 -0
- opentrons/protocol_api/core/common.py +33 -0
- opentrons/protocol_api/core/core_map.py +74 -0
- opentrons/protocol_api/core/engine/__init__.py +22 -0
- opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
- opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
- opentrons/protocol_api/core/engine/exceptions.py +19 -0
- opentrons/protocol_api/core/engine/instrument.py +2391 -0
- opentrons/protocol_api/core/engine/labware.py +238 -0
- opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
- opentrons/protocol_api/core/engine/module_core.py +1025 -0
- opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
- opentrons/protocol_api/core/engine/point_calculations.py +64 -0
- opentrons/protocol_api/core/engine/protocol.py +1153 -0
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/stringify.py +74 -0
- opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
- opentrons/protocol_api/core/engine/well.py +241 -0
- opentrons/protocol_api/core/instrument.py +459 -0
- opentrons/protocol_api/core/labware.py +151 -0
- opentrons/protocol_api/core/legacy/__init__.py +11 -0
- opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
- opentrons/protocol_api/core/legacy/deck.py +369 -0
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
- opentrons/protocol_api/core/legacy/load_info.py +67 -0
- opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
- opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
- opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
- opentrons/protocol_api/core/module.py +484 -0
- opentrons/protocol_api/core/protocol.py +311 -0
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/core/well.py +116 -0
- opentrons/protocol_api/core/well_grid.py +45 -0
- opentrons/protocol_api/create_protocol_context.py +177 -0
- opentrons/protocol_api/deck.py +223 -0
- opentrons/protocol_api/disposal_locations.py +244 -0
- opentrons/protocol_api/instrument_context.py +3212 -0
- opentrons/protocol_api/labware.py +1579 -0
- opentrons/protocol_api/module_contexts.py +1425 -0
- opentrons/protocol_api/module_validation_and_errors.py +61 -0
- opentrons/protocol_api/protocol_context.py +1688 -0
- opentrons/protocol_api/robot_context.py +303 -0
- opentrons/protocol_api/validation.py +761 -0
- opentrons/protocol_engine/__init__.py +155 -0
- opentrons/protocol_engine/actions/__init__.py +65 -0
- opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
- opentrons/protocol_engine/actions/action_handler.py +13 -0
- opentrons/protocol_engine/actions/actions.py +302 -0
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/__init__.py +5 -0
- opentrons/protocol_engine/clients/sync_client.py +174 -0
- opentrons/protocol_engine/clients/transports.py +197 -0
- opentrons/protocol_engine/commands/__init__.py +757 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
- opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
- opentrons/protocol_engine/commands/aspirate.py +244 -0
- opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
- opentrons/protocol_engine/commands/blow_out.py +146 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
- opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
- opentrons/protocol_engine/commands/command.py +308 -0
- opentrons/protocol_engine/commands/command_unions.py +974 -0
- opentrons/protocol_engine/commands/comment.py +57 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
- opentrons/protocol_engine/commands/custom.py +67 -0
- opentrons/protocol_engine/commands/dispense.py +194 -0
- opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +232 -0
- opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
- opentrons/protocol_engine/commands/hash_command_params.py +38 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
- opentrons/protocol_engine/commands/home.py +100 -0
- opentrons/protocol_engine/commands/identify_module.py +86 -0
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +464 -0
- opentrons/protocol_engine/commands/load_labware.py +210 -0
- opentrons/protocol_engine/commands/load_lid.py +154 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
- opentrons/protocol_engine/commands/load_liquid.py +95 -0
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +223 -0
- opentrons/protocol_engine/commands/load_pipette.py +167 -0
- opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
- opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
- opentrons/protocol_engine/commands/move_labware.py +546 -0
- opentrons/protocol_engine/commands/move_relative.py +102 -0
- opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
- opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
- opentrons/protocol_engine/commands/move_to_well.py +119 -0
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
- opentrons/protocol_engine/commands/pipetting_common.py +443 -0
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
- opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
- opentrons/protocol_engine/commands/reload_labware.py +90 -0
- opentrons/protocol_engine/commands/retract_axis.py +75 -0
- opentrons/protocol_engine/commands/robot/__init__.py +70 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/save_position.py +109 -0
- opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
- opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
- opentrons/protocol_engine/commands/set_status_bar.py +89 -0
- opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
- opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
- opentrons/protocol_engine/commands/touch_tip.py +189 -0
- opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
- opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
- opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
- opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
- opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
- opentrons/protocol_engine/create_protocol_engine.py +193 -0
- opentrons/protocol_engine/engine_support.py +28 -0
- opentrons/protocol_engine/error_recovery_policy.py +81 -0
- opentrons/protocol_engine/errors/__init__.py +191 -0
- opentrons/protocol_engine/errors/error_occurrence.py +182 -0
- opentrons/protocol_engine/errors/exceptions.py +1308 -0
- opentrons/protocol_engine/execution/__init__.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +216 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
- opentrons/protocol_engine/execution/door_watcher.py +119 -0
- opentrons/protocol_engine/execution/equipment.py +819 -0
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +686 -0
- opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
- opentrons/protocol_engine/execution/labware_movement.py +297 -0
- opentrons/protocol_engine/execution/movement.py +349 -0
- opentrons/protocol_engine/execution/pipetting.py +607 -0
- opentrons/protocol_engine/execution/queue_worker.py +86 -0
- opentrons/protocol_engine/execution/rail_lights.py +25 -0
- opentrons/protocol_engine/execution/run_control.py +33 -0
- opentrons/protocol_engine/execution/status_bar.py +34 -0
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
- opentrons/protocol_engine/execution/tip_handler.py +550 -0
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/notes/__init__.py +17 -0
- opentrons/protocol_engine/notes/notes.py +59 -0
- opentrons/protocol_engine/plugins.py +104 -0
- opentrons/protocol_engine/protocol_engine.py +683 -0
- opentrons/protocol_engine/resources/__init__.py +26 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +58 -0
- opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
- opentrons/protocol_engine/resources/labware_validation.py +73 -0
- opentrons/protocol_engine/resources/model_utils.py +32 -0
- opentrons/protocol_engine/resources/module_data_provider.py +44 -0
- opentrons/protocol_engine/resources/ot3_validation.py +21 -0
- opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
- opentrons/protocol_engine/slot_standardization.py +128 -0
- opentrons/protocol_engine/state/__init__.py +1 -0
- opentrons/protocol_engine/state/_abstract_store.py +27 -0
- opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_move_types.py +83 -0
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +699 -0
- opentrons/protocol_engine/state/command_history.py +309 -0
- opentrons/protocol_engine/state/commands.py +1158 -0
- opentrons/protocol_engine/state/config.py +39 -0
- opentrons/protocol_engine/state/files.py +57 -0
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/geometry.py +2359 -0
- opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
- opentrons/protocol_engine/state/labware.py +1459 -0
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +73 -0
- opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
- opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
- opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
- opentrons/protocol_engine/state/modules.py +1500 -0
- opentrons/protocol_engine/state/motion.py +373 -0
- opentrons/protocol_engine/state/pipettes.py +905 -0
- opentrons/protocol_engine/state/state.py +421 -0
- opentrons/protocol_engine/state/state_summary.py +36 -0
- opentrons/protocol_engine/state/tips.py +420 -0
- opentrons/protocol_engine/state/update_types.py +904 -0
- opentrons/protocol_engine/state/wells.py +290 -0
- opentrons/protocol_engine/types/__init__.py +308 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +81 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +131 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +303 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/__init__.py +37 -0
- opentrons/protocol_reader/extract_labware_definitions.py +66 -0
- opentrons/protocol_reader/file_format_validator.py +152 -0
- opentrons/protocol_reader/file_hasher.py +27 -0
- opentrons/protocol_reader/file_identifier.py +284 -0
- opentrons/protocol_reader/file_reader_writer.py +90 -0
- opentrons/protocol_reader/input_file.py +16 -0
- opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
- opentrons/protocol_reader/protocol_reader.py +188 -0
- opentrons/protocol_reader/protocol_source.py +124 -0
- opentrons/protocol_reader/role_analyzer.py +86 -0
- opentrons/protocol_runner/__init__.py +26 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
- opentrons/protocol_runner/json_file_reader.py +55 -0
- opentrons/protocol_runner/json_translator.py +314 -0
- opentrons/protocol_runner/legacy_command_mapper.py +848 -0
- opentrons/protocol_runner/legacy_context_plugin.py +116 -0
- opentrons/protocol_runner/protocol_runner.py +530 -0
- opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
- opentrons/protocol_runner/run_orchestrator.py +496 -0
- opentrons/protocol_runner/task_queue.py +95 -0
- opentrons/protocols/__init__.py +6 -0
- opentrons/protocols/advanced_control/__init__.py +0 -0
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +60 -0
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +180 -0
- opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
- opentrons/protocols/api_support/__init__.py +0 -0
- opentrons/protocols/api_support/constants.py +8 -0
- opentrons/protocols/api_support/deck_type.py +110 -0
- opentrons/protocols/api_support/definitions.py +18 -0
- opentrons/protocols/api_support/instrument.py +151 -0
- opentrons/protocols/api_support/labware_like.py +233 -0
- opentrons/protocols/api_support/tip_tracker.py +175 -0
- opentrons/protocols/api_support/types.py +32 -0
- opentrons/protocols/api_support/util.py +403 -0
- opentrons/protocols/bundle.py +89 -0
- opentrons/protocols/duration/__init__.py +4 -0
- opentrons/protocols/duration/errors.py +5 -0
- opentrons/protocols/duration/estimator.py +628 -0
- opentrons/protocols/execution/__init__.py +0 -0
- opentrons/protocols/execution/dev_types.py +181 -0
- opentrons/protocols/execution/errors.py +40 -0
- opentrons/protocols/execution/execute.py +84 -0
- opentrons/protocols/execution/execute_json_v3.py +275 -0
- opentrons/protocols/execution/execute_json_v4.py +359 -0
- opentrons/protocols/execution/execute_json_v5.py +28 -0
- opentrons/protocols/execution/execute_python.py +169 -0
- opentrons/protocols/execution/json_dispatchers.py +87 -0
- opentrons/protocols/execution/types.py +7 -0
- opentrons/protocols/geometry/__init__.py +0 -0
- opentrons/protocols/geometry/planning.py +297 -0
- opentrons/protocols/labware.py +312 -0
- opentrons/protocols/models/__init__.py +0 -0
- opentrons/protocols/models/json_protocol.py +679 -0
- opentrons/protocols/parameters/__init__.py +0 -0
- opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
- opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
- opentrons/protocols/parameters/exceptions.py +34 -0
- opentrons/protocols/parameters/parameter_definition.py +272 -0
- opentrons/protocols/parameters/types.py +17 -0
- opentrons/protocols/parameters/validation.py +267 -0
- opentrons/protocols/parse.py +671 -0
- opentrons/protocols/types.py +159 -0
- opentrons/py.typed +0 -0
- opentrons/resources/scripts/lpc21isp +0 -0
- opentrons/resources/smoothie-edge-8414642.hex +23010 -0
- opentrons/simulate.py +1065 -0
- opentrons/system/__init__.py +6 -0
- opentrons/system/camera.py +51 -0
- opentrons/system/log_control.py +59 -0
- opentrons/system/nmcli.py +856 -0
- opentrons/system/resin.py +24 -0
- opentrons/system/smoothie_update.py +15 -0
- opentrons/system/wifi.py +204 -0
- opentrons/tools/__init__.py +0 -0
- opentrons/tools/args_handler.py +22 -0
- opentrons/tools/write_pipette_memory.py +157 -0
- opentrons/types.py +618 -0
- opentrons/util/__init__.py +1 -0
- opentrons/util/async_helpers.py +166 -0
- opentrons/util/broker.py +84 -0
- opentrons/util/change_notifier.py +47 -0
- opentrons/util/entrypoint_util.py +278 -0
- opentrons/util/get_union_elements.py +26 -0
- opentrons/util/helpers.py +6 -0
- opentrons/util/linal.py +178 -0
- opentrons/util/logging_config.py +265 -0
- opentrons/util/logging_queue_handler.py +61 -0
- opentrons/util/performance_helpers.py +157 -0
- opentrons-8.6.0a1.dist-info/METADATA +37 -0
- opentrons-8.6.0a1.dist-info/RECORD +600 -0
- opentrons-8.6.0a1.dist-info/WHEEL +4 -0
- opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
- opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Awaitable,
|
|
8
|
+
Callable,
|
|
9
|
+
Dict,
|
|
10
|
+
List,
|
|
11
|
+
Literal,
|
|
12
|
+
Optional,
|
|
13
|
+
Mapping,
|
|
14
|
+
cast,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from opentrons.drivers.flex_stacker.types import (
|
|
18
|
+
AxisParams,
|
|
19
|
+
Direction,
|
|
20
|
+
LEDColor,
|
|
21
|
+
LEDPattern,
|
|
22
|
+
MoveParams,
|
|
23
|
+
MoveResult,
|
|
24
|
+
StackerAxis,
|
|
25
|
+
StallGuardParams,
|
|
26
|
+
TOFDetection,
|
|
27
|
+
TOFMeasurementResult,
|
|
28
|
+
TOFSensor,
|
|
29
|
+
HardwareRevision,
|
|
30
|
+
TOFSensorMode,
|
|
31
|
+
TOFSensorState,
|
|
32
|
+
TOFSensorStatus,
|
|
33
|
+
)
|
|
34
|
+
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
35
|
+
from opentrons.drivers.flex_stacker.driver import (
|
|
36
|
+
FlexStackerDriver,
|
|
37
|
+
)
|
|
38
|
+
from opentrons.drivers.flex_stacker.abstract import AbstractFlexStackerDriver
|
|
39
|
+
from opentrons.drivers.flex_stacker.simulator import SimulatingDriver
|
|
40
|
+
from opentrons.hardware_control.execution_manager import ExecutionManager
|
|
41
|
+
from opentrons.hardware_control.poller import Reader, Poller
|
|
42
|
+
from opentrons.hardware_control.modules import mod_abc, update
|
|
43
|
+
from opentrons.hardware_control.modules.types import (
|
|
44
|
+
FlexStackerStatus,
|
|
45
|
+
HopperDoorState,
|
|
46
|
+
LatchState,
|
|
47
|
+
ModuleDisconnectedCallback,
|
|
48
|
+
ModuleType,
|
|
49
|
+
PlatformState,
|
|
50
|
+
StackerAxisState,
|
|
51
|
+
UploadFunction,
|
|
52
|
+
LiveData,
|
|
53
|
+
FlexStackerData,
|
|
54
|
+
)
|
|
55
|
+
from opentrons.hardware_control.types import StatusBarState, StatusBarUpdateEvent
|
|
56
|
+
from opentrons.config import feature_flags as ff
|
|
57
|
+
|
|
58
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
59
|
+
FlexStackerStallError,
|
|
60
|
+
FlexStackerShuttleMissingError,
|
|
61
|
+
FlexStackerShuttleLabwareError,
|
|
62
|
+
FlexStackerHopperLabwareError,
|
|
63
|
+
FlexStackerShuttleNotEmptyError,
|
|
64
|
+
)
|
|
65
|
+
from opentrons_shared_data.module import load_tof_baseline_data
|
|
66
|
+
|
|
67
|
+
log = logging.getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
POLL_PERIOD = 2.0
|
|
70
|
+
SIMULATING_POLL_PERIOD = POLL_PERIOD / 20.0
|
|
71
|
+
|
|
72
|
+
DFU_PID = "df11"
|
|
73
|
+
|
|
74
|
+
# Maximum distance in mm the axis can travel.
|
|
75
|
+
MAX_TRAVEL = {
|
|
76
|
+
StackerAxis.X: 194.0,
|
|
77
|
+
StackerAxis.Z: 139.5,
|
|
78
|
+
StackerAxis.L: 22.0,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Min/Max height in mm of labware stack to store/dispense
|
|
82
|
+
MIN_LABWARE_HEIGHT = 4.0
|
|
83
|
+
MAX_LABWARE_HEIGHT = 102.5
|
|
84
|
+
|
|
85
|
+
# The offset in mm to subtract from MAX_TRAVEL when moving an axis before we home.
|
|
86
|
+
# This lets us use `move_axis` to move fast, leaving the axis OFFSET mm
|
|
87
|
+
# from the limit switch. Then we can use `home_axis` to move the axis the rest
|
|
88
|
+
# of the way until we trigger the expected limit switch.
|
|
89
|
+
HOME_OFFSET_SM = 5.0
|
|
90
|
+
HOME_OFFSET_MD = 10.0
|
|
91
|
+
|
|
92
|
+
# The labware platform will contact the labware this mm before the platform
|
|
93
|
+
# touches the +Z endstop.
|
|
94
|
+
PLATFORM_OFFSET = 2.25
|
|
95
|
+
|
|
96
|
+
# Should put the bottom of the plate above this mm above the latch when dispensing.
|
|
97
|
+
# Should put the bottom of the plate this mm below the latch when storing.
|
|
98
|
+
LATCH_CLEARANCE = 2.5
|
|
99
|
+
|
|
100
|
+
# TOF Baseline Configs
|
|
101
|
+
# These are generated manually with the help of the tof_analysis.py tool
|
|
102
|
+
# Which can be found in `hardware_testing/tools/tof-analysis/README.md` where
|
|
103
|
+
# it goes over using use the tool.
|
|
104
|
+
TOF_DETECTION_CONFIG = {
|
|
105
|
+
TOFSensor.X: {
|
|
106
|
+
Direction.EXTEND: TOFDetection(
|
|
107
|
+
TOFSensor.X,
|
|
108
|
+
zones=[5, 6, 7],
|
|
109
|
+
bins=list(range(30, 40)),
|
|
110
|
+
threshold=1000,
|
|
111
|
+
),
|
|
112
|
+
Direction.RETRACT: TOFDetection(
|
|
113
|
+
TOFSensor.X,
|
|
114
|
+
zones=[5, 6, 7],
|
|
115
|
+
bins=list(range(17, 30)),
|
|
116
|
+
threshold=1000,
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
TOFSensor.Z: {
|
|
120
|
+
Direction.EXTEND: TOFDetection(
|
|
121
|
+
TOFSensor.Z,
|
|
122
|
+
zones=[1, 2, 3],
|
|
123
|
+
bins=list(range(15, 63)),
|
|
124
|
+
threshold=1000,
|
|
125
|
+
),
|
|
126
|
+
Direction.RETRACT: TOFDetection(
|
|
127
|
+
TOFSensor.Z,
|
|
128
|
+
zones=[1, 2, 3],
|
|
129
|
+
bins=list(range(15, 63)),
|
|
130
|
+
threshold=1000,
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Stallguard defaults
|
|
137
|
+
STALLGUARD_CONFIG = {
|
|
138
|
+
StackerAxis.X: StallGuardParams(StackerAxis.X, True, 0),
|
|
139
|
+
StackerAxis.Z: StallGuardParams(StackerAxis.Z, True, 2),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Motion Parameter defaults
|
|
143
|
+
STACKER_MOTION_CONFIG = {
|
|
144
|
+
StackerAxis.X: {
|
|
145
|
+
"home": AxisParams(
|
|
146
|
+
run_current=1.5, # Amps RMS
|
|
147
|
+
hold_current=0.75,
|
|
148
|
+
move_params=MoveParams(
|
|
149
|
+
max_speed=10.0, # mm/s
|
|
150
|
+
acceleration=100.0, # mm/s^2
|
|
151
|
+
max_speed_discont=40.0, # mm/s
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
"move": AxisParams(
|
|
155
|
+
run_current=1.2,
|
|
156
|
+
hold_current=0.75,
|
|
157
|
+
move_params=MoveParams(
|
|
158
|
+
max_speed=200.0,
|
|
159
|
+
acceleration=1500.0,
|
|
160
|
+
max_speed_discont=40.0,
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
},
|
|
164
|
+
StackerAxis.Z: {
|
|
165
|
+
"home": AxisParams(
|
|
166
|
+
run_current=1.5,
|
|
167
|
+
hold_current=1.5,
|
|
168
|
+
move_params=MoveParams(
|
|
169
|
+
max_speed=10.0,
|
|
170
|
+
acceleration=100.0,
|
|
171
|
+
max_speed_discont=25.0,
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
"move": AxisParams(
|
|
175
|
+
run_current=1.5,
|
|
176
|
+
hold_current=1.5,
|
|
177
|
+
move_params=MoveParams(
|
|
178
|
+
max_speed=150.0,
|
|
179
|
+
acceleration=500.0,
|
|
180
|
+
max_speed_discont=25.0,
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
},
|
|
184
|
+
StackerAxis.L: {
|
|
185
|
+
"home": AxisParams(
|
|
186
|
+
run_current=1.2,
|
|
187
|
+
hold_current=0.5,
|
|
188
|
+
move_params=MoveParams(
|
|
189
|
+
max_speed=100.0,
|
|
190
|
+
acceleration=800.0,
|
|
191
|
+
max_speed_discont=40.0,
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
"move": AxisParams(
|
|
195
|
+
run_current=1.2,
|
|
196
|
+
hold_current=0.5,
|
|
197
|
+
move_params=MoveParams(
|
|
198
|
+
max_speed=100.0,
|
|
199
|
+
acceleration=800.0,
|
|
200
|
+
max_speed_discont=40.0,
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class FlexStacker(mod_abc.AbstractModule):
|
|
208
|
+
"""Hardware control interface for an attached Flex-Stacker module."""
|
|
209
|
+
|
|
210
|
+
MODULE_TYPE = ModuleType.FLEX_STACKER
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
async def build(
|
|
214
|
+
cls,
|
|
215
|
+
port: str,
|
|
216
|
+
usb_port: USBPort,
|
|
217
|
+
hw_control_loop: asyncio.AbstractEventLoop,
|
|
218
|
+
execution_manager: Optional[ExecutionManager] = None,
|
|
219
|
+
poll_interval_seconds: Optional[float] = None,
|
|
220
|
+
simulating: bool = False,
|
|
221
|
+
sim_model: Optional[str] = None,
|
|
222
|
+
sim_serial_number: Optional[str] = None,
|
|
223
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
224
|
+
) -> "FlexStacker":
|
|
225
|
+
"""
|
|
226
|
+
Build a FlexStacker
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
port: The port to connect to
|
|
230
|
+
usb_port: USB Port
|
|
231
|
+
execution_manager: Execution manager.
|
|
232
|
+
hw_control_loop: The event loop running in the hardware control thread.
|
|
233
|
+
poll_interval_seconds: Poll interval override.
|
|
234
|
+
simulating: whether to build a simulating driver
|
|
235
|
+
loop: Loop
|
|
236
|
+
sim_model: The model name used by simulator
|
|
237
|
+
disconnected_callback: Callback to inform the module controller that the device was disconnected
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
FlexStacker instance
|
|
241
|
+
"""
|
|
242
|
+
driver: AbstractFlexStackerDriver
|
|
243
|
+
if not simulating:
|
|
244
|
+
driver = await FlexStackerDriver.create(port=port, loop=hw_control_loop)
|
|
245
|
+
poll_interval_seconds = poll_interval_seconds or POLL_PERIOD
|
|
246
|
+
else:
|
|
247
|
+
driver = SimulatingDriver(serial_number=sim_serial_number)
|
|
248
|
+
poll_interval_seconds = poll_interval_seconds or SIMULATING_POLL_PERIOD
|
|
249
|
+
|
|
250
|
+
reader = FlexStackerReader(driver=driver)
|
|
251
|
+
poller = Poller(reader=reader, interval=poll_interval_seconds)
|
|
252
|
+
module = cls(
|
|
253
|
+
port=port,
|
|
254
|
+
usb_port=usb_port,
|
|
255
|
+
driver=driver,
|
|
256
|
+
reader=reader,
|
|
257
|
+
poller=poller,
|
|
258
|
+
device_info=(await driver.get_device_info()).to_dict(),
|
|
259
|
+
hw_control_loop=hw_control_loop,
|
|
260
|
+
execution_manager=execution_manager,
|
|
261
|
+
disconnected_callback=disconnected_callback,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Set initialized callback
|
|
265
|
+
reader.set_initialized_callback(module._initialized_callback)
|
|
266
|
+
|
|
267
|
+
# Enable stallguard
|
|
268
|
+
for axis, config in STALLGUARD_CONFIG.items():
|
|
269
|
+
await driver.set_stallguard_threshold(
|
|
270
|
+
axis, config.enabled, config.threshold
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
await poller.start()
|
|
275
|
+
except Exception:
|
|
276
|
+
log.exception(f"First read of Flex-Stacker on port {port} failed")
|
|
277
|
+
|
|
278
|
+
return module
|
|
279
|
+
|
|
280
|
+
def __init__(
|
|
281
|
+
self,
|
|
282
|
+
port: str,
|
|
283
|
+
usb_port: USBPort,
|
|
284
|
+
driver: AbstractFlexStackerDriver,
|
|
285
|
+
reader: FlexStackerReader,
|
|
286
|
+
poller: Poller,
|
|
287
|
+
device_info: Mapping[str, str],
|
|
288
|
+
hw_control_loop: asyncio.AbstractEventLoop,
|
|
289
|
+
execution_manager: Optional[ExecutionManager] = None,
|
|
290
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
291
|
+
):
|
|
292
|
+
super().__init__(
|
|
293
|
+
port=port,
|
|
294
|
+
usb_port=usb_port,
|
|
295
|
+
hw_control_loop=hw_control_loop,
|
|
296
|
+
execution_manager=execution_manager,
|
|
297
|
+
disconnected_callback=disconnected_callback,
|
|
298
|
+
)
|
|
299
|
+
self._device_info = device_info
|
|
300
|
+
self._driver = driver
|
|
301
|
+
self._reader = reader
|
|
302
|
+
self._poller = poller
|
|
303
|
+
self._stall_detected = False
|
|
304
|
+
self._stacker_status = FlexStackerStatus.IDLE
|
|
305
|
+
self._last_status_bar_event: Optional[StatusBarUpdateEvent] = None
|
|
306
|
+
self._should_identify = False
|
|
307
|
+
|
|
308
|
+
async def _initialized_callback(self) -> None:
|
|
309
|
+
"""Called by the reader once the module is initialized."""
|
|
310
|
+
if self._last_status_bar_event:
|
|
311
|
+
await self._handle_status_bar_event(self._last_status_bar_event)
|
|
312
|
+
|
|
313
|
+
async def cleanup(self) -> None:
|
|
314
|
+
"""Stop the poller task"""
|
|
315
|
+
await self._poller.stop()
|
|
316
|
+
await self._driver.disconnect()
|
|
317
|
+
|
|
318
|
+
@classmethod
|
|
319
|
+
def name(cls) -> str:
|
|
320
|
+
"""Used for picking up serial port symlinks"""
|
|
321
|
+
return "flexstacker"
|
|
322
|
+
|
|
323
|
+
def firmware_prefix(self) -> str:
|
|
324
|
+
"""The prefix used for looking up firmware"""
|
|
325
|
+
return "flex-stacker"
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def _model_from_revision(revision: Optional[str]) -> str:
|
|
329
|
+
"""Defines the revision -> model mapping"""
|
|
330
|
+
return "flexStackerModuleV1"
|
|
331
|
+
|
|
332
|
+
def model(self) -> str:
|
|
333
|
+
return self._model_from_revision(self._device_info.get("model"))
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def latch_state(self) -> LatchState:
|
|
337
|
+
"""The state of the latch."""
|
|
338
|
+
return LatchState.from_state(self.limit_switch_status[StackerAxis.L])
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def platform_state(self) -> PlatformState:
|
|
342
|
+
"""The state of the platform."""
|
|
343
|
+
return self._reader.platform_state
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def initialized(self) -> bool:
|
|
347
|
+
"""The stacker is ready..."""
|
|
348
|
+
return self._reader.initialized
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def hopper_door_state(self) -> HopperDoorState:
|
|
352
|
+
"""The status of the hopper door."""
|
|
353
|
+
return HopperDoorState.from_state(self._reader.hopper_door_closed)
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def limit_switch_status(self) -> Dict[StackerAxis, StackerAxisState]:
|
|
357
|
+
"""The status of the Limit switches."""
|
|
358
|
+
return self._reader.limit_switch_status
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def install_detected(self) -> bool:
|
|
362
|
+
"""Whether the stacker is installed on Flex."""
|
|
363
|
+
return self._reader.installation_detected
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def device_info(self) -> Mapping[str, str]:
|
|
367
|
+
return self._device_info
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def status(self) -> FlexStackerStatus:
|
|
371
|
+
"""Module status or error state details."""
|
|
372
|
+
return self._stacker_status
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def is_simulated(self) -> bool:
|
|
376
|
+
return isinstance(self._driver, SimulatingDriver)
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def live_data(self) -> LiveData:
|
|
380
|
+
data: FlexStackerData = {
|
|
381
|
+
"latchState": self.latch_state.value,
|
|
382
|
+
"platformState": self.platform_state.value,
|
|
383
|
+
"hopperDoorState": self.hopper_door_state.value,
|
|
384
|
+
"installDetected": self.install_detected,
|
|
385
|
+
"errorDetails": self._reader.error,
|
|
386
|
+
}
|
|
387
|
+
return {"status": self.status.value, "data": data}
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def should_identify(self) -> bool:
|
|
391
|
+
return self._should_identify
|
|
392
|
+
|
|
393
|
+
async def prep_for_update(self) -> str:
|
|
394
|
+
await self._poller.stop()
|
|
395
|
+
await self._driver.stop_motors()
|
|
396
|
+
await self._driver.enter_programming_mode()
|
|
397
|
+
# flex stacker has three unique "devices" over DFU
|
|
398
|
+
dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=3)
|
|
399
|
+
return dfu_info
|
|
400
|
+
|
|
401
|
+
def bootloader(self) -> UploadFunction:
|
|
402
|
+
return update.upload_via_dfu
|
|
403
|
+
|
|
404
|
+
async def deactivate(self, must_be_running: bool = True) -> None:
|
|
405
|
+
await self._driver.stop_motors()
|
|
406
|
+
|
|
407
|
+
async def set_led_state(
|
|
408
|
+
self,
|
|
409
|
+
power: float,
|
|
410
|
+
color: Optional[LEDColor] = None,
|
|
411
|
+
pattern: Optional[LEDPattern] = None,
|
|
412
|
+
duration: Optional[int] = None,
|
|
413
|
+
reps: Optional[int] = None,
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Sets the statusbar state."""
|
|
416
|
+
return await self._driver.set_led(
|
|
417
|
+
power, color=color, pattern=pattern, duration=duration, reps=reps
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
async def move_axis(
|
|
421
|
+
self,
|
|
422
|
+
axis: StackerAxis,
|
|
423
|
+
direction: Direction,
|
|
424
|
+
distance: float,
|
|
425
|
+
speed: Optional[float] = None,
|
|
426
|
+
acceleration: Optional[float] = None,
|
|
427
|
+
current: Optional[float] = None,
|
|
428
|
+
) -> bool:
|
|
429
|
+
"""Move the axis in a direction by the given distance in mm."""
|
|
430
|
+
default = STACKER_MOTION_CONFIG[axis]["move"]
|
|
431
|
+
old_run_current = self._reader.motion_params[axis].run_current
|
|
432
|
+
new_run_current = current if current is not None else default.run_current
|
|
433
|
+
if new_run_current != old_run_current:
|
|
434
|
+
await self._driver.set_run_current(axis, new_run_current)
|
|
435
|
+
self._reader.motion_params[axis].run_current = new_run_current
|
|
436
|
+
|
|
437
|
+
old_hold_current = self._reader.motion_params[axis].hold_current
|
|
438
|
+
new_hold_current = default.hold_current
|
|
439
|
+
if new_hold_current != old_hold_current:
|
|
440
|
+
await self._driver.set_ihold_current(axis, new_hold_current)
|
|
441
|
+
self._reader.motion_params[axis].hold_current = new_hold_current
|
|
442
|
+
|
|
443
|
+
motion_params = default.move_params.update(
|
|
444
|
+
max_speed=speed, acceleration=acceleration
|
|
445
|
+
)
|
|
446
|
+
distance = direction.distance(distance)
|
|
447
|
+
res = await self._driver.move_in_mm(axis, distance, params=motion_params)
|
|
448
|
+
if res == MoveResult.STALL_ERROR:
|
|
449
|
+
self._stall_detected = True
|
|
450
|
+
raise FlexStackerStallError(self.device_info["serial"], axis)
|
|
451
|
+
return res == MoveResult.NO_ERROR
|
|
452
|
+
|
|
453
|
+
async def home_axis(
|
|
454
|
+
self,
|
|
455
|
+
axis: StackerAxis,
|
|
456
|
+
direction: Direction,
|
|
457
|
+
speed: Optional[float] = None,
|
|
458
|
+
acceleration: Optional[float] = None,
|
|
459
|
+
current: Optional[float] = None,
|
|
460
|
+
) -> bool:
|
|
461
|
+
default = STACKER_MOTION_CONFIG[axis]["home"]
|
|
462
|
+
old_run_current = self._reader.motion_params[axis].run_current
|
|
463
|
+
new_run_current = current if current is not None else default.run_current
|
|
464
|
+
if new_run_current != old_run_current:
|
|
465
|
+
await self._driver.set_run_current(axis, new_run_current)
|
|
466
|
+
self._reader.motion_params[axis].run_current = new_run_current
|
|
467
|
+
|
|
468
|
+
old_hold_current = self._reader.motion_params[axis].hold_current
|
|
469
|
+
new_hold_current = default.hold_current
|
|
470
|
+
if new_hold_current != old_hold_current:
|
|
471
|
+
await self._driver.set_ihold_current(axis, new_hold_current)
|
|
472
|
+
self._reader.motion_params[axis].hold_current = new_hold_current
|
|
473
|
+
|
|
474
|
+
motion_params = default.move_params.update(
|
|
475
|
+
max_speed=speed, acceleration=acceleration
|
|
476
|
+
)
|
|
477
|
+
success = await self._driver.move_to_limit_switch(
|
|
478
|
+
axis=axis, direction=direction, params=motion_params
|
|
479
|
+
)
|
|
480
|
+
await self._reader.get_limit_switch_status()
|
|
481
|
+
if success == MoveResult.STALL_ERROR:
|
|
482
|
+
self._stall_detected = True
|
|
483
|
+
raise FlexStackerStallError(self.device_info["serial"], axis)
|
|
484
|
+
return success == MoveResult.NO_ERROR
|
|
485
|
+
|
|
486
|
+
async def close_latch(
|
|
487
|
+
self,
|
|
488
|
+
velocity: Optional[float] = None,
|
|
489
|
+
acceleration: Optional[float] = None,
|
|
490
|
+
) -> bool:
|
|
491
|
+
"""Close the latch, dropping any labware its holding."""
|
|
492
|
+
success = await self.home_axis(
|
|
493
|
+
StackerAxis.L,
|
|
494
|
+
Direction.RETRACT,
|
|
495
|
+
speed=velocity,
|
|
496
|
+
acceleration=acceleration,
|
|
497
|
+
)
|
|
498
|
+
# Check that the latch is closed.
|
|
499
|
+
await self._reader.get_limit_switch_status()
|
|
500
|
+
return (
|
|
501
|
+
success
|
|
502
|
+
and self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
async def open_latch(
|
|
506
|
+
self,
|
|
507
|
+
velocity: Optional[float] = None,
|
|
508
|
+
acceleration: Optional[float] = None,
|
|
509
|
+
) -> bool:
|
|
510
|
+
"""Open the latch."""
|
|
511
|
+
# The latch only has one limit switch, so we have to travel a fixed distance
|
|
512
|
+
# to open the latch.
|
|
513
|
+
success = await self.move_axis(
|
|
514
|
+
StackerAxis.L,
|
|
515
|
+
Direction.EXTEND,
|
|
516
|
+
distance=MAX_TRAVEL[StackerAxis.L],
|
|
517
|
+
speed=velocity,
|
|
518
|
+
acceleration=acceleration,
|
|
519
|
+
)
|
|
520
|
+
# Check that the latch is opened.
|
|
521
|
+
await self._reader.get_limit_switch_status()
|
|
522
|
+
return (
|
|
523
|
+
success
|
|
524
|
+
and self.limit_switch_status[StackerAxis.L] == StackerAxisState.RETRACTED
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
async def dispense_labware(
|
|
528
|
+
self,
|
|
529
|
+
labware_height: float,
|
|
530
|
+
enforce_hopper_lw_sensing: bool = True,
|
|
531
|
+
enforce_shuttle_lw_sensing: bool = True,
|
|
532
|
+
) -> None:
|
|
533
|
+
"""Dispenses the next labware in the stacker."""
|
|
534
|
+
self.verify_labware_height(labware_height)
|
|
535
|
+
await self._prepare_for_action()
|
|
536
|
+
|
|
537
|
+
if enforce_hopper_lw_sensing:
|
|
538
|
+
# TODO: re-enable this function after TOF calibration is implemented.
|
|
539
|
+
# Until then, we should also check the TOF X sensor before raising the error
|
|
540
|
+
# await self.verify_hopper_labware_presence(Direction.EXTEND, True)
|
|
541
|
+
hopper_empty = not await self.labware_detected(
|
|
542
|
+
StackerAxis.Z, Direction.EXTEND
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# Move platform along the X and make sure we DONT detect labware
|
|
546
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, HOME_OFFSET_MD)
|
|
547
|
+
if enforce_shuttle_lw_sensing:
|
|
548
|
+
await self.verify_shuttle_labware_presence(Direction.RETRACT, False)
|
|
549
|
+
|
|
550
|
+
# Move platform along the Z axis
|
|
551
|
+
await self._move_and_home_axis(StackerAxis.Z, Direction.EXTEND, HOME_OFFSET_SM)
|
|
552
|
+
|
|
553
|
+
# Transfer
|
|
554
|
+
await self.open_latch()
|
|
555
|
+
latch_clear_distance = labware_height + PLATFORM_OFFSET - LATCH_CLEARANCE
|
|
556
|
+
await self.move_axis(StackerAxis.Z, Direction.RETRACT, latch_clear_distance)
|
|
557
|
+
await self.close_latch()
|
|
558
|
+
|
|
559
|
+
# Move Z down the rest of the way
|
|
560
|
+
z_distance = MAX_TRAVEL[StackerAxis.Z] - latch_clear_distance - HOME_OFFSET_SM
|
|
561
|
+
await self.move_axis(StackerAxis.Z, Direction.RETRACT, z_distance)
|
|
562
|
+
await self.home_axis(StackerAxis.Z, Direction.RETRACT)
|
|
563
|
+
|
|
564
|
+
if enforce_shuttle_lw_sensing:
|
|
565
|
+
try:
|
|
566
|
+
await self.verify_shuttle_labware_presence(Direction.RETRACT, True)
|
|
567
|
+
except FlexStackerShuttleLabwareError:
|
|
568
|
+
# No labware detected on the shuttle, so we need to check what the Z TOF
|
|
569
|
+
# sensor says about the hopper
|
|
570
|
+
if hopper_empty:
|
|
571
|
+
# homing here so we don't have to modify the error recovery flow
|
|
572
|
+
await self._move_and_home_axis(
|
|
573
|
+
StackerAxis.X, Direction.EXTEND, HOME_OFFSET_MD
|
|
574
|
+
)
|
|
575
|
+
raise FlexStackerHopperLabwareError(
|
|
576
|
+
self.device_info["serial"],
|
|
577
|
+
labware_expected=True,
|
|
578
|
+
) from None
|
|
579
|
+
raise
|
|
580
|
+
|
|
581
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.EXTEND, HOME_OFFSET_MD)
|
|
582
|
+
|
|
583
|
+
async def store_labware(
|
|
584
|
+
self,
|
|
585
|
+
labware_height: float,
|
|
586
|
+
enforce_shuttle_lw_sensing: bool = True,
|
|
587
|
+
) -> None:
|
|
588
|
+
"""Stores a labware in the stacker."""
|
|
589
|
+
self.verify_labware_height(labware_height)
|
|
590
|
+
await self._prepare_for_action()
|
|
591
|
+
|
|
592
|
+
# Move the X and check that labware is detected
|
|
593
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, HOME_OFFSET_MD)
|
|
594
|
+
if enforce_shuttle_lw_sensing:
|
|
595
|
+
await self.verify_shuttle_labware_presence(Direction.RETRACT, True)
|
|
596
|
+
|
|
597
|
+
# Move the Z so the labware sits right under the labware already stored
|
|
598
|
+
latch_clear_distance = labware_height + PLATFORM_OFFSET - LATCH_CLEARANCE
|
|
599
|
+
distance = MAX_TRAVEL[StackerAxis.Z] - latch_clear_distance
|
|
600
|
+
await self.move_axis(StackerAxis.Z, Direction.EXTEND, distance)
|
|
601
|
+
|
|
602
|
+
await self.open_latch()
|
|
603
|
+
# Move the labware the rest of the way at half move speed to increase torque.
|
|
604
|
+
remaining_z = latch_clear_distance - HOME_OFFSET_SM
|
|
605
|
+
speed_z = STACKER_MOTION_CONFIG[StackerAxis.Z]["move"].move_params.max_speed / 2
|
|
606
|
+
await self.move_axis(StackerAxis.Z, Direction.EXTEND, remaining_z, speed_z)
|
|
607
|
+
await self.home_axis(StackerAxis.Z, Direction.EXTEND, speed_z)
|
|
608
|
+
await self.close_latch()
|
|
609
|
+
|
|
610
|
+
# Move the Z down and check that labware is not detected.
|
|
611
|
+
await self._move_and_home_axis(StackerAxis.Z, Direction.RETRACT, HOME_OFFSET_MD)
|
|
612
|
+
if enforce_shuttle_lw_sensing:
|
|
613
|
+
await self.verify_shuttle_labware_presence(Direction.RETRACT, False)
|
|
614
|
+
|
|
615
|
+
# Move the X to the gripper position
|
|
616
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.EXTEND, HOME_OFFSET_MD)
|
|
617
|
+
|
|
618
|
+
async def _move_and_home_axis(
|
|
619
|
+
self,
|
|
620
|
+
axis: StackerAxis,
|
|
621
|
+
direction: Direction,
|
|
622
|
+
offset: float = 0,
|
|
623
|
+
speed: Optional[float] = None,
|
|
624
|
+
) -> bool:
|
|
625
|
+
"""Move the axis in a direction by the given offset in mm and home it.
|
|
626
|
+
|
|
627
|
+
Warning: It is assumed that the axis is already in a known state
|
|
628
|
+
before this function gets called. Do not use this function if the axis
|
|
629
|
+
has not been homed/has recently stalled."""
|
|
630
|
+
distance = MAX_TRAVEL[axis] - offset
|
|
631
|
+
await self.move_axis(axis, direction, distance, speed)
|
|
632
|
+
return await self.home_axis(axis, direction, speed)
|
|
633
|
+
|
|
634
|
+
async def _prepare_for_action(self) -> None:
|
|
635
|
+
"""Helper to prepare axis for dispensing or storing labware."""
|
|
636
|
+
# TODO: check if we need to home first
|
|
637
|
+
await self.home_axis(StackerAxis.X, Direction.EXTEND)
|
|
638
|
+
await self.home_axis(StackerAxis.Z, Direction.RETRACT)
|
|
639
|
+
await self.close_latch()
|
|
640
|
+
await self.verify_shuttle_location(PlatformState.EXTENDED)
|
|
641
|
+
|
|
642
|
+
async def home_all(self, ignore_latch: bool = False) -> None:
|
|
643
|
+
"""Home all axes based on current state, assuming normal operation.
|
|
644
|
+
|
|
645
|
+
If ignore_latch is True, we will not attempt to close the latch. This
|
|
646
|
+
is useful when we want the shuttle to be out of the way for error
|
|
647
|
+
recovery (e.g. when the latch is stuck open).
|
|
648
|
+
"""
|
|
649
|
+
await self._reader.get_installation_detected()
|
|
650
|
+
await self._reader.get_limit_switch_status()
|
|
651
|
+
await self._reader.get_platform_sensor_state()
|
|
652
|
+
|
|
653
|
+
# Z axis is unknown, lets move it up in case it is holding a labware
|
|
654
|
+
if not ignore_latch:
|
|
655
|
+
if self.limit_switch_status[StackerAxis.Z] == StackerAxisState.UNKNOWN:
|
|
656
|
+
if self.latch_state == LatchState.OPENED:
|
|
657
|
+
# let's make sure the latch is opened all the way before homging the Z
|
|
658
|
+
await self.open_latch()
|
|
659
|
+
# self.latch_state is OPENED, so we need to home Z in the EXTEND direction
|
|
660
|
+
await self.home_axis(StackerAxis.Z, Direction.EXTEND)
|
|
661
|
+
await self.close_latch()
|
|
662
|
+
|
|
663
|
+
if (
|
|
664
|
+
# if the platform is on the z or if x has not been homed
|
|
665
|
+
self.platform_state == PlatformState.UNKNOWN
|
|
666
|
+
or self.limit_switch_status[StackerAxis.X] == StackerAxisState.UNKNOWN
|
|
667
|
+
):
|
|
668
|
+
# if the z is not retracted, we need to make sure the x is retracted
|
|
669
|
+
# so we can retract the z properly later
|
|
670
|
+
if self.limit_switch_status[StackerAxis.Z] != StackerAxisState.RETRACTED:
|
|
671
|
+
await self.home_axis(StackerAxis.X, Direction.RETRACT)
|
|
672
|
+
else:
|
|
673
|
+
await self.home_axis(StackerAxis.X, Direction.EXTEND)
|
|
674
|
+
|
|
675
|
+
# Finally, retract Z and extend X if they are not already
|
|
676
|
+
await self.home_axis(StackerAxis.Z, Direction.RETRACT)
|
|
677
|
+
await self.home_axis(StackerAxis.X, Direction.EXTEND)
|
|
678
|
+
|
|
679
|
+
async def labware_detected(
|
|
680
|
+
self,
|
|
681
|
+
axis: StackerAxis,
|
|
682
|
+
direction: Direction,
|
|
683
|
+
histogram: Optional[TOFMeasurementResult] = None,
|
|
684
|
+
baseline: Optional[Dict[int, List[float]]] = None,
|
|
685
|
+
) -> bool:
|
|
686
|
+
"""Detect labware on the TOF sensor using the `baseline` method
|
|
687
|
+
|
|
688
|
+
NOTE: This method is still under development and is inconsistent when detecting
|
|
689
|
+
labware on the X axis in the Extended position. We can consistently detect
|
|
690
|
+
labware on the Z, but we need to do more data collection and testing
|
|
691
|
+
to validate this method.
|
|
692
|
+
"""
|
|
693
|
+
dir_str = cast(Literal["extend", "retract"], str(direction))
|
|
694
|
+
sensor = TOFSensor.X if axis == StackerAxis.X else TOFSensor.Z
|
|
695
|
+
baseline = (
|
|
696
|
+
baseline or load_tof_baseline_data(self.model())[sensor.value][dir_str]
|
|
697
|
+
)
|
|
698
|
+
config = TOF_DETECTION_CONFIG[sensor][direction]
|
|
699
|
+
|
|
700
|
+
# Take a histogram reading and determine if labware was detected
|
|
701
|
+
histogram = histogram or await self._driver.get_tof_histogram(sensor)
|
|
702
|
+
for zone in config.zones:
|
|
703
|
+
raw_data = histogram.bins[zone]
|
|
704
|
+
baseline_data = baseline[zone]
|
|
705
|
+
for bin in config.bins:
|
|
706
|
+
# We need to ignore raw photon count below N photons as
|
|
707
|
+
# it becomes inconsistent to detect labware given false positives.
|
|
708
|
+
if raw_data[bin] < config.threshold:
|
|
709
|
+
continue
|
|
710
|
+
delta = raw_data[bin] - baseline_data[bin]
|
|
711
|
+
if delta > 0:
|
|
712
|
+
return True
|
|
713
|
+
return False
|
|
714
|
+
|
|
715
|
+
async def verify_shuttle_location(self, expected: PlatformState) -> None:
|
|
716
|
+
"""Verify the shuttle is present and in the expected location."""
|
|
717
|
+
await self._reader.get_platform_sensor_state()
|
|
718
|
+
# Validate the platform state matches, ignore EXTENDED checks on EVT
|
|
719
|
+
if self.platform_state != expected:
|
|
720
|
+
if (
|
|
721
|
+
self.device_info["model"] == HardwareRevision.EVT.value
|
|
722
|
+
and expected == PlatformState.EXTENDED
|
|
723
|
+
):
|
|
724
|
+
return
|
|
725
|
+
else:
|
|
726
|
+
raise FlexStackerShuttleMissingError(
|
|
727
|
+
self.device_info["serial"], expected, self.platform_state
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
async def verify_shuttle_labware_presence(
|
|
731
|
+
self, direction: Direction, labware_expected: bool
|
|
732
|
+
) -> None:
|
|
733
|
+
"""Check whether or not a labware is detected on the shuttle."""
|
|
734
|
+
if ff.flex_stacker_tof_sensors_disabled():
|
|
735
|
+
return
|
|
736
|
+
result = await self.labware_detected(StackerAxis.X, direction)
|
|
737
|
+
if labware_expected != result:
|
|
738
|
+
if labware_expected:
|
|
739
|
+
raise FlexStackerShuttleLabwareError(
|
|
740
|
+
self.device_info["serial"],
|
|
741
|
+
shuttle_state=self.platform_state,
|
|
742
|
+
labware_expected=labware_expected,
|
|
743
|
+
)
|
|
744
|
+
raise FlexStackerShuttleNotEmptyError(
|
|
745
|
+
self.device_info["serial"],
|
|
746
|
+
shuttle_state=self.platform_state,
|
|
747
|
+
labware_expected=labware_expected,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
async def verify_hopper_labware_presence(
|
|
751
|
+
self, direction: Direction, labware_expected: bool
|
|
752
|
+
) -> None:
|
|
753
|
+
"""Check whether or not a labware is detected inside the hopper."""
|
|
754
|
+
if ff.flex_stacker_tof_sensors_disabled():
|
|
755
|
+
return
|
|
756
|
+
result = await self.labware_detected(StackerAxis.Z, direction)
|
|
757
|
+
if labware_expected != result:
|
|
758
|
+
raise FlexStackerHopperLabwareError(
|
|
759
|
+
self.device_info["serial"],
|
|
760
|
+
labware_expected=labware_expected,
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
def verify_labware_height(self, labware_height: float) -> None:
|
|
764
|
+
"""Check that the labware height is within valid range."""
|
|
765
|
+
if labware_height < MIN_LABWARE_HEIGHT or labware_height > MAX_LABWARE_HEIGHT:
|
|
766
|
+
raise ValueError(
|
|
767
|
+
f"Labware height must be between {MIN_LABWARE_HEIGHT}-{MAX_LABWARE_HEIGHT}mm."
|
|
768
|
+
"Received {labware_height}mm."
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
def event_listener(self, event: Any) -> None:
|
|
772
|
+
if isinstance(event, StatusBarUpdateEvent):
|
|
773
|
+
self._last_status_bar_event = event
|
|
774
|
+
asyncio.run_coroutine_threadsafe(
|
|
775
|
+
self._handle_status_bar_event(event), self._loop
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
async def _handle_status_bar_event(self, event: StatusBarUpdateEvent) -> None:
|
|
779
|
+
if event.enabled and self.initialized:
|
|
780
|
+
match event.state:
|
|
781
|
+
case StatusBarState.RUNNING:
|
|
782
|
+
await self.set_led_state(0.5, LEDColor.GREEN, LEDPattern.STATIC)
|
|
783
|
+
case StatusBarState.PAUSED:
|
|
784
|
+
if (
|
|
785
|
+
self.hopper_door_state == HopperDoorState.OPENED
|
|
786
|
+
or self.should_identify
|
|
787
|
+
):
|
|
788
|
+
await self._stacker_bar_pause()
|
|
789
|
+
else:
|
|
790
|
+
await self._stacker_bar_idle()
|
|
791
|
+
case StatusBarState.IDLE:
|
|
792
|
+
if self.hopper_door_state == HopperDoorState.OPENED:
|
|
793
|
+
await self._stacker_bar_pause()
|
|
794
|
+
else:
|
|
795
|
+
await self._stacker_bar_idle()
|
|
796
|
+
case StatusBarState.HARDWARE_ERROR:
|
|
797
|
+
if self.should_identify:
|
|
798
|
+
await self.set_led_state(
|
|
799
|
+
0.5, LEDColor.RED, LEDPattern.FLASH, duration=300
|
|
800
|
+
)
|
|
801
|
+
else:
|
|
802
|
+
await self._stacker_bar_idle()
|
|
803
|
+
case StatusBarState.SOFTWARE_ERROR:
|
|
804
|
+
await self.set_led_state(0.5, LEDColor.YELLOW, LEDPattern.STATIC)
|
|
805
|
+
case StatusBarState.ERROR_RECOVERY:
|
|
806
|
+
if self.hopper_door_state == HopperDoorState.OPENED:
|
|
807
|
+
await self._stacker_bar_pause()
|
|
808
|
+
elif self.should_identify:
|
|
809
|
+
await self.set_led_state(
|
|
810
|
+
0.5, LEDColor.YELLOW, LEDPattern.PULSE, duration=2000
|
|
811
|
+
)
|
|
812
|
+
else:
|
|
813
|
+
await self._stacker_bar_idle()
|
|
814
|
+
case StatusBarState.RUN_COMPLETED:
|
|
815
|
+
await self.set_led_state(0.5, LEDColor.GREEN, LEDPattern.PULSE)
|
|
816
|
+
case StatusBarState.UPDATING:
|
|
817
|
+
await self.set_led_state(0.5, LEDColor.WHITE, LEDPattern.PULSE)
|
|
818
|
+
case _:
|
|
819
|
+
await self._stacker_bar_idle()
|
|
820
|
+
|
|
821
|
+
async def _stacker_bar_pause(self) -> None:
|
|
822
|
+
await self.set_led_state(0.5, LEDColor.BLUE, LEDPattern.PULSE, duration=2000)
|
|
823
|
+
|
|
824
|
+
async def _stacker_bar_idle(self) -> None:
|
|
825
|
+
await self.set_led_state(0.5, LEDColor.WHITE, LEDPattern.STATIC)
|
|
826
|
+
|
|
827
|
+
async def identify(self, start: bool, color_name: Optional[str] = None) -> None:
|
|
828
|
+
"""Identify the module."""
|
|
829
|
+
reps = -1 if start else 0
|
|
830
|
+
color = LEDColor.from_name(color_name or LEDColor.BLUE.name)
|
|
831
|
+
await self.set_led_state(0.5, color, LEDPattern.PULSE, reps=reps)
|
|
832
|
+
if not start and self._last_status_bar_event:
|
|
833
|
+
await self._handle_status_bar_event(self._last_status_bar_event)
|
|
834
|
+
|
|
835
|
+
def set_stacker_identify(self, state: bool) -> None:
|
|
836
|
+
self._should_identify = state
|
|
837
|
+
|
|
838
|
+
def cleanup_persistent(self) -> None:
|
|
839
|
+
"""Reset persistent data on the module that should not exist outside of a run."""
|
|
840
|
+
self.set_stacker_identify(False)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
class FlexStackerReader(Reader):
|
|
844
|
+
error: Optional[str]
|
|
845
|
+
|
|
846
|
+
def __init__(self, driver: AbstractFlexStackerDriver) -> None:
|
|
847
|
+
self.error: Optional[str] = None
|
|
848
|
+
self._driver = driver
|
|
849
|
+
self.limit_switch_status = {
|
|
850
|
+
axis: StackerAxisState.UNKNOWN for axis in StackerAxis
|
|
851
|
+
}
|
|
852
|
+
self.tof_sensor_status: Dict[TOFSensor, TOFSensorStatus] = {
|
|
853
|
+
s: TOFSensorStatus(
|
|
854
|
+
s, TOFSensorState.INITIALIZING, TOFSensorMode.UNKNOWN, False
|
|
855
|
+
)
|
|
856
|
+
for s in TOFSensor
|
|
857
|
+
}
|
|
858
|
+
self.motion_params: Dict[StackerAxis, AxisParams] = {
|
|
859
|
+
axis: AxisParams(0, 0, MoveParams(0, 0, 0)) for axis in StackerAxis
|
|
860
|
+
}
|
|
861
|
+
self.platform_state = PlatformState.UNKNOWN
|
|
862
|
+
self.hopper_door_closed = False
|
|
863
|
+
self.initialized = False
|
|
864
|
+
self.installation_detected = False
|
|
865
|
+
self._refresh_state = False
|
|
866
|
+
self._initialized_callback: Optional[Callable[[], Awaitable[None]]] = None
|
|
867
|
+
|
|
868
|
+
def set_initialized_callback(self, callback: Callable[[], Awaitable[None]]) -> None:
|
|
869
|
+
"""Sets the callback used when done initializing the module."""
|
|
870
|
+
self._initialized_callback = callback
|
|
871
|
+
|
|
872
|
+
async def read(self) -> None:
|
|
873
|
+
await self.get_door_closed()
|
|
874
|
+
await self.get_platform_sensor_state()
|
|
875
|
+
if not self.initialized or self._refresh_state:
|
|
876
|
+
initialized = True
|
|
877
|
+
await self.get_installation_detected()
|
|
878
|
+
await self.get_limit_switch_status()
|
|
879
|
+
await self.get_motion_parameters()
|
|
880
|
+
for sensor, status in self.tof_sensor_status.items():
|
|
881
|
+
if status.state == TOFSensorState.INITIALIZING:
|
|
882
|
+
status = await self._driver.get_tof_sensor_status(sensor)
|
|
883
|
+
self.tof_sensor_status[sensor] = status
|
|
884
|
+
initialized &= status.ok
|
|
885
|
+
|
|
886
|
+
self._refresh_state = False
|
|
887
|
+
# We are done initializing, sync the led state
|
|
888
|
+
if not self.initialized and initialized:
|
|
889
|
+
self.initialized = True
|
|
890
|
+
if self._initialized_callback:
|
|
891
|
+
await self._initialized_callback()
|
|
892
|
+
|
|
893
|
+
self._set_error(None)
|
|
894
|
+
|
|
895
|
+
async def get_limit_switch_status(self) -> None:
|
|
896
|
+
"""Get the limit switch status."""
|
|
897
|
+
status = await self._driver.get_limit_switches_status()
|
|
898
|
+
self.limit_switch_status = {
|
|
899
|
+
axis: StackerAxisState.from_status(status, axis) for axis in StackerAxis
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
async def get_motion_parameters(self) -> None:
|
|
903
|
+
"""Get the motion parameters used by the axis motors."""
|
|
904
|
+
for axis in StackerAxis:
|
|
905
|
+
self.motion_params[axis].move_params = await self._driver.get_motion_params(
|
|
906
|
+
axis
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
async def get_platform_sensor_state(self) -> None:
|
|
910
|
+
"""Get the platform state."""
|
|
911
|
+
status = await self._driver.get_platform_status()
|
|
912
|
+
platform_state = PlatformState.from_status(status)
|
|
913
|
+
if self.initialized and platform_state == PlatformState.UNKNOWN:
|
|
914
|
+
# If the platform state is unknown but the X axis is known,
|
|
915
|
+
# the platform is missing.
|
|
916
|
+
await self.get_limit_switch_status()
|
|
917
|
+
if self.limit_switch_status[StackerAxis.X] != StackerAxisState.UNKNOWN:
|
|
918
|
+
platform_state = PlatformState.MISSING
|
|
919
|
+
self.platform_state = platform_state
|
|
920
|
+
|
|
921
|
+
async def get_door_closed(self) -> None:
|
|
922
|
+
"""Check if the hopper door is closed."""
|
|
923
|
+
old_door_state = self.hopper_door_closed
|
|
924
|
+
self.hopper_door_closed = await self._driver.get_hopper_door_closed()
|
|
925
|
+
if old_door_state != self.hopper_door_closed and self._initialized_callback:
|
|
926
|
+
await self._initialized_callback()
|
|
927
|
+
|
|
928
|
+
async def get_installation_detected(self) -> None:
|
|
929
|
+
"""Check if the stacker install detect is set."""
|
|
930
|
+
detected = await self._driver.get_installation_detected()
|
|
931
|
+
self.installation_detected = detected
|
|
932
|
+
|
|
933
|
+
def set_refresh_state(self) -> None:
|
|
934
|
+
"""Tell the reader to refresh all states, even ones that arent polled."""
|
|
935
|
+
self._refresh_state = True
|
|
936
|
+
|
|
937
|
+
def on_error(self, exception: Exception) -> None:
|
|
938
|
+
self._driver.reset_serial_buffers()
|
|
939
|
+
self._set_error(exception)
|
|
940
|
+
|
|
941
|
+
def _set_error(self, exception: Optional[Exception]) -> None:
|
|
942
|
+
if exception is None:
|
|
943
|
+
self.error = None
|
|
944
|
+
else:
|
|
945
|
+
try:
|
|
946
|
+
self.error = str(exception.args[0])
|
|
947
|
+
except Exception:
|
|
948
|
+
self.error = repr(exception)
|