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.
Files changed (192) hide show
  1. opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
  2. opentrons/calibration_storage/ot2/tip_length.py +6 -6
  3. opentrons/config/advanced_settings.py +9 -11
  4. opentrons/config/feature_flags.py +0 -4
  5. opentrons/config/reset.py +7 -2
  6. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  7. opentrons/drivers/asyncio/communication/async_serial.py +4 -0
  8. opentrons/drivers/asyncio/communication/errors.py +41 -8
  9. opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
  10. opentrons/drivers/flex_stacker/__init__.py +9 -3
  11. opentrons/drivers/flex_stacker/abstract.py +140 -15
  12. opentrons/drivers/flex_stacker/driver.py +593 -47
  13. opentrons/drivers/flex_stacker/errors.py +64 -0
  14. opentrons/drivers/flex_stacker/simulator.py +222 -24
  15. opentrons/drivers/flex_stacker/types.py +211 -15
  16. opentrons/drivers/flex_stacker/utils.py +19 -0
  17. opentrons/execute.py +4 -2
  18. opentrons/hardware_control/api.py +5 -0
  19. opentrons/hardware_control/backends/flex_protocol.py +4 -0
  20. opentrons/hardware_control/backends/ot3controller.py +12 -1
  21. opentrons/hardware_control/backends/ot3simulator.py +3 -0
  22. opentrons/hardware_control/backends/subsystem_manager.py +8 -4
  23. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
  24. opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
  25. opentrons/hardware_control/modules/__init__.py +12 -1
  26. opentrons/hardware_control/modules/absorbance_reader.py +11 -9
  27. opentrons/hardware_control/modules/flex_stacker.py +498 -0
  28. opentrons/hardware_control/modules/heater_shaker.py +12 -10
  29. opentrons/hardware_control/modules/magdeck.py +5 -1
  30. opentrons/hardware_control/modules/tempdeck.py +5 -1
  31. opentrons/hardware_control/modules/thermocycler.py +15 -14
  32. opentrons/hardware_control/modules/types.py +191 -1
  33. opentrons/hardware_control/modules/utils.py +3 -0
  34. opentrons/hardware_control/motion_utilities.py +20 -0
  35. opentrons/hardware_control/ot3api.py +145 -15
  36. opentrons/hardware_control/protocols/liquid_handler.py +47 -1
  37. opentrons/hardware_control/types.py +6 -0
  38. opentrons/legacy_commands/commands.py +19 -3
  39. opentrons/legacy_commands/helpers.py +15 -0
  40. opentrons/legacy_commands/types.py +3 -2
  41. opentrons/protocol_api/__init__.py +2 -0
  42. opentrons/protocol_api/_liquid.py +39 -8
  43. opentrons/protocol_api/_liquid_properties.py +20 -19
  44. opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
  45. opentrons/protocol_api/core/common.py +3 -1
  46. opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
  47. opentrons/protocol_api/core/engine/instrument.py +1233 -65
  48. opentrons/protocol_api/core/engine/labware.py +8 -4
  49. opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
  50. opentrons/protocol_api/core/engine/module_core.py +118 -2
  51. opentrons/protocol_api/core/engine/protocol.py +253 -11
  52. opentrons/protocol_api/core/engine/stringify.py +19 -8
  53. opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
  54. opentrons/protocol_api/core/engine/well.py +60 -5
  55. opentrons/protocol_api/core/instrument.py +65 -19
  56. opentrons/protocol_api/core/labware.py +6 -2
  57. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  58. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
  59. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  60. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  61. opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
  62. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  63. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  64. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
  66. opentrons/protocol_api/core/module.py +43 -0
  67. opentrons/protocol_api/core/protocol.py +33 -0
  68. opentrons/protocol_api/core/well.py +21 -1
  69. opentrons/protocol_api/instrument_context.py +246 -123
  70. opentrons/protocol_api/labware.py +75 -11
  71. opentrons/protocol_api/module_contexts.py +140 -0
  72. opentrons/protocol_api/protocol_context.py +156 -16
  73. opentrons/protocol_api/validation.py +51 -41
  74. opentrons/protocol_engine/__init__.py +21 -2
  75. opentrons/protocol_engine/actions/actions.py +5 -5
  76. opentrons/protocol_engine/clients/sync_client.py +6 -0
  77. opentrons/protocol_engine/commands/__init__.py +30 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  79. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  80. opentrons/protocol_engine/commands/aspirate.py +6 -2
  81. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  82. opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
  83. opentrons/protocol_engine/commands/blow_out.py +2 -0
  84. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  85. opentrons/protocol_engine/commands/command_unions.py +69 -0
  86. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  87. opentrons/protocol_engine/commands/dispense.py +3 -1
  88. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  89. opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
  90. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  91. opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
  92. opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
  93. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
  94. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  95. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  96. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  97. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  98. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  99. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  100. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  101. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  102. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  103. opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
  104. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  105. opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
  106. opentrons/protocol_engine/commands/liquid_probe.py +21 -12
  107. opentrons/protocol_engine/commands/load_labware.py +42 -39
  108. opentrons/protocol_engine/commands/load_lid.py +21 -13
  109. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  110. opentrons/protocol_engine/commands/load_module.py +18 -17
  111. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  112. opentrons/protocol_engine/commands/move_labware.py +139 -20
  113. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  114. opentrons/protocol_engine/commands/pipetting_common.py +154 -7
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  118. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  119. opentrons/protocol_engine/errors/__init__.py +8 -0
  120. opentrons/protocol_engine/errors/exceptions.py +50 -0
  121. opentrons/protocol_engine/execution/equipment.py +123 -106
  122. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  123. opentrons/protocol_engine/execution/pipetting.py +233 -26
  124. opentrons/protocol_engine/execution/tip_handler.py +14 -5
  125. opentrons/protocol_engine/labware_offset_standardization.py +173 -0
  126. opentrons/protocol_engine/protocol_engine.py +22 -13
  127. opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
  128. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  129. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  130. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  131. opentrons/protocol_engine/slot_standardization.py +11 -23
  132. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  133. opentrons/protocol_engine/state/frustum_helpers.py +26 -10
  134. opentrons/protocol_engine/state/geometry.py +683 -100
  135. opentrons/protocol_engine/state/labware.py +252 -55
  136. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  137. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  138. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  139. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  140. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  141. opentrons/protocol_engine/state/modules.py +178 -52
  142. opentrons/protocol_engine/state/pipettes.py +54 -0
  143. opentrons/protocol_engine/state/state.py +1 -1
  144. opentrons/protocol_engine/state/tips.py +14 -0
  145. opentrons/protocol_engine/state/update_types.py +180 -25
  146. opentrons/protocol_engine/state/wells.py +54 -8
  147. opentrons/protocol_engine/types/__init__.py +292 -0
  148. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  149. opentrons/protocol_engine/types/command_annotations.py +53 -0
  150. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  151. opentrons/protocol_engine/types/execution.py +96 -0
  152. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  153. opentrons/protocol_engine/types/instrument.py +47 -0
  154. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  155. opentrons/protocol_engine/types/labware.py +110 -0
  156. opentrons/protocol_engine/types/labware_movement.py +22 -0
  157. opentrons/protocol_engine/types/labware_offset_location.py +108 -0
  158. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  159. opentrons/protocol_engine/types/liquid.py +40 -0
  160. opentrons/protocol_engine/types/liquid_class.py +59 -0
  161. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  162. opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
  163. opentrons/protocol_engine/types/location.py +193 -0
  164. opentrons/protocol_engine/types/module.py +269 -0
  165. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  166. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  167. opentrons/protocol_engine/types/tip.py +18 -0
  168. opentrons/protocol_engine/types/util.py +21 -0
  169. opentrons/protocol_engine/types/well_position.py +107 -0
  170. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  171. opentrons/protocol_reader/file_format_validator.py +5 -3
  172. opentrons/protocol_runner/json_translator.py +4 -2
  173. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  174. opentrons/protocol_runner/run_orchestrator.py +4 -1
  175. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  176. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  177. opentrons/protocols/api_support/definitions.py +1 -1
  178. opentrons/protocols/api_support/instrument.py +16 -3
  179. opentrons/protocols/labware.py +5 -6
  180. opentrons/protocols/models/__init__.py +0 -21
  181. opentrons/simulate.py +4 -2
  182. opentrons/types.py +15 -6
  183. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
  184. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
  185. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  186. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  187. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  188. opentrons/protocol_engine/types.py +0 -1311
  189. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
  190. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
  191. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
  192. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,70 @@
