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
|
@@ -201,6 +201,22 @@ NonStackedLocation = Union[
|
|
|
201
201
|
class WellOrigin(str, Enum):
|
|
202
202
|
"""Origin of WellLocation offset.
|
|
203
203
|
|
|
204
|
+
Props:
|
|
205
|
+
TOP: the top-center of the well
|
|
206
|
+
BOTTOM: the bottom-center of the well
|
|
207
|
+
CENTER: the middle-center of the well
|
|
208
|
+
MENISCUS: the meniscus-center of the well
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
TOP = "top"
|
|
212
|
+
BOTTOM = "bottom"
|
|
213
|
+
CENTER = "center"
|
|
214
|
+
MENISCUS = "meniscus"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class PickUpTipWellOrigin(str, Enum):
|
|
218
|
+
"""The origin of a PickUpTipWellLocation offset.
|
|
219
|
+
|
|
204
220
|
Props:
|
|
205
221
|
TOP: the top-center of the well
|
|
206
222
|
BOTTOM: the bottom-center of the well
|
|
@@ -243,6 +259,34 @@ class WellLocation(BaseModel):
|
|
|
243
259
|
|
|
244
260
|
origin: WellOrigin = WellOrigin.TOP
|
|
245
261
|
offset: WellOffset = Field(default_factory=WellOffset)
|
|
262
|
+
volumeOffset: float = Field(
|
|
263
|
+
default=0.0,
|
|
264
|
+
description="""A volume of liquid, in µL, to offset the z-axis offset.""",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class LiquidHandlingWellLocation(BaseModel):
|
|
269
|
+
"""A relative location in reference to a well's location.
|
|
270
|
+
|
|
271
|
+
To be used with commands that handle liquids.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
origin: WellOrigin = WellOrigin.TOP
|
|
275
|
+
offset: WellOffset = Field(default_factory=WellOffset)
|
|
276
|
+
volumeOffset: Union[float, Literal["operationVolume"]] = Field(
|
|
277
|
+
default=0.0,
|
|
278
|
+
description="""A volume of liquid, in µL, to offset the z-axis offset. When "operationVolume" is specified, this volume is pulled from the command volume parameter.""",
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class PickUpTipWellLocation(BaseModel):
|
|
283
|
+
"""A relative location in reference to a well's location.
|
|
284
|
+
|
|
285
|
+
To be used for picking up tips.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
origin: PickUpTipWellOrigin = PickUpTipWellOrigin.TOP
|
|
289
|
+
offset: WellOffset = Field(default_factory=WellOffset)
|
|
246
290
|
|
|
247
291
|
|
|
248
292
|
class DropTipWellLocation(BaseModel):
|
|
@@ -311,6 +355,48 @@ class CurrentWell:
|
|
|
311
355
|
well_name: str
|
|
312
356
|
|
|
313
357
|
|
|
358
|
+
class LoadedVolumeInfo(BaseModel):
|
|
359
|
+
"""A well's liquid volume, initialized by a LoadLiquid, updated by Aspirate and Dispense."""
|
|
360
|
+
|
|
361
|
+
volume: Optional[float] = None
|
|
362
|
+
last_loaded: datetime
|
|
363
|
+
operations_since_load: int
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class ProbedHeightInfo(BaseModel):
|
|
367
|
+
"""A well's liquid height, initialized by a LiquidProbe, cleared by Aspirate and Dispense."""
|
|
368
|
+
|
|
369
|
+
height: Optional[float] = None
|
|
370
|
+
last_probed: datetime
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class ProbedVolumeInfo(BaseModel):
|
|
374
|
+
"""A well's liquid volume, initialized by a LiquidProbe, updated by Aspirate and Dispense."""
|
|
375
|
+
|
|
376
|
+
volume: Optional[float] = None
|
|
377
|
+
last_probed: datetime
|
|
378
|
+
operations_since_probe: int
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class WellInfoSummary(BaseModel):
|
|
382
|
+
"""Payload for a well's liquid info in StateSummary."""
|
|
383
|
+
|
|
384
|
+
labware_id: str
|
|
385
|
+
well_name: str
|
|
386
|
+
loaded_volume: Optional[float] = None
|
|
387
|
+
probed_height: Optional[float] = None
|
|
388
|
+
probed_volume: Optional[float] = None
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@dataclass
|
|
392
|
+
class WellLiquidInfo:
|
|
393
|
+
"""Tracked and sensed information about liquid in a well."""
|
|
394
|
+
|
|
395
|
+
probed_height: Optional[ProbedHeightInfo]
|
|
396
|
+
loaded_volume: Optional[LoadedVolumeInfo]
|
|
397
|
+
probed_volume: Optional[ProbedVolumeInfo]
|
|
398
|
+
|
|
399
|
+
|
|
314
400
|
@dataclass(frozen=True)
|
|
315
401
|
class CurrentAddressableArea:
|
|
316
402
|
"""The latest addressable area the robot has accessed."""
|
|
@@ -797,6 +883,7 @@ class AreaType(Enum):
|
|
|
797
883
|
TEMPERATURE = "temperatureModule"
|
|
798
884
|
MAGNETICBLOCK = "magneticBlock"
|
|
799
885
|
ABSORBANCE_READER = "absorbanceReader"
|
|
886
|
+
LID_DOCK = "lidDock"
|
|
800
887
|
|
|
801
888
|
|
|
802
889
|
@dataclass(frozen=True)
|
|
@@ -1071,3 +1158,6 @@ PrimitiveRunTimeParamValuesType = Mapping[
|
|
|
1071
1158
|
|
|
1072
1159
|
CSVRunTimeParamFilesType = Mapping[StrictStr, StrictStr]
|
|
1073
1160
|
CSVRuntimeParamPaths = Dict[str, Path]
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
ABSMeasureMode = Literal["single", "multi"]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""File format validation interface."""
|
|
2
|
-
|
|
2
|
+
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from typing import Iterable
|
|
5
5
|
|
|
@@ -29,6 +29,16 @@ from .protocol_files_invalid_error import ProtocolFilesInvalidError
|
|
|
29
29
|
class FileFormatValidationError(ProtocolFilesInvalidError):
|
|
30
30
|
"""Raised when a file does not conform to the format it's supposed to."""
|
|
31
31
|
|
|
32
|
+
@classmethod
|
|
33
|
+
def _generic_json_failure(
|
|
34
|
+
cls, info: IdentifiedJsonMain, exc: Exception
|
|
35
|
+
) -> FileFormatValidationError:
|
|
36
|
+
return cls(
|
|
37
|
+
message=f"{info.original_file.name} could not be read as a JSON protocol.",
|
|
38
|
+
detail={"kind": "bad-json-protocol"},
|
|
39
|
+
wrapping=[PythonException(exc)],
|
|
40
|
+
)
|
|
41
|
+
|
|
32
42
|
|
|
33
43
|
class FileFormatValidator:
|
|
34
44
|
"""File format validation interface."""
|
|
@@ -61,22 +71,80 @@ async def _validate_labware_definition(info: IdentifiedLabwareDefinition) -> Non
|
|
|
61
71
|
await anyio.to_thread.run_sync(validate_sync)
|
|
62
72
|
|
|
63
73
|
|
|
74
|
+
def _handle_v8_json_protocol_validation_error(
|
|
75
|
+
info: IdentifiedJsonMain, pve: PydanticValidationError
|
|
76
|
+
) -> None:
|
|
77
|
+
for error in pve.errors():
|
|
78
|
+
if error["loc"] == ("commandSchemaId",) and error["type"] == "type_error.enum":
|
|
79
|
+
# type_error.enum is for "this entry is not in this enum" and happens if you constrain a field by
|
|
80
|
+
# annotating it with Enum, as we now do for command schema IDs
|
|
81
|
+
raise FileFormatValidationError(
|
|
82
|
+
message=(
|
|
83
|
+
f"{info.original_file.name} could not be read as a JSON protocol, in part because its command schema "
|
|
84
|
+
"id is unknown. This protocol may have been exported from a future version of authorship software. "
|
|
85
|
+
"Updating your Opentrons software may help."
|
|
86
|
+
),
|
|
87
|
+
detail={
|
|
88
|
+
"kind": "bad-command-schema-id",
|
|
89
|
+
"command-schema-id": info.unvalidated_json.get(
|
|
90
|
+
"commandSchemaId", "<unknown>"
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
wrapping=[PythonException(pve)],
|
|
94
|
+
) from pve
|
|
95
|
+
if (
|
|
96
|
+
error["loc"] == ("labwareDefinitionSchemaId",)
|
|
97
|
+
and error["type"] == "value_error.const"
|
|
98
|
+
):
|
|
99
|
+
# value_error.const is for "this entry is not one of these const values", which is different from type_error.enum
|
|
100
|
+
# for I'm sure a very good reason, and happens if you constrain a field by annotating it with a Literal
|
|
101
|
+
raise FileFormatValidationError(
|
|
102
|
+
message=(
|
|
103
|
+
f"{info.original_file.name} could not be read as a JSON protocol, in part because its labware schema "
|
|
104
|
+
"id is unknown. This protocol may have been exported from a future version of authorship software. "
|
|
105
|
+
"Updating your Opentrons software may help."
|
|
106
|
+
),
|
|
107
|
+
detail={
|
|
108
|
+
"kind": "bad-labware-schema-id",
|
|
109
|
+
"labware-schema-id": info.unvalidated_json.get(
|
|
110
|
+
"labwareDefinitionSchemaId", "<unknown>"
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
if error["loc"] == ("liquidSchemaId",) and error["type"] == "value_error.const":
|
|
115
|
+
raise FileFormatValidationError(
|
|
116
|
+
message=(
|
|
117
|
+
f"{info.original_file.name} could not be read as a JSON protocol, in part because its liquid schema "
|
|
118
|
+
"id is unknown. This protocol may have been exported from a future version of authorship software. "
|
|
119
|
+
"Updating your Opentrons software may help."
|
|
120
|
+
),
|
|
121
|
+
detail={
|
|
122
|
+
"kind": "bad-liquid-schema-id",
|
|
123
|
+
"liquid-schema-id": info.unvalidated_json.get(
|
|
124
|
+
"liquidSchemaId", "<unknown>"
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
raise FileFormatValidationError._generic_json_failure(info, pve) from pve
|
|
130
|
+
|
|
131
|
+
|
|
64
132
|
async def _validate_json_protocol(info: IdentifiedJsonMain) -> None:
|
|
65
133
|
def validate_sync() -> None:
|
|
66
|
-
|
|
67
|
-
|
|
134
|
+
if info.schema_version == 8:
|
|
135
|
+
try:
|
|
68
136
|
JsonProtocolV8.parse_obj(info.unvalidated_json)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
137
|
+
except PydanticValidationError as pve:
|
|
138
|
+
_handle_v8_json_protocol_validation_error(info, pve)
|
|
139
|
+
else:
|
|
140
|
+
try:
|
|
141
|
+
if info.schema_version == 7:
|
|
142
|
+
JsonProtocolV7.parse_obj(info.unvalidated_json)
|
|
143
|
+
elif info.schema_version == 6:
|
|
144
|
+
JsonProtocolV6.parse_obj(info.unvalidated_json)
|
|
145
|
+
else:
|
|
146
|
+
JsonProtocolUpToV5.parse_obj(info.unvalidated_json)
|
|
147
|
+
except PydanticValidationError as e:
|
|
148
|
+
raise FileFormatValidationError._generic_json_failure(info, e) from e
|
|
81
149
|
|
|
82
150
|
await anyio.to_thread.run_sync(validate_sync)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Translation of JSON protocol commands into ProtocolEngine commands."""
|
|
2
|
-
from typing import cast, List, Union
|
|
3
|
-
from pydantic import parse_obj_as
|
|
2
|
+
from typing import cast, List, Union, Iterator
|
|
3
|
+
from pydantic import parse_obj_as, ValidationError as PydanticValidationError
|
|
4
4
|
|
|
5
5
|
from opentrons_shared_data.pipette.types import PipetteNameType
|
|
6
6
|
from opentrons_shared_data.protocol.models import (
|
|
@@ -12,6 +12,7 @@ from opentrons_shared_data.protocol.models import (
|
|
|
12
12
|
protocol_schema_v8,
|
|
13
13
|
)
|
|
14
14
|
from opentrons_shared_data import command as command_schema
|
|
15
|
+
from opentrons_shared_data.errors.exceptions import InvalidProtocolData, PythonException
|
|
15
16
|
|
|
16
17
|
from opentrons.types import MountType
|
|
17
18
|
from opentrons.protocol_engine import (
|
|
@@ -196,7 +197,7 @@ class JsonTranslator:
|
|
|
196
197
|
"""Class that translates commands/liquids from PD/JSON to ProtocolEngine."""
|
|
197
198
|
|
|
198
199
|
def translate_liquids(
|
|
199
|
-
self, protocol: Union[ProtocolSchemaV6, ProtocolSchemaV7]
|
|
200
|
+
self, protocol: Union[ProtocolSchemaV6, ProtocolSchemaV7, ProtocolSchemaV8]
|
|
200
201
|
) -> List[Liquid]:
|
|
201
202
|
"""Takes json protocol v6 and translates liquids->protocol engine liquids."""
|
|
202
203
|
protocol_liquids = protocol.liquids or {}
|
|
@@ -258,7 +259,8 @@ class JsonTranslator:
|
|
|
258
259
|
self, protocol: ProtocolSchemaV8
|
|
259
260
|
) -> List[pe_commands.CommandCreate]:
|
|
260
261
|
"""Translate commands in json protocol schema v8, which might be of different command schemas."""
|
|
261
|
-
command_schema_ref = protocol.commandSchemaId
|
|
262
|
+
command_schema_ref = protocol.commandSchemaId.value
|
|
263
|
+
|
|
262
264
|
# these calls will raise if the command schema version is invalid or unknown
|
|
263
265
|
command_schema_version = command_schema.schema_version_from_ref(
|
|
264
266
|
command_schema_ref
|
|
@@ -267,4 +269,18 @@ class JsonTranslator:
|
|
|
267
269
|
command_schema_version
|
|
268
270
|
)
|
|
269
271
|
|
|
270
|
-
|
|
272
|
+
def translate_all_commands() -> Iterator[pe_commands.CommandCreate]:
|
|
273
|
+
for command in protocol.commands:
|
|
274
|
+
try:
|
|
275
|
+
yield _translate_simple_command(command)
|
|
276
|
+
except PydanticValidationError as pve:
|
|
277
|
+
raise InvalidProtocolData(
|
|
278
|
+
message=(
|
|
279
|
+
"The protocol is invalid because it contains an unknown or malformed command, "
|
|
280
|
+
f'"{command.commandType}".'
|
|
281
|
+
),
|
|
282
|
+
detail={"kind": "invalid-command"},
|
|
283
|
+
wrapping=[PythonException(pve)],
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
return list(translate_all_commands())
|
|
@@ -34,6 +34,9 @@ from opentrons.protocol_engine.resources import (
|
|
|
34
34
|
ModuleDataProvider,
|
|
35
35
|
pipette_data_provider,
|
|
36
36
|
)
|
|
37
|
+
from opentrons.protocol_engine.state.update_types import (
|
|
38
|
+
StateUpdate,
|
|
39
|
+
)
|
|
37
40
|
|
|
38
41
|
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
39
42
|
from opentrons_shared_data.errors import ErrorCodes, EnumeratedError, PythonException
|
|
@@ -267,7 +270,8 @@ class LegacyCommandMapper:
|
|
|
267
270
|
)
|
|
268
271
|
results.append(
|
|
269
272
|
pe_actions.SucceedCommandAction(
|
|
270
|
-
completed_command,
|
|
273
|
+
completed_command,
|
|
274
|
+
state_update=StateUpdate(),
|
|
271
275
|
)
|
|
272
276
|
)
|
|
273
277
|
|
|
@@ -672,9 +676,19 @@ class LegacyCommandMapper:
|
|
|
672
676
|
# We just set this above, so we know it's not None.
|
|
673
677
|
started_at=succeeded_command.startedAt, # type: ignore[arg-type]
|
|
674
678
|
)
|
|
679
|
+
state_update = StateUpdate()
|
|
680
|
+
assert succeeded_command.result is not None
|
|
681
|
+
state_update.set_loaded_labware(
|
|
682
|
+
labware_id=labware_id,
|
|
683
|
+
definition=succeeded_command.result.definition,
|
|
684
|
+
display_name=labware_load_info.labware_display_name,
|
|
685
|
+
offset_id=labware_load_info.offset_id,
|
|
686
|
+
location=location,
|
|
687
|
+
)
|
|
688
|
+
|
|
675
689
|
succeed_action = pe_actions.SucceedCommandAction(
|
|
676
690
|
command=succeeded_command,
|
|
677
|
-
|
|
691
|
+
state_update=state_update,
|
|
678
692
|
)
|
|
679
693
|
|
|
680
694
|
self._command_count["LOAD_LABWARE"] = count + 1
|
|
@@ -715,7 +729,14 @@ class LegacyCommandMapper:
|
|
|
715
729
|
result=pe_commands.LoadPipetteResult.construct(pipetteId=pipette_id),
|
|
716
730
|
)
|
|
717
731
|
serial = instrument_load_info.pipette_dict.get("pipette_id", None) or ""
|
|
718
|
-
|
|
732
|
+
state_update = StateUpdate()
|
|
733
|
+
state_update.set_load_pipette(
|
|
734
|
+
pipette_id=pipette_id,
|
|
735
|
+
mount=succeeded_command.params.mount,
|
|
736
|
+
pipette_name=succeeded_command.params.pipetteName,
|
|
737
|
+
liquid_presence_detection=succeeded_command.params.liquidPresenceDetection,
|
|
738
|
+
)
|
|
739
|
+
state_update.update_pipette_config(
|
|
719
740
|
pipette_id=pipette_id,
|
|
720
741
|
serial_number=serial,
|
|
721
742
|
config=pipette_data_provider.get_pipette_static_config(
|
|
@@ -738,9 +759,10 @@ class LegacyCommandMapper:
|
|
|
738
759
|
# We just set this above, so we know it's not None.
|
|
739
760
|
started_at=succeeded_command.startedAt, # type: ignore[arg-type]
|
|
740
761
|
)
|
|
762
|
+
|
|
741
763
|
succeed_action = pe_actions.SucceedCommandAction(
|
|
742
764
|
command=succeeded_command,
|
|
743
|
-
|
|
765
|
+
state_update=state_update,
|
|
744
766
|
)
|
|
745
767
|
|
|
746
768
|
self._command_count["LOAD_PIPETTE"] = count + 1
|
|
@@ -805,8 +827,7 @@ class LegacyCommandMapper:
|
|
|
805
827
|
started_at=succeeded_command.startedAt, # type: ignore[arg-type]
|
|
806
828
|
)
|
|
807
829
|
succeed_action = pe_actions.SucceedCommandAction(
|
|
808
|
-
command=succeeded_command,
|
|
809
|
-
private_result=None,
|
|
830
|
+
command=succeeded_command, state_update=StateUpdate()
|
|
810
831
|
)
|
|
811
832
|
|
|
812
833
|
self._command_count["LOAD_MODULE"] = count + 1
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Customize the ProtocolEngine to monitor and control legacy (APIv2) protocols."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import asyncio
|
|
5
5
|
from contextlib import ExitStack
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Optional
|
|
7
7
|
|
|
8
8
|
from opentrons.legacy_commands.types import CommandMessage as LegacyCommand
|
|
9
9
|
from opentrons.legacy_broker import LegacyBroker
|
|
@@ -12,7 +12,6 @@ from opentrons.protocol_engine import AbstractPlugin, actions as pe_actions
|
|
|
12
12
|
from opentrons.util.broker import ReadOnlyBroker
|
|
13
13
|
|
|
14
14
|
from .legacy_command_mapper import LegacyCommandMapper
|
|
15
|
-
from .thread_async_queue import ThreadAsyncQueue
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class LegacyContextPlugin(AbstractPlugin):
|
|
@@ -21,59 +20,36 @@ class LegacyContextPlugin(AbstractPlugin):
|
|
|
21
20
|
In the legacy ProtocolContext, protocol execution is accomplished
|
|
22
21
|
by direct communication with the HardwareControlAPI, as opposed to an
|
|
23
22
|
intermediate layer like the ProtocolEngine. This plugin wraps up
|
|
24
|
-
and hides this behavior, so the ProtocolEngine can monitor
|
|
23
|
+
and hides this behavior, so the ProtocolEngine can monitor
|
|
25
24
|
the run of a legacy protocol without affecting the execution of
|
|
26
25
|
the protocol commands themselves.
|
|
27
26
|
|
|
28
|
-
This plugin allows a ProtocolEngine to
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
the ProtocolEngine existed.
|
|
32
|
-
2. Subscribe to what is being done with the legacy ProtocolContext,
|
|
33
|
-
and insert matching commands into ProtocolEngine state for
|
|
34
|
-
purely progress-tracking purposes.
|
|
27
|
+
This plugin allows a ProtocolEngine to subscribe to what is being done with the
|
|
28
|
+
legacy ProtocolContext, and insert matching commands into ProtocolEngine state for
|
|
29
|
+
purely progress-tracking purposes.
|
|
35
30
|
"""
|
|
36
31
|
|
|
37
32
|
def __init__(
|
|
38
33
|
self,
|
|
34
|
+
engine_loop: asyncio.AbstractEventLoop,
|
|
39
35
|
broker: LegacyBroker,
|
|
40
36
|
equipment_broker: ReadOnlyBroker[LoadInfo],
|
|
41
37
|
legacy_command_mapper: Optional[LegacyCommandMapper] = None,
|
|
42
38
|
) -> None:
|
|
43
39
|
"""Initialize the plugin with its dependencies."""
|
|
40
|
+
self._engine_loop = engine_loop
|
|
41
|
+
|
|
44
42
|
self._broker = broker
|
|
45
43
|
self._equipment_broker = equipment_broker
|
|
46
44
|
self._legacy_command_mapper = legacy_command_mapper or LegacyCommandMapper()
|
|
47
45
|
|
|
48
|
-
# We use a non-blocking queue to communicate activity
|
|
49
|
-
# from the APIv2 protocol, which is running in its own thread,
|
|
50
|
-
# to the ProtocolEngine, which is running in the main thread's async event loop.
|
|
51
|
-
#
|
|
52
|
-
# The queue being non-blocking lets the protocol communicate its activity
|
|
53
|
-
# instantly *even if the event loop is currently occupied by something else.*
|
|
54
|
-
# Various things can accidentally occupy the event loop for too long.
|
|
55
|
-
# So if the protocol had to wait for the event loop to be free
|
|
56
|
-
# every time it reported some activity,
|
|
57
|
-
# it could visibly stall for a moment, making its motion jittery.
|
|
58
|
-
#
|
|
59
|
-
# TODO(mm, 2024-03-22): See if we can remove this non-blockingness now.
|
|
60
|
-
# It was one of several band-aids introduced in ~v5.0.0 to mitigate performance
|
|
61
|
-
# problems. v6.3.0 started running some Python protocols directly through
|
|
62
|
-
# Protocol Engine, without this plugin, and without any non-blocking queue.
|
|
63
|
-
# If performance is sufficient for those, that probably means the
|
|
64
|
-
# performance problems have been resolved in better ways elsewhere
|
|
65
|
-
# and we don't need this anymore.
|
|
66
|
-
self._actions_to_dispatch = ThreadAsyncQueue[List[pe_actions.Action]]()
|
|
67
|
-
self._action_dispatching_task: Optional[Task[None]] = None
|
|
68
|
-
|
|
69
46
|
self._subscription_exit_stack: Optional[ExitStack] = None
|
|
70
47
|
|
|
71
48
|
def setup(self) -> None:
|
|
72
49
|
"""Set up the plugin.
|
|
73
50
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* Kick off a background task to inform Protocol Engine of that activity.
|
|
51
|
+
Subscribe to the APIv2 context's message brokers to be informed
|
|
52
|
+
of the APIv2 protocol's activity.
|
|
77
53
|
"""
|
|
78
54
|
# Subscribe to activity on the APIv2 context,
|
|
79
55
|
# and arrange to unsubscribe when this plugin is torn down.
|
|
@@ -97,24 +73,16 @@ class LegacyContextPlugin(AbstractPlugin):
|
|
|
97
73
|
# to clean up these subscriptions.
|
|
98
74
|
self._subscription_exit_stack = exit_stack.pop_all()
|
|
99
75
|
|
|
100
|
-
|
|
101
|
-
self._action_dispatching_task = create_task(self._dispatch_all_actions())
|
|
102
|
-
|
|
76
|
+
# todo(mm, 2024-08-21): This no longer needs to be async.
|
|
103
77
|
async def teardown(self) -> None:
|
|
104
78
|
"""Tear down the plugin, undoing the work done in `setup()`.
|
|
105
79
|
|
|
106
80
|
Called by Protocol Engine.
|
|
107
81
|
At this point, the APIv2 protocol script must have exited.
|
|
108
82
|
"""
|
|
109
|
-
self.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await self._action_dispatching_task
|
|
113
|
-
self._action_dispatching_task = None
|
|
114
|
-
finally:
|
|
115
|
-
if self._subscription_exit_stack is not None:
|
|
116
|
-
self._subscription_exit_stack.close()
|
|
117
|
-
self._subscription_exit_stack = None
|
|
83
|
+
if self._subscription_exit_stack is not None:
|
|
84
|
+
self._subscription_exit_stack.close()
|
|
85
|
+
self._subscription_exit_stack = None
|
|
118
86
|
|
|
119
87
|
def handle_action(self, action: pe_actions.Action) -> None:
|
|
120
88
|
"""React to a ProtocolEngine action."""
|
|
@@ -127,7 +95,10 @@ class LegacyContextPlugin(AbstractPlugin):
|
|
|
127
95
|
Used as a broker callback, so this will run in the APIv2 protocol's thread.
|
|
128
96
|
"""
|
|
129
97
|
pe_actions = self._legacy_command_mapper.map_command(command=command)
|
|
130
|
-
|
|
98
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
99
|
+
self._dispatch_action_list(pe_actions), self._engine_loop
|
|
100
|
+
)
|
|
101
|
+
future.result()
|
|
131
102
|
|
|
132
103
|
def _handle_equipment_loaded(self, load_info: LoadInfo) -> None:
|
|
133
104
|
"""Handle an equipment load reported by the legacy APIv2 protocol.
|
|
@@ -135,26 +106,11 @@ class LegacyContextPlugin(AbstractPlugin):
|
|
|
135
106
|
Used as a broker callback, so this will run in the APIv2 protocol's thread.
|
|
136
107
|
"""
|
|
137
108
|
pe_actions = self._legacy_command_mapper.map_equipment_load(load_info=load_info)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
async for action_batch in self._actions_to_dispatch.get_async_until_closed():
|
|
147
|
-
# It's critical that we dispatch this batch of actions as one atomic
|
|
148
|
-
# sequence, without yielding to the event loop.
|
|
149
|
-
# Although this plugin only means to use the ProtocolEngine as a way of
|
|
150
|
-
# passively exposing the protocol's progress, the ProtocolEngine is still
|
|
151
|
-
# theoretically active, which means it's constantly watching in the
|
|
152
|
-
# background to execute any commands that it finds `queued`.
|
|
153
|
-
#
|
|
154
|
-
# For example, one of these action batches will often want to
|
|
155
|
-
# instantaneously create a running command by having a queue action
|
|
156
|
-
# immediately followed by a run action. We cannot let the
|
|
157
|
-
# ProtocolEngine's background task see the command in the `queued` state,
|
|
158
|
-
# or it will try to execute it, which the legacy protocol is already doing.
|
|
159
|
-
for action in action_batch:
|
|
160
|
-
self.dispatch(action)
|
|
109
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
110
|
+
self._dispatch_action_list(pe_actions), self._engine_loop
|
|
111
|
+
)
|
|
112
|
+
future.result()
|
|
113
|
+
|
|
114
|
+
async def _dispatch_action_list(self, actions: list[pe_actions.Action]) -> None:
|
|
115
|
+
for action in actions:
|
|
116
|
+
self.dispatch(action)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Protocol run control and management."""
|
|
2
|
+
import asyncio
|
|
2
3
|
from typing import List, NamedTuple, Optional, Union
|
|
3
4
|
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
@@ -122,9 +123,9 @@ class AbstractRunner(ABC):
|
|
|
122
123
|
post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
|
|
123
124
|
)
|
|
124
125
|
|
|
125
|
-
def resume_from_recovery(self) -> None:
|
|
126
|
+
def resume_from_recovery(self, reconcile_false_positive: bool) -> None:
|
|
126
127
|
"""See `ProtocolEngine.resume_from_recovery()`."""
|
|
127
|
-
self._protocol_engine.resume_from_recovery()
|
|
128
|
+
self._protocol_engine.resume_from_recovery(reconcile_false_positive)
|
|
128
129
|
|
|
129
130
|
@abstractmethod
|
|
130
131
|
async def run(
|
|
@@ -220,7 +221,9 @@ class PythonAndLegacyRunner(AbstractRunner):
|
|
|
220
221
|
equipment_broker = Broker[LoadInfo]()
|
|
221
222
|
self._protocol_engine.add_plugin(
|
|
222
223
|
LegacyContextPlugin(
|
|
223
|
-
|
|
224
|
+
engine_loop=asyncio.get_running_loop(),
|
|
225
|
+
broker=self._broker,
|
|
226
|
+
equipment_broker=equipment_broker,
|
|
224
227
|
)
|
|
225
228
|
)
|
|
226
229
|
self._hardware_api.should_taskify_movement_execution(taskify=True)
|
|
@@ -14,6 +14,7 @@ from opentrons_shared_data.robot.types import RobotType
|
|
|
14
14
|
from . import protocol_runner, RunResult, JsonRunner, PythonAndLegacyRunner
|
|
15
15
|
from ..hardware_control import HardwareControlAPI
|
|
16
16
|
from ..hardware_control.modules import AbstractModule as HardwareModuleAPI
|
|
17
|
+
from ..hardware_control.nozzle_manager import NozzleMap
|
|
17
18
|
from ..protocol_engine import (
|
|
18
19
|
ProtocolEngine,
|
|
19
20
|
CommandCreate,
|
|
@@ -204,9 +205,9 @@ class RunOrchestrator:
|
|
|
204
205
|
post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
|
|
205
206
|
)
|
|
206
207
|
|
|
207
|
-
def resume_from_recovery(self) -> None:
|
|
208
|
+
def resume_from_recovery(self, reconcile_false_positive: bool) -> None:
|
|
208
209
|
"""Resume the run from recovery."""
|
|
209
|
-
self._protocol_engine.resume_from_recovery()
|
|
210
|
+
self._protocol_engine.resume_from_recovery(reconcile_false_positive)
|
|
210
211
|
|
|
211
212
|
async def finish(
|
|
212
213
|
self,
|
|
@@ -256,19 +257,34 @@ class RunOrchestrator:
|
|
|
256
257
|
"""Get the "current" command, if any."""
|
|
257
258
|
return self._protocol_engine.state_view.commands.get_current()
|
|
258
259
|
|
|
260
|
+
def get_most_recently_finalized_command(self) -> Optional[CommandPointer]:
|
|
261
|
+
"""Get the most recently finalized command, if any."""
|
|
262
|
+
most_recently_finalized_command = (
|
|
263
|
+
self._protocol_engine.state_view.commands.get_most_recently_finalized_command()
|
|
264
|
+
)
|
|
265
|
+
return (
|
|
266
|
+
CommandPointer(
|
|
267
|
+
command_id=most_recently_finalized_command.command.id,
|
|
268
|
+
command_key=most_recently_finalized_command.command.key,
|
|
269
|
+
created_at=most_recently_finalized_command.command.createdAt,
|
|
270
|
+
index=most_recently_finalized_command.index,
|
|
271
|
+
)
|
|
272
|
+
if most_recently_finalized_command
|
|
273
|
+
else None
|
|
274
|
+
)
|
|
275
|
+
|
|
259
276
|
def get_command_slice(
|
|
260
|
-
self,
|
|
261
|
-
cursor: Optional[int],
|
|
262
|
-
length: int,
|
|
277
|
+
self, cursor: Optional[int], length: int, include_fixit_commands: bool
|
|
263
278
|
) -> CommandSlice:
|
|
264
279
|
"""Get a slice of run commands.
|
|
265
280
|
|
|
266
281
|
Args:
|
|
267
282
|
cursor: Requested index of first command in the returned slice.
|
|
268
283
|
length: Length of slice to return.
|
|
284
|
+
include_fixit_commands: Get all command intents.
|
|
269
285
|
"""
|
|
270
286
|
return self._protocol_engine.state_view.commands.get_slice(
|
|
271
|
-
cursor=cursor, length=length
|
|
287
|
+
cursor=cursor, length=length, include_fixit_commands=include_fixit_commands
|
|
272
288
|
)
|
|
273
289
|
|
|
274
290
|
def get_command_error_slice(
|
|
@@ -398,6 +414,10 @@ class RunOrchestrator:
|
|
|
398
414
|
"""Get engine deck type."""
|
|
399
415
|
return self._protocol_engine.state_view.config.deck_type
|
|
400
416
|
|
|
417
|
+
def get_nozzle_maps(self) -> Dict[str, NozzleMap]:
|
|
418
|
+
"""Get current nozzle maps keyed by pipette id."""
|
|
419
|
+
return self._protocol_engine.state_view.tips.get_pipette_nozzle_maps()
|
|
420
|
+
|
|
401
421
|
def set_error_recovery_policy(self, policy: ErrorRecoveryPolicy) -> None:
|
|
402
422
|
"""Create error recovery policy for the run."""
|
|
403
423
|
self._protocol_engine.set_error_recovery_policy(policy)
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
from typing import Any, Dict, Tuple
|
|
2
2
|
|
|
3
3
|
from opentrons.protocols.advanced_control.transfers import MixStrategy, Mix
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def mix_from_kwargs(
|
|
7
|
-
top_kwargs: typing.Dict[str, typing.Any]
|
|
8
|
-
) -> typing.Tuple[MixStrategy, Mix]:
|
|
6
|
+
def mix_from_kwargs(top_kwargs: Dict[str, Any]) -> Tuple[MixStrategy, Mix]:
|
|
9
7
|
"""A utility function to determine mix strategy from key word arguments
|
|
10
8
|
to InstrumentContext.mix"""
|
|
11
9
|
|
|
12
|
-
def _mix_requested(kwargs, opt):
|
|
10
|
+
def _mix_requested(kwargs: Dict[str, Any], opt: str) -> bool:
|
|
13
11
|
"""
|
|
14
12
|
Helper for determining mix options from :py:meth:`transfer` kwargs
|
|
15
13
|
Mixes can be ignored in kwargs by either
|