opentrons 8.3.0a0__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.
Files changed (228) 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/drivers/asyncio/communication/__init__.py +2 -0
  9. opentrons/drivers/asyncio/communication/errors.py +16 -3
  10. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  11. opentrons/drivers/command_builder.py +2 -2
  12. opentrons/drivers/flex_stacker/__init__.py +9 -0
  13. opentrons/drivers/flex_stacker/abstract.py +89 -0
  14. opentrons/drivers/flex_stacker/driver.py +260 -0
  15. opentrons/drivers/flex_stacker/simulator.py +109 -0
  16. opentrons/drivers/flex_stacker/types.py +138 -0
  17. opentrons/drivers/heater_shaker/driver.py +18 -3
  18. opentrons/drivers/temp_deck/driver.py +13 -3
  19. opentrons/drivers/thermocycler/driver.py +17 -3
  20. opentrons/execute.py +3 -1
  21. opentrons/hardware_control/__init__.py +1 -2
  22. opentrons/hardware_control/api.py +28 -20
  23. opentrons/hardware_control/backends/flex_protocol.py +4 -6
  24. opentrons/hardware_control/backends/ot3controller.py +177 -59
  25. opentrons/hardware_control/backends/ot3simulator.py +10 -8
  26. opentrons/hardware_control/backends/ot3utils.py +3 -13
  27. opentrons/hardware_control/dev_types.py +2 -0
  28. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  29. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  30. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  31. opentrons/hardware_control/emulation/settings.py +3 -4
  32. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  33. opentrons/hardware_control/instruments/ot2/pipette.py +9 -21
  34. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  35. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  36. opentrons/hardware_control/instruments/ot3/pipette.py +13 -22
  37. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  38. opentrons/hardware_control/modules/mod_abc.py +2 -2
  39. opentrons/hardware_control/motion_utilities.py +68 -0
  40. opentrons/hardware_control/nozzle_manager.py +39 -41
  41. opentrons/hardware_control/ot3_calibration.py +1 -1
  42. opentrons/hardware_control/ot3api.py +34 -22
  43. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  44. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  45. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  46. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  47. opentrons/hardware_control/robot_calibration.py +1 -1
  48. opentrons/hardware_control/types.py +61 -0
  49. opentrons/protocol_api/__init__.py +20 -1
  50. opentrons/protocol_api/_liquid.py +24 -49
  51. opentrons/protocol_api/_liquid_properties.py +754 -0
  52. opentrons/protocol_api/_types.py +24 -0
  53. opentrons/protocol_api/core/common.py +2 -0
  54. opentrons/protocol_api/core/engine/instrument.py +67 -10
  55. opentrons/protocol_api/core/engine/labware.py +29 -7
  56. opentrons/protocol_api/core/engine/protocol.py +130 -5
  57. opentrons/protocol_api/core/engine/robot.py +139 -0
  58. opentrons/protocol_api/core/engine/well.py +4 -1
  59. opentrons/protocol_api/core/instrument.py +42 -4
  60. opentrons/protocol_api/core/labware.py +13 -4
  61. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -3
  62. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  63. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  64. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +34 -3
  66. opentrons/protocol_api/core/protocol.py +34 -1
  67. opentrons/protocol_api/core/robot.py +51 -0
  68. opentrons/protocol_api/instrument_context.py +145 -43
  69. opentrons/protocol_api/labware.py +231 -7
  70. opentrons/protocol_api/module_contexts.py +21 -17
  71. opentrons/protocol_api/protocol_context.py +125 -4
  72. opentrons/protocol_api/robot_context.py +204 -32
  73. opentrons/protocol_api/validation.py +261 -3
  74. opentrons/protocol_engine/__init__.py +4 -0
  75. opentrons/protocol_engine/actions/actions.py +2 -3
  76. opentrons/protocol_engine/clients/sync_client.py +18 -0
  77. opentrons/protocol_engine/commands/__init__.py +81 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  79. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  80. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  81. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  82. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  83. opentrons/protocol_engine/commands/aspirate.py +103 -53
  84. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  85. opentrons/protocol_engine/commands/blow_out.py +44 -39
  86. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  87. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  88. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  89. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  90. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  91. opentrons/protocol_engine/commands/command.py +73 -66
  92. opentrons/protocol_engine/commands/command_unions.py +101 -1
  93. opentrons/protocol_engine/commands/comment.py +1 -1
  94. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  95. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  96. opentrons/protocol_engine/commands/custom.py +6 -12
  97. opentrons/protocol_engine/commands/dispense.py +82 -48
  98. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  99. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  100. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  101. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  102. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  103. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  104. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  111. opentrons/protocol_engine/commands/home.py +13 -4
  112. opentrons/protocol_engine/commands/liquid_probe.py +60 -25
  113. opentrons/protocol_engine/commands/load_labware.py +29 -7
  114. opentrons/protocol_engine/commands/load_lid.py +146 -0
  115. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  116. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  117. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  118. opentrons/protocol_engine/commands/load_module.py +31 -10
  119. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  120. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  121. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  122. opentrons/protocol_engine/commands/move_labware.py +19 -6
  123. opentrons/protocol_engine/commands/move_relative.py +35 -25
  124. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  125. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  126. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  127. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  128. opentrons/protocol_engine/commands/movement_common.py +338 -0
  129. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  130. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  131. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  132. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  133. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  134. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  135. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  136. opentrons/protocol_engine/commands/robot/common.py +18 -0
  137. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  139. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  140. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  141. opentrons/protocol_engine/commands/save_position.py +14 -5
  142. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  143. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  144. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  147. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  148. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  152. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  153. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  154. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  158. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  159. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  160. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  161. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  162. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  163. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  164. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  165. opentrons/protocol_engine/errors/__init__.py +8 -0
  166. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  167. opentrons/protocol_engine/errors/exceptions.py +50 -0
  168. opentrons/protocol_engine/execution/command_executor.py +1 -1
  169. opentrons/protocol_engine/execution/equipment.py +73 -5
  170. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  171. opentrons/protocol_engine/execution/movement.py +27 -0
  172. opentrons/protocol_engine/execution/pipetting.py +5 -1
  173. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  174. opentrons/protocol_engine/notes/notes.py +1 -1
  175. opentrons/protocol_engine/protocol_engine.py +7 -6
  176. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  177. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  178. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  179. opentrons/protocol_engine/resources/pipette_data_provider.py +12 -0
  180. opentrons/protocol_engine/slot_standardization.py +9 -9
  181. opentrons/protocol_engine/state/_move_types.py +9 -5
  182. opentrons/protocol_engine/state/_well_math.py +193 -0
  183. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  184. opentrons/protocol_engine/state/command_history.py +12 -0
  185. opentrons/protocol_engine/state/commands.py +17 -13
  186. opentrons/protocol_engine/state/files.py +10 -12
  187. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  188. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  189. opentrons/protocol_engine/state/geometry.py +47 -1
  190. opentrons/protocol_engine/state/labware.py +79 -25
  191. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  192. opentrons/protocol_engine/state/liquids.py +16 -4
  193. opentrons/protocol_engine/state/modules.py +52 -70
  194. opentrons/protocol_engine/state/motion.py +6 -1
  195. opentrons/protocol_engine/state/pipettes.py +135 -58
  196. opentrons/protocol_engine/state/state.py +21 -2
  197. opentrons/protocol_engine/state/state_summary.py +4 -2
  198. opentrons/protocol_engine/state/tips.py +11 -44
  199. opentrons/protocol_engine/state/update_types.py +343 -48
  200. opentrons/protocol_engine/state/wells.py +19 -11
  201. opentrons/protocol_engine/types.py +176 -28
  202. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  203. opentrons/protocol_reader/file_format_validator.py +5 -5
  204. opentrons/protocol_runner/json_file_reader.py +9 -3
  205. opentrons/protocol_runner/json_translator.py +51 -25
  206. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  207. opentrons/protocol_runner/protocol_runner.py +35 -4
  208. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  209. opentrons/protocol_runner/run_orchestrator.py +13 -3
  210. opentrons/protocols/advanced_control/common.py +38 -0
  211. opentrons/protocols/advanced_control/mix.py +1 -1
  212. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  213. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  214. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  215. opentrons/protocols/api_support/definitions.py +1 -1
  216. opentrons/protocols/api_support/instrument.py +1 -1
  217. opentrons/protocols/api_support/util.py +10 -0
  218. opentrons/protocols/labware.py +39 -6
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/logging_config.py +1 -1
  223. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +228 -201
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  228. {opentrons-8.3.0a0.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,6 +27,7 @@ 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
@@ -35,18 +37,15 @@ from opentrons_shared_data.errors.exceptions import (
35
37
  UnsupportedHardwareCommand,
36
38
  )
37
39
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
38
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
39
- from opentrons.hardware_control.nozzle_manager import NozzleMap
40
40
  from . import overlap_versions, pipette_movement_conflict
41
41
 
42
- from ..instrument import AbstractInstrument
43
42
  from .well import WellCore
44
-
43
+ from ..instrument import AbstractInstrument
45
44
  from ...disposal_locations import TrashBin, WasteChute
46
45
 
47
46
  if TYPE_CHECKING:
48
47
  from .protocol import ProtocolCore
49
-
48
+ from opentrons.protocol_api._liquid import LiquidClass
50
49
 
51
50
  _DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
52
51
 
@@ -114,6 +113,19 @@ class InstrumentCore(AbstractInstrument[WellCore]):
114
113
  pipette_id=self._pipette_id, speed=speed
115
114
  )
