opentrons 8.1.0__py2.py3-none-any.whl → 8.2.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opentrons/cli/analyze.py +71 -7
- opentrons/config/__init__.py +9 -0
- opentrons/config/advanced_settings.py +22 -0
- opentrons/config/defaults_ot3.py +14 -36
- opentrons/config/feature_flags.py +4 -0
- opentrons/config/types.py +6 -17
- opentrons/drivers/absorbance_reader/abstract.py +27 -3
- opentrons/drivers/absorbance_reader/async_byonoy.py +208 -154
- opentrons/drivers/absorbance_reader/driver.py +24 -15
- opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
- opentrons/drivers/absorbance_reader/simulator.py +32 -6
- opentrons/drivers/types.py +23 -1
- opentrons/execute.py +2 -2
- opentrons/hardware_control/api.py +18 -10
- opentrons/hardware_control/backends/controller.py +3 -2
- opentrons/hardware_control/backends/flex_protocol.py +11 -5
- opentrons/hardware_control/backends/ot3controller.py +18 -50
- opentrons/hardware_control/backends/ot3simulator.py +7 -6
- opentrons/hardware_control/backends/ot3utils.py +1 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
- opentrons/hardware_control/module_control.py +43 -2
- opentrons/hardware_control/modules/__init__.py +7 -1
- opentrons/hardware_control/modules/absorbance_reader.py +232 -83
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/heater_shaker.py +8 -3
- opentrons/hardware_control/modules/magdeck.py +12 -3
- opentrons/hardware_control/modules/mod_abc.py +27 -2
- opentrons/hardware_control/modules/tempdeck.py +15 -7
- opentrons/hardware_control/modules/thermocycler.py +69 -3
- opentrons/hardware_control/modules/types.py +11 -5
- opentrons/hardware_control/modules/update.py +11 -5
- opentrons/hardware_control/modules/utils.py +3 -1
- opentrons/hardware_control/ot3_calibration.py +6 -6
- opentrons/hardware_control/ot3api.py +131 -94
- opentrons/hardware_control/poller.py +15 -11
- opentrons/hardware_control/protocols/__init__.py +1 -7
- opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
- opentrons/hardware_control/protocols/liquid_handler.py +5 -0
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/hardware_control/types.py +2 -0
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/motion_planning/__init__.py +2 -0
- opentrons/motion_planning/waypoints.py +32 -0
- opentrons/protocol_api/__init__.py +2 -1
- opentrons/protocol_api/_liquid.py +87 -1
- opentrons/protocol_api/_parameter_context.py +10 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
- opentrons/protocol_api/core/engine/instrument.py +29 -25
- opentrons/protocol_api/core/engine/labware.py +20 -4
- opentrons/protocol_api/core/engine/module_core.py +166 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
- opentrons/protocol_api/core/engine/protocol.py +30 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/module.py +22 -4
- opentrons/protocol_api/core/protocol.py +6 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +115 -17
- opentrons/protocol_api/protocol_context.py +49 -5
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +3 -0
- opentrons/protocol_engine/actions/actions.py +30 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +1 -1
- opentrons/protocol_engine/clients/transports.py +1 -1
- opentrons/protocol_engine/commands/__init__.py +0 -4
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
- opentrons/protocol_engine/commands/blow_out.py +63 -14
- opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
- opentrons/protocol_engine/commands/command.py +31 -18
- opentrons/protocol_engine/commands/command_unions.py +37 -24
- opentrons/protocol_engine/commands/comment.py +5 -3
- opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
- opentrons/protocol_engine/commands/custom.py +5 -3
- opentrons/protocol_engine/commands/dispense.py +42 -20
- opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
- opentrons/protocol_engine/commands/drop_tip.py +70 -16
- opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
- opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/home.py +11 -5
- opentrons/protocol_engine/commands/liquid_probe.py +146 -88
- opentrons/protocol_engine/commands/load_labware.py +28 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +4 -6
- opentrons/protocol_engine/commands/load_pipette.py +18 -17
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
- opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
- opentrons/protocol_engine/commands/move_labware.py +155 -23
- opentrons/protocol_engine/commands/move_relative.py +15 -3
- opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
- opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
- opentrons/protocol_engine/commands/move_to_well.py +37 -10
- opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
- opentrons/protocol_engine/commands/pipetting_common.py +47 -16
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
- opentrons/protocol_engine/commands/reload_labware.py +13 -4
- opentrons/protocol_engine/commands/retract_axis.py +6 -3
- opentrons/protocol_engine/commands/save_position.py +2 -3
- opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
- opentrons/protocol_engine/commands/set_status_bar.py +5 -3
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
- opentrons/protocol_engine/commands/touch_tip.py +19 -7
- opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
- opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
- opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
- opentrons/protocol_engine/create_protocol_engine.py +60 -10
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +20 -0
- opentrons/protocol_engine/errors/error_occurrence.py +8 -3
- opentrons/protocol_engine/errors/exceptions.py +127 -2
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +22 -13
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/door_watcher.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +2 -1
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +4 -2
- opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
- opentrons/protocol_engine/execution/labware_movement.py +73 -22
- opentrons/protocol_engine/execution/movement.py +17 -7
- opentrons/protocol_engine/execution/pipetting.py +7 -4
- opentrons/protocol_engine/execution/queue_worker.py +6 -2
- opentrons/protocol_engine/execution/run_control.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
- opentrons/protocol_engine/execution/tip_handler.py +77 -43
- opentrons/protocol_engine/notes/__init__.py +14 -2
- opentrons/protocol_engine/notes/notes.py +18 -1
- opentrons/protocol_engine/plugins.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +47 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +11 -1
- opentrons/protocol_engine/resources/labware_validation.py +10 -0
- opentrons/protocol_engine/state/__init__.py +0 -70
- opentrons/protocol_engine/state/addressable_areas.py +1 -1
- opentrons/protocol_engine/state/command_history.py +21 -2
- opentrons/protocol_engine/state/commands.py +110 -31
- opentrons/protocol_engine/state/files.py +59 -0
- opentrons/protocol_engine/state/frustum_helpers.py +440 -0
- opentrons/protocol_engine/state/geometry.py +445 -59
- opentrons/protocol_engine/state/labware.py +264 -84
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
- opentrons/protocol_engine/state/modules.py +145 -90
- opentrons/protocol_engine/state/motion.py +33 -14
- opentrons/protocol_engine/state/pipettes.py +157 -317
- opentrons/protocol_engine/state/state.py +30 -1
- opentrons/protocol_engine/state/state_summary.py +3 -0
- opentrons/protocol_engine/state/tips.py +69 -114
- opentrons/protocol_engine/state/update_types.py +424 -0
- opentrons/protocol_engine/state/wells.py +236 -0
- opentrons/protocol_engine/types.py +90 -0
- opentrons/protocol_reader/file_format_validator.py +83 -15
- opentrons/protocol_runner/json_translator.py +21 -5
- opentrons/protocol_runner/legacy_command_mapper.py +27 -6
- opentrons/protocol_runner/legacy_context_plugin.py +27 -71
- opentrons/protocol_runner/protocol_runner.py +6 -3
- opentrons/protocol_runner/run_orchestrator.py +41 -6
- opentrons/protocols/advanced_control/mix.py +3 -5
- opentrons/protocols/advanced_control/transfers.py +125 -56
- opentrons/protocols/api_support/constants.py +1 -1
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/labware_like.py +4 -4
- opentrons/protocols/api_support/tip_tracker.py +2 -2
- opentrons/protocols/api_support/types.py +15 -2
- opentrons/protocols/api_support/util.py +30 -42
- opentrons/protocols/duration/errors.py +1 -1
- opentrons/protocols/duration/estimator.py +50 -29
- opentrons/protocols/execution/dev_types.py +2 -2
- opentrons/protocols/execution/execute_json_v4.py +15 -10
- opentrons/protocols/execution/execute_python.py +8 -3
- opentrons/protocols/geometry/planning.py +12 -12
- opentrons/protocols/labware.py +17 -33
- opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
- opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
- opentrons/protocol_engine/commands/configuring_common.py +0 -26
- opentrons/protocol_runner/thread_async_queue.py +0 -174
- /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
- /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -13,11 +13,16 @@ from typing import (
|
|
|
13
13
|
NamedTuple,
|
|
14
14
|
cast,
|
|
15
15
|
Union,
|
|
16
|
+
overload,
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
from opentrons.protocol_engine.state import update_types
|
|
18
20
|
from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
19
21
|
from opentrons_shared_data.gripper.constants import LABWARE_GRIP_FORCE
|
|
20
|
-
from opentrons_shared_data.labware.labware_definition import
|
|
22
|
+
from opentrons_shared_data.labware.labware_definition import (
|
|
23
|
+
LabwareRole,
|
|
24
|
+
InnerWellGeometry,
|
|
25
|
+
)
|
|
21
26
|
from opentrons_shared_data.pipette.types import LabwareUri
|
|
22
27
|
|
|
23
28
|
from opentrons.types import DeckSlotName, StagingSlotName, MountType
|
|
@@ -27,12 +32,6 @@ from opentrons.calibration_storage.helpers import uri_from_details
|
|
|
27
32
|
|
|
28
33
|
from .. import errors
|
|
29
34
|
from ..resources import DeckFixedLabware, labware_validation, fixture_validation
|
|
30
|
-
from ..commands import (
|
|
31
|
-
Command,
|
|
32
|
-
LoadLabwareResult,
|
|
33
|
-
MoveLabwareResult,
|
|
34
|
-
ReloadLabwareResult,
|
|
35
|
-
)
|
|
36
35
|
from ..types import (
|
|
37
36
|
DeckSlotLocation,
|
|
38
37
|
OnLabwareLocation,
|
|
@@ -53,12 +52,12 @@ from ..types import (
|
|
|
53
52
|
)
|
|
54
53
|
from ..actions import (
|
|
55
54
|
Action,
|
|
56
|
-
SucceedCommandAction,
|
|
57
55
|
AddLabwareOffsetAction,
|
|
58
56
|
AddLabwareDefinitionAction,
|
|
57
|
+
get_state_updates,
|
|
59
58
|
)
|
|
60
|
-
from .
|
|
61
|
-
from .
|
|
59
|
+
from ._abstract_store import HasState, HandlesActions
|
|
60
|
+
from ._move_types import EdgePathType
|
|
62
61
|
|
|
63
62
|
|
|
64
63
|
# URIs of labware whose definitions accidentally specify an engage height
|
|
@@ -69,8 +68,6 @@ _MAGDECK_HALF_MM_LABWARE = {
|
|
|
69
68
|
"opentrons/usascientific_96_wellplate_2.4ml_deep/1",
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
_OT3_INSTRUMENT_ATTACH_SLOT = DeckSlotName.SLOT_D1
|
|
73
|
-
|
|
74
71
|
_RIGHT_SIDE_SLOTS = {
|
|
75
72
|
# OT-2:
|
|
76
73
|
DeckSlotName.FIXED_TRASH,
|
|
@@ -85,6 +82,10 @@ _RIGHT_SIDE_SLOTS = {
|
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
|
|
85
|
+
# The max height of the labware that can fit in a plate reader
|
|
86
|
+
_PLATE_READER_MAX_LABWARE_Z_MM = 16
|
|
87
|
+
|
|
88
|
+
|
|
88
89
|
class LabwareLoadParams(NamedTuple):
|
|
89
90
|
"""Parameters required to load a labware in Protocol Engine."""
|
|
90
91
|
|
|
@@ -153,10 +154,11 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
153
154
|
|
|
154
155
|
def handle_action(self, action: Action) -> None:
|
|
155
156
|
"""Modify state in reaction to an action."""
|
|
156
|
-
|
|
157
|
-
self.
|
|
157
|
+
for state_update in get_state_updates(action):
|
|
158
|
+
self._add_loaded_labware(state_update)
|
|
159
|
+
self._set_labware_location(state_update)
|
|
158
160
|
|
|
159
|
-
|
|
161
|
+
if isinstance(action, AddLabwareOffsetAction):
|
|
160
162
|
labware_offset = LabwareOffset.construct(
|
|
161
163
|
id=action.labware_offset_id,
|
|
162
164
|
createdAt=action.created_at,
|
|
@@ -174,66 +176,72 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
174
176
|
)
|
|
175
177
|
self._state.definitions_by_uri[uri] = action.definition
|
|
176
178
|
|
|
177
|
-
def
|
|
178
|
-
"""
|
|
179
|
-
|
|
179
|
+
def _add_labware_offset(self, labware_offset: LabwareOffset) -> None:
|
|
180
|
+
"""Add a new labware offset to state.
|
|
181
|
+
|
|
182
|
+
`labware_offset.id` must not match any existing labware offset ID.
|
|
183
|
+
`LoadLabwareCommand`s retain references to their corresponding labware offsets
|
|
184
|
+
and expect them to be immutable.
|
|
185
|
+
"""
|
|
186
|
+
assert labware_offset.id not in self._state.labware_offsets_by_id
|
|
187
|
+
|
|
188
|
+
self._state.labware_offsets_by_id[labware_offset.id] = labware_offset
|
|
189
|
+
|
|
190
|
+
def _add_loaded_labware(self, state_update: update_types.StateUpdate) -> None:
|
|
191
|
+
loaded_labware_update = state_update.loaded_labware
|
|
192
|
+
if loaded_labware_update != update_types.NO_CHANGE:
|
|
180
193
|
# If the labware load refers to an offset, that offset must actually exist.
|
|
181
|
-
if
|
|
182
|
-
assert
|
|
194
|
+
if loaded_labware_update.offset_id is not None:
|
|
195
|
+
assert (
|
|
196
|
+
loaded_labware_update.offset_id in self._state.labware_offsets_by_id
|
|
197
|
+
)
|
|
183
198
|
|
|
184
199
|
definition_uri = uri_from_details(
|
|
185
|
-
namespace=
|
|
186
|
-
load_name=
|
|
187
|
-
version=
|
|
200
|
+
namespace=loaded_labware_update.definition.namespace,
|
|
201
|
+
load_name=loaded_labware_update.definition.parameters.loadName,
|
|
202
|
+
version=loaded_labware_update.definition.version,
|
|
188
203
|
)
|
|
189
204
|
|
|
190
|
-
self._state.definitions_by_uri[
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
self._state.definitions_by_uri[
|
|
206
|
+
definition_uri
|
|
207
|
+
] = loaded_labware_update.definition
|
|
208
|
+
|
|
209
|
+
location = loaded_labware_update.new_location
|
|
210
|
+
|
|
211
|
+
display_name = loaded_labware_update.display_name
|
|
195
212
|
|
|
196
213
|
self._state.labware_by_id[
|
|
197
|
-
|
|
214
|
+
loaded_labware_update.labware_id
|
|
198
215
|
] = LoadedLabware.construct(
|
|
199
|
-
id=
|
|
216
|
+
id=loaded_labware_update.labware_id,
|
|
200
217
|
location=location,
|
|
201
|
-
loadName=
|
|
218
|
+
loadName=loaded_labware_update.definition.parameters.loadName,
|
|
202
219
|
definitionUri=definition_uri,
|
|
203
|
-
offsetId=
|
|
204
|
-
displayName=
|
|
220
|
+
offsetId=loaded_labware_update.offset_id,
|
|
221
|
+
displayName=display_name,
|
|
205
222
|
)
|
|
206
223
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
elif isinstance(command.result, MoveLabwareResult):
|
|
213
|
-
labware_id = command.params.labwareId
|
|
214
|
-
new_location = command.params.newLocation
|
|
215
|
-
new_offset_id = command.result.offsetId
|
|
224
|
+
def _set_labware_location(self, state_update: update_types.StateUpdate) -> None:
|
|
225
|
+
labware_location_update = state_update.labware_location
|
|
226
|
+
if labware_location_update != update_types.NO_CHANGE:
|
|
227
|
+
labware_id = labware_location_update.labware_id
|
|
228
|
+
new_offset_id = labware_location_update.offset_id
|
|
216
229
|
|
|
217
230
|
self._state.labware_by_id[labware_id].offsetId = new_offset_id
|
|
218
|
-
if isinstance(
|
|
219
|
-
new_location, AddressableAreaLocation
|
|
220
|
-
) and fixture_validation.is_gripper_waste_chute(
|
|
221
|
-
new_location.addressableAreaName
|
|
222
|
-
):
|
|
223
|
-
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
|
|
224
|
-
new_location = OFF_DECK_LOCATION
|
|
225
|
-
self._state.labware_by_id[labware_id].location = new_location
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
|
|
232
|
+
if labware_location_update.new_location:
|
|
233
|
+
new_location = labware_location_update.new_location
|
|
229
234
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
if isinstance(new_location, AddressableAreaLocation) and (
|
|
236
|
+
fixture_validation.is_gripper_waste_chute(
|
|
237
|
+
new_location.addressableAreaName
|
|
238
|
+
)
|
|
239
|
+
or fixture_validation.is_trash(new_location.addressableAreaName)
|
|
240
|
+
):
|
|
241
|
+
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
|
|
242
|
+
new_location = OFF_DECK_LOCATION
|
|
235
243
|
|
|
236
|
-
|
|
244
|
+
self._state.labware_by_id[labware_id].location = new_location
|
|
237
245
|
|
|
238
246
|
|
|
239
247
|
class LabwareView(HasState[LabwareState]):
|
|
@@ -313,6 +321,22 @@ class LabwareView(HasState[LabwareState]):
|
|
|
313
321
|
|
|
314
322
|
return None
|
|
315
323
|
|
|
324
|
+
def get_by_addressable_area(
|
|
325
|
+
self,
|
|
326
|
+
addressable_area: str,
|
|
327
|
+
) -> Optional[LoadedLabware]:
|
|
328
|
+
"""Get the labware located in a given addressable area, if any."""
|
|
329
|
+
loaded_labware = list(self._state.labware_by_id.values())
|
|
330
|
+
|
|
331
|
+
for labware in loaded_labware:
|
|
332
|
+
if (
|
|
333
|
+
isinstance(labware.location, AddressableAreaLocation)
|
|
334
|
+
and labware.location.addressableAreaName == addressable_area
|
|
335
|
+
):
|
|
336
|
+
return labware
|
|
337
|
+
|
|
338
|
+
return None
|
|
339
|
+
|
|
316
340
|
def get_definition(self, labware_id: str) -> LabwareDefinition:
|
|
317
341
|
"""Get labware definition by the labware's unique identifier."""
|
|
318
342
|
return self.get_definition_by_uri(
|
|
@@ -378,6 +402,16 @@ class LabwareView(HasState[LabwareState]):
|
|
|
378
402
|
return self.get_parent_location(parent.labwareId)
|
|
379
403
|
return parent
|
|
380
404
|
|
|
405
|
+
def get_labware_stack(
|
|
406
|
+
self, labware_stack: List[LoadedLabware]
|
|
407
|
+
) -> List[LoadedLabware]:
|
|
408
|
+
"""Get the a stack of labware starting from a given labware or existing stack."""
|
|
409
|
+
parent = self.get_location(labware_stack[-1].id)
|
|
410
|
+
if isinstance(parent, OnLabwareLocation):
|
|
411
|
+
labware_stack.append(self.get(parent.labwareId))
|
|
412
|
+
return self.get_labware_stack(labware_stack)
|
|
413
|
+
return labware_stack
|
|
414
|
+
|
|
381
415
|
def get_all(self) -> List[LoadedLabware]:
|
|
382
416
|
"""Get a list of all labware entries in state."""
|
|
383
417
|
return list(self._state.labware_by_id.values())
|
|
@@ -402,6 +436,27 @@ class LabwareView(HasState[LabwareState]):
|
|
|
402
436
|
and len(self.get_definition(labware_id).wells) < 96
|
|
403
437
|
)
|
|
404
438
|
|
|
439
|
+
def get_labware_stacking_maximum(self, labware: LabwareDefinition) -> int:
|
|
440
|
+
"""Returns the maximum number of labware allowed in a stack for a given labware definition.
|
|
441
|
+
|
|
442
|
+
If not defined within a labware, defaults to one.
|
|
443
|
+
"""
|
|
444
|
+
stacking_quirks = {
|
|
445
|
+
"stackingMaxFive": 5,
|
|
446
|
+
"stackingMaxFour": 4,
|
|
447
|
+
"stackingMaxThree": 3,
|
|
448
|
+
"stackingMaxTwo": 2,
|
|
449
|
+
"stackingMaxOne": 1,
|
|
450
|
+
"stackingMaxZero": 0,
|
|
451
|
+
}
|
|
452
|
+
for quirk in stacking_quirks.keys():
|
|
453
|
+
if (
|
|
454
|
+
labware.parameters.quirks is not None
|
|
455
|
+
and quirk in labware.parameters.quirks
|
|
456
|
+
):
|
|
457
|
+
return stacking_quirks[quirk]
|
|
458
|
+
return 1
|
|
459
|
+
|
|
405
460
|
def get_should_center_pipette_on_target_well(self, labware_id: str) -> bool:
|
|
406
461
|
"""True if a pipette moving to a well of this labware should center its body on the target.
|
|
407
462
|
|
|
@@ -435,6 +490,29 @@ class LabwareView(HasState[LabwareState]):
|
|
|
435
490
|
f"{well_name} does not exist in {labware_id}."
|
|
436
491
|
) from e
|
|
437
492
|
|
|
493
|
+
def get_well_geometry(
|
|
494
|
+
self, labware_id: str, well_name: Optional[str] = None
|
|
495
|
+
) -> InnerWellGeometry:
|
|
496
|
+
"""Get a well's inner geometry by labware and well name."""
|
|
497
|
+
labware_def = self.get_definition(labware_id)
|
|
498
|
+
if labware_def.innerLabwareGeometry is None:
|
|
499
|
+
raise errors.IncompleteLabwareDefinitionError(
|
|
500
|
+
message=f"No innerLabwareGeometry found in labware definition for labware_id: {labware_id}."
|
|
501
|
+
)
|
|
502
|
+
well_def = self.get_well_definition(labware_id, well_name)
|
|
503
|
+
well_id = well_def.geometryDefinitionId
|
|
504
|
+
if well_id is None:
|
|
505
|
+
raise errors.IncompleteWellDefinitionError(
|
|
506
|
+
message=f"No geometryDefinitionId found in well definition for well: {well_name} in labware_id: {labware_id}"
|
|
507
|
+
)
|
|
508
|
+
else:
|
|
509
|
+
well_geometry = labware_def.innerLabwareGeometry.get(well_id)
|
|
510
|
+
if well_geometry is None:
|
|
511
|
+
raise errors.IncompleteLabwareDefinitionError(
|
|
512
|
+
message=f"No innerLabwareGeometry found in labware definition for well_id: {well_id} in labware_id: {labware_id}"
|
|
513
|
+
)
|
|
514
|
+
return well_geometry
|
|
515
|
+
|
|
438
516
|
def get_well_size(
|
|
439
517
|
self, labware_id: str, well_name: str
|
|
440
518
|
) -> Tuple[float, float, float]:
|
|
@@ -553,10 +631,26 @@ class LabwareView(HasState[LabwareState]):
|
|
|
553
631
|
definition = self.get_definition(labware_id)
|
|
554
632
|
return definition.parameters.loadName
|
|
555
633
|
|
|
556
|
-
|
|
634
|
+
@overload
|
|
635
|
+
def get_dimensions(self, *, labware_definition: LabwareDefinition) -> Dimensions:
|
|
636
|
+
pass
|
|
637
|
+
|
|
638
|
+
@overload
|
|
639
|
+
def get_dimensions(self, *, labware_id: str) -> Dimensions:
|
|
640
|
+
pass
|
|
641
|
+
|
|
642
|
+
def get_dimensions(
|
|
643
|
+
self,
|
|
644
|
+
*,
|
|
645
|
+
labware_definition: LabwareDefinition | None = None,
|
|
646
|
+
labware_id: str | None = None,
|
|
647
|
+
) -> Dimensions:
|
|
557
648
|
"""Get the labware's dimensions."""
|
|
558
|
-
|
|
559
|
-
|
|
649
|
+
if labware_definition is None:
|
|
650
|
+
assert labware_id is not None # From our @overloads.
|
|
651
|
+
labware_definition = self.get_definition(labware_id)
|
|
652
|
+
|
|
653
|
+
dims = labware_definition.dimensions
|
|
560
654
|
|
|
561
655
|
return Dimensions(
|
|
562
656
|
x=dims.xDimension,
|
|
@@ -565,22 +659,25 @@ class LabwareView(HasState[LabwareState]):
|
|
|
565
659
|
)
|
|
566
660
|
|
|
567
661
|
def get_labware_overlap_offsets(
|
|
568
|
-
self,
|
|
662
|
+
self, definition: LabwareDefinition, below_labware_name: str
|
|
569
663
|
) -> OverlapOffset:
|
|
570
664
|
"""Get the labware's overlap with requested labware's load name."""
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
665
|
+
if below_labware_name in definition.stackingOffsetWithLabware.keys():
|
|
666
|
+
stacking_overlap = definition.stackingOffsetWithLabware.get(
|
|
667
|
+
below_labware_name, OverlapOffset(x=0, y=0, z=0)
|
|
668
|
+
)
|
|
669
|
+
else:
|
|
670
|
+
stacking_overlap = definition.stackingOffsetWithLabware.get(
|
|
671
|
+
"default", OverlapOffset(x=0, y=0, z=0)
|
|
672
|
+
)
|
|
575
673
|
return OverlapOffset(
|
|
576
674
|
x=stacking_overlap.x, y=stacking_overlap.y, z=stacking_overlap.z
|
|
577
675
|
)
|
|
578
676
|
|
|
579
677
|
def get_module_overlap_offsets(
|
|
580
|
-
self,
|
|
678
|
+
self, definition: LabwareDefinition, module_model: ModuleModel
|
|
581
679
|
) -> OverlapOffset:
|
|
582
680
|
"""Get the labware's overlap with requested module model."""
|
|
583
|
-
definition = self.get_definition(labware_id)
|
|
584
681
|
stacking_overlap = definition.stackingOffsetWithModule.get(
|
|
585
682
|
str(module_model.value)
|
|
586
683
|
)
|
|
@@ -704,6 +801,12 @@ class LabwareView(HasState[LabwareState]):
|
|
|
704
801
|
"""Check if labware is fixed trash."""
|
|
705
802
|
return self.get_has_quirk(labware_id, "fixedTrash")
|
|
706
803
|
|
|
804
|
+
def is_absorbance_reader_lid(self, labware_id: str) -> bool:
|
|
805
|
+
"""Check if labware is an absorbance reader lid."""
|
|
806
|
+
return labware_validation.is_absorbance_reader_lid(
|
|
807
|
+
self.get(labware_id).loadName
|
|
808
|
+
)
|
|
809
|
+
|
|
707
810
|
def raise_if_labware_inaccessible_by_pipette(self, labware_id: str) -> None:
|
|
708
811
|
"""Raise an error if the specified location cannot be reached via a pipette."""
|
|
709
812
|
labware = self.get(labware_id)
|
|
@@ -734,7 +837,25 @@ class LabwareView(HasState[LabwareState]):
|
|
|
734
837
|
f"Labware {labware.loadName} is already present at {location}."
|
|
735
838
|
)
|
|
736
839
|
|
|
737
|
-
def
|
|
840
|
+
def raise_if_labware_incompatible_with_plate_reader(
|
|
841
|
+
self,
|
|
842
|
+
labware_definition: LabwareDefinition,
|
|
843
|
+
) -> None:
|
|
844
|
+
"""Raise an error if the labware is not compatible with the plate reader."""
|
|
845
|
+
load_name = labware_definition.parameters.loadName
|
|
846
|
+
number_of_wells = len(labware_definition.wells)
|
|
847
|
+
if number_of_wells != 96:
|
|
848
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
849
|
+
f"Cannot move '{load_name}' into plate reader because the"
|
|
850
|
+
f" labware contains {number_of_wells} wells where 96 wells is expected."
|
|
851
|
+
)
|
|
852
|
+
elif labware_definition.dimensions.zDimension > _PLATE_READER_MAX_LABWARE_Z_MM:
|
|
853
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
854
|
+
f"Cannot move '{load_name}' into plate reader because the"
|
|
855
|
+
f" maximum allowed labware height is {_PLATE_READER_MAX_LABWARE_Z_MM}mm."
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
def raise_if_labware_cannot_be_stacked( # noqa: C901
|
|
738
859
|
self, top_labware_definition: LabwareDefinition, bottom_labware_id: str
|
|
739
860
|
) -> None:
|
|
740
861
|
"""Raise if the specified labware definition cannot be placed on top of the bottom labware."""
|
|
@@ -753,17 +874,37 @@ class LabwareView(HasState[LabwareState]):
|
|
|
753
874
|
)
|
|
754
875
|
elif isinstance(below_labware.location, ModuleLocation):
|
|
755
876
|
below_definition = self.get_definition(labware_id=below_labware.id)
|
|
756
|
-
if not labware_validation.validate_definition_is_adapter(
|
|
877
|
+
if not labware_validation.validate_definition_is_adapter(
|
|
878
|
+
below_definition
|
|
879
|
+
) and not labware_validation.validate_definition_is_lid(
|
|
880
|
+
top_labware_definition
|
|
881
|
+
):
|
|
757
882
|
raise errors.LabwareCannotBeStackedError(
|
|
758
883
|
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded"
|
|
759
884
|
f" onto a labware on top of a module"
|
|
760
885
|
)
|
|
761
886
|
elif isinstance(below_labware.location, OnLabwareLocation):
|
|
887
|
+
labware_stack = self.get_labware_stack([below_labware])
|
|
888
|
+
stack_without_adapters = []
|
|
889
|
+
for lw in labware_stack:
|
|
890
|
+
if not labware_validation.validate_definition_is_adapter(
|
|
891
|
+
self.get_definition(lw.id)
|
|
892
|
+
):
|
|
893
|
+
stack_without_adapters.append(lw)
|
|
894
|
+
if len(stack_without_adapters) >= self.get_labware_stacking_maximum(
|
|
895
|
+
top_labware_definition
|
|
896
|
+
):
|
|
897
|
+
raise errors.LabwareCannotBeStackedError(
|
|
898
|
+
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded to stack of more than {self.get_labware_stacking_maximum(top_labware_definition)} labware."
|
|
899
|
+
)
|
|
900
|
+
|
|
762
901
|
further_below_definition = self.get_definition(
|
|
763
902
|
labware_id=below_labware.location.labwareId
|
|
764
903
|
)
|
|
765
904
|
if labware_validation.validate_definition_is_adapter(
|
|
766
905
|
further_below_definition
|
|
906
|
+
) and not labware_validation.validate_definition_is_lid(
|
|
907
|
+
top_labware_definition
|
|
767
908
|
):
|
|
768
909
|
raise errors.LabwareCannotBeStackedError(
|
|
769
910
|
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded"
|
|
@@ -797,22 +938,60 @@ class LabwareView(HasState[LabwareState]):
|
|
|
797
938
|
else None
|
|
798
939
|
)
|
|
799
940
|
|
|
800
|
-
def
|
|
941
|
+
def get_absorbance_reader_lid_definition(self) -> LabwareDefinition:
|
|
942
|
+
"""Return the special labware definition for the plate reader lid.
|
|
943
|
+
|
|
944
|
+
See todo comments in `create_protocol_engine().
|
|
945
|
+
"""
|
|
946
|
+
# NOTE: This needs to stay in sync with create_protocol_engine().
|
|
947
|
+
return self._state.definitions_by_uri[
|
|
948
|
+
"opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1"
|
|
949
|
+
]
|
|
950
|
+
|
|
951
|
+
@overload
|
|
952
|
+
def get_child_gripper_offsets(
|
|
801
953
|
self,
|
|
802
|
-
|
|
954
|
+
*,
|
|
955
|
+
labware_definition: LabwareDefinition,
|
|
956
|
+
slot_name: Optional[DeckSlotName],
|
|
957
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
958
|
+
pass
|
|
959
|
+
|
|
960
|
+
@overload
|
|
961
|
+
def get_child_gripper_offsets(
|
|
962
|
+
self, *, labware_id: str, slot_name: Optional[DeckSlotName]
|
|
963
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
964
|
+
pass
|
|
965
|
+
|
|
966
|
+
def get_child_gripper_offsets(
|
|
967
|
+
self,
|
|
968
|
+
*,
|
|
969
|
+
labware_definition: Optional[LabwareDefinition] = None,
|
|
970
|
+
labware_id: Optional[str] = None,
|
|
803
971
|
slot_name: Optional[DeckSlotName],
|
|
804
972
|
) -> Optional[LabwareMovementOffsetData]:
|
|
805
|
-
"""Get the labware
|
|
973
|
+
"""Get the grip offsets that a labware says should be applied to children stacked atop it.
|
|
974
|
+
|
|
975
|
+
Params:
|
|
976
|
+
labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked).
|
|
977
|
+
slot_name: The ancestor slot that the parent labware is ultimately loaded into,
|
|
978
|
+
perhaps after going through a module in the middle.
|
|
806
979
|
|
|
807
980
|
Returns:
|
|
808
|
-
If `slot_name` is provided, returns the gripper offsets that the labware definition
|
|
981
|
+
If `slot_name` is provided, returns the gripper offsets that the parent labware definition
|
|
809
982
|
specifies just for that slot, or `None` if the labware definition doesn't have an
|
|
810
983
|
exact match.
|
|
811
984
|
|
|
812
|
-
If `slot_name` is `None`, returns the gripper offsets that the labware
|
|
985
|
+
If `slot_name` is `None`, returns the gripper offsets that the parent labware
|
|
813
986
|
definition designates as "default," or `None` if it doesn't designate any as such.
|
|
814
987
|
"""
|
|
815
|
-
|
|
988
|
+
if labware_id is not None:
|
|
989
|
+
labware_definition = self.get_definition(labware_id)
|
|
990
|
+
else:
|
|
991
|
+
# Should be ensured by our @overloads.
|
|
992
|
+
assert labware_definition is not None
|
|
993
|
+
|
|
994
|
+
parsed_offsets = labware_definition.gripperOffsets
|
|
816
995
|
offset_key = slot_name.id if slot_name else "default"
|
|
817
996
|
|
|
818
997
|
if parsed_offsets is None or offset_key not in parsed_offsets:
|
|
@@ -827,20 +1006,22 @@ class LabwareView(HasState[LabwareState]):
|
|
|
827
1006
|
),
|
|
828
1007
|
)
|
|
829
1008
|
|
|
830
|
-
def get_grip_force(self,
|
|
1009
|
+
def get_grip_force(self, labware_definition: LabwareDefinition) -> float:
|
|
831
1010
|
"""Get the recommended grip force for gripping labware using gripper."""
|
|
832
|
-
recommended_force =
|
|
1011
|
+
recommended_force = labware_definition.gripForce
|
|
833
1012
|
return (
|
|
834
1013
|
recommended_force if recommended_force is not None else LABWARE_GRIP_FORCE
|
|
835
1014
|
)
|
|
836
1015
|
|
|
837
|
-
def get_grip_height_from_labware_bottom(
|
|
1016
|
+
def get_grip_height_from_labware_bottom(
|
|
1017
|
+
self, labware_definition: LabwareDefinition
|
|
1018
|
+
) -> float:
|
|
838
1019
|
"""Get the recommended grip height from labware bottom, if present."""
|
|
839
|
-
recommended_height =
|
|
1020
|
+
recommended_height = labware_definition.gripHeightFromLabwareBottom
|
|
840
1021
|
return (
|
|
841
1022
|
recommended_height
|
|
842
1023
|
if recommended_height is not None
|
|
843
|
-
else self.get_dimensions(
|
|
1024
|
+
else self.get_dimensions(labware_definition=labware_definition).z / 2
|
|
844
1025
|
)
|
|
845
1026
|
|
|
846
1027
|
@staticmethod
|
|
@@ -883,7 +1064,7 @@ class LabwareView(HasState[LabwareState]):
|
|
|
883
1064
|
def _max_z_of_well(well_defn: WellDefinition) -> float:
|
|
884
1065
|
return well_defn.z + well_defn.depth
|
|
885
1066
|
|
|
886
|
-
def get_well_bbox(self,
|
|
1067
|
+
def get_well_bbox(self, labware_definition: LabwareDefinition) -> Dimensions:
|
|
887
1068
|
"""Get the bounding box implied by the wells.
|
|
888
1069
|
|
|
889
1070
|
The bounding box of the labware that is implied by the wells is that required
|
|
@@ -894,14 +1075,13 @@ class LabwareView(HasState[LabwareState]):
|
|
|
894
1075
|
This is used for the specific purpose of finding the reasonable uncertainty bounds of
|
|
895
1076
|
where and how a gripper will interact with a labware.
|
|
896
1077
|
"""
|
|
897
|
-
defn = self.get_definition(labware_id)
|
|
898
1078
|
max_x: Optional[float] = None
|
|
899
1079
|
min_x: Optional[float] = None
|
|
900
1080
|
max_y: Optional[float] = None
|
|
901
1081
|
min_y: Optional[float] = None
|
|
902
1082
|
max_z: Optional[float] = None
|
|
903
1083
|
|
|
904
|
-
for well in
|
|
1084
|
+
for well in labware_definition.wells.values():
|
|
905
1085
|
well_max_x = self._max_x_of_well(well)
|
|
906
1086
|
well_min_x = self._min_x_of_well(well)
|
|
907
1087
|
well_max_y = self._max_y_of_well(well)
|
|
@@ -3,7 +3,7 @@ from dataclasses import dataclass
|
|
|
3
3
|
from typing import Dict, List
|
|
4
4
|
from opentrons.protocol_engine.types import Liquid
|
|
5
5
|
|
|
6
|
-
from .
|
|
6
|
+
from ._abstract_store import HasState, HandlesActions
|
|
7
7
|
from ..actions import Action, AddLiquidAction
|
|
8
8
|
from ..errors import LiquidDoesNotExistError
|
|
9
9
|
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"""Heater-Shaker Module sub-state."""
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import NewType, Optional,
|
|
3
|
+
from typing import List, NewType, Optional, Dict
|
|
4
4
|
|
|
5
|
+
from opentrons.protocol_engine.errors import CannotPerformModuleAction
|
|
5
6
|
|
|
6
7
|
AbsorbanceReaderId = NewType("AbsorbanceReaderId", str)
|
|
8
|
+
AbsorbanceReaderLidId = NewType("AbsorbanceReaderLidId", str)
|
|
9
|
+
AbsorbanceReaderMeasureMode = NewType("AbsorbanceReaderMeasureMode", str)
|
|
7
10
|
|
|
8
11
|
|
|
12
|
+
# todo(mm, 2024-11-08): frozen=True is getting pretty painful because ModuleStore has
|
|
13
|
+
# no type-safe way to modify just a single attribute. Consider unfreezing this
|
|
14
|
+
# (taking care to ensure that consumers of ModuleView still only get a read-only view).
|
|
9
15
|
@dataclass(frozen=True)
|
|
10
16
|
class AbsorbanceReaderSubState:
|
|
11
17
|
"""Absorbance-Plate-Reader-specific state."""
|
|
@@ -13,5 +19,17 @@ class AbsorbanceReaderSubState:
|
|
|
13
19
|
module_id: AbsorbanceReaderId
|
|
14
20
|
configured: bool
|
|
15
21
|
measured: bool
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
is_lid_on: bool
|
|
23
|
+
data: Optional[Dict[int, Dict[str, float]]]
|
|
24
|
+
configured_wavelengths: Optional[List[int]]
|
|
25
|
+
measure_mode: Optional[AbsorbanceReaderMeasureMode]
|
|
26
|
+
reference_wavelength: Optional[int]
|
|
27
|
+
|
|
28
|
+
def raise_if_lid_status_not_expected(self, lid_on_expected: bool) -> None:
|
|
29
|
+
"""Raise if the lid status is not correct."""
|
|
30
|
+
match = self.is_lid_on is lid_on_expected
|
|
31
|
+
if not match:
|
|
32
|
+
raise CannotPerformModuleAction(
|
|
33
|
+
"Cannot perform lid action because the lid is already "
|
|
34
|
+
f"{'closed' if self.is_lid_on else 'open'}"
|
|
35
|
+
)
|