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
@@ -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
- LabwareLocation,
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: LabwareLocation = Field(
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(BaseModel):
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
- labware_validation.validate_definition_is_lid(
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
- raise ValueError(
185
- f"Labware Lid {params.loadName} may not be loaded on parent labware {self._state_view.labware.get_display_name(verified_location.labwareId)}."
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
- elif isinstance(params.location, ModuleLocation):
190
- module = self._state_view.modules.get(params.location.moduleId)
191
- if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1:
192
- self._state_view.labware.raise_if_labware_incompatible_with_plate_reader(
193
- loaded_labware.definition
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
  )
@@ -1,4 +1,5 @@
1
1
  """Load lid command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
4
  from pydantic import BaseModel, Field
4
5
  from typing import TYPE_CHECKING, Optional, Type
@@ -9,10 +10,12 @@ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
9
10
  from ..errors import LabwareCannotBeStackedError, LabwareIsNotAllowedInLocationError
10
11
  from ..resources import labware_validation
11
12
  from ..types import (
12
- LabwareLocation,
13
+ LoadableLabwareLocation,
13
14
  OnLabwareLocation,
15
+ OnLabwareLocationSequenceComponent,
14
16
  )
15
17
 
18
+ from .labware_handling_common import LabwareHandlingResultMixin
16
19
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
17
20
  from ..errors.error_occurrence import ErrorOccurrence
18
21
  from ..state.update_types import StateUpdate
@@ -28,7 +31,7 @@ LoadLidCommandType = Literal["loadLid"]
28
31
  class LoadLidParams(BaseModel):
29
32
  """Payload required to load a lid onto a labware."""
30
33
 
31
- location: LabwareLocation = Field(
34
+ location: LoadableLabwareLocation = Field(
32
35
  ...,
33
36
  description="Labware the lid should be loaded onto.",
34
37
  )
@@ -46,13 +49,9 @@ class LoadLidParams(BaseModel):
46
49
  )
47
50
 
48
51
 
49
- class LoadLidResult(BaseModel):
52
+ class LoadLidResult(LabwareHandlingResultMixin):
50
53
  """Result data from the execution of a LoadLabware command."""
51
54
 
52
- labwareId: str = Field(
53
- ...,
54
- description="An ID to reference this lid labware in subsequent commands.",
55
- )
56
55
  definition: LabwareDefinition = Field(
57
56
  ...,
58
57
  description="The full definition data for this lid labware.",
@@ -88,9 +87,6 @@ class LoadLidImplementation(
88
87
  labware_id=None,
89
88
  )
90
89
 
91
- # TODO(chb 2024-12-12) these validation checks happen after the labware is loaded, because they rely on
92
- # on the definition. In practice this will not cause any issues since they will raise protocol ending
93
- # exception, but for correctness should be refactored to do this check beforehand.
94
90
  if not labware_validation.validate_definition_is_lid(loaded_labware.definition):
95
91
  raise LabwareCannotBeStackedError(
96
92
  f"Labware {params.loadName} is not a Lid and cannot be loaded onto {self._state_view.labware.get_display_name(params.location.labwareId)}."
@@ -99,9 +95,9 @@ class LoadLidImplementation(
99
95
  state_update = StateUpdate()
100
96
 
101
97
  # In the case of lids being loaded on top of other labware, set the parent labware's lid
102
- state_update.set_lid(
103
- parent_labware_id=params.location.labwareId,
104
- lid_id=loaded_labware.labware_id,
98
+ state_update.set_lids(
99
+ parent_labware_ids=[params.location.labwareId],
100
+ lid_ids=[loaded_labware.labware_id],
105
101
  )
106
102
 
107
103
  state_update.set_loaded_labware(
@@ -122,6 +118,18 @@ class LoadLidImplementation(
122
118
  public=LoadLidResult(
123
119
  labwareId=loaded_labware.labware_id,
124
120
  definition=loaded_labware.definition,
121
+ # Note: the lid is not yet loaded and therefore won't be found as the lid id for the
122
+ # labware onto which we're loading it, so build that part of the location sequence
123
+ # here and then build the rest of the sequence from the parent labware
124
+ locationSequence=[
125
+ OnLabwareLocationSequenceComponent(
126
+ labwareId=params.location.labwareId,
127
+ lidId=loaded_labware.labware_id,
128
+ )
129
+ ]
130
+ + self._state_view.geometry.get_location_sequence(
131
+ params.location.labwareId
132
+ ),
125
133
  ),
126
134
  state_update=state_update,
127
135
  )
@@ -1,7 +1,9 @@
1
1
  """Load lid stack command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
4
  from pydantic import BaseModel, Field
4
- from typing import TYPE_CHECKING, Optional, Type, List
5
+ from typing import TYPE_CHECKING, Optional, Type, List, Any
6
+ from pydantic.json_schema import SkipJsonSchema
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from opentrons_shared_data.labware.labware_definition import LabwareDefinition
@@ -9,10 +11,13 @@ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
9
11
  from ..errors import LabwareIsNotAllowedInLocationError, ProtocolEngineError
10
12
  from ..resources import fixture_validation, labware_validation
11
13
  from ..types import (
12
- LabwareLocation,
14
+ LoadableLabwareLocation,
15
+ SYSTEM_LOCATION,
13
16
  OnLabwareLocation,
14
17
  DeckSlotLocation,
15
18
  AddressableAreaLocation,
19
+ LabwareLocationSequence,
20
+ OnLabwareLocationSequenceComponent,
16
21
  )
17
22
 
18
23
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
@@ -21,11 +26,16 @@ from ..state.update_types import StateUpdate
21
26
 
22
27
  if TYPE_CHECKING:
23
28
  from ..state.state import StateView
24
- from ..execution import EquipmentHandler
29
+ from ..execution import LoadedLabwareData, EquipmentHandler
25
30
 
26
31
 
27
32
  LoadLidStackCommandType = Literal["loadLidStack"]
28
33
 
34
+
35
+ def _remove_default(s: dict[str, Any]) -> None:
36
+ s.pop("default", None)
37
+
38
+
29
39
  _LID_STACK_PE_LABWARE = "protocol_engine_lid_stack_object"
30
40
  _LID_STACK_PE_NAMESPACE = "opentrons"
31
41
  _LID_STACK_PE_VERSION = 1
@@ -34,7 +44,7 @@ _LID_STACK_PE_VERSION = 1
34
44
  class LoadLidStackParams(BaseModel):
35
45
  """Payload required to load a lid stack onto a location."""
36
46
 
37
- location: LabwareLocation = Field(
47
+ location: LoadableLabwareLocation = Field(
38
48
  ...,
39
49
  description="Location the lid stack should be loaded into.",
40
50
  )
@@ -50,6 +60,18 @@ class LoadLidStackParams(BaseModel):
50
60
  ...,
51
61
  description="The lid labware definition version.",
52
62
  )
63
+ stackLabwareId: str | SkipJsonSchema[None] = Field(
64
+ None,
65
+ description="An optional ID to assign to the lid stack labware object created."
66
+ "If None, an ID will be generated.",
67
+ json_schema_extra=_remove_default,
68
+ )
69
+ labwareIds: List[str] | SkipJsonSchema[None] = Field(
70
+ None,
71
+ description="An optional list of IDs to assign to the lids in the stack."
72
+ "If None, an ID will be generated.",
73
+ json_schema_extra=_remove_default,
74
+ )
53
75
  quantity: int = Field(
54
76
  ...,
55
77
  description="The quantity of lids to load.",
@@ -61,19 +83,31 @@ class LoadLidStackResult(BaseModel):
61
83
 
62
84
  stackLabwareId: str = Field(
63
85
  ...,
64
- description="An ID to reference the Protocol Engine Labware Lid Stack in subsequent commands.",
86
+ description="An ID to reference the lid stack labware object created.",
65
87
  )
66
88
  labwareIds: List[str] = Field(
67
89
  ...,
68
90
  description="A list of lid labware IDs to reference the lids in this stack by. The first ID is the bottom of the stack.",
69
91
  )
70
- definition: LabwareDefinition = Field(
92
+ definition: LabwareDefinition | None = Field(
71
93
  ...,
72
94
  description="The full definition data for this lid labware.",
73
95
  )
74
- location: LabwareLocation = Field(
96
+ location: LoadableLabwareLocation = Field(
75
97
  ..., description="The Location that the stack of lid labware has been loaded."
76
98
  )
99
+ lidStackDefinition: LabwareDefinition | None = Field(
100
+ None,
101
+ description="The definition of the lid stack object. Optional for backwards-compatibility.",
102
+ )
103
+ stackLocationSequence: LabwareLocationSequence | None = Field(
104
+ None,
105
+ description="The location sequence for the lid stack labware object created.",
106
+ )
107
+ locationSequences: List[LabwareLocationSequence] | None = Field(
108
+ None,
109
+ description="The location sequences for the lids just loaded into the stack. These are in the same order as labwareIds.",
110
+ )
77
111
 
78
112
 
79
113
  class LoadLidStackImplementation(
@@ -87,10 +121,7 @@ class LoadLidStackImplementation(
87
121
  self._equipment = equipment
88
122
  self._state_view = state_view
89
123
 
90
- async def execute(
91
- self, params: LoadLidStackParams
92
- ) -> SuccessData[LoadLidStackResult]:
93
- """Load definition and calibration data necessary for a lid stack."""
124
+ def _validate_location(self, params: LoadLidStackParams) -> LoadableLabwareLocation:
94
125
  if isinstance(params.location, AddressableAreaLocation):
95
126
  area_name = params.location.addressableAreaName
96
127
  if not (
@@ -107,36 +138,47 @@ class LoadLidStackImplementation(
107
138
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
108
139
  params.location.slotName.id
109
140
  )
110
-
111
- verified_location = self._state_view.geometry.ensure_location_not_occupied(
112
- params.location
113
- )
114
-
115
- lid_stack_object = await self._equipment.load_labware(
116
- load_name=_LID_STACK_PE_LABWARE,
117
- namespace=_LID_STACK_PE_NAMESPACE,
118
- version=_LID_STACK_PE_VERSION,
119
- location=verified_location,
120
- labware_id=None,
121
- )
122
- if not labware_validation.validate_definition_is_system(
123
- lid_stack_object.definition
124
- ):
141
+ if params.quantity <= 0 and params.location != SYSTEM_LOCATION:
125
142
  raise ProtocolEngineError(
126
- message="Lid Stack Labware Object Labware Definition does not contain required allowed role 'system'."
143
+ message="Lid Stack Labware Object with quantity 0 must be loaded onto System Location."
127
144
  )
145
+ return self._state_view.geometry.ensure_location_not_occupied(params.location)
128
146
 
129
- loaded_lid_labwares = await self._equipment.load_lids(
130
- load_name=params.loadName,
131
- namespace=params.namespace,
132
- version=params.version,
133
- location=OnLabwareLocation(labwareId=lid_stack_object.labware_id),
134
- quantity=params.quantity,
147
+ def _format_results(
148
+ self,
149
+ verified_location: LoadableLabwareLocation,
150
+ lid_stack_object: LoadedLabwareData,
151
+ loaded_lid_labwares: list[LoadedLabwareData],
152
+ lid_labware_definition: LabwareDefinition | None,
153
+ ) -> SuccessData[LoadLidStackResult]:
154
+ stack_location_sequence = (
155
+ self._state_view.geometry.get_predicted_location_sequence(verified_location)
135
156
  )
136
- loaded_lid_locations_by_id = {}
157
+ loaded_lid_locations_by_id: dict[str, OnLabwareLocation] = {}
158
+ loaded_lid_ids_ordered: list[str] = []
159
+ loaded_lid_location_sequences_ordered: list[LabwareLocationSequence] = []
160
+ lid_location_sequence_accumulated: LabwareLocationSequence = [
161
+ OnLabwareLocationSequenceComponent(
162
+ labwareId=lid_stack_object.labware_id, lidId=None
163
+ )
164
+ ] + stack_location_sequence
137
165
  load_location = OnLabwareLocation(labwareId=lid_stack_object.labware_id)
166
+ last_lid_id: str | None = None
138
167
  for loaded_lid in loaded_lid_labwares:
139
168
  loaded_lid_locations_by_id[loaded_lid.labware_id] = load_location
169
+ loaded_lid_ids_ordered.append(loaded_lid.labware_id)
170
+ if last_lid_id is None:
171
+ last_lid_id = loaded_lid.labware_id
172
+ else:
173
+ lid_location_sequence_accumulated = [
174
+ OnLabwareLocationSequenceComponent(
175
+ labwareId=last_lid_id, lidId=None
176
+ )
177
+ ] + lid_location_sequence_accumulated
178
+ last_lid_id = loaded_lid.labware_id
179
+ loaded_lid_location_sequences_ordered.append(
180
+ [loc for loc in lid_location_sequence_accumulated]
181
+ )
140
182
  load_location = OnLabwareLocation(labwareId=loaded_lid.labware_id)
141
183
 
142
184
  state_update = StateUpdate()
@@ -144,29 +186,70 @@ class LoadLidStackImplementation(
144
186
  stack_id=lid_stack_object.labware_id,
145
187
  stack_object_definition=lid_stack_object.definition,
146
188
  stack_location=verified_location,
147
- labware_ids=list(loaded_lid_locations_by_id.keys()),
148
- labware_definition=loaded_lid_labwares[0].definition,
149
189
  locations=loaded_lid_locations_by_id,
190
+ labware_definition=lid_labware_definition,
150
191
  )
151
192
 
152
- if isinstance(verified_location, OnLabwareLocation):
153
- self._state_view.labware.raise_if_labware_cannot_be_stacked(
154
- top_labware_definition=loaded_lid_labwares[
155
- params.quantity - 1
156
- ].definition,
157
- bottom_labware_id=verified_location.labwareId,
158
- )
159
-
160
193
  return SuccessData(
161
194
  public=LoadLidStackResult(
162
195
  stackLabwareId=lid_stack_object.labware_id,
163
- labwareIds=list(loaded_lid_locations_by_id.keys()),
164
- definition=loaded_lid_labwares[0].definition,
165
- location=params.location,
196
+ lidStackDefinition=lid_stack_object.definition,
197
+ labwareIds=loaded_lid_ids_ordered,
198
+ definition=lid_labware_definition,
199
+ location=verified_location,
200
+ stackLocationSequence=stack_location_sequence,
201
+ locationSequences=loaded_lid_location_sequences_ordered,
166
202
  ),
167
203
  state_update=state_update,
168
204
  )
169
205
 
206
+ async def execute(
207
+ self, params: LoadLidStackParams
208
+ ) -> SuccessData[LoadLidStackResult]:
209
+ """Load definition and calibration data necessary for a lid stack."""
210
+ verified_location = self._validate_location(params)
211
+
212
+ lid_stack_object = await self._equipment.load_labware(
213
+ load_name=_LID_STACK_PE_LABWARE,
214
+ namespace=_LID_STACK_PE_NAMESPACE,
215
+ version=_LID_STACK_PE_VERSION,
216
+ location=verified_location,
217
+ labware_id=params.stackLabwareId,
218
+ )
219
+
220
+ if not labware_validation.validate_definition_is_system(
221
+ lid_stack_object.definition
222
+ ):
223
+ raise ProtocolEngineError(
224
+ message="Lid Stack Labware Object Labware Definition does not contain required allowed role 'system'."
225
+ )
226
+
227
+ loaded_lid_labwares: list[LoadedLabwareData] = []
228
+ lid_labware_definition: LabwareDefinition | None = None
229
+
230
+ if params.quantity > 0:
231
+ loaded_lid_labwares = await self._equipment.load_lids(
232
+ load_name=params.loadName,
233
+ namespace=params.namespace,
234
+ version=params.version,
235
+ location=OnLabwareLocation(labwareId=lid_stack_object.labware_id),
236
+ quantity=params.quantity,
237
+ labware_ids=params.labwareIds,
238
+ )
239
+ lid_labware_definition = loaded_lid_labwares[-1].definition
240
+ if isinstance(verified_location, OnLabwareLocation):
241
+ self._state_view.labware.raise_if_labware_cannot_be_stacked(
242
+ top_labware_definition=lid_labware_definition,
243
+ bottom_labware_id=verified_location.labwareId,
244
+ )
245
+
246
+ return self._format_results(
247
+ verified_location,
248
+ lid_stack_object,
249
+ loaded_lid_labwares,
250
+ lid_labware_definition,
251
+ )
252
+
170
253
 
171
254
  class LoadLidStack(
172
255
  BaseCommand[LoadLidStackParams, LoadLidStackResult, ErrorOccurrence]
@@ -11,6 +11,7 @@ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, Succes
11
11
  from ..errors.error_occurrence import ErrorOccurrence
12
12
  from ..types import (
13
13
  DeckSlotLocation,
14
+ AddressableAreaLocation,
14
15
  ModuleType,
15
16
  ModuleModel,
16
17
  ModuleDefinition,
@@ -129,41 +130,41 @@ class LoadModuleImplementation(
129
130
  module_type = params.model.as_type()
130
131
  self._ensure_module_location(params.location.slotName, module_type)
131
132
 
132
- # todo(mm, 2024-12-03): Theoretically, we should be able to deal with
133
- # addressable areas and deck configurations the same way between OT-2 and Flex.
134
- # Can this be simplified?
135
- if self._state_view.config.robot_type == "OT-2 Standard":
136
- self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
137
- params.location.slotName.id
138
- )
139
- else:
140
- addressable_area_provided_by_module = (
133
+ if self._state_view.modules.get_deck_supports_module_fixtures():
134
+ addressable_area_module_reference = (
141
135
  self._state_view.modules.ensure_and_convert_module_fixture_location(
142
136
  deck_slot=params.location.slotName,
143
137
  model=params.model,
144
138
  )
145
139
  )
146
- self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
147
- addressable_area_provided_by_module
140
+ else:
141
+ addressable_area_module_reference = params.location.slotName.id
142
+ state_update.set_addressable_area_used(
143
+ addressable_area_name=addressable_area_module_reference
148
144
  )
149
145
 
150
- verified_location = self._state_view.geometry.ensure_location_not_occupied(
151
- params.location
146
+ self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
147
+ addressable_area_module_reference
152
148
  )
153
- state_update.set_addressable_area_used(
154
- addressable_area_name=params.location.slotName.id
149
+
150
+ self._state_view.geometry.ensure_location_not_occupied(
151
+ params.location, addressable_area_module_reference
155
152
  )
156
153
 
157
154
  if params.model == ModuleModel.MAGNETIC_BLOCK_V1:
158
155
  loaded_module = await self._equipment.load_magnetic_block(
159
156
  model=params.model,
160
- location=verified_location,
157
+ location=AddressableAreaLocation(
158
+ addressableAreaName=addressable_area_module_reference
159
+ ),
161
160
  module_id=params.moduleId,
162
161
  )
163
162
  else:
164
163
  loaded_module = await self._equipment.load_module(
165
164
  model=params.model,
166
- location=verified_location,
165
+ location=AddressableAreaLocation(
166
+ addressableAreaName=addressable_area_module_reference
167
+ ),
167
168
  module_id=params.moduleId,
168
169
  )
169
170
 
@@ -138,6 +138,9 @@ class LoadPipetteImplementation(
138
138
  config=loaded_pipette.static_config,
139
139
  )
140
140
  state_update.set_fluid_unknown(pipette_id=loaded_pipette.pipette_id)
141
+ state_update.set_pipette_ready_to_aspirate(
142
+ pipette_id=loaded_pipette.pipette_id, ready_to_aspirate=False
143
+ ),
141
144
 
142
145
  return SuccessData(
143
146
  public=LoadPipetteResult(pipetteId=loaded_pipette.pipette_id),