116
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
+
117
129
  def aspirate(
118
130
  self,
119
131
  location: Location,
@@ -734,7 +746,7 @@ class InstrumentCore(AbstractInstrument[WellCore]):
734
746
  self._pipette_id
735
747
  )
736
748
 
737
- def get_nozzle_map(self) -> NozzleMap:
749
+ def get_nozzle_map(self) -> NozzleMapInterface:
738
750
  return self._engine_client.state.tips.get_pipette_nozzle_map(self._pipette_id)
739
751
 
740
752
  def has_tip(self) -> bool:
@@ -852,6 +864,45 @@ class InstrumentCore(AbstractInstrument[WellCore]):
852
864
  )
853
865
  )
854
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
+
855
906
  def retract(self) -> None:
856
907
  """Retract this instrument to the top of the gantry."""
857
908
  z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id)
@@ -937,3 +988,9 @@ class InstrumentCore(AbstractInstrument[WellCore]):
937
988
  self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
938
989
 
939
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,
@@ -3,14 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import abstractmethod, ABC
6
- from typing import Any, Generic, Optional, TypeVar, Union
6
+ from typing import Any, Generic, Optional, TypeVar, Union, List
7
7
 
8
8
  from opentrons import types
9
9
  from opentrons.hardware_control.dev_types import PipetteDict
