opentrons 8.2.0a0__py2.py3-none-any.whl → 8.2.0a2__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/drivers/absorbance_reader/async_byonoy.py +3 -3
- opentrons/hardware_control/ot3api.py +5 -5
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/protocol_api/core/engine/labware.py +10 -2
- opentrons/protocol_api/core/engine/module_core.py +38 -1
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +12 -5
- opentrons/protocol_api/core/engine/protocol.py +5 -30
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -0
- opentrons/protocol_api/core/protocol.py +1 -0
- opentrons/protocol_api/module_contexts.py +13 -0
- opentrons/protocol_api/protocol_context.py +12 -2
- opentrons/protocol_engine/actions/__init__.py +0 -2
- opentrons/protocol_engine/actions/actions.py +0 -12
- opentrons/protocol_engine/clients/sync_client.py +0 -6
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +18 -31
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -7
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +17 -29
- opentrons/protocol_engine/commands/load_labware.py +9 -0
- opentrons/protocol_engine/commands/load_module.py +0 -39
- opentrons/protocol_engine/commands/move_labware.py +49 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +49 -35
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -1
- opentrons/protocol_engine/create_protocol_engine.py +18 -1
- opentrons/protocol_engine/errors/__init__.py +2 -0
- opentrons/protocol_engine/errors/exceptions.py +13 -0
- opentrons/protocol_engine/execution/labware_movement.py +69 -21
- opentrons/protocol_engine/execution/movement.py +9 -4
- opentrons/protocol_engine/protocol_engine.py +0 -7
- opentrons/protocol_engine/resources/deck_data_provider.py +0 -39
- opentrons/protocol_engine/resources/file_provider.py +11 -7
- opentrons/protocol_engine/resources/fixture_validation.py +6 -1
- opentrons/protocol_engine/state/geometry.py +91 -49
- opentrons/protocol_engine/state/labware.py +102 -25
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +3 -1
- opentrons/protocol_engine/state/modules.py +49 -79
- opentrons/protocol_engine/state/motion.py +17 -5
- opentrons/protocol_engine/state/update_types.py +16 -0
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/METADATA +4 -4
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/RECORD +47 -47
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/WHEEL +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/top_level.txt +0 -0
|
@@ -24,7 +24,7 @@ from opentrons.hardware_control.modules.errors import AbsorbanceReaderDisconnect
|
|
|
24
24
|
|
|
25
25
|
SN_PARSER = re.compile(r'ATTRS{serial}=="(?P<serial>.+?)"')
|
|
26
26
|
VERSION_PARSER = re.compile(r"Absorbance (?P<version>V\d+\.\d+\.\d+)")
|
|
27
|
-
SERIAL_PARSER = re.compile(r"(?P<serial>BYO[A-Z]{3}[0-9]
|
|
27
|
+
SERIAL_PARSER = re.compile(r"(?P<serial>(OPT|BYO)[A-Z]{3}[0-9]+)")
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class AsyncByonoy:
|
|
@@ -156,9 +156,9 @@ class AsyncByonoy:
|
|
|
156
156
|
func=partial(self._interface.get_device_information, handle),
|
|
157
157
|
)
|
|
158
158
|
self._raise_if_error(err.name, f"Error getting device information: {err}")
|
|
159
|
-
serial_match = SERIAL_PARSER.
|
|
159
|
+
serial_match = SERIAL_PARSER.fullmatch(device_info.sn)
|
|
160
160
|
version_match = VERSION_PARSER.match(device_info.version)
|
|
161
|
-
serial = serial_match["serial"] if serial_match else "
|
|
161
|
+
serial = serial_match["serial"].strip() if serial_match else "OPTMAA00000"
|
|
162
162
|
version = version_match["version"].lower() if version_match else "v0.0.0"
|
|
163
163
|
info = {
|
|
164
164
|
"serial": serial,
|
|
@@ -775,12 +775,12 @@ class OT3API(
|
|
|
775
775
|
"""
|
|
776
776
|
Function to update motor estimation for a set of axes
|
|
777
777
|
"""
|
|
778
|
+
if axes is None:
|
|
779
|
+
axes = [ax for ax in Axis]
|
|
778
780
|
|
|
779
|
-
if
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
checked_axes = [ax for ax in Axis]
|
|
783
|
-
await self._backend.update_motor_estimation(checked_axes)
|
|
781
|
+
axes = [ax for ax in axes if self._backend.axis_is_present(ax)]
|
|
782
|
+
|
|
783
|
+
await self._backend.update_motor_estimation(axes)
|
|
784
784
|
|
|
785
785
|
# Global actions API
|
|
786
786
|
def pause(self, pause_type: PauseType) -> None:
|
|
@@ -10,7 +10,7 @@ class PositionEstimator(Protocol):
|
|
|
10
10
|
"""Update the specified axes' position estimators from their encoders.
|
|
11
11
|
|
|
12
12
|
This will allow these axes to make a non-home move even if they do not currently have
|
|
13
|
-
a position estimation (unless there is no tracked
|
|
13
|
+
a position estimation (unless there is no tracked position from the encoders, as would be
|
|
14
14
|
true immediately after boot).
|
|
15
15
|
|
|
16
16
|
Axis encoders have less precision than their position estimators. Calling this function will
|
|
@@ -19,6 +19,8 @@ class PositionEstimator(Protocol):
|
|
|
19
19
|
|
|
20
20
|
This function updates only the requested axes. If other axes have bad position estimation,
|
|
21
21
|
moves that require those axes or attempts to get the position of those axes will still fail.
|
|
22
|
+
Axes that are not currently available (like a plunger for a pipette that is not connected)
|
|
23
|
+
will be ignored.
|
|
22
24
|
"""
|
|
23
25
|
...
|
|
24
26
|
|
|
@@ -49,7 +49,9 @@ def stringify_disposal_location(location: Union[TrashBin, WasteChute]) -> str:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def _stringify_labware_movement_location(
|
|
52
|
-
location: Union[
|
|
52
|
+
location: Union[
|
|
53
|
+
DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
|
|
54
|
+
]
|
|
53
55
|
) -> str:
|
|
54
56
|
if isinstance(location, (int, str)):
|
|
55
57
|
return f"slot {location}"
|
|
@@ -61,11 +63,15 @@ def _stringify_labware_movement_location(
|
|
|
61
63
|
return str(location)
|
|
62
64
|
elif isinstance(location, WasteChute):
|
|
63
65
|
return "Waste Chute"
|
|
66
|
+
elif isinstance(location, TrashBin):
|
|
67
|
+
return "Trash Bin " + location.location.name
|
|
64
68
|
|
|
65
69
|
|
|
66
70
|
def stringify_labware_movement_command(
|
|
67
71
|
source_labware: Labware,
|
|
68
|
-
destination: Union[
|
|
72
|
+
destination: Union[
|
|
73
|
+
DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
|
|
74
|
+
],
|
|
69
75
|
use_gripper: bool,
|
|
70
76
|
) -> str:
|
|
71
77
|
source_labware_text = _stringify_labware_movement_location(source_labware)
|
|
@@ -19,7 +19,7 @@ from opentrons.protocol_engine.types import (
|
|
|
19
19
|
LabwareOffsetCreate,
|
|
20
20
|
LabwareOffsetVector,
|
|
21
21
|
)
|
|
22
|
-
from opentrons.types import DeckSlotName, Point
|
|
22
|
+
from opentrons.types import DeckSlotName, Point, StagingSlotName
|
|
23
23
|
from opentrons.hardware_control.nozzle_manager import NozzleMap
|
|
24
24
|
|
|
25
25
|
|
|
@@ -139,6 +139,10 @@ class LabwareCore(AbstractLabware[WellCore]):
|
|
|
139
139
|
"""Whether the labware is an adapter."""
|
|
140
140
|
return LabwareRole.adapter in self._definition.allowedRoles
|
|
141
141
|
|
|
142
|
+
def is_lid(self) -> bool:
|
|
143
|
+
"""Whether the labware is a lid."""
|
|
144
|
+
return LabwareRole.lid in self._definition.allowedRoles
|
|
145
|
+
|
|
142
146
|
def is_fixed_trash(self) -> bool:
|
|
143
147
|
"""Whether the labware is a fixed trash."""
|
|
144
148
|
return self._engine_client.state.labware.is_fixed_trash(
|
|
@@ -186,9 +190,13 @@ class LabwareCore(AbstractLabware[WellCore]):
|
|
|
186
190
|
def get_deck_slot(self) -> Optional[DeckSlotName]:
|
|
187
191
|
"""Get the deck slot the labware is in, if on deck."""
|
|
188
192
|
try:
|
|
189
|
-
|
|
193
|
+
ancestor = self._engine_client.state.geometry.get_ancestor_slot_name(
|
|
190
194
|
self.labware_id
|
|
191
195
|
)
|
|
196
|
+
if isinstance(ancestor, StagingSlotName):
|
|
197
|
+
# The only use case for get_deck_slot is with a legacy OT-2 function which resolves to a numerical deck slot, so we can ignore staging area slots for now
|
|
198
|
+
return None
|
|
199
|
+
return ancestor
|
|
192
200
|
except (
|
|
193
201
|
LabwareNotOnDeckError,
|
|
194
202
|
ModuleNotOnDeckError,
|
|
@@ -41,6 +41,11 @@ from ..module import (
|
|
|
41
41
|
from .exceptions import InvalidMagnetEngageHeightError
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
# Valid wavelength range for absorbance reader
|
|
45
|
+
ABS_WAVELENGTH_MIN = 350
|
|
46
|
+
ABS_WAVELENGTH_MAX = 1000
|
|
47
|
+
|
|
48
|
+
|
|
44
49
|
class ModuleCore(AbstractModuleCore):
|
|
45
50
|
"""Module core logic implementation for Python protocols.
|
|
46
51
|
Args:
|
|
@@ -581,7 +586,39 @@ class AbsorbanceReaderCore(ModuleCore, AbstractAbsorbanceReaderCore):
|
|
|
581
586
|
"Cannot perform Initialize action on Absorbance Reader without calling `.close_lid()` first."
|
|
582
587
|
)
|
|
583
588
|
|
|
584
|
-
|
|
589
|
+
wavelength_len = len(wavelengths)
|
|
590
|
+
if mode == "single" and wavelength_len != 1:
|
|
591
|
+
raise ValueError(
|
|
592
|
+
f"Single mode can only be initialized with 1 wavelength"
|
|
593
|
+
f" {wavelength_len} wavelengths provided instead."
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
if mode == "multi" and (wavelength_len < 1 or wavelength_len > 6):
|
|
597
|
+
raise ValueError(
|
|
598
|
+
f"Multi mode can only be initialized with 1 - 6 wavelengths."
|
|
599
|
+
f" {wavelength_len} wavelengths provided instead."
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
if reference_wavelength is not None and (
|
|
603
|
+
reference_wavelength < ABS_WAVELENGTH_MIN
|
|
604
|
+
or reference_wavelength > ABS_WAVELENGTH_MAX
|
|
605
|
+
):
|
|
606
|
+
raise ValueError(
|
|
607
|
+
f"Unsupported reference wavelength: ({reference_wavelength}) needs"
|
|
608
|
+
f" to between {ABS_WAVELENGTH_MIN} and {ABS_WAVELENGTH_MAX} nm."
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
for wavelength in wavelengths:
|
|
612
|
+
if (
|
|
613
|
+
not isinstance(wavelength, int)
|
|
614
|
+
or wavelength < ABS_WAVELENGTH_MIN
|
|
615
|
+
or wavelength > ABS_WAVELENGTH_MAX
|
|
616
|
+
):
|
|
617
|
+
raise ValueError(
|
|
618
|
+
f"Unsupported sample wavelength: ({wavelength}) needs"
|
|
619
|
+
f" to between {ABS_WAVELENGTH_MIN} and {ABS_WAVELENGTH_MAX} nm."
|
|
620
|
+
)
|
|
621
|
+
|
|
585
622
|
self._engine_client.execute_command(
|
|
586
623
|
cmd.absorbance_reader.InitializeParams(
|
|
587
624
|
moduleId=self.module_id,
|
|
@@ -9,6 +9,7 @@ from typing import (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
from opentrons_shared_data.errors.exceptions import MotionPlanningFailureError
|
|
12
|
+
from opentrons.protocol_engine.errors import LocationIsStagingSlotError
|
|
12
13
|
from opentrons_shared_data.module import FLEX_TC_LID_COLLISION_ZONE
|
|
13
14
|
|
|
14
15
|
from opentrons.hardware_control import CriticalPoint
|
|
@@ -63,7 +64,7 @@ _FLEX_TC_LID_FRONT_RIGHT_PT = Point(
|
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def check_safe_for_pipette_movement(
|
|
67
|
+
def check_safe_for_pipette_movement( # noqa: C901
|
|
67
68
|
engine_state: StateView,
|
|
68
69
|
pipette_id: str,
|
|
69
70
|
labware_id: str,
|
|
@@ -121,8 +122,12 @@ def check_safe_for_pipette_movement(
|
|
|
121
122
|
f"Requested motion with the {primary_nozzle} nozzle partial configuration"
|
|
122
123
|
f" is outside of robot bounds for the pipette."
|
|
123
124
|
)
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
ancestor = engine_state.geometry.get_ancestor_slot_name(labware_id)
|
|
126
|
+
if isinstance(ancestor, StagingSlotName):
|
|
127
|
+
raise LocationIsStagingSlotError(
|
|
128
|
+
"Cannot perform pipette actions on labware in Staging Area Slot."
|
|
129
|
+
)
|
|
130
|
+
labware_slot = ancestor
|
|
126
131
|
|
|
127
132
|
surrounding_slots = adjacent_slots_getters.get_surrounding_slots(
|
|
128
133
|
slot=labware_slot.as_int(), robot_type=engine_state.config.robot_type
|
|
@@ -282,8 +287,10 @@ def check_safe_for_tip_pickup_and_return(
|
|
|
282
287
|
is_96_ch_tiprack_adapter = engine_state.labware.get_has_quirk(
|
|
283
288
|
labware_id=tiprack_parent.labwareId, quirk="tiprackAdapterFor96Channel"
|
|
284
289
|
)
|
|
285
|
-
tiprack_height = engine_state.labware.get_dimensions(labware_id).z
|
|
286
|
-
adapter_height = engine_state.labware.get_dimensions(
|
|
290
|
+
tiprack_height = engine_state.labware.get_dimensions(labware_id=labware_id).z
|
|
291
|
+
adapter_height = engine_state.labware.get_dimensions(
|
|
292
|
+
labware_id=tiprack_parent.labwareId
|
|
293
|
+
).z
|
|
287
294
|
if is_partial_config and tiprack_height < adapter_height:
|
|
288
295
|
raise PartialTipMovementNotAllowedError(
|
|
289
296
|
f"{tiprack_name} cannot be on an adapter taller than the tip rack"
|
|
@@ -329,6 +329,7 @@ class ProtocolCore(
|
|
|
329
329
|
NonConnectedModuleCore,
|
|
330
330
|
OffDeckType,
|
|
331
331
|
WasteChute,
|
|
332
|
+
TrashBin,
|
|
332
333
|
],
|
|
333
334
|
use_gripper: bool,
|
|
334
335
|
pause_for_manual_move: bool,
|
|
@@ -447,40 +448,10 @@ class ProtocolCore(
|
|
|
447
448
|
existing_module_ids=list(self._module_cores_by_id.keys()),
|
|
448
449
|
)
|
|
449
450
|
|
|
450
|
-
# When the protocol engine is created, we add Module Lids as part of the deck fixed labware
|
|
451
|
-
# If a valid module exists in the deck config. For analysis, we add the labware here since
|
|
452
|
-
# deck fixed labware is not created under the same conditions. We also need to inject the Module
|
|
453
|
-
# lids when the module isnt already on the deck config, like when adding a new
|
|
454
|
-
# module during a protocol setup.
|
|
455
|
-
self._load_virtual_module_lid(module_core)
|
|
456
|
-
|
|
457
451
|
self._module_cores_by_id[module_core.module_id] = module_core
|
|
458
452
|
|
|
459
453
|
return module_core
|
|
460
454
|
|
|
461
|
-
def _load_virtual_module_lid(
|
|
462
|
-
self, module_core: Union[ModuleCore, NonConnectedModuleCore]
|
|
463
|
-
) -> None:
|
|
464
|
-
if isinstance(module_core, AbsorbanceReaderCore):
|
|
465
|
-
substate = self._engine_client.state.modules.get_absorbance_reader_substate(
|
|
466
|
-
module_core.module_id
|
|
467
|
-
)
|
|
468
|
-
if substate.lid_id is None:
|
|
469
|
-
lid = self._engine_client.execute_command_without_recovery(
|
|
470
|
-
cmd.LoadLabwareParams(
|
|
471
|
-
loadName="opentrons_flex_lid_absorbance_plate_reader_module",
|
|
472
|
-
location=ModuleLocation(moduleId=module_core.module_id),
|
|
473
|
-
namespace="opentrons",
|
|
474
|
-
version=1,
|
|
475
|
-
displayName="Absorbance Reader Lid",
|
|
476
|
-
)
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
self._engine_client.add_absorbance_reader_lid(
|
|
480
|
-
module_id=module_core.module_id,
|
|
481
|
-
lid_id=lid.labwareId,
|
|
482
|
-
)
|
|
483
|
-
|
|
484
455
|
def _create_non_connected_module_core(
|
|
485
456
|
self, load_module_result: LoadModuleResult
|
|
486
457
|
) -> NonConnectedModuleCore:
|
|
@@ -807,6 +778,7 @@ class ProtocolCore(
|
|
|
807
778
|
NonConnectedModuleCore,
|
|
808
779
|
OffDeckType,
|
|
809
780
|
WasteChute,
|
|
781
|
+
TrashBin,
|
|
810
782
|
],
|
|
811
783
|
) -> LabwareLocation:
|
|
812
784
|
if isinstance(location, LabwareCore):
|
|
@@ -823,6 +795,7 @@ class ProtocolCore(
|
|
|
823
795
|
NonConnectedModuleCore,
|
|
824
796
|
OffDeckType,
|
|
825
797
|
WasteChute,
|
|
798
|
+
TrashBin,
|
|
826
799
|
]
|
|
827
800
|
) -> NonStackedLocation:
|
|
828
801
|
if isinstance(location, (ModuleCore, NonConnectedModuleCore)):
|
|
@@ -836,3 +809,5 @@ class ProtocolCore(
|
|
|
836
809
|
elif isinstance(location, WasteChute):
|
|
837
810
|
# TODO(mm, 2023-12-06) This will need to determine the appropriate Waste Chute to return, but only move_labware uses this for now
|
|
838
811
|
return AddressableAreaLocation(addressableAreaName="gripperWasteChute")
|
|
812
|
+
elif isinstance(location, TrashBin):
|
|
813
|
+
return AddressableAreaLocation(addressableAreaName=location.area_name)
|
|
@@ -97,6 +97,10 @@ class AbstractLabware(ABC, Generic[WellCoreType]):
|
|
|
97
97
|
def is_adapter(self) -> bool:
|
|
98
98
|
"""Whether the labware is an adapter."""
|
|
99
99
|
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def is_lid(self) -> bool:
|
|
102
|
+
"""Whether the labware is a lid."""
|
|
103
|
+
|
|
100
104
|
@abstractmethod
|
|
101
105
|
def is_fixed_trash(self) -> bool:
|
|
102
106
|
"""Whether the labware is a fixed trash."""
|
|
@@ -138,6 +138,11 @@ class LegacyLabwareCore(AbstractLabware[LegacyWellCore]):
|
|
|
138
138
|
def is_adapter(self) -> bool:
|
|
139
139
|
return False # Adapters were introduced in v2.15 and not supported in legacy protocols
|
|
140
140
|
|
|
141
|
+
def is_lid(self) -> bool:
|
|
142
|
+
return (
|
|
143
|
+
False # Lids were introduced in v2.21 and not supported in legacy protocols
|
|
144
|
+
)
|
|
145
|
+
|
|
141
146
|
def is_fixed_trash(self) -> bool:
|
|
142
147
|
"""Whether the labware is fixed trash."""
|
|
143
148
|
return "fixedTrash" in self.get_quirks()
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import List, Dict, Optional, Union, cast
|
|
5
5
|
|
|
6
|
+
from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated
|
|
7
|
+
|
|
6
8
|
from opentrons.protocol_engine.types import ABSMeasureMode
|
|
7
9
|
from opentrons_shared_data.labware.types import LabwareDefinition
|
|
8
10
|
from opentrons_shared_data.module.types import ModuleModel, ModuleType
|
|
@@ -159,7 +161,18 @@ class ModuleContext(CommandPublisher):
|
|
|
159
161
|
load_location = loaded_adapter._core
|
|
160
162
|
else:
|
|
161
163
|
load_location = self._core
|
|
164
|
+
|
|
162
165
|
name = validation.ensure_lowercase_name(name)
|
|
166
|
+
|
|
167
|
+
# todo(mm, 2024-11-08): This check belongs in opentrons.protocol_api.core.engine.deck_conflict.
|
|
168
|
+
# We're currently doing it here, at the ModuleContext level, for consistency with what
|
|
169
|
+
# ProtocolContext.load_labware() does. (It should also be moved to the deck_conflict module.)
|
|
170
|
+
if isinstance(self._core, AbsorbanceReaderCore):
|
|
171
|
+
if self._core.is_lid_on():
|
|
172
|
+
raise CommandPreconditionViolated(
|
|
173
|
+
f"Cannot load {name} onto the Absorbance Reader Module when its lid is closed."
|
|
174
|
+
)
|
|
175
|
+
|
|
163
176
|
labware_core = self._protocol_core.load_labware(
|
|
164
177
|
load_name=name,
|
|
165
178
|
label=label,
|
|
@@ -45,6 +45,7 @@ from opentrons.protocols.api_support.util import (
|
|
|
45
45
|
UnsupportedAPIError,
|
|
46
46
|
)
|
|
47
47
|
from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated
|
|
48
|
+
from opentrons.protocol_engine.errors import LabwareMovementNotAllowedError
|
|
48
49
|
|
|
49
50
|
from ._types import OffDeckType
|
|
50
51
|
from .core.common import ModuleCore, LabwareCore, ProtocolCore
|
|
@@ -668,7 +669,7 @@ class ProtocolContext(CommandPublisher):
|
|
|
668
669
|
self,
|
|
669
670
|
labware: Labware,
|
|
670
671
|
new_location: Union[
|
|
671
|
-
DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute
|
|
672
|
+
DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute, TrashBin
|
|
672
673
|
],
|
|
673
674
|
use_gripper: bool = False,
|
|
674
675
|
pick_up_offset: Optional[Mapping[str, float]] = None,
|
|
@@ -713,7 +714,8 @@ class ProtocolContext(CommandPublisher):
|
|
|
713
714
|
f"Expected labware of type 'Labware' but got {type(labware)}."
|
|
714
715
|
)
|
|
715
716
|
|
|
716
|
-
# Ensure that when moving to an absorbance reader
|
|
717
|
+
# Ensure that when moving to an absorbance reader that the lid is open
|
|
718
|
+
# todo(mm, 2024-11-08): Unify this with opentrons.protocol_api.core.engine.deck_conflict.
|
|
717
719
|
if isinstance(new_location, AbsorbanceReaderContext):
|
|
718
720
|
if new_location.is_lid_on():
|
|
719
721
|
raise CommandPreconditionViolated(
|
|
@@ -727,11 +729,19 @@ class ProtocolContext(CommandPublisher):
|
|
|
727
729
|
OffDeckType,
|
|
728
730
|
DeckSlotName,
|
|
729
731
|
StagingSlotName,
|
|
732
|
+
TrashBin,
|
|
730
733
|
]
|
|
731
734
|
if isinstance(new_location, (Labware, ModuleContext)):
|
|
732
735
|
location = new_location._core
|
|
733
736
|
elif isinstance(new_location, (OffDeckType, WasteChute)):
|
|
734
737
|
location = new_location
|
|
738
|
+
elif isinstance(new_location, TrashBin):
|
|
739
|
+
if labware._core.is_lid():
|
|
740
|
+
location = new_location
|
|
741
|
+
else:
|
|
742
|
+
raise LabwareMovementNotAllowedError(
|
|
743
|
+
"Can only dispose of tips and Lid-type labware in a Trash Bin. Did you mean to use a Waste Chute?"
|
|
744
|
+
)
|
|
735
745
|
else:
|
|
736
746
|
location = validation.ensure_and_convert_deck_slot(
|
|
737
747
|
new_location, self._api_version, self._core.robot_type
|
|
@@ -28,7 +28,6 @@ from .actions import (
|
|
|
28
28
|
DoorChangeAction,
|
|
29
29
|
ResetTipsAction,
|
|
30
30
|
SetPipetteMovementSpeedAction,
|
|
31
|
-
AddAbsorbanceReaderLidAction,
|
|
32
31
|
)
|
|
33
32
|
from .get_state_update import get_state_updates
|
|
34
33
|
|
|
@@ -58,7 +57,6 @@ __all__ = [
|
|
|
58
57
|
"DoorChangeAction",
|
|
59
58
|
"ResetTipsAction",
|
|
60
59
|
"SetPipetteMovementSpeedAction",
|
|
61
|
-
"AddAbsorbanceReaderLidAction",
|
|
62
60
|
# action payload values
|
|
63
61
|
"PauseSource",
|
|
64
62
|
"FinishErrorDetails",
|
|
@@ -271,17 +271,6 @@ class SetPipetteMovementSpeedAction:
|
|
|
271
271
|
speed: Optional[float]
|
|
272
272
|
|
|
273
273
|
|
|
274
|
-
@dataclasses.dataclass(frozen=True)
|
|
275
|
-
class AddAbsorbanceReaderLidAction:
|
|
276
|
-
"""Add the absorbance reader lid id to the absorbance reader module substate.
|
|
277
|
-
|
|
278
|
-
This action is dispatched the absorbance reader module is first loaded.
|
|
279
|
-
"""
|
|
280
|
-
|
|
281
|
-
module_id: str
|
|
282
|
-
lid_id: str
|
|
283
|
-
|
|
284
|
-
|
|
285
274
|
@dataclasses.dataclass(frozen=True)
|
|
286
275
|
class SetErrorRecoveryPolicyAction:
|
|
287
276
|
"""See `ProtocolEngine.set_error_recovery_policy()`."""
|
|
@@ -309,6 +298,5 @@ Action = Union[
|
|
|
309
298
|
AddLiquidAction,
|
|
310
299
|
ResetTipsAction,
|
|
311
300
|
SetPipetteMovementSpeedAction,
|
|
312
|
-
AddAbsorbanceReaderLidAction,
|
|
313
301
|
SetErrorRecoveryPolicyAction,
|
|
314
302
|
]
|
|
@@ -119,12 +119,6 @@ class SyncClient:
|
|
|
119
119
|
"add_addressable_area", addressable_area_name=addressable_area_name
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
-
def add_absorbance_reader_lid(self, module_id: str, lid_id: str) -> None:
|
|
123
|
-
"""Add an absorbance reader lid to the module state."""
|
|
124
|
-
self._transport.call_method(
|
|
125
|
-
"add_absorbance_reader_lid", module_id=module_id, lid_id=lid_id
|
|
126
|
-
)
|
|
127
|
-
|
|
128
122
|
def add_liquid(
|
|
129
123
|
self, name: str, color: Optional[str], description: Optional[str]
|
|
130
124
|
) -> Liquid:
|
|
@@ -10,7 +10,6 @@ from ...errors.error_occurrence import ErrorOccurrence
|
|
|
10
10
|
from ...errors import CannotPerformModuleAction
|
|
11
11
|
from opentrons.protocol_engine.types import AddressableAreaLocation
|
|
12
12
|
|
|
13
|
-
from opentrons.protocol_engine.resources import labware_validation
|
|
14
13
|
from ...state.update_types import StateUpdate
|
|
15
14
|
|
|
16
15
|
|
|
@@ -53,41 +52,35 @@ class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResul
|
|
|
53
52
|
|
|
54
53
|
async def execute(self, params: CloseLidParams) -> SuccessData[CloseLidResult]:
|
|
55
54
|
"""Execute the close lid command."""
|
|
55
|
+
state_update = StateUpdate()
|
|
56
56
|
mod_substate = self._state_view.modules.get_absorbance_reader_substate(
|
|
57
57
|
module_id=params.moduleId
|
|
58
58
|
)
|
|
59
59
|
|
|
60
|
-
# lid should currently be on the module
|
|
61
|
-
assert mod_substate.lid_id is not None
|
|
62
|
-
loaded_lid = self._state_view.labware.get(mod_substate.lid_id)
|
|
63
|
-
assert labware_validation.is_absorbance_reader_lid(loaded_lid.loadName)
|
|
64
|
-
|
|
65
60
|
hardware_lid_status = AbsorbanceReaderLidStatus.OFF
|
|
66
|
-
# If the lid is closed, if the lid is open No-op out
|
|
67
61
|
if not self._state_view.config.use_virtual_modules:
|
|
68
62
|
abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
69
63
|
|
|
70
64
|
if abs_reader is not None:
|
|
71
|
-
|
|
72
|
-
hardware_lid_status = result
|
|
65
|
+
hardware_lid_status = await abs_reader.get_current_lid_status()
|
|
73
66
|
else:
|
|
74
67
|
raise CannotPerformModuleAction(
|
|
75
68
|
"Could not reach the Hardware API for Opentrons Plate Reader Module."
|
|
76
69
|
)
|
|
77
70
|
|
|
78
|
-
# If the lid is already ON, no-op losing lid
|
|
79
71
|
if hardware_lid_status is AbsorbanceReaderLidStatus.ON:
|
|
80
|
-
# The lid is already
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
new_offset_id = self._equipment.find_applicable_labware_offset_id(
|
|
84
|
-
labware_definition_uri=loaded_lid.definitionUri,
|
|
85
|
-
labware_location=loaded_lid.location,
|
|
72
|
+
# The lid is already physically ON, so we can no-op physically closing it
|
|
73
|
+
state_update.set_absorbance_reader_lid(
|
|
74
|
+
module_id=mod_substate.module_id, is_lid_on=True
|
|
86
75
|
)
|
|
87
76
|
else:
|
|
88
77
|
# Allow propagation of ModuleNotAttachedError.
|
|
89
78
|
_ = self._equipment.get_module_hardware_api(mod_substate.module_id)
|
|
90
79
|
|
|
80
|
+
lid_definition = (
|
|
81
|
+
self._state_view.labware.get_absorbance_reader_lid_definition()
|
|
82
|
+
)
|
|
83
|
+
|
|
91
84
|
current_location = self._state_view.modules.absorbance_reader_dock_location(
|
|
92
85
|
params.moduleId
|
|
93
86
|
)
|
|
@@ -107,35 +100,29 @@ class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResul
|
|
|
107
100
|
)
|
|
108
101
|
)
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
# The lid's labware definition stores gripper offsets for itself in the
|
|
104
|
+
# space normally meant for offsets for labware stacked atop it.
|
|
105
|
+
lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets(
|
|
106
|
+
labware_definition=lid_definition,
|
|
107
|
+
slot_name=None,
|
|
112
108
|
)
|
|
113
109
|
if lid_gripper_offsets is None:
|
|
114
110
|
raise ValueError(
|
|
115
111
|
"Gripper Offset values for Absorbance Reader Lid labware must not be None."
|
|
116
112
|
)
|
|
117
113
|
|
|
118
|
-
# Skips gripper moves when using virtual gripper
|
|
119
114
|
await self._labware_movement.move_labware_with_gripper(
|
|
120
|
-
|
|
115
|
+
labware_definition=lid_definition,
|
|
121
116
|
current_location=current_location,
|
|
122
117
|
new_location=new_location,
|
|
123
118
|
user_offset_data=lid_gripper_offsets,
|
|
124
119
|
post_drop_slide_offset=None,
|
|
125
120
|
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
labware_location=new_location,
|
|
121
|
+
state_update.set_absorbance_reader_lid(
|
|
122
|
+
module_id=mod_substate.module_id,
|
|
123
|
+
is_lid_on=True,
|
|
130
124
|
)
|
|
131
125
|
|
|
132
|
-
state_update = StateUpdate()
|
|
133
|
-
state_update.set_labware_location(
|
|
134
|
-
labware_id=loaded_lid.id,
|
|
135
|
-
new_location=new_location,
|
|
136
|
-
new_offset_id=new_offset_id,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
126
|
return SuccessData(
|
|
140
127
|
public=CloseLidResult(),
|
|
141
128
|
state_update=state_update,
|
|
@@ -10,6 +10,7 @@ from opentrons.protocol_engine.types import ABSMeasureMode
|
|
|
10
10
|
|
|
11
11
|
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
12
12
|
from ...errors.error_occurrence import ErrorOccurrence
|
|
13
|
+
from ...errors import InvalidWavelengthError
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
from opentrons.protocol_engine.state.state import StateView
|
|
@@ -69,30 +70,41 @@ class InitializeImpl(
|
|
|
69
70
|
unsupported_wavelengths = sample_wavelengths.difference(
|
|
70
71
|
supported_wavelengths
|
|
71
72
|
)
|
|
73
|
+
sample_wl_str = ", ".join([str(w) + "nm" for w in sample_wavelengths])
|
|
74
|
+
supported_wl_str = ", ".join([str(w) + "nm" for w in supported_wavelengths])
|
|
75
|
+
unsupported_wl_str = ", ".join(
|
|
76
|
+
[str(w) + "nm" for w in unsupported_wavelengths]
|
|
77
|
+
)
|
|
72
78
|
if unsupported_wavelengths:
|
|
73
|
-
raise
|
|
79
|
+
raise InvalidWavelengthError(
|
|
80
|
+
f"Unsupported wavelengths: {unsupported_wl_str}. "
|
|
81
|
+
f" Use one of {supported_wl_str} instead."
|
|
82
|
+
)
|
|
74
83
|
|
|
75
84
|
if params.measureMode == "single":
|
|
76
85
|
if sample_wavelengths_len != 1:
|
|
77
86
|
raise ValueError(
|
|
78
|
-
f"single requires one sample wavelength,
|
|
87
|
+
f"Measure mode `single` requires one sample wavelength,"
|
|
88
|
+
f" {sample_wl_str} provided instead."
|
|
79
89
|
)
|
|
80
90
|
if (
|
|
81
91
|
reference_wavelength is not None
|
|
82
92
|
and reference_wavelength not in supported_wavelengths
|
|
83
93
|
):
|
|
84
|
-
raise
|
|
85
|
-
f"Reference wavelength {reference_wavelength} not supported
|
|
94
|
+
raise InvalidWavelengthError(
|
|
95
|
+
f"Reference wavelength {reference_wavelength}nm is not supported."
|
|
96
|
+
f" Use one of {supported_wl_str} instead."
|
|
86
97
|
)
|
|
87
98
|
|
|
88
99
|
if params.measureMode == "multi":
|
|
89
100
|
if sample_wavelengths_len < 1 or sample_wavelengths_len > 6:
|
|
90
101
|
raise ValueError(
|
|
91
|
-
f"multi requires 1-6 sample wavelengths,
|
|
102
|
+
f"Measure mode `multi` requires 1-6 sample wavelengths,"
|
|
103
|
+
f" {sample_wl_str} provided instead."
|
|
92
104
|
)
|
|
93
105
|
if reference_wavelength is not None:
|
|
94
|
-
raise
|
|
95
|
-
"Reference wavelength cannot be used with
|
|
106
|
+
raise ValueError(
|
|
107
|
+
"Reference wavelength cannot be used with Measure mode `multi`."
|
|
96
108
|
)
|
|
97
109
|
|
|
98
110
|
await abs_reader.set_sample_wavelength(
|