1
+ """Command models to open the latch of a Flex Stacker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from __future__ import annotations
6
+ from typing import Optional, Literal, TYPE_CHECKING
7
+ from typing_extensions import Type
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
12
+ from ...errors import (
13
+ ErrorOccurrence,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from ...state.state import StateView
18
+ from opentrons.protocol_engine.execution import EquipmentHandler
19
+
20
+ OpenLatchCommandType = Literal["flexStacker/openLatch"]
21
+
22
+
23
+ class OpenLatchParams(BaseModel):
24
+ """The parameters defining how a stacker should open its latch."""
25
+
26
+ moduleId: str = Field(..., description="Unique ID of the Flex Stacker")
27
+
28
+
29
+ class OpenLatchResult(BaseModel):
30
+ """Result data from a stacker OpenLatch command."""
31
+
32
+
33
+ class OpenLatchImpl(AbstractCommandImpl[OpenLatchParams, SuccessData[OpenLatchResult]]):
34
+ """Implementation of a stacker OpenLatch command."""
35
+
36
+ def __init__(
37
+ self, state_view: StateView, equipment: EquipmentHandler, **kwargs: object
38
+ ) -> None:
39
+ self._state_view = state_view
40
+ self._equipment = equipment
41
+
42
+ async def execute(self, params: OpenLatchParams) -> SuccessData[OpenLatchResult]:
43
+ """Execute the stacker OpenLatch command."""
44
+ stacker_state = self._state_view.modules.get_flex_stacker_substate(
45
+ params.moduleId
46
+ )
47
+ stacker_hw = self._equipment.get_module_hardware_api(stacker_state.module_id)
48
+
49
+ if stacker_hw is not None:
50
+ await stacker_hw.open_latch()
51
+ return SuccessData(public=OpenLatchResult())
52
+
53
+
54
+ class OpenLatch(BaseCommand[OpenLatchParams, OpenLatchResult, ErrorOccurrence]):
55
+ """A command to OpenLatch the Flex Stacker of labware."""
56
+
57
+ commandType: OpenLatchCommandType = "flexStacker/openLatch"
58
+ params: OpenLatchParams
59
+ result: Optional[OpenLatchResult] = None
60
+
61
+ _ImplementationCls: Type[OpenLatchImpl] = OpenLatchImpl
62
+
63
+
64
+ class OpenLatchCreate(BaseCommandCreate[OpenLatchParams]):
65
+ """A request to execute a Flex Stacker OpenLatch command."""
66
+
67
+ commandType: OpenLatchCommandType = "flexStacker/openLatch"
68
+ params: OpenLatchParams
69
+
70
+ _CommandCls: Type[OpenLatch] = OpenLatch
@@ -0,0 +1,112 @@
1
+ """Command models to prepare the stacker shuttle for movement."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from __future__ import annotations
6
+ from typing import Literal, Union, TYPE_CHECKING
7
+ from typing_extensions import Type
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+ from .common import FlexStackerStallOrCollisionError
12
+ from opentrons_shared_data.errors.exceptions import FlexStackerStallError
13
+
14
+ from ..command import (
15
+ AbstractCommandImpl,
16
+ BaseCommand,
17
+ BaseCommandCreate,
18
+ SuccessData,
19
+ DefinedErrorData,
20
+ )
21
+ from ...errors import ErrorOccurrence
22
+ from ...resources import ModelUtils
23
+
24
+ if TYPE_CHECKING:
25
+ from ...state.state import StateView
26
+ from ...execution import EquipmentHandler
27
+
28
+ PrepareShuttleCommandType = Literal["flexStacker/prepareShuttle"]
29
+
30
+
31
+ class PrepareShuttleParams(BaseModel):
32
+ """The parameters for a PrepareShuttle command."""
33
+
34
+ moduleId: str = Field(..., description="Unique ID of the Flex Stacker")
35
+ ignoreLatch: bool = Field(
36
+ default=False, description="Ignore the latch state of the shuttle"
37
+ )
38
+
39
+
40
+ class PrepareShuttleResult(BaseModel):
41
+ """Result data from a stacker PrepareShuttle command."""
42
+
43
+
44
+ _ExecuteReturn = Union[
45
+ SuccessData[PrepareShuttleResult],
46
+ DefinedErrorData[FlexStackerStallOrCollisionError],
47
+ ]
48
+
49
+
50
+ class PrepareShuttleImpl(AbstractCommandImpl[PrepareShuttleParams, _ExecuteReturn]):
51
+ """Implementation of a stacker prepare shuttle command."""
52
+
53
+ def __init__(
54
+ self,
55
+ state_view: StateView,
56
+ equipment: EquipmentHandler,
57
+ model_utils: ModelUtils,
58
+ **kwargs: object,
59
+ ) -> None:
60
+ self._state_view = state_view
61
+ self._equipment = equipment
62
+ self._model_utils = model_utils
63
+
64
+ async def execute(self, params: PrepareShuttleParams) -> _ExecuteReturn:
65
+ """Execute the stacker prepare shuttle command."""
66
+ stacker_state = self._state_view.modules.get_flex_stacker_substate(
67
+ params.moduleId
68
+ )
69
+ # Allow propagation of ModuleNotAttachedError.
70
+ stacker_hw = self._equipment.get_module_hardware_api(stacker_state.module_id)
71
+
72
+ try:
73
+ if stacker_hw is not None:
74
+ await stacker_hw.home_all(params.ignoreLatch)
75
+ except FlexStackerStallError as e:
76
+ return DefinedErrorData(
77
+ public=FlexStackerStallOrCollisionError(
78
+ id=self._model_utils.generate_id(),
79
+ createdAt=self._model_utils.get_timestamp(),
80
+ wrappedErrors=[
81
+ ErrorOccurrence.from_failed(
82
+ id=self._model_utils.generate_id(),
83
+ createdAt=self._model_utils.get_timestamp(),
84
+ error=e,
85
+ )
86
+ ],
87
+ ),
88
+ )
89
+ # TODO we should also add a check for shuttle not detected error
90
+
91
+ return SuccessData(public=PrepareShuttleResult())
92
+
93
+
94
+ class PrepareShuttle(
95
+ BaseCommand[PrepareShuttleParams, PrepareShuttleResult, ErrorOccurrence]
96
+ ):
97
+ """A command to prepare Flex Stacker shuttle."""
98
+
99
+ commandType: PrepareShuttleCommandType = "flexStacker/prepareShuttle"
100
+ params: PrepareShuttleParams
101
+ result: PrepareShuttleResult | None = None
102
+
103
+ _ImplementationCls: Type[PrepareShuttleImpl] = PrepareShuttleImpl
104
+
105
+
106
+ class PrepareShuttleCreate(BaseCommandCreate[PrepareShuttleParams]):
107
+ """A request to execute a Flex Stacker PrepareShuttle command."""
108
+
109
+ commandType: PrepareShuttleCommandType = "flexStacker/prepareShuttle"
110
+ params: PrepareShuttleParams
111
+
112
+ _CommandCls: Type[PrepareShuttle] = PrepareShuttle
@@ -0,0 +1,394 @@
1
+ """Command models to retrieve a labware from a Flex Stacker."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Literal, TYPE_CHECKING, Any, Union
5
+ from typing_extensions import Type
6
+
7
+ from pydantic import BaseModel, Field
8
+ from pydantic.json_schema import SkipJsonSchema
9
+
10
+ from ..command import (
11
+ AbstractCommandImpl,
12
+ BaseCommand,
13
+ BaseCommandCreate,
14
+ SuccessData,
15
+ DefinedErrorData,
16
+ )
17
+ from ..flex_stacker.common import FlexStackerStallOrCollisionError
18
+ from ...errors import (
19
+ ErrorOccurrence,
20
+ CannotPerformModuleAction,
21
+ LocationIsOccupiedError,
22
+ FlexStackerLabwarePoolNotYetDefinedError,
23
+ )
24
+ from ...resources import ModelUtils
25
+ from ...state import update_types
26
+ from ...types import (
27
+ ModuleLocation,
28
+ OnLabwareLocation,
29
+ LabwareLocationSequence,
30
+ LabwareLocation,
31
+ LoadedLabware,
32
+ )
33
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
34
+ from opentrons_shared_data.errors.exceptions import FlexStackerStallError
35
+ from opentrons.calibration_storage.helpers import uri_from_details
36
+
37
+ if TYPE_CHECKING:
38
+ from opentrons.protocol_engine.state.state import StateView
39
+ from opentrons.protocol_engine.state.module_substates import FlexStackerSubState
40
+ from opentrons.protocol_engine.execution import EquipmentHandler
41
+
42
+ RetrieveCommandType = Literal["flexStacker/retrieve"]
43
+
44
+
45
+ def _remove_default(s: dict[str, Any]) -> None:
46
+ s.pop("default", None)
47
+
48
+
49
+ class RetrieveParams(BaseModel):
50
+ """Input parameters for a labware retrieval command."""
51
+
52
+ moduleId: str = Field(
53
+ ...,
54
+ description="Unique ID of the Flex Stacker.",
55
+ )
56
+ labwareId: str | SkipJsonSchema[None] = Field(
57
+ None,
58
+ description="An optional ID to assign to this labware. If None, an ID "
59
+ "will be generated.",
60
+ json_schema_extra=_remove_default,
61
+ )
62
+ displayName: str | SkipJsonSchema[None] = Field(
63
+ None,
64
+ description="An optional user-specified display name "
65
+ "or label for this labware.",
66
+ json_schema_extra=_remove_default,
67
+ )
68
+ adapterId: str | SkipJsonSchema[None] = Field(
69
+ None,
70
+ description="An optional ID to assign to an adapter. If None, an ID "
71
+ "will be generated.",
72
+ json_schema_extra=_remove_default,
73
+ )
74
+ lidId: str | SkipJsonSchema[None] = Field(
75
+ None,
76
+ description="An optional ID to assign to a lid. If None, an ID "
77
+ "will be generated.",
78
+ json_schema_extra=_remove_default,
79
+ )
80
+
81
+
82
+ class RetrieveResult(BaseModel):
83
+ """Result data from a labware retrieval command."""
84
+
85
+ labwareId: str = Field(
86
+ ...,
87
+ description="The labware ID of the primary retrieved labware.",
88
+ )
89
+ adapterId: str | None = Field(
90
+ None,
91
+ description="The optional Adapter Labware ID of the adapter under a primary labware.",
92
+ )
93
+ lidId: str | None = Field(
94
+ None,
95
+ description="The optional Lid Labware ID of the lid on a primary labware.",
96
+ )
97
+ primaryLocationSequence: LabwareLocationSequence = Field(
98
+ ..., description="The origin location of the primary labware."
99
+ )
100
+ lidLocationSequence: LabwareLocationSequence | None = Field(
101
+ None,
102
+ description="The origin location of the adapter labware under a primary labware.",
103
+ )
104
+ adapterLocationSequence: LabwareLocationSequence | None = Field(
105
+ None, description="The origin location of the lid labware on a primary labware."
106
+ )
107
+ primaryLabwareURI: str = Field(
108
+ ...,
109
+ description="The labware definition URI of the primary labware.",
110
+ )
111
+ adapterLabwareURI: str | None = Field(
112
+ None,
113
+ description="The labware definition URI of the adapter labware.",
114
+ )
115
+ lidLabwareURI: str | None = Field(
116
+ None,
117
+ description="The labware definition URI of the lid labware.",
118
+ )
119
+
120
+
121
+ _ExecuteReturn = Union[
122
+ SuccessData[RetrieveResult],
123
+ DefinedErrorData[FlexStackerStallOrCollisionError],
124
+ ]
125
+
126
+
127
+ class RetrieveImpl(AbstractCommandImpl[RetrieveParams, _ExecuteReturn]):
128
+ """Implementation of a labware retrieval command."""
129
+
130
+ def __init__(
131
+ self,
132
+ state_view: StateView,
133
+ equipment: EquipmentHandler,
134
+ model_utils: ModelUtils,
135
+ **kwargs: object,
136
+ ) -> None:
137
+ self._state_view = state_view
138
+ self._equipment = equipment
139
+ self._model_utils = model_utils
140
+
141
+ async def _load_labware_from_pool(
142
+ self, params: RetrieveParams, stacker_state: FlexStackerSubState
143
+ ) -> tuple[RetrieveResult, update_types.StateUpdate]:
144
+ state_update = update_types.StateUpdate()
145
+
146
+ # If there is an adapter load it
147
+ adapter_lw = None
148
+ lid_lw = None
149
+ definitions_by_id: dict[str, LabwareDefinition] = {}
150
+ offset_ids_by_id: dict[str, str | None] = {}
151
+ display_names_by_id: dict[str, str | None] = {}
152
+ new_locations_by_id: dict[str, LabwareLocation] = {}
153
+ labware_by_id: dict[str, LoadedLabware] = {}
154
+ adapter_uri: str | None = None
155
+ if stacker_state.pool_adapter_definition is not None:
156
+ adapter_location = ModuleLocation(moduleId=params.moduleId)
157
+ adapter_lw = await self._equipment.load_labware_from_definition(
158
+ definition=stacker_state.pool_adapter_definition,
159
+ location=adapter_location,
160
+ labware_id=params.adapterId,
161
+ labware_pending_load=labware_by_id,
162
+ )
163
+ definitions_by_id[adapter_lw.labware_id] = adapter_lw.definition
164
+ offset_ids_by_id[adapter_lw.labware_id] = adapter_lw.offsetId
165
+ display_names_by_id[
166
+ adapter_lw.labware_id
167
+ ] = adapter_lw.definition.metadata.displayName
168
+ new_locations_by_id[adapter_lw.labware_id] = adapter_location
169
+ adapter_uri = str(
170
+ uri_from_details(
171
+ namespace=adapter_lw.definition.namespace,
172
+ load_name=adapter_lw.definition.parameters.loadName,
173
+ version=adapter_lw.definition.version,
174
+ )
175
+ )
176
+ labware_by_id[adapter_lw.labware_id] = LoadedLabware.model_construct(
177
+ id=adapter_lw.labware_id,
178
+ location=adapter_location,
179
+ loadName=adapter_lw.definition.parameters.loadName,
180
+ definitionUri=adapter_uri,
181
+ offsetId=None,
182
+ )
183
+ # Always load the primary labware
184
+ if stacker_state.pool_primary_definition is None:
185
+ raise CannotPerformModuleAction(
186
+ f"Flex Stacker {params.moduleId} has no labware to retrieve"
187
+ )
188
+ primary_location: ModuleLocation | OnLabwareLocation = (
189
+ ModuleLocation(moduleId=params.moduleId)
190
+ if adapter_lw is None
191
+ else OnLabwareLocation(labwareId=adapter_lw.labware_id)
192
+ )
193
+ loaded_labware = await self._equipment.load_labware_from_definition(
194
+ definition=stacker_state.pool_primary_definition,
195
+ location=primary_location,
196
+ labware_id=params.labwareId,
197
+ labware_pending_load={lw_id: lw for lw_id, lw in labware_by_id.items()},
198
+ )
199
+ definitions_by_id[loaded_labware.labware_id] = loaded_labware.definition
200
+ offset_ids_by_id[loaded_labware.labware_id] = loaded_labware.offsetId
201
+ display_names_by_id[
202
+ loaded_labware.labware_id
203
+ ] = loaded_labware.definition.metadata.displayName
204
+ new_locations_by_id[loaded_labware.labware_id] = primary_location
205
+ primary_uri = str(
206
+ uri_from_details(
207
+ namespace=loaded_labware.definition.namespace,
208
+ load_name=loaded_labware.definition.parameters.loadName,
209
+ version=loaded_labware.definition.version,
210
+ )
211
+ )
212
+ labware_by_id[loaded_labware.labware_id] = LoadedLabware.model_construct(
213
+ id=loaded_labware.labware_id,
214
+ location=primary_location,
215
+ loadName=loaded_labware.definition.parameters.loadName,
216
+ definitionUri=primary_uri,
217
+ )
218
+
219
+ lid_uri: str | None = None
220
+ # If there is a lid load it
221
+ if stacker_state.pool_lid_definition is not None:
222
+ lid_location = OnLabwareLocation(labwareId=loaded_labware.labware_id)
223
+ lid_lw = await self._equipment.load_labware_from_definition(
224
+ definition=stacker_state.pool_lid_definition,
225
+ location=lid_location,
226
+ labware_id=params.lidId,
227
+ labware_pending_load={lw_id: lw for lw_id, lw in labware_by_id.items()},
228
+ )
229
+ lid_uri = str(
230
+ uri_from_details(
231
+ namespace=lid_lw.definition.namespace,
232
+ load_name=lid_lw.definition.parameters.loadName,
233
+ version=lid_lw.definition.version,
234
+ )
235
+ )
236
+ definitions_by_id[lid_lw.labware_id] = lid_lw.definition
237
+ offset_ids_by_id[lid_lw.labware_id] = lid_lw.offsetId
238
+ display_names_by_id[
239
+ lid_lw.labware_id
240
+ ] = lid_lw.definition.metadata.displayName
241
+ new_locations_by_id[lid_lw.labware_id] = lid_location
242
+ labware_by_id[lid_lw.labware_id] = LoadedLabware.model_construct(
243
+ id=lid_lw.labware_id,
244
+ location=lid_location,
245
+ loadName=lid_lw.definition.parameters.loadName,
246
+ definitionUri=lid_uri,
247
+ offsetId=None,
248
+ )
249
+
250
+ # Get the labware dimensions for the labware being retrieved,
251
+ # which is the first one in the hopper labware id list
252
+ primary_location_sequence = (
253
+ self._state_view.geometry.get_predicted_location_sequence(
254
+ new_locations_by_id[loaded_labware.labware_id], labware_by_id
255
+ )
256
+ )
257
+ adapter_location_sequence = (
258
+ self._state_view.geometry.get_predicted_location_sequence(
259
+ new_locations_by_id[adapter_lw.labware_id], labware_by_id
260
+ )
261
+ if adapter_lw is not None
262
+ else None
263
+ )
264
+ lid_location_sequence = (
265
+ self._state_view.geometry.get_predicted_location_sequence(
266
+ new_locations_by_id[lid_lw.labware_id], labware_by_id
267
+ )
268
+ if lid_lw is not None
269
+ else None
270
+ )
271
+
272
+ state_update.set_batch_loaded_labware(
273
+ definitions_by_id=definitions_by_id,
274
+ display_names_by_id=display_names_by_id,
275
+ offset_ids_by_id=offset_ids_by_id,
276
+ new_locations_by_id=new_locations_by_id,
277
+ )
278
+ state_update.update_flex_stacker_labware_pool_count(
279
+ module_id=params.moduleId, count=stacker_state.pool_count - 1
280
+ )
281
+
282
+ if lid_lw is not None:
283
+ state_update.set_lids(
284
+ parent_labware_ids=[loaded_labware.labware_id],
285
+ lid_ids=[lid_lw.labware_id],
286
+ )
287
+
288
+ return (
289
+ RetrieveResult(
290
+ labwareId=loaded_labware.labware_id,
291
+ adapterId=adapter_lw.labware_id if adapter_lw is not None else None,
292
+ lidId=lid_lw.labware_id if lid_lw is not None else None,
293
+ primaryLocationSequence=primary_location_sequence,
294
+ adapterLocationSequence=adapter_location_sequence,
295
+ lidLocationSequence=lid_location_sequence,
296
+ primaryLabwareURI=primary_uri,
297
+ adapterLabwareURI=adapter_uri,
298
+ lidLabwareURI=lid_uri,
299
+ ),
300
+ state_update,
301
+ )
302
+
303
+ async def execute(self, params: RetrieveParams) -> _ExecuteReturn:
304
+ """Execute the labware retrieval command."""
305
+ stacker_state = self._state_view.modules.get_flex_stacker_substate(
306
+ params.moduleId
307
+ )
308
+
309
+ pool_definitions = stacker_state.get_pool_definition_ordered_list()
310
+ if pool_definitions is None:
311
+ location = self._state_view.modules.get_location(params.moduleId)
312
+ raise FlexStackerLabwarePoolNotYetDefinedError(
313
+ message=f"The Flex Stacker in {location} has not been configured yet and cannot be filled."
314
+ )
315
+
316
+ if stacker_state.pool_count == 0:
317
+ raise CannotPerformModuleAction(
318
+ message="Cannot retrieve labware from Flex Stacker because it contains no labware"
319
+ )
320
+
321
+ stacker_loc = ModuleLocation(moduleId=params.moduleId)
322
+ # Allow propagation of ModuleNotAttachedError.
323
+ stacker_hw = self._equipment.get_module_hardware_api(stacker_state.module_id)
324
+
325
+ try:
326
+ self._state_view.labware.raise_if_labware_in_location(stacker_loc)
327
+ except LocationIsOccupiedError:
328
+ raise CannotPerformModuleAction(
329
+ "Cannot retrieve a labware from Flex Stacker if the carriage is occupied"
330
+ )
331
+
332
+ retrieve_result, state_update = await self._load_labware_from_pool(
333
+ params, stacker_state
334
+ )
335
+
336
+ labware_height = self._state_view.geometry.get_height_of_labware_stack(
337
+ definitions=pool_definitions
338
+ )
339
+
340
+ try:
341
+ if stacker_hw is not None:
342
+ await stacker_hw.dispense_labware(labware_height=labware_height)
343
+ except FlexStackerStallError as e:
344
+ return DefinedErrorData(
345
+ public=FlexStackerStallOrCollisionError(
346
+ id=self._model_utils.generate_id(),
347
+ createdAt=self._model_utils.get_timestamp(),
348
+ wrappedErrors=[
349
+ ErrorOccurrence.from_failed(
350
+ id=self._model_utils.generate_id(),
351
+ createdAt=self._model_utils.get_timestamp(),
352
+ error=e,
353
+ )
354
+ ],
355
+ ),
356
+ )
357
+
358
+ # Update the state to reflect the labware is now in the Flex Stacker slot
359
+ # todo(chb, 2025-02-19): This ModuleLocation piece should probably instead be an AddressableAreaLocation
360
+ # but that has implications for where labware are set by things like module.load_labware(..) and what
361
+ # happens when we move labware.
362
+ stacker_area = (
363
+ self._state_view.modules.ensure_and_convert_module_fixture_location(
364
+ deck_slot=self._state_view.modules.get_location(
365
+ params.moduleId
366
+ ).slotName,
367
+ model=self._state_view.modules.get(params.moduleId).model,
368
+ )
369
+ )
370
+ state_update.set_addressable_area_used(stacker_area)
371
+
372
+ return SuccessData(
373
+ public=retrieve_result,
374
+ state_update=state_update,
375
+ )
376
+
377
+
378
+ class Retrieve(BaseCommand[RetrieveParams, RetrieveResult, ErrorOccurrence]):
379
+ """A command to retrieve a labware from a Flex Stacker."""
380
+
381
+ commandType: RetrieveCommandType = "flexStacker/retrieve"
382
+ params: RetrieveParams
383
+ result: RetrieveResult | None = None
384
+
385
+ _ImplementationCls: Type[RetrieveImpl] = RetrieveImpl
386
+
387
+
388
+ class RetrieveCreate(BaseCommandCreate[RetrieveParams]):
389
+ """A request to execute a Flex Stacker retrieve command."""
390
+
391
+ commandType: RetrieveCommandType = "flexStacker/retrieve"
392
+ params: RetrieveParams
393
+
394
+ _CommandCls: Type[Retrieve] = Retrieve