opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.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/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +208 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/backends/ot3utils.py +1 -0
  20. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  21. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  22. opentrons/hardware_control/module_control.py +43 -2
  23. opentrons/hardware_control/modules/__init__.py +7 -1
  24. opentrons/hardware_control/modules/absorbance_reader.py +232 -83
  25. opentrons/hardware_control/modules/errors.py +7 -0
  26. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  27. opentrons/hardware_control/modules/magdeck.py +12 -3
  28. opentrons/hardware_control/modules/mod_abc.py +27 -2
  29. opentrons/hardware_control/modules/tempdeck.py +15 -7
  30. opentrons/hardware_control/modules/thermocycler.py +69 -3
  31. opentrons/hardware_control/modules/types.py +11 -5
  32. opentrons/hardware_control/modules/update.py +11 -5
  33. opentrons/hardware_control/modules/utils.py +3 -1
  34. opentrons/hardware_control/ot3_calibration.py +6 -6
  35. opentrons/hardware_control/ot3api.py +131 -94
  36. opentrons/hardware_control/poller.py +15 -11
  37. opentrons/hardware_control/protocols/__init__.py +1 -7
  38. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  39. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  40. opentrons/hardware_control/protocols/position_estimator.py +3 -1
  41. opentrons/hardware_control/types.py +2 -0
  42. opentrons/legacy_commands/helpers.py +8 -2
  43. opentrons/motion_planning/__init__.py +2 -0
  44. opentrons/motion_planning/waypoints.py +32 -0
  45. opentrons/protocol_api/__init__.py +2 -1
  46. opentrons/protocol_api/_liquid.py +87 -1
  47. opentrons/protocol_api/_parameter_context.py +10 -1
  48. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  49. opentrons/protocol_api/core/engine/instrument.py +29 -25
  50. opentrons/protocol_api/core/engine/labware.py +20 -4
  51. opentrons/protocol_api/core/engine/module_core.py +166 -17
  52. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
  53. opentrons/protocol_api/core/engine/protocol.py +30 -2
  54. opentrons/protocol_api/core/instrument.py +2 -0
  55. opentrons/protocol_api/core/labware.py +4 -0
  56. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  57. opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
  58. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
  59. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  60. opentrons/protocol_api/core/module.py +22 -4
  61. opentrons/protocol_api/core/protocol.py +6 -2
  62. opentrons/protocol_api/instrument_context.py +52 -20
  63. opentrons/protocol_api/labware.py +13 -1
  64. opentrons/protocol_api/module_contexts.py +115 -17
  65. opentrons/protocol_api/protocol_context.py +49 -5
  66. opentrons/protocol_api/validation.py +5 -3
  67. opentrons/protocol_engine/__init__.py +10 -9
  68. opentrons/protocol_engine/actions/__init__.py +3 -0
  69. opentrons/protocol_engine/actions/actions.py +30 -25
  70. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  71. opentrons/protocol_engine/clients/sync_client.py +1 -1
  72. opentrons/protocol_engine/clients/transports.py +1 -1
  73. opentrons/protocol_engine/commands/__init__.py +0 -4
  74. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  75. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
  76. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
  77. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
  79. opentrons/protocol_engine/commands/aspirate.py +29 -16
  80. opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
  81. opentrons/protocol_engine/commands/blow_out.py +63 -14
  82. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  83. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  84. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  85. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  86. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  87. opentrons/protocol_engine/commands/command.py +31 -18
  88. opentrons/protocol_engine/commands/command_unions.py +37 -24
  89. opentrons/protocol_engine/commands/comment.py +5 -3
  90. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  91. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  92. opentrons/protocol_engine/commands/custom.py +5 -3
  93. opentrons/protocol_engine/commands/dispense.py +42 -20
  94. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  95. opentrons/protocol_engine/commands/drop_tip.py +70 -16
  96. opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
  97. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  98. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  99. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  100. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  101. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  102. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  103. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  104. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  105. opentrons/protocol_engine/commands/home.py +11 -5
  106. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  107. opentrons/protocol_engine/commands/load_labware.py +28 -5
  108. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  109. opentrons/protocol_engine/commands/load_module.py +4 -6
  110. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  111. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  112. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  113. opentrons/protocol_engine/commands/move_labware.py +155 -23
  114. opentrons/protocol_engine/commands/move_relative.py +15 -3
  115. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  116. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  117. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  118. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  119. opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
  120. opentrons/protocol_engine/commands/pipetting_common.py +47 -16
  121. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  122. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  123. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  124. opentrons/protocol_engine/commands/save_position.py +2 -3
  125. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  126. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  127. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  128. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  129. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  130. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  131. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  132. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  133. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  135. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  136. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  137. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  138. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  139. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  140. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  141. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  142. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  143. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  144. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  145. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  146. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  147. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  148. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
  149. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  150. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  151. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  152. opentrons/protocol_engine/create_protocol_engine.py +60 -10
  153. opentrons/protocol_engine/engine_support.py +2 -1
  154. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  155. opentrons/protocol_engine/errors/__init__.py +20 -0
  156. opentrons/protocol_engine/errors/error_occurrence.py +8 -3
  157. opentrons/protocol_engine/errors/exceptions.py +127 -2
  158. opentrons/protocol_engine/execution/__init__.py +2 -0
  159. opentrons/protocol_engine/execution/command_executor.py +22 -13
  160. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  161. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  162. opentrons/protocol_engine/execution/equipment.py +2 -1
  163. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  164. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  165. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  166. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  167. opentrons/protocol_engine/execution/labware_movement.py +73 -22
  168. opentrons/protocol_engine/execution/movement.py +17 -7
  169. opentrons/protocol_engine/execution/pipetting.py +7 -4
  170. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  171. opentrons/protocol_engine/execution/run_control.py +1 -1
  172. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  173. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  175. opentrons/protocol_engine/notes/__init__.py +14 -2
  176. opentrons/protocol_engine/notes/notes.py +18 -1
  177. opentrons/protocol_engine/plugins.py +1 -1
  178. opentrons/protocol_engine/protocol_engine.py +47 -31
  179. opentrons/protocol_engine/resources/__init__.py +2 -0
  180. opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
  181. opentrons/protocol_engine/resources/file_provider.py +161 -0
  182. opentrons/protocol_engine/resources/fixture_validation.py +11 -1
  183. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  184. opentrons/protocol_engine/state/__init__.py +0 -70
  185. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  186. opentrons/protocol_engine/state/command_history.py +21 -2
  187. opentrons/protocol_engine/state/commands.py +110 -31
  188. opentrons/protocol_engine/state/files.py +59 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  190. opentrons/protocol_engine/state/geometry.py +445 -59
  191. opentrons/protocol_engine/state/labware.py +264 -84
  192. opentrons/protocol_engine/state/liquids.py +1 -1
  193. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
  194. opentrons/protocol_engine/state/modules.py +145 -90
  195. opentrons/protocol_engine/state/motion.py +33 -14
  196. opentrons/protocol_engine/state/pipettes.py +157 -317
  197. opentrons/protocol_engine/state/state.py +30 -1
  198. opentrons/protocol_engine/state/state_summary.py +3 -0
  199. opentrons/protocol_engine/state/tips.py +69 -114
  200. opentrons/protocol_engine/state/update_types.py +424 -0
  201. opentrons/protocol_engine/state/wells.py +236 -0
  202. opentrons/protocol_engine/types.py +90 -0
  203. opentrons/protocol_reader/file_format_validator.py +83 -15
  204. opentrons/protocol_runner/json_translator.py +21 -5
  205. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  206. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  207. opentrons/protocol_runner/protocol_runner.py +6 -3
  208. opentrons/protocol_runner/run_orchestrator.py +41 -6
  209. opentrons/protocols/advanced_control/mix.py +3 -5
  210. opentrons/protocols/advanced_control/transfers.py +125 -56
  211. opentrons/protocols/api_support/constants.py +1 -1
  212. opentrons/protocols/api_support/definitions.py +1 -1
  213. opentrons/protocols/api_support/labware_like.py +4 -4
  214. opentrons/protocols/api_support/tip_tracker.py +2 -2
  215. opentrons/protocols/api_support/types.py +15 -2
  216. opentrons/protocols/api_support/util.py +30 -42
  217. opentrons/protocols/duration/errors.py +1 -1
  218. opentrons/protocols/duration/estimator.py +50 -29
  219. opentrons/protocols/execution/dev_types.py +2 -2
  220. opentrons/protocols/execution/execute_json_v4.py +15 -10
  221. opentrons/protocols/execution/execute_python.py +8 -3
  222. opentrons/protocols/geometry/planning.py +12 -12
  223. opentrons/protocols/labware.py +17 -33
  224. opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
  225. opentrons/simulate.py +3 -3
  226. opentrons/types.py +30 -3
  227. opentrons/util/logging_config.py +34 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
  230. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  231. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  232. opentrons/protocol_runner/thread_async_queue.py +0 -174
  233. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  234. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  235. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
  236. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
  237. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
