opentrons 8.3.2a0__py2.py3-none-any.whl → 8.4.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (196) 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 +102 -5
  39. opentrons/legacy_commands/helpers.py +74 -1
  40. opentrons/legacy_commands/types.py +33 -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 +1356 -107
  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/pipette_movement_conflict.py +6 -14
  52. opentrons/protocol_api/core/engine/protocol.py +253 -11
  53. opentrons/protocol_api/core/engine/stringify.py +19 -8
  54. opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
  55. opentrons/protocol_api/core/engine/well.py +73 -5
  56. opentrons/protocol_api/core/instrument.py +71 -21
  57. opentrons/protocol_api/core/labware.py +6 -2
  58. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  59. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
  60. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  61. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  62. opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
  63. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  64. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  65. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
  67. opentrons/protocol_api/core/module.py +43 -0
  68. opentrons/protocol_api/core/protocol.py +33 -0
  69. opentrons/protocol_api/core/well.py +23 -2
  70. opentrons/protocol_api/instrument_context.py +454 -150
  71. opentrons/protocol_api/labware.py +98 -50
  72. opentrons/protocol_api/module_contexts.py +140 -0
  73. opentrons/protocol_api/protocol_context.py +163 -19
  74. opentrons/protocol_api/validation.py +51 -41
  75. opentrons/protocol_engine/__init__.py +21 -2
  76. opentrons/protocol_engine/actions/actions.py +5 -5
  77. opentrons/protocol_engine/clients/sync_client.py +6 -0
  78. opentrons/protocol_engine/commands/__init__.py +66 -36
  79. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  80. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  81. opentrons/protocol_engine/commands/aspirate.py +6 -2
  82. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  83. opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
  84. opentrons/protocol_engine/commands/blow_out.py +2 -0
  85. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  86. opentrons/protocol_engine/commands/command_unions.py +102 -33
  87. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  88. opentrons/protocol_engine/commands/dispense.py +3 -1
  89. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  90. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  91. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  92. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  93. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  94. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  95. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  96. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  97. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  98. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  99. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  100. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  101. opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
  102. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  103. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  104. opentrons/protocol_engine/commands/liquid_probe.py +27 -13
  105. opentrons/protocol_engine/commands/load_labware.py +42 -39
  106. opentrons/protocol_engine/commands/load_lid.py +21 -13
  107. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  108. opentrons/protocol_engine/commands/load_module.py +18 -17
  109. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  110. opentrons/protocol_engine/commands/move_labware.py +139 -20
  111. opentrons/protocol_engine/commands/move_to_well.py +5 -11
  112. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  113. opentrons/protocol_engine/commands/pipetting_common.py +159 -8
  114. opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
  115. opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
  118. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  119. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  120. opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
  121. opentrons/protocol_engine/errors/__init__.py +10 -0
  122. opentrons/protocol_engine/errors/exceptions.py +62 -0
  123. opentrons/protocol_engine/execution/equipment.py +123 -106
  124. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  125. opentrons/protocol_engine/execution/pipetting.py +235 -25
  126. opentrons/protocol_engine/execution/tip_handler.py +82 -32
  127. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  128. opentrons/protocol_engine/protocol_engine.py +22 -13
  129. opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
  130. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  131. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  132. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  133. opentrons/protocol_engine/slot_standardization.py +11 -23
  134. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  135. opentrons/protocol_engine/state/frustum_helpers.py +36 -14
  136. opentrons/protocol_engine/state/geometry.py +892 -227
  137. opentrons/protocol_engine/state/labware.py +252 -55
  138. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  139. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  140. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  141. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  142. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  143. opentrons/protocol_engine/state/modules.py +210 -67
  144. opentrons/protocol_engine/state/pipettes.py +54 -0
  145. opentrons/protocol_engine/state/state.py +1 -1
  146. opentrons/protocol_engine/state/tips.py +14 -0
  147. opentrons/protocol_engine/state/update_types.py +180 -25
  148. opentrons/protocol_engine/state/wells.py +55 -9
  149. opentrons/protocol_engine/types/__init__.py +300 -0
  150. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  151. opentrons/protocol_engine/types/command_annotations.py +53 -0
  152. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  153. opentrons/protocol_engine/types/execution.py +96 -0
  154. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  155. opentrons/protocol_engine/types/instrument.py +47 -0
  156. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  157. opentrons/protocol_engine/types/labware.py +111 -0
  158. opentrons/protocol_engine/types/labware_movement.py +22 -0
  159. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  160. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  161. opentrons/protocol_engine/types/liquid.py +40 -0
  162. opentrons/protocol_engine/types/liquid_class.py +59 -0
  163. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  164. opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
  165. opentrons/protocol_engine/types/location.py +194 -0
  166. opentrons/protocol_engine/types/module.py +301 -0
  167. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  168. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  169. opentrons/protocol_engine/types/tip.py +18 -0
  170. opentrons/protocol_engine/types/util.py +21 -0
  171. opentrons/protocol_engine/types/well_position.py +124 -0
  172. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  173. opentrons/protocol_reader/file_format_validator.py +5 -3
  174. opentrons/protocol_runner/json_translator.py +4 -2
  175. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  176. opentrons/protocol_runner/run_orchestrator.py +4 -1
  177. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/instrument.py +16 -3
  181. opentrons/protocols/labware.py +27 -23
  182. opentrons/protocols/models/__init__.py +0 -21
  183. opentrons/simulate.py +4 -2
  184. opentrons/types.py +20 -7
  185. opentrons/util/logging_config.py +94 -25
  186. opentrons/util/logging_queue_handler.py +61 -0
  187. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
  188. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
  189. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  190. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  191. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  192. opentrons/protocol_engine/types.py +0 -1311
  193. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
  194. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
  195. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
  196. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,12 @@
1
- """Unseal evotip resin tip command request, result, and implementation models."""
1
+ """Unseal tip from pipette command request, result, and implementation models."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  from pydantic import Field
6
- from typing import TYPE_CHECKING, Optional, Type
6
+ from typing import TYPE_CHECKING, Optional, Type, Final
7
7
  from typing_extensions import Literal
8
8
 
9
9
  from opentrons.protocol_engine.resources.model_utils import ModelUtils
10
- from opentrons.protocol_engine.errors import UnsupportedLabwareForActionError
11
- from opentrons.protocol_engine.types import MotorAxis
12
- from opentrons.types import MountType
13
10
 
14
11
  from ..types import DropTipWellLocation
15
12
  from .pipetting_common import (
@@ -27,17 +24,16 @@ from .command import (
27
24
  DefinedErrorData,
28
25
  SuccessData,
29
26
  )
30
- from ..resources import labware_validation
31
27
 
32
28
  if TYPE_CHECKING:
33
29
  from ..state.state import StateView
34
30
  from ..execution import MovementHandler, TipHandler, GantryMover
35
31
 
36
32
 
37
- EvotipUnsealPipetteCommandType = Literal["evotipUnsealPipette"]
33
+ UnsealPipetteFromTipCommandType = Literal["unsealPipetteFromTip"]
38
34
 
39
35
 
40
- class EvotipUnsealPipetteParams(PipetteIdMixin):
36
+ class UnsealPipetteFromTipParams(PipetteIdMixin):
41
37
  """Payload required to drop a tip in a specific well."""
42
38
 
43
39
  labwareId: str = Field(..., description="Identifier of labware to use.")
@@ -48,19 +44,21 @@ class EvotipUnsealPipetteParams(PipetteIdMixin):
48
44
  )
49
45
 
50
46
 
51
- class EvotipUnsealPipetteResult(DestinationPositionResult):
47
+ class UnsealPipetteFromTipResult(DestinationPositionResult):
52
48
  """Result data from the execution of a DropTip command."""
53
49
 
54
50
  pass
55
51
 
56
52
 
57
53
  _ExecuteReturn = (
58
- SuccessData[EvotipUnsealPipetteResult] | DefinedErrorData[StallOrCollisionError]
54
+ SuccessData[UnsealPipetteFromTipResult] | DefinedErrorData[StallOrCollisionError]
59
55
  )
60
56
 
57
+ CUSTOM_TIP_LENGTH_MARGIN: Final = 10
61
58
 
62
- class EvotipUnsealPipetteImplementation(
63
- AbstractCommandImpl[EvotipUnsealPipetteParams, _ExecuteReturn]
59
+
60
+ class UnsealPipetteFromTipImplementation(
61
+ AbstractCommandImpl[UnsealPipetteFromTipParams, _ExecuteReturn]
64
62
  ):
65
63
  """Drop tip command implementation."""
66
64
 
@@ -79,18 +77,18 @@ class EvotipUnsealPipetteImplementation(
79
77
  self._model_utils = model_utils
80
78
  self._gantry_mover = gantry_mover
81
79
 
82
- async def execute(self, params: EvotipUnsealPipetteParams) -> _ExecuteReturn:
80
+ async def execute(self, params: UnsealPipetteFromTipParams) -> _ExecuteReturn:
83
81
  """Move to and drop a tip using the requested pipette."""
84
82
  pipette_id = params.pipetteId
85
83
  labware_id = params.labwareId
86
84
  well_name = params.wellName
87
85
 
88
86
  well_location = params.wellLocation
89
- labware_definition = self._state_view.labware.get_definition(params.labwareId)
90
- if not labware_validation.is_evotips(labware_definition.parameters.loadName):
91
- raise UnsupportedLabwareForActionError(
92
- f"Cannot use command: `EvotipUnsealPipette` with labware: {labware_definition.parameters.loadName}"
93
- )
87
+
88
+ tip_geometry = self._state_view.geometry.get_nominal_tip_geometry(
89
+ pipette_id, labware_id, well_name
90
+ )
91
+
94
92
  is_partially_configured = self._state_view.pipettes.get_is_partially_configured(
95
93
  pipette_id=pipette_id
96
94
  )
@@ -99,6 +97,7 @@ class EvotipUnsealPipetteImplementation(
99
97
  labware_id=labware_id,
100
98
  well_location=well_location,
101
99
  partially_configured=is_partially_configured,
100
+ override_default_offset=-(tip_geometry.length - CUSTOM_TIP_LENGTH_MARGIN),
102
101
  )
103
102
 
104
103
  move_result = await move_to_well(
@@ -112,14 +111,6 @@ class EvotipUnsealPipetteImplementation(
112
111
  if isinstance(move_result, DefinedErrorData):
113
112
  return move_result
114
113
 
115
- # Move to an appropriate position
116
- mount = self._state_view.pipettes.get_mount(pipette_id)
117
-
118
- mount_axis = MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
119
- await self._gantry_mover.move_axes(
120
- axis_map={mount_axis: -14}, speed=10, relative_move=True
121
- )
122
-
123
114
  await self._tip_handler.drop_tip(
124
115
  pipette_id=pipette_id,
125
116
  home_after=None,
@@ -128,33 +119,33 @@ class EvotipUnsealPipetteImplementation(
128
119
  )
129
120
 
130
121
  return SuccessData(
131
- public=EvotipUnsealPipetteResult(position=move_result.public.position),
122
+ public=UnsealPipetteFromTipResult(position=move_result.public.position),
132
123
  state_update=move_result.state_update.set_fluid_unknown(
133
124
  pipette_id=pipette_id
134
125
  ).update_pipette_tip_state(pipette_id=params.pipetteId, tip_geometry=None),
135
126
  )
136
127
 
137
128
 
138
- class EvotipUnsealPipette(
129
+ class UnsealPipetteFromTip(
139
130
  BaseCommand[
140
- EvotipUnsealPipetteParams, EvotipUnsealPipetteResult, StallOrCollisionError
131
+ UnsealPipetteFromTipParams, UnsealPipetteFromTipResult, StallOrCollisionError
141
132
  ]
142
133
  ):
143
- """Evotip unseal command model."""
134
+ """Unseal pipette command model."""
144
135
 
145
- commandType: EvotipUnsealPipetteCommandType = "evotipUnsealPipette"
146
- params: EvotipUnsealPipetteParams
147
- result: Optional[EvotipUnsealPipetteResult] = None
136
+ commandType: UnsealPipetteFromTipCommandType = "unsealPipetteFromTip"
137
+ params: UnsealPipetteFromTipParams
138
+ result: Optional[UnsealPipetteFromTipResult] = None
148
139
 
149
140
  _ImplementationCls: Type[
150
- EvotipUnsealPipetteImplementation
151
- ] = EvotipUnsealPipetteImplementation
141
+ UnsealPipetteFromTipImplementation
142
+ ] = UnsealPipetteFromTipImplementation
152
143
 
153
144
 
154
- class EvotipUnsealPipetteCreate(BaseCommandCreate[EvotipUnsealPipetteParams]):
155
- """Evotip unseal command creation request model."""
145
+ class UnsealPipetteFromTipCreate(BaseCommandCreate[UnsealPipetteFromTipParams]):
146
+ """Unseal pipette command creation request model."""
156
147
 
157
- commandType: EvotipUnsealPipetteCommandType = "evotipUnsealPipette"
158
- params: EvotipUnsealPipetteParams
148
+ commandType: UnsealPipetteFromTipCommandType = "unsealPipetteFromTip"
149
+ params: UnsealPipetteFromTipParams
159
150
 
160
- _CommandCls: Type[EvotipUnsealPipette] = EvotipUnsealPipette
151
+ _CommandCls: Type[UnsealPipetteFromTip] = UnsealPipetteFromTip
@@ -76,6 +76,7 @@ from .exceptions import (
76
76
  CommandNotAllowedError,
77
77
  InvalidLiquidHeightFound,
78
78
  LiquidHeightUnknownError,
79
+ LiquidVolumeUnknownError,
79
80
  IncompleteLabwareDefinitionError,
80
81
  IncompleteWellDefinitionError,
81
82
  OperationLocationNotInWellError,
@@ -84,6 +85,10 @@ from .exceptions import (
84
85
  InvalidLiquidError,
85
86
  LiquidClassDoesNotExistError,
86
87
  LiquidClassRedefinitionError,
88
+ OffsetLocationInvalidError,
89
+ FlexStackerLabwarePoolNotYetDefinedError,
90
+ FlexStackerNotLogicallyEmptyError,
91
+ InvalidLabwarePositionError,
87
92
  )
88
93
 
89
94
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -164,11 +169,16 @@ __all__ = [
164
169
  "LocationIsLidDockSlotError",
165
170
  "InvalidAxisForRobotType",
166
171
  "NotSupportedOnRobotType",
172
+ "OffsetLocationInvalidError",
173
+ "FlexStackerLabwarePoolNotYetDefinedError",
174
+ "FlexStackerNotLogicallyEmptyError",
175
+ "InvalidLabwarePositionError",
167
176
  # error occurrence models
168
177
  "ErrorOccurrence",
169
178
  "CommandNotAllowedError",
170
179
  "InvalidLiquidHeightFound",
171
180
  "LiquidHeightUnknownError",
181
+ "LiquidVolumeUnknownError",
172
182
  "IncompleteLabwareDefinitionError",
173
183
  "IncompleteWellDefinitionError",
174
184
  "OperationLocationNotInWellError",
@@ -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,39 @@ 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)
1284
+
1285
+
1286
+ class InvalidLabwarePositionError(ProtocolEngineError):
1287
+ """Raised when a labware position is internally invalid."""
1288
+
1289
+ def __init__(
1290
+ self,
1291
+ message: Optional[str] = None,
1292
+ details: Optional[dict[str, Any]] = None,
1293
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1294
+ ) -> None:
1295
+ 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,