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
@@ -459,6 +459,19 @@ class ModuleNotConnectedError(ProtocolEngineError):
459
459
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
460
460
 
461
461
 
462
+ class OffsetLocationInvalidError(ProtocolEngineError):
463
+ """Raised when encountering an invalid labware offset location sequence."""
464
+
465
+ def __init__(
466
+ self,
467
+ message: Optional[str] = None,
468
+ details: Optional[Dict[str, Any]] = None,
469
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
470
+ ) -> None:
471
+ """Build an OffsetLocationSequenceDoesNotTerminateAtAnAddressableAreaError."""
472
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
473
+
474
+
462
475
  class SlotDoesNotExistError(ProtocolEngineError):
463
476
  """Raised when referencing a deck slot that does not exist."""
464
477
 
@@ -1114,6 +1127,19 @@ class LiquidHeightUnknownError(ProtocolEngineError):
1114
1127
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1115
1128
 
1116
1129
 
1130
+ class LiquidVolumeUnknownError(ProtocolEngineError):
1131
+ """Raised when attempting to report an unknown liquid volume."""
1132
+
1133
+ def __init__(
1134
+ self,
1135
+ message: Optional[str] = None,
1136
+ details: Optional[Dict[str, Any]] = None,
1137
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1138
+ ) -> None:
1139
+ """Build a LiquidVolumeUnknownError."""
1140
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1141
+
1142
+
1117
1143
  class EStopActivatedError(ProtocolEngineError):
1118
1144
  """Represents an E-stop event."""
1119
1145
 
@@ -1231,3 +1257,27 @@ class LiquidClassRedefinitionError(ProtocolEngineError):
1231
1257
  wrapping: Optional[Sequence[EnumeratedError]] = None,
1232
1258
  ) -> None:
1233
1259
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1260
+
1261
+
1262
+ class FlexStackerNotLogicallyEmptyError(ProtocolEngineError):
1263
+ """Raised when attempting a stacker operation that requires it to be empty when it is known from the protocol that it is not."""
1264
+
1265
+ def __init__(
1266
+ self,
1267
+ message: Optional[str] = None,
1268
+ details: Optional[dict[str, Any]] = None,
1269
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1270
+ ) -> None:
1271
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1272
+
1273
+
1274
+ class FlexStackerLabwarePoolNotYetDefinedError(ProtocolEngineError):
1275
+ """Raised when attempting to modify labware in a stacker whose labware pool is not yet defined."""
1276
+
1277
+ def __init__(
1278
+ self,
1279
+ message: Optional[str] = None,
1280
+ details: Optional[dict[str, Any]] = None,
1281
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1282
+ ) -> None:
1283
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
@@ -1,11 +1,12 @@
1
1
  """Equipment command side-effect logic."""
2
+
2
3
  from dataclasses import dataclass
3
- from typing import Optional, overload, Union, List
4
+ from typing import Optional, overload, List
4
5
 
6
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
5
7
  from opentrons_shared_data.pipette.types import PipetteNameType
6
8
 
7
9
  from opentrons.calibration_storage.helpers import uri_from_details
8
- from opentrons.protocols.models import LabwareDefinition
9
10
  from opentrons.types import MountType
10
11
  from opentrons.hardware_control import HardwareControlAPI