@@ -2,11 +2,17 @@
2
2
  from __future__ import annotations
3
3
  from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING
4
4
 
5
+ from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExist
6
+
5
7
  from opentrons.protocol_engine import commands as cmd
6
8
  from opentrons.protocol_engine.commands import LoadModuleResult
7
9
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
8
10
  from opentrons_shared_data.labware.labware_definition import LabwareDefinition
9
11
  from opentrons_shared_data.labware.types import LabwareDefinition as LabwareDefDict
12
+ from opentrons_shared_data import liquid_classes
13
+ from opentrons_shared_data.liquid_classes.liquid_class_definition import (
14
+ LiquidClassSchemaV1,
15
+ )
10
16
  from opentrons_shared_data.pipette.types import PipetteNameType
11
17
  from opentrons_shared_data.robot.types import RobotType
12
18
 
@@ -51,7 +57,7 @@ from opentrons.protocol_engine.errors import (
51
57
 
52
58
  from ... import validation
53
59
  from ..._types import OffDeckType
54
- from ..._liquid import Liquid
60
+ from ..._liquid import Liquid, LiquidClass
55
61
  from ...disposal_locations import TrashBin, WasteChute
56
62
  from ..protocol import AbstractProtocol
57
63
  from ..labware import LabwareLoadParams
@@ -103,6 +109,7 @@ class ProtocolCore(
103
109
  str, Union[ModuleCore, NonConnectedModuleCore]
104
110
  ] = {}
105
111
  self._disposal_locations: List[Union[Labware, TrashBin, WasteChute]] = []
112
+ self._defined_liquid_class_defs_by_name: Dict[str, LiquidClassSchemaV1] = {}
106
113
  self._load_fixed_trash()
107
114
 
108
115
  @property
@@ -311,7 +318,6 @@ class ProtocolCore(
311
318
 
312
319
  return labware_core
313
320
 
314
- # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237
315
321
  def move_labware(
316
322
  self,
317
323
  labware_core: LabwareCore,
@@ -323,6 +329,7 @@ class ProtocolCore(
323
329
  NonConnectedModuleCore,
324
330
  OffDeckType,
325
331
  WasteChute,
332
+ TrashBin,
326
333
  ],
327
334
  use_gripper: bool,
328
335
  pause_for_manual_move: bool,
@@ -723,6 +730,23 @@ class ProtocolCore(
723
730
  ),
724
731
  )
725
732
 
733
+ def define_liquid_class(self, name: str) -> LiquidClass:
734
+ """Define a liquid class for use in transfer functions."""
735
+ try:
736
+ # Check if we have already loaded this liquid class' definition
737
+ liquid_class_def = self._defined_liquid_class_defs_by_name[name]
738
+ except KeyError:
739
+ try:
740
+ # Fetching the liquid class data from file and parsing it
741
+ # is an expensive operation and should be avoided.
742
+ # Calling this often will degrade protocol execution performance.
743
+ liquid_class_def = liquid_classes.load_definition(name)
744
+ self._defined_liquid_class_defs_by_name[name] = liquid_class_def
745
+ except LiquidClassDefinitionDoesNotExist:
746
+ raise ValueError(f"Liquid class definition not found for '{name}'.")
747
+
748
+ return LiquidClass.create(liquid_class_def)
749
+
726
750
  def get_labware_location(
727
751
  self, labware_core: LabwareCore
728
752
  ) -> Union[str, LabwareCore, ModuleCore, NonConnectedModuleCore, OffDeckType]:
@@ -754,6 +778,7 @@ class ProtocolCore(
754
778
  NonConnectedModuleCore,
755
779
  OffDeckType,
756
780
  WasteChute,
781
+ TrashBin,
757
782
  ],
758
783
  ) -> LabwareLocation:
759
784
  if isinstance(location, LabwareCore):
@@ -770,6 +795,7 @@ class ProtocolCore(
770
795
  NonConnectedModuleCore,
771
796
  OffDeckType,
772
797
  WasteChute,
798
+ TrashBin,
773
799
  ]
774
800
  ) -> NonStackedLocation:
775
801
  if isinstance(location, (ModuleCore, NonConnectedModuleCore)):
@@ -783,3 +809,5 @@ class ProtocolCore(
783
809
  elif isinstance(location, WasteChute):
784
810
  # TODO(mm, 2023-12-06) This will need to determine the appropriate Waste Chute to return, but only move_labware uses this for now
785
811
  return AddressableAreaLocation(addressableAreaName="gripperWasteChute")
812
+ elif isinstance(location, TrashBin):
813
+ return AddressableAreaLocation(addressableAreaName=location.area_name)
@@ -33,6 +33,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
33
33
  rate: float,
34
34
  flow_rate: float,
35
35
  in_place: bool,
36
+ is_meniscus: Optional[bool] = None,
36
37
  ) -> None:
37
38
  """Aspirate a given volume of liquid from the specified location.
38
39
  Args:
@@ -55,6 +56,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
55
56
  flow_rate: float,
56
57
  in_place: bool,
57
58
  push_out: Optional[float],
59
+ is_meniscus: Optional[bool] = None,
58
60
  ) -> None:
59
61
  """Dispense a given volume of liquid into the specified location.
60
62
  Args:
@@ -97,6 +97,10 @@ class AbstractLabware(ABC, Generic[WellCoreType]):
97
97
  def is_adapter(self) -> bool:
98
98
  """Whether the labware is an adapter."""
99
99
 
100
+ @abstractmethod
101
+ def is_lid(self) -> bool:
102
+ """Whether the labware is a lid."""
103
+
100
104
  @abstractmethod
101
105
  def is_fixed_trash(self) -> bool:
102
106
  """Whether the labware is a fixed trash."""
@@ -80,6 +80,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
80
80
  rate: float,
81
81
  flow_rate: float,
82
82
  in_place: bool,
83
+ is_meniscus: Optional[bool] = None,
83
84
  ) -> None:
84
85
  """Aspirate a given volume of liquid from the specified location.
85
86
  Args:
@@ -122,6 +123,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore]):
122
123
  flow_rate: float,
123
124
  in_place: bool,
124
125
  push_out: Optional[float],
126
+ is_meniscus: Optional[bool] = None,
125
127
  ) -> None:
126
128
  """Dispense a given volume of liquid into the specified location.
127
129
  Args:
@@ -138,6 +138,11 @@ class LegacyLabwareCore(AbstractLabware[LegacyWellCore]):
138
138
  def is_adapter(self) -> bool:
139
139
  return False # Adapters were introduced in v2.15 and not supported in legacy protocols
140
140
 
141
+ def is_lid(self) -> bool:
142
+ return (
143
+ False # Lids were introduced in v2.21 and not supported in legacy protocols
144
+ )
145
+
141
146
  def is_fixed_trash(self) -> bool:
142
147
  """Whether the labware is fixed trash."""
143
148
  return "fixedTrash" in self.get_quirks()
@@ -17,7 +17,7 @@ from opentrons.protocols import labware as labware_definition
17
17
 
18
18
  from ...labware import Labware
19
19
  from ...disposal_locations import TrashBin, WasteChute
20
- from ..._liquid import Liquid
20
+ from ..._liquid import Liquid, LiquidClass
21
21
  from ..._types import OffDeckType
22
22
  from ..protocol import AbstractProtocol
23
23
  from ..labware import LabwareLoadParams
@@ -267,7 +267,6 @@ class LegacyProtocolCore(
267
267
  """Load an adapter using its identifying parameters"""
268
268
  raise APIVersionError(api_element="Loading adapter")
269
269
 
270
- # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237
271
270
  def move_labware(
272
271
  self,
273
272
  labware_core: LegacyLabwareCore,
@@ -278,6 +277,7 @@ class LegacyProtocolCore(
278
277
  legacy_module_core.LegacyModuleCore,
279
278
  OffDeckType,
280
279
  WasteChute,
280
+ TrashBin,
281
281
  ],
282
282
  use_gripper: bool,
283
283
  pause_for_manual_move: bool,
@@ -532,6 +532,10 @@ class LegacyProtocolCore(
532
532
  """Define a liquid to load into a well."""
533
533
  assert False, "define_liquid only supported on engine core"
534
534
 
535
+ def define_liquid_class(self, name: str) -> LiquidClass:
536
+ """Define a liquid class."""
537
+ assert False, "define_liquid_class is only supported on engine core"
538
+
535
539
  def get_labware_location(
536
540
  self, labware_core: LegacyLabwareCore
537
541
  ) -> Union[
@@ -91,6 +91,7 @@ class LegacyInstrumentCoreSimulator(AbstractInstrument[LegacyWellCore]):
91
91
  rate: float,
92
92
  flow_rate: float,
93
93
  in_place: bool,
94
+ is_meniscus: Optional[bool] = None,
94
95
  ) -> None:
95
96
  if self.get_current_volume() == 0:
96
97
  # Make sure we're at the top of the labware and clear of any
@@ -132,6 +133,7 @@ class LegacyInstrumentCoreSimulator(AbstractInstrument[LegacyWellCore]):
132
133
  flow_rate: float,
133
134
  in_place: bool,
134
135
  push_out: Optional[float],
136
+ is_meniscus: Optional[bool] = None,
135
137
  ) -> None:
136
138
  if isinstance(location, (TrashBin, WasteChute)):
137
139
  raise APIVersionError(
@@ -2,7 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from abc import ABC, abstractmethod
5
- from typing import List, Optional, TypeVar, ClassVar
5
+ from typing import List, Dict, Optional, TypeVar, ClassVar
6
6
 
7
7
  from opentrons.drivers.types import (
8
8
  HeaterShakerLabwareLatchStatus,
@@ -16,6 +16,7 @@ from opentrons.hardware_control.modules.types import (
16
16
  MagneticStatus,
17
17
  SpeedStatus,
18
18
  )
19
+ from opentrons.protocol_engine.types import ABSMeasureMode
19
20
  from opentrons.types import DeckSlotName
20
21
 
21
22
 
@@ -355,9 +356,26 @@ class AbstractAbsorbanceReaderCore(AbstractModuleCore):
355
356
  """Get the module's unique hardware serial number."""
356
357
 
357
358
  @abstractmethod
358
- def initialize(self, wavelength: int) -> None:
359
+ def initialize(
360
+ self,
361
+ mode: ABSMeasureMode,
362
+ wavelengths: List[int],
363
+ reference_wavelength: Optional[int] = None,
364
+ ) -> None:
359
365
  """Initialize the Absorbance Reader by taking zero reading."""
360
366
 
361
367
  @abstractmethod
362
- def initiate_read(self) -> None:
363
- """Initiate read on the Absorbance Reader."""
368
+ def read(self, filename: Optional[str] = None) -> Dict[int, Dict[str, float]]:
369
+ """Get an absorbance reading from the Absorbance Reader."""
370
+
371
+ @abstractmethod
372
+ def close_lid(self) -> None:
373
+ """Close the Absorbance Reader's lid."""
374
+
375
+ @abstractmethod
376
+ def open_lid(self) -> None:
377
+ """Open the Absorbance Reader's lid."""
378
+
379
+ @abstractmethod
380
+ def is_lid_on(self) -> bool:
381
+ """Return True if the Absorbance Reader's lid is currently closed."""
@@ -18,7 +18,7 @@ from opentrons.protocols.api_support.util import AxisMaxSpeeds
18
18
  from .instrument import InstrumentCoreType
19
19
  from .labware import LabwareCoreType, LabwareLoadParams
20
20
  from .module import ModuleCoreType
21
- from .._liquid import Liquid
21
+ from .._liquid import Liquid, LiquidClass
22
22
  from .._types import OffDeckType
23
23
  from ..disposal_locations import TrashBin, WasteChute
24
24
 
@@ -93,7 +93,6 @@ class AbstractProtocol(
93
93
  """Load an adapter using its identifying parameters"""
94
94
  ...
95
95
 
96
- # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237
97
96
  @abstractmethod
98
97
  def move_labware(
99
98
  self,
@@ -105,6 +104,7 @@ class AbstractProtocol(
105
104
  ModuleCoreType,
106
105
  OffDeckType,
107
106
  WasteChute,
107
+ TrashBin,
108
108
  ],
109
109
  use_gripper: bool,
110
110
  pause_for_manual_move: bool,
@@ -248,6 +248,10 @@ class AbstractProtocol(
248
248
  ) -> Liquid:
249
249
  """Define a liquid to load into a well."""
250
250
 
251
+ @abstractmethod
252
+ def define_liquid_class(self, name: str) -> LiquidClass:
253
+ """Define a liquid class for use in transfer functions."""
254
+
251
255
  @abstractmethod
252
256
  def get_labware_location(
253
257
  self, labware_core: LabwareCoreType
@@ -39,12 +39,7 @@ from ._nozzle_layout import NozzleLayout
39
39
  from . import labware, validation
40
40
 
41
41
 
42
- AdvancedLiquidHandling = Union[
43
- labware.Well,
44
- types.Location,
45
- Sequence[Union[labware.Well, types.Location]],
46
- Sequence[Sequence[labware.Well]],
47
- ]
42
+ AdvancedLiquidHandling = transfers.AdvancedLiquidHandling
48
43
 
49
44
  _DEFAULT_ASPIRATE_CLEARANCE = 1.0
50
45
  _DEFAULT_DISPENSE_CLEARANCE = 1.0
@@ -222,8 +217,9 @@ class InstrumentContext(publisher.CommandPublisher):
222
217
  )
223
218
  )
224
219
 
225
- well: Optional[labware.Well] = None
226
220
  move_to_location: types.Location
221
+ well: Optional[labware.Well] = None
222
+ is_meniscus: Optional[bool] = None
227
223
  last_location = self._get_last_location_by_api_version()
228
224
  try:
229
225
  target = validation.validate_location(
@@ -237,17 +233,13 @@ class InstrumentContext(publisher.CommandPublisher):
237
233
  "knows where it is."
238
234
  ) from e
239
235
 
240
- if isinstance(target, validation.WellTarget):
241
- move_to_location = target.location or target.well.bottom(
242
- z=self._well_bottom_clearances.aspirate
243
- )
244
- well = target.well
245
- if isinstance(target, validation.PointTarget):
246
- move_to_location = target.location
247
236
  if isinstance(target, (TrashBin, WasteChute)):
248
237
  raise ValueError(
249
238
  "Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands."
250
239
  )
240
+ move_to_location, well, is_meniscus = self._handle_aspirate_target(
241
+ target=target
242
+ )
251
243
  if self.api_version >= APIVersion(2, 11):
252
244
  instrument.validate_takes_liquid(
253
245
  location=move_to_location,
@@ -287,6 +279,7 @@ class InstrumentContext(publisher.CommandPublisher):
287
279
  rate=rate,
288
280
  flow_rate=flow_rate,
289
281
  in_place=target.in_place,
282
+ is_meniscus=is_meniscus,
290
283
  )
291
284
 
292
285
  return self
@@ -389,6 +382,7 @@ class InstrumentContext(publisher.CommandPublisher):
389
382
  )
390
383
  )
391
384
  well: Optional[labware.Well] = None
385
+ is_meniscus: Optional[bool] = None
392
386
  last_location = self._get_last_location_by_api_version()
393
387
 
394
388
  try:
@@ -407,6 +401,7 @@ class InstrumentContext(publisher.CommandPublisher):
407
401
  well = target.well
408
402
  if target.location:
409
403
  move_to_location = target.location
404
+ is_meniscus = target.location.is_meniscus
410
405
  elif well.parent._core.is_fixed_trash():
411
406
  move_to_location = target.well.top()
412
407
  else:
@@ -472,6 +467,7 @@ class InstrumentContext(publisher.CommandPublisher):
472
467
  flow_rate=flow_rate,
473
468
  in_place=target.in_place,
474
469
  push_out=push_out,
470
+ is_meniscus=is_meniscus,
475
471
  )
476
472
 
477
473
  return self
@@ -545,12 +541,12 @@ class InstrumentContext(publisher.CommandPublisher):
545
541
  ),
546
542
  ):
