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,190 @@
1
+ """Command models to configure the stored labware pool of a Flex Stacker.."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Optional, Literal, TYPE_CHECKING
5
+ from typing_extensions import Type
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
10
+
11
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
12
+ from ...errors import (
13
+ ErrorOccurrence,
14
+ )
15
+ from ...errors.exceptions import FlexStackerNotLogicallyEmptyError
16
+ from ...state import update_types
17
+
18
+ if TYPE_CHECKING:
19
+ from opentrons.protocol_engine.state.state import StateView
20
+ from opentrons.protocol_engine.execution import EquipmentHandler
21
+
22
+ SetStoredLabwareCommandType = Literal["flexStacker/setStoredLabware"]
23
+
24
+
25
+ class StackerStoredLabwareDetails(BaseModel):
26
+ """The parameters defining a labware to be stored in the stacker."""
27
+
28
+ loadName: str = Field(
29
+ ..., description="Name used to reference the definition of this labware."
30
+ )
31
+ namespace: str = Field(
32
+ ..., description="Namespace of the definition of this labware."
33
+ )
34
+ version: int = Field(..., description="Version of the definition of this labware.")
35
+
36
+
37
+ class SetStoredLabwareParams(BaseModel):
38
+ """Input parameters for a setStoredLabware command."""
39
+
40
+ moduleId: str = Field(
41
+ ...,
42
+ description="Unique ID of the Flex Stacker.",
43
+ )
44
+ primaryLabware: StackerStoredLabwareDetails = Field(
45
+ ...,
46
+ description="The details of the primary labware (i.e. not the lid or adapter, if any) stored in the stacker.",
47
+ )
48
+ lidLabware: StackerStoredLabwareDetails | None = Field(
49
+ default=None,
50
+ description="The details of the lid on the primary labware, if any.",
51
+ )
52
+ adapterLabware: StackerStoredLabwareDetails | None = Field(
53
+ default=None,
54
+ description="The details of the adapter under the primary labware, if any.",
55
+ )
56
+ initialCount: int | None = Field(
57
+ None,
58
+ description=(
59
+ "The number of labware that should be initially stored in the stacker. This number will be silently clamped to "
60
+ "the maximum number of labware that will fit; do not rely on the parameter to know how many labware are in the stacker."
61
+ ),
62
+ ge=0,
63
+ )
64
+
65
+
66
+ class SetStoredLabwareResult(BaseModel):
67
+ """Result data from a setstoredlabware command."""
68
+
69
+ primaryLabwareDefinition: LabwareDefinition = Field(
70
+ ..., description="The definition of the primary labware."
71
+ )
72
+ lidLabwareDefinition: LabwareDefinition | None = Field(
73
+ ..., description="The definition of the lid on the primary labware, if any."
74
+ )
75
+ adapterLabwareDefinition: LabwareDefinition | None = Field(
76
+ ...,
77
+ description="The definition of the adapter under the primary labware, if any.",
78
+ )
79
+ count: int = Field(
80
+ ..., description="The number of labware loaded into the stacker labware pool."
81
+ )
82
+
83
+
84
+ class SetStoredLabwareImpl(
85
+ AbstractCommandImpl[SetStoredLabwareParams, SuccessData[SetStoredLabwareResult]]
86
+ ):
87
+ """Implementation of a setstoredlabware command."""
88
+
89
+ def __init__(
90
+ self,
91
+ state_view: StateView,
92
+ equipment: EquipmentHandler,
93
+ **kwargs: object,
94
+ ) -> None:
95
+ self._state_view = state_view
96
+ self._equipment = equipment
97
+
98
+ async def execute(
99
+ self, params: SetStoredLabwareParams
100
+ ) -> SuccessData[SetStoredLabwareResult]:
101
+ """Execute the setstoredlabwarecommand."""
102
+ stacker_state = self._state_view.modules.get_flex_stacker_substate(
103
+ params.moduleId
104
+ )
105
+
106
+ if stacker_state.pool_count != 0:
107
+ # Note: this error catches if the protocol tells us the stacker is not empty, making this command
108
+ # invalid at this point in the protocol. This error is not recoverable and should occur during
109
+ # analysis; the protocol must be changed.
110
+
111
+ raise FlexStackerNotLogicallyEmptyError(
112
+ message=(
113
+ "The Flex Stacker must be known to be empty before reconfiguring its labware pool, but it has "
114
+ f"a pool of {stacker_state.pool_count} labware"
115
+ )
116
+ )
117
+
118
+ labware_def, _ = await self._equipment.load_definition_for_details(
119
+ load_name=params.primaryLabware.loadName,
120
+ namespace=params.primaryLabware.namespace,
121
+ version=params.primaryLabware.version,
122
+ )
123
+ lid_def: LabwareDefinition | None = None
124
+ if params.lidLabware:
125
+ lid_def, _ = await self._equipment.load_definition_for_details(
126
+ load_name=params.lidLabware.loadName,
127
+ namespace=params.lidLabware.namespace,
128
+ version=params.lidLabware.version,
129
+ )
130
+ adapter_def: LabwareDefinition | None = None
131
+ if params.adapterLabware:
132
+ adapter_def, _ = await self._equipment.load_definition_for_details(
133
+ load_name=params.adapterLabware.loadName,
134
+ namespace=params.adapterLabware.namespace,
135
+ version=params.adapterLabware.version,
136
+ )
137
+
138
+ self._state_view.labware.raise_if_stacker_labware_pool_is_not_valid(
139
+ labware_def, lid_def, adapter_def
140
+ )
141
+
142
+ pool_height = self._state_view.geometry.get_height_of_labware_stack(
143
+ [x for x in [lid_def, labware_def, adapter_def] if x is not None]
144
+ )
145
+ max_pool_count = self._state_view.modules.stacker_max_pool_count_by_height(
146
+ params.moduleId, pool_height
147
+ )
148
+
149
+ initial_count = (
150
+ params.initialCount if params.initialCount is not None else max_pool_count
151
+ )
152
+ count = min(initial_count, max_pool_count)
153
+
154
+ state_update = (
155
+ update_types.StateUpdate()
156
+ .update_flex_stacker_labware_pool_definition(
157
+ params.moduleId, max_pool_count, labware_def, adapter_def, lid_def
158
+ )
159
+ .update_flex_stacker_labware_pool_count(params.moduleId, count)
160
+ )
161
+ return SuccessData(
162
+ public=SetStoredLabwareResult.model_construct(
163
+ primaryLabwareDefinition=labware_def,
164
+ lidLabwareDefinition=lid_def,
165
+ adapterLabwareDefinition=adapter_def,
166
+ count=count,
167
+ ),
168
+ state_update=state_update,
169
+ )
170
+
171
+
172
+ class SetStoredLabware(
173
+ BaseCommand[SetStoredLabwareParams, SetStoredLabwareResult, ErrorOccurrence]
174
+ ):
175
+ """A command to setstoredlabware the Flex Stacker."""
176
+
177
+ commandType: SetStoredLabwareCommandType = "flexStacker/setStoredLabware"
178
+ params: SetStoredLabwareParams
179
+ result: Optional[SetStoredLabwareResult] = None
180
+
181
+ _ImplementationCls: Type[SetStoredLabwareImpl] = SetStoredLabwareImpl
182
+
183
+
184
+ class SetStoredLabwareCreate(BaseCommandCreate[SetStoredLabwareParams]):
185
+ """A request to execute a Flex Stacker SetStoredLabware command."""
186
+
187
+ commandType: SetStoredLabwareCommandType = "flexStacker/setStoredLabware"
188
+ params: SetStoredLabwareParams
189
+
190
+ _CommandCls: Type[SetStoredLabware] = SetStoredLabware
@@ -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
- help="The command schema version. This is a single integer (e.g. 7) that will be used to name the generated schema file",
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
- print(generate_command_schema(args.version))
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: float = Field(
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: float | SkipJsonSchema[None] = Field(
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: float | PipetteLiquidNotFoundError | PipetteOverpressureError
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: float | update_types.ClearType = (
307
- self._state_view.geometry.get_well_volume_at_height(
308
- labware_id=params.labwareId,
309
- well_name=params.wellName,
310
- height=z_pos_or_error,
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: float | update_types.ClearType = update_types.CLEAR
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,