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,431 @@
|
|
|
1
|
+
"""Manager for the :py:class:`.hardware_control.API` thread."""
|
|
2
|
+
import functools
|
|
3
|
+
import threading
|
|
4
|
+
import logging
|
|
5
|
+
import asyncio
|
|
6
|
+
import inspect
|
|
7
|
+
import weakref
|
|
8
|
+
from typing import (
|
|
9
|
+
Any,
|
|
10
|
+
Awaitable,
|
|
11
|
+
Callable,
|
|
12
|
+
Generic,
|
|
13
|
+
Optional,
|
|
14
|
+
TypeVar,
|
|
15
|
+
cast,
|
|
16
|
+
Sequence,
|
|
17
|
+
Mapping,
|
|
18
|
+
AsyncGenerator,
|
|
19
|
+
Union,
|
|
20
|
+
Type,
|
|
21
|
+
ParamSpec,
|
|
22
|
+
)
|
|
23
|
+
from .adapters import SynchronousAdapter
|
|
24
|
+
from .modules.mod_abc import AbstractModule
|
|
25
|
+
from .protocols import (
|
|
26
|
+
AsyncioConfigurable,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
MODULE_LOG = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ThreadManagerException(Exception):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
WrappedReturn = TypeVar("WrappedReturn", contravariant=True)
|
|
37
|
+
WrappedYield = TypeVar("WrappedYield", contravariant=True)
|
|
38
|
+
P = ParamSpec("P")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def call_coroutine_threadsafe(
|
|
42
|
+
loop: asyncio.AbstractEventLoop,
|
|
43
|
+
coro: Callable[P, Awaitable[WrappedReturn]],
|
|
44
|
+
*args: P.args,
|
|
45
|
+
**kwargs: P.kwargs,
|
|
46
|
+
) -> WrappedReturn:
|
|
47
|
+
fut = cast(
|
|
48
|
+
"asyncio.Future[WrappedReturn]",
|
|
49
|
+
asyncio.run_coroutine_threadsafe(coro(*args, **kwargs), loop),
|
|
50
|
+
)
|
|
51
|
+
wrapped = asyncio.wrap_future(fut)
|
|
52
|
+
return await wrapped
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def execute_asyncgen_threadsafe(
|
|
56
|
+
loop: asyncio.AbstractEventLoop,
|
|
57
|
+
agenfunc: Callable[P, AsyncGenerator[WrappedYield, None]],
|
|
58
|
+
*args: P.args,
|
|
59
|
+
**kwargs: P.kwargs,
|
|
60
|
+
) -> AsyncGenerator[WrappedYield, None]:
|
|
61
|
+
|
|
62
|
+
# This function should bridge an async generator function between two asyncio
|
|
63
|
+
# loops running in different threads. There are several stages to this because
|
|
64
|
+
# there are several stages to generator execution.
|
|
65
|
+
|
|
66
|
+
# These clues will help us later
|
|
67
|
+
class _DoneSingleton:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
async def _build_queue() -> "asyncio.Queue[Union[WrappedYield, _DoneSingleton]]":
|
|
71
|
+
return asyncio.Queue(maxsize=1)
|
|
72
|
+
|
|
73
|
+
yield_queue = await asyncio.wrap_future(
|
|
74
|
+
cast(
|
|
75
|
+
"asyncio.Future[asyncio.Queue[Union[WrappedYield, _DoneSingleton]]]",
|
|
76
|
+
asyncio.run_coroutine_threadsafe(_build_queue(), loop),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# the async generator function needs to run on the target loop. it is not a coroutine
|
|
81
|
+
# and cannot be run with run_coroutine_threadsafe in the same way. however, we can
|
|
82
|
+
# make a coroutine that _can_, and that exhausts the async generator and puts what
|
|
83
|
+
# the generator yields into a queue. since something later will have to combine
|
|
84
|
+
# awaiting yield results and the function finishing, we can now use the _DoneSingleton
|
|
85
|
+
# to short circuit the caller waiting for a yield result when the function is done.
|
|
86
|
+
# in addition, the queue being 1 element max should give us similar "backpressure"
|
|
87
|
+
# behavior as an async for.
|
|
88
|
+
async def _inner_agen_wrap() -> None:
|
|
89
|
+
item: WrappedYield
|
|
90
|
+
try:
|
|
91
|
+
async for item in agenfunc(*args, **kwargs):
|
|
92
|
+
await yield_queue.put(item)
|
|
93
|
+
finally:
|
|
94
|
+
await yield_queue.put(_DoneSingleton())
|
|
95
|
+
|
|
96
|
+
# as promised, we can run our coroutine pretty easily now. note that this coroutine
|
|
97
|
+
# returns None, because it is handling the generator yields internally.
|
|
98
|
+
fut = cast(
|
|
99
|
+
"asyncio.Future[None]",
|
|
100
|
+
asyncio.run_coroutine_threadsafe(_inner_agen_wrap(), loop),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# now we have to bridge the output of the generator to the calling loop. this is
|
|
104
|
+
# fun because this uses an asyncio queue which isn't thread safe. so we also need
|
|
105
|
+
# to pull stuff out of the asyncio queue via the other loop
|
|
106
|
+
while not fut.done():
|
|
107
|
+
getter = cast(
|
|
108
|
+
"asyncio.Future[Union[WrappedYield, _DoneSingleton]]",
|
|
109
|
+
asyncio.run_coroutine_threadsafe(yield_queue.get(), loop),
|
|
110
|
+
)
|
|
111
|
+
asyncio_getter = asyncio.wrap_future(getter)
|
|
112
|
+
item = await asyncio_getter
|
|
113
|
+
if isinstance(item, _DoneSingleton):
|
|
114
|
+
break
|
|
115
|
+
yield item
|
|
116
|
+
# if there was an exception then this should re-raise it in the calling loop
|
|
117
|
+
_ = fut.result()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
WrappedObj = TypeVar("WrappedObj", bound=AsyncioConfigurable, covariant=True)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class CallBridger(Generic[WrappedObj]):
|
|
124
|
+
def __init__(
|
|
125
|
+
self, wrapped_obj: WrappedObj, loop: asyncio.AbstractEventLoop
|
|
126
|
+
) -> None:
|
|
127
|
+
self.wrapped_obj = wrapped_obj
|
|
128
|
+
self._loop = loop
|
|
129
|
+
|
|
130
|
+
def __getattribute__(self, attr_name: str) -> Any:
|
|
131
|
+
# Almost every attribute retrieved from us will be for people actually
|
|
132
|
+
# looking for an attribute of the managed object, so check there first.
|
|
133
|
+
managed_obj = object.__getattribute__(self, "wrapped_obj")
|
|
134
|
+
loop = object.__getattribute__(self, "_loop")
|
|
135
|
+
try:
|
|
136
|
+
attr = getattr(managed_obj, attr_name)
|
|
137
|
+
except AttributeError:
|
|
138
|
+
# Maybe this actually was for us? Let’s find it
|
|
139
|
+
return object.__getattribute__(self, attr_name)
|
|
140
|
+
|
|
141
|
+
if asyncio.iscoroutinefunction(attr):
|
|
142
|
+
# Return coroutine result of async function
|
|
143
|
+
# executed in managed thread to calling thread
|
|
144
|
+
|
|
145
|
+
@functools.wraps(attr)
|
|
146
|
+
async def wrapper(
|
|
147
|
+
*args: Sequence[Any], **kwargs: Mapping[str, Any]
|
|
148
|
+
) -> WrappedReturn:
|
|
149
|
+
return await call_coroutine_threadsafe(loop, attr, *args, **kwargs)
|
|
150
|
+
|
|
151
|
+
return wrapper
|
|
152
|
+
|
|
153
|
+
elif asyncio.iscoroutine(attr):
|
|
154
|
+
# Return awaitable coroutine properties run in managed thread/loop
|
|
155
|
+
fut = asyncio.run_coroutine_threadsafe(attr, loop)
|
|
156
|
+
wrapped = asyncio.wrap_future(fut)
|
|
157
|
+
return wrapped
|
|
158
|
+
|
|
159
|
+
elif inspect.isasyncgenfunction(attr):
|
|
160
|
+
# Return a wrapper that will exectue the resulting async generator
|
|
161
|
+
# in managed thread loop
|
|
162
|
+
|
|
163
|
+
@functools.wraps(attr)
|
|
164
|
+
async def wrapper(
|
|
165
|
+
*args: Sequence[Any], **kwargs: Mapping[str, Any]
|
|
166
|
+
) -> AsyncGenerator[WrappedYield, None]:
|
|
167
|
+
item: WrappedYield
|
|
168
|
+
async for item in execute_asyncgen_threadsafe(
|
|
169
|
+
loop, attr, *args, **kwargs
|
|
170
|
+
):
|
|
171
|
+
yield item
|
|
172
|
+
|
|
173
|
+
return wrapper
|
|
174
|
+
|
|
175
|
+
return attr
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# TODO: BC 2020-02-25 instead of overwriting __get_attribute__ in this class
|
|
179
|
+
# use inspect.getmembers to iterate over appropriate members of adapted
|
|
180
|
+
# instance and setattr on the outer instance with the proper threadsafe
|
|
181
|
+
# resolution logic injected. This approach avoids requiring calls to
|
|
182
|
+
# object.__get_attribute__(self,...) to opt out of the overwritten
|
|
183
|
+
# functionality. It is more readable and protected from
|
|
184
|
+
# unintentional recursion.
|
|
185
|
+
class ThreadManager(Generic[WrappedObj]):
|
|
186
|
+
"""A wrapper to make every call into :py:class:`.hardware_control.API`
|
|
187
|
+
execute within the same thread.
|
|
188
|
+
|
|
189
|
+
This class spawns a worker thread and starts an event loop within.
|
|
190
|
+
It then calls the async builder parameter within that worker thread's
|
|
191
|
+
event loop passing thru all args and kwargs and injecting the worker
|
|
192
|
+
thread's loop as a kwarg to the builder. The resulting built object
|
|
193
|
+
is stored as a member of the class, and a synchronous interface to
|
|
194
|
+
the managed object's members is also exposed for convenience.
|
|
195
|
+
|
|
196
|
+
If you want to wait for the managed object's creation separately
|
|
197
|
+
(with managed_thread_ready_blocking or managed_thread_ready_async)
|
|
198
|
+
use the nonblocking_builder static method to add an attribute to the builder
|
|
199
|
+
function, i.e.
|
|
200
|
+
|
|
201
|
+
thread_manager = ThreadManager(ThreadManager.nonblocking_builder(builder), ...)
|
|
202
|
+
|
|
203
|
+
Example
|
|
204
|
+
-------
|
|
205
|
+
.. code-block::
|
|
206
|
+
>>> from opentrons.hardware_control import API, ThreadManager
|
|
207
|
+
>>> api_single_thread = ThreadManager(API.build_hardware_simulator)
|
|
208
|
+
>>> await api_single_thread.home() # call as awaitable async
|
|
209
|
+
>>> api_single_thread.sync.home() # call as blocking sync
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
Builder = ParamSpec("Builder")
|
|
213
|
+
Built = TypeVar("Built")
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def nonblocking_builder(
|
|
217
|
+
builder: Callable[Builder, Awaitable[Built]]
|
|
218
|
+
) -> Callable[Builder, Awaitable[Built]]:
|
|
219
|
+
"""Wrap an instance of a builder function to make initializes that use it nonblocking.
|
|
220
|
+
|
|
221
|
+
For instance, you can build a ThreadManager like this:
|
|
222
|
+
|
|
223
|
+
thread_manager = ThreadManager(ThreadManager.nonblocking_builder(API.build_hardware_controller), ...)
|
|
224
|
+
|
|
225
|
+
to make the initialize call return immediately so you can later wait on it via
|
|
226
|
+
managed_thread_ready_blocking or managed_thread_ready_async
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
@functools.wraps(builder)
|
|
230
|
+
async def wrapper(
|
|
231
|
+
*args: ThreadManager.Builder.args, **kwargs: ThreadManager.Builder.kwargs
|
|
232
|
+
) -> ThreadManager.Built:
|
|
233
|
+
return await builder(*args, **kwargs)
|
|
234
|
+
|
|
235
|
+
setattr(wrapper, "nonblocking", True)
|
|
236
|
+
return wrapper
|
|
237
|
+
|
|
238
|
+
def __init__(
|
|
239
|
+
self,
|
|
240
|
+
builder: Callable[Builder, Awaitable[WrappedObj]],
|
|
241
|
+
*args: Builder.args,
|
|
242
|
+
**kwargs: Builder.kwargs,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Build the ThreadManager.
|
|
245
|
+
|
|
246
|
+
builder: The api function to use to build the instance.
|
|
247
|
+
|
|
248
|
+
The args and kwargs will be forwarded to the builder function.
|
|
249
|
+
|
|
250
|
+
Note: by default, this function will block until the managed thread is ready and the hardware controller
|
|
251
|
+
has been built. To make this function return immediately you can wrap its builder argument in
|
|
252
|
+
ThreadManager.nonblocking_builder(), like this:
|
|
253
|
+
|
|
254
|
+
thread_manager = ThreadManager(ThreadManager.nonblocking_builder(API.build_hardware_controller), ...)
|
|
255
|
+
|
|
256
|
+
Afterwards, you'll need to call ThreadManager.managed_thread_ready_blocking or its async variant before
|
|
257
|
+
you can actually use thei nstance.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
261
|
+
self.managed_obj: Optional[WrappedObj] = None
|
|
262
|
+
self.bridged_obj: Optional[CallBridger[WrappedObj]] = None
|
|
263
|
+
self._sync_managed_obj: Optional[SynchronousAdapter[WrappedObj]] = None
|
|
264
|
+
is_running = threading.Event()
|
|
265
|
+
self._is_running = is_running
|
|
266
|
+
self._cached_modules: weakref.WeakKeyDictionary[
|
|
267
|
+
AbstractModule, CallBridger[AbstractModule]
|
|
268
|
+
] = weakref.WeakKeyDictionary()
|
|
269
|
+
# TODO: remove this if we switch to python 3.8
|
|
270
|
+
# https://docs.python.org/3/library/asyncio-subprocess.html#subprocess-and-threads
|
|
271
|
+
# On windows, the event loop and system interface is different and
|
|
272
|
+
# this won't work.
|
|
273
|
+
try:
|
|
274
|
+
asyncio.get_child_watcher()
|
|
275
|
+
except NotImplementedError:
|
|
276
|
+
pass
|
|
277
|
+
blocking = not getattr(builder, "nonblocking", False)
|
|
278
|
+
target = object.__getattribute__(self, "_build_and_start_loop")
|
|
279
|
+
thread = threading.Thread(
|
|
280
|
+
target=target,
|
|
281
|
+
name="ManagedThread",
|
|
282
|
+
args=(builder, *args),
|
|
283
|
+
kwargs=kwargs,
|
|
284
|
+
daemon=True,
|
|
285
|
+
)
|
|
286
|
+
self._thread = thread
|
|
287
|
+
thread.start()
|
|
288
|
+
if blocking:
|
|
289
|
+
object.__getattribute__(self, "managed_thread_ready_blocking")()
|
|
290
|
+
|
|
291
|
+
def managed_thread_ready_blocking(self) -> None:
|
|
292
|
+
object.__getattribute__(self, "_is_running").wait()
|
|
293
|
+
if not object.__getattribute__(self, "managed_obj"):
|
|
294
|
+
raise ThreadManagerException("Failed to create Managed Object")
|
|
295
|
+
|
|
296
|
+
async def managed_thread_ready_async(self) -> None:
|
|
297
|
+
is_running = object.__getattribute__(self, "_is_running")
|
|
298
|
+
while not is_running.is_set():
|
|
299
|
+
await asyncio.sleep(0.1)
|
|
300
|
+
# Thread initialization is done.
|
|
301
|
+
if not object.__getattribute__(self, "managed_obj"):
|
|
302
|
+
raise ThreadManagerException("Failed to create Managed Object")
|
|
303
|
+
|
|
304
|
+
def _build_and_start_loop(
|
|
305
|
+
self,
|
|
306
|
+
builder: Callable[..., Awaitable[WrappedObj]],
|
|
307
|
+
*args: Sequence[Any],
|
|
308
|
+
**kwargs: Mapping[str, Any],
|
|
309
|
+
) -> None:
|
|
310
|
+
loop = asyncio.new_event_loop()
|
|
311
|
+
asyncio.set_event_loop(loop)
|
|
312
|
+
self._loop = loop
|
|
313
|
+
try:
|
|
314
|
+
managed_obj = loop.run_until_complete(builder(*args, loop=loop, **kwargs))
|
|
315
|
+
self.managed_obj = managed_obj
|
|
316
|
+
self.bridged_obj = CallBridger(managed_obj, loop)
|
|
317
|
+
self._sync_managed_obj = SynchronousAdapter(managed_obj)
|
|
318
|
+
except Exception:
|
|
319
|
+
MODULE_LOG.exception("Exception in Thread Manager build")
|
|
320
|
+
finally:
|
|
321
|
+
object.__getattribute__(self, "_is_running").set()
|
|
322
|
+
loop.run_forever()
|
|
323
|
+
loop.close()
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def sync(self) -> SynchronousAdapter[WrappedObj]:
|
|
327
|
+
# Why the ignore?
|
|
328
|
+
# While self._sync_managed_obj is initialized None, a failure to build
|
|
329
|
+
# the managed_obj and _sync_managed_obj is a catastrophic failure.
|
|
330
|
+
# All callers of this property assume it to be valid.
|
|
331
|
+
return self._sync_managed_obj # type: ignore
|
|
332
|
+
|
|
333
|
+
def __repr__(self) -> str:
|
|
334
|
+
return "<ThreadManager>"
|
|
335
|
+
|
|
336
|
+
def clean_up_tm(self) -> None:
|
|
337
|
+
try:
|
|
338
|
+
loop = object.__getattribute__(self, "_loop")
|
|
339
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
340
|
+
except Exception:
|
|
341
|
+
pass
|
|
342
|
+
object.__setattr__(self, "_cached_modules", weakref.WeakKeyDictionary({}))
|
|
343
|
+
object.__getattribute__(self, "_thread").join()
|
|
344
|
+
|
|
345
|
+
def wrap_module(self, module: AbstractModule) -> CallBridger[AbstractModule]:
|
|
346
|
+
"""Return the module object wrapped in a CallBridger and cache it.
|
|
347
|
+
|
|
348
|
+
The wrapped module objects are cached in `self._cached_modules` so they can be
|
|
349
|
+
re-used throughout the module object's life, as creating a wrapper is expensive.
|
|
350
|
+
We use a WeakKeyDictionary for caching so that module objects can be
|
|
351
|
+
garbage collected when modules are detached (since entries in WeakKeyDictionary
|
|
352
|
+
get discarded when there is no longer a strong reference to the key).
|
|
353
|
+
"""
|
|
354
|
+
wrapper_cache = object.__getattribute__(self, "_cached_modules")
|
|
355
|
+
this_module_wrapper = wrapper_cache.get(module)
|
|
356
|
+
|
|
357
|
+
if this_module_wrapper is None:
|
|
358
|
+
this_module_wrapper = CallBridger(
|
|
359
|
+
module, object.__getattribute__(self, "_loop")
|
|
360
|
+
)
|
|
361
|
+
wrapper_cache.update({module: this_module_wrapper})
|
|
362
|
+
|
|
363
|
+
return this_module_wrapper # type: ignore
|
|
364
|
+
|
|
365
|
+
def __getattribute__(self, attr_name: str) -> Any:
|
|
366
|
+
# hardware_control.api.API.attached_modules is the only hardware
|
|
367
|
+
# API method that returns something other than data. The module
|
|
368
|
+
# objects it returns have associated methods that can be called.
|
|
369
|
+
# That means they need the same wrapping treatment as the API
|
|
370
|
+
# itself.
|
|
371
|
+
if attr_name == "attached_modules":
|
|
372
|
+
wrap = object.__getattribute__(self, "wrap_module")
|
|
373
|
+
managed = object.__getattribute__(self, "managed_obj")
|
|
374
|
+
attr = getattr(managed, attr_name)
|
|
375
|
+
return [wrap(mod) for mod in attr]
|
|
376
|
+
elif attr_name == "clean_up":
|
|
377
|
+
# the wrapped object probably has this attr as well as us, and we
|
|
378
|
+
# want to call both, with the wrapped one first
|
|
379
|
+
|
|
380
|
+
# we only want to call cleanup once, and then only if the loop
|
|
381
|
+
# is running
|
|
382
|
+
wrapped_loop = object.__getattribute__(self, "_loop")
|
|
383
|
+
if not wrapped_loop.is_running():
|
|
384
|
+
return lambda: None
|
|
385
|
+
|
|
386
|
+
wrapped_cleanup = getattr(
|
|
387
|
+
object.__getattribute__(self, "bridged_obj"), "clean_up"
|
|
388
|
+
)
|
|
389
|
+
our_cleanup = object.__getattribute__(self, "clean_up_tm")
|
|
390
|
+
|
|
391
|
+
def call_both() -> None:
|
|
392
|
+
# the wrapped cleanup wants to happen in the managed thread,
|
|
393
|
+
# started from the managed loop. our cleanup wants to happen
|
|
394
|
+
# in the current thread, _after_ the wrapped cleanup is done
|
|
395
|
+
# so cancelled tasks can have a chance to complete.
|
|
396
|
+
async def clean_and_notify() -> None:
|
|
397
|
+
await wrapped_cleanup()
|
|
398
|
+
# this sleep allows the wrapped loop to spin to clean up the
|
|
399
|
+
# tasks we just cancelled.
|
|
400
|
+
await asyncio.sleep(0)
|
|
401
|
+
|
|
402
|
+
fut = asyncio.run_coroutine_threadsafe(clean_and_notify(), wrapped_loop)
|
|
403
|
+
fut.result()
|
|
404
|
+
our_cleanup()
|
|
405
|
+
|
|
406
|
+
return call_both
|
|
407
|
+
|
|
408
|
+
else:
|
|
409
|
+
try:
|
|
410
|
+
return getattr(object.__getattribute__(self, "bridged_obj"), attr_name)
|
|
411
|
+
except AttributeError:
|
|
412
|
+
return object.__getattribute__(self, attr_name)
|
|
413
|
+
|
|
414
|
+
def wrapped(self) -> WrappedObj:
|
|
415
|
+
"""Expose the type of the underlying wrapped object.
|
|
416
|
+
|
|
417
|
+
This isn't a method that does anything (it just returns self again) but the cast
|
|
418
|
+
means that the type of self will be what the threadmanager's generic wrapped
|
|
419
|
+
object is. You can therefore use this to get typechecking when using
|
|
420
|
+
ThreadManagers.
|
|
421
|
+
|
|
422
|
+
While the generic type is what you say it is when you annotate the instance
|
|
423
|
+
variable containing a ThreadManager, if you restrict yourself to annotating
|
|
424
|
+
those instances using a protocol from hardware_api.protocols, things will more
|
|
425
|
+
or less work out through the rest of the system. Not perfect, but ok.
|
|
426
|
+
"""
|
|
427
|
+
return cast(WrappedObj, self)
|
|
428
|
+
|
|
429
|
+
def wraps_instance(self, of_type: Type[Any]) -> bool:
|
|
430
|
+
"""Do isinstance() on the wrapped object."""
|
|
431
|
+
return isinstance(object.__getattribute__(self, "managed_obj"), of_type)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ThreadedAsyncLock:
|
|
12
|
+
"""A thread-safe async lock
|
|
13
|
+
|
|
14
|
+
This is required to properly lock access to motion calls, which are
|
|
15
|
+
a) done in async contexts (rpc methods and http methods) and should
|
|
16
|
+
block as little as possible
|
|
17
|
+
b) done from several different threads (rpc workers and main thread)
|
|
18
|
+
|
|
19
|
+
This is a code wart that needs to be removed. It can be removed by
|
|
20
|
+
- making smoothie async so we don't need worker threads anymore
|
|
21
|
+
- removing said threads
|
|
22
|
+
|
|
23
|
+
This object can be used as either an asynchronous context manager using
|
|
24
|
+
``async with`` or a synchronous context manager using ``with``.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self._thread_lock = threading.Lock()
|
|
29
|
+
|
|
30
|
+
def lock(self) -> "_Internal":
|
|
31
|
+
"""Create a context manager that locks access to a code block"""
|
|
32
|
+
return _Internal(lock=self._thread_lock, forbid=False)
|
|
33
|
+
|
|
34
|
+
def forbid(self) -> "_Internal":
|
|
35
|
+
"""Create a context manager that forbids concurrent attempts to
|
|
36
|
+
access to a code block"""
|
|
37
|
+
return _Internal(lock=self._thread_lock, forbid=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ThreadedAsyncForbidden(Exception):
|
|
41
|
+
"""Exception indicating that lock is acquired and that blocking
|
|
42
|
+
is forbidden"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
msg: str = "Robot is currently moving. Please wait and try "
|
|
47
|
+
"this command again.",
|
|
48
|
+
) -> None:
|
|
49
|
+
super().__init__(msg)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _Internal:
|
|
53
|
+
def __init__(self, lock: threading.Lock, forbid: bool) -> None:
|
|
54
|
+
"""
|
|
55
|
+
The private context manager that interacts with the lock. It can
|
|
56
|
+
behave in normal locking mode or in `forbid` mode. When `forbid` is
|
|
57
|
+
True, then trying to acquire a lock that is already acquired will
|
|
58
|
+
raise ThreadedAsyncForbidden. This forbids blocking on
|
|
59
|
+
critical sections.
|
|
60
|
+
|
|
61
|
+
:param lock: The Lock
|
|
62
|
+
:param forbid: whether to block or raise an exception
|
|
63
|
+
"""
|
|
64
|
+
self._thread_lock = lock
|
|
65
|
+
self._forbid = forbid
|
|
66
|
+
|
|
67
|
+
async def __aenter__(self) -> None:
|
|
68
|
+
pref = (
|
|
69
|
+
f"[ThreadedAsyncLock tid {threading.get_ident()} "
|
|
70
|
+
f"task {asyncio.current_task()}] "
|
|
71
|
+
)
|
|
72
|
+
log.debug(pref + "will acquire")
|
|
73
|
+
then = time.perf_counter()
|
|
74
|
+
while not self._thread_lock.acquire(blocking=False):
|
|
75
|
+
if self._forbid:
|
|
76
|
+
# Lock is already acquired and blocking is forbidden
|
|
77
|
+
raise ThreadedAsyncForbidden()
|
|
78
|
+
await asyncio.sleep(0.1)
|
|
79
|
+
now = time.perf_counter()
|
|
80
|
+
log.debug(pref + f"acquired in {now-then}s")
|
|
81
|
+
|
|
82
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
83
|
+
log.debug(
|
|
84
|
+
f"[ThreadedAsyncLock tid {threading.get_ident()} "
|
|
85
|
+
f"task {asyncio.current_task()}] will release"
|
|
86
|
+
)
|
|
87
|
+
self._thread_lock.release()
|
|
88
|
+
|
|
89
|
+
def __enter__(self) -> None:
|
|
90
|
+
if not self._forbid:
|
|
91
|
+
self._thread_lock.acquire()
|
|
92
|
+
elif not self._thread_lock.acquire(blocking=False):
|
|
93
|
+
# Lock is already acquired and blocking is forbidden
|
|
94
|
+
raise ThreadedAsyncForbidden()
|
|
95
|
+
|
|
96
|
+
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
97
|
+
self._thread_lock.release()
|