opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__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.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- 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 +207 -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/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 +230 -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 +126 -89
- 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/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 +10 -2
- opentrons/protocol_api/core/engine/module_core.py +129 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
- opentrons/protocol_api/core/engine/protocol.py +55 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -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 +5 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +68 -13
- opentrons/protocol_api/protocol_context.py +38 -4
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +5 -0
- opentrons/protocol_engine/actions/actions.py +42 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +7 -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 +161 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
- 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 +28 -17
- 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 +68 -15
- opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
- 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 +19 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +43 -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 +106 -19
- 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 +50 -29
- opentrons/protocol_engine/commands/pipetting_common.py +39 -15
- 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 +194 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
- 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 +41 -8
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +18 -0
- opentrons/protocol_engine/errors/exceptions.py +114 -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 +6 -3
- opentrons/protocol_engine/execution/movement.py +8 -3
- 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 +54 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
- opentrons/protocol_engine/resources/file_provider.py +157 -0
- opentrons/protocol_engine/resources/fixture_validation.py +5 -0
- 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 +359 -15
- opentrons/protocol_engine/state/labware.py +166 -63
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
- opentrons/protocol_engine/state/modules.py +167 -85
- opentrons/protocol_engine/state/motion.py +16 -9
- 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 +408 -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 +26 -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/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.0a0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
- 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.0a0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -5,13 +5,22 @@ import typing
|
|
|
5
5
|
|
|
6
6
|
from opentrons.hardware_control import HardwareControlAPI
|
|
7
7
|
from opentrons.hardware_control.types import DoorState
|
|
8
|
-
from opentrons.protocol_engine.
|
|
8
|
+
from opentrons.protocol_engine.execution.error_recovery_hardware_state_synchronizer import (
|
|
9
|
+
ErrorRecoveryHardwareStateSynchronizer,
|
|
10
|
+
)
|
|
9
11
|
from opentrons.util.async_helpers import async_context_manager_in_thread
|
|
12
|
+
|
|
10
13
|
from opentrons_shared_data.robot import load as load_robot
|
|
11
14
|
|
|
15
|
+
from .actions.action_dispatcher import ActionDispatcher
|
|
16
|
+
from .error_recovery_policy import ErrorRecoveryPolicy
|
|
17
|
+
from .execution.door_watcher import DoorWatcher
|
|
18
|
+
from .execution.hardware_stopper import HardwareStopper
|
|
19
|
+
from .plugins import PluginStarter
|
|
12
20
|
from .protocol_engine import ProtocolEngine
|
|
13
|
-
from .resources import DeckDataProvider, ModuleDataProvider
|
|
14
|
-
from .state import Config
|
|
21
|
+
from .resources import DeckDataProvider, ModuleDataProvider, FileProvider, ModelUtils
|
|
22
|
+
from .state.config import Config
|
|
23
|
+
from .state.state import StateStore
|
|
15
24
|
from .types import PostRunHardwareState, DeckConfigurationType
|
|
16
25
|
|
|
17
26
|
from .engine_support import create_run_orchestrator
|
|
@@ -25,6 +34,7 @@ async def create_protocol_engine(
|
|
|
25
34
|
error_recovery_policy: ErrorRecoveryPolicy,
|
|
26
35
|
load_fixed_trash: bool = False,
|
|
27
36
|
deck_configuration: typing.Optional[DeckConfigurationType] = None,
|
|
37
|
+
file_provider: typing.Optional[FileProvider] = None,
|
|
28
38
|
notify_publishers: typing.Optional[typing.Callable[[], None]] = None,
|
|
29
39
|
) -> ProtocolEngine:
|
|
30
40
|
"""Create a ProtocolEngine instance.
|
|
@@ -36,17 +46,18 @@ async def create_protocol_engine(
|
|
|
36
46
|
See documentation on `ErrorRecoveryPolicy`.
|
|
37
47
|
load_fixed_trash: Automatically load fixed trash labware in engine.
|
|
38
48
|
deck_configuration: The initial deck configuration the engine will be instantiated with.
|
|
49
|
+
file_provider: Provides access to robot server file writing procedures for protocol output.
|
|
39
50
|
notify_publishers: Notifies robot server publishers of internal state change.
|
|
40
51
|
"""
|
|
41
52
|
deck_data = DeckDataProvider(config.deck_type)
|
|
42
53
|
deck_definition = await deck_data.get_deck_definition()
|
|
43
|
-
deck_fixed_labware = (
|
|
44
|
-
|
|
45
|
-
if load_fixed_trash
|
|
46
|
-
else []
|
|
54
|
+
deck_fixed_labware = await deck_data.get_deck_fixed_labware(
|
|
55
|
+
load_fixed_trash, deck_definition, deck_configuration
|
|
47
56
|
)
|
|
57
|
+
|
|
48
58
|
module_calibration_offsets = ModuleDataProvider.load_module_calibrations()
|
|
49
59
|
robot_definition = load_robot(config.robot_type)
|
|
60
|
+
|
|
50
61
|
state_store = StateStore(
|
|
51
62
|
config=config,
|
|
52
63
|
deck_definition=deck_definition,
|
|
@@ -58,10 +69,28 @@ async def create_protocol_engine(
|
|
|
58
69
|
deck_configuration=deck_configuration,
|
|
59
70
|
notify_publishers=notify_publishers,
|
|
60
71
|
)
|
|
72
|
+
hardware_state_synchronizer = ErrorRecoveryHardwareStateSynchronizer(
|
|
73
|
+
hardware_api, state_store
|
|
74
|
+
)
|
|
75
|
+
action_dispatcher = ActionDispatcher(state_store)
|
|
76
|
+
action_dispatcher.add_handler(hardware_state_synchronizer)
|
|
77
|
+
plugin_starter = PluginStarter(state_store, action_dispatcher)
|
|
78
|
+
model_utils = ModelUtils()
|
|
79
|
+
hardware_stopper = HardwareStopper(hardware_api, state_store)
|
|
80
|
+
door_watcher = DoorWatcher(state_store, hardware_api, action_dispatcher)
|
|
81
|
+
module_data_provider = ModuleDataProvider()
|
|
82
|
+
file_provider = file_provider or FileProvider()
|
|
61
83
|
|
|
62
84
|
return ProtocolEngine(
|
|
63
|
-
state_store=state_store,
|
|
64
85
|
hardware_api=hardware_api,
|
|
86
|
+
state_store=state_store,
|
|
87
|
+
action_dispatcher=action_dispatcher,
|
|
88
|
+
plugin_starter=plugin_starter,
|
|
89
|
+
model_utils=model_utils,
|
|
90
|
+
hardware_stopper=hardware_stopper,
|
|
91
|
+
door_watcher=door_watcher,
|
|
92
|
+
module_data_provider=module_data_provider,
|
|
93
|
+
file_provider=file_provider,
|
|
65
94
|
)
|
|
66
95
|
|
|
67
96
|
|
|
@@ -70,6 +99,7 @@ def create_protocol_engine_in_thread(
|
|
|
70
99
|
hardware_api: HardwareControlAPI,
|
|
71
100
|
config: Config,
|
|
72
101
|
deck_configuration: typing.Optional[DeckConfigurationType],
|
|
102
|
+
file_provider: typing.Optional[FileProvider],
|
|
73
103
|
error_recovery_policy: ErrorRecoveryPolicy,
|
|
74
104
|
drop_tips_after_run: bool,
|
|
75
105
|
post_run_hardware_state: PostRunHardwareState,
|
|
@@ -97,6 +127,7 @@ def create_protocol_engine_in_thread(
|
|
|
97
127
|
with async_context_manager_in_thread(
|
|
98
128
|
_protocol_engine(
|
|
99
129
|
hardware_api,
|
|
130
|
+
file_provider,
|
|
100
131
|
config,
|
|
101
132
|
deck_configuration,
|
|
102
133
|
error_recovery_policy,
|
|
@@ -114,6 +145,7 @@ def create_protocol_engine_in_thread(
|
|
|
114
145
|
@contextlib.asynccontextmanager
|
|
115
146
|
async def _protocol_engine(
|
|
116
147
|
hardware_api: HardwareControlAPI,
|
|
148
|
+
file_provider: typing.Optional[FileProvider],
|
|
117
149
|
config: Config,
|
|
118
150
|
deck_configuration: typing.Optional[DeckConfigurationType],
|
|
119
151
|
error_recovery_policy: ErrorRecoveryPolicy,
|
|
@@ -123,6 +155,7 @@ async def _protocol_engine(
|
|
|
123
155
|
) -> typing.AsyncGenerator[ProtocolEngine, None]:
|
|
124
156
|
protocol_engine = await create_protocol_engine(
|
|
125
157
|
hardware_api=hardware_api,
|
|
158
|
+
file_provider=file_provider,
|
|
126
159
|
config=config,
|
|
127
160
|
error_recovery_policy=error_recovery_policy,
|
|
128
161
|
load_fixed_trash=load_fixed_trash,
|
|
@@ -6,7 +6,8 @@ from opentrons.protocol_runner import protocol_runner, RunOrchestrator
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def create_run_orchestrator(
|
|
9
|
-
hardware_api: HardwareControlAPI,
|
|
9
|
+
hardware_api: HardwareControlAPI,
|
|
10
|
+
protocol_engine: ProtocolEngine,
|
|
10
11
|
) -> RunOrchestrator:
|
|
11
12
|
"""Create a RunOrchestrator instance."""
|
|
12
13
|
return RunOrchestrator(
|
|
@@ -26,10 +26,20 @@ class ErrorRecoveryType(enum.Enum):
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
WAIT_FOR_RECOVERY = enum.auto()
|
|
29
|
-
"""
|
|
29
|
+
"""Enter interactive error recovery mode."""
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
"""Continue
|
|
31
|
+
CONTINUE_WITH_ERROR = enum.auto()
|
|
32
|
+
"""Continue without interruption, carrying on from whatever error state the failed
|
|
33
|
+
command left the engine in.
|
|
34
|
+
|
|
35
|
+
This is like `ProtocolEngine.resume_from_recovery(reconcile_false_positive=False)`.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
ASSUME_FALSE_POSITIVE_AND_CONTINUE = enum.auto()
|
|
39
|
+
"""Continue without interruption, acting as if the underlying error was a false positive.
|
|
40
|
+
|
|
41
|
+
This is like `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
|
|
42
|
+
"""
|
|
33
43
|
|
|
34
44
|
|
|
35
45
|
class ErrorRecoveryPolicy(Protocol):
|
|
@@ -40,6 +50,7 @@ class ErrorRecoveryPolicy(Protocol):
|
|
|
40
50
|
and return an appropriate `ErrorRecoveryType`.
|
|
41
51
|
|
|
42
52
|
Args:
|
|
53
|
+
config: The config of the calling `ProtocolEngine`.
|
|
43
54
|
failed_command: The command that failed, in its final `status=="failed"` state.
|
|
44
55
|
defined_error_data: If the command failed with a defined error, details about
|
|
45
56
|
that error. If the command failed with an undefined error, `None`.
|
|
@@ -8,6 +8,7 @@ from .exceptions import (
|
|
|
8
8
|
InvalidSpecificationForRobotTypeError,
|
|
9
9
|
InvalidLoadPipetteSpecsError,
|
|
10
10
|
TipNotAttachedError,
|
|
11
|
+
PickUpTipTipNotAttachedError,
|
|
11
12
|
TipAttachedError,
|
|
12
13
|
CommandDoesNotExistError,
|
|
13
14
|
LabwareNotLoadedError,
|
|
@@ -65,9 +66,17 @@ from .exceptions import (
|
|
|
65
66
|
LocationIsOccupiedError,
|
|
66
67
|
LocationNotAccessibleByPipetteError,
|
|
67
68
|
LocationIsStagingSlotError,
|
|
69
|
+
LocationIsLidDockSlotError,
|
|
68
70
|
InvalidAxisForRobotType,
|
|
69
71
|
NotSupportedOnRobotType,
|
|
70
72
|
CommandNotAllowedError,
|
|
73
|
+
InvalidLiquidHeightFound,
|
|
74
|
+
LiquidHeightUnknownError,
|
|
75
|
+
IncompleteLabwareDefinitionError,
|
|
76
|
+
IncompleteWellDefinitionError,
|
|
77
|
+
OperationLocationNotInWellError,
|
|
78
|
+
InvalidDispenseVolumeError,
|
|
79
|
+
StorageLimitReachedError,
|
|
71
80
|
)
|
|
72
81
|
|
|
73
82
|
from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
|
|
@@ -81,6 +90,7 @@ __all__ = [
|
|
|
81
90
|
"InvalidSpecificationForRobotTypeError",
|
|
82
91
|
"InvalidLoadPipetteSpecsError",
|
|
83
92
|
"TipNotAttachedError",
|
|
93
|
+
"PickUpTipTipNotAttachedError",
|
|
84
94
|
"TipAttachedError",
|
|
85
95
|
"CommandDoesNotExistError",
|
|
86
96
|
"LabwareNotLoadedError",
|
|
@@ -139,9 +149,17 @@ __all__ = [
|
|
|
139
149
|
"LocationIsOccupiedError",
|
|
140
150
|
"LocationNotAccessibleByPipetteError",
|
|
141
151
|
"LocationIsStagingSlotError",
|
|
152
|
+
"LocationIsLidDockSlotError",
|
|
142
153
|
"InvalidAxisForRobotType",
|
|
143
154
|
"NotSupportedOnRobotType",
|
|
144
155
|
# error occurrence models
|
|
145
156
|
"ErrorOccurrence",
|
|
146
157
|
"CommandNotAllowedError",
|
|
158
|
+
"InvalidLiquidHeightFound",
|
|
159
|
+
"LiquidHeightUnknownError",
|
|
160
|
+
"IncompleteLabwareDefinitionError",
|
|
161
|
+
"IncompleteWellDefinitionError",
|
|
162
|
+
"OperationLocationNotInWellError",
|
|
163
|
+
"InvalidDispenseVolumeError",
|
|
164
|
+
"StorageLimitReachedError",
|
|
147
165
|
]
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"""Protocol engine exceptions."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from logging import getLogger
|
|
4
|
-
from typing import Any, Dict, Optional, Union, Iterator, Sequence
|
|
6
|
+
from typing import Any, Dict, Final, Optional, Union, Iterator, Sequence, TYPE_CHECKING
|
|
5
7
|
|
|
6
8
|
from opentrons_shared_data.errors import ErrorCodes
|
|
7
9
|
from opentrons_shared_data.errors.exceptions import EnumeratedError, PythonException
|
|
8
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from opentrons.protocol_engine.types import TipGeometry
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
log = getLogger(__name__)
|
|
10
16
|
|
|
11
17
|
|
|
@@ -132,6 +138,21 @@ class TipNotAttachedError(ProtocolEngineError):
|
|
|
132
138
|
super().__init__(ErrorCodes.UNEXPECTED_TIP_REMOVAL, message, details, wrapping)
|
|
133
139
|
|
|
134
140
|
|
|
141
|
+
class PickUpTipTipNotAttachedError(TipNotAttachedError):
|
|
142
|
+
"""Raised from TipHandler.pick_up_tip().
|
|
143
|
+
|
|
144
|
+
This is like TipNotAttachedError except that it carries some extra information
|
|
145
|
+
about the attempted operation.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
tip_geometry: Final[TipGeometry]
|
|
149
|
+
"""The tip geometry that would have been on the pipette, had the operation succeeded."""
|
|
150
|
+
|
|
151
|
+
def __init__(self, tip_geometry: TipGeometry) -> None:
|
|
152
|
+
super().__init__()
|
|
153
|
+
self.tip_geometry = tip_geometry
|
|
154
|
+
|
|
155
|
+
|
|
135
156
|
class TipAttachedError(ProtocolEngineError):
|
|
136
157
|
"""Raised when a tip shouldn't be attached, but is."""
|
|
137
158
|
|
|
@@ -897,6 +918,19 @@ class LocationIsStagingSlotError(ProtocolEngineError):
|
|
|
897
918
|
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
898
919
|
|
|
899
920
|
|
|
921
|
+
class LocationIsLidDockSlotError(ProtocolEngineError):
|
|
922
|
+
"""Raised when referencing a labware on a lid dock slot when trying to get standard deck slot."""
|
|
923
|
+
|
|
924
|
+
def __init__(
|
|
925
|
+
self,
|
|
926
|
+
message: Optional[str] = None,
|
|
927
|
+
details: Optional[Dict[str, Any]] = None,
|
|
928
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
929
|
+
) -> None:
|
|
930
|
+
"""Build a LocationIsLidDockSlotError."""
|
|
931
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
932
|
+
|
|
933
|
+
|
|
900
934
|
class FirmwareUpdateRequired(ProtocolEngineError):
|
|
901
935
|
"""Raised when the firmware needs to be updated."""
|
|
902
936
|
|
|
@@ -939,7 +973,7 @@ class InvalidAspirateVolumeError(ProtocolEngineError):
|
|
|
939
973
|
"""Build a InvalidPipettingVolumeError."""
|
|
940
974
|
message = (
|
|
941
975
|
f"Cannot aspirate {attempted_aspirate_volume} µL when only"
|
|
942
|
-
f" {available_volume} is available."
|
|
976
|
+
f" {available_volume} is available in the tip."
|
|
943
977
|
)
|
|
944
978
|
details = {
|
|
945
979
|
"attempted_aspirate_volume": attempted_aspirate_volume,
|
|
@@ -989,6 +1023,32 @@ class InvalidAxisForRobotType(ProtocolEngineError):
|
|
|
989
1023
|
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
990
1024
|
|
|
991
1025
|
|
|
1026
|
+
class InvalidLiquidHeightFound(ProtocolEngineError):
|
|
1027
|
+
"""Raised when attempting to estimate liquid height based on volume fails."""
|
|
1028
|
+
|
|
1029
|
+
def __init__(
|
|
1030
|
+
self,
|
|
1031
|
+
message: Optional[str] = None,
|
|
1032
|
+
details: Optional[Dict[str, Any]] = None,
|
|
1033
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1034
|
+
) -> None:
|
|
1035
|
+
"""Build an InvalidLiquidHeightFound error."""
|
|
1036
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
class LiquidHeightUnknownError(ProtocolEngineError):
|
|
1040
|
+
"""Raised when attempting to specify WellOrigin.MENISCUS before liquid probing has been done."""
|
|
1041
|
+
|
|
1042
|
+
def __init__(
|
|
1043
|
+
self,
|
|
1044
|
+
message: Optional[str] = None,
|
|
1045
|
+
details: Optional[Dict[str, Any]] = None,
|
|
1046
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1047
|
+
) -> None:
|
|
1048
|
+
"""Build a LiquidHeightUnknownError."""
|
|
1049
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1050
|
+
|
|
1051
|
+
|
|
992
1052
|
class EStopActivatedError(ProtocolEngineError):
|
|
993
1053
|
"""Represents an E-stop event."""
|
|
994
1054
|
|
|
@@ -1030,3 +1090,55 @@ class TipNotEmptyError(ProtocolEngineError):
|
|
|
1030
1090
|
) -> None:
|
|
1031
1091
|
"""Build a TipNotEmptyError."""
|
|
1032
1092
|
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
class IncompleteLabwareDefinitionError(ProtocolEngineError):
|
|
1096
|
+
"""Raised when a labware definition lacks innerLabwareGeometry in general or for a specific well_id."""
|
|
1097
|
+
|
|
1098
|
+
def __init__(
|
|
1099
|
+
self,
|
|
1100
|
+
message: Optional[str] = None,
|
|
1101
|
+
details: Optional[Dict[str, Any]] = None,
|
|
1102
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1103
|
+
) -> None:
|
|
1104
|
+
"""Build an IncompleteLabwareDefinitionError."""
|
|
1105
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
class IncompleteWellDefinitionError(ProtocolEngineError):
|
|
1109
|
+
"""Raised when a well definition lacks a geometryDefinitionId."""
|
|
1110
|
+
|
|
1111
|
+
def __init__(
|
|
1112
|
+
self,
|
|
1113
|
+
message: Optional[str] = None,
|
|
1114
|
+
details: Optional[Dict[str, Any]] = None,
|
|
1115
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1116
|
+
) -> None:
|
|
1117
|
+
"""Build an IncompleteWellDefinitionError."""
|
|
1118
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
class OperationLocationNotInWellError(ProtocolEngineError):
|
|
1122
|
+
"""Raised when a calculated operation location is not within a well."""
|
|
1123
|
+
|
|
1124
|
+
def __init__(
|
|
1125
|
+
self,
|
|
1126
|
+
message: Optional[str] = None,
|
|
1127
|
+
details: Optional[Dict[str, Any]] = None,
|
|
1128
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1129
|
+
) -> None:
|
|
1130
|
+
"""Build an OperationLocationNotInWellError."""
|
|
1131
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
class StorageLimitReachedError(ProtocolEngineError):
|
|
1135
|
+
"""Raised to indicate that a file cannot be created due to storage limitations."""
|
|
1136
|
+
|
|
1137
|
+
def __init__(
|
|
1138
|
+
self,
|
|
1139
|
+
message: Optional[str] = None,
|
|
1140
|
+
detail: Optional[Dict[str, str]] = None,
|
|
1141
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1142
|
+
) -> None:
|
|
1143
|
+
"""Build an StorageLimitReached."""
|
|
1144
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, detail, wrapping)
|
|
@@ -21,6 +21,7 @@ from .run_control import RunControlHandler
|
|
|
21
21
|
from .hardware_stopper import HardwareStopper
|
|
22
22
|
from .door_watcher import DoorWatcher
|
|
23
23
|
from .status_bar import StatusBarHandler
|
|
24
|
+
from ..resources.file_provider import FileProvider
|
|
24
25
|
|
|
25
26
|
# .thermocycler_movement_flagger omitted from package's public interface.
|
|
26
27
|
|
|
@@ -45,4 +46,5 @@ __all__ = [
|
|
|
45
46
|
"DoorWatcher",
|
|
46
47
|
"RailLightsHandler",
|
|
47
48
|
"StatusBarHandler",
|
|
49
|
+
"FileProvider",
|
|
48
50
|
]
|
|
@@ -12,9 +12,10 @@ from opentrons_shared_data.errors.exceptions import (
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
from opentrons.protocol_engine.commands.command import SuccessData
|
|
15
|
+
from opentrons.protocol_engine.notes import make_error_recovery_debug_note
|
|
15
16
|
|
|
16
|
-
from ..state import StateStore
|
|
17
|
-
from ..resources import ModelUtils
|
|
17
|
+
from ..state.state import StateStore
|
|
18
|
+
from ..resources import ModelUtils, FileProvider
|
|
18
19
|
from ..commands import CommandStatus
|
|
19
20
|
from ..actions import (
|
|
20
21
|
ActionDispatcher,
|
|
@@ -72,6 +73,7 @@ class CommandExecutor:
|
|
|
72
73
|
def __init__(
|
|
73
74
|
self,
|
|
74
75
|
hardware_api: HardwareControlAPI,
|
|
76
|
+
file_provider: FileProvider,
|
|
75
77
|
state_store: StateStore,
|
|
76
78
|
action_dispatcher: ActionDispatcher,
|
|
77
79
|
equipment: EquipmentHandler,
|
|
@@ -88,6 +90,7 @@ class CommandExecutor:
|
|
|
88
90
|
) -> None:
|
|
89
91
|
"""Initialize the CommandExecutor with access to its dependencies."""
|
|
90
92
|
self._hardware_api = hardware_api
|
|
93
|
+
self._file_provider = file_provider
|
|
91
94
|
self._state_store = state_store
|
|
92
95
|
self._action_dispatcher = action_dispatcher
|
|
93
96
|
self._equipment = equipment
|
|
@@ -116,6 +119,7 @@ class CommandExecutor:
|
|
|
116
119
|
command_impl = queued_command._ImplementationCls(
|
|
117
120
|
state_view=self._state_store,
|
|
118
121
|
hardware_api=self._hardware_api,
|
|
122
|
+
file_provider=self._file_provider,
|
|
119
123
|
equipment=self._equipment,
|
|
120
124
|
movement=self._movement,
|
|
121
125
|
gantry_mover=self._gantry_mover,
|
|
@@ -158,6 +162,12 @@ class CommandExecutor:
|
|
|
158
162
|
elif not isinstance(error, EnumeratedError):
|
|
159
163
|
error = PythonException(error)
|
|
160
164
|
|
|
165
|
+
error_recovery_type = error_recovery_policy(
|
|
166
|
+
self._state_store.config,
|
|
167
|
+
running_command,
|
|
168
|
+
None,
|
|
169
|
+
)
|
|
170
|
+
note_tracker(make_error_recovery_debug_note(error_recovery_type))
|
|
161
171
|
self._action_dispatcher.dispatch(
|
|
162
172
|
FailCommandAction(
|
|
163
173
|
error=error,
|
|
@@ -166,11 +176,7 @@ class CommandExecutor:
|
|
|
166
176
|
error_id=self._model_utils.generate_id(),
|
|
167
177
|
failed_at=self._model_utils.get_timestamp(),
|
|
168
178
|
notes=note_tracker.get_notes(),
|
|
169
|
-
type=
|
|
170
|
-
self._state_store.config,
|
|
171
|
-
running_command,
|
|
172
|
-
None,
|
|
173
|
-
),
|
|
179
|
+
type=error_recovery_type,
|
|
174
180
|
)
|
|
175
181
|
)
|
|
176
182
|
|
|
@@ -185,11 +191,18 @@ class CommandExecutor:
|
|
|
185
191
|
succeeded_command = running_command.copy(update=update)
|
|
186
192
|
self._action_dispatcher.dispatch(
|
|
187
193
|
SucceedCommandAction(
|
|
188
|
-
command=succeeded_command,
|
|
194
|
+
command=succeeded_command,
|
|
195
|
+
state_update=result.state_update,
|
|
189
196
|
),
|
|
190
197
|
)
|
|
191
198
|
else:
|
|
192
199
|
# The command encountered a defined error.
|
|
200
|
+
error_recovery_type = error_recovery_policy(
|
|
201
|
+
self._state_store.config,
|
|
202
|
+
running_command,
|
|
203
|
+
result,
|
|
204
|
+
)
|
|
205
|
+
note_tracker(make_error_recovery_debug_note(error_recovery_type))
|
|
193
206
|
self._action_dispatcher.dispatch(
|
|
194
207
|
FailCommandAction(
|
|
195
208
|
error=result,
|
|
@@ -198,10 +211,6 @@ class CommandExecutor:
|
|
|
198
211
|
error_id=result.public.id,
|
|
199
212
|
failed_at=result.public.createdAt,
|
|
200
213
|
notes=note_tracker.get_notes(),
|
|
201
|
-
type=
|
|
202
|
-
self._state_store.config,
|
|
203
|
-
running_command,
|
|
204
|
-
result,
|
|
205
|
-
),
|
|
214
|
+
type=error_recovery_type,
|
|
206
215
|
)
|
|
207
216
|
)
|
|
@@ -4,8 +4,9 @@ from typing import AsyncGenerator, Callable
|
|
|
4
4
|
from opentrons.hardware_control import HardwareControlAPI
|
|
5
5
|
from opentrons.protocol_engine.execution.rail_lights import RailLightsHandler
|
|
6
6
|
|
|
7
|
-
from ..state import StateStore
|
|
7
|
+
from ..state.state import StateStore
|
|
8
8
|
from ..actions import ActionDispatcher
|
|
9
|
+
from ..resources import FileProvider
|
|
9
10
|
from .equipment import EquipmentHandler
|
|
10
11
|
from .movement import MovementHandler
|
|
11
12
|
from .gantry_mover import create_gantry_mover
|
|
@@ -20,6 +21,7 @@ from .status_bar import StatusBarHandler
|
|
|
20
21
|
|
|
21
22
|
def create_queue_worker(
|
|
22
23
|
hardware_api: HardwareControlAPI,
|
|
24
|
+
file_provider: FileProvider,
|
|
23
25
|
state_store: StateStore,
|
|
24
26
|
action_dispatcher: ActionDispatcher,
|
|
25
27
|
command_generator: Callable[[], AsyncGenerator[str, None]],
|
|
@@ -28,6 +30,7 @@ def create_queue_worker(
|
|
|
28
30
|
|
|
29
31
|
Arguments:
|
|
30
32
|
hardware_api: Hardware control API to pass down to dependencies.
|
|
33
|
+
file_provider: Provides access to robot server file writing procedures for protocol output.
|
|
31
34
|
state_store: StateStore to pass down to dependencies.
|
|
32
35
|
action_dispatcher: ActionDispatcher to pass down to dependencies.
|
|
33
36
|
error_recovery_policy: ErrorRecoveryPolicy to pass down to dependencies.
|
|
@@ -78,6 +81,7 @@ def create_queue_worker(
|
|
|
78
81
|
|
|
79
82
|
command_executor = CommandExecutor(
|
|
80
83
|
hardware_api=hardware_api,
|
|
84
|
+
file_provider=file_provider,
|
|
81
85
|
state_store=state_store,
|
|
82
86
|
action_dispatcher=action_dispatcher,
|
|
83
87
|
equipment=equipment_handler,
|
|
@@ -15,7 +15,7 @@ from opentrons.hardware_control.types import (
|
|
|
15
15
|
|
|
16
16
|
from opentrons.protocol_engine.actions import ActionDispatcher, DoorChangeAction
|
|
17
17
|
|
|
18
|
-
from ..state import StateStore
|
|
18
|
+
from ..state.state import StateStore
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
_UnsubscribeCallback = Callable[[], None]
|
|
@@ -35,7 +35,8 @@ from ..resources import (
|
|
|
35
35
|
ModelUtils,
|
|
36
36
|
pipette_data_provider,
|
|
37
37
|
)
|
|
38
|
-
from ..state import StateStore
|
|
38
|
+
from ..state.state import StateStore
|
|
39
|
+
from ..state.modules import HardwareModule
|
|
39
40
|
from ..types import (
|
|
40
41
|
LabwareLocation,
|
|
41
42
|
DeckSlotLocation,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from opentrons.hardware_control import HardwareControlAPI
|
|
5
|
+
from opentrons.protocol_engine.actions.action_handler import ActionHandler
|
|
6
|
+
from opentrons.protocol_engine.actions.actions import (
|
|
7
|
+
Action,
|
|
8
|
+
FailCommandAction,
|
|
9
|
+
ResumeFromRecoveryAction,
|
|
10
|
+
)
|
|
11
|
+
from opentrons.protocol_engine.commands.command import DefinedErrorData
|
|
12
|
+
from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType
|
|
13
|
+
from opentrons.protocol_engine.execution.tip_handler import HardwareTipHandler
|
|
14
|
+
from opentrons.protocol_engine.state import update_types
|
|
15
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ErrorRecoveryHardwareStateSynchronizer(ActionHandler):
|
|
19
|
+
"""A hack to keep the hardware API's state correct through certain error recovery flows.
|
|
20
|
+
|
|
21
|
+
BACKGROUND:
|
|
22
|
+
|
|
23
|
+
Certain parts of robot state are duplicated between `opentrons.protocol_engine` and
|
|
24
|
+
`opentrons.hardware_control`. Stuff like "is there a tip attached."
|
|
25
|
+
|
|
26
|
+
Normally, Protocol Engine command implementations (`opentrons.protocol_engine.commands`)
|
|
27
|
+
mutate hardware API state when they execute; and then when they finish executing,
|
|
28
|
+
the Protocol Engine state stores (`opentrons.protocol_engine.state`) update Protocol
|
|
29
|
+
Engine state accordingly. So both halves are accounted for. This generally works fine.
|
|
30
|
+
|
|
31
|
+
However, we need to go out of our way to support
|
|
32
|
+
`ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
|
|
33
|
+
It wants to apply a second set of state updates to "fix things up" with the
|
|
34
|
+
new knowledge that some error was a false positive. The Protocol Engine half of that
|
|
35
|
+
is easy for us to apply the normal way, through the state stores; but the
|
|
36
|
+
hardware API half of that cannot be applied the normal way, from the command
|
|
37
|
+
implementation, because the command in question is no longer running.
|
|
38
|
+
|
|
39
|
+
THE HACK:
|
|
40
|
+
|
|
41
|
+
This listens for the same error recovery state updates that the state stores do,
|
|
42
|
+
figures out what hardware API state mutations ought to go along with them,
|
|
43
|
+
and then does those mutations.
|
|
44
|
+
|
|
45
|
+
The problem is that hardware API state is now mutated from two different places
|
|
46
|
+
(sometimes the command implementations, and sometimes here), which are bound
|
|
47
|
+
to grow accidental differences.
|
|
48
|
+
|
|
49
|
+
TO FIX:
|
|
50
|
+
|
|
51
|
+
Make Protocol Engine's use of the hardware API less stateful. e.g. supply
|
|
52
|
+
tip geometry every time we call a hardware API movement method, instead of
|
|
53
|
+
just once when we pick up a tip. Use Protocol Engine state as the single source
|
|
54
|
+
of truth.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, hardware_api: HardwareControlAPI, state_view: StateView) -> None:
|
|
58
|
+
self._hardware_api = hardware_api
|
|
59
|
+
self._state_view = state_view
|
|
60
|
+
|
|
61
|
+
def handle_action(self, action: Action) -> None:
|
|
62
|
+
"""Modify hardware API state in reaction to a Protocol Engine action."""
|
|
63
|
+
state_update = _get_state_update(action)
|
|
64
|
+
if state_update:
|
|
65
|
+
self._synchronize(state_update)
|
|
66
|
+
|
|
67
|
+
def _synchronize(self, state_update: update_types.StateUpdate) -> None:
|
|
68
|
+
tip_handler = HardwareTipHandler(self._state_view, self._hardware_api)
|
|
69
|
+
|
|
70
|
+
if state_update.pipette_tip_state != update_types.NO_CHANGE:
|
|
71
|
+
pipette_id = state_update.pipette_tip_state.pipette_id
|
|
72
|
+
tip_geometry = state_update.pipette_tip_state.tip_geometry
|
|
73
|
+
if tip_geometry is None:
|
|
74
|
+
tip_handler.remove_tip(pipette_id)
|
|
75
|
+
else:
|
|
76
|
+
tip_handler.cache_tip(pipette_id=pipette_id, tip=tip_geometry)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_state_update(action: Action) -> update_types.StateUpdate | None:
|
|
80
|
+
"""Get the mutations that we need to do on the hardware API to stay in sync with an engine action.
|
|
81
|
+
|
|
82
|
+
The mutations are returned in Protocol Engine terms, as a StateUpdate.
|
|
83
|
+
They then need to be converted to hardware API terms.
|
|
84
|
+
"""
|
|
85
|
+
match action:
|
|
86
|
+
case ResumeFromRecoveryAction(state_update=state_update):
|
|
87
|
+
return state_update
|
|
88
|
+
|
|
89
|
+
case FailCommandAction(
|
|
90
|
+
error=DefinedErrorData(
|
|
91
|
+
state_update_if_false_positive=state_update_if_false_positive
|
|
92
|
+
)
|
|
93
|
+
):
|
|
94
|
+
return (
|
|
95
|
+
state_update_if_false_positive
|
|
96
|
+
if action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
|
|
97
|
+
else None
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
case _:
|
|
101
|
+
return None
|
|
@@ -10,7 +10,7 @@ from opentrons_shared_data.errors.exceptions import PositionUnknownError
|
|
|
10
10
|
|
|
11
11
|
from opentrons.motion_planning import Waypoint
|
|
12
12
|
|
|
13
|
-
from ..state import StateView
|
|
13
|
+
from ..state.state import StateView
|
|
14
14
|
from ..types import MotorAxis, CurrentWell
|
|
15
15
|
from ..errors import MustHomeError, InvalidAxisForRobotType
|
|
16
16
|
|
|
@@ -273,7 +273,9 @@ class VirtualGantryMover(GantryMover):
|
|
|
273
273
|
)
|
|
274
274
|
else:
|
|
275
275
|
instrument_height = VIRTUAL_MAX_OT3_HEIGHT
|
|
276
|
-
|
|
276
|
+
|
|
277
|
+
tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette_id)
|
|
278
|
+
tip_length = tip.length if tip is not None else 0
|
|
277
279
|
return instrument_height - tip_length
|
|
278
280
|
|
|
279
281
|
async def move_to(
|
|
@@ -6,7 +6,7 @@ from opentrons.hardware_control import HardwareControlAPI
|
|
|
6
6
|
from opentrons.types import PipetteNotAttachedError as HwPipetteNotAttachedError
|
|
7
7
|
|
|
8
8
|
from ..resources.ot3_validation import ensure_ot3_hardware
|
|
9
|
-
from ..state import StateStore
|
|
9
|
+
from ..state.state import StateStore
|
|
10
10
|
from ..types import MotorAxis, PostRunHardwareState
|
|
11
11
|
from ..errors import HardwareNotSupportedError
|
|
12
12
|
|
|
@@ -78,7 +78,7 @@ class HardwareStopper:
|
|
|
78
78
|
try:
|
|
79
79
|
if self._state_store.labware.get_fixed_trash_id() == FIXED_TRASH_ID:
|
|
80
80
|
# OT-2 and Flex 2.15 protocols will default to the Fixed Trash Labware
|
|
81
|
-
|
|
81
|
+
self._tip_handler.cache_tip(pipette_id=pipette_id, tip=tip)
|
|
82
82
|
await self._movement_handler.move_to_well(
|
|
83
83
|
pipette_id=pipette_id,
|
|
84
84
|
labware_id=FIXED_TRASH_ID,
|
|
@@ -90,7 +90,7 @@ class HardwareStopper:
|
|
|
90
90
|
)
|
|
91
91
|
elif self._state_store.config.robot_type == "OT-2 Standard":
|
|
92
92
|
# API 2.16 and above OT2 protocols use addressable areas
|
|
93
|
-
|
|
93
|
+
self._tip_handler.cache_tip(pipette_id=pipette_id, tip=tip)
|
|
94
94
|
await self._movement_handler.move_to_addressable_area(
|
|
95
95
|
pipette_id=pipette_id,
|
|
96
96
|
addressable_area_name="fixedTrash",
|