547
543
  self.aspirate(volume, location, rate)
548
- while repetitions - 1 > 0:
549
- self.dispense(volume, rate=rate, **dispense_kwargs)
550
- self.aspirate(volume, rate=rate)
551
- repetitions -= 1
552
- self.dispense(volume, rate=rate)
553
-
544
+ with AutoProbeDisable(self):
545
+ while repetitions - 1 > 0:
546
+ self.dispense(volume, rate=rate, **dispense_kwargs)
547
+ self.aspirate(volume, rate=rate)
548
+ repetitions -= 1
549
+ self.dispense(volume, rate=rate)
554
550
  return self
555
551
 
556
552
  @requires_version(2, 0)
@@ -2196,6 +2192,42 @@ class InstrumentContext(publisher.CommandPublisher):
2196
2192
  )
2197
2193
  # SINGLE, QUADRANT and ALL are supported by all pipettes
2198
2194
 
2195
+ def _handle_aspirate_target(
2196
+ self, target: validation.ValidTarget
2197
+ ) -> tuple[types.Location, Optional[labware.Well], Optional[bool]]:
2198
+ move_to_location: types.Location
2199
+ well: Optional[labware.Well] = None
2200
+ is_meniscus: Optional[bool] = None
2201
+ if isinstance(target, validation.WellTarget):
2202
+ well = target.well
2203
+ if target.location:
2204
+ move_to_location = target.location
2205
+ is_meniscus = target.location.is_meniscus
2206
+
2207
+ else:
2208
+ move_to_location = target.well.bottom(
2209
+ z=self._well_bottom_clearances.aspirate
2210
+ )
2211
+ if isinstance(target, validation.PointTarget):
2212
+ move_to_location = target.location
2213
+ return (move_to_location, well, is_meniscus)
2214
+
2215
+
2216
+ class AutoProbeDisable:
2217
+ """Use this class to temporarily disable automatic liquid presence detection."""
2218
+
2219
+ def __init__(self, instrument: InstrumentContext):
2220
+ self.instrument = instrument
2221
+
2222
+ def __enter__(self) -> None:
2223
+ if self.instrument.api_version >= APIVersion(2, 21):
2224
+ self.auto_presence = self.instrument.liquid_presence_detection
2225
+ self.instrument.liquid_presence_detection = False
2226
+
2227
+ def __exit__(self, *args: Any, **kwargs: Any) -> None:
2228
+ if self.instrument.api_version >= APIVersion(2, 21):
2229
+ self.instrument.liquid_presence_detection = self.auto_presence
2230
+
2199
2231
 