11
12
  from opentrons.hardware_control.modules import (
@@ -15,6 +16,7 @@ from opentrons.hardware_control.modules import (
15
16
  TempDeck,
16
17
  Thermocycler,
17
18
  AbsorbanceReader,
19
+ FlexStacker,
18
20
  )
19
21
  from opentrons.hardware_control.nozzle_manager import NozzleMap
20
22
  from opentrons.protocol_engine.state.module_substates import (
@@ -23,6 +25,7 @@ from opentrons.protocol_engine.state.module_substates import (
23
25
  TemperatureModuleId,
24
26
  ThermocyclerModuleId,
25
27
  AbsorbanceReaderId,
28
+ FlexStackerId,
26
29
  )
27
30
  from ..errors import (
28
31
  FailedToLoadPipetteError,
@@ -40,13 +43,11 @@ from ..state.modules import HardwareModule
40
43
  from ..types import (
41
44
  LabwareLocation,
42
45
  DeckSlotLocation,
43
- ModuleLocation,
44
- OnLabwareLocation,
45
46
  LabwareOffset,
46
- LabwareOffsetLocation,
47
47
  ModuleModel,
48
48
  ModuleDefinition,
49
49
  AddressableAreaLocation,
50
+ LoadedLabware,
50
51
  )
51
52
 
52
53
 
@@ -127,30 +128,18 @@ class EquipmentHandler:
127
128
  or pipette_data_provider.VirtualPipetteDataProvider()
128
129
  )
129
130
 
130
- async def load_labware(
131
- self,
132
- load_name: str,
133
- namespace: str,
134
- version: int,
135
- location: LabwareLocation,
136
- labware_id: Optional[str],
137
- ) -> LoadedLabwareData:
138
- """Load labware by assigning an identifier and pulling required data.
131
+ async def load_definition_for_details(
132
+ self, load_name: str, namespace: str, version: int
133
+ ) -> tuple[LabwareDefinition, str]:
134
+ """Load the definition for a labware from the parameters passed for it to a command.
139
135
 
140
136
  Args:
141
137
  load_name: The labware's load name.
142
138
  namespace: The labware's namespace.
143
139
  version: The labware's version.
144
- location: The deck location at which labware is placed.
145
- labware_id: An optional identifier to assign the labware. If None, an
146
- identifier will be generated.
147
-
148
- Raises:
149
- ModuleNotLoadedError: If `location` references a module ID
150
- that doesn't point to a valid loaded module.
151
140
 
152
141
  Returns:
153
- A LoadedLabwareData object.
142
+ A tuple of the loaded LabwareDefinition object and its definition URI.
154
143
  """
155
144
  definition_uri = uri_from_details(
156
145
  load_name=load_name,
@@ -160,14 +149,43 @@ class EquipmentHandler:
160
149
 
161
150
  try:
162
151
  # Try to use existing definition in state.
163
- definition = self._state_store.labware.get_definition_by_uri(definition_uri)
152
+ return (
153
+ self._state_store.labware.get_definition_by_uri(definition_uri),
154
+ definition_uri,
155
+ )
164
156
  except LabwareDefinitionDoesNotExistError:
165
157
  definition = await self._labware_data_provider.get_labware_definition(
166
158
  load_name=load_name,
167
159
  namespace=namespace,
168
160
  version=version,
169
161
  )
162
+ return definition, definition_uri
163
+
164
+ async def load_labware_from_definition(
165
+ self,
166
+ definition: LabwareDefinition,
167
+ location: LabwareLocation,
168
+ labware_id: Optional[str],
169
+ labware_pending_load: dict[str, LoadedLabware] | None = None,
170
+ ) -> LoadedLabwareData:
171
+ """Load labware from already-found definition."""
172
+ definition_uri = uri_from_details(
173
+ load_name=definition.parameters.loadName,
174
+ namespace=definition.namespace,
175
+ version=definition.version,
176
+ )
177
+ return await self._load_labware_from_def_and_uri(
178
+ definition, definition_uri, location, labware_id, labware_pending_load
179
+ )
170
180
 
181
+ async def _load_labware_from_def_and_uri(
182
+ self,
183
+ definition: LabwareDefinition,
184
+ definition_uri: str,
185
+ location: LabwareLocation,
186
+ labware_id: str | None,
187
+ labware_pending_load: dict[str, LoadedLabware] | None,
188
+ ) -> LoadedLabwareData:
171
189
  labware_id = (
172
190
  labware_id if labware_id is not None else self._model_utils.generate_id()
173
191
  )
@@ -176,12 +194,45 @@ class EquipmentHandler:
176
194
  offset_id = self.find_applicable_labware_offset_id(
177
195
  labware_definition_uri=definition_uri,
178
196
  labware_location=location,
197
+ labware_pending_load=labware_pending_load,
179
198
  )
180
199
 
181
200
  return LoadedLabwareData(
182
201
  labware_id=labware_id, definition=definition, offsetId=offset_id
183
202
  )
184
203
 
204
+ async def load_labware(
205
+ self,
206
+ load_name: str,
207
+ namespace: str,
208
+ version: int,
209
+ location: LabwareLocation,
210
+ labware_id: Optional[str],
211
+ ) -> LoadedLabwareData:
212
+ """Load labware by assigning an identifier and pulling required data.
213
+
214
+ Args:
215
+ load_name: The labware's load name.
216
+ namespace: The labware's namespace.
217
+ version: The labware's version.
218
+ location: The deck location at which labware is placed.
219
+ labware_id: An optional identifier to assign the labware. If None, an
220
+ identifier will be generated.
221
+
222
+ Raises:
223
+ ModuleNotLoadedError: If `location` references a module ID
224
+ that doesn't point to a valid loaded module.
225
+
226
+ Returns:
227
+ A LoadedLabwareData object.
228
+ """
229
+ definition, definition_uri = await self.load_definition_for_details(
230
+ load_name, namespace, version
231
+ )
232
+ return await self._load_labware_from_def_and_uri(
233
+ definition, definition_uri, location, labware_id, None
234
+ )
235
+
185
236
  async def reload_labware(self, labware_id: str) -> ReloadedLabwareData:
186
237
  """Reload an already-loaded labware. This cannot change the labware location.
187
238
 
@@ -290,7 +341,7 @@ class EquipmentHandler:
290
341
  async def load_magnetic_block(
291
342
  self,
292
343
  model: ModuleModel,
293
- location: Union[DeckSlotLocation, AddressableAreaLocation],
344
+ location: AddressableAreaLocation,
294
345
  module_id: Optional[str],
295
346
  ) -> LoadedModuleData:
296
347
  """Ensure the required magnetic block is attached.
@@ -321,7 +372,7 @@ class EquipmentHandler:
321
372
  async def load_module(
322
373
  self,
323
374
  model: ModuleModel,
324
- location: DeckSlotLocation,
375
+ location: AddressableAreaLocation,
325
376
  module_id: Optional[str],
326
377
  ) -> LoadedModuleData:
327
378
  """Ensure the required module is attached.
@@ -355,12 +406,18 @@ class EquipmentHandler:
355
406
  for hw_mod in self._hardware_api.attached_modules
356
407
  ]
357
408
 
358
- serial_number_at_locaiton = self._state_store.geometry._addressable_areas.get_fixture_serial_from_deck_configuration_by_deck_slot(
359
- location.slotName
409
+ serial_number_at_locaiton = self._state_store.geometry._addressable_areas.get_fixture_serial_from_deck_configuration_by_addressable_area(
410
+ addressable_area_name=location.addressableAreaName
411
+ )
412
+ cutout_id = self._state_store.geometry._addressable_areas.get_cutout_id_by_deck_slot_name(
413
+ slot_name=self._state_store.geometry._addressable_areas.get_addressable_area_base_slot(
414
+ location.addressableAreaName
415
+ )
360
416
  )
417
+
361
418
  attached_module = self._state_store.modules.select_hardware_module_to_load(
362
419
  model=model,
363
- location=location,
420
+ location=cutout_id,
364
421
  attached_modules=attached_modules,
365
422
  expected_serial_number=serial_number_at_locaiton,
366
423
  )
@@ -386,6 +443,7 @@ class EquipmentHandler:
386
443
  version: int,
387
444
  location: LabwareLocation,
388
445
  quantity: int,
446
+ labware_ids: Optional[List[str]] = None,
389
447
  ) -> List[LoadedLabwareData]:
390
448
  """Load one or many lid labware by assigning an identifier and pulling required data.
391
449
 
@@ -394,6 +452,7 @@ class EquipmentHandler:
394
452
  namespace: The lid labware's namespace.
395
453
  version: The lid labware's version.
396
454
  location: The deck location at which lid(s) will be placed.
455
+ quantity: The quantity of lids to load at a location.
397
456
  labware_ids: An optional list of identifiers to assign the labware. If None,
398
457
  an identifier will be generated.
399
458
 
@@ -425,21 +484,32 @@ class EquipmentHandler:
425
484
  f"Requested quantity {quantity} is greater than the stack limit of {stack_limit} provided by definition for {load_name}."
426
485
  )
427
486
 
428
- # Allow propagation of ModuleNotLoadedError.
429
- if (
430
- isinstance(location, DeckSlotLocation)
431
- and definition.parameters.isDeckSlotCompatible is not None
432
- and not definition.parameters.isDeckSlotCompatible
433
- ):
487
+ is_deck_slot_compatible = (
488
+ True
489
+ if definition.parameters.isDeckSlotCompatible is None
490
+ else definition.parameters.isDeckSlotCompatible
491
+ )
492
+
493
+ if isinstance(location, DeckSlotLocation) and not is_deck_slot_compatible:
434
494
  raise ValueError(
435
495
  f"Lid Labware {load_name} cannot be loaded onto a Deck Slot."
436
496
  )
437
497
 
438
- load_labware_data_list = []
439
- for i in range(quantity):
498
+ load_labware_data_list: list[LoadedLabwareData] = []
499
+ ids: list[str] = []
500
+ if labware_ids is not None:
501
+ if len(labware_ids) < quantity:
502
+ raise ValueError(
503
+ f"Requested quantity {quantity} is greater than the number of labware lid IDs provided for {load_name}."
504
+ )
505
+ ids = labware_ids
506
+ else:
507
+ for i in range(quantity):
508
+ ids.append(self._model_utils.generate_id())
509
+ for id in ids:
440
510
  load_labware_data_list.append(
441
511
  LoadedLabwareData(
442
- labware_id=self._model_utils.generate_id(),
512
+ labware_id=id,
443
513
  definition=definition,
444
514
  offsetId=None,
445
515
  )
@@ -581,6 +651,13 @@ class EquipmentHandler:
581
651
  ) -> Optional[AbsorbanceReader]:
582
652
  ...
583
653
 
654
+ @overload
655
+ def get_module_hardware_api(
656
+ self,
657
+ module_id: FlexStackerId,
658
+ ) -> Optional[FlexStacker]:
659
+ ...
660
+
584
661
  def get_module_hardware_api(self, module_id: str) -> Optional[AbstractModule]:
585
662
  """Get the hardware API for a given module."""
586
663
  use_virtual_modules = self._state_store.config.use_virtual_modules
@@ -599,8 +676,11 @@ class EquipmentHandler:
599
676
  )
600
677
 
601
678
  def find_applicable_labware_offset_id(
602
- self, labware_definition_uri: str, labware_location: LabwareLocation
603
- ) -> Optional[str]:
679
+ self,
680
+ labware_definition_uri: str,
681
+ labware_location: LabwareLocation,
682
+ labware_pending_load: dict[str, LoadedLabware] | None = None,
683
+ ) -> str | None:
604
684
  """Figure out what offset would apply to a labware in the given location.
605
685
 
606
686
  Raises:
@@ -612,8 +692,11 @@ class EquipmentHandler:
612
692
  or None if no labware offset will apply.
613
693
  """
614
694
  labware_offset_location = (
615
- self._get_labware_offset_location_from_labware_location(labware_location)
695
+ self._state_store.geometry.get_projected_offset_location(
696
+ labware_location, labware_pending_load
697
+ )
616
698
  )
699
+
617
700
  if labware_offset_location is None:
618
701
  # No offset for off-deck location.
619
702
  # Returning None instead of raising an exception allows loading a labware
@@ -626,72 +709,6 @@ class EquipmentHandler:
626
709
  )
627
710
  return self._get_id_from_offset(offset)
628
711
 
629
- def _get_labware_offset_location_from_labware_location(
630
- self, labware_location: LabwareLocation
631
- ) -> Optional[LabwareOffsetLocation]:
632
- if isinstance(labware_location, DeckSlotLocation):
633
- return LabwareOffsetLocation(slotName=labware_location.slotName)
634
- elif isinstance(labware_location, ModuleLocation):
635
- module_id = labware_location.moduleId
636
- # Allow ModuleNotLoadedError to propagate.
637
- # Note also that we match based on the module's requested model, not its
638
- # actual model, to implement robot-server's documented HTTP API semantics.
639
- module_model = self._state_store.modules.get_requested_model(
640
- module_id=module_id
641
- )
642
-
643
- # If `module_model is None`, it probably means that this module was added by
644
- # `ProtocolEngine.use_attached_modules()`, instead of an explicit
645
- # `loadModule` command.
646
- #
647
- # This assert should never raise in practice because:
648
- # 1. `ProtocolEngine.use_attached_modules()` is only used by
649
- # robot-server's "stateless command" endpoints, under `/commands`.
650
- # 2. Those endpoints don't support loading labware, so this code will
651
- # never run.
652
- #
653
- # Nevertheless, if it does happen somehow, we do NOT want to pass the
654
- # `None` value along to `LabwareView.find_applicable_labware_offset()`.
655
- # `None` means something different there, which will cause us to return
656
- # wrong results.
657
- assert module_model is not None, (
658
- "Can't find offsets for labware"
659
- " that are loaded on modules"
660
- " that were loaded with ProtocolEngine.use_attached_modules()."
661
- )
662
-
663
- module_location = self._state_store.modules.get_location(
664
- module_id=module_id
665
- )
666
- slot_name = module_location.slotName
667
- return LabwareOffsetLocation(slotName=slot_name, moduleModel=module_model)
668
- elif isinstance(labware_location, OnLabwareLocation):
669
- parent_labware_id = labware_location.labwareId
670
- parent_labware_uri = self._state_store.labware.get_definition_uri(
671
- parent_labware_id
672
- )
673
-
674
- base_location = self._state_store.labware.get_parent_location(
675
- parent_labware_id
676
- )
677
- base_labware_offset_location = (
678
- self._get_labware_offset_location_from_labware_location(base_location)
679
- )
680
- if base_labware_offset_location is None:
681
- # No offset for labware sitting on labware off-deck
682
- return None
683
-
684
- # If labware is being stacked on itself, all labware in the stack will share a labware offset due to
685
- # them sharing the same definitionUri in `LabwareOffsetLocation`. This will not be true for the
686
- # bottom-most labware, which will have a `DeckSlotLocation` and have its definitionUri field empty.
687
- return LabwareOffsetLocation(
688
- slotName=base_labware_offset_location.slotName,
689
- moduleModel=base_labware_offset_location.moduleModel,
690
- definitionUri=parent_labware_uri,
691
- )
692
- else: # Off deck
693
- return None
694
-
695
712
  @staticmethod
696
713
  def _get_id_from_offset(labware_offset: Optional[LabwareOffset]) -> Optional[str]:
697
714
  return None if labware_offset is None else labware_offset.id
@@ -1,4 +1,5 @@
1
1
  """Labware movement command handling."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Optional, TYPE_CHECKING, overload
@@ -128,6 +129,13 @@ class LabwareMovementHandler:
128
129
  assert labware_id is not None # From this method's @typing.overloads.
129
130
  labware_definition = self._state_store.labware.get_definition(labware_id)
130
131
 
132
+ from_labware_center = self._state_store.geometry.get_labware_grip_point(
133
+ labware_definition=labware_definition, location=current_location
134
+ )
135
+ to_labware_center = self._state_store.geometry.get_labware_grip_point(
136
+ labware_definition=labware_definition, location=new_location
137
+ )
138
+
131
139
  if use_virtual_gripper:
132
140
  # todo(mm, 2024-11-07): We should do this collision checking even when we
133
141
  # only have a `labware_definition`, not a `labware_id`. Resolve when
@@ -182,12 +190,6 @@ class LabwareMovementHandler:
182
190
  current_labware=labware_definition,
183
191
  )
184
192
  )
185
- from_labware_center = self._state_store.geometry.get_labware_grip_point(
186
- labware_definition=labware_definition, location=current_location
187
- )
188
- to_labware_center = self._state_store.geometry.get_labware_grip_point(
189
- labware_definition=labware_definition, location=new_location
190
- )
191
193
  movement_waypoints = get_gripper_labware_movement_waypoints(
192
194
  from_labware_center=from_labware_center,
193
195
  to_labware_center=to_labware_center,