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,13 +1,18 @@
1
1
  """ProtocolEngine-based Well core implementations."""
2
- from typing import Optional
2
+ from typing import Optional, Union
3
3
 
4
4
  from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
5
5
 
6
+ from opentrons.types import Point
7
+
6
8
  from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset
7
9
  from opentrons.protocol_engine import commands as cmd
8
10
  from opentrons.protocol_engine.clients import SyncClient as EngineClient
9
11
  from opentrons.protocols.api_support.util import UnsupportedAPIError
10
- from opentrons.types import Point
12
+ from opentrons.protocol_engine.types.liquid_level_detection import (
13
+ SimulatedProbeResult,
14
+ LiquidTrackingType,
15
+ )
11
16
 
12
17
  from . import point_calculations
13
18
  from . import stringify
@@ -44,17 +49,27 @@ class WellCore(AbstractWellCore):
44
49
  @property
45
50
  def diameter(self) -> Optional[float]:
46
51
  """Get the well's diameter, if circular."""
47
- return self._definition.diameter
52
+ return (
53
+ self._definition.diameter if self._definition.shape == "circular" else None
54
+ )
48
55
 
49
56
  @property
50
57
  def length(self) -> Optional[float]:
51
58
  """Get the well's length, if rectangular."""
52
- return self._definition.xDimension
59
+ return (
60
+ self._definition.xDimension
61
+ if self._definition.shape == "rectangular"
62
+ else None
63
+ )
53
64
 
54
65
  @property
55
66
  def width(self) -> Optional[float]:
56
67
  """Get the well's width, if rectangular."""
57
- return self._definition.yDimension
68
+ return (
69
+ self._definition.yDimension
70
+ if self._definition.shape == "rectangular"
71
+ else None
72
+ )
58
73
 
59
74
  @property
60
75
  def depth(self) -> float:
@@ -125,6 +140,14 @@ class WellCore(AbstractWellCore):
125
140
  well_location=WellLocation(origin=WellOrigin.CENTER),
126
141
  )
127
142
 
143
+ def get_meniscus(self) -> Union[Point, SimulatedProbeResult]:
144
+ """Get the coordinate of the well's meniscus."""
145
+ current_liquid_height = self.current_liquid_height()
146
+ if isinstance(current_liquid_height, float):
147
+ return self.get_bottom(z_offset=current_liquid_height)
148
+ else:
149
+ return current_liquid_height
150
+
128
151
  def load_liquid(
129
152
  self,
130
153
  liquid: Liquid,
@@ -155,3 +178,35 @@ class WellCore(AbstractWellCore):
155
178
  y_ratio=y,
156
179
  z_ratio=z,
157
180
  )
181
+
182
+ def estimate_liquid_height_after_pipetting(
183
+ self,
184
+ operation_volume: float,
185
+ ) -> LiquidTrackingType:
186
+ """Return an estimate of liquid height after pipetting without raising an error."""
187
+ labware_id = self.labware_id
188
+ well_name = self._name
189
+ starting_liquid_height = self.current_liquid_height()
190
+ projected_final_height = self._engine_client.state.geometry.get_well_height_after_liquid_handling_no_error(
191
+ labware_id=labware_id,
192
+ well_name=well_name,
193
+ initial_height=starting_liquid_height,
194
+ volume=operation_volume,
195
+ )
196
+ return projected_final_height
197
+
198
+ def current_liquid_height(self) -> LiquidTrackingType:
199
+ """Return the current liquid height within a well."""
200
+ labware_id = self.labware_id
201
+ well_name = self._name
202
+ return self._engine_client.state.geometry.get_meniscus_height(
203
+ labware_id=labware_id, well_name=well_name
204
+ )
205
+
206
+ def get_liquid_volume(self) -> LiquidTrackingType:
207
+ """Return the current volume in a well."""
208
+ labware_id = self.labware_id
209
+ well_name = self._name
210
+ return self._engine_client.state.geometry.get_current_well_volume(
211
+ labware_id=labware_id, well_name=well_name
212
+ )
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import abstractmethod, ABC
6
- from typing import Any, Generic, Optional, TypeVar, Union, List
6
+ from typing import Any, Generic, Optional, TypeVar, Union, List, Tuple
7
7
 