10
10
  from opentrons.protocols.api_support.util import FlowRates
11
+ from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2
11
12
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
12
- from opentrons.hardware_control.nozzle_manager import NozzleMap
13
-
13
+ from opentrons.protocol_api._liquid import LiquidClass
14
14
  from ..disposal_locations import TrashBin, WasteChute
15
15
  from .well import WellCoreType
16
16
 
@@ -24,6 +24,14 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
24
24
  def set_default_speed(self, speed: float) -> None:
25
25
  ...
26
26
 
27
+ @abstractmethod
28
+ def air_gap_in_place(self, volume: float, flow_rate: float) -> None:
29
+ """Aspirate a given volume of air from the current location of the pipette.
30
+ Args:
31
+ volume: The volume of air to aspirate, in microliters.
32
+ flow_rate: The flow rate of air into the pipette, in microliters.
33
+ """
34
+
27
35
  @abstractmethod
28
36
  def aspirate(
29
37
  self,
@@ -222,7 +230,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
222
230
  ...
223
231
 
224
232
  @abstractmethod
225
- def get_nozzle_map(self) -> NozzleMap:
233
+ def get_nozzle_map(self) -> types.NozzleMapInterface:
226
234
  ...
227
235
 
228
236
  @abstractmethod
@@ -302,6 +310,32 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
302
310
  """
303
311
  ...
304
312
 
313
+ @abstractmethod
314
+ def load_liquid_class(
315
+ self,
316
+ liquid_class: LiquidClass,
317
+ pipette_load_name: str,
318
+ tiprack_uri: str,
319
+ ) -> str:
320
+ """Load the liquid class properties of given pipette and tiprack into the engine.
321
+
322
+ Returns: ID of the liquid class record
323
+ """
324
+ ...
325
+
326
+ @abstractmethod
327
+ def transfer_liquid(
328
+ self,
329
+ liquid_class_id: str,
330
+ volume: float,
331
+ source: List[WellCoreType],
332
+ dest: List[WellCoreType],
333
+ new_tip: TransferTipPolicyV2,
334
+ trash_location: Union[WellCoreType, types.Location, TrashBin, WasteChute],
335
+ ) -> None:
336
+ """Transfer a liquid from source to dest according to liquid class properties."""
337
+ ...
338
+
305
339
  @abstractmethod
306
340
  def is_tip_tracking_available(self) -> bool:
307
341
  """Return whether auto tip tracking is available for the pipette's current nozzle configuration."""
@@ -331,5 +365,9 @@ class AbstractInstrument(ABC, Generic[WellCoreType]):
331
365
  """Do a liquid probe to find the level of the liquid in the well."""
332
366
  ...
333
367
 
368
+ @abstractmethod
369
+ def nozzle_configuration_valid_for_lld(self) -> bool:
370
+ """Check if the nozzle configuration currently supports LLD."""
371
+
334
372
 
335
373
  InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any])