opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.0a1__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/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +19 -3
- opentrons/legacy_commands/helpers.py +15 -0
- opentrons/legacy_commands/types.py +3 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1233 -65
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
- opentrons/protocol_api/core/engine/well.py +60 -5
- opentrons/protocol_api/core/instrument.py +65 -19
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +21 -1
- opentrons/protocol_api/instrument_context.py +246 -123
- opentrons/protocol_api/labware.py +75 -11
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +156 -16
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +30 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +69 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
- opentrons/protocol_engine/commands/liquid_probe.py +21 -12
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +154 -7
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +233 -26
- opentrons/protocol_engine/execution/tip_handler.py +14 -5
- opentrons/protocol_engine/labware_offset_standardization.py +173 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +26 -10
- opentrons/protocol_engine/state/geometry.py +683 -100
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +178 -52
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +54 -8
- opentrons/protocol_engine/types/__init__.py +292 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +110 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +108 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
- opentrons/protocol_engine/types/location.py +193 -0
- opentrons/protocol_engine/types/module.py +269 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +107 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +5 -6
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +15 -6
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Basic labware data state and store."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from dataclasses import dataclass
|
|
@@ -11,23 +12,25 @@ from typing import (
|
|
|
11
12
|
Sequence,
|
|
12
13
|
Tuple,
|
|
13
14
|
NamedTuple,
|
|
14
|
-
cast,
|
|
15
15
|
Union,
|
|
16
16
|
overload,
|
|
17
17
|
)
|
|
18
|
+
from typing_extensions import assert_never
|
|
18
19
|
|
|
19
20
|
from opentrons.protocol_engine.state import update_types
|
|
20
21
|
from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
21
22
|
from opentrons_shared_data.gripper.constants import LABWARE_GRIP_FORCE
|
|
22
23
|
from opentrons_shared_data.labware.labware_definition import (
|
|
23
|
-
LabwareRole,
|
|
24
24
|
InnerWellGeometry,
|
|
25
|
+
LabwareDefinition,
|
|
26
|
+
LabwareRole,
|
|
27
|
+
WellDefinition2,
|
|
28
|
+
WellDefinition3,
|
|
25
29
|
)
|
|
26
30
|
from opentrons_shared_data.pipette.types import LabwareUri
|
|
27
31
|
|
|
28
32
|
from opentrons.types import DeckSlotName, StagingSlotName, MountType
|
|
29
33
|
from opentrons.protocols.api_support.constants import OPENTRONS_NAMESPACE
|
|
30
|
-
from opentrons.protocols.models import LabwareDefinition, WellDefinition
|
|
31
34
|
from opentrons.calibration_storage.helpers import uri_from_details
|
|
32
35
|
|
|
33
36
|
from .. import errors
|
|
@@ -40,7 +43,9 @@ from ..types import (
|
|
|
40
43
|
Dimensions,
|
|
41
44
|
LabwareOffset,
|
|
42
45
|
LabwareOffsetVector,
|
|
43
|
-
|
|
46
|
+
LabwareOffsetLocationSequence,
|
|
47
|
+
LegacyLabwareOffsetLocation,
|
|
48
|
+
InStackerHopperLocation,
|
|
44
49
|
LabwareLocation,
|
|
45
50
|
LoadedLabware,
|
|
46
51
|
ModuleLocation,
|
|
@@ -49,6 +54,7 @@ from ..types import (
|
|
|
49
54
|
LabwareMovementOffsetData,
|
|
50
55
|
OnDeckLabwareLocation,
|
|
51
56
|
OFF_DECK_LOCATION,
|
|
57
|
+
SYSTEM_LOCATION,
|
|
52
58
|
)
|
|
53
59
|
from ..actions import (
|
|
54
60
|
Action,
|
|
@@ -86,6 +92,9 @@ _RIGHT_SIDE_SLOTS = {
|
|
|
86
92
|
_PLATE_READER_MAX_LABWARE_Z_MM = 16
|
|
87
93
|
|
|
88
94
|
|
|
95
|
+
_WellDefinition = WellDefinition2 | WellDefinition3
|
|
96
|
+
|
|
97
|
+
|
|
89
98
|
class LabwareLoadParams(NamedTuple):
|
|
90
99
|
"""Parameters required to load a labware in Protocol Engine."""
|
|
91
100
|
|
|
@@ -156,8 +165,10 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
156
165
|
"""Modify state in reaction to an action."""
|
|
157
166
|
for state_update in get_state_updates(action):
|
|
158
167
|
self._add_loaded_labware(state_update)
|
|
168
|
+
self._add_batch_loaded_labwares(state_update)
|
|
159
169
|
self._add_loaded_lid_stack(state_update)
|
|
160
170
|
self._set_labware_location(state_update)
|
|
171
|
+
self._set_batch_labware_location(state_update)
|
|
161
172
|
self._set_labware_lid(state_update)
|
|
162
173
|
|
|
163
174
|
if isinstance(action, AddLabwareOffsetAction):
|
|
@@ -165,7 +176,8 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
165
176
|
id=action.labware_offset_id,
|
|
166
177
|
createdAt=action.created_at,
|
|
167
178
|
definitionUri=action.request.definitionUri,
|
|
168
|
-
location=action.request.
|
|
179
|
+
location=action.request.legacyLocation,
|
|
180
|
+
locationSequence=action.request.locationSequence,
|
|
169
181
|
vector=action.request.vector,
|
|
170
182
|
)
|
|
171
183
|
self._add_labware_offset(labware_offset)
|
|
@@ -223,6 +235,49 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
223
235
|
displayName=display_name,
|
|
224
236
|
)
|
|
225
237
|
|
|
238
|
+
def _add_batch_loaded_labwares(
|
|
239
|
+
self, state_update: update_types.StateUpdate
|
|
240
|
+
) -> None:
|
|
241
|
+
batch_loaded_labware_update = state_update.batch_loaded_labware
|
|
242
|
+
if batch_loaded_labware_update == update_types.NO_CHANGE:
|
|
243
|
+
return
|
|
244
|
+
# If the labware load refers to an offset, that offset must actually exist.
|
|
245
|
+
for labware_id in batch_loaded_labware_update.new_locations_by_id:
|
|
246
|
+
if batch_loaded_labware_update.offset_ids_by_id[labware_id] is not None:
|
|
247
|
+
assert (
|
|
248
|
+
batch_loaded_labware_update.offset_ids_by_id[labware_id]
|
|
249
|
+
in self._state.labware_offsets_by_id
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
definition_uri = uri_from_details(
|
|
253
|
+
namespace=batch_loaded_labware_update.definitions_by_id[
|
|
254
|
+
labware_id
|
|
255
|
+
].namespace,
|
|
256
|
+
load_name=batch_loaded_labware_update.definitions_by_id[
|
|
257
|
+
labware_id
|
|
258
|
+
].parameters.loadName,
|
|
259
|
+
version=batch_loaded_labware_update.definitions_by_id[
|
|
260
|
+
labware_id
|
|
261
|
+
].version,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
self._state.definitions_by_uri[
|
|
265
|
+
definition_uri
|
|
266
|
+
] = batch_loaded_labware_update.definitions_by_id[labware_id]
|
|
267
|
+
|
|
268
|
+
location = batch_loaded_labware_update.new_locations_by_id[labware_id]
|
|
269
|
+
|
|
270
|
+
self._state.labware_by_id[labware_id] = LoadedLabware.model_construct(
|
|
271
|
+
id=labware_id,
|
|
272
|
+
location=location,
|
|
273
|
+
loadName=batch_loaded_labware_update.definitions_by_id[
|
|
274
|
+
labware_id
|
|
275
|
+
].parameters.loadName,
|
|
276
|
+
definitionUri=definition_uri,
|
|
277
|
+
offsetId=batch_loaded_labware_update.offset_ids_by_id[labware_id],
|
|
278
|
+
displayName=batch_loaded_labware_update.display_names_by_id[labware_id],
|
|
279
|
+
)
|
|
280
|
+
|
|
226
281
|
def _add_loaded_lid_stack(self, state_update: update_types.StateUpdate) -> None:
|
|
227
282
|
loaded_lid_stack_update = state_update.loaded_lid_stack
|
|
228
283
|
if loaded_lid_stack_update != update_types.NO_CHANGE:
|
|
@@ -247,7 +302,11 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
247
302
|
)
|
|
248
303
|
|
|
249
304
|
# Add the Lids on top of the stack object
|
|
250
|
-
for
|
|
305
|
+
for labware_id in loaded_lid_stack_update.new_locations_by_id:
|
|
306
|
+
if loaded_lid_stack_update.definition is None:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
"Lid Stack Labware Definition cannot be None when multiple lids are loaded."
|
|
309
|
+
)
|
|
251
310
|
definition_uri = uri_from_details(
|
|
252
311
|
namespace=loaded_lid_stack_update.definition.namespace,
|
|
253
312
|
load_name=loaded_lid_stack_update.definition.parameters.loadName,
|
|
@@ -258,14 +317,10 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
258
317
|
definition_uri
|
|
259
318
|
] = loaded_lid_stack_update.definition
|
|
260
319
|
|
|
261
|
-
location = loaded_lid_stack_update.new_locations_by_id[
|
|
262
|
-
loaded_lid_stack_update.labware_ids[i]
|
|
263
|
-
]
|
|
320
|
+
location = loaded_lid_stack_update.new_locations_by_id[labware_id]
|
|
264
321
|
|
|
265
|
-
self._state.labware_by_id[
|
|
266
|
-
|
|
267
|
-
] = LoadedLabware.construct(
|
|
268
|
-
id=loaded_lid_stack_update.labware_ids[i],
|
|
322
|
+
self._state.labware_by_id[labware_id] = LoadedLabware.construct(
|
|
323
|
+
id=labware_id,
|
|
269
324
|
location=location,
|
|
270
325
|
loadName=loaded_lid_stack_update.definition.parameters.loadName,
|
|
271
326
|
definitionUri=definition_uri,
|
|
@@ -276,31 +331,51 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
276
331
|
def _set_labware_lid(self, state_update: update_types.StateUpdate) -> None:
|
|
277
332
|
labware_lid_update = state_update.labware_lid
|
|
278
333
|
if labware_lid_update != update_types.NO_CHANGE:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
334
|
+
parent_labware_ids = labware_lid_update.parent_labware_ids
|
|
335
|
+
for i in range(len(parent_labware_ids)):
|
|
336
|
+
lid_id = labware_lid_update.lid_ids[i]
|
|
337
|
+
self._state.labware_by_id[parent_labware_ids[i]].lid_id = lid_id
|
|
282
338
|
|
|
283
|
-
def
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
new_offset_id = labware_location_update.offset_id
|
|
339
|
+
def _do_update_labware_location(
|
|
340
|
+
self, labware_id: str, new_location: LabwareLocation, new_offset_id: str | None
|
|
341
|
+
) -> None:
|
|
342
|
+
self._state.labware_by_id[labware_id].offsetId = new_offset_id
|
|
288
343
|
|
|
289
|
-
|
|
344
|
+
if isinstance(new_location, AddressableAreaLocation) and (
|
|
345
|
+
fixture_validation.is_gripper_waste_chute(new_location.addressableAreaName)
|
|
346
|
+
or fixture_validation.is_trash(new_location.addressableAreaName)
|
|
347
|
+
):
|
|
348
|
+
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
|
|
349
|
+
new_location = OFF_DECK_LOCATION
|
|
290
350
|
|
|
291
|
-
|
|
292
|
-
new_location = labware_location_update.new_location
|
|
351
|
+
self._state.labware_by_id[labware_id].location = new_location
|
|
293
352
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
or fixture_validation.is_trash(new_location.addressableAreaName)
|
|
299
|
-
):
|
|
300
|
-
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
|
|
301
|
-
new_location = OFF_DECK_LOCATION
|
|
353
|
+
def _set_labware_location(self, state_update: update_types.StateUpdate) -> None:
|
|
354
|
+
labware_location_update = state_update.labware_location
|
|
355
|
+
if labware_location_update == update_types.NO_CHANGE:
|
|
356
|
+
return
|
|
302
357
|
|
|
303
|
-
|
|
358
|
+
self._do_update_labware_location(
|
|
359
|
+
labware_location_update.labware_id,
|
|
360
|
+
labware_location_update.new_location,
|
|
361
|
+
labware_location_update.offset_id,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def _set_batch_labware_location(
|
|
365
|
+
self, state_update: update_types.StateUpdate
|
|
366
|
+
) -> None:
|
|
367
|
+
batch_location_update = state_update.batch_labware_location
|
|
368
|
+
if batch_location_update == update_types.NO_CHANGE:
|
|
369
|
+
return
|
|
370
|
+
for (
|
|
371
|
+
labware_id,
|
|
372
|
+
new_location,
|
|
373
|
+
) in batch_location_update.new_locations_by_id.items():
|
|
374
|
+
self._do_update_labware_location(
|
|
375
|
+
labware_id,
|
|
376
|
+
new_location,
|
|
377
|
+
batch_location_update.new_offset_ids_by_id.get(labware_id, None),
|
|
378
|
+
)
|
|
304
379
|
|
|
305
380
|
|
|
306
381
|
class LabwareView:
|
|
@@ -350,6 +425,19 @@ class LabwareView:
|
|
|
350
425
|
f"There is not labware loaded onto labware {labware_id}"
|
|
351
426
|
)
|
|
352
427
|
|
|
428
|
+
def raise_if_labware_has_non_lid_labware_on_top(self, labware_id: str) -> None:
|
|
429
|
+
"""Raise if labware has another labware that is not its lid on top."""
|
|
430
|
+
lid_id = self.get_lid_id_by_labware_id(labware_id)
|
|
431
|
+
for candidate_id, candidate_labware in self._state.labware_by_id.items():
|
|
432
|
+
if (
|
|
433
|
+
isinstance(candidate_labware.location, OnLabwareLocation)
|
|
434
|
+
and candidate_labware.location.labwareId == labware_id
|
|
435
|
+
and candidate_id != lid_id
|
|
436
|
+
):
|
|
437
|
+
raise errors.LabwareIsInStackError(
|
|
438
|
+
f"Cannot access labware {labware_id} because it has a non-lid labware stacked on top."
|
|
439
|
+
)
|
|
440
|
+
|
|
353
441
|
def raise_if_labware_has_labware_on_top(self, labware_id: str) -> None:
|
|
354
442
|
"""Raise if labware has another labware on top."""
|
|
355
443
|
for labware in self._state.labware_by_id.values():
|
|
@@ -358,7 +446,7 @@ class LabwareView:
|
|
|
358
446
|
and labware.location.labwareId == labware_id
|
|
359
447
|
):
|
|
360
448
|
raise errors.LabwareIsInStackError(
|
|
361
|
-
f"Cannot
|
|
449
|
+
f"Cannot access labware {labware_id} because it has another labware stacked on top."
|
|
362
450
|
)
|
|
363
451
|
|
|
364
452
|
def get_by_slot(
|
|
@@ -459,8 +547,42 @@ class LabwareView:
|
|
|
459
547
|
parent = self.get_location(labware_id)
|
|
460
548
|
if isinstance(parent, OnLabwareLocation):
|
|
461
549
|
return self.get_parent_location(parent.labwareId)
|
|
550
|
+
elif isinstance(parent, InStackerHopperLocation):
|
|
551
|
+
# TODO: This function really wants to return something like an "EventuallyOnDeckLocation"
|
|
552
|
+
# and either raise or return None for labware that isn't traceable to a place on the robot
|
|
553
|
+
# deck (i.e. not in a stacker hopper, not off-deck, not in system). We don't really have
|
|
554
|
+
# that concept yet but should add it soon. In the meantime, other checks should prevent
|
|
555
|
+
# this being called in those cases.
|
|
556
|
+
return ModuleLocation(moduleId=parent.moduleId)
|
|
462
557
|
return parent
|
|
463
558
|
|
|
559
|
+
def get_highest_child_labware(self, labware_id: str) -> str:
|
|
560
|
+
"""Get labware's highest child labware returning the labware ID."""
|
|
561
|
+
if (child_id := self.get_next_child_labware(labware_id)) is not None:
|
|
562
|
+
return self.get_highest_child_labware(labware_id=child_id)
|
|
563
|
+
return labware_id
|
|
564
|
+
|
|
565
|
+
def get_next_child_labware(self, labware_id: str) -> str | None:
|
|
566
|
+
"""Get the labware that is on this labware, if any.
|
|
567
|
+
|
|
568
|
+
This includes lids.
|
|
569
|
+
"""
|
|
570
|
+
for labware in self._state.labware_by_id.values():
|
|
571
|
+
if (
|
|
572
|
+
isinstance(labware.location, OnLabwareLocation)
|
|
573
|
+
and labware.location.labwareId == labware_id
|
|
574
|
+
):
|
|
575
|
+
return labware.id
|
|
576
|
+
return None
|
|
577
|
+
|
|
578
|
+
def get_labware_stack_from_parent(self, labware_id: str) -> list[str]:
|
|
579
|
+
"""Get the stack of labware starting from the specified labware ID and moving up."""
|
|
580
|
+
labware_ids = [labware_id]
|
|
581
|
+
while (next_id := self.get_next_child_labware(labware_id)) is not None:
|
|
582
|
+
labware_ids.append(next_id)
|
|
583
|
+
labware_id = next_id
|
|
584
|
+
return labware_ids
|
|
585
|
+
|
|
464
586
|
def get_labware_stack(
|
|
465
587
|
self, labware_stack: List[LoadedLabware]
|
|
466
588
|
) -> List[LoadedLabware]:
|
|
@@ -471,6 +593,26 @@ class LabwareView:
|
|
|
471
593
|
return self.get_labware_stack(labware_stack)
|
|
472
594
|
return labware_stack
|
|
473
595
|
|
|
596
|
+
def get_lid_id_by_labware_id(self, labware_id: str) -> str | None:
|
|
597
|
+
"""Get the ID of a lid labware on top of a given labware, if any."""
|
|
598
|
+
return self._state.labware_by_id[labware_id].lid_id
|
|
599
|
+
|
|
600
|
+
def get_lid_by_labware_id(self, labware_id: str) -> LoadedLabware | None:
|
|
601
|
+
"""Get the Lid Labware that is currently on top of a given labware, if there is one."""
|
|
602
|
+
lid_id = self.get_lid_id_by_labware_id(labware_id)
|
|
603
|
+
if lid_id:
|
|
604
|
+
return self._state.labware_by_id[lid_id]
|
|
605
|
+
else:
|
|
606
|
+
return None
|
|
607
|
+
|
|
608
|
+
def get_labware_by_lid_id(self, lid_id: str) -> LoadedLabware | None:
|
|
609
|
+
"""Get the labware that is currently covered by a given lid, if there is one."""
|
|
610
|
+
loaded_labware = list(self._state.labware_by_id.values())
|
|
611
|
+
for labware in loaded_labware:
|
|
612
|
+
if labware.lid_id == lid_id:
|
|
613
|
+
return labware
|
|
614
|
+
return None
|
|
615
|
+
|
|
474
616
|
def get_all(self) -> List[LoadedLabware]:
|
|
475
617
|
"""Get a list of all labware entries in state."""
|
|
476
618
|
return list(self._state.labware_by_id.values())
|
|
@@ -517,7 +659,7 @@ class LabwareView:
|
|
|
517
659
|
self,
|
|
518
660
|
labware_id: str,
|
|
519
661
|
well_name: Optional[str] = None,
|
|
520
|
-
) ->
|
|
662
|
+
) -> WellDefinition2 | WellDefinition3:
|
|
521
663
|
"""Get a well's definition by labware and well name.
|
|
522
664
|
|
|
523
665
|
If `well_name` is omitted, the first well in the labware
|
|
@@ -544,16 +686,16 @@ class LabwareView:
|
|
|
544
686
|
message=f"No innerLabwareGeometry found in labware definition for labware_id: {labware_id}."
|
|
545
687
|
)
|
|
546
688
|
well_def = self.get_well_definition(labware_id, well_name)
|
|
547
|
-
|
|
548
|
-
if
|
|
689
|
+
geometry_id = well_def.geometryDefinitionId
|
|
690
|
+
if geometry_id is None:
|
|
549
691
|
raise errors.IncompleteWellDefinitionError(
|
|
550
692
|
message=f"No geometryDefinitionId found in well definition for well: {well_name} in labware_id: {labware_id}"
|
|
551
693
|
)
|
|
552
694
|
else:
|
|
553
|
-
well_geometry = labware_def.innerLabwareGeometry.get(
|
|
695
|
+
well_geometry = labware_def.innerLabwareGeometry.get(geometry_id)
|
|
554
696
|
if well_geometry is None:
|
|
555
697
|
raise errors.IncompleteLabwareDefinitionError(
|
|
556
|
-
message=f"No innerLabwareGeometry found in labware definition for well_id: {
|
|
698
|
+
message=f"No innerLabwareGeometry found in labware definition for well_id: {geometry_id} in labware_id: {labware_id}"
|
|
557
699
|
)
|
|
558
700
|
return well_geometry
|
|
559
701
|
|
|
@@ -572,12 +714,13 @@ class LabwareView:
|
|
|
572
714
|
"""
|
|
573
715
|
well_definition = self.get_well_definition(labware_id, well_name)
|
|
574
716
|
|
|
575
|
-
if well_definition.
|
|
717
|
+
if well_definition.shape == "circular":
|
|
576
718
|
x_size = y_size = well_definition.diameter
|
|
719
|
+
elif well_definition.shape == "rectangular":
|
|
720
|
+
x_size = well_definition.xDimension
|
|
721
|
+
y_size = well_definition.yDimension
|
|
577
722
|
else:
|
|
578
|
-
|
|
579
|
-
x_size = cast(float, well_definition.xDimension)
|
|
580
|
-
y_size = cast(float, well_definition.yDimension)
|
|
723
|
+
assert_never(well_definition.shape)
|
|
581
724
|
|
|
582
725
|
return x_size, y_size, well_definition.depth
|
|
583
726
|
|
|
@@ -796,15 +939,32 @@ class LabwareView:
|
|
|
796
939
|
"""Get all labware offsets, in the order they were added."""
|
|
797
940
|
return list(self._state.labware_offsets_by_id.values())
|
|
798
941
|
|
|
799
|
-
# TODO: Make this slightly more ergonomic for the caller by
|
|
800
|
-
# only returning the optional str ID, at the cost of baking redundant lookups
|
|
801
|
-
# into the API?
|
|
802
942
|
def find_applicable_labware_offset(
|
|
943
|
+
self, definition_uri: str, location: LabwareOffsetLocationSequence
|
|
944
|
+
) -> Optional[LabwareOffset]:
|
|
945
|
+
"""Find a labware offset that applies to the given definition and location sequence.
|
|
946
|
+
|
|
947
|
+
Returns the *most recently* added matching offset, so later ones can override earlier ones.
|
|
948
|
+
Returns ``None`` if no loaded offset matches the location.
|
|
949
|
+
|
|
950
|
+
An offset matches a labware instance if the sequence of locations formed by following the
|
|
951
|
+
.location elements of the labware instance until you reach an addressable area has the same
|
|
952
|
+
definition URIs as the sequence of definition URIs stored by the offset.
|
|
953
|
+
"""
|
|
954
|
+
for candidate in reversed(list(self._state.labware_offsets_by_id.values())):
|
|
955
|
+
if (
|
|
956
|
+
candidate.definitionUri == definition_uri
|
|
957
|
+
and candidate.locationSequence == location
|
|
958
|
+
):
|
|
959
|
+
return candidate
|
|
960
|
+
return None
|
|
961
|
+
|
|
962
|
+
def find_applicable_labware_offset_by_legacy_location(
|
|
803
963
|
self,
|
|
804
964
|
definition_uri: str,
|
|
805
|
-
location:
|
|
965
|
+
location: LegacyLabwareOffsetLocation,
|
|
806
966
|
) -> Optional[LabwareOffset]:
|
|
807
|
-
"""Find a labware offset that applies to the given definition and location.
|
|
967
|
+
"""Find a labware offset that applies to the given definition and legacy location.
|
|
808
968
|
|
|
809
969
|
Returns the *most recently* added matching offset,
|
|
810
970
|
so later offsets can override earlier ones.
|
|
@@ -870,7 +1030,9 @@ class LabwareView:
|
|
|
870
1030
|
f"Cannot move pipette to {labware.loadName},"
|
|
871
1031
|
f" labware is on staging slot {labware_location.addressableAreaName}"
|
|
872
1032
|
)
|
|
873
|
-
elif
|
|
1033
|
+
elif (
|
|
1034
|
+
labware_location == OFF_DECK_LOCATION or labware_location == SYSTEM_LOCATION
|
|
1035
|
+
):
|
|
874
1036
|
raise errors.LocationNotAccessibleByPipetteError(
|
|
875
1037
|
f"Cannot move pipette to {labware.loadName}, labware is off-deck."
|
|
876
1038
|
)
|
|
@@ -917,6 +1079,41 @@ class LabwareView:
|
|
|
917
1079
|
f" maximum allowed labware height is {_PLATE_READER_MAX_LABWARE_Z_MM}mm."
|
|
918
1080
|
)
|
|
919
1081
|
|
|
1082
|
+
def raise_if_stacker_labware_pool_is_not_valid(
|
|
1083
|
+
self,
|
|
1084
|
+
primary_labware_definition: LabwareDefinition,
|
|
1085
|
+
lid_labware_definition: LabwareDefinition | None,
|
|
1086
|
+
adapter_labware_definition: LabwareDefinition | None,
|
|
1087
|
+
) -> None:
|
|
1088
|
+
"""Raise if the primary, lid, and adapter do not go together."""
|
|
1089
|
+
if lid_labware_definition:
|
|
1090
|
+
if not labware_validation.validate_definition_is_lid(
|
|
1091
|
+
lid_labware_definition
|
|
1092
|
+
):
|
|
1093
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1094
|
+
f"Labware {lid_labware_definition.parameters.loadName} cannot be used as a lid in the Flex Stacker."
|
|
1095
|
+
)
|
|
1096
|
+
if not labware_validation.validate_labware_can_be_stacked(
|
|
1097
|
+
lid_labware_definition, primary_labware_definition.parameters.loadName
|
|
1098
|
+
):
|
|
1099
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1100
|
+
f"Labware {lid_labware_definition.parameters.loadName} cannot be used as a lid for {primary_labware_definition.parameters.loadName}"
|
|
1101
|
+
)
|
|
1102
|
+
if adapter_labware_definition:
|
|
1103
|
+
if not labware_validation.validate_definition_is_adapter(
|
|
1104
|
+
adapter_labware_definition
|
|
1105
|
+
):
|
|
1106
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1107
|
+
f"Labware {adapter_labware_definition.parameters.loadName} cannot be used as an adapter in the Flex Stacker."
|
|
1108
|
+
)
|
|
1109
|
+
if not labware_validation.validate_labware_can_be_stacked(
|
|
1110
|
+
primary_labware_definition,
|
|
1111
|
+
adapter_labware_definition.parameters.loadName,
|
|
1112
|
+
):
|
|
1113
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1114
|
+
f"Labware {adapter_labware_definition.parameters.loadName} cannot be used as an adapter for {primary_labware_definition.parameters.loadName}"
|
|
1115
|
+
)
|
|
1116
|
+
|
|
920
1117
|
def raise_if_labware_cannot_be_stacked( # noqa: C901
|
|
921
1118
|
self, top_labware_definition: LabwareDefinition, bottom_labware_id: str
|
|
922
1119
|
) -> None:
|
|
@@ -951,7 +1148,7 @@ class LabwareView:
|
|
|
951
1148
|
for lw in labware_stack:
|
|
952
1149
|
if not labware_validation.validate_definition_is_adapter(
|
|
953
1150
|
self.get_definition(lw.id)
|
|
954
|
-
):
|
|
1151
|
+
) and not labware_validation.is_lid_stack(self.get_load_name(lw.id)):
|
|
955
1152
|
stack_without_adapters.append(lw)
|
|
956
1153
|
if len(stack_without_adapters) >= self.get_labware_stacking_maximum(
|
|
957
1154
|
top_labware_definition
|
|
@@ -1091,7 +1288,7 @@ class LabwareView:
|
|
|
1091
1288
|
)
|
|
1092
1289
|
|
|
1093
1290
|
@staticmethod
|
|
1094
|
-
def _max_x_of_well(well_defn:
|
|
1291
|
+
def _max_x_of_well(well_defn: _WellDefinition) -> float:
|
|
1095
1292
|
if well_defn.shape == "rectangular":
|
|
1096
1293
|
return well_defn.x + (well_defn.xDimension or 0) / 2
|
|
1097
1294
|
elif well_defn.shape == "circular":
|
|
@@ -1100,7 +1297,7 @@ class LabwareView:
|
|
|
1100
1297
|
return well_defn.x
|
|
1101
1298
|
|
|
1102
1299
|
@staticmethod
|
|
1103
|
-
def _min_x_of_well(well_defn:
|
|
1300
|
+
def _min_x_of_well(well_defn: _WellDefinition) -> float:
|
|
1104
1301
|
if well_defn.shape == "rectangular":
|
|
1105
1302
|
return well_defn.x - (well_defn.xDimension or 0) / 2
|
|
1106
1303
|
elif well_defn.shape == "circular":
|
|
@@ -1109,7 +1306,7 @@ class LabwareView:
|
|
|
1109
1306
|
return 0
|
|
1110
1307
|
|
|
1111
1308
|
@staticmethod
|
|
1112
|
-
def _max_y_of_well(well_defn:
|
|
1309
|
+
def _max_y_of_well(well_defn: _WellDefinition) -> float:
|
|
1113
1310
|
if well_defn.shape == "rectangular":
|
|
1114
1311
|
return well_defn.y + (well_defn.yDimension or 0) / 2
|
|
1115
1312
|
elif well_defn.shape == "circular":
|
|
@@ -1118,7 +1315,7 @@ class LabwareView:
|
|
|
1118
1315
|
return 0
|
|
1119
1316
|
|
|
1120
1317
|
@staticmethod
|
|
1121
|
-
def _min_y_of_well(well_defn:
|
|
1318
|
+
def _min_y_of_well(well_defn: _WellDefinition) -> float:
|
|
1122
1319
|
if well_defn.shape == "rectangular":
|
|
1123
1320
|
return well_defn.y - (well_defn.yDimension or 0) / 2
|
|
1124
1321
|
elif well_defn.shape == "circular":
|
|
@@ -1127,7 +1324,7 @@ class LabwareView:
|
|
|
1127
1324
|
return 0
|
|
1128
1325
|
|
|
1129
1326
|
@staticmethod
|
|
1130
|
-
def _max_z_of_well(well_defn:
|
|
1327
|
+
def _max_z_of_well(well_defn: _WellDefinition) -> float:
|
|
1131
1328
|
return well_defn.z + well_defn.depth
|
|
1132
1329
|
|
|
1133
1330
|
def get_well_bbox(self, labware_definition: LabwareDefinition) -> Dimensions:
|
|
@@ -13,6 +13,7 @@ from .thermocycler_module_substate import (
|
|
|
13
13
|
)
|
|
14
14
|
from .magnetic_block_substate import MagneticBlockSubState, MagneticBlockId
|
|
15
15
|
from .absorbance_reader_substate import AbsorbanceReaderSubState, AbsorbanceReaderId
|
|
16
|
+
from .flex_stacker_substate import FlexStackerSubState, FlexStackerId
|
|
16
17
|
|
|
17
18
|
ModuleSubStateType = Union[
|
|
18
19
|
HeaterShakerModuleSubState,
|
|
@@ -21,6 +22,7 @@ ModuleSubStateType = Union[
|
|
|
21
22
|
ThermocyclerModuleSubState,
|
|
22
23
|
MagneticBlockSubState,
|
|
23
24
|
AbsorbanceReaderSubState,
|
|
25
|
+
FlexStackerSubState,
|
|
24
26
|
]
|
|
25
27
|
|
|
26
28
|
__all__ = [
|
|
@@ -36,6 +38,8 @@ __all__ = [
|
|
|
36
38
|
"MagneticBlockId",
|
|
37
39
|
"AbsorbanceReaderSubState",
|
|
38
40
|
"AbsorbanceReaderId",
|
|
41
|
+
"FlexStackerSubState",
|
|
42
|
+
"FlexStackerId",
|
|
39
43
|
# Union of all module substates
|
|
40
44
|
"ModuleSubStateType",
|
|
41
45
|
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Flex Stacker substate."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import NewType
|
|
5
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
6
|
+
from opentrons.protocol_engine.state.update_types import (
|
|
7
|
+
FlexStackerStateUpdate,
|
|
8
|
+
NO_CHANGE,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
FlexStackerId = NewType("FlexStackerId", str)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class FlexStackerSubState:
|
|
17
|
+
"""Flex Stacker-specific state.
|
|
18
|
+
|
|
19
|
+
Provides calculations and read-only state access
|
|
20
|
+
for an individual loaded Flex Stacker Module.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
module_id: FlexStackerId
|
|
24
|
+
pool_primary_definition: LabwareDefinition | None
|
|
25
|
+
pool_adapter_definition: LabwareDefinition | None
|
|
26
|
+
pool_lid_definition: LabwareDefinition | None
|
|
27
|
+
pool_count: int
|
|
28
|
+
max_pool_count: int
|
|
29
|
+
|
|
30
|
+
def new_from_state_change(
|
|
31
|
+
self, update: FlexStackerStateUpdate
|
|
32
|
+
) -> "FlexStackerSubState":
|
|
33
|
+
"""Return a new state with the given update applied."""
|
|
34
|
+
pool_primary_definition = self.pool_primary_definition
|
|
35
|
+
pool_adapter_definition = self.pool_adapter_definition
|
|
36
|
+
pool_lid_definition = self.pool_lid_definition
|
|
37
|
+
max_pool_count = self.max_pool_count
|
|
38
|
+
if update.pool_constraint != NO_CHANGE:
|
|
39
|
+
max_pool_count = update.pool_constraint.max_pool_count
|
|
40
|
+
pool_primary_definition = update.pool_constraint.primary_definition
|
|
41
|
+
pool_adapter_definition = update.pool_constraint.adapter_definition
|
|
42
|
+
pool_lid_definition = update.pool_constraint.lid_definition
|
|
43
|
+
|
|
44
|
+
pool_count = self.pool_count
|
|
45
|
+
if update.pool_count != NO_CHANGE:
|
|
46
|
+
pool_count = update.pool_count
|
|
47
|
+
|
|
48
|
+
return FlexStackerSubState(
|
|
49
|
+
module_id=self.module_id,
|
|
50
|
+
pool_primary_definition=pool_primary_definition,
|
|
51
|
+
pool_adapter_definition=pool_adapter_definition,
|
|
52
|
+
pool_lid_definition=pool_lid_definition,
|
|
53
|
+
pool_count=pool_count,
|
|
54
|
+
max_pool_count=max_pool_count,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def get_pool_definition_ordered_list(self) -> list[LabwareDefinition] | None:
|
|
58
|
+
"""Get the pool definitions in a list suitable for getting the height."""
|
|
59
|
+
if not self.pool_primary_definition:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
defs: list[LabwareDefinition] = []
|
|
63
|
+
if self.pool_lid_definition is not None:
|
|
64
|
+
defs.append(self.pool_lid_definition)
|
|
65
|
+
defs.append(self.pool_primary_definition)
|
|
66
|
+
if self.pool_adapter_definition is not None:
|
|
67
|
+
defs.append(self.pool_adapter_definition)
|
|
68
|
+
return defs
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import NewType, Optional
|
|
4
4
|
|
|
5
|
+
from opentrons.hardware_control.modules import ModuleDataValidator, ModuleData
|
|
5
6
|
from opentrons.protocol_engine.types import (
|
|
6
7
|
TemperatureRange,
|
|
7
8
|
SpeedRange,
|
|
@@ -89,3 +90,24 @@ class HeaterShakerModuleSubState:
|
|
|
89
90
|
raise CannotPerformModuleAction(
|
|
90
91
|
"Heater-Shaker cannot open its labware latch while it is shaking."
|
|
91
92
|
)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_live_data(
|
|
96
|
+
cls, module_id: HeaterShakerModuleId, data: ModuleData | None
|
|
97
|
+
) -> "HeaterShakerModuleSubState":
|
|
98
|
+
"""Create a HeaterShakerModuleSubState from live data."""
|
|
99
|
+
if ModuleDataValidator.is_heater_shaker_data(data):
|
|
100
|
+
return cls(
|
|
101
|
+
module_id=module_id,
|
|
102
|
+
labware_latch_status=HeaterShakerLatchStatus.CLOSED
|
|
103
|
+
if data["labwareLatchStatus"] == "idle_closed"
|
|
104
|
+
else HeaterShakerLatchStatus.OPEN,
|
|
105
|
+
is_plate_shaking=data["targetSpeed"] is not None,
|
|
106
|
+
plate_target_temperature=data["targetTemp"],
|
|
107
|
+
)
|
|
108
|
+
return cls(
|
|
109
|
+
module_id=module_id,
|
|
110
|
+
labware_latch_status=HeaterShakerLatchStatus.UNKNOWN,
|
|
111
|
+
is_plate_shaking=False,
|
|
112
|
+
plate_target_temperature=None,
|
|
113
|
+
)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import NewType, Optional
|
|
5
5
|
|
|
6
|
+
from opentrons.hardware_control.modules import ModuleDataValidator, ModuleData
|
|
6
7
|
from opentrons.protocol_engine.types import TemperatureRange
|
|
7
8
|
from opentrons.protocol_engine.errors import (
|
|
8
9
|
InvalidTargetTemperatureError,
|
|
@@ -52,3 +53,15 @@ class TemperatureModuleSubState:
|
|
|
52
53
|
f"Module {self.module_id} does not have a target temperature set."
|
|
53
54
|
)
|
|
54
55
|
return target
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_live_data(
|
|
59
|
+
cls, module_id: TemperatureModuleId, data: ModuleData | None
|
|
60
|
+
) -> "TemperatureModuleSubState":
|
|
61
|
+
"""Create a TemperatureModuleSubState from live data."""
|
|
62
|
+
return cls(
|
|
63
|
+
module_id=module_id,
|
|
64
|
+
plate_target_temperature=data["targetTemp"]
|
|
65
|
+
if ModuleDataValidator.is_temperature_module_data(data)
|
|
66
|
+
else None,
|
|
67
|
+
)
|