8
8
  from opentrons import types
9
9
  from opentrons.hardware_control.dev_types import PipetteDict
@@ -11,11 +11,14 @@ from opentrons.protocols.api_support.util import FlowRates
11
11
  from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2
12
12
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
13
13
  from opentrons.protocol_api._liquid import LiquidClass
14
+ from opentrons.protocol_engine.types.liquid_level_detection import LiquidTrackingType
15
+
14
16
  from ..disposal_locations import TrashBin, WasteChute
15
17
  from .well import WellCoreType
18
+ from .labware import LabwareCoreType
16
19
 
17
20
 
18
- class AbstractInstrument(ABC, Generic[WellCoreType]):
21
+ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
19
22
  @abstractmethod
20
23
  def get_default_speed(self) -> float:
21
24
  ...
@@ -25,11 +28,14 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
25
28
  ...
26
29
 
27
30
  @abstractmethod
28
- def air_gap_in_place(self, volume: float, flow_rate: float) -> None:
31
+ def air_gap_in_place(
32
+ self, volume: float, flow_rate: float, correction_volume: Optional[float] = None
33
+ ) -> None:
29
34
  """Aspirate a given volume of air from the current location of the pipette.
30
35
  Args:
31
36
  volume: The volume of air to aspirate, in microliters.
32
37
  flow_rate: The flow rate of air into the pipette, in microliters.
38
+ correction_volume: The correction volume in uL.
33
39
  """
34
40
 
35
41
  @abstractmethod
@@ -41,7 +47,8 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
41
47
  rate: float,
42
48
  flow_rate: float,
43
49
  in_place: bool,
44
- is_meniscus: Optional[bool] = None,
50
+ meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
51
+ correction_volume: Optional[float] = None,
45
52
  ) -> None:
46
53
  """Aspirate a given volume of liquid from the specified location.
47
54
  Args:
@@ -51,6 +58,8 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
51
58
  rate: The rate for how quickly to aspirate.
52
59
  flow_rate: The flow rate in µL/s to aspirate at.
53
60
  in_place: Whether this is in-place.
61
+ meniscus_tracking: Optional data about where to aspirate from.
62
+ correction_volume: The correction volume in uL
54
63
  """
55
64
  ...
56
65
 
@@ -64,7 +73,8 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
64
73
  flow_rate: float,
65
74
  in_place: bool,
66
75
  push_out: Optional[float],
67
- is_meniscus: Optional[bool] = None,
76
+ meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
77
+ correction_volume: Optional[float] = None,
68
78
  ) -> None:
69
79
  """Dispense a given volume of liquid into the specified location.
70
80
  Args:
@@ -75,6 +85,8 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
75
85
  flow_rate: The flow rate in µL/s to dispense at.
76
86
  in_place: Whether this is in-place.
77
87
  push_out: The amount to push the plunger below bottom position.
88
+ correction_volume: The correction volume in uL
89
+ meniscus_tracking: Optional data about where to dispense from.
78
90
  """
79
91
  ...
80
92
 
@@ -102,6 +114,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
102
114
  radius: float,
103
115
  z_offset: float,
104
116
  speed: float,
117
+ mm_from_edge: Optional[float] = None,
105
118
  ) -> None:
106
119
  ...
107
120
 
@@ -239,10 +252,18 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
239
252
  def get_current_volume(self) -> float:
240
253
  ...
241
254
 
255
+ @abstractmethod
256
+ def get_has_clean_tip(self) -> bool:
257
+ ...
258
+
242
259
  @abstractmethod
243
260
  def get_available_volume(self) -> float:
244
261
  ...
245
262
 
263
+ @abstractmethod
264
+ def get_minimum_liquid_sense_height(self) -> float:
265
+ ...
266
+
246
267
  @abstractmethod
247
268
  def get_hardware_state(self) -> PipetteDict:
248
269
  """Get the current state of the pipette hardware as a dictionary."""
@@ -338,29 +359,54 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
338
359
  ...
339
360
 
340
361
  @abstractmethod
