opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.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.
Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +36 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -1
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +52 -70
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,13 @@ from opentrons_shared_data.pipette.types import PipetteNameType
10
10
  from opentrons_shared_data.labware.types import LabwareDefinition
11
11
  from opentrons_shared_data.robot.types import RobotType
12
12
 
13
- from opentrons.types import DeckSlotName, StagingSlotName, Location, Mount, Point
13
+ from opentrons.types import (
14
+ DeckSlotName,
15
+ StagingSlotName,
16
+ Location,
17
+ Mount,
18
+ Point,
19
+ )
14
20
  from opentrons.hardware_control import SyncHardwareAPI
15
21
  from opentrons.hardware_control.modules.types import ModuleModel
16
22
  from opentrons.protocols.api_support.util import AxisMaxSpeeds
@@ -19,6 +25,7 @@ from .instrument import InstrumentCoreType
19
25
  from .labware import LabwareCoreType, LabwareLoadParams
20
26
  from .module import ModuleCoreType
21
27
  from .._liquid import Liquid, LiquidClass
28
+ from .robot import AbstractRobot
22
29
  from .._types import OffDeckType
23
30
  from ..disposal_locations import TrashBin, WasteChute
24
31
 
@@ -93,6 +100,17 @@ class AbstractProtocol(
93
100
  """Load an adapter using its identifying parameters"""
94
101
  ...
95
102
 
103
+ @abstractmethod
104
+ def load_lid(
105
+ self,
106
+ load_name: str,
107
+ location: LabwareCoreType,
108
+ namespace: Optional[str],
109
+ version: Optional[int],
110
+ ) -> LabwareCoreType:
111
+ """Load an individual lid labware using its identifying parameters. Must be loaded on a labware."""
112
+ ...
113
+
96
114
  @abstractmethod
97
115
  def move_labware(
98
116
  self,
@@ -190,6 +208,17 @@ class AbstractProtocol(
190
208
  ) -> None:
191
209
  ...
192
210
 
211
+ @abstractmethod
212
+ def load_lid_stack(
213
+ self,
214
+ load_name: str,
215
+ location: Union[DeckSlotName, StagingSlotName, LabwareCoreType],
216
+ quantity: int,
217
+ namespace: Optional[str],
218
+ version: Optional[int],
219
+ ) -> LabwareCoreType:
220
+ ...
221
+
193
222
  @abstractmethod
194
223
  def get_deck_definition(self) -> DeckDefinitionV5:
195
224
  """Get the geometry definition of the robot's deck."""
@@ -257,3 +286,7 @@ class AbstractProtocol(
257
286
  self, labware_core: LabwareCoreType
258
287
  ) -> Union[str, LabwareCoreType, ModuleCoreType, OffDeckType]:
259
288
  """Get labware parent location."""
289
+
290
+ @abstractmethod
291
+ def load_robot(self) -> AbstractRobot:
292
+ """Load a Robot Core context into a protocol"""
@@ -0,0 +1,51 @@
1
+ from abc import abstractmethod, ABC
2
+ from typing import Optional, Union
3
+
4
+ from opentrons.types import AxisMapType, Mount, Point
5
+ from opentrons_shared_data.pipette.types import PipetteNameType
6
+ from opentrons.protocol_api._types import PlungerPositionTypes, PipetteActionTypes
7
+
8
+
9
+ class AbstractRobot(ABC):
10
+ @abstractmethod
11
+ def get_pipette_type_from_engine(
12
+ self, mount: Union[Mount, str]
13
+ ) -> Optional[PipetteNameType]:
14
+ ...
15
+
16
+ @abstractmethod
17
+ def get_plunger_position_from_volume(
18
+ self, mount: Mount, volume: float, action: PipetteActionTypes, robot_type: str
19
+ ) -> float:
20
+ ...
21
+
22
+ @abstractmethod
23
+ def get_plunger_position_from_name(
24
+ self, mount: Mount, position_name: PlungerPositionTypes
25
+ ) -> float:
26
+ ...
27
+
28
+ @abstractmethod
29
+ def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None:
30
+ ...
31
+
32
+ @abstractmethod
33
+ def move_axes_to(
34
+ self,
35
+ axis_map: AxisMapType,
36
+ critical_point: Optional[AxisMapType],
37
+ speed: Optional[float],
38
+ ) -> None:
39
+ ...
40
+
41
+ @abstractmethod
42
+ def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None:
43
+ ...
44
+
45
+ @abstractmethod
46
+ def release_grip(self) -> None:
47
+ ...
48
+
49
+ @abstractmethod
50
+ def close_gripper(self, force: Optional[float] = None) -> None:
51
+ ...
@@ -2,12 +2,14 @@ from __future__ import annotations
2
2
  import logging
3
3
  from contextlib import ExitStack
4
4
  from typing import Any, List, Optional, Sequence, Union, cast, Dict
5
- from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
6
5
  from opentrons_shared_data.errors.exceptions import (
7
6
  CommandPreconditionViolated,
8
7
  CommandParameterLimitViolated,
9
8
  UnexpectedTipRemovalError,
9
+ UnsupportedHardwareCommand,
10
10
  )
11
+ from opentrons_shared_data.robot.types import RobotTypeEnum
12
+
11
13
  from opentrons.legacy_broker import LegacyBroker
12
14
  from opentrons.hardware_control.dev_types import PipetteDict
13
15
  from opentrons import types
@@ -15,8 +17,7 @@ from opentrons.legacy_commands import commands as cmds
15
17
 
16
18
  from opentrons.legacy_commands import publisher
17
19
  from opentrons.protocols.advanced_control.mix import mix_from_kwargs
18
- from opentrons.protocols.advanced_control import transfers
19
-
20
+ from opentrons.protocols.advanced_control.transfers import transfer as v1_transfer
20
21
  from opentrons.protocols.api_support.deck_type import NoTrashDefinedError
21
22
  from opentrons.protocols.api_support.types import APIVersion
22
23
  from opentrons.protocols.api_support import instrument
@@ -28,7 +29,6 @@ from opentrons.protocols.api_support.util import (
28
29
  APIVersionError,
29
30
  UnsupportedAPIError,
30
31
  )
31
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
32
32
 
33
33
  from .core.common import InstrumentCore, ProtocolCore
34
34
  from .core.engine import ENGINE_CORE_API_VERSION
@@ -36,10 +36,13 @@ from .core.legacy.legacy_instrument_core import LegacyInstrumentCore
36
36
  from .config import Clearances
37
37
  from .disposal_locations import TrashBin, WasteChute
38
38
  from ._nozzle_layout import NozzleLayout
39
+ from ._liquid import LiquidClass
39
40
  from . import labware, validation
40
-
41
-
42
- AdvancedLiquidHandling = transfers.AdvancedLiquidHandling
41
+ from ..config import feature_flags
42
+ from ..protocols.advanced_control.transfers.common import (
43
+ TransferTipPolicyV2,
44
+ TransferTipPolicyV2Type,
45
+ )
43
46
 
44
47
  _DEFAULT_ASPIRATE_CLEARANCE = 1.0
45
48
  _DEFAULT_DISPENSE_CLEARANCE = 1.0
@@ -60,6 +63,10 @@ _DISPOSAL_LOCATION_OFFSET_ADDED_IN = APIVersion(2, 18)
60
63
  """The version after which offsets for deck configured trash containers and changes to alternating tip drop behavior were introduced."""
61
64
  _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN = APIVersion(2, 20)
62
65
  """The version after which partial nozzle configurations of single, row, and partial column layouts became available."""
66
+ _AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
67
+ """The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking."""
68
+
69
+ AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
63
70
 
64
71
 
65
72
  class InstrumentContext(publisher.CommandPublisher):
@@ -257,9 +264,10 @@ class InstrumentContext(publisher.CommandPublisher):
257
264
  self.api_version >= APIVersion(2, 20)
258
265
  and well is not None
259
266
  and self.liquid_presence_detection
260
- and self._96_tip_config_valid()
267
+ and self._core.nozzle_configuration_valid_for_lld()
261
268
  and self._core.get_current_volume() == 0
262
269
  ):
270
+ self._raise_if_pressure_not_supported_by_pipette()
263
271
  self.require_liquid_presence(well=well)
264
272
 
265
273
  with publisher.publish_context(
@@ -513,6 +521,8 @@ class InstrumentContext(publisher.CommandPublisher):
513
521
  ``pipette.mix(1, location=wellplate['A1'])`` is a valid call, but
514
522
  ``pipette.mix(1, wellplate['A1'])`` is not.
515
523
 
524
+ .. versionchanged:: 2.21
525
+ Does not repeatedly check for liquid presence.
516
526
  """
517
527
  _log.debug(
518
528
  "mixing {}uL with {} repetitions in {} at rate={}".format(
@@ -753,7 +763,11 @@ class InstrumentContext(publisher.CommandPublisher):
753
763
  ``pipette.air_gap(height=2)``. If you call ``air_gap`` with a single,
754
764
  unnamed argument, it will always be interpreted as a volume.
755
765
 
756
-
766
+ .. TODO: restore this as a note block for 2.22 docs
767
+ Before API version 2.22, this function was implemented as an aspirate, and
768
+ dispensing into a well would add the air gap volume to the liquid tracked in
769
+ the well. At or above API version 2.22, air gap volume is not counted as liquid
770
+ when dispensing into a well.
757
771
  """
758
772
  if not self._core.has_tip():
759
773
  raise UnexpectedTipRemovalError("air_gap", self.name, self.mount)
@@ -765,7 +779,12 @@ class InstrumentContext(publisher.CommandPublisher):
765
779
  raise RuntimeError("No previous Well cached to perform air gap")
766
780
  target = loc.labware.as_well().top(height)
767
781
  self.move_to(target, publish=False)
768
- self.aspirate(volume)
782
+ if self.api_version >= _AIR_GAP_TRACKING_ADDED_IN:
783
+ c_vol = self._core.get_available_volume() if volume is None else volume
784
+ flow_rate = self._core.get_aspirate_flow_rate()
785
+ self._core.air_gap_in_place(c_vol, flow_rate)
786
+ else:
787
+ self.aspirate(volume)
769
788
  return self
770
789
 
771
790
  @publisher.publish(command=cmds.return_tip)
@@ -934,7 +953,7 @@ class InstrumentContext(publisher.CommandPublisher):
934
953
  if location is None:
935
954
  if (
936
955
  nozzle_map is not None
937
- and nozzle_map.configuration != NozzleConfigurationType.FULL
956
+ and nozzle_map.configuration != types.NozzleConfigurationType.FULL
938
957
  and self.starting_tip is not None
939
958
  ):
940
959
  # Disallowing this avoids concerning the system with the direction
@@ -1206,7 +1225,6 @@ class InstrumentContext(publisher.CommandPublisher):
1206
1225
  self._core.home_plunger()
1207
1226
  return self
1208
1227
 
1209
- # TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling
1210
1228
  @publisher.publish(command=cmds.distribute)
1211
1229
  @requires_version(2, 0)
1212
1230
  def distribute(
@@ -1246,7 +1264,6 @@ class InstrumentContext(publisher.CommandPublisher):
1246
1264
 
1247
1265
  return self.transfer(volume, source, dest, **kwargs)
1248
1266
 
1249
- # TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling
1250
1267
  @publisher.publish(command=cmds.consolidate)
1251
1268
  @requires_version(2, 0)
1252
1269
  def consolidate(
@@ -1396,9 +1413,9 @@ class InstrumentContext(publisher.CommandPublisher):
1396
1413
  mix_strategy, mix_opts = mix_from_kwargs(kwargs)
1397
1414
 
1398
1415
  if trash:
1399
- drop_tip = transfers.DropTipStrategy.TRASH
1416
+ drop_tip = v1_transfer.DropTipStrategy.TRASH
1400
1417
  else:
1401
- drop_tip = transfers.DropTipStrategy.RETURN
1418
+ drop_tip = v1_transfer.DropTipStrategy.RETURN
1402
1419
 
1403
1420
  new_tip = kwargs.get("new_tip")
1404
1421
  if isinstance(new_tip, str):
@@ -1420,19 +1437,19 @@ class InstrumentContext(publisher.CommandPublisher):
1420
1437
 
1421
1438
  if blow_out and not blowout_location:
1422
1439
  if self.current_volume:
1423
- blow_out_strategy = transfers.BlowOutStrategy.SOURCE
1440
+ blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE
1424
1441
  else:
1425
- blow_out_strategy = transfers.BlowOutStrategy.TRASH
1442
+ blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH
1426
1443
  elif blow_out and blowout_location:
1427
1444
  if blowout_location == "source well":
1428
- blow_out_strategy = transfers.BlowOutStrategy.SOURCE
1445
+ blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE
1429
1446
  elif blowout_location == "destination well":
1430
- blow_out_strategy = transfers.BlowOutStrategy.DEST
1447
+ blow_out_strategy = v1_transfer.BlowOutStrategy.DEST
1431
1448
  elif blowout_location == "trash":
1432
- blow_out_strategy = transfers.BlowOutStrategy.TRASH
1449
+ blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH
1433
1450
 
1434
1451
  if new_tip != types.TransferTipPolicy.NEVER:
1435
- tr, next_tip = labware.next_available_tip(
1452
+ _, next_tip = labware.next_available_tip(
1436
1453
  self.starting_tip,
1437
1454
  self.tip_racks,
1438
1455
  active_channels,
@@ -1444,9 +1461,9 @@ class InstrumentContext(publisher.CommandPublisher):
1444
1461
 
1445
1462
  touch_tip = None
1446
1463
  if kwargs.get("touch_tip"):
1447
- touch_tip = transfers.TouchTipStrategy.ALWAYS
1464
+ touch_tip = v1_transfer.TouchTipStrategy.ALWAYS
1448
1465
 
1449
- default_args = transfers.Transfer()
1466
+ default_args = v1_transfer.Transfer()
1450
1467
 
1451
1468
  disposal = kwargs.get("disposal_volume")
1452
1469
  if disposal is None:
@@ -1459,7 +1476,7 @@ class InstrumentContext(publisher.CommandPublisher):
1459
1476
  f"working volume, {max_volume}uL"
1460
1477
  )
1461
1478
 
1462
- transfer_args = transfers.Transfer(
1479
+ transfer_args = v1_transfer.Transfer(
1463
1480
  new_tip=new_tip or default_args.new_tip,
1464
1481
  air_gap=air_gap,
1465
1482
  carryover=kwargs.get("carryover") or default_args.carryover,
@@ -1472,10 +1489,10 @@ class InstrumentContext(publisher.CommandPublisher):
1472
1489
  blow_out_strategy=blow_out_strategy or default_args.blow_out_strategy,
1473
1490
  touch_tip_strategy=(touch_tip or default_args.touch_tip_strategy),
1474
1491
  )
1475
- transfer_options = transfers.TransferOptions(
1492
+ transfer_options = v1_transfer.TransferOptions(
1476
1493
  transfer=transfer_args, mix=mix_opts
1477
1494
  )
1478
- plan = transfers.TransferPlan(
1495
+ plan = v1_transfer.TransferPlan(
1479
1496
  volume,
1480
1497
  source,
1481
1498
  dest,
@@ -1488,10 +1505,113 @@ class InstrumentContext(publisher.CommandPublisher):
1488
1505
  self._execute_transfer(plan)
1489
1506
  return self
1490
1507
 
1491
- def _execute_transfer(self, plan: transfers.TransferPlan) -> None:
1508
+ def _execute_transfer(self, plan: v1_transfer.TransferPlan) -> None:
1492
1509
  for cmd in plan:
1493
1510
  getattr(self, cmd["method"])(*cmd["args"], **cmd["kwargs"])
1494
1511
 
1512
+ def transfer_liquid(
1513
+ self,
1514
+ liquid_class: LiquidClass,
1515
+ volume: float,
1516
+ source: Union[
1517
+ labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1518
+ ],
1519
+ dest: Union[
1520
+ labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1521
+ ],
1522
+ new_tip: TransferTipPolicyV2Type = "once",
1523
+ tip_drop_location: Optional[
1524
+ Union[types.Location, labware.Well, TrashBin, WasteChute]
1525
+ ] = None, # Maybe call this 'tip_drop_location' which is similar to PD
1526
+ ) -> InstrumentContext:
1527
+ """Transfer liquid from source to dest using the specified liquid class properties.
1528
+
1529
+ TODO: Add args description.
1530
+ """
1531
+ if not feature_flags.allow_liquid_classes(
1532
+ robot_type=RobotTypeEnum.robot_literal_to_enum(
1533
+ self._protocol_core.robot_type
1534
+ )
1535
+ ):
1536
+ raise NotImplementedError("This method is not implemented.")
1537
+
1538
+ flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(
1539
+ source
1540
+ )
1541
+ flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest)
1542
+ for well in flat_sources_list + flat_dests_list:
1543
+ instrument.validate_takes_liquid(
1544
+ location=well.top(),
1545
+ reject_module=True,
1546
+ reject_adapter=True,
1547
+ )
1548
+ if len(flat_sources_list) != len(flat_dests_list):
1549
+ raise ValueError(
1550
+ "Sources and destinations should be of the same length in order to perform a transfer."
1551
+ " To transfer liquid from one source to many destinations, use 'distribute_liquid',"
1552
+ " to transfer liquid onto one destinations from many sources, use 'consolidate_liquid'."
1553
+ )
1554
+
1555
+ valid_new_tip = validation.ensure_new_tip_policy(new_tip)
1556
+ if valid_new_tip == TransferTipPolicyV2.NEVER:
1557
+ if self._last_tip_picked_up_from is None:
1558
+ raise RuntimeError(
1559
+ "Pipette has no tip attached to perform transfer."
1560
+ " Either do a pick_up_tip beforehand or specify a new_tip parameter"
1561
+ " of 'once' or 'always'."
1562
+ )
1563
+ else:
1564
+ tiprack = self._last_tip_picked_up_from.parent
1565
+ else:
1566
+ tiprack, well = labware.next_available_tip(
1567
+ starting_tip=self.starting_tip,
1568
+ tip_racks=self.tip_racks,
1569
+ channels=self.active_channels,
1570
+ nozzle_map=self._core.get_nozzle_map(),
1571
+ )
1572
+ if self.current_volume != 0:
1573
+ raise RuntimeError(
1574
+ "A transfer on a liquid class cannot start with liquid already in the tip."
1575
+ " Ensure that all previously aspirated liquid is dispensed before starting"
1576
+ " a new transfer."
1577
+ )
1578
+
1579
+ _trash_location: Union[types.Location, labware.Well, TrashBin, WasteChute]
1580
+ if tip_drop_location is None:
1581
+ saved_trash = self.trash_container
1582
+ if isinstance(saved_trash, labware.Labware):
1583
+ _trash_location = saved_trash.wells()[0]
1584
+ else:
1585
+ _trash_location = saved_trash
1586
+ else:
1587
+ _trash_location = tip_drop_location
1588
+
1589
+ checked_trash_location = (
1590
+ validation.ensure_valid_tip_drop_location_for_transfer_v2(
1591
+ tip_drop_location=_trash_location
1592
+ )
1593
+ )
1594
+ liquid_class_id = self._core.load_liquid_class(
1595
+ liquid_class=liquid_class,
1596
+ pipette_load_name=self.name,
1597
+ tiprack_uri=tiprack.uri,
1598
+ )
1599
+
1600
+ self._core.transfer_liquid(
1601
+ liquid_class_id=liquid_class_id,
1602
+ volume=volume,
1603
+ source=[well._core for well in flat_sources_list],
1604
+ dest=[well._core for well in flat_dests_list],
1605
+ new_tip=valid_new_tip,
1606
+ trash_location=(
1607
+ checked_trash_location._core
1608
+ if isinstance(checked_trash_location, labware.Well)
1609
+ else checked_trash_location
1610
+ ),
1611
+ )
1612
+
1613
+ return self
1614
+
1495
1615
  @requires_version(2, 0)
1496
1616
  def delay(self, *args: Any, **kwargs: Any) -> None:
1497
1617
  """
@@ -1592,6 +1712,147 @@ class InstrumentContext(publisher.CommandPublisher):
1592
1712
 
1593
1713
  return self
1594
1714
 
1715
+ @requires_version(2, 22)
1716
+ def resin_tip_seal(
1717
+ self,
1718
+ location: Union[labware.Well, labware.Labware],
1719
+ ) -> InstrumentContext:
1720
+ """Seal resin tips onto the pipette.
1721
+
1722
+ The location provided should contain resin tips. Sealing the
1723
+ tip will perform a `pick up` action but there will be no tip tracking
1724
+ associated with the pipette.
1725
+
1726
+ :param location: A location containing resin tips, must be a Labware or a Well.
1727
+
1728
+ :type location: :py:class:`~.types.Location`
1729
+ """
1730
+ if isinstance(location, labware.Labware):
1731
+ well = location.wells()[0]
1732
+ else:
1733
+ well = location
1734
+
1735
+ with publisher.publish_context(
1736
+ broker=self.broker,
1737
+ command=cmds.seal(
1738
+ instrument=self,
1739
+ location=well,
1740
+ ),
1741
+ ):
1742
+ self._core.resin_tip_seal(
1743
+ location=well.top(), well_core=well._core, in_place=False
1744
+ )
1745
+ return self
1746
+
1747
+ @requires_version(2, 22)
1748
+ def resin_tip_unseal(
1749
+ self,
1750
+ location: Union[labware.Well, labware.Labware],
1751
+ ) -> InstrumentContext:
1752
+ """Release resin tips from the pipette.
1753
+
1754
+ The location provided should be a valid location to drop resin tips.
1755
+
1756
+ :param location: A location containing that can accept tips.
1757
+
1758
+ :type location: :py:class:`~.types.Location`
1759
+
1760
+ :param home_after:
1761
+ Whether to home the pipette after dropping the tip. If not specified
1762
+ defaults to ``True`` on a Flex. The plunger will not home on an unseal.
1763
+
1764
+ When ``False``, the pipette does not home its plunger. This can save a few
1765
+ seconds, but is not recommended. Homing helps the robot track the pipette's
1766
+ position.
1767
+
1768
+ """
1769
+ if isinstance(location, labware.Labware):
1770
+ well = location.wells()[0]
1771
+ else:
1772
+ well = location
1773
+
1774
+ with publisher.publish_context(
1775
+ broker=self.broker,
1776
+ command=cmds.unseal(
1777
+ instrument=self,
1778
+ location=well,
1779
+ ),
1780
+ ):
1781
+ self._core.resin_tip_unseal(location=well.top(), well_core=well._core)
1782
+
1783
+ return self
1784
+
1785
+ @requires_version(2, 22)
1786
+ def resin_tip_dispense(
1787
+ self,
1788
+ location: types.Location,
1789
+ volume: Optional[float] = None,
1790
+ rate: Optional[float] = None,
1791
+ ) -> InstrumentContext:
1792
+ """Dispense a volume from resin tips into a labware.
1793
+
1794
+ The location provided should contain resin tips labware as well as a
1795
+ receptical for dispensed liquid. Dispensing from tip will perform a
1796
+ `dispense` action of the specified volume at a desired flow rate.
1797
+
1798
+ :param location: A location containing resin tips.
1799
+ :type location: :py:class:`~.types.Location`
1800
+
1801
+ :param volume: Will default to maximum, recommended to use the default.
1802
+ The volume, in µL, that the pipette will prepare to handle.
1803
+ :type volume: float
1804
+
1805
+ :param rate: Will default to 10.0, recommended to use the default. How quickly
1806
+ a pipette dispenses liquid. The speed in µL/s is calculated as
1807
+ ``rate`` multiplied by :py:attr:`flow_rate.dispense<flow_rate>`.
1808
+ :type rate: float
1809
+
1810
+ """
1811
+ well: Optional[labware.Well] = None
1812
+ last_location = self._get_last_location_by_api_version()
1813
+
1814
+ try:
1815
+ target = validation.validate_location(
1816
+ location=location, last_location=last_location
1817
+ )
1818
+ except validation.NoLocationError as e:
1819
+ raise RuntimeError(
1820
+ "If dispense is called without an explicit location, another"
1821
+ " method that moves to a location (such as move_to or "
1822
+ "aspirate) must previously have been called so the robot "
1823
+ "knows where it is."
1824
+ ) from e
1825
+
1826
+ if isinstance(target, validation.WellTarget):
1827
+ well = target.well
1828
+ if target.location:
1829
+ move_to_location = target.location
1830
+ elif well.parent._core.is_fixed_trash():
1831
+ move_to_location = target.well.top()
1832
+ else:
1833
+ move_to_location = target.well.bottom(
1834
+ z=self._well_bottom_clearances.dispense
1835
+ )
1836
+ else:
1837
+ raise RuntimeError(
1838
+ "A well must be specified when using `resin_tip_dispense`."
1839
+ )
1840
+
1841
+ with publisher.publish_context(
1842
+ broker=self.broker,
1843
+ command=cmds.resin_tip_dispense(
1844
+ instrument=self,
1845
+ flow_rate=rate,
1846
+ ),
1847
+ ):
1848
+ self._core.resin_tip_dispense(
1849
+ move_to_location,
1850
+ well_core=well._core,
1851
+ volume=volume,
1852
+ flow_rate=rate,
1853
+ )
1854
+ return self
1855
+
1595
1856
  @requires_version(2, 18)
1596
1857
  def _retract(
1597
1858
  self,
@@ -1694,6 +1955,8 @@ class InstrumentContext(publisher.CommandPublisher):
1694
1955
  @liquid_presence_detection.setter
1695
1956
  @requires_version(2, 20)
1696
1957
  def liquid_presence_detection(self, enable: bool) -> None:
1958
+ if enable:
1959
+ self._raise_if_pressure_not_supported_by_pipette()
1697
1960
  self._core.set_liquid_presence_detection(enable)
1698
1961
 
1699
1962
  @property
@@ -1870,19 +2133,6 @@ class InstrumentContext(publisher.CommandPublisher):
1870
2133
  else:
1871
2134
  return self._protocol_core.get_last_location()
1872
2135
 
1873
- def _96_tip_config_valid(self) -> bool:
1874
- n_map = self._core.get_nozzle_map()
1875
- channels = self._core.get_active_channels()
1876
- if channels == 96:
1877
- if (
1878
- n_map.back_left != n_map.full_instrument_back_left
1879
- and n_map.front_right != n_map.full_instrument_front_right
1880
- ):
1881
- raise TipNotAttachedError(
1882
- "Either the front right or the back left nozzle must have a tip attached to do LLD."
1883
- )
1884
- return True
1885
-
1886
2136
  def __repr__(self) -> str:
1887
2137
  return "<{}: {} in {}>".format(
1888
2138
  self.__class__.__name__,
@@ -2143,8 +2393,8 @@ class InstrumentContext(publisher.CommandPublisher):
2143
2393
  .. note::
2144
2394
  The pressure sensors for the Flex 8-channel pipette are on channels 1 and 8 (positions A1 and H1). For the Flex 96-channel pipette, the pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid.
2145
2395
  """
2396
+ self._raise_if_pressure_not_supported_by_pipette()
2146
2397
  loc = well.top()
2147
- self._96_tip_config_valid()
2148
2398
  return self._core.detect_liquid_presence(well._core, loc)
2149
2399
 
2150
2400
  @requires_version(2, 20)
@@ -2156,8 +2406,8 @@ class InstrumentContext(publisher.CommandPublisher):
2156
2406
  .. note::
2157
2407
  The pressure sensors for the Flex 8-channel pipette are on channels 1 and 8 (positions A1 and H1). For the Flex 96-channel pipette, the pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid.
2158
2408
  """
2409
+ self._raise_if_pressure_not_supported_by_pipette()
2159
2410
  loc = well.top()
2160
- self._96_tip_config_valid()
2161
2411
  self._core.liquid_probe_with_recovery(well._core, loc)
2162
2412
 
2163
2413
  @requires_version(2, 20)
@@ -2170,9 +2420,8 @@ class InstrumentContext(publisher.CommandPublisher):
2170
2420
 
2171
2421
  This is intended for Opentrons internal use only and is not a guaranteed API.
2172
2422
  """
2173
-
2423
+ self._raise_if_pressure_not_supported_by_pipette()
2174
2424
  loc = well.top()
2175
- self._96_tip_config_valid()
2176
2425
  height = self._core.liquid_probe_without_recovery(well._core, loc)
2177
2426
  return height
2178
2427
 
@@ -2192,6 +2441,12 @@ class InstrumentContext(publisher.CommandPublisher):
2192
2441
  )
2193
2442
  # SINGLE, QUADRANT and ALL are supported by all pipettes
2194
2443
 
2444
+ def _raise_if_pressure_not_supported_by_pipette(self) -> None:
2445
+ if not self._core._pressure_supported_by_pipette():
2446
+ raise UnsupportedHardwareCommand(
2447
+ "Pressure sensor not available for this pipette"
2448
+ )
2449
+
2195
2450
  def _handle_aspirate_target(
2196
2451
  self, target: validation.ValidTarget
2197
2452
  ) -> tuple[types.Location, Optional[labware.Well], Optional[bool]]: