opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.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.

Potentially problematic release.


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

Files changed (229) 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 +28 -20
  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 +60 -23
  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 +18 -0
  47. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/protocol_api/__init__.py +20 -1
  51. opentrons/protocol_api/_liquid.py +24 -49
  52. opentrons/protocol_api/_liquid_properties.py +754 -0
  53. opentrons/protocol_api/_types.py +24 -0
  54. opentrons/protocol_api/core/common.py +2 -0
  55. opentrons/protocol_api/core/engine/instrument.py +82 -10
  56. opentrons/protocol_api/core/engine/labware.py +29 -7
  57. opentrons/protocol_api/core/engine/protocol.py +130 -5
  58. opentrons/protocol_api/core/engine/robot.py +139 -0
  59. opentrons/protocol_api/core/engine/well.py +4 -1
  60. opentrons/protocol_api/core/instrument.py +46 -4
  61. opentrons/protocol_api/core/labware.py +13 -4
  62. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
  63. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  65. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
  67. opentrons/protocol_api/core/protocol.py +34 -1
  68. opentrons/protocol_api/core/robot.py +51 -0
  69. opentrons/protocol_api/instrument_context.py +158 -44
  70. opentrons/protocol_api/labware.py +231 -7
  71. opentrons/protocol_api/module_contexts.py +21 -17
  72. opentrons/protocol_api/protocol_context.py +125 -4
  73. opentrons/protocol_api/robot_context.py +204 -32
  74. opentrons/protocol_api/validation.py +262 -3
  75. opentrons/protocol_engine/__init__.py +4 -0
  76. opentrons/protocol_engine/actions/actions.py +2 -3
  77. opentrons/protocol_engine/clients/sync_client.py +18 -0
  78. opentrons/protocol_engine/commands/__init__.py +81 -0
  79. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  80. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  81. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  82. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  83. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  84. opentrons/protocol_engine/commands/aspirate.py +103 -53
  85. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  86. opentrons/protocol_engine/commands/blow_out.py +44 -39
  87. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  88. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  89. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  90. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  91. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  92. opentrons/protocol_engine/commands/command.py +73 -66
  93. opentrons/protocol_engine/commands/command_unions.py +101 -1
  94. opentrons/protocol_engine/commands/comment.py +1 -1
  95. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  96. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  97. opentrons/protocol_engine/commands/custom.py +6 -12
  98. opentrons/protocol_engine/commands/dispense.py +82 -48
  99. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  100. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  101. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  102. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  103. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  104. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  112. opentrons/protocol_engine/commands/home.py +13 -4
  113. opentrons/protocol_engine/commands/liquid_probe.py +67 -24
  114. opentrons/protocol_engine/commands/load_labware.py +29 -7
  115. opentrons/protocol_engine/commands/load_lid.py +146 -0
  116. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  117. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  118. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  119. opentrons/protocol_engine/commands/load_module.py +31 -10
  120. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  121. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  122. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  123. opentrons/protocol_engine/commands/move_labware.py +19 -6
  124. opentrons/protocol_engine/commands/move_relative.py +35 -25
  125. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  126. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  127. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  128. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  129. opentrons/protocol_engine/commands/movement_common.py +338 -0
  130. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  131. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  132. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  133. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  134. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  135. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  136. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  137. opentrons/protocol_engine/commands/robot/common.py +18 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  139. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  140. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  141. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  142. opentrons/protocol_engine/commands/save_position.py +14 -5
  143. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  144. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  147. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  148. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  152. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  153. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  154. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  155. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  158. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  159. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  160. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  161. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  162. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  163. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  164. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  165. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  166. opentrons/protocol_engine/errors/__init__.py +8 -0
  167. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  168. opentrons/protocol_engine/errors/exceptions.py +50 -0
  169. opentrons/protocol_engine/execution/command_executor.py +1 -1
  170. opentrons/protocol_engine/execution/equipment.py +73 -5
  171. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  172. opentrons/protocol_engine/execution/movement.py +27 -0
  173. opentrons/protocol_engine/execution/pipetting.py +5 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  175. opentrons/protocol_engine/notes/notes.py +1 -1
  176. opentrons/protocol_engine/protocol_engine.py +7 -6
  177. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  178. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  179. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  180. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  181. opentrons/protocol_engine/slot_standardization.py +9 -9
  182. opentrons/protocol_engine/state/_move_types.py +9 -5
  183. opentrons/protocol_engine/state/_well_math.py +193 -0
  184. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  185. opentrons/protocol_engine/state/command_history.py +12 -0
  186. opentrons/protocol_engine/state/commands.py +17 -13
  187. opentrons/protocol_engine/state/files.py +10 -12
  188. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  190. opentrons/protocol_engine/state/geometry.py +47 -1
  191. opentrons/protocol_engine/state/labware.py +79 -25
  192. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  193. opentrons/protocol_engine/state/liquids.py +16 -4
  194. opentrons/protocol_engine/state/modules.py +52 -70
  195. opentrons/protocol_engine/state/motion.py +6 -1
  196. opentrons/protocol_engine/state/pipettes.py +144 -58
  197. opentrons/protocol_engine/state/state.py +21 -2
  198. opentrons/protocol_engine/state/state_summary.py +4 -2
  199. opentrons/protocol_engine/state/tips.py +11 -44
  200. opentrons/protocol_engine/state/update_types.py +343 -48
  201. opentrons/protocol_engine/state/wells.py +19 -11
  202. opentrons/protocol_engine/types.py +176 -28
  203. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  204. opentrons/protocol_reader/file_format_validator.py +5 -5
  205. opentrons/protocol_runner/json_file_reader.py +9 -3
  206. opentrons/protocol_runner/json_translator.py +51 -25
  207. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  208. opentrons/protocol_runner/protocol_runner.py +35 -4
  209. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  210. opentrons/protocol_runner/run_orchestrator.py +13 -3
  211. opentrons/protocols/advanced_control/common.py +38 -0
  212. opentrons/protocols/advanced_control/mix.py +1 -1
  213. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  214. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  215. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  216. opentrons/protocols/api_support/definitions.py +1 -1
  217. opentrons/protocols/api_support/instrument.py +1 -1
  218. opentrons/protocols/api_support/util.py +10 -0
  219. opentrons/protocols/labware.py +39 -6
  220. opentrons/protocols/models/json_protocol.py +5 -9
  221. opentrons/simulate.py +3 -1
  222. opentrons/types.py +162 -2
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  225. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
  226. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  227. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  228. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -17,3 +17,27 @@ A special location value, indicating that a labware is not currently on the robo