341
- def load_liquid_class(
362
+ def transfer_liquid(
342
363
  self,
343
364
  liquid_class: LiquidClass,
344
- pipette_load_name: str,
345
- tiprack_uri: str,
346
- ) -> str:
347
- """Load the liquid class properties of given pipette and tiprack into the engine.
365
+ volume: float,
366
+ source: List[Tuple[types.Location, WellCoreType]],
367
+ dest: List[Tuple[types.Location, WellCoreType]],
368
+ new_tip: TransferTipPolicyV2,
369
+ tip_racks: List[Tuple[types.Location, LabwareCoreType]],
370
+ trash_location: Union[types.Location, TrashBin, WasteChute],
371
+ return_tip: bool,
372
+ ) -> None:
373
+ """Transfer a liquid from source to dest according to liquid class properties."""
374
+ ...
348
375
 
349
- Returns: ID of the liquid class record
376
+ @abstractmethod
377
+ def distribute_liquid(
378
+ self,
379
+ liquid_class: LiquidClass,
380
+ volume: float,
381
+ source: Tuple[types.Location, WellCoreType],
382
+ dest: List[Tuple[types.Location, WellCoreType]],
383
+ new_tip: TransferTipPolicyV2,
384
+ tip_racks: List[Tuple[types.Location, LabwareCoreType]],
385
+ trash_location: Union[types.Location, TrashBin, WasteChute],
386
+ return_tip: bool,
387
+ ) -> None:
388
+ """
389
+ Distribute a liquid from single source to multiple destinations
390
+ according to liquid class properties.
350
391
  """
351
392
  ...
352
393
 
353
394
  @abstractmethod
354
- def transfer_liquid(
395
+ def consolidate_liquid(
355
396
  self,
356
- liquid_class_id: str,
397
+ liquid_class: LiquidClass,
357
398
  volume: float,
358
- source: List[WellCoreType],
359
- dest: List[WellCoreType],
399
+ source: List[Tuple[types.Location, WellCoreType]],
400
+ dest: Tuple[types.Location, WellCoreType],
360
401
  new_tip: TransferTipPolicyV2,
361
- trash_location: Union[WellCoreType, types.Location, TrashBin, WasteChute],
402
+ tip_racks: List[Tuple[types.Location, LabwareCoreType]],
403
+ trash_location: Union[types.Location, TrashBin, WasteChute],
404
+ return_tip: bool,
362
405
  ) -> None:
363
- """Transfer a liquid from source to dest according to liquid class properties."""
406
+ """
407
+ Consolidate liquid from multiple sources to a single destination
408
+ using the specified liquid class properties.
409
+ """
364
410
  ...
365
411
 
366
412
  @abstractmethod
@@ -388,7 +434,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
388
434
  @abstractmethod
389
435
  def liquid_probe_without_recovery(
390
436
  self, well_core: WellCoreType, loc: types.Location
391
- ) -> float:
437
+ ) -> LiquidTrackingType:
392
438
  """Do a liquid probe to find the level of the liquid in the well."""
393
439
  ...
394
440
 
@@ -397,4 +443,4 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
397
443
  """Check if the nozzle configuration currently supports LLD."""
398
444
 
399
445
 
400
- InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any])
446
+ InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any, Any])
@@ -7,7 +7,8 @@ from typing import Any, Generic, List, NamedTuple, Optional, TypeVar, Dict
7
7
 
8
8
  from opentrons_shared_data.labware.types import (
9
9
  LabwareUri,
10
- LabwareParameters as LabwareParametersDict,
10
+ LabwareParameters2,
11
+ LabwareParameters3,
11
12
  LabwareDefinition as LabwareDefinitionDict,
12
13
  )
13
14
 
@@ -17,6 +18,9 @@ from .._liquid import Liquid
17
18
  from .well import WellCoreType
18
19
 
19
20
 
21
+ _LabwareParametersDict = LabwareParameters2 | LabwareParameters3
22
+
23
+
20
24
  class LabwareLoadParams(NamedTuple):
21
25
  """Unique load parameters of a labware."""
22
26
 
@@ -75,7 +79,7 @@ class AbstractLabware(ABC, Generic[WellCoreType]):
75
79
  """Get the labware's definition as a plain dictionary."""
76
80
 
77
81
  @abstractmethod
78
- def get_parameters(self) -> LabwareParametersDict:
82
+ def get_parameters(self) -> _LabwareParametersDict:
79
83
  """Get the labware's definition's `parameters` field as a plain dictionary."""
80
84
 
81
85
  @abstractmethod
@@ -3,7 +3,11 @@ from dataclasses import dataclass
3
3
  from typing import Optional
4
4
 
5
5
  from opentrons.hardware_control.modules import ModuleModel as HardwareModuleModel
6
- from opentrons.protocol_engine import ProtocolEngine, LabwareOffsetLocation, ModuleModel
6
+ from opentrons.protocol_engine import (
7
+ ProtocolEngine,
8
+ LegacyLabwareOffsetLocation,
9
+ ModuleModel,
10
+ )
7
11
  from opentrons.types import DeckSlotName, Point
8
12
 
9
13
  from ..labware import LabwareLoadParams
@@ -81,9 +85,9 @@ class LabwareOffsetProvider(AbstractLabwareOffsetProvider):
81
85
 
82
86
  See the parent class for param details.
83
87
  """
84
- offset = self._labware_view.find_applicable_labware_offset(
88
+ offset = self._labware_view.find_applicable_labware_offset_by_legacy_location(
85
89
  definition_uri=load_params.as_uri(),
86
- location=LabwareOffsetLocation(
90
+ location=LegacyLabwareOffsetLocation(
87
91
  slotName=deck_slot,
88
92
  moduleModel=(
89
93
  None
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from typing import TYPE_CHECKING, Optional, Union, List
4
+ from typing import TYPE_CHECKING, Optional, Union, List, Tuple
5
5
 
6
6
  from opentrons import types
7
7
  from opentrons.hardware_control import CriticalPoint
@@ -22,9 +22,12 @@ from opentrons.protocols.geometry import planning
22
22
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
23
23
  from opentrons.protocol_api._liquid import LiquidClass
24
24
 
25
+ from opentrons.protocol_engine.types.liquid_level_detection import LiquidTrackingType
26
+
25
27
  from ...disposal_locations import TrashBin, WasteChute
26
28
  from ..instrument import AbstractInstrument
27
29
  from .legacy_well_core import LegacyWellCore
30
+ from .legacy_labware_core import LegacyLabwareCore
28
31
  from .legacy_module_core import LegacyThermocyclerCore, LegacyHeaterShakerCore
29
32
 
30
33
  if TYPE_CHECKING:
@@ -37,7 +40,7 @@ _PRE_2_2_TIP_DROP_HEIGHT_MM = 10
37
40
  """In PAPIv2.1 and below, tips are always dropped 10 mm from the bottom of the well."""
38
41
 
39
42
 
40
- class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
43
+ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]):
41
44
  """Implementation of the InstrumentContext interface."""
42
45
 
43
46
  def __init__(
@@ -73,7 +76,9 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
73
76
  """Sets the speed at which the robot's gantry moves."""
74
77
  self._default_speed = speed
75
78
 
76
- def air_gap_in_place(self, volume: float, flow_rate: float) -> None:
79
+ def air_gap_in_place(
80
+ self, volume: float, flow_rate: float, correction_volume: Optional[float] = None
81
+ ) -> None:
77
82
  assert False, "Air gap tracking only available in API version 2.22 and later"
78
83
 
79
84
  def aspirate(
@@ -84,7 +89,8 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
84
89
  rate: float,
85
90
  flow_rate: float,
86
91
  in_place: bool,
87
- is_meniscus: Optional[bool] = None,
92
+ meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
93
+ correction_volume: Optional[float] = None,
88
94
  ) -> None:
89
95
  """Aspirate a given volume of liquid from the specified location.
90
96
  Args:
@@ -94,6 +100,8 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
94
100
  rate: The rate in µL/s to aspirate at.
95
101
  flow_rate: Not used in this core.
96
102
  in_place: Whether we should move_to location.
103
+ correction_volume: Not used in this core
104
+ meniscus_tracking: Optional data about where to aspirate from.
97
105
  """
98
106
  if self.get_current_volume() == 0:
99
107
  # Make sure we're at the top of the labware and clear of any
@@ -127,7 +135,8 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
127
135
  flow_rate: float,
128
136
  in_place: bool,
129
137
  push_out: Optional[float],
130
- is_meniscus: Optional[bool] = None,
138
+ meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
139
+ correction_volume: Optional[float] = None,
131
140
  ) -> None:
132
141
  """Dispense a given volume of liquid into the specified location.
133
142
  Args:
@@ -137,7 +146,9 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
137
146
  rate: The rate in µL/s to dispense at.
138
147
  flow_rate: Not used in this core.
139
148
  in_place: Whether we should move_to location.
149
+ correction_volume: Not used in this core.
140
150
  push_out: The amount to push the plunger below bottom position.
151
+ meniscus_tracking: Optional data about where to dispense from.
141
152
  """
142
153
  if isinstance(location, (TrashBin, WasteChute)):
143
154
  raise APIVersionError(
@@ -179,11 +190,14 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
179
190
  radius: float,
180
191
  z_offset: float,
181
192
  speed: float,
193
+ mm_from_edge: Optional[float] = None,
182
194
  ) -> None:
183
195
  """
184
196
  Touch the pipette tip to the sides of a well, with the intent of
185
197
  removing left-over droplets
186
198
  """
199
+ if mm_from_edge is not None:
200
+ raise APIVersionError(api_element="mm_from_edge argument")
187
201
  # TODO al 20201110 - build_edges relies on where being a Well. This is
188
202
  # an unpleasant compromise until refactoring build_edges to support
189
203
  # LegacyWellCore.
@@ -486,6 +500,10 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
486
500
  """Get the current volume."""
