opentrons 8.1.0__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.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0.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.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -17,11 +17,15 @@ from opentrons.protocol_engine.actions.actions import (
|
|
|
17
17
|
RunCommandAction,
|
|
18
18
|
SetErrorRecoveryPolicyAction,
|
|
19
19
|
)
|
|
20
|
+
from opentrons.protocol_engine.commands.unsafe.unsafe_ungrip_labware import (
|
|
21
|
+
UnsafeUngripLabwareCommandType,
|
|
22
|
+
)
|
|
20
23
|
from opentrons.protocol_engine.error_recovery_policy import (
|
|
21
24
|
ErrorRecoveryPolicy,
|
|
22
25
|
ErrorRecoveryType,
|
|
23
26
|
)
|
|
24
27
|
from opentrons.protocol_engine.notes.notes import CommandNote
|
|
28
|
+
from opentrons.protocol_engine.state import update_types
|
|
25
29
|
|
|
26
30
|
from ..actions import (
|
|
27
31
|
Action,
|
|
@@ -36,7 +40,7 @@ from ..actions import (
|
|
|
36
40
|
DoorChangeAction,
|
|
37
41
|
)
|
|
38
42
|
|
|
39
|
-
from ..commands import Command, CommandStatus, CommandIntent
|
|
43
|
+
from ..commands import Command, CommandStatus, CommandIntent, CommandCreate
|
|
40
44
|
from ..errors import (
|
|
41
45
|
RunStoppedError,
|
|
42
46
|
ErrorOccurrence,
|
|
@@ -49,7 +53,7 @@ from ..errors import (
|
|
|
49
53
|
ProtocolCommandFailedError,
|
|
50
54
|
)
|
|
51
55
|
from ..types import EngineStatus
|
|
52
|
-
from .
|
|
56
|
+
from ._abstract_store import HasState, HandlesActions
|
|
53
57
|
from .command_history import (
|
|
54
58
|
CommandEntry,
|
|
55
59
|
CommandHistory,
|
|
@@ -95,7 +99,9 @@ class QueueStatus(enum.Enum):
|
|
|
95
99
|
AWAITING_RECOVERY_PAUSED = enum.auto()
|
|
96
100
|
"""Execution of fixit commands has been paused.
|
|
97
101
|
|
|
98
|
-
New protocol and fixit commands may be enqueued, but will wait to execute.
|
|
102
|
+
New protocol and fixit commands may be enqueued, but will usually wait to execute.
|
|
103
|
+
There are certain exceptions where fixit commands will still run.
|
|
104
|
+
|
|
99
105
|
New setup commands may not be enqueued.
|
|
100
106
|
"""
|
|
101
107
|
|
|
@@ -136,6 +142,16 @@ class CommandPointer:
|
|
|
136
142
|
index: int
|
|
137
143
|
|
|
138
144
|
|
|
145
|
+
@dataclass(frozen=True)
|
|
146
|
+
class _RecoveryTargetInfo:
|
|
147
|
+
"""Info about the failed command that we're currently recovering from."""
|
|
148
|
+
|
|
149
|
+
command_id: str
|
|
150
|
+
|
|
151
|
+
state_update_if_false_positive: update_types.StateUpdate
|
|
152
|
+
"""See `CommandView.get_state_update_if_continued()`."""
|
|
153
|
+
|
|
154
|
+
|
|
139
155
|
@dataclass
|
|
140
156
|
class CommandState:
|
|
141
157
|
"""State of all protocol engine command resources."""
|
|
@@ -200,8 +216,8 @@ class CommandState:
|
|
|
200
216
|
stable. Eventually, we might want this info to be stored directly on each command.
|
|
201
217
|
"""
|
|
202
218
|
|
|
203
|
-
|
|
204
|
-
"""If we're currently recovering from a command failure,
|
|
219
|
+
recovery_target: Optional[_RecoveryTargetInfo]
|
|
220
|
+
"""If we're currently recovering from a command failure, info about that command."""
|
|
205
221
|
|
|
206
222
|
finish_error: Optional[ErrorOccurrence]
|
|
207
223
|
"""The error that happened during the post-run finish steps (homing & dropping tips), if any."""
|
|
@@ -248,7 +264,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
248
264
|
finish_error=None,
|
|
249
265
|
failed_command=None,
|
|
250
266
|
command_error_recovery_types={},
|
|
251
|
-
|
|
267
|
+
recovery_target=None,
|
|
252
268
|
run_completed_at=None,
|
|
253
269
|
run_started_at=None,
|
|
254
270
|
latest_protocol_command_hash=None,
|
|
@@ -330,14 +346,17 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
330
346
|
def _handle_fail_command_action(self, action: FailCommandAction) -> None:
|
|
331
347
|
prev_entry = self.state.command_history.get(action.command_id)
|
|
332
348
|
|
|
333
|
-
if isinstance(action.error, EnumeratedError):
|
|
349
|
+
if isinstance(action.error, EnumeratedError): # The error was undefined.
|
|
334
350
|
public_error_occurrence = ErrorOccurrence.from_failed(
|
|
335
351
|
id=action.error_id,
|
|
336
352
|
createdAt=action.failed_at,
|
|
337
353
|
error=action.error,
|
|
338
354
|
)
|
|
339
|
-
|
|
355
|
+
# An empty state update, to no-op.
|
|
356
|
+
state_update_if_false_positive = update_types.StateUpdate()
|
|
357
|
+
else: # The error was defined.
|
|
340
358
|
public_error_occurrence = action.error.public
|
|
359
|
+
state_update_if_false_positive = action.error.state_update_if_false_positive
|
|
341
360
|
|
|
342
361
|
self._update_to_failed(
|
|
343
362
|
command_id=action.command_id,
|
|
@@ -349,6 +368,19 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
349
368
|
self._state.failed_command = self._state.command_history.get(action.command_id)
|
|
350
369
|
self._state.failed_command_errors.append(public_error_occurrence)
|
|
351
370
|
|
|
371
|
+
if (
|
|
372
|
+
prev_entry.command.intent in (CommandIntent.PROTOCOL, None)
|
|
373
|
+
and action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY
|
|
374
|
+
):
|
|
375
|
+
self._state.queue_status = QueueStatus.AWAITING_RECOVERY
|
|
376
|
+
self._state.recovery_target = _RecoveryTargetInfo(
|
|
377
|
+
command_id=action.command_id,
|
|
378
|
+
state_update_if_false_positive=state_update_if_false_positive,
|
|
379
|
+
)
|
|
380
|
+
self._state.has_entered_error_recovery = True
|
|
381
|
+
|
|
382
|
+
# When one command fails, we generally also cancel the commands that
|
|
383
|
+
# would have been queued after it.
|
|
352
384
|
other_command_ids_to_fail: List[str]
|
|
353
385
|
if prev_entry.command.intent == CommandIntent.SETUP:
|
|
354
386
|
other_command_ids_to_fail = list(
|
|
@@ -368,7 +400,8 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
368
400
|
)
|
|
369
401
|
elif (
|
|
370
402
|
action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY
|
|
371
|
-
or action.type == ErrorRecoveryType.
|
|
403
|
+
or action.type == ErrorRecoveryType.CONTINUE_WITH_ERROR
|
|
404
|
+
or action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
|
|
372
405
|
):
|
|
373
406
|
other_command_ids_to_fail = []
|
|
374
407
|
else:
|
|
@@ -385,14 +418,6 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
385
418
|
notes=None,
|
|
386
419
|
)
|
|
387
420
|
|
|
388
|
-
if (
|
|
389
|
-
prev_entry.command.intent in (CommandIntent.PROTOCOL, None)
|
|
390
|
-
and action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY
|
|
391
|
-
):
|
|
392
|
-
self._state.queue_status = QueueStatus.AWAITING_RECOVERY
|
|
393
|
-
self._state.recovery_target_command_id = action.command_id
|
|
394
|
-
self._state.has_entered_error_recovery = True
|
|
395
|
-
|
|
396
421
|
def _handle_play_action(self, action: PlayAction) -> None:
|
|
397
422
|
if not self._state.run_result:
|
|
398
423
|
self._state.run_started_at = (
|
|
@@ -420,13 +445,13 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
420
445
|
self, action: ResumeFromRecoveryAction
|
|
421
446
|
) -> None:
|
|
422
447
|
self._state.queue_status = QueueStatus.RUNNING
|
|
423
|
-
self._state.
|
|
448
|
+
self._state.recovery_target = None
|
|
424
449
|
|
|
425
450
|
def _handle_stop_action(self, action: StopAction) -> None:
|
|
426
451
|
if not self._state.run_result:
|
|
427
|
-
self._state.
|
|
428
|
-
|
|
452
|
+
self._state.recovery_target = None
|
|
429
453
|
self._state.queue_status = QueueStatus.PAUSED
|
|
454
|
+
|
|
430
455
|
if action.from_estop:
|
|
431
456
|
self._state.stopped_by_estop = True
|
|
432
457
|
self._state.run_result = RunResult.FAILED
|
|
@@ -435,7 +460,9 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
435
460
|
|
|
436
461
|
def _handle_finish_action(self, action: FinishAction) -> None:
|
|
437
462
|
if not self._state.run_result:
|
|
463
|
+
self._state.recovery_target = None
|
|
438
464
|
self._state.queue_status = QueueStatus.PAUSED
|
|
465
|
+
|
|
439
466
|
if action.set_run_status:
|
|
440
467
|
self._state.run_result = (
|
|
441
468
|
RunResult.SUCCEEDED
|
|
@@ -580,18 +607,19 @@ class CommandView(HasState[CommandState]):
|
|
|
580
607
|
return self._state.command_history.get_all_commands()
|
|
581
608
|
|
|
582
609
|
def get_slice(
|
|
583
|
-
self,
|
|
584
|
-
cursor: Optional[int],
|
|
585
|
-
length: int,
|
|
610
|
+
self, cursor: Optional[int], length: int, include_fixit_commands: bool
|
|
586
611
|
) -> CommandSlice:
|
|
587
612
|
"""Get a subset of commands around a given cursor.
|
|
588
613
|
|
|
589
614
|
If the cursor is omitted, a cursor will be selected automatically
|
|
590
615
|
based on the currently running or most recently executed command.
|
|
591
616
|
"""
|
|
617
|
+
command_ids = self._state.command_history.get_filtered_command_ids(
|
|
618
|
+
include_fixit_commands=include_fixit_commands
|
|
619
|
+
)
|
|
592
620
|
running_command = self._state.command_history.get_running_command()
|
|
593
621
|
queued_command_ids = self._state.command_history.get_queue_ids()
|
|
594
|
-
total_length =
|
|
622
|
+
total_length = len(command_ids)
|
|
595
623
|
|
|
596
624
|
# TODO(mm, 2024-05-17): This looks like it's attempting to do the same thing
|
|
597
625
|
# as self.get_current(), but in a different way. Can we unify them?
|
|
@@ -620,7 +648,9 @@ class CommandView(HasState[CommandState]):
|
|
|
620
648
|
# start is inclusive, stop is exclusive
|
|
621
649
|
actual_cursor = max(0, min(cursor, total_length - 1))
|
|
622
650
|
stop = min(total_length, actual_cursor + length)
|
|
623
|
-
commands = self._state.command_history.get_slice(
|
|
651
|
+
commands = self._state.command_history.get_slice(
|
|
652
|
+
start=actual_cursor, stop=stop, command_ids=command_ids
|
|
653
|
+
)
|
|
624
654
|
|
|
625
655
|
return CommandSlice(
|
|
626
656
|
commands=commands,
|
|
@@ -737,6 +767,12 @@ class CommandView(HasState[CommandState]):
|
|
|
737
767
|
next_fixit_cmd = self._state.command_history.get_fixit_queue_ids().head(None)
|
|
738
768
|
if next_fixit_cmd and self._state.queue_status == QueueStatus.AWAITING_RECOVERY:
|
|
739
769
|
return next_fixit_cmd
|
|
770
|
+
if (
|
|
771
|
+
next_fixit_cmd
|
|
772
|
+
and self._state.queue_status == QueueStatus.AWAITING_RECOVERY_PAUSED
|
|
773
|
+
and self._may_run_with_door_open(fixit_command=self.get(next_fixit_cmd))
|
|
774
|
+
):
|
|
775
|
+
return next_fixit_cmd
|
|
740
776
|
|
|
741
777
|
# if there is a setup command queued, prioritize it
|
|
742
778
|
next_setup_cmd = self._state.command_history.get_setup_queue_ids().head(None)
|
|
@@ -852,11 +888,11 @@ class CommandView(HasState[CommandState]):
|
|
|
852
888
|
|
|
853
889
|
def get_recovery_target(self) -> Optional[CommandPointer]:
|
|
854
890
|
"""Return the command currently undergoing error recovery, if any."""
|
|
855
|
-
|
|
856
|
-
if
|
|
891
|
+
recovery_target = self._state.recovery_target
|
|
892
|
+
if recovery_target is None:
|
|
857
893
|
return None
|
|
858
894
|
else:
|
|
859
|
-
entry = self._state.command_history.get(
|
|
895
|
+
entry = self._state.command_history.get(recovery_target.command_id)
|
|
860
896
|
return CommandPointer(
|
|
861
897
|
command_id=entry.command.id,
|
|
862
898
|
command_key=entry.command.key,
|
|
@@ -967,12 +1003,23 @@ class CommandView(HasState[CommandState]):
|
|
|
967
1003
|
"Setup commands are not allowed after run has started."
|
|
968
1004
|
)
|
|
969
1005
|
elif action.request.intent == CommandIntent.FIXIT:
|
|
970
|
-
if self.
|
|
1006
|
+
if self.get_status() == EngineStatus.AWAITING_RECOVERY:
|
|
1007
|
+
return action
|
|
1008
|
+
elif self.get_status() in (
|
|
1009
|
+
EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR,
|
|
1010
|
+
EngineStatus.AWAITING_RECOVERY_PAUSED,
|
|
1011
|
+
):
|
|
1012
|
+
if self._may_run_with_door_open(fixit_command=action.request):
|
|
1013
|
+
return action
|
|
1014
|
+
else:
|
|
1015
|
+
raise FixitCommandNotAllowedError(
|
|
1016
|
+
f"{action.request.commandType} fixit command may not run"
|
|
1017
|
+
" until the door is closed and the run is played again."
|
|
1018
|
+
)
|
|
1019
|
+
else:
|
|
971
1020
|
raise FixitCommandNotAllowedError(
|
|
972
1021
|
"Fixit commands are not allowed when the run is not in a recoverable state."
|
|
973
1022
|
)
|
|
974
|
-
else:
|
|
975
|
-
return action
|
|
976
1023
|
else:
|
|
977
1024
|
return action
|
|
978
1025
|
|
|
@@ -1057,3 +1104,35 @@ class CommandView(HasState[CommandState]):
|
|
|
1057
1104
|
higher-level code.
|
|
1058
1105
|
"""
|
|
1059
1106
|
return self._state.error_recovery_policy
|
|
1107
|
+
|
|
1108
|
+
def get_state_update_for_false_positive(self) -> update_types.StateUpdate:
|
|
1109
|
+
"""Return the state update for if the current recovery target was a false positive.
|
|
1110
|
+
|
|
1111
|
+
If we're currently in error recovery mode, and you have decided that the
|
|
1112
|
+
underlying command error was a false positive, this returns a state update
|
|
1113
|
+
that will undo the error's effects on engine state.
|
|
1114
|
+
See `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
|
|
1115
|
+
"""
|
|
1116
|
+
if self._state.recovery_target is None:
|
|
1117
|
+
return update_types.StateUpdate() # Empty/no-op.
|
|
1118
|
+
else:
|
|
1119
|
+
return self._state.recovery_target.state_update_if_false_positive
|
|
1120
|
+
|
|
1121
|
+
def _may_run_with_door_open(
|
|
1122
|
+
self, *, fixit_command: Command | CommandCreate
|
|
1123
|
+
) -> bool:
|
|
1124
|
+
"""Return whether the given fixit command is exempt from the usual open-door auto pause.
|
|
1125
|
+
|
|
1126
|
+
This is required for certain error recovery flows, where we want the robot to
|
|
1127
|
+
do stuff while the door is open.
|
|
1128
|
+
"""
|
|
1129
|
+
# CommandIntent.PROTOCOL and CommandIntent.SETUP have their own rules for whether
|
|
1130
|
+
# they run while the door is open. Passing one of those commands to this function
|
|
1131
|
+
# is probably a mistake in the caller's logic.
|
|
1132
|
+
assert fixit_command.intent == CommandIntent.FIXIT
|
|
1133
|
+
|
|
1134
|
+
# This type annotation is to make sure the string constant stays in sync and isn't typo'd.
|
|
1135
|
+
required_command_type: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware"
|
|
1136
|
+
# todo(mm, 2024-10-04): Instead of allowlisting command types, maybe we should
|
|
1137
|
+
# add a `mayRunWithDoorOpen: bool` field to command requests.
|
|
1138
|
+
return fixit_command.commandType == required_command_type
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Basic protocol engine create file data state and store."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from ._abstract_store import HasState, HandlesActions
|
|
6
|
+
from ..actions import Action, SucceedCommandAction
|
|
7
|
+
from ..commands import (
|
|
8
|
+
Command,
|
|
9
|
+
absorbance_reader,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class FileState:
|
|
15
|
+
"""State of Engine created files."""
|
|
16
|
+
|
|
17
|
+
file_ids: List[str]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FileStore(HasState[FileState], HandlesActions):
|
|
21
|
+
"""File state container."""
|
|
22
|
+
|
|
23
|
+
_state: FileState
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
"""Initialize a File store and its state."""
|
|
27
|
+
self._state = FileState(file_ids=[])
|
|
28
|
+
|
|
29
|
+
def handle_action(self, action: Action) -> None:
|
|
30
|
+
"""Modify state in reaction to an action."""
|
|
31
|
+
if isinstance(action, SucceedCommandAction):
|
|
32
|
+
self._handle_command(action.command)
|
|
33
|
+
|
|
34
|
+
def _handle_command(self, command: Command) -> None:
|
|
35
|
+
if isinstance(command.result, absorbance_reader.ReadAbsorbanceResult):
|
|
36
|
+
if command.result.fileIds is not None:
|
|
37
|
+
self._state.file_ids.extend(command.result.fileIds)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FileView(HasState[FileState]):
|
|
41
|
+
"""Read-only engine created file state view."""
|
|
42
|
+
|
|
43
|
+
_state: FileState
|
|
44
|
+
|
|
45
|
+
def __init__(self, state: FileState) -> None:
|
|
46
|
+
"""Initialize the view of file state.
|
|
47
|
+
|
|
48
|
+
Arguments:
|
|
49
|
+
state: File state dataclass used for tracking file creation status.
|
|
50
|
+
"""
|
|
51
|
+
self._state = state
|
|
52
|
+
|
|
53
|
+
def get_filecount(self) -> int:
|
|
54
|
+
"""Get the number of files currently created by the protocol."""
|
|
55
|
+
return len(self._state.file_ids)
|
|
56
|
+
|
|
57
|
+
def get_file_id_list(self) -> List[str]:
|
|
58
|
+
"""Get the list of files by file ID created by the protocol."""
|
|
59
|
+
return self._state.file_ids
|