17
17
 
18
18
  See :ref:`off-deck-location` for details on using ``OFF_DECK`` with :py:obj:`ProtocolContext.move_labware()`.
19
19
  """
20
+
21
+
22
+ class PlungerPositionTypes(enum.Enum):
23
+ PLUNGER_TOP = "top"
24
+ PLUNGER_BOTTOM = "bottom"
25
+ PLUNGER_BLOWOUT = "blow_out"
26
+ PLUNGER_DROPTIP = "drop_tip"
27
+
28
+
29
+ PLUNGER_TOP: Final = PlungerPositionTypes.PLUNGER_TOP
30
+ PLUNGER_BOTTOM: Final = PlungerPositionTypes.PLUNGER_BOTTOM
31
+ PLUNGER_BLOWOUT: Final = PlungerPositionTypes.PLUNGER_BLOWOUT
32
+ PLUNGER_DROPTIP: Final = PlungerPositionTypes.PLUNGER_DROPTIP
33
+
34
+
35
+ class PipetteActionTypes(enum.Enum):
36
+ ASPIRATE_ACTION = "aspirate"
37
+ DISPENSE_ACTION = "dispense"
38
+ BLOWOUT_ACTION = "blowout"
39
+
40
+
41
+ ASPIRATE_ACTION: Final = PipetteActionTypes.ASPIRATE_ACTION
42
+ DISPENSE_ACTION: Final = PipetteActionTypes.DISPENSE_ACTION
43
+ BLOWOUT_ACTION: Final = PipetteActionTypes.BLOWOUT_ACTION
@@ -14,6 +14,7 @@ from .module import (
14
14
  )
15
15
  from .protocol import AbstractProtocol
16
16
  from .well import AbstractWellCore
17
+ from .robot import AbstractRobot
17
18
 
18
19
 
19
20
  WellCore = AbstractWellCore
@@ -26,4 +27,5 @@ ThermocyclerCore = AbstractThermocyclerCore
26
27
  HeaterShakerCore = AbstractHeaterShakerCore
27
28
  MagneticBlockCore = AbstractMagneticBlockCore
28
29
  AbsorbanceReaderCore = AbstractAbsorbanceReaderCore
30
+ RobotCore = AbstractRobot
29
31
  ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore]
@@ -1,13 +1,14 @@
1
1
  """ProtocolEngine-based InstrumentContext core implementation."""
2
- from __future__ import annotations
3
2
 
4
- from typing import Optional, TYPE_CHECKING, cast, Union
5
- from opentrons.protocols.api_support.types import APIVersion
3
+ from __future__ import annotations
6
4
 
7
- from opentrons.types import Location, Mount
5
+ from typing import Optional, TYPE_CHECKING, cast, Union, List
6
+ from opentrons.types import Location, Mount, NozzleConfigurationType, NozzleMapInterface
8
7
  from opentrons.hardware_control import SyncHardwareAPI
9
8
  from opentrons.hardware_control.dev_types import PipetteDict
10
9
  from opentrons.protocols.api_support.util import FlowRates, find_value_for_api_version
10
+ from opentrons.protocols.api_support.types import APIVersion
11
+ from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2
11
12
  from opentrons.protocol_engine import commands as cmd
12
13
  from opentrons.protocol_engine import (
13
14
  DeckPoint,
@@ -26,24 +27,25 @@ from opentrons.protocol_engine.types import (
26
27
  PRIMARY_NOZZLE_LITERAL,
27
28
  NozzleLayoutConfigurationType,
28
29
  AddressableOffsetVector,
30
+ LiquidClassRecord,
29
31
  )
30
32
  from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
31
33
  from opentrons.protocol_engine.clients import SyncClient as EngineClient
32
34
  from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
33
35
  from opentrons_shared_data.pipette.types import PipetteNameType
36
+ from opentrons_shared_data.errors.exceptions import (
37
+ UnsupportedHardwareCommand,
38
+ )
34
39
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
35
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
36
- from opentrons.hardware_control.nozzle_manager import NozzleMap
37
40
  from . import overlap_versions, pipette_movement_conflict
38
41
 
39
- from ..instrument import AbstractInstrument
40
42
  from .well import WellCore
41
-
43
+ from ..instrument import AbstractInstrument
42
44
  from ...disposal_locations import TrashBin, WasteChute
43
45
 
44
46
  if TYPE_CHECKING:
45
47
  from .protocol import ProtocolCore
46
-
48
+ from opentrons.protocol_api._liquid import LiquidClass
47
49
 
48
50
  _DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
49
51
 
@@ -86,6 +88,13 @@ class InstrumentCore(AbstractInstrument[WellCore]):
86
88
  self._liquid_presence_detection = bool(
87
89
  self._engine_client.state.pipettes.get_liquid_presence_detection(pipette_id)
88
90
  )
91
+ if (
92
+ self._liquid_presence_detection
93
+ and not self._pressure_supported_by_pipette()
94
+ ):
95
+ raise UnsupportedHardwareCommand(
96
+ "Pressure sensor not available for this pipette"
97
+ )
89
98
 
90
99
  @property
91
100
  def pipette_id(self) -> str:
@@ -104,6 +113,19 @@ class InstrumentCore(AbstractInstrument[WellCore]):
104
113
  pipette_id=self._pipette_id, speed=speed
105
114
  )
106
115
 
116
+ def air_gap_in_place(self, volume: float, flow_rate: float) -> None:
117
+ """Aspirate a given volume of air from the current location of the pipette.
118
+
119
+ Args:
120
+ volume: The volume of air to aspirate, in microliters.
121
+ folw_rate: The flow rate of air into the pipette, in microliters/s
122
+ """
123
+ self._engine_client.execute_command(
124
+ cmd.AirGapInPlaceParams(
125
+ pipetteId=self._pipette_id, volume=volume, flowRate=flow_rate
126
+ )
127
+ )
128
+
107
129
  def aspirate(
108
130
  self,
109
131
  location: Location,
@@ -724,7 +746,7 @@ class InstrumentCore(AbstractInstrument[WellCore]):
724
746
  self._pipette_id
725
747
  )
726
748
 
727
- def get_nozzle_map(self) -> NozzleMap:
749
+ def get_nozzle_map(self) -> NozzleMapInterface:
728
750
  return self._engine_client.state.tips.get_pipette_nozzle_map(self._pipette_id)
729
751
 
730
752
  def has_tip(self) -> bool:
@@ -842,11 +864,55 @@ class InstrumentCore(AbstractInstrument[WellCore]):
842
864
  )
843
865
  )
844
866
 
867
+ def load_liquid_class(
868
+ self,
869
+ liquid_class: LiquidClass,
870
+ pipette_load_name: str,
871
+ tiprack_uri: str,
872
+ ) -> str:
873
+ """Load a liquid class into the engine and return its ID."""
874
+ transfer_props = liquid_class.get_for(
875
+ pipette=pipette_load_name, tiprack=tiprack_uri
876
+ )
877
+
878
+ liquid_class_record = LiquidClassRecord(
879
+ liquidClassName=liquid_class.name,
880
+ pipetteModel=self.get_model(), # TODO: verify this is the correct 'model' to use
881
+ tiprack=tiprack_uri,
882
+ aspirate=transfer_props.aspirate.as_shared_data_model(),
883
+ singleDispense=transfer_props.dispense.as_shared_data_model(),
884
+ multiDispense=transfer_props.multi_dispense.as_shared_data_model()
885
+ if transfer_props.multi_dispense
886
+ else None,
887
+ )
888
+ result = self._engine_client.execute_command_without_recovery(
889
+ cmd.LoadLiquidClassParams(
890
+ liquidClassRecord=liquid_class_record,
891
+ )
892
+ )
893
+ return result.liquidClassId
894
+
895
+ def transfer_liquid(
896
+ self,
897
+ liquid_class_id: str,
898
+ volume: float,
899
+ source: List[WellCore],
900
+ dest: List[WellCore],
901
+ new_tip: TransferTipPolicyV2,
902
+ trash_location: Union[WellCore, Location, TrashBin, WasteChute],
903
+ ) -> None:
904
+ """Execute transfer using liquid class properties."""
905
+
845
906
  def retract(self) -> None:
846
907
  """Retract this instrument to the top of the gantry."""
847
908
  z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id)
848
909
  self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis]))
849
910
 
911
+ def _pressure_supported_by_pipette(self) -> bool:
912
+ return self._engine_client.state.pipettes.get_pipette_supports_pressure(
913
+ self.pipette_id
914
+ )
915
+
850
916
  def detect_liquid_presence(self, well_core: WellCore, loc: Location) -> bool:
851
917
  labware_id = well_core.labware_id
852
918
  well_name = well_core.get_name()
@@ -922,3 +988,9 @@ class InstrumentCore(AbstractInstrument[WellCore]):
922
988
  self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
923
989
 
924
990
  return result.z_position
991
+
992
+ def nozzle_configuration_valid_for_lld(self) -> bool:
993
+ """Check if the nozzle configuration currently supports LLD."""
994
+ return self._engine_client.state.pipettes.get_nozzle_configuration_supports_lld(
995
+ self.pipette_id
996
+ )
@@ -1,5 +1,6 @@
1
1
  """ProtocolEngine-based Labware core implementations."""
2
- from typing import List, Optional, cast
2
+
3
+ from typing import List, Optional, cast, Dict
3
4
 
4
5
  from opentrons_shared_data.labware.types import (
5
6
  LabwareParameters as LabwareParametersDict,
@@ -19,11 +20,12 @@ from opentrons.protocol_engine.types import (
19
20
  LabwareOffsetCreate,
20
21
  LabwareOffsetVector,
21
22
  )
22
- from opentrons.types import DeckSlotName, Point, StagingSlotName
23
- from opentrons.hardware_control.nozzle_manager import NozzleMap
23
+ from opentrons.types import DeckSlotName, NozzleMapInterface, Point, StagingSlotName
24
24
 
25
25
 
26
+ from ..._liquid import Liquid
26
27
  from ..labware import AbstractLabware, LabwareLoadParams
28
+
27
29
  from .well import WellCore
28
30
 
29
31
 
@@ -90,12 +92,14 @@ class LabwareCore(AbstractLabware[WellCore]):
90
92
 
91
93
  def get_definition(self) -> LabwareDefinitionDict:
92
94
  """Get the labware's definition as a plain dictionary."""