487
501
  return self.get_hardware_state()["current_volume"]
488
502
 
503
+ def get_has_clean_tip(self) -> bool:
504
+ """Get if has a clean tip, only used with LLD and engine commands."""
505
+ return False
506
+
489
507
  def get_available_volume(self) -> float:
490
508
  """Get the available volume."""
491
509
  return self.get_hardware_state()["available_volume"]
@@ -606,28 +624,47 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
606
624
  """This will never be called because it was added in API 2.16."""
607
625
  pass
608
626
 
609
- def load_liquid_class(
627
+ def transfer_liquid(
610
628
  self,
611
629
  liquid_class: LiquidClass,
612
- pipette_load_name: str,
613
- tiprack_uri: str,
614
- ) -> str:
615
- """This will never be called because it was added in .."""
616
- # TODO(spp, 2024-11-20): update the docstring and error to include API version
617
- assert False, "load_liquid_class is not supported in legacy context"
630
+ volume: float,
631
+ source: List[Tuple[types.Location, LegacyWellCore]],
632
+ dest: List[Tuple[types.Location, LegacyWellCore]],
633
+ new_tip: TransferTipPolicyV2,
634
+ tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
635
+ trash_location: Union[types.Location, TrashBin, WasteChute],
636
+ return_tip: bool,
637
+ ) -> None:
638
+ """This will never be called because it was added in API 2.23"""
639
+ assert False, "transfer_liquid is not supported in legacy context"
618
640
 
