opentrons 8.2.0__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/drivers/absorbance_reader/async_byonoy.py +5 -6
- opentrons/hardware_control/backends/ot3utils.py +0 -1
- opentrons/hardware_control/modules/absorbance_reader.py +0 -2
- opentrons/hardware_control/ot3api.py +5 -5
- opentrons/hardware_control/protocols/position_estimator.py +1 -3
- opentrons/hardware_control/types.py +0 -2
- opentrons/legacy_commands/helpers.py +2 -8
- opentrons/protocol_api/core/engine/labware.py +2 -10
- opentrons/protocol_api/core/engine/module_core.py +1 -38
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +5 -12
- opentrons/protocol_api/core/engine/protocol.py +30 -5
- opentrons/protocol_api/core/labware.py +0 -4
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +0 -5
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +0 -1
- opentrons/protocol_api/core/protocol.py +0 -1
- opentrons/protocol_api/module_contexts.py +26 -69
- opentrons/protocol_api/protocol_context.py +2 -12
- opentrons/protocol_engine/actions/__init__.py +2 -0
- opentrons/protocol_engine/actions/actions.py +12 -0
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +31 -18
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +7 -19
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +29 -17
- opentrons/protocol_engine/commands/absorbance_reader/read.py +0 -4
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -3
- opentrons/protocol_engine/commands/command.py +1 -3
- opentrons/protocol_engine/commands/dispense_in_place.py +1 -1
- opentrons/protocol_engine/commands/drop_tip.py +1 -2
- opentrons/protocol_engine/commands/drop_tip_in_place.py +2 -7
- opentrons/protocol_engine/commands/load_labware.py +0 -9
- opentrons/protocol_engine/commands/load_module.py +39 -0
- opentrons/protocol_engine/commands/move_labware.py +4 -49
- opentrons/protocol_engine/commands/pick_up_tip.py +1 -1
- opentrons/protocol_engine/commands/pipetting_common.py +1 -8
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +35 -49
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -3
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -5
- opentrons/protocol_engine/create_protocol_engine.py +1 -18
- opentrons/protocol_engine/errors/__init__.py +0 -2
- opentrons/protocol_engine/errors/error_occurrence.py +3 -8
- opentrons/protocol_engine/errors/exceptions.py +0 -13
- opentrons/protocol_engine/execution/labware_movement.py +21 -69
- opentrons/protocol_engine/execution/movement.py +4 -9
- opentrons/protocol_engine/protocol_engine.py +7 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +39 -0
- opentrons/protocol_engine/resources/file_provider.py +7 -11
- opentrons/protocol_engine/resources/fixture_validation.py +1 -6
- opentrons/protocol_engine/state/geometry.py +49 -91
- opentrons/protocol_engine/state/labware.py +25 -102
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +1 -3
- opentrons/protocol_engine/state/modules.py +80 -53
- opentrons/protocol_engine/state/motion.py +5 -17
- opentrons/protocol_engine/state/update_types.py +0 -16
- opentrons/protocol_runner/run_orchestrator.py +0 -15
- opentrons/protocols/parameters/csv_parameter_interface.py +1 -3
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +4 -4
- {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +62 -62
- {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
- {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -23,9 +23,8 @@ from opentrons.hardware_control.modules.errors import AbsorbanceReaderDisconnect
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
SN_PARSER = re.compile(r'ATTRS{serial}=="(?P<serial>.+?)"')
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
SERIAL_PARSER = re.compile(r"(?P<serial>(OPT|BYO)[A-Z]{3}[0-9]+)")
|
|
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]{5})")
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class AsyncByonoy:
|
|
@@ -158,9 +157,9 @@ class AsyncByonoy:
|
|
|
158
157
|
)
|
|
159
158
|
self._raise_if_error(err.name, f"Error getting device information: {err}")
|
|
160
159
|
serial_match = SERIAL_PARSER.match(device_info.sn)
|
|
161
|
-
version_match = VERSION_PARSER.
|
|
162
|
-
serial = serial_match["serial"]
|
|
163
|
-
version = version_match["version"].lower() if version_match else "v0"
|
|
160
|
+
version_match = VERSION_PARSER.match(device_info.version)
|
|
161
|
+
serial = serial_match["serial"] if serial_match else "BYOMAA00000"
|
|
162
|
+
version = version_match["version"].lower() if version_match else "v0.0.0"
|
|
164
163
|
info = {
|
|
165
164
|
"serial": serial,
|
|
166
165
|
"version": version,
|
|
@@ -652,7 +652,6 @@ _gripper_jaw_state_lookup: Dict[FirmwareGripperjawState, GripperJawState] = {
|
|
|
652
652
|
FirmwareGripperjawState.force_controlling_home: GripperJawState.HOMED_READY,
|
|
653
653
|
FirmwareGripperjawState.force_controlling: GripperJawState.GRIPPING,
|
|
654
654
|
FirmwareGripperjawState.position_controlling: GripperJawState.HOLDING,
|
|
655
|
-
FirmwareGripperjawState.stopped: GripperJawState.STOPPED,
|
|
656
655
|
}
|
|
657
656
|
|
|
658
657
|
|
|
@@ -312,8 +312,6 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
312
312
|
log.debug(f"Updating {self.name}: {self.port} with {firmware_file_path}")
|
|
313
313
|
self._updating = True
|
|
314
314
|
success, res = await self._driver.update_firmware(firmware_file_path)
|
|
315
|
-
# it takes time for the plate reader to re-init after an update.
|
|
316
|
-
await asyncio.sleep(10)
|
|
317
315
|
self._device_info = await self._driver.get_device_info()
|
|
318
316
|
await self._poller.start()
|
|
319
317
|
self._updating = False
|
|
@@ -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]
|
|
780
778
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
779
|
+
if axes:
|
|
780
|
+
checked_axes = [ax for ax in axes if ax in Axis]
|
|
781
|
+
else:
|
|
782
|
+
checked_axes = [ax for ax in Axis]
|
|
783
|
+
await self._backend.update_motor_estimation(checked_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 poition 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,8 +19,6 @@ 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.
|
|
24
22
|
"""
|
|
25
23
|
...
|
|
26
24
|
|
|
@@ -625,8 +625,6 @@ class GripperJawState(enum.Enum):
|
|
|
625
625
|
#: the gripper is actively force-control gripping something
|
|
626
626
|
HOLDING = enum.auto()
|
|
627
627
|
#: the gripper is in position-control mode
|
|
628
|
-
STOPPED = enum.auto()
|
|
629
|
-
#: the gripper has been homed before but is stopped now
|
|
630
628
|
|
|
631
629
|
|
|
632
630
|
class InstrumentProbeType(enum.Enum):
|
|
@@ -49,9 +49,7 @@ def stringify_disposal_location(location: Union[TrashBin, WasteChute]) -> str:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def _stringify_labware_movement_location(
|
|
52
|
-
location: Union[
|
|
53
|
-
DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
|
|
54
|
-
]
|
|
52
|
+
location: Union[DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute]
|
|
55
53
|
) -> str:
|
|
56
54
|
if isinstance(location, (int, str)):
|
|
57
55
|
return f"slot {location}"
|
|
@@ -63,15 +61,11 @@ def _stringify_labware_movement_location(
|
|
|
63
61
|
return str(location)
|
|
64
62
|
elif isinstance(location, WasteChute):
|
|
65
63
|
return "Waste Chute"
|
|
66
|
-
elif isinstance(location, TrashBin):
|
|
67
|
-
return "Trash Bin " + location.location.name
|
|
68
64
|
|
|
69
65
|
|
|
70
66
|
def stringify_labware_movement_command(
|
|
71
67
|
source_labware: Labware,
|
|
72
|
-
destination: Union[
|
|
73
|
-
DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
|
|
74
|
-
],
|
|
68
|
+
destination: Union[DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute],
|
|
75
69
|
use_gripper: bool,
|
|
76
70
|
) -> str:
|
|
77
71
|
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
|
|
23
23
|
from opentrons.hardware_control.nozzle_manager import NozzleMap
|
|
24
24
|
|
|
25
25
|
|
|
@@ -139,10 +139,6 @@ 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
|
-
|
|
146
142
|
def is_fixed_trash(self) -> bool:
|
|
147
143
|
"""Whether the labware is a fixed trash."""
|
|
148
144
|
return self._engine_client.state.labware.is_fixed_trash(
|
|
@@ -190,13 +186,9 @@ class LabwareCore(AbstractLabware[WellCore]):
|
|
|
190
186
|
def get_deck_slot(self) -> Optional[DeckSlotName]:
|
|
191
187
|
"""Get the deck slot the labware is in, if on deck."""
|
|
192
188
|
try:
|
|
193
|
-
|
|
189
|
+
return self._engine_client.state.geometry.get_ancestor_slot_name(
|
|
194
190
|
self.labware_id
|
|
195
191
|
)
|
|
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
|
|
200
192
|
except (
|
|
201
193
|
LabwareNotOnDeckError,
|
|
202
194
|
ModuleNotOnDeckError,
|
|
@@ -41,11 +41,6 @@ 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
|
-
|
|
49
44
|
class ModuleCore(AbstractModuleCore):
|
|
50
45
|
"""Module core logic implementation for Python protocols.
|
|
51
46
|
Args:
|
|
@@ -586,39 +581,7 @@ class AbsorbanceReaderCore(ModuleCore, AbstractAbsorbanceReaderCore):
|
|
|
586
581
|
"Cannot perform Initialize action on Absorbance Reader without calling `.close_lid()` first."
|
|
587
582
|
)
|
|
588
583
|
|
|
589
|
-
|
|
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
|
-
|
|
584
|
+
# TODO: check that the wavelengths are within the supported wavelengths
|
|
622
585
|
self._engine_client.execute_command(
|
|
623
586
|
cmd.absorbance_reader.InitializeParams(
|
|
624
587
|
moduleId=self.module_id,
|
|
@@ -9,7 +9,6 @@ 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
|
|
13
12
|
from opentrons_shared_data.module import FLEX_TC_LID_COLLISION_ZONE
|
|
14
13
|
|
|
15
14
|
from opentrons.hardware_control import CriticalPoint
|
|
@@ -64,7 +63,7 @@ _FLEX_TC_LID_FRONT_RIGHT_PT = Point(
|
|
|
64
63
|
)
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
def check_safe_for_pipette_movement(
|
|
66
|
+
def check_safe_for_pipette_movement(
|
|
68
67
|
engine_state: StateView,
|
|
69
68
|
pipette_id: str,
|
|
70
69
|
labware_id: str,
|
|
@@ -122,12 +121,8 @@ def check_safe_for_pipette_movement( # noqa: C901
|
|
|
122
121
|
f"Requested motion with the {primary_nozzle} nozzle partial configuration"
|
|
123
122
|
f" is outside of robot bounds for the pipette."
|
|
124
123
|
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
raise LocationIsStagingSlotError(
|
|
128
|
-
"Cannot perform pipette actions on labware in Staging Area Slot."
|
|
129
|
-
)
|
|
130
|
-
labware_slot = ancestor
|
|
124
|
+
|
|
125
|
+
labware_slot = engine_state.geometry.get_ancestor_slot_name(labware_id)
|
|
131
126
|
|
|
132
127
|
surrounding_slots = adjacent_slots_getters.get_surrounding_slots(
|
|
133
128
|
slot=labware_slot.as_int(), robot_type=engine_state.config.robot_type
|
|
@@ -287,10 +282,8 @@ def check_safe_for_tip_pickup_and_return(
|
|
|
287
282
|
is_96_ch_tiprack_adapter = engine_state.labware.get_has_quirk(
|
|
288
283
|
labware_id=tiprack_parent.labwareId, quirk="tiprackAdapterFor96Channel"
|
|
289
284
|
)
|
|
290
|
-
tiprack_height = engine_state.labware.get_dimensions(labware_id
|
|
291
|
-
adapter_height = engine_state.labware.get_dimensions(
|
|
292
|
-
labware_id=tiprack_parent.labwareId
|
|
293
|
-
).z
|
|
285
|
+
tiprack_height = engine_state.labware.get_dimensions(labware_id).z
|
|
286
|
+
adapter_height = engine_state.labware.get_dimensions(tiprack_parent.labwareId).z
|
|
294
287
|
if is_partial_config and tiprack_height < adapter_height:
|
|
295
288
|
raise PartialTipMovementNotAllowedError(
|
|
296
289
|
f"{tiprack_name} cannot be on an adapter taller than the tip rack"
|
|
@@ -329,7 +329,6 @@ class ProtocolCore(
|
|
|
329
329
|
NonConnectedModuleCore,
|
|
330
330
|
OffDeckType,
|
|
331
331
|
WasteChute,
|
|
332
|
-
TrashBin,
|
|
333
332
|
],
|
|
334
333
|
use_gripper: bool,
|
|
335
334
|
pause_for_manual_move: bool,
|
|
@@ -448,10 +447,40 @@ class ProtocolCore(
|
|
|
448
447
|
existing_module_ids=list(self._module_cores_by_id.keys()),
|
|
449
448
|
)
|
|
450
449
|
|
|
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
|
+
|
|
451
457
|
self._module_cores_by_id[module_core.module_id] = module_core
|
|
452
458
|
|
|
453
459
|
return module_core
|
|
454
460
|
|
|
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
|
+
|
|
455
484
|
def _create_non_connected_module_core(
|
|
456
485
|
self, load_module_result: LoadModuleResult
|
|
457
486
|
) -> NonConnectedModuleCore:
|
|
@@ -778,7 +807,6 @@ class ProtocolCore(
|
|
|
778
807
|
NonConnectedModuleCore,
|
|
779
808
|
OffDeckType,
|
|
780
809
|
WasteChute,
|
|
781
|
-
TrashBin,
|
|
782
810
|
],
|
|
783
811
|
) -> LabwareLocation:
|
|
784
812
|
if isinstance(location, LabwareCore):
|
|
@@ -795,7 +823,6 @@ class ProtocolCore(
|
|
|
795
823
|
NonConnectedModuleCore,
|
|
796
824
|
OffDeckType,
|
|
797
825
|
WasteChute,
|
|
798
|
-
TrashBin,
|
|
799
826
|
]
|
|
800
827
|
) -> NonStackedLocation:
|
|
801
828
|
if isinstance(location, (ModuleCore, NonConnectedModuleCore)):
|
|
@@ -809,5 +836,3 @@ class ProtocolCore(
|
|
|
809
836
|
elif isinstance(location, WasteChute):
|
|
810
837
|
# TODO(mm, 2023-12-06) This will need to determine the appropriate Waste Chute to return, but only move_labware uses this for now
|
|
811
838
|
return AddressableAreaLocation(addressableAreaName="gripperWasteChute")
|
|
812
|
-
elif isinstance(location, TrashBin):
|
|
813
|
-
return AddressableAreaLocation(addressableAreaName=location.area_name)
|
|
@@ -97,10 +97,6 @@ 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
|
-
|
|
104
100
|
@abstractmethod
|
|
105
101
|
def is_fixed_trash(self) -> bool:
|
|
106
102
|
"""Whether the labware is a fixed trash."""
|
|
@@ -138,11 +138,6 @@ 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
|
-
|
|
146
141
|
def is_fixed_trash(self) -> bool:
|
|
147
142
|
"""Whether the labware is fixed trash."""
|
|
148
143
|
return "fixedTrash" in self.get_quirks()
|
|
@@ -3,8 +3,6 @@ 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
|
-
|
|
8
6
|
from opentrons.protocol_engine.types import ABSMeasureMode
|
|
9
7
|
from opentrons_shared_data.labware.types import LabwareDefinition
|
|
10
8
|
from opentrons_shared_data.module.types import ModuleModel, ModuleType
|
|
@@ -161,18 +159,7 @@ class ModuleContext(CommandPublisher):
|
|
|
161
159
|
load_location = loaded_adapter._core
|
|
162
160
|
else:
|
|
163
161
|
load_location = self._core
|
|
164
|
-
|
|
165
162
|
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
|
-
|
|
176
163
|
labware_core = self._protocol_core.load_labware(
|
|
177
164
|
load_name=name,
|
|
178
165
|
label=label,
|
|
@@ -581,7 +568,7 @@ class ThermocyclerContext(ModuleContext):
|
|
|
581
568
|
individual well of the loaded labware, in µL.
|
|
582
569
|
If not specified, the default is 25 µL.
|
|
583
570
|
|
|
584
|
-
.. note
|
|
571
|
+
.. note:
|
|
585
572
|
|
|
586
573
|
If ``hold_time_minutes`` and ``hold_time_seconds`` are not
|
|
587
574
|
specified, the Thermocycler will proceed to the next command
|
|
@@ -605,7 +592,7 @@ class ThermocyclerContext(ModuleContext):
|
|
|
605
592
|
:param temperature: A value between 37 and 110, representing the target
|
|
606
593
|
temperature in °C.
|
|
607
594
|
|
|
608
|
-
.. note
|
|
595
|
+
.. note:
|
|
609
596
|
|
|
610
597
|
The Thermocycler will proceed to the next command immediately after
|
|
611
598
|
``temperature`` has been reached.
|
|
@@ -635,13 +622,13 @@ class ThermocyclerContext(ModuleContext):
|
|
|
635
622
|
individual well of the loaded labware, in µL.
|
|
636
623
|
If not specified, the default is 25 µL.
|
|
637
624
|
|
|
638
|
-
.. note
|
|
625
|
+
.. note:
|
|
639
626
|
|
|
640
627
|
Unlike with :py:meth:`set_block_temperature`, either or both of
|
|
641
628
|
``hold_time_minutes`` and ``hold_time_seconds`` must be defined
|
|
642
629
|
and for each step.
|
|
643
630
|
|
|
644
|
-
.. note
|
|
631
|
+
.. note:
|
|
645
632
|
|
|
646
633
|
Before API Version 2.21, Thermocycler profiles run with this command
|
|
647
634
|
would be listed in the app as having a number of repetitions equal to
|
|
@@ -991,7 +978,7 @@ class MagneticBlockContext(ModuleContext):
|
|
|
991
978
|
|
|
992
979
|
|
|
993
980
|
class AbsorbanceReaderContext(ModuleContext):
|
|
994
|
-
"""An object representing a connected Absorbance
|
|
981
|
+
"""An object representing a connected Absorbance Reader Module.
|
|
995
982
|
|
|
996
983
|
It should not be instantiated directly; instead, it should be
|
|
997
984
|
created through :py:meth:`.ProtocolContext.load_module`.
|
|
@@ -1009,21 +996,17 @@ class AbsorbanceReaderContext(ModuleContext):
|
|
|
1009
996
|
|
|
1010
997
|
@requires_version(2, 21)
|
|
1011
998
|
def close_lid(self) -> None:
|
|
1012
|
-
"""
|
|
1013
|
-
|
|
1014
|
-
You must call this method before initializing the reader, even if the reader was
|
|
1015
|
-
in the closed position at the start of the protocol.
|
|
1016
|
-
"""
|
|
999
|
+
"""Close the lid of the Absorbance Reader."""
|
|
1017
1000
|
self._core.close_lid()
|
|
1018
1001
|
|
|
1019
1002
|
@requires_version(2, 21)
|
|
1020
1003
|
def open_lid(self) -> None:
|
|
1021
|
-
"""
|
|
1004
|
+
"""Open the lid of the Absorbance Reader."""
|
|
1022
1005
|
self._core.open_lid()
|
|
1023
1006
|
|
|
1024
1007
|
@requires_version(2, 21)
|
|
1025
1008
|
def is_lid_on(self) -> bool:
|
|
1026
|
-
"""Return ``True`` if the Absorbance
|
|
1009
|
+
"""Return ``True`` if the Absorbance Reader's lid is currently closed."""
|
|
1027
1010
|
return self._core.is_lid_on()
|
|
1028
1011
|
|
|
1029
1012
|
@requires_version(2, 21)
|
|
@@ -1033,28 +1016,19 @@ class AbsorbanceReaderContext(ModuleContext):
|
|
|
1033
1016
|
wavelengths: List[int],
|
|
1034
1017
|
reference_wavelength: Optional[int] = None,
|
|
1035
1018
|
) -> None:
|
|
1036
|
-
"""
|
|
1037
|
-
|
|
1038
|
-
See :ref:`absorbance-initialization` for examples.
|
|
1019
|
+
"""Take a zero reading on the Absorbance Plate Reader Module.
|
|
1039
1020
|
|
|
1040
1021
|
:param mode: Either ``"single"`` or ``"multi"``.
|
|
1041
1022
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
:param wavelengths: A list of wavelengths, in
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
between 350 and 1000.
|
|
1052
|
-
- The list must contain only one item when initializing a single measurement.
|
|
1053
|
-
- The list can contain one to six items when initializing a multiple measurement.
|
|
1054
|
-
:param reference_wavelength: An optional reference wavelength, in nm. If provided,
|
|
1055
|
-
:py:meth:`.AbsorbanceReaderContext.read` will read at the reference
|
|
1056
|
-
wavelength and then subtract the reference wavelength values from the
|
|
1057
|
-
measurement wavelength values. Can only be used with single measurements.
|
|
1023
|
+
- In single measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses
|
|
1024
|
+
one sample wavelength and an optional reference wavelength.
|
|
1025
|
+
- In multiple measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses
|
|
1026
|
+
a list of up to six sample wavelengths.
|
|
1027
|
+
:param wavelengths: A list of wavelengths, in mm, to measure.
|
|
1028
|
+
- Must contain only one item when initializing a single measurement.
|
|
1029
|
+
- Must contain one to six items when initializing a multiple measurement.
|
|
1030
|
+
:param reference_wavelength: An optional reference wavelength, in mm. Cannot be
|
|
1031
|
+
used with multiple measurements.
|
|
1058
1032
|
"""
|
|
1059
1033
|
self._core.initialize(
|
|
1060
1034
|
mode, wavelengths, reference_wavelength=reference_wavelength
|
|
@@ -1064,33 +1038,16 @@ class AbsorbanceReaderContext(ModuleContext):
|
|
|
1064
1038
|
def read(
|
|
1065
1039
|
self, export_filename: Optional[str] = None
|
|
1066
1040
|
) -> Dict[int, Dict[str, float]]:
|
|
1067
|
-
"""
|
|
1068
|
-
|
|
1069
|
-
This method always returns a dictionary of measurement data. It optionally will
|
|
1070
|
-
save a CSV file of the results to the Flex filesystem, which you can access from
|
|
1071
|
-
the Recent Protocol Runs screen in the Opentrons App. These files are `only` saved
|
|
1072
|
-
if you specify ``export_filename``.
|
|
1073
|
-
|
|
1074
|
-
In simulation, the values for each well key in the dictionary are set to zero, and
|
|
1075
|
-
no files are written.
|
|
1076
|
-
|
|
1077
|
-
.. note::
|
|
1078
|
-
|
|
1079
|
-
Avoid divide-by-zero errors when simulating and using the results of this
|
|
1080
|
-
method later in the protocol. If you divide by any of the measurement
|
|
1081
|
-
values, use :py:meth:`.ProtocolContext.is_simulating` to use alternate dummy
|
|
1082
|
-
data or skip the division step.
|
|
1041
|
+
"""Initiate read on the Absorbance Reader.
|
|
1083
1042
|
|
|
1084
|
-
|
|
1085
|
-
will write a CSV file for each measurement in the read operation. File
|
|
1086
|
-
names will use the value of this parameter, the measurement wavelength
|
|
1087
|
-
supplied in :py:meth:`~.AbsorbanceReaderContext.initialize`, and a
|
|
1088
|
-
``.csv`` extension. For example, when reading at wavelengths 450 and 562
|
|
1089
|
-
with ``export_filename="my_data"``, there will be two output files:
|
|
1090
|
-
``my_data_450.csv`` and ``my_data_562.csv``.
|
|
1043
|
+
Returns a dictionary of wavelengths to dictionary of values ordered by well name.
|
|
1091
1044
|
|
|
1092
|
-
|
|
1045
|
+
:param export_filename: Optional, if a filename is provided a CSV file will be saved
|
|
1046
|
+
as a result of the read action containing measurement data. The filename will
|
|
1047
|
+
be modified to include the wavelength used during measurement. If multiple
|
|
1048
|
+
measurements are taken, then a file will be generated for each wavelength provided.
|
|
1093
1049
|
|
|
1094
|
-
:
|
|
1050
|
+
Example: If `export_filename="my_data"` and wavelengths 450 and 531 are used during
|
|
1051
|
+
measurement, the output files will be "my_data_450.csv" and "my_data_531.csv".
|
|
1095
1052
|
"""
|
|
1096
1053
|
return self._core.read(filename=export_filename)
|
|
@@ -45,7 +45,6 @@ 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
|
|
49
48
|
|
|
50
49
|
from ._types import OffDeckType
|
|
51
50
|
from .core.common import ModuleCore, LabwareCore, ProtocolCore
|
|
@@ -669,7 +668,7 @@ class ProtocolContext(CommandPublisher):
|
|
|
669
668
|
self,
|
|
670
669
|
labware: Labware,
|
|
671
670
|
new_location: Union[
|
|
672
|
-
DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute
|
|
671
|
+
DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute
|
|
673
672
|
],
|
|
674
673
|
use_gripper: bool = False,
|
|
675
674
|
pick_up_offset: Optional[Mapping[str, float]] = None,
|
|
@@ -714,8 +713,7 @@ class ProtocolContext(CommandPublisher):
|
|
|
714
713
|
f"Expected labware of type 'Labware' but got {type(labware)}."
|
|
715
714
|
)
|
|
716
715
|
|
|
717
|
-
# Ensure that when moving to an absorbance reader
|
|
718
|
-
# todo(mm, 2024-11-08): Unify this with opentrons.protocol_api.core.engine.deck_conflict.
|
|
716
|
+
# Ensure that when moving to an absorbance reader than the lid is open
|
|
719
717
|
if isinstance(new_location, AbsorbanceReaderContext):
|
|
720
718
|
if new_location.is_lid_on():
|
|
721
719
|
raise CommandPreconditionViolated(
|
|
@@ -729,19 +727,11 @@ class ProtocolContext(CommandPublisher):
|
|
|
729
727
|
OffDeckType,
|
|
730
728
|
DeckSlotName,
|
|
731
729
|
StagingSlotName,
|
|
732
|
-
TrashBin,
|
|
733
730
|
]
|
|
734
731
|
if isinstance(new_location, (Labware, ModuleContext)):
|
|
735
732
|
location = new_location._core
|
|
736
733
|
elif isinstance(new_location, (OffDeckType, WasteChute)):
|
|
737
734
|
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
|
-
)
|
|
745
735
|
else:
|
|
746
736
|
location = validation.ensure_and_convert_deck_slot(
|
|
747
737
|
new_location, self._api_version, self._core.robot_type
|
|
@@ -28,6 +28,7 @@ from .actions import (
|
|
|
28
28
|
DoorChangeAction,
|
|
29
29
|
ResetTipsAction,
|
|
30
30
|
SetPipetteMovementSpeedAction,
|
|
31
|
+
AddAbsorbanceReaderLidAction,
|
|
31
32
|
)
|
|
32
33
|
from .get_state_update import get_state_updates
|
|
33
34
|
|
|
@@ -57,6 +58,7 @@ __all__ = [
|
|
|
57
58
|
"DoorChangeAction",
|
|
58
59
|
"ResetTipsAction",
|
|
59
60
|
"SetPipetteMovementSpeedAction",
|
|
61
|
+
"AddAbsorbanceReaderLidAction",
|
|
60
62
|
# action payload values
|
|
61
63
|
"PauseSource",
|
|
62
64
|
"FinishErrorDetails",
|
|
@@ -271,6 +271,17 @@ 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
|
+
|
|
274
285
|
@dataclasses.dataclass(frozen=True)
|
|
275
286
|
class SetErrorRecoveryPolicyAction:
|
|
276
287
|
"""See `ProtocolEngine.set_error_recovery_policy()`."""
|
|
@@ -298,5 +309,6 @@ Action = Union[
|
|
|
298
309
|
AddLiquidAction,
|
|
299
310
|
ResetTipsAction,
|
|
300
311
|
SetPipetteMovementSpeedAction,
|
|
312
|
+
AddAbsorbanceReaderLidAction,
|
|
301
313
|
SetErrorRecoveryPolicyAction,
|
|
302
314
|
]
|
|
@@ -119,6 +119,12 @@ 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
|
+
|
|
122
128
|
def add_liquid(
|
|
123
129
|
self, name: str, color: Optional[str], description: Optional[str]
|
|
124
130
|
) -> Liquid:
|