93
- return cast(LabwareDefinitionDict, self._definition.dict(exclude_none=True))
95
+ return cast(
96
+ LabwareDefinitionDict, self._definition.model_dump(exclude_none=True)
97
+ )
94
98
 
95
99
  def get_parameters(self) -> LabwareParametersDict:
96
100
  return cast(
97
101
  LabwareParametersDict,
98
- self._definition.parameters.dict(exclude_none=True),
102
+ self._definition.parameters.model_dump(exclude_none=True),
99
103
  )
100
104
 
101
105
  def get_quirks(self) -> List[str]:
@@ -116,7 +120,7 @@ class LabwareCore(AbstractLabware[WellCore]):
116
120
  details={"kind": "labware-not-in-slot"},
117
121
  )
118
122
 
119
- request = LabwareOffsetCreate.construct(
123
+ request = LabwareOffsetCreate.model_construct(
120
124
  definitionUri=self.get_uri(),
121
125
  location=offset_location,
122
126
  vector=LabwareOffsetVector(x=delta.x, y=delta.y, z=delta.z),
@@ -162,7 +166,7 @@ class LabwareCore(AbstractLabware[WellCore]):
162
166
  self,
163
167
  num_tips: int,
164
168
  starting_tip: Optional[WellCore],
165
- nozzle_map: Optional[NozzleMap],
169
+ nozzle_map: Optional[NozzleMapInterface],
166
170
  ) -> Optional[str]:
167
171
  return self._engine_client.state.tips.get_next_tip(
168
172
  labware_id=self._labware_id,
@@ -203,3 +207,21 @@ class LabwareCore(AbstractLabware[WellCore]):
203
207
  LocationIsStagingSlotError,
204
208
  ):
205
209
  return None
210
+
211
+ def load_liquid(self, volumes: Dict[str, float], liquid: Liquid) -> None:
212
+ """Load liquid into wells of the labware."""
213
+ self._engine_client.execute_command(
214
+ cmd.LoadLiquidParams(
215
+ labwareId=self._labware_id, liquidId=liquid._id, volumeByWell=volumes
216
+ )
217
+ )
218
+
219
+ def load_empty(self, wells: List[str]) -> None:
220
+ """Mark wells of the labware as empty."""
221
+ self._engine_client.execute_command(
222
+ cmd.LoadLiquidParams(
223
+ labwareId=self._labware_id,
224
+ liquidId="EMPTY",
225
+ volumeByWell={well: 0.0 for well in wells},
226
+ )
227
+ )
@@ -6,6 +6,7 @@ from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExi
6
6
 
7
7
  from opentrons.protocol_engine import commands as cmd
8
8
  from opentrons.protocol_engine.commands import LoadModuleResult
9
+
9
10
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
10
11
  from opentrons_shared_data.labware.labware_definition import LabwareDefinition
11
12
  from opentrons_shared_data.labware.types import LabwareDefinition as LabwareDefDict
@@ -63,6 +64,7 @@ from ..protocol import AbstractProtocol
63
64
  from ..labware import LabwareLoadParams
64
65
  from .labware import LabwareCore
65
66
  from .instrument import InstrumentCore
67
+ from .robot import RobotCore
66
68
  from .module_core import (
67
69
  ModuleCore,
68
70
  TemperatureModuleCore,
@@ -82,7 +84,9 @@ if TYPE_CHECKING:
82
84
 
83
85
  class ProtocolCore(
84
86
  AbstractProtocol[
85
- InstrumentCore, LabwareCore, Union[ModuleCore, NonConnectedModuleCore]
87
+ InstrumentCore,
88
+ LabwareCore,
89
+ Union[ModuleCore, NonConnectedModuleCore],
86
90
  ]
87
91
  ):
88
92
  """Protocol API core using a ProtocolEngine.
@@ -189,7 +193,7 @@ class ProtocolCore(
189
193
  ) -> LabwareLoadParams:
190
194
  """Add a labware definition to the set of loadable definitions."""
191
195
  uri = self._engine_client.add_labware_definition(
192
- LabwareDefinition.parse_obj(definition)
196
+ LabwareDefinition.model_validate(definition)
193
197
  )
194
198
  return LabwareLoadParams.from_uri(uri)
195
199
 
@@ -229,6 +233,9 @@ class ProtocolCore(
229
233
  )
230
234
  # FIXME(jbl, 2023-08-14) validating after loading the object issue
231
235
  validation.ensure_definition_is_labware(load_result.definition)
236
+ validation.ensure_definition_is_not_lid_after_api_version(
237
+ self.api_version, load_result.definition
238
+ )
232
239
 
233
240
  # FIXME(mm, 2023-02-21):
234
241
  #
@@ -318,6 +325,52 @@ class ProtocolCore(
318
325
 
319
326
  return labware_core
320
327
 
328
+ def load_lid(
329
+ self,
330
+ load_name: str,
331
+ location: LabwareCore,
332
+ namespace: Optional[str],
333
+ version: Optional[int],
334
+ ) -> LabwareCore:
335
+ """Load an individual lid using its identifying parameters. Must be loaded on an existing Labware."""
336
+ load_location = self._convert_labware_location(location=location)
337
+ custom_labware_params = (
338
+ self._engine_client.state.labware.find_custom_labware_load_params()
339
+ )
340
+ namespace, version = load_labware_params.resolve(
341
+ load_name, namespace, version, custom_labware_params
342
+ )
343
+ load_result = self._engine_client.execute_command_without_recovery(
344
+ cmd.LoadLidParams(
345
+ loadName=load_name,
346
+ location=load_location,
347
+ namespace=namespace,
348
+ version=version,
349
+ )
350
+ )
351
+ # FIXME(chb, 2024-12-06) validating after loading the object issue
352
+ validation.ensure_definition_is_lid(load_result.definition)
353
+
354
+ deck_conflict.check(
355
+ engine_state=self._engine_client.state,
356
+ new_labware_id=load_result.labwareId,
357
+ existing_disposal_locations=self._disposal_locations,
358
+ # TODO: We can now fetch these IDs from engine too.
359
+ # See comment in self.load_labware().
360
+ #
361
+ # Wrapping .keys() in list() is just to make Decoy verification easier.
362
+ existing_labware_ids=list(self._labware_cores_by_id.keys()),
363
+ existing_module_ids=list(self._module_cores_by_id.keys()),
364
+ )
365
+
366
+ labware_core = LabwareCore(
367
+ labware_id=load_result.labwareId,
368
+ engine_client=self._engine_client,
369
+ )
370
+
371
+ self._labware_cores_by_id[labware_core.labware_id] = labware_core
372
+ return labware_core
373
+
321
374
  def move_labware(
322
375
  self,
323
376
  labware_core: LabwareCore,
@@ -422,6 +475,8 @@ class ProtocolCore(
422
475
  raise InvalidModuleLocationError(deck_slot, model.name)
423
476
 
424
477
  robot_type = self._engine_client.state.config.robot_type
478
+ # todo(mm, 2024-12-03): This might be possible to remove:
479
+ # Protocol Engine will normalize the deck slot itself.
425
480
  normalized_deck_slot = deck_slot.to_equivalent_for_robot_type(robot_type)
426
481
 
427
482
  result = self._engine_client.execute_command_without_recovery(
@@ -502,6 +557,12 @@ class ProtocolCore(
502
557
  load_module_result=load_module_result, model=model
503
558
  )
504
559
 
560
+ def load_robot(self) -> RobotCore:
561
+ """Load a robot core into the RobotContext."""
562
+ return RobotCore(
563
+ engine_client=self._engine_client, sync_hardware_api=self._sync_hardware
564
+ )
565
+
505
566
  def load_instrument(
506
567
  self,
507
568
  instrument_name: PipetteNameType,
@@ -632,6 +693,72 @@ class ProtocolCore(
632
693
  self._last_location = location
633
694
  self._last_mount = mount
634
695
 
696
+ def load_lid_stack(
697
+ self,
698
+ load_name: str,
699
+ location: Union[DeckSlotName, StagingSlotName, LabwareCore],
700
+ quantity: int,
701
+ namespace: Optional[str],
702
+ version: Optional[int],
703
+ ) -> LabwareCore:
704
+ """Load a Stack of Lids to a given location, creating a Lid Stack."""
705
+ if quantity < 1:
706
+ raise ValueError(
707
+ "When loading a lid stack quantity cannot be less than one."
708
+ )
709
+ if isinstance(location, DeckSlotName) or isinstance(location, StagingSlotName):
710
+ load_location = self._convert_labware_location(location=location)
711
+ else:
712
+ if isinstance(location, LabwareCore):
713
+ load_location = self._convert_labware_location(location=location)
714
+ else:
715
+ raise ValueError(
716
+ "Expected type of Labware Location for lid stack must be Labware, not Legacy Labware or Well."
717
+ )
718
+
719
+ custom_labware_params = (
720
+ self._engine_client.state.labware.find_custom_labware_load_params()
721
+ )
722
+ namespace, version = load_labware_params.resolve(
723
+ load_name, namespace, version, custom_labware_params
724
+ )
725
+
726
+ load_result = self._engine_client.execute_command_without_recovery(
727
+ cmd.LoadLidStackParams(
728
+ loadName=load_name,
729
+ location=load_location,
730
+ namespace=namespace,
731
+ version=version,
732
+ quantity=quantity,
733
+ )
734
+ )
735
+
736
+ # FIXME(CHB, 2024-12-04) just like load labware and load adapter we have a validating after loading the object issue
737
+ validation.ensure_definition_is_lid(load_result.definition)
738
+
739
+ deck_conflict.check(
740
+ engine_state=self._engine_client.state,
741
+ new_labware_id=load_result.stackLabwareId,
742
+ existing_disposal_locations=self._disposal_locations,
743
+ # TODO (spp, 2023-11-27): We've been using IDs from _labware_cores_by_id
744
+ # and _module_cores_by_id instead of getting the lists directly from engine
745
+ # because of the chance of engine carrying labware IDs from LPC too.
746
+ # But with https://github.com/Opentrons/opentrons/pull/13943,
747
+ # & LPC in maintenance runs, we can now rely on engine state for these IDs too.
748
+ # Wrapping .keys() in list() is just to make Decoy verification easier.
749
+ existing_labware_ids=list(self._labware_cores_by_id.keys()),
750
+ existing_module_ids=list(self._module_cores_by_id.keys()),
751
+ )
752
+
753
+ labware_core = LabwareCore(
754
+ labware_id=load_result.stackLabwareId,
755
+ engine_client=self._engine_client,
756
+ )
757
+
758
+ self._labware_cores_by_id[labware_core.labware_id] = labware_core
759
+
760
+ return labware_core
761
+
635
762
  def get_deck_definition(self) -> DeckDefinitionV5:
636
763
  """Get the geometry definition of the robot's deck."""
637
764
  return self._engine_client.state.labware.get_deck_definition()
@@ -725,9 +852,7 @@ class ProtocolCore(
725
852
  _id=liquid.id,
726
853
  name=liquid.displayName,
727
854
  description=liquid.description,
728
- display_color=(
729
- liquid.displayColor.__root__ if liquid.displayColor else None
730
- ),
855
+ display_color=(liquid.displayColor.root if liquid.displayColor else None),
731
856
  )
732
857
 
733
858
  def define_liquid_class(self, name: str) -> LiquidClass:
@@ -0,0 +1,139 @@
1
+ from typing import Optional, Dict, Union
2
+ from opentrons.hardware_control import SyncHardwareAPI
3
+
4
+ from opentrons.types import Mount, MountType, Point, AxisType, AxisMapType
5
+ from opentrons_shared_data.pipette import types as pip_types
6
+ from opentrons.protocol_api._types import PipetteActionTypes, PlungerPositionTypes
7
+ from opentrons.protocol_engine import commands as cmd
8
+ from opentrons.protocol_engine.clients import SyncClient as EngineClient
9
+ from opentrons.protocol_engine.types import DeckPoint, MotorAxis
10
+
11
+ from opentrons.protocol_api.core.robot import AbstractRobot
12
+
13
+
14
+ _AXIS_TYPE_TO_MOTOR_AXIS = {
15
+ AxisType.X: MotorAxis.X,
16
+ AxisType.Y: MotorAxis.Y,
17
+ AxisType.P_L: MotorAxis.LEFT_PLUNGER,
18
+ AxisType.P_R: MotorAxis.RIGHT_PLUNGER,
19
+ AxisType.Z_L: MotorAxis.LEFT_Z,
20
+ AxisType.Z_R: MotorAxis.RIGHT_Z,
21
+ AxisType.Z_G: MotorAxis.EXTENSION_Z,
22
+ AxisType.G: MotorAxis.EXTENSION_JAW,
23
+ AxisType.Q: MotorAxis.AXIS_96_CHANNEL_CAM,
24
+ }
25
+
26
+
27
+ class RobotCore(AbstractRobot):
28
+ """Robot API core using a ProtocolEngine.
29
+
30
+ Args:
31
+ engine_client: A client to the ProtocolEngine that is executing the protocol.
32
+ api_version: The Python Protocol API versionat which this core is operating.
33
+ sync_hardware: A SynchronousAdapter-wrapped Hardware Control API.
34
+ """
35
+
36
+ def __init__(
37
+ self, engine_client: EngineClient, sync_hardware_api: SyncHardwareAPI
38
+ ) -> None:
39
+ self._engine_client = engine_client
40
+ self._sync_hardware_api = sync_hardware_api
41
+
42
+ def _convert_to_engine_mount(self, axis_map: AxisMapType) -> Dict[MotorAxis, float]:
43
+ return {_AXIS_TYPE_TO_MOTOR_AXIS[ax]: dist for ax, dist in axis_map.items()}
44
+
45
+ def get_pipette_type_from_engine(
46
+ self, mount: Union[Mount, str]
47
+ ) -> Optional[pip_types.PipetteNameType]:
48
+ """Get the pipette attached to the given mount."""
49
+ if isinstance(mount, Mount):
50
+ engine_mount = MountType[mount.name]
51
+ else:
52
+ if mount.lower() == "right":
53
+ engine_mount = MountType.RIGHT
54
+ else:
55
+ engine_mount = MountType.LEFT
56
+ maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount)
57
+ return maybe_pipette.pipetteName if maybe_pipette else None
58
+
59
+ def get_plunger_position_from_name(
60
+ self, mount: Mount, position_name: PlungerPositionTypes
61
+ ) -> float:
62
+ engine_mount = MountType[mount.name]
63
+ maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount)
64
+ if not maybe_pipette:
65
+ return 0.0
66
+ return self._engine_client.state.pipettes.lookup_plunger_position_name(
67
+ maybe_pipette.id, position_name.value
68
+ )
69
+
70
+ def get_plunger_position_from_volume(
71
+ self, mount: Mount, volume: float, action: PipetteActionTypes, robot_type: str
72
+ ) -> float:
73
+ engine_mount = MountType[mount.name]
74
+ maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount)
75
+ if not maybe_pipette:
76
+ raise RuntimeError(
77
+ f"Cannot load plunger position as no pipette is attached to {mount}"
78
+ )
79
+ convert_volume = (
80
+ self._engine_client.state.pipettes.lookup_volume_to_mm_conversion(
81
+ maybe_pipette.id, volume, action.value
82
+ )
83
+ )
84
+ plunger_bottom = (
85
+ self._engine_client.state.pipettes.lookup_plunger_position_name(
86
+ maybe_pipette.id, "bottom"
87
+ )
88
+ )
89
+ mm = volume / convert_volume
90
+ if robot_type == "OT-2 Standard":
91
+ position = plunger_bottom + mm
92
+ else:
93
+ position = plunger_bottom - mm
94
+ return round(position, 6)
95
+
96
+ def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None:
97
+ engine_mount = MountType[mount.name]
98
+ engine_destination = DeckPoint(
99
+ x=destination.x, y=destination.y, z=destination.z
100
+ )
101
+ self._engine_client.execute_command(
102
+ cmd.robot.MoveToParams(
103
+ mount=engine_mount, destination=engine_destination, speed=speed
104
+ )
105
+ )
106
+
107
+ def move_axes_to(
108
+ self,
109
+ axis_map: AxisMapType,
110
+ critical_point: Optional[AxisMapType],
111
+ speed: Optional[float],
112
+ ) -> None:
113
+ axis_engine_map = self._convert_to_engine_mount(axis_map)
114
+ if critical_point:
115
+ critical_point_engine = self._convert_to_engine_mount(critical_point)
116
+ else:
117
+ critical_point_engine = None
118
+
119
+ self._engine_client.execute_command(
120
+ cmd.robot.MoveAxesToParams(
121
+ axis_map=axis_engine_map,
122
+ critical_point=critical_point_engine,
123
+ speed=speed,
124
+ )
125
+ )
126
+
127
+ def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None:
128
+ axis_engine_map = self._convert_to_engine_mount(axis_map)
129
+ self._engine_client.execute_command(
130
+ cmd.robot.MoveAxesRelativeParams(axis_map=axis_engine_map, speed=speed)
131
+ )
132
+
133
+ def release_grip(self) -> None:
134
+ self._engine_client.execute_command(cmd.robot.openGripperJawParams())
135
+
136
+ def close_gripper(self, force: Optional[float] = None) -> None:
137
+ self._engine_client.execute_command(
138
+ cmd.robot.closeGripperJawParams(force=force)
139
+ )
@@ -130,7 +130,10 @@ class WellCore(AbstractWellCore):
130
130
  liquid: Liquid,
131
131
  volume: float,
132
132
  ) -> None:
133
- """Load liquid into a well."""
133
+ """Load liquid into a well.
134
+
135
+ If the well is known to be empty, use ``load_empty()`` instead of calling this with a 0.0 volume.
136
+ """
134
137
  self._engine_client.execute_command(
135
138
  cmd.LoadLiquidParams(
136
139
  labwareId=self._labware_id,