2200
2232
  def _raise_if_has_end_or_front_right_or_back_left(
2201
2233
  style: NozzleLayout,
@@ -30,7 +30,6 @@ from opentrons.hardware_control.nozzle_manager import NozzleMap
30
30
  # remove when their usage is no longer needed
31
31
  from opentrons.protocols.labware import ( # noqa: F401
32
32
  get_labware_definition as get_labware_definition,
33
- get_all_labware_definitions as get_all_labware_definitions,
34
33
  verify_definition as verify_definition,
35
34
  save_definition as save_definition,
36
35
  )
@@ -222,6 +221,19 @@ class Well:
222
221
  """
223
222
  return Location(self._core.get_center(), self)
224
223
 
224
+ @requires_version(2, 21)
225
+ def meniscus(self, z: float = 0.0) -> Location:
226
+ """
227
+ :param z: An offset on the z-axis, in mm. Positive offsets are higher and
228
+ negative offsets are lower.
229
+ :return: A :py:class:`~opentrons.types.Location` that indicates location is meniscus and that holds the ``z`` offset in its point.z field.
230
+
231
+ :meta private:
232
+ """
233
+ return Location(
234
+ point=Point(x=0, y=0, z=z), labware=self, _ot_internal_is_meniscus=True
235
+ )
236
+
225
237
  @requires_version(2, 8)
226
238
  def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
227
239
  """
@@ -1,16 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from typing import List, Optional, Union, cast
4
+ from typing import List, Dict, Optional, Union, cast
5
5
 
6
+ from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated
7
+
8
+ from opentrons.protocol_engine.types import ABSMeasureMode
6
9
  from opentrons_shared_data.labware.types import LabwareDefinition
7
10
  from opentrons_shared_data.module.types import ModuleModel, ModuleType
8
11
 
9
12
  from opentrons.legacy_broker import LegacyBroker
10
- from opentrons.hardware_control.modules import ThermocyclerStep
11
13
  from opentrons.legacy_commands import module_commands as cmds
12
14
  from opentrons.legacy_commands.publisher import CommandPublisher, publish
13
- from opentrons.protocols.api_support.types import APIVersion
15
+ from opentrons.protocols.api_support.types import APIVersion, ThermocyclerStep
14
16
  from opentrons.protocols.api_support.util import (
15
17
  APIVersionError,
16
18
  requires_version,
@@ -159,7 +161,18 @@ class ModuleContext(CommandPublisher):
159
161
  load_location = loaded_adapter._core
160
162
  else:
161
163
  load_location = self._core
164
+
162
165
  name = validation.ensure_lowercase_name(name)
166
+
167
+ # todo(mm, 2024-11-08): This check belongs in opentrons.protocol_api.core.engine.deck_conflict.
168
+ # We're currently doing it here, at the ModuleContext level, for consistency with what
169
+ # ProtocolContext.load_labware() does. (It should also be moved to the deck_conflict module.)
170
+ if isinstance(self._core, AbsorbanceReaderCore):
171
+ if self._core.is_lid_on():
172
+ raise CommandPreconditionViolated(
173
+ f"Cannot load {name} onto the Absorbance Reader Module when its lid is closed."
174
+ )
175
+
163
176
  labware_core = self._protocol_core.load_labware(
164
177
  load_name=name,
165
178
  label=label,
@@ -568,7 +581,7 @@ class ThermocyclerContext(ModuleContext):
568
581
  individual well of the loaded labware, in µL.
569
582
  If not specified, the default is 25 µL.
570
583
 
571
- .. note:
584
+ .. note::
572
585
 
573
586
  If ``hold_time_minutes`` and ``hold_time_seconds`` are not
574
587
  specified, the Thermocycler will proceed to the next command
@@ -592,7 +605,7 @@ class ThermocyclerContext(ModuleContext):
592
605
  :param temperature: A value between 37 and 110, representing the target
593
606
  temperature in °C.
594
607
 
595
- .. note:
608
+ .. note::
596
609
 
597
610
  The Thermocycler will proceed to the next command immediately after
598
611
  ``temperature`` has been reached.
@@ -622,12 +635,19 @@ class ThermocyclerContext(ModuleContext):
622
635
  individual well of the loaded labware, in µL.
623
636
  If not specified, the default is 25 µL.
624
637
 
625
- .. note:
638
+ .. note::
626
639
 
627
640
  Unlike with :py:meth:`set_block_temperature`, either or both of
628
641
  ``hold_time_minutes`` and ``hold_time_seconds`` must be defined
629
642
  and for each step.
630
643
 
644
+ .. note::
645
+
646
+ Before API Version 2.21, Thermocycler profiles run with this command
647
+ would be listed in the app as having a number of repetitions equal to
648
+ their step count. At or above API Version 2.21, the structure of the
649
+ Thermocycler cycles is preserved.
650
+
631
651
  """
632
652
  repetitions = validation.ensure_thermocycler_repetition_count(repetitions)
633
653
  validated_steps = validation.ensure_thermocycler_profile_steps(steps)
@@ -971,28 +991,106 @@ class MagneticBlockContext(ModuleContext):
971
991
 
972
992
 
973
993
  class AbsorbanceReaderContext(ModuleContext):
974
- """An object representing a connected Absorbance Reader Module.
994
+ """An object representing a connected Absorbance Plate Reader Module.
975
995
 
976
996
  It should not be instantiated directly; instead, it should be
977
997
  created through :py:meth:`.ProtocolContext.load_module`.
978
998
 
979
- .. versionadded:: 2.18
999
+ .. versionadded:: 2.21
980
1000
  """
981
1001
 
982
1002
  _core: AbsorbanceReaderCore
983
1003
 
984
1004
  @property
985
- @requires_version(2, 18)
1005
+ @requires_version(2, 21)
986
1006
  def serial_number(self) -> str:
987
1007
  """Get the module's unique hardware serial number."""
988
1008
  return self._core.get_serial_number()
989
1009
 
990
- @requires_version(2, 18)
991
- def initialize(self, wavelength: int) -> None:
992
- """Initialize the Absorbance Reader by taking zero reading."""
993
- self._core.initialize(wavelength)
1010
+ @requires_version(2, 21)
1011
+ def close_lid(self) -> None:
1012
+ """Use the Flex Gripper to close the lid of the Absorbance Plate Reader.
1013
+
1014
+ You must call this method before initializing the reader, even if the reader was
1015
+ in the closed position at the start of the protocol.
1016
+ """
1017
+ self._core.close_lid()
1018
+
1019
+ @requires_version(2, 21)
1020
+ def open_lid(self) -> None:
1021
+ """Use the Flex Gripper to open the lid of the Absorbance Plate Reader."""
1022
+ self._core.open_lid()
1023
+
1024
+ @requires_version(2, 21)
1025
+ def is_lid_on(self) -> bool:
1026
+ """Return ``True`` if the Absorbance Plate Reader's lid is currently closed."""
1027
+ return self._core.is_lid_on()
994
1028
 
995
- @requires_version(2, 18)
996
- def initiate_read(self) -> None:
997
- """Initiate read on the Absorbance Reader."""
998
- self._core.initiate_read()
1029
+ @requires_version(2, 21)
1030
+ def initialize(
1031
+ self,
1032
+ mode: ABSMeasureMode,
1033
+ wavelengths: List[int],
1034
+ reference_wavelength: Optional[int] = None,
1035
+ ) -> None:
1036
+ """Prepare the Absorbance Plate Reader to read a plate.
1037
+
1038
+ See :ref:`absorbance-initialization` for examples.
1039
+
1040
+ :param mode: Either ``"single"`` or ``"multi"``.
1041
+
1042
+ - In single measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses
1043
+ one sample wavelength and an optional reference wavelength.
1044
+ - In multiple measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses
1045
+ a list of up to six sample wavelengths.
1046
+ :param wavelengths: A list of wavelengths, in nm, to measure.
1047
+
1048
+ - In the default hardware configuration, each wavelength must be one of
1049
+ ``450`` (blue), ``562`` (green), ``600`` (orange), or ``650`` (red). In
1050
+ custom hardware configurations, the module may accept other integers
1051
+ between 350 and 1000.
1052
+ - The list must contain only one item when initializing a single measurement.
1053
+ - The list can contain one to six items when initializing a multiple measurement.
1054
+ :param reference_wavelength: An optional reference wavelength, in nm. If provided,
1055
+ :py:meth:`.AbsorbanceReaderContext.read` will read at the reference
1056
+ wavelength and then subtract the reference wavelength values from the
1057
+ measurement wavelength values. Can only be used with single measurements.
1058
+ """
1059
+ self._core.initialize(
1060
+ mode, wavelengths, reference_wavelength=reference_wavelength
1061
+ )
1062
+
1063
+ @requires_version(2, 21)
1064
+ def read(
1065
+ self, export_filename: Optional[str] = None
1066
+ ) -> Dict[int, Dict[str, float]]:
1067
+ """Read a plate on the Absorbance Plate Reader.
1068
+
1069
+ This method always returns a dictionary of measurement data. It optionally will
1070
+ save a CSV file of the results to the Flex filesystem, which you can access from
1071
+ the Recent Protocol Runs screen in the Opentrons App. These files are `only` saved
1072
+ if you specify ``export_filename``.
1073
+
1074
+ In simulation, the values for each well key in the dictionary are set to zero, and
1075
+ no files are written.
1076
+
1077
+ .. note::
1078
+
1079
+ Avoid divide-by-zero errors when simulating and using the results of this
1080
+ method later in the protocol. If you divide by any of the measurement
1081
+ values, use :py:meth:`.ProtocolContext.is_simulating` to use alternate dummy
1082
+ data or skip the division step.
1083
+
1084
+ :param export_filename: An optional file basename. If provided, this method
1085
+ will write a CSV file for each measurement in the read operation. File
1086
+ names will use the value of this parameter, the measurement wavelength
1087
+ supplied in :py:meth:`~.AbsorbanceReaderContext.initialize`, and a
1088
+ ``.csv`` extension. For example, when reading at wavelengths 450 and 562
1089
+ with ``export_filename="my_data"``, there will be two output files:
1090
+ ``my_data_450.csv`` and ``my_data_562.csv``.
1091
+
1092
+ See :ref:`absorbance-csv` for information on working with these CSV files.
1093
+
1094
+ :returns: A dictionary of wavelengths to dictionary of values ordered by well name.
1095
+ """
1096
+ return self._core.read(filename=export_filename)