opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.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/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 +7 -2
- 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 +245 -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 +2 -9
- 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 +3 -1
- 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/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.0a0.dist-info}/METADATA +4 -4
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/RECORD +187 -147
- 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.0a0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Command models to retrieve a labware from a Flex Stacker."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Optional, Literal, TYPE_CHECKING, Type, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from ..command import (
|
|
9
|
+
AbstractCommandImpl,
|
|
10
|
+
BaseCommand,
|
|
11
|
+
BaseCommandCreate,
|
|
12
|
+
SuccessData,
|
|
13
|
+
DefinedErrorData,
|
|
14
|
+
)
|
|
15
|
+
from ..flex_stacker.common import FlexStackerStallOrCollisionError
|
|
16
|
+
from ...errors import (
|
|
17
|
+
ErrorOccurrence,
|
|
18
|
+
CannotPerformModuleAction,
|
|
19
|
+
LabwareNotLoadedOnModuleError,
|
|
20
|
+
FlexStackerLabwarePoolNotYetDefinedError,
|
|
21
|
+
)
|
|
22
|
+
from ...resources import ModelUtils
|
|
23
|
+
from ...state import update_types
|
|
24
|
+
from ...types import (
|
|
25
|
+
LabwareLocationSequence,
|
|
26
|
+
InStackerHopperLocation,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from opentrons_shared_data.errors.exceptions import FlexStackerStallError
|
|
30
|
+
from opentrons.calibration_storage.helpers import uri_from_details
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
35
|
+
from opentrons.protocol_engine.state.module_substates import FlexStackerSubState
|
|
36
|
+
from opentrons.protocol_engine.execution import EquipmentHandler
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
StoreCommandType = Literal["flexStacker/store"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StoreParams(BaseModel):
|
|
43
|
+
"""Input parameters for a labware storage command."""
|
|
44
|
+
|
|
45
|
+
moduleId: str = Field(
|
|
46
|
+
...,
|
|
47
|
+
description="Unique ID of the flex stacker.",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class StoreResult(BaseModel):
|
|
52
|
+
"""Result data from a labware storage command."""
|
|
53
|
+
|
|
54
|
+
eventualDestinationLocationSequence: LabwareLocationSequence | None = Field(
|
|
55
|
+
None,
|
|
56
|
+
description=(
|
|
57
|
+
"The full location in which all labware moved by this command will eventually reside."
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
primaryOriginLocationSequence: LabwareLocationSequence | None = Field(
|
|
61
|
+
None, description=("The origin location of the primary labware.")
|
|
62
|
+
)
|
|
63
|
+
primaryLabwareId: str | None = Field(
|
|
64
|
+
None, description="The primary labware in the stack that was stored."
|
|
65
|
+
)
|
|
66
|
+
adapterOriginLocationSequence: LabwareLocationSequence | None = Field(
|
|
67
|
+
None, description=("The origin location of the adapter labware, if any.")
|
|
68
|
+
)
|
|
69
|
+
adapterLabwareId: str | None = Field(
|
|
70
|
+
None, description="The adapter in the stack that was stored, if any."
|
|
71
|
+
)
|
|
72
|
+
lidOriginLocationSequence: LabwareLocationSequence | None = Field(
|
|
73
|
+
None, description=("The origin location of the lid labware, if any.")
|
|
74
|
+
)
|
|
75
|
+
lidLabwareId: str | None = Field(
|
|
76
|
+
None, description="The lid in the stack that was stored, if any."
|
|
77
|
+
)
|
|
78
|
+
primaryLabwareURI: str = Field(
|
|
79
|
+
...,
|
|
80
|
+
description="The labware definition URI of the primary labware.",
|
|
81
|
+
)
|
|
82
|
+
adapterLabwareURI: str | None = Field(
|
|
83
|
+
None,
|
|
84
|
+
description="The labware definition URI of the adapter labware.",
|
|
85
|
+
)
|
|
86
|
+
lidLabwareURI: str | None = Field(
|
|
87
|
+
None,
|
|
88
|
+
description="The labware definition URI of the lid labware.",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
_ExecuteReturn = Union[
|
|
93
|
+
SuccessData[StoreResult],
|
|
94
|
+
DefinedErrorData[FlexStackerStallOrCollisionError],
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class StoreImpl(AbstractCommandImpl[StoreParams, _ExecuteReturn]):
|
|
99
|
+
"""Implementation of a labware storage command."""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
state_view: StateView,
|
|
104
|
+
equipment: EquipmentHandler,
|
|
105
|
+
model_utils: ModelUtils,
|
|
106
|
+
**kwargs: object,
|
|
107
|
+
) -> None:
|
|
108
|
+
self._state_view = state_view
|
|
109
|
+
self._equipment = equipment
|
|
110
|
+
self._model_utils = model_utils
|
|
111
|
+
|
|
112
|
+
def _verify_labware_to_store(
|
|
113
|
+
self, params: StoreParams, stacker_state: FlexStackerSubState
|
|
114
|
+
) -> tuple[str, str | None, str | None]:
|
|
115
|
+
try:
|
|
116
|
+
bottom_id = self._state_view.labware.get_id_by_module(params.moduleId)
|
|
117
|
+
except LabwareNotLoadedOnModuleError:
|
|
118
|
+
raise CannotPerformModuleAction(
|
|
119
|
+
"Cannot store labware if Flex Stacker carriage is empty"
|
|
120
|
+
)
|
|
121
|
+
labware_ids = self._state_view.labware.get_labware_stack_from_parent(bottom_id)
|
|
122
|
+
labware_defs = [
|
|
123
|
+
self._state_view.labware.get_definition(id) for id in labware_ids
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
lid_id: str | None = None
|
|
127
|
+
|
|
128
|
+
pool_list = stacker_state.get_pool_definition_ordered_list()
|
|
129
|
+
assert pool_list is not None
|
|
130
|
+
if len(labware_ids) != len(pool_list):
|
|
131
|
+
raise CannotPerformModuleAction(
|
|
132
|
+
"Cannot store labware stack that does not correspond with Flex Stacker configuration"
|
|
133
|
+
)
|
|
134
|
+
if stacker_state.pool_lid_definition is not None:
|
|
135
|
+
if labware_defs[-1] != stacker_state.pool_lid_definition:
|
|
136
|
+
raise CannotPerformModuleAction(
|
|
137
|
+
"Cannot store labware stack that does not correspond with Flex Stacker configuration"
|
|
138
|
+
)
|
|
139
|
+
lid_id = labware_ids[-1]
|
|
140
|
+
|
|
141
|
+
if stacker_state.pool_adapter_definition is not None:
|
|
142
|
+
if (
|
|
143
|
+
labware_defs[0] != stacker_state.pool_adapter_definition
|
|
144
|
+
or labware_defs[1] != stacker_state.pool_primary_definition
|
|
145
|
+
):
|
|
146
|
+
raise CannotPerformModuleAction(
|
|
147
|
+
"Cannot store labware stack that does not correspond with Flex Stacker configuration"
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
return labware_ids[1], labware_ids[0], lid_id
|
|
151
|
+
else:
|
|
152
|
+
if labware_defs[0] != stacker_state.pool_primary_definition:
|
|
153
|
+
raise CannotPerformModuleAction(
|
|
154
|
+
"Cannot store labware stack that does not correspond with Flex Stacker configuration"
|
|
155
|
+
)
|
|
156
|
+
return labware_ids[0], None, lid_id
|
|
157
|
+
|
|
158
|
+
async def execute(self, params: StoreParams) -> _ExecuteReturn:
|
|
159
|
+
"""Execute the labware storage command."""
|
|
160
|
+
stacker_state = self._state_view.modules.get_flex_stacker_substate(
|
|
161
|
+
params.moduleId
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if stacker_state.pool_count == stacker_state.max_pool_count:
|
|
165
|
+
raise CannotPerformModuleAction(
|
|
166
|
+
"Cannot store labware in Flex Stacker while it is full"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
pool_definitions = stacker_state.get_pool_definition_ordered_list()
|
|
170
|
+
if pool_definitions is None:
|
|
171
|
+
raise FlexStackerLabwarePoolNotYetDefinedError(
|
|
172
|
+
message="The Flex Stacker has not been configured yet and cannot be filled."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
primary_id, maybe_adapter_id, maybe_lid_id = self._verify_labware_to_store(
|
|
176
|
+
params, stacker_state
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Allow propagation of ModuleNotAttachedError.
|
|
180
|
+
stacker_hw = self._equipment.get_module_hardware_api(stacker_state.module_id)
|
|
181
|
+
|
|
182
|
+
eventual_target_location_sequence = (
|
|
183
|
+
self._state_view.geometry.get_predicted_location_sequence(
|
|
184
|
+
InStackerHopperLocation(moduleId=params.moduleId)
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
stack_height = self._state_view.geometry.get_height_of_labware_stack(
|
|
188
|
+
pool_definitions
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
state_update = update_types.StateUpdate()
|
|
192
|
+
try:
|
|
193
|
+
if stacker_hw is not None:
|
|
194
|
+
await stacker_hw.store_labware(labware_height=stack_height)
|
|
195
|
+
except FlexStackerStallError as e:
|
|
196
|
+
return DefinedErrorData(
|
|
197
|
+
public=FlexStackerStallOrCollisionError(
|
|
198
|
+
id=self._model_utils.generate_id(),
|
|
199
|
+
createdAt=self._model_utils.get_timestamp(),
|
|
200
|
+
wrappedErrors=[
|
|
201
|
+
ErrorOccurrence.from_failed(
|
|
202
|
+
id=self._model_utils.generate_id(),
|
|
203
|
+
createdAt=self._model_utils.get_timestamp(),
|
|
204
|
+
error=e,
|
|
205
|
+
)
|
|
206
|
+
],
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
id_list = [
|
|
211
|
+
id for id in (primary_id, maybe_adapter_id, maybe_lid_id) if id is not None
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
state_update.set_batch_labware_location(
|
|
215
|
+
new_locations_by_id={
|
|
216
|
+
id: InStackerHopperLocation(moduleId=params.moduleId) for id in id_list
|
|
217
|
+
},
|
|
218
|
+
new_offset_ids_by_id={id: None for id in id_list},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
state_update.update_flex_stacker_labware_pool_count(
|
|
222
|
+
module_id=params.moduleId, count=stacker_state.pool_count + 1
|
|
223
|
+
)
|
|
224
|
+
if stacker_state.pool_primary_definition is None:
|
|
225
|
+
raise FlexStackerLabwarePoolNotYetDefinedError(
|
|
226
|
+
"The Primary Labware must be defined in the stacker pool."
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return SuccessData(
|
|
230
|
+
public=StoreResult(
|
|
231
|
+
eventualDestinationLocationSequence=eventual_target_location_sequence,
|
|
232
|
+
primaryOriginLocationSequence=self._state_view.geometry.get_location_sequence(
|
|
233
|
+
primary_id
|
|
234
|
+
),
|
|
235
|
+
primaryLabwareId=primary_id,
|
|
236
|
+
adapterOriginLocationSequence=(
|
|
237
|
+
self._state_view.geometry.get_location_sequence(maybe_adapter_id)
|
|
238
|
+
if maybe_adapter_id is not None
|
|
239
|
+
else None
|
|
240
|
+
),
|
|
241
|
+
adapterLabwareId=maybe_adapter_id,
|
|
242
|
+
lidOriginLocationSequence=(
|
|
243
|
+
self._state_view.geometry.get_location_sequence(maybe_lid_id)
|
|
244
|
+
if maybe_lid_id is not None
|
|
245
|
+
else None
|
|
246
|
+
),
|
|
247
|
+
lidLabwareId=maybe_lid_id,
|
|
248
|
+
primaryLabwareURI=uri_from_details(
|
|
249
|
+
stacker_state.pool_primary_definition.namespace,
|
|
250
|
+
stacker_state.pool_primary_definition.parameters.loadName,
|
|
251
|
+
stacker_state.pool_primary_definition.version,
|
|
252
|
+
),
|
|
253
|
+
adapterLabwareURI=uri_from_details(
|
|
254
|
+
stacker_state.pool_adapter_definition.namespace,
|
|
255
|
+
stacker_state.pool_adapter_definition.parameters.loadName,
|
|
256
|
+
stacker_state.pool_adapter_definition.version,
|
|
257
|
+
)
|
|
258
|
+
if stacker_state.pool_adapter_definition is not None
|
|
259
|
+
else None,
|
|
260
|
+
lidLabwareURI=uri_from_details(
|
|
261
|
+
stacker_state.pool_lid_definition.namespace,
|
|
262
|
+
stacker_state.pool_lid_definition.parameters.loadName,
|
|
263
|
+
stacker_state.pool_lid_definition.version,
|
|
264
|
+
)
|
|
265
|
+
if stacker_state.pool_lid_definition is not None
|
|
266
|
+
else None,
|
|
267
|
+
),
|
|
268
|
+
state_update=state_update,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class Store(BaseCommand[StoreParams, StoreResult, ErrorOccurrence]):
|
|
273
|
+
"""A command to store a labware in a Flex Stacker."""
|
|
274
|
+
|
|
275
|
+
commandType: StoreCommandType = "flexStacker/store"
|
|
276
|
+
params: StoreParams
|
|
277
|
+
result: Optional[StoreResult] = None
|
|
278
|
+
|
|
279
|
+
_ImplementationCls: Type[StoreImpl] = StoreImpl
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class StoreCreate(BaseCommandCreate[StoreParams]):
|
|
283
|
+
"""A request to execute a Flex Stacker store command."""
|
|
284
|
+
|
|
285
|
+
commandType: StoreCommandType = "flexStacker/store"
|
|
286
|
+
params: StoreParams
|
|
287
|
+
|
|
288
|
+
_CommandCls: Type[Store] = Store
|
|
@@ -5,6 +5,9 @@ import argparse
|
|
|
5
5
|
import sys
|
|
6
6
|
from opentrons.protocol_engine.commands.command_unions import CommandCreateAdapter
|
|
7
7
|
|
|
8
|
+
from opentrons_shared_data.command import get_newest_schema_version
|
|
9
|
+
from opentrons_shared_data.load import get_shared_data_root
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
def generate_command_schema(version: str) -> str:
|
|
10
13
|
"""Generate a JSON Schema that all valid create commands can validate against."""
|
|
@@ -14,6 +17,13 @@ def generate_command_schema(version: str) -> str:
|
|
|
14
17
|
return json.dumps(schema_as_dict, indent=2, sort_keys=True)
|
|
15
18
|
|
|
16
19
|
|
|
20
|
+
def write_command_schema(json_string: str, version: str) -> None:
|
|
21
|
+
"""Write a JSON command schema to the shared-data command schema directory."""
|
|
22
|
+
path = get_shared_data_root() / "command" / "schemas" / f"{version}.json"
|
|
23
|
+
with open(path, "w") as schema_file:
|
|
24
|
+
schema_file.write(json_string)
|
|
25
|
+
|
|
26
|
+
|
|
17
27
|
if __name__ == "__main__":
|
|
18
28
|
parser = argparse.ArgumentParser(
|
|
19
29
|
prog="generate_command_schema",
|
|
@@ -22,10 +32,29 @@ if __name__ == "__main__":
|
|
|
22
32
|
parser.add_argument(
|
|
23
33
|
"version",
|
|
24
34
|
type=str,
|
|
25
|
-
|
|
35
|
+
nargs="?",
|
|
36
|
+
help="The command schema version. This is a single integer (e.g. 7) that will be used to name the generated"
|
|
37
|
+
" schema file. If not included, it will automatically use the latest version in shared-data.",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--overwrite-shared-data",
|
|
41
|
+
action="store_true",
|
|
42
|
+
help="If used, overwrites the specified or automatically chosen command schema version in shared-data."
|
|
43
|
+
" If not included, the generated schema will be printed to stdout.",
|
|
26
44
|
)
|
|
27
45
|
args = parser.parse_args()
|
|
28
|
-
|
|
46
|
+
|
|
47
|
+
if args.version is None:
|
|
48
|
+
version_string = get_newest_schema_version()
|
|
49
|
+
else:
|
|
50
|
+
version_string = args.version
|
|
51
|
+
|
|
52
|
+
command_schema = generate_command_schema(version_string)
|
|
53
|
+
|
|
54
|
+
if args.overwrite_shared_data:
|
|
55
|
+
write_command_schema(command_schema, version_string)
|
|
56
|
+
else:
|
|
57
|
+
print(command_schema)
|
|
29
58
|
|
|
30
59
|
sys.exit()
|
|
31
60
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Helpers for commands that alter the position of labware."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from ..types import LabwareLocationSequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LabwareHandlingResultMixin(BaseModel):
|
|
9
|
+
"""A result for commands that create a labware entity."""
|
|
10
|
+
|
|
11
|
+
labwareId: str = Field(..., description="The id of the labware.")
|
|
12
|
+
locationSequence: LabwareLocationSequence | None = Field(
|
|
13
|
+
None,
|
|
14
|
+
description="The full location down to the deck on which this labware exists.",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LabwarePositionResultMixin(LabwareHandlingResultMixin):
|
|
19
|
+
"""A result for commands that create an offsetable labware entity."""
|
|
20
|
+
|
|
21
|
+
offsetId: str | None = Field(
|
|
22
|
+
None,
|
|
23
|
+
description="An ID referencing the labware offset that will apply to this labware in this location.",
|
|
24
|
+
)
|
|
@@ -23,6 +23,7 @@ from opentrons_shared_data.errors.exceptions import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
from ..types import DeckPoint
|
|
26
|
+
from ..types.liquid_level_detection import LiquidTrackingType
|
|
26
27
|
from .pipetting_common import (
|
|
27
28
|
LiquidNotFoundError,
|
|
28
29
|
PipetteIdMixin,
|
|
@@ -80,7 +81,7 @@ class TryLiquidProbeParams(_CommonParams):
|
|
|
80
81
|
class LiquidProbeResult(DestinationPositionResult):
|
|
81
82
|
"""Result data from the execution of a `liquidProbe` command."""
|
|
82
83
|
|
|
83
|
-
z_position:
|
|
84
|
+
z_position: LiquidTrackingType = Field(
|
|
84
85
|
..., description="The Z coordinate, in mm, of the found liquid in deck space."
|
|
85
86
|
)
|
|
86
87
|
# New fields should use camelCase. z_position is snake_case for historical reasons.
|
|
@@ -89,7 +90,7 @@ class LiquidProbeResult(DestinationPositionResult):
|
|
|
89
90
|
class TryLiquidProbeResult(DestinationPositionResult):
|
|
90
91
|
"""Result data from the execution of a `tryLiquidProbe` command."""
|
|
91
92
|
|
|
92
|
-
z_position:
|
|
93
|
+
z_position: Union[LiquidTrackingType, SkipJsonSchema[None]] = Field(
|
|
93
94
|
...,
|
|
94
95
|
description=(
|
|
95
96
|
"The Z coordinate, in mm, of the found liquid in deck space."
|
|
@@ -116,8 +117,7 @@ class _ExecuteCommonResult(NamedTuple):
|
|
|
116
117
|
# If the probe succeeded, the z_pos that it returned.
|
|
117
118
|
# Or, if the probe found no liquid, the error representing that,
|
|
118
119
|
# so calling code can propagate those details up.
|
|
119
|
-
z_pos_or_error:
|
|
120
|
-
|
|
120
|
+
z_pos_or_error: LiquidTrackingType | PipetteLiquidNotFoundError | PipetteOverpressureError
|
|
121
121
|
state_update: update_types.StateUpdate
|
|
122
122
|
deck_point: DeckPoint
|
|
123
123
|
|
|
@@ -190,6 +190,9 @@ async def _execute_common( # noqa: C901
|
|
|
190
190
|
well_location=params.wellLocation,
|
|
191
191
|
)
|
|
192
192
|
except PipetteLiquidNotFoundError as exception:
|
|
193
|
+
move_result.state_update.set_pipette_ready_to_aspirate(
|
|
194
|
+
pipette_id=pipette_id, ready_to_aspirate=True
|
|
195
|
+
)
|
|
193
196
|
return _ExecuteCommonResult(
|
|
194
197
|
z_pos_or_error=exception,
|
|
195
198
|
state_update=move_result.state_update,
|
|
@@ -223,6 +226,9 @@ async def _execute_common( # noqa: C901
|
|
|
223
226
|
),
|
|
224
227
|
)
|
|
225
228
|
else:
|
|
229
|
+
move_result.state_update.set_pipette_ready_to_aspirate(
|
|
230
|
+
pipette_id=pipette_id, ready_to_aspirate=True
|
|
231
|
+
)
|
|
226
232
|
return _ExecuteCommonResult(
|
|
227
233
|
z_pos_or_error=z_pos,
|
|
228
234
|
state_update=move_result.state_update,
|
|
@@ -303,12 +309,13 @@ class LiquidProbeImplementation(
|
|
|
303
309
|
)
|
|
304
310
|
else:
|
|
305
311
|
try:
|
|
306
|
-
well_volume:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
+
well_volume: Union[
|
|
313
|
+
LiquidTrackingType,
|
|
314
|
+
update_types.ClearType,
|
|
315
|
+
] = self._state_view.geometry.get_well_volume_at_height(
|
|
316
|
+
labware_id=params.labwareId,
|
|
317
|
+
well_name=params.wellName,
|
|
318
|
+
height=z_pos_or_error,
|
|
312
319
|
)
|
|
313
320
|
except IncompleteLabwareDefinitionError:
|
|
314
321
|
well_volume = update_types.CLEAR
|
|
@@ -370,7 +377,10 @@ class TryLiquidProbeImplementation(
|
|
|
370
377
|
z_pos_or_error, (PipetteLiquidNotFoundError, PipetteOverpressureError)
|
|
371
378
|
):
|
|
372
379
|
z_pos = None
|
|
373
|
-
well_volume:
|
|
380
|
+
well_volume: Union[
|
|
381
|
+
LiquidTrackingType,
|
|
382
|
+
update_types.ClearType,
|
|
383
|
+
] = update_types.CLEAR
|
|
374
384
|
else:
|
|
375
385
|
z_pos = z_pos_or_error
|
|
376
386
|
try:
|
|
@@ -387,7 +397,6 @@ class TryLiquidProbeImplementation(
|
|
|
387
397
|
volume=well_volume,
|
|
388
398
|
last_probed=self._model_utils.get_timestamp(),
|
|
389
399
|
)
|
|
390
|
-
|
|
391
400
|
return SuccessData(
|
|
392
401
|
public=TryLiquidProbeResult(
|
|
393
402
|
z_position=z_pos,
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
"""Load labware command request, result, and implementation models."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
from typing import TYPE_CHECKING, Optional, Type, Any
|
|
4
5
|
|
|
5
6
|
from pydantic import BaseModel, Field
|
|
6
7
|
from pydantic.json_schema import SkipJsonSchema
|
|
7
|
-
from typing_extensions import Literal
|
|
8
|
+
from typing_extensions import Literal, TypeGuard
|
|
8
9
|
|
|
9
10
|
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
10
11
|
|
|
11
12
|
from ..errors import LabwareIsNotAllowedInLocationError
|
|
12
13
|
from ..resources import labware_validation, fixture_validation
|
|
13
14
|
from ..types import (
|
|
14
|
-
|
|
15
|
+
LoadableLabwareLocation,
|
|
15
16
|
ModuleLocation,
|
|
16
17
|
ModuleModel,
|
|
17
18
|
OnLabwareLocation,
|
|
18
19
|
DeckSlotLocation,
|
|
19
20
|
AddressableAreaLocation,
|
|
21
|
+
LoadedModule,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
24
|
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
25
|
+
from .labware_handling_common import LabwarePositionResultMixin
|
|
23
26
|
from ..errors.error_occurrence import ErrorOccurrence
|
|
24
27
|
from ..state.update_types import StateUpdate
|
|
25
28
|
|
|
@@ -38,7 +41,7 @@ def _remove_default(s: dict[str, Any]) -> None:
|
|
|
38
41
|
class LoadLabwareParams(BaseModel):
|
|
39
42
|
"""Payload required to load a labware into a slot."""
|
|
40
43
|
|
|
41
|
-
location:
|
|
44
|
+
location: LoadableLabwareLocation = Field(
|
|
42
45
|
...,
|
|
43
46
|
description="Location the labware should be loaded into.",
|
|
44
47
|
)
|
|
@@ -71,30 +74,13 @@ class LoadLabwareParams(BaseModel):
|
|
|
71
74
|
)
|
|
72
75
|
|
|
73
76
|
|
|
74
|
-
class LoadLabwareResult(
|
|
77
|
+
class LoadLabwareResult(LabwarePositionResultMixin):
|
|
75
78
|
"""Result data from the execution of a LoadLabware command."""
|
|
76
79
|
|
|
77
|
-
labwareId: str = Field(
|
|
78
|
-
...,
|
|
79
|
-
description="An ID to reference this labware in subsequent commands.",
|
|
80
|
-
)
|
|
81
80
|
definition: LabwareDefinition = Field(
|
|
82
81
|
...,
|
|
83
82
|
description="The full definition data for this labware.",
|
|
84
83
|
)
|
|
85
|
-
offsetId: Optional[str] = Field(
|
|
86
|
-
# Default `None` instead of `...` so this field shows up as non-required in
|
|
87
|
-
# OpenAPI. The server is allowed to omit it or make it null.
|
|
88
|
-
None,
|
|
89
|
-
description=(
|
|
90
|
-
"An ID referencing the labware offset that will apply"
|
|
91
|
-
" to the newly-placed labware."
|
|
92
|
-
" This offset will be in effect until the labware is moved"
|
|
93
|
-
" with a `moveLabware` command."
|
|
94
|
-
" Null or undefined means no offset applies,"
|
|
95
|
-
" so the default of (0, 0, 0) will be used."
|
|
96
|
-
),
|
|
97
|
-
)
|
|
98
84
|
|
|
99
85
|
|
|
100
86
|
class LoadLabwareImplementation(
|
|
@@ -108,6 +94,15 @@ class LoadLabwareImplementation(
|
|
|
108
94
|
self._equipment = equipment
|
|
109
95
|
self._state_view = state_view
|
|
110
96
|
|
|
97
|
+
def _is_loading_to_module(
|
|
98
|
+
self, location: LoadableLabwareLocation, module_model: ModuleModel
|
|
99
|
+
) -> TypeGuard[ModuleLocation]:
|
|
100
|
+
if not isinstance(location, ModuleLocation):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
module: LoadedModule = self._state_view.modules.get(location.moduleId)
|
|
104
|
+
return module.model == module_model
|
|
105
|
+
|
|
111
106
|
async def execute(
|
|
112
107
|
self, params: LoadLabwareParams
|
|
113
108
|
) -> SuccessData[LoadLabwareResult]:
|
|
@@ -148,6 +143,7 @@ class LoadLabwareImplementation(
|
|
|
148
143
|
verified_location = self._state_view.geometry.ensure_location_not_occupied(
|
|
149
144
|
params.location
|
|
150
145
|
)
|
|
146
|
+
|
|
151
147
|
loaded_labware = await self._equipment.load_labware(
|
|
152
148
|
load_name=params.loadName,
|
|
153
149
|
namespace=params.namespace,
|
|
@@ -164,34 +160,38 @@ class LoadLabwareImplementation(
|
|
|
164
160
|
display_name=params.displayName,
|
|
165
161
|
)
|
|
166
162
|
|
|
167
|
-
# TODO(jbl 2023-06-23) these validation checks happen after the labware is loaded, because they rely on
|
|
168
|
-
# on the definition. In practice this will not cause any issues since they will raise protocol ending
|
|
169
|
-
# exception, but for correctness should be refactored to do this check beforehand.
|
|
170
163
|
if isinstance(verified_location, OnLabwareLocation):
|
|
171
164
|
self._state_view.labware.raise_if_labware_cannot_be_stacked(
|
|
172
165
|
top_labware_definition=loaded_labware.definition,
|
|
173
166
|
bottom_labware_id=verified_location.labwareId,
|
|
174
167
|
)
|
|
175
168
|
# Validate load location is valid for lids
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
definition=loaded_labware.definition
|
|
179
|
-
)
|
|
180
|
-
and loaded_labware.definition.compatibleParentLabware is not None
|
|
181
|
-
and self._state_view.labware.get_load_name(verified_location.labwareId)
|
|
182
|
-
not in loaded_labware.definition.compatibleParentLabware
|
|
169
|
+
if labware_validation.validate_definition_is_lid(
|
|
170
|
+
definition=loaded_labware.definition
|
|
183
171
|
):
|
|
184
|
-
|
|
185
|
-
|
|
172
|
+
# This parent is assumed to be compatible, unless the lid enumerates
|
|
173
|
+
# all its compatible parents and this parent is missing from the list.
|
|
174
|
+
parent_is_incompatible = (
|
|
175
|
+
loaded_labware.definition.compatibleParentLabware is not None
|
|
176
|
+
and self._state_view.labware.get_load_name(
|
|
177
|
+
verified_location.labwareId
|
|
178
|
+
)
|
|
179
|
+
not in loaded_labware.definition.compatibleParentLabware
|
|
186
180
|
)
|
|
187
181
|
|
|
182
|
+
if parent_is_incompatible:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"Labware Lid {params.loadName} may not be loaded on parent labware"
|
|
185
|
+
f" {self._state_view.labware.get_display_name(verified_location.labwareId)}."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
188
|
# Validate labware for the absorbance reader
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
if self._is_loading_to_module(
|
|
190
|
+
params.location, ModuleModel.ABSORBANCE_READER_V1
|
|
191
|
+
):
|
|
192
|
+
self._state_view.labware.raise_if_labware_incompatible_with_plate_reader(
|
|
193
|
+
loaded_labware.definition
|
|
194
|
+
)
|
|
195
195
|
|
|
196
196
|
self._state_view.labware.raise_if_labware_cannot_be_ondeck(
|
|
197
197
|
location=params.location, labware_definition=loaded_labware.definition
|
|
@@ -202,6 +202,9 @@ class LoadLabwareImplementation(
|
|
|
202
202
|
labwareId=loaded_labware.labware_id,
|
|
203
203
|
definition=loaded_labware.definition,
|
|
204
204
|
offsetId=loaded_labware.offsetId,
|
|
205
|
+
locationSequence=self._state_view.geometry.get_predicted_location_sequence(
|
|
206
|
+
verified_location,
|
|
207
|
+
),
|
|
205
208
|
),
|
|
206
209
|
state_update=state_update,
|
|
207
210
|
)
|