opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0__py2.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.
- opentrons/cli/analyze.py +71 -7
- opentrons/config/__init__.py +9 -0
- opentrons/config/advanced_settings.py +22 -0
- opentrons/config/defaults_ot3.py +14 -36
- opentrons/config/feature_flags.py +4 -0
- opentrons/config/types.py +6 -17
- opentrons/drivers/absorbance_reader/abstract.py +27 -3
- opentrons/drivers/absorbance_reader/async_byonoy.py +208 -154
- opentrons/drivers/absorbance_reader/driver.py +24 -15
- opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
- opentrons/drivers/absorbance_reader/simulator.py +32 -6
- opentrons/drivers/types.py +23 -1
- opentrons/execute.py +2 -2
- opentrons/hardware_control/api.py +18 -10
- opentrons/hardware_control/backends/controller.py +3 -2
- opentrons/hardware_control/backends/flex_protocol.py +11 -5
- opentrons/hardware_control/backends/ot3controller.py +18 -50
- opentrons/hardware_control/backends/ot3simulator.py +7 -6
- opentrons/hardware_control/backends/ot3utils.py +1 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
- opentrons/hardware_control/module_control.py +43 -2
- opentrons/hardware_control/modules/__init__.py +7 -1
- opentrons/hardware_control/modules/absorbance_reader.py +232 -83
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/heater_shaker.py +8 -3
- opentrons/hardware_control/modules/magdeck.py +12 -3
- opentrons/hardware_control/modules/mod_abc.py +27 -2
- opentrons/hardware_control/modules/tempdeck.py +15 -7
- opentrons/hardware_control/modules/thermocycler.py +69 -3
- opentrons/hardware_control/modules/types.py +11 -5
- opentrons/hardware_control/modules/update.py +11 -5
- opentrons/hardware_control/modules/utils.py +3 -1
- opentrons/hardware_control/ot3_calibration.py +6 -6
- opentrons/hardware_control/ot3api.py +131 -94
- opentrons/hardware_control/poller.py +15 -11
- opentrons/hardware_control/protocols/__init__.py +1 -7
- opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
- opentrons/hardware_control/protocols/liquid_handler.py +5 -0
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/hardware_control/types.py +2 -0
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/motion_planning/__init__.py +2 -0
- opentrons/motion_planning/waypoints.py +32 -0
- opentrons/protocol_api/__init__.py +2 -1
- opentrons/protocol_api/_liquid.py +87 -1
- opentrons/protocol_api/_parameter_context.py +10 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
- opentrons/protocol_api/core/engine/instrument.py +29 -25
- opentrons/protocol_api/core/engine/labware.py +20 -4
- opentrons/protocol_api/core/engine/module_core.py +166 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
- opentrons/protocol_api/core/engine/protocol.py +30 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/module.py +22 -4
- opentrons/protocol_api/core/protocol.py +6 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +115 -17
- opentrons/protocol_api/protocol_context.py +49 -5
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +3 -0
- opentrons/protocol_engine/actions/actions.py +30 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +1 -1
- opentrons/protocol_engine/clients/transports.py +1 -1
- opentrons/protocol_engine/commands/__init__.py +0 -4
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
- opentrons/protocol_engine/commands/blow_out.py +63 -14
- opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
- opentrons/protocol_engine/commands/command.py +31 -18
- opentrons/protocol_engine/commands/command_unions.py +37 -24
- opentrons/protocol_engine/commands/comment.py +5 -3
- opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
- opentrons/protocol_engine/commands/custom.py +5 -3
- opentrons/protocol_engine/commands/dispense.py +42 -20
- opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
- opentrons/protocol_engine/commands/drop_tip.py +70 -16
- opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
- opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/home.py +11 -5
- opentrons/protocol_engine/commands/liquid_probe.py +146 -88
- opentrons/protocol_engine/commands/load_labware.py +28 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +4 -6
- opentrons/protocol_engine/commands/load_pipette.py +18 -17
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
- opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
- opentrons/protocol_engine/commands/move_labware.py +155 -23
- opentrons/protocol_engine/commands/move_relative.py +15 -3
- opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
- opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
- opentrons/protocol_engine/commands/move_to_well.py +37 -10
- opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
- opentrons/protocol_engine/commands/pipetting_common.py +47 -16
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
- opentrons/protocol_engine/commands/reload_labware.py +13 -4
- opentrons/protocol_engine/commands/retract_axis.py +6 -3
- opentrons/protocol_engine/commands/save_position.py +2 -3
- opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
- opentrons/protocol_engine/commands/set_status_bar.py +5 -3
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
- opentrons/protocol_engine/commands/touch_tip.py +19 -7
- opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
- opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
- opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
- opentrons/protocol_engine/create_protocol_engine.py +60 -10
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +20 -0
- opentrons/protocol_engine/errors/error_occurrence.py +8 -3
- opentrons/protocol_engine/errors/exceptions.py +127 -2
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +22 -13
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/door_watcher.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +2 -1
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +4 -2
- opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
- opentrons/protocol_engine/execution/labware_movement.py +73 -22
- opentrons/protocol_engine/execution/movement.py +17 -7
- opentrons/protocol_engine/execution/pipetting.py +7 -4
- opentrons/protocol_engine/execution/queue_worker.py +6 -2
- opentrons/protocol_engine/execution/run_control.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
- opentrons/protocol_engine/execution/tip_handler.py +77 -43
- opentrons/protocol_engine/notes/__init__.py +14 -2
- opentrons/protocol_engine/notes/notes.py +18 -1
- opentrons/protocol_engine/plugins.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +47 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +11 -1
- opentrons/protocol_engine/resources/labware_validation.py +10 -0
- opentrons/protocol_engine/state/__init__.py +0 -70
- opentrons/protocol_engine/state/addressable_areas.py +1 -1
- opentrons/protocol_engine/state/command_history.py +21 -2
- opentrons/protocol_engine/state/commands.py +110 -31
- opentrons/protocol_engine/state/files.py +59 -0
- opentrons/protocol_engine/state/frustum_helpers.py +440 -0
- opentrons/protocol_engine/state/geometry.py +445 -59
- opentrons/protocol_engine/state/labware.py +264 -84
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
- opentrons/protocol_engine/state/modules.py +145 -90
- opentrons/protocol_engine/state/motion.py +33 -14
- opentrons/protocol_engine/state/pipettes.py +157 -317
- opentrons/protocol_engine/state/state.py +30 -1
- opentrons/protocol_engine/state/state_summary.py +3 -0
- opentrons/protocol_engine/state/tips.py +69 -114
- opentrons/protocol_engine/state/update_types.py +424 -0
- opentrons/protocol_engine/state/wells.py +236 -0
- opentrons/protocol_engine/types.py +90 -0
- opentrons/protocol_reader/file_format_validator.py +83 -15
- opentrons/protocol_runner/json_translator.py +21 -5
- opentrons/protocol_runner/legacy_command_mapper.py +27 -6
- opentrons/protocol_runner/legacy_context_plugin.py +27 -71
- opentrons/protocol_runner/protocol_runner.py +6 -3
- opentrons/protocol_runner/run_orchestrator.py +41 -6
- opentrons/protocols/advanced_control/mix.py +3 -5
- opentrons/protocols/advanced_control/transfers.py +125 -56
- opentrons/protocols/api_support/constants.py +1 -1
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/labware_like.py +4 -4
- opentrons/protocols/api_support/tip_tracker.py +2 -2
- opentrons/protocols/api_support/types.py +15 -2
- opentrons/protocols/api_support/util.py +30 -42
- opentrons/protocols/duration/errors.py +1 -1
- opentrons/protocols/duration/estimator.py +50 -29
- opentrons/protocols/execution/dev_types.py +2 -2
- opentrons/protocols/execution/execute_json_v4.py +15 -10
- opentrons/protocols/execution/execute_python.py +8 -3
- opentrons/protocols/geometry/planning.py +12 -12
- opentrons/protocols/labware.py +17 -33
- opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
- opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
- opentrons/protocol_engine/commands/configuring_common.py +0 -26
- opentrons/protocol_runner/thread_async_queue.py +0 -174
- /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
- /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,6 @@ from opentrons.protocol_engine.actions.actions import (
|
|
|
6
6
|
ResumeFromRecoveryAction,
|
|
7
7
|
SetErrorRecoveryPolicyAction,
|
|
8
8
|
)
|
|
9
|
-
from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy
|
|
10
9
|
|
|
11
10
|
from opentrons.protocols.models import LabwareDefinition
|
|
12
11
|
from opentrons.hardware_control import HardwareControlAPI
|
|
@@ -19,8 +18,9 @@ from opentrons_shared_data.errors import (
|
|
|
19
18
|
|
|
20
19
|
from .errors import ProtocolCommandFailedError, ErrorOccurrence, CommandNotAllowedError
|
|
21
20
|
from .errors.exceptions import EStopActivatedError
|
|
21
|
+
from .error_recovery_policy import ErrorRecoveryPolicy
|
|
22
22
|
from . import commands, slot_standardization
|
|
23
|
-
from .resources import ModelUtils, ModuleDataProvider
|
|
23
|
+
from .resources import ModelUtils, ModuleDataProvider, FileProvider
|
|
24
24
|
from .types import (
|
|
25
25
|
LabwareOffset,
|
|
26
26
|
LabwareOffsetCreate,
|
|
@@ -38,7 +38,8 @@ from .execution import (
|
|
|
38
38
|
DoorWatcher,
|
|
39
39
|
HardwareStopper,
|
|
40
40
|
)
|
|
41
|
-
from .state import StateStore, StateView
|
|
41
|
+
from .state.state import StateStore, StateView
|
|
42
|
+
from .state.update_types import StateUpdate
|
|
42
43
|
from .plugins import AbstractPlugin, PluginStarter
|
|
43
44
|
from .actions import (
|
|
44
45
|
ActionDispatcher,
|
|
@@ -87,41 +88,31 @@ class ProtocolEngine:
|
|
|
87
88
|
self,
|
|
88
89
|
hardware_api: HardwareControlAPI,
|
|
89
90
|
state_store: StateStore,
|
|
90
|
-
action_dispatcher:
|
|
91
|
-
plugin_starter:
|
|
91
|
+
action_dispatcher: ActionDispatcher,
|
|
92
|
+
plugin_starter: PluginStarter,
|
|
93
|
+
model_utils: ModelUtils,
|
|
94
|
+
hardware_stopper: HardwareStopper,
|
|
95
|
+
door_watcher: DoorWatcher,
|
|
96
|
+
module_data_provider: ModuleDataProvider,
|
|
97
|
+
file_provider: FileProvider,
|
|
92
98
|
queue_worker: Optional[QueueWorker] = None,
|
|
93
|
-
model_utils: Optional[ModelUtils] = None,
|
|
94
|
-
hardware_stopper: Optional[HardwareStopper] = None,
|
|
95
|
-
door_watcher: Optional[DoorWatcher] = None,
|
|
96
|
-
module_data_provider: Optional[ModuleDataProvider] = None,
|
|
97
99
|
) -> None:
|
|
98
100
|
"""Initialize a ProtocolEngine instance.
|
|
99
101
|
|
|
100
102
|
Must be called while an event loop is active.
|
|
101
103
|
|
|
102
|
-
This constructor
|
|
104
|
+
This constructor is only for `ProtocolEngine` unit tests.
|
|
103
105
|
Prefer the `create_protocol_engine()` factory function.
|
|
104
106
|
"""
|
|
105
107
|
self._hardware_api = hardware_api
|
|
108
|
+
self._file_provider = file_provider
|
|
106
109
|
self._state_store = state_store
|
|
107
|
-
self._model_utils = model_utils
|
|
108
|
-
self._action_dispatcher = action_dispatcher
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self.
|
|
112
|
-
|
|
113
|
-
action_dispatcher=self._action_dispatcher,
|
|
114
|
-
)
|
|
115
|
-
self._hardware_stopper = hardware_stopper or HardwareStopper(
|
|
116
|
-
hardware_api=hardware_api,
|
|
117
|
-
state_store=state_store,
|
|
118
|
-
)
|
|
119
|
-
self._door_watcher = door_watcher or DoorWatcher(
|
|
120
|
-
state_store=state_store,
|
|
121
|
-
hardware_api=hardware_api,
|
|
122
|
-
action_dispatcher=self._action_dispatcher,
|
|
123
|
-
)
|
|
124
|
-
self._module_data_provider = module_data_provider or ModuleDataProvider()
|
|
110
|
+
self._model_utils = model_utils
|
|
111
|
+
self._action_dispatcher = action_dispatcher
|
|
112
|
+
self._plugin_starter = plugin_starter
|
|
113
|
+
self._hardware_stopper = hardware_stopper
|
|
114
|
+
self._door_watcher = door_watcher
|
|
115
|
+
self._module_data_provider = module_data_provider
|
|
125
116
|
self._queue_worker = queue_worker
|
|
126
117
|
if self._queue_worker:
|
|
127
118
|
self._queue_worker.start()
|
|
@@ -183,11 +174,35 @@ class ProtocolEngine:
|
|
|
183
174
|
self._action_dispatcher.dispatch(action)
|
|
184
175
|
self._hardware_api.pause(HardwarePauseType.PAUSE)
|
|
185
176
|
|
|
186
|
-
def resume_from_recovery(self) -> None:
|
|
187
|
-
"""Resume normal protocol execution after the engine was `AWAITING_RECOVERY`.
|
|
177
|
+
def resume_from_recovery(self, reconcile_false_positive: bool) -> None:
|
|
178
|
+
"""Resume normal protocol execution after the engine was `AWAITING_RECOVERY`.
|
|
179
|
+
|
|
180
|
+
If `reconcile_false_positive` is `False`, the engine will continue naively from
|
|
181
|
+
whatever state the error left it in. (Each defined error individually documents
|
|
182
|
+
exactly how it affects state.) This is appropriate for client-driven error
|
|
183
|
+
recovery, where the client wants predictable behavior from the engine.
|
|
184
|
+
|
|
185
|
+
If `reconcile_false_positive` is `True`, the engine may apply additional fixups
|
|
186
|
+
to its state to try to get the rest of the run to just work, assuming the error
|
|
187
|
+
was a false-positive.
|
|
188
|
+
|
|
189
|
+
For example, a `tipPhysicallyMissing` error from a `pickUpTip` would normally
|
|
190
|
+
leave the engine state without a tip on the pipette. If `reconcile_false_positive=True`,
|
|
191
|
+
the engine will set the pipette to have that missing tip before continuing, so
|
|
192
|
+
subsequent path planning, aspirates, dispenses, etc. will work as if nothing
|
|
193
|
+
went wrong.
|
|
194
|
+
"""
|
|
195
|
+
if reconcile_false_positive:
|
|
196
|
+
state_update = (
|
|
197
|
+
self._state_store.commands.get_state_update_for_false_positive()
|
|
198
|
+
)
|
|
199
|
+
else:
|
|
200
|
+
state_update = StateUpdate() # Empty/no-op.
|
|
201
|
+
|
|
188
202
|
action = self._state_store.commands.validate_action_allowed(
|
|
189
|
-
ResumeFromRecoveryAction()
|
|
203
|
+
ResumeFromRecoveryAction(state_update)
|
|
190
204
|
)
|
|
205
|
+
|
|
191
206
|
self._action_dispatcher.dispatch(action)
|
|
192
207
|
|
|
193
208
|
def add_command(
|
|
@@ -609,6 +624,7 @@ class ProtocolEngine:
|
|
|
609
624
|
assert self._queue_worker is None
|
|
610
625
|
self._queue_worker = create_queue_worker(
|
|
611
626
|
hardware_api=self._hardware_api,
|
|
627
|
+
file_provider=self._file_provider,
|
|
612
628
|
state_store=self._state_store,
|
|
613
629
|
action_dispatcher=self._action_dispatcher,
|
|
614
630
|
command_generator=command_generator,
|
|
@@ -9,6 +9,7 @@ from .model_utils import ModelUtils
|
|
|
9
9
|
from .deck_data_provider import DeckDataProvider, DeckFixedLabware
|
|
10
10
|
from .labware_data_provider import LabwareDataProvider
|
|
11
11
|
from .module_data_provider import ModuleDataProvider
|
|
12
|
+
from .file_provider import FileProvider
|
|
12
13
|
from .ot3_validation import ensure_ot3_hardware
|
|
13
14
|
|
|
14
15
|
|
|
@@ -18,6 +19,7 @@ __all__ = [
|
|
|
18
19
|
"DeckDataProvider",
|
|
19
20
|
"DeckFixedLabware",
|
|
20
21
|
"ModuleDataProvider",
|
|
22
|
+
"FileProvider",
|
|
21
23
|
"ensure_ot3_hardware",
|
|
22
24
|
"pipette_data_provider",
|
|
23
25
|
"labware_validation",
|
|
@@ -13,7 +13,12 @@ from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
|
13
13
|
from opentrons.protocols.models import LabwareDefinition
|
|
14
14
|
from opentrons.types import DeckSlotName
|
|
15
15
|
|
|
16
|
-
from ..types import
|
|
16
|
+
from ..types import (
|
|
17
|
+
DeckSlotLocation,
|
|
18
|
+
DeckType,
|
|
19
|
+
LabwareLocation,
|
|
20
|
+
DeckConfigurationType,
|
|
21
|
+
)
|
|
17
22
|
from .labware_data_provider import LabwareDataProvider
|
|
18
23
|
|
|
19
24
|
|
|
@@ -23,7 +28,7 @@ class DeckFixedLabware:
|
|
|
23
28
|
"""A labware fixture that is always present on a deck."""
|
|
24
29
|
|
|
25
30
|
labware_id: str
|
|
26
|
-
location:
|
|
31
|
+
location: LabwareLocation
|
|
27
32
|
definition: LabwareDefinition
|
|
28
33
|
|
|
29
34
|
|
|
@@ -51,7 +56,9 @@ class DeckDataProvider:
|
|
|
51
56
|
|
|
52
57
|
async def get_deck_fixed_labware(
|
|
53
58
|
self,
|
|
59
|
+
load_fixed_trash: bool,
|
|
54
60
|
deck_definition: DeckDefinitionV5,
|
|
61
|
+
deck_configuration: Optional[DeckConfigurationType] = None,
|
|
55
62
|
) -> List[DeckFixedLabware]:
|
|
56
63
|
"""Get a list of all labware fixtures from a given deck definition."""
|
|
57
64
|
labware: List[DeckFixedLabware] = []
|
|
@@ -61,8 +68,15 @@ class DeckDataProvider:
|
|
|
61
68
|
load_name = cast(Optional[str], fixture.get("labware"))
|
|
62
69
|
slot = cast(Optional[str], fixture.get("slot"))
|
|
63
70
|
|
|
64
|
-
if
|
|
65
|
-
|
|
71
|
+
if (
|
|
72
|
+
load_fixed_trash
|
|
73
|
+
and load_name is not None
|
|
74
|
+
and slot is not None
|
|
75
|
+
and slot in DeckSlotName._value2member_map_
|
|
76
|
+
):
|
|
77
|
+
deck_slot_location = DeckSlotLocation(
|
|
78
|
+
slotName=DeckSlotName.from_primitive(slot)
|
|
79
|
+
)
|
|
66
80
|
definition = await self._labware_data.get_labware_definition(
|
|
67
81
|
load_name=load_name,
|
|
68
82
|
namespace="opentrons",
|
|
@@ -73,7 +87,7 @@ class DeckDataProvider:
|
|
|
73
87
|
DeckFixedLabware(
|
|
74
88
|
labware_id=labware_id,
|
|
75
89
|
definition=definition,
|
|
76
|
-
location=
|
|
90
|
+
location=deck_slot_location,
|
|
77
91
|
)
|
|
78
92
|
)
|
|
79
93
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""File interaction resource provider."""
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import List, Optional, Callable, Awaitable, Dict
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from ..errors import StorageLimitReachedError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
MAXIMUM_CSV_FILE_LIMIT = 400
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GenericCsvTransform:
|
|
12
|
+
"""Generic CSV File Type data for rows of data to be seperated by a delimeter."""
|
|
13
|
+
|
|
14
|
+
filename: str
|
|
15
|
+
rows: List[List[str]]
|
|
16
|
+
delimiter: str = ","
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def build(
|
|
20
|
+
filename: str, rows: List[List[str]], delimiter: str = ","
|
|
21
|
+
) -> "GenericCsvTransform":
|
|
22
|
+
"""Build a Generic CSV datatype class."""
|
|
23
|
+
if "." in filename and not filename.endswith(".csv"):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Provided filename {filename} invalid. Only CSV file format is accepted."
|
|
26
|
+
)
|
|
27
|
+
elif "." not in filename:
|
|
28
|
+
filename = f"{filename}.csv"
|
|
29
|
+
csv = GenericCsvTransform()
|
|
30
|
+
csv.filename = filename
|
|
31
|
+
csv.rows = rows
|
|
32
|
+
csv.delimiter = delimiter
|
|
33
|
+
return csv
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ReadData(BaseModel):
|
|
37
|
+
"""Read Data type containing the wavelength for a Plate Reader read alongside the Measurement Data of that read."""
|
|
38
|
+
|
|
39
|
+
wavelength: int
|
|
40
|
+
data: Dict[str, float]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PlateReaderData(BaseModel):
|
|
44
|
+
"""Data from a Opentrons Plate Reader Read. Can be converted to CSV template format."""
|
|
45
|
+
|
|
46
|
+
read_results: List[ReadData]
|
|
47
|
+
reference_wavelength: Optional[int] = None
|
|
48
|
+
start_time: datetime
|
|
49
|
+
finish_time: datetime
|
|
50
|
+
serial_number: str
|
|
51
|
+
|
|
52
|
+
def build_generic_csv( # noqa: C901
|
|
53
|
+
self, filename: str, measurement: ReadData
|
|
54
|
+
) -> GenericCsvTransform:
|
|
55
|
+
"""Builds a CSV compatible object containing Plate Reader Measurements.
|
|
56
|
+
|
|
57
|
+
This will also automatically reformat the provided filename to include the wavelength of those measurements.
|
|
58
|
+
"""
|
|
59
|
+
plate_alpharows = ["A", "B", "C", "D", "E", "F", "G", "H"]
|
|
60
|
+
rows = []
|
|
61
|
+
|
|
62
|
+
rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"])
|
|
63
|
+
for i in range(8):
|
|
64
|
+
row = [plate_alpharows[i]]
|
|
65
|
+
for j in range(12):
|
|
66
|
+
row.append(str(measurement.data[f"{plate_alpharows[i]}{j+1}"]))
|
|
67
|
+
rows.append(row)
|
|
68
|
+
for i in range(3):
|
|
69
|
+
rows.append([])
|
|
70
|
+
rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"])
|
|
71
|
+
for i in range(8):
|
|
72
|
+
row = [plate_alpharows[i]]
|
|
73
|
+
for j in range(12):
|
|
74
|
+
row.append("")
|
|
75
|
+
rows.append(row)
|
|
76
|
+
for i in range(3):
|
|
77
|
+
rows.append([])
|
|
78
|
+
rows.append(
|
|
79
|
+
[
|
|
80
|
+
"",
|
|
81
|
+
"ID",
|
|
82
|
+
"Well",
|
|
83
|
+
"Absorbance (OD)",
|
|
84
|
+
"Mean Absorbance (OD)",
|
|
85
|
+
"Absorbance %CV",
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
for i in range(3):
|
|
89
|
+
rows.append([])
|
|
90
|
+
rows.append(
|
|
91
|
+
[
|
|
92
|
+
"",
|
|
93
|
+
"ID",
|
|
94
|
+
"Well",
|
|
95
|
+
"Absorbance (OD)",
|
|
96
|
+
"Mean Absorbance (OD)",
|
|
97
|
+
"Dilution Factor",
|
|
98
|
+
"Absorbance %CV",
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
rows.append(["1", "Sample 1", "", "", "", "1", "", "", "", "", "", ""])
|
|
102
|
+
for i in range(3):
|
|
103
|
+
rows.append([])
|
|
104
|
+
|
|
105
|
+
# end of file metadata
|
|
106
|
+
rows.append(["Protocol"])
|
|
107
|
+
rows.append(["Assay"])
|
|
108
|
+
rows.append(["Sample Wavelength (nm)", str(measurement.wavelength)])
|
|
109
|
+
if self.reference_wavelength is not None:
|
|
110
|
+
rows.append(["Reference Wavelength (nm)", str(self.reference_wavelength)])
|
|
111
|
+
rows.append(["Serial No.", self.serial_number])
|
|
112
|
+
rows.append(
|
|
113
|
+
["Measurement started at", self.start_time.strftime("%m %d %H:%M:%S %Y")]
|
|
114
|
+
)
|
|
115
|
+
rows.append(
|
|
116
|
+
["Measurement finished at", self.finish_time.strftime("%m %d %H:%M:%S %Y")]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Ensure the filename adheres to ruleset contains the wavelength for a given measurement
|
|
120
|
+
if filename.endswith(".csv"):
|
|
121
|
+
filename = filename[:-4]
|
|
122
|
+
filename = filename + str(measurement.wavelength) + "nm.csv"
|
|
123
|
+
|
|
124
|
+
return GenericCsvTransform.build(
|
|
125
|
+
filename=filename,
|
|
126
|
+
rows=rows,
|
|
127
|
+
delimiter=",",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class FileProvider:
|
|
132
|
+
"""Provider class to wrap file read write interactions to the data files directory in the engine."""
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
data_files_write_csv_callback: Optional[
|
|
137
|
+
Callable[[GenericCsvTransform], Awaitable[str]]
|
|
138
|
+
] = None,
|
|
139
|
+
data_files_filecount: Optional[Callable[[], Awaitable[int]]] = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Initialize the interface callbacks of the File Provider for data file handling within the Protocol Engine.
|
|
142
|
+
|
|
143
|
+
Params:
|
|
144
|
+
data_files_write_csv_callback: Callback to write a CSV file to the data files directory and add it to the database.
|
|
145
|
+
data_files_filecount: Callback to check the amount of data files already present in the data files directory.
|
|
146
|
+
"""
|
|
147
|
+
self._data_files_write_csv_callback = data_files_write_csv_callback
|
|
148
|
+
self._data_files_filecount = data_files_filecount
|
|
149
|
+
|
|
150
|
+
async def write_csv(self, write_data: GenericCsvTransform) -> str:
|
|
151
|
+
"""Writes the provided CSV object to a file in the Data Files directory. Returns the File ID of the file created."""
|
|
152
|
+
if self._data_files_filecount is not None:
|
|
153
|
+
file_count = await self._data_files_filecount()
|
|
154
|
+
if file_count >= MAXIMUM_CSV_FILE_LIMIT:
|
|
155
|
+
raise StorageLimitReachedError(
|
|
156
|
+
f"Not enough space to store file {write_data.filename}."
|
|
157
|
+
)
|
|
158
|
+
if self._data_files_write_csv_callback is not None:
|
|
159
|
+
return await self._data_files_write_csv_callback(write_data)
|
|
160
|
+
# If we are in an analysis or simulation state, return an empty file ID
|
|
161
|
+
return ""
|
|
@@ -29,7 +29,12 @@ def is_drop_tip_waste_chute(addressable_area_name: str) -> bool:
|
|
|
29
29
|
|
|
30
30
|
def is_trash(addressable_area_name: str) -> bool:
|
|
31
31
|
"""Check if an addressable area is a trash bin."""
|
|
32
|
-
return
|
|
32
|
+
return any(
|
|
33
|
+
[
|
|
34
|
+
s in addressable_area_name
|
|
35
|
+
for s in {"movableTrash", "fixedTrash", "shortFixedTrash"}
|
|
36
|
+
]
|
|
37
|
+
)
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
def is_staging_slot(addressable_area_name: str) -> bool:
|
|
@@ -46,3 +51,8 @@ def is_deck_slot(addressable_area_name: str) -> bool:
|
|
|
46
51
|
except ValueError:
|
|
47
52
|
return False
|
|
48
53
|
return True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_abs_reader(addressable_area_name: str) -> bool:
|
|
57
|
+
"""Check if an addressable area is an absorbance plate reader area."""
|
|
58
|
+
return "absorbanceReaderV1" in addressable_area_name
|
|
@@ -9,6 +9,11 @@ def is_flex_trash(load_name: str) -> bool:
|
|
|
9
9
|
return load_name == "opentrons_1_trash_3200ml_fixed"
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def is_absorbance_reader_lid(load_name: str) -> bool:
|
|
13
|
+
"""Check if a labware is an absorbance reader lid."""
|
|
14
|
+
return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
|
|
15
|
+
|
|
16
|
+
|
|
12
17
|
def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
|
|
13
18
|
"""Validate that one of the definition's allowed roles is `labware`.
|
|
14
19
|
|
|
@@ -22,6 +27,11 @@ def validate_definition_is_adapter(definition: LabwareDefinition) -> bool:
|
|
|
22
27
|
return LabwareRole.adapter in definition.allowedRoles
|
|
23
28
|
|
|
24
29
|
|
|
30
|
+
def validate_definition_is_lid(definition: LabwareDefinition) -> bool:
|
|
31
|
+
"""Validate that one of the definition's allowed roles is `lid`."""
|
|
32
|
+
return LabwareRole.lid in definition.allowedRoles
|
|
33
|
+
|
|
34
|
+
|
|
25
35
|
def validate_labware_can_be_stacked(
|
|
26
36
|
top_labware_definition: LabwareDefinition, below_labware_load_name: str
|
|
27
37
|
) -> bool:
|
|
@@ -1,71 +1 @@
|
|
|
1
1
|
"""Protocol engine state module."""
|
|
2
|
-
|
|
3
|
-
from .state import State, StateStore, StateView
|
|
4
|
-
from .state_summary import StateSummary
|
|
5
|
-
from .config import Config
|
|
6
|
-
from .commands import (
|
|
7
|
-
CommandState,
|
|
8
|
-
CommandView,
|
|
9
|
-
CommandSlice,
|
|
10
|
-
CommandErrorSlice,
|
|
11
|
-
CommandPointer,
|
|
12
|
-
)
|
|
13
|
-
from .command_history import CommandEntry
|
|
14
|
-
from .labware import LabwareState, LabwareView
|
|
15
|
-
from .pipettes import PipetteState, PipetteView, HardwarePipette
|
|
16
|
-
from .modules import ModuleState, ModuleView, HardwareModule
|
|
17
|
-
from .module_substates import (
|
|
18
|
-
MagneticModuleId,
|
|
19
|
-
MagneticModuleSubState,
|
|
20
|
-
HeaterShakerModuleId,
|
|
21
|
-
HeaterShakerModuleSubState,
|
|
22
|
-
TemperatureModuleId,
|
|
23
|
-
TemperatureModuleSubState,
|
|
24
|
-
ThermocyclerModuleId,
|
|
25
|
-
ThermocyclerModuleSubState,
|
|
26
|
-
ModuleSubStateType,
|
|
27
|
-
)
|
|
28
|
-
from .geometry import GeometryView
|
|
29
|
-
from .motion import MotionView, PipetteLocationData
|
|
30
|
-
|
|
31
|
-
__all__ = [
|
|
32
|
-
# top level state value and interfaces
|
|
33
|
-
"State",
|
|
34
|
-
"StateStore",
|
|
35
|
-
"StateView",
|
|
36
|
-
"StateSummary",
|
|
37
|
-
# static engine configuration
|
|
38
|
-
"Config",
|
|
39
|
-
# command state and values
|
|
40
|
-
"CommandState",
|
|
41
|
-
"CommandView",
|
|
42
|
-
"CommandSlice",
|
|
43
|
-
"CommandErrorSlice",
|
|
44
|
-
"CommandPointer",
|
|
45
|
-
"CommandEntry",
|
|
46
|
-
# labware state and values
|
|
47
|
-
"LabwareState",
|
|
48
|
-
"LabwareView",
|
|
49
|
-
# pipette state and values
|
|
50
|
-
"PipetteState",
|
|
51
|
-
"PipetteView",
|
|
52
|
-
"HardwarePipette",
|
|
53
|
-
# module state and values
|
|
54
|
-
"ModuleState",
|
|
55
|
-
"ModuleView",
|
|
56
|
-
"HardwareModule",
|
|
57
|
-
"MagneticModuleId",
|
|
58
|
-
"MagneticModuleSubState",
|
|
59
|
-
"HeaterShakerModuleId",
|
|
60
|
-
"HeaterShakerModuleSubState",
|
|
61
|
-
"TemperatureModuleId",
|
|
62
|
-
"TemperatureModuleSubState",
|
|
63
|
-
"ThermocyclerModuleId",
|
|
64
|
-
"ThermocyclerModuleSubState",
|
|
65
|
-
"ModuleSubStateType",
|
|
66
|
-
# computed geometry state
|
|
67
|
-
"GeometryView",
|
|
68
|
-
# computed motion state
|
|
69
|
-
"MotionView",
|
|
70
|
-
"PipetteLocationData",
|
|
71
|
-
]
|
|
@@ -24,6 +24,9 @@ class CommandHistory:
|
|
|
24
24
|
_all_command_ids: List[str]
|
|
25
25
|
"""All command IDs, in insertion order."""
|
|
26
26
|
|
|
27
|
+
_all_command_ids_but_fixit_command_ids: List[str]
|
|
28
|
+
"""All command IDs besides fixit command intents, in insertion order."""
|
|
29
|
+
|
|
27
30
|
_commands_by_id: Dict[str, CommandEntry]
|
|
28
31
|
"""All command resources, in insertion order, mapped by their unique IDs."""
|
|
29
32
|
|
|
@@ -44,6 +47,7 @@ class CommandHistory:
|
|
|
44
47
|
|
|
45
48
|
def __init__(self) -> None:
|
|
46
49
|
self._all_command_ids = []
|
|
50
|
+
self._all_command_ids_but_fixit_command_ids = []
|
|
47
51
|
self._queued_command_ids = OrderedSet()
|
|
48
52
|
self._queued_setup_command_ids = OrderedSet()
|
|
49
53
|
self._queued_fixit_command_ids = OrderedSet()
|
|
@@ -97,13 +101,26 @@ class CommandHistory:
|
|
|
97
101
|
for command_id in self._all_command_ids
|
|
98
102
|
]
|
|
99
103
|
|
|
104
|
+
def get_filtered_command_ids(self, include_fixit_commands: bool) -> List[str]:
|
|
105
|
+
"""Get all fixit command IDs."""
|
|
106
|
+
if include_fixit_commands:
|
|
107
|
+
return self._all_command_ids
|
|
108
|
+
else:
|
|
109
|
+
return self._all_command_ids_but_fixit_command_ids
|
|
110
|
+
|
|
100
111
|
def get_all_ids(self) -> List[str]:
|
|
101
112
|
"""Get all command IDs."""
|
|
102
113
|
return self._all_command_ids
|
|
103
114
|
|
|
104
|
-
def get_slice(
|
|
105
|
-
|
|
115
|
+
def get_slice(
|
|
116
|
+
self, start: int, stop: int, command_ids: Optional[list[str]] = None
|
|
117
|
+
) -> List[Command]:
|
|
118
|
+
"""Get a list of commands between start and stop.""" """Get a list of commands between start and stop."""
|
|
106
119
|
commands = self._all_command_ids[start:stop]
|
|
120
|
+
selected_command_ids = (
|
|
121
|
+
command_ids if command_ids is not None else self._all_command_ids
|
|
122
|
+
)
|
|
123
|
+
commands = selected_command_ids[start:stop]
|
|
107
124
|
return [self._commands_by_id[command].command for command in commands]
|
|
108
125
|
|
|
109
126
|
def get_tail_command(self) -> Optional[CommandEntry]:
|
|
@@ -230,6 +247,8 @@ class CommandHistory:
|
|
|
230
247
|
"""Create or update a command entry."""
|
|
231
248
|
if command_id not in self._commands_by_id:
|
|
232
249
|
self._all_command_ids.append(command_id)
|
|
250
|
+
if command_entry.command.intent != CommandIntent.FIXIT:
|
|
251
|
+
self._all_command_ids_but_fixit_command_ids.append(command_id)
|
|
233
252
|
self._commands_by_id[command_id] = command_entry
|
|
234
253
|
|
|
235
254
|
def _add_to_queue(self, command_id: str) -> None:
|