619
- def transfer_liquid(
641
+ def distribute_liquid(
620
642
  self,
621
- liquid_class_id: str,
643
+ liquid_class: LiquidClass,
622
644
  volume: float,
623
- source: List[LegacyWellCore],
624
- dest: List[LegacyWellCore],
645
+ source: Tuple[types.Location, LegacyWellCore],
646
+ dest: List[Tuple[types.Location, LegacyWellCore]],
625
647
  new_tip: TransferTipPolicyV2,
626
- trash_location: Union[LegacyWellCore, types.Location, TrashBin, WasteChute],
648
+ tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
649
+ trash_location: Union[types.Location, TrashBin, WasteChute],
650
+ return_tip: bool,
627
651
  ) -> None:
628
- """This will never be called because it was added in .."""
629
- # TODO(spp, 2024-11-20): update the docstring and error to include API version
630
- assert False, "transfer_liquid is not supported in legacy context"
652
+ """This will never be called because it was added in API 2.23"""
653
+ assert False, "distribute_liquid is not supported in legacy context"
654
+
655
+ def consolidate_liquid(
656
+ self,
657
+ liquid_class: LiquidClass,
658
+ volume: float,
659
+ source: List[Tuple[types.Location, LegacyWellCore]],
660
+ dest: Tuple[types.Location, LegacyWellCore],
661
+ new_tip: TransferTipPolicyV2,
662
+ tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
663
+ trash_location: Union[types.Location, TrashBin, WasteChute],
664
+ return_tip: bool,
665
+ ) -> None:
666
+ """This will never be called because it was added in API 2.23."""
667
+ assert False, "consolidate_liquid is not supported in legacy context"
631
668
 
632
669
  def get_active_channels(self) -> int:
633
670
  """This will never be called because it was added in API 2.16."""
@@ -657,7 +694,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
657
694
 
658
695
  def liquid_probe_without_recovery(
659
696
  self, well_core: WellCore, loc: types.Location
660
- ) -> float:
697
+ ) -> LiquidTrackingType:
661
698
  """This will never be called because it was added in API 2.20."""
662
699
  assert False, "liquid_probe_without_recovery only supported in API 2.20 & later"
663
700
 
@@ -667,3 +704,14 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
667
704
  def nozzle_configuration_valid_for_lld(self) -> bool:
668
705
  """Check if the nozzle configuration currently supports LLD."""
669
706
  return False
707
+
708
+ def get_minimum_liquid_sense_height(self) -> float:
709
+ return 0.0
710
+
711
+ def estimate_liquid_height(
712
+ self,
713
+ well_core: LegacyWellCore,
714
+ starting_liquid_height: float,
715
+ operation_volume: float,
716
+ ) -> float:
717
+ return 0.0
@@ -6,7 +6,7 @@ from opentrons.protocols.api_support.tip_tracker import TipTracker
6
6
 
7
7
  from opentrons.types import DeckSlotName, Location, Point, NozzleMapInterface
8
8
 
9
- from opentrons_shared_data.labware.types import LabwareParameters, LabwareDefinition
9
+ from opentrons_shared_data.labware.types import LabwareParameters2, LabwareDefinition2
10
10
 
11
11
  from ..._liquid import Liquid
12
12
  from ..labware import AbstractLabware, LabwareLoadParams
@@ -36,7 +36,11 @@ class LegacyLabwareCore(AbstractLabware[LegacyWellCore]):
36
36
 
37
37
  def __init__(
38
38
  self,
39
- definition: LabwareDefinition,
39
+ # We need labware schema 2, specifically, because schema 3 changes how positions
40
+ # are calculated, and we don't attempt to handle that here in
41
+ # `opentrons.protocol_api.core.legacy`. We do handle it in
42
+ # `opentrons.protocol_api.core.engine` and `opentrons.protocol_engine`.
43
+ definition: LabwareDefinition2,
40
44
  parent: Location,
41
45
  label: Optional[str] = None,
42
46
  ) -> None:
@@ -106,10 +110,10 @@ class LegacyLabwareCore(AbstractLabware[LegacyWellCore]):
106
110
  def set_name(self, new_name: str) -> None:
107
111
  self._name = new_name
108
112
 
109
- def get_definition(self) -> LabwareDefinition:
113
+ def get_definition(self) -> LabwareDefinition2:
110
114
  return self._definition
111
115
 
112
- def get_parameters(self) -> LabwareParameters:
116
+ def get_parameters(self) -> LabwareParameters2:
113
117
  return self._parameters
114
118
 
115
119
  def get_quirks(self) -> List[str]:
@@ -209,6 +209,10 @@ class LegacyProtocolCore(
209
209
  bundled_defs=self._bundled_labware,
210
210
  extra_defs=self._extra_labware,
211
211
  )
212
+ # For type checking. This should always pass because
213
+ # opentrons.protocol_api.core.legacy should only load labware with schema 2.
214
+ assert labware_def["schemaVersion"] == 2
215
+
212
216
  labware_core = LegacyLabwareCore(
213
217
  definition=labware_def,
214
218
  parent=parent,
@@ -307,6 +311,25 @@ class LegacyProtocolCore(
307
311
  """Move labware to new location."""
308
312
  raise APIVersionError(api_element="Labware movement")
309
313
 
314
+ def move_lid(
315
+ self,
316
+ source_location: Union[DeckSlotName, StagingSlotName, LegacyLabwareCore],
317
+ new_location: Union[
318
+ DeckSlotName,
319
+ StagingSlotName,
320
+ LegacyLabwareCore,
321
+ OffDeckType,
322
+ WasteChute,
323
+ TrashBin,
324
+ ],
325
+ use_gripper: bool,
326
+ pause_for_manual_move: bool,
327
+ pick_up_offset: Optional[Tuple[float, float, float]],
328
+ drop_offset: Optional[Tuple[float, float, float]],
329
+ ) -> LegacyLabwareCore | None:
330
+ """Move lid to new location."""
331
+ raise APIVersionError(api_element="Lid movement")
332
+
310
333
  def load_module(
311
334
  self,
312
335
  model: ModuleModel,
@@ -505,6 +528,19 @@ class LegacyProtocolCore(
505
528
  """Load a Stack of Lids to a given location, creating a Lid Stack."""
506
529
  raise APIVersionError(api_element="Lid stack")
507
530
 
531
+ def load_labware_to_flex_stacker_hopper(
532
+ self,
533
+ module_core: legacy_module_core.LegacyModuleCore,
534
+ load_name: str,
535
+ quantity: int,
536
+ label: Optional[str],
537
+ namespace: Optional[str],
538
+ version: Optional[int],
539
+ lid: Optional[str],
540
+ ) -> None:
541
+ """Load labware to a Flex stacker hopper."""
542
+ raise APIVersionError(api_element="Flex stacker")
543
+
508
544
  def get_module_cores(self) -> List[legacy_module_core.LegacyModuleCore]:
509
545
  """Get loaded module cores."""
510
546
  return self._module_cores
@@ -1,5 +1,5 @@
1
1
  """Legacy Well core implementation."""
2
- from typing import Optional
2
+ from typing import Optional, Union
3
3
 
4
4
  from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
5
5
 
@@ -7,6 +7,11 @@ from opentrons.protocols.api_support.util import APIVersionError
7
7
 
8
8
  from opentrons.types import Point
9
9
 
10
+ from opentrons.protocol_engine.types.liquid_level_detection import (
11
+ SimulatedProbeResult,
12
+ LiquidTrackingType,
13
+ )
14
+
10
15
  from .well_geometry import WellGeometry
11
16
  from ..well import AbstractWellCore
12
17
  from ..._liquid import Liquid
@@ -106,6 +111,10 @@ class LegacyWellCore(AbstractWellCore):
106
111
  """Get the coordinate of the well's center."""
107
112
  return self._geometry.center()
108
113
 
114
+ def get_meniscus(self) -> Union[Point, SimulatedProbeResult]:
115
+ """Get the coordinate of the well's center."""
116
+ raise APIVersionError(api_element="Getting a meniscus")
117
+
109
118
  def load_liquid(
110
119
  self,
111
120
  liquid: Liquid,
@@ -118,6 +127,21 @@ class LegacyWellCore(AbstractWellCore):
118
127
  """Gets point in deck coordinates based on percentage of the radius of each axis."""
119
128
  return self._geometry.from_center_cartesian(x, y, z)
120
129
 
130
+ def estimate_liquid_height_after_pipetting(
131
+ self,
132
+ operation_volume: float,
133
+ ) -> LiquidTrackingType:
134
+ """Estimate what the liquid height will be after pipetting, without raising an error."""
135
+ return 0.0
136
+
137
+ def current_liquid_height(self) -> LiquidTrackingType:
138
+ """Get the current liquid height."""
139
+ return 0.0
140
+
141
+ def get_liquid_volume(self) -> LiquidTrackingType:
142
+ """Get the current well volume."""
143
+ return 0.0
144
+
121
145
  # TODO(mc, 2022-10-28): is this used and/or necessary?
122
146
  def __repr__(self) -> str:
123
147
  """Use the well's display name as its repr."""