opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,25 @@
1
- from typing import NamedTuple, Union, Dict, Optional
1
+ from typing import NamedTuple, Union, Optional
2
2
 
3
- from opentrons.types import Mount, DeckLocation, Point
3
+ from opentrons.types import (
4
+ Mount,
5
+ DeckLocation,
6
+ Location,
7
+ Point,
8
+ AxisMapType,
9
+ AxisType,
10
+ StringAxisMap,
11
+ )
4
12
  from opentrons.legacy_commands import publisher
5
- from opentrons.hardware_control import SyncHardwareAPI, types as hw_types
13
+ from opentrons.hardware_control import SyncHardwareAPI
14
+ from opentrons.protocols.api_support.util import requires_version
15
+ from opentrons.protocols.api_support.types import APIVersion
16
+ from opentrons_shared_data.pipette.types import PipetteNameType
6
17
 
7
- from ._types import OffDeckType
8
- from .core.common import ProtocolCore
18
+ from . import validation
19
+ from .core.common import ProtocolCore, RobotCore
20
+ from .module_contexts import ModuleContext
21
+ from .labware import Labware
22
+ from ._types import PipetteActionTypes, PlungerPositionTypes
9
23
 
10
24
 
11
25
  class HardwareManager(NamedTuple):
@@ -34,56 +48,214 @@ class RobotContext(publisher.CommandPublisher):
34
48
 
35
49
  """
36
50
 
37
- def __init__(self, core: ProtocolCore) -> None:
38
- self._hardware = HardwareManager(hardware=core.get_hardware())
51
+ def __init__(
52
+ self, core: RobotCore, protocol_core: ProtocolCore, api_version: APIVersion
53
+ ) -> None:
54
+ self._hardware = HardwareManager(hardware=protocol_core.get_hardware())
55
+ self._core = core
56
+ self._protocol_core = protocol_core
57
+ self._api_version = api_version
58
+
59
+ @property
60
+ @requires_version(2, 22)
61
+ def api_version(self) -> APIVersion:
62
+ return self._api_version
39
63
 
40
64
  @property
41
65
  def hardware(self) -> HardwareManager:
66
+ # TODO this hardware attribute should be deprecated
67
+ # in version 3.0+ as we will only support exposed robot
68
+ # context commands.
42
69
  return self._hardware
43
70
 
71
+ @requires_version(2, 22)
44
72
  def move_to(
45
73
  self,
46
74
  mount: Union[Mount, str],
47
- destination: Point,
48
- velocity: float,
75
+ destination: Location,
76
+ speed: Optional[float] = None,
49
77
  ) -> None:
50
- raise NotImplementedError()
78
+ """
79
+ Move a specified mount to a destination location on the deck.
80
+
81
+ :param mount: The mount of the instrument you wish to move.
82
+ This can either be an instance of :py:class:`.types.Mount` or one
83
+ of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note
84
+ that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``.
85
+ :type mount: types.Mount or str
86
+ :param Location destination:
87
+ :param speed:
88
+ """
89
+ mount = validation.ensure_instrument_mount(mount)
90
+ self._core.move_to(mount, destination.point, speed)
51
91
 
92
+ @requires_version(2, 22)
52
93
  def move_axes_to(
53
94
  self,
54
- abs_axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue],
55
- velocity: float,
56
- critical_point: Optional[hw_types.CriticalPoint],
95
+ axis_map: Union[AxisMapType, StringAxisMap],
96
+ critical_point: Optional[Union[AxisMapType, StringAxisMap]] = None,
97
+ speed: Optional[float] = None,
57
98
  ) -> None:
58
- raise NotImplementedError()
99
+ """
100
+ Move a set of axes to an absolute position on the deck.
59
101
 
102
+ :param axis_map: A dictionary mapping axes to an absolute position on the deck in mm.
103
+ :param critical_point: The critical point to move the axes with. It should only
104
+ specify the gantry axes (i.e. `x`, `y`, `z`).
105
+ :param float speed: The maximum speed with which you want to move all the axes
106
+ in the axis map.
107
+ """
108
+ instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
109
+ is_96_channel = instrument_on_left == PipetteNameType.P1000_96
110
+ axis_map = validation.ensure_axis_map_type(
111
+ axis_map, self._protocol_core.robot_type, is_96_channel
112
+ )
113
+ if critical_point:
114
+ critical_point = validation.ensure_axis_map_type(
115
+ critical_point, self._protocol_core.robot_type, is_96_channel
116
+ )
117
+ validation.ensure_only_gantry_axis_map_type(
118
+ critical_point, self._protocol_core.robot_type
119
+ )
120
+ else:
121
+ critical_point = None
122
+ self._core.move_axes_to(axis_map, critical_point, speed)
123
+
124
+ @requires_version(2, 22)
60
125
  def move_axes_relative(
61
- self, rel_axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue], velocity: float
126
+ self,
127
+ axis_map: Union[AxisMapType, StringAxisMap],
128
+ speed: Optional[float] = None,
62
129
  ) -> None:
63
- raise NotImplementedError()
130
+ """
131
+ Move a set of axes to a relative position on the deck.
132
+
133
+ :param axis_map: A dictionary mapping axes to relative movements in mm.
134
+ :type mount: types.Mount or str
64
135
 
65
- def close_gripper_jaw(self, force: float) -> None:
66
- raise NotImplementedError()
136
+ :param float speed: The maximum speed with which you want to move all the axes
137
+ in the axis map.
138
+ """
139
+ instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
140
+ is_96_channel = instrument_on_left == PipetteNameType.P1000_96
141
+
142
+ axis_map = validation.ensure_axis_map_type(
143
+ axis_map, self._protocol_core.robot_type, is_96_channel
144
+ )
145
+ self._core.move_axes_relative(axis_map, speed)
146
+
147
+ def close_gripper_jaw(self, force: Optional[float] = None) -> None:
148
+ """Command the gripper closed with some force."""
149
+ self._core.close_gripper(force)
67
150
 
68
151
  def open_gripper_jaw(self) -> None:
69
- raise NotImplementedError()
152
+ """Command the gripper open."""
153
+ self._core.release_grip()
70
154
 
71
155
  def axis_coordinates_for(
72
- self, mount: Union[Mount, str], location: Union[DeckLocation, OffDeckType]
73
- ) -> None:
74
- raise NotImplementedError()
156
+ self,
157
+ mount: Union[Mount, str],
158
+ location: Union[Location, ModuleContext, DeckLocation],
159
+ ) -> AxisMapType:
160
+ """
161
+ Build a :py:class:`.types.AxisMapType` from a location to be compatible with
162
+ either :py:meth:`.RobotContext.move_axes_to` or :py:meth:`.RobotContext.move_axes_relative`.
163
+ You must provide only one of `location`, `slot`, or `module` to build
164
+ the axis map.
165
+
166
+ :param mount: The mount of the instrument you wish create an axis map for.
167
+ This can either be an instance of :py:class:`.types.Mount` or one
168
+ of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note
169
+ that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``.
170
+ :type mount: types.Mount or str
171
+ :param location: The location to format an axis map for.
172
+ :type location: `Well`, `ModuleContext`, `DeckLocation` or `OffDeckType`
173
+ """
174
+ mount = validation.ensure_instrument_mount(mount)
175
+
176
+ mount_axis = AxisType.axis_for_mount(mount)
177
+ if location:
178
+ loc: Union[Point, Labware, None]
179
+ if isinstance(location, ModuleContext):
180
+ loc = location.labware
181
+ if not loc:
182
+ raise ValueError(f"There must be a labware on {location}")
183
+ top_of_labware = loc.wells()[0].top()
184
+ loc = top_of_labware.point
185
+ return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y}
186
+ elif location is DeckLocation and not isinstance(location, Location):
187
+ slot_name = validation.ensure_and_convert_deck_slot(
188
+ location,
189
+ api_version=self._api_version,
190
+ robot_type=self._protocol_core.robot_type,
191
+ )
192
+ loc = self._protocol_core.get_slot_center(slot_name)
193
+ return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y}
194
+ elif isinstance(location, Location):
195
+ assert isinstance(location, Location)
196
+ loc = location.point
197
+ return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y}
198
+ else:
199
+ raise ValueError(
200
+ "Location parameter must be a Module, Deck Location, or Location type."
201
+ )
202
+ else:
203
+ raise TypeError("You must specify a location to move to.")
75
204
 
76
205
  def plunger_coordinates_for_volume(
77
- self, mount: Union[Mount, str], volume: float
78
- ) -> None:
79
- raise NotImplementedError()
206
+ self, mount: Union[Mount, str], volume: float, action: PipetteActionTypes
207
+ ) -> AxisMapType:
208
+ """
209
+ Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from volume.
210
+
211
+ """
212
+ pipette_name = self._core.get_pipette_type_from_engine(mount)
213
+ if not pipette_name:
214
+ raise ValueError(
215
+ f"Expected a pipette to be attached to provided mount {mount}"
216
+ )
217
+ mount = validation.ensure_mount_for_pipette(mount, pipette_name)
218
+ pipette_axis = AxisType.plunger_axis_for_mount(mount)
219
+
220
+ pipette_position = self._core.get_plunger_position_from_volume(
221
+ mount, volume, action, self._protocol_core.robot_type
222
+ )
223
+ return {pipette_axis: pipette_position}
80
224
 
81
225
  def plunger_coordinates_for_named_position(
82
- self, mount: Union[Mount, str], position_name: str
83
- ) -> None:
84
- raise NotImplementedError()
226
+ self, mount: Union[Mount, str], position_name: PlungerPositionTypes
227
+ ) -> AxisMapType:
228
+ """
229
+ Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from position_name.
85
230
 
86
- def build_axis_map(
87
- self, axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue]
88
- ) -> None:
89
- raise NotImplementedError()
231
+ """
232
+ pipette_name = self._core.get_pipette_type_from_engine(mount)
233
+ if not pipette_name:
234
+ raise ValueError(
235
+ f"Expected a pipette to be attached to provided mount {mount}"
236
+ )
237
+ mount = validation.ensure_mount_for_pipette(mount, pipette_name)
238
+ pipette_axis = AxisType.plunger_axis_for_mount(mount)
239
+ pipette_position = self._core.get_plunger_position_from_name(
240
+ mount, position_name
241
+ )
242
+ return {pipette_axis: pipette_position}
243
+
244
+ def build_axis_map(self, axis_map: StringAxisMap) -> AxisMapType:
245
+ """Take in a :py:class:`.types.StringAxisMap` and output a :py:class:`.types.AxisMapType`.
246
+ A :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings:
247
+ ``"x"``, ``"y"``, "``z_l"``, "``z_r"``, "``z_g"``, ``"q"``.
248
+
249
+ An example of a valid axis map could be:
250
+
251
+ {"x": 1, "y": 2} or {"Z_L": 100}
252
+
253
+ Note that capitalization does not matter.
254
+
255
+ """
256
+ instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
257
+ is_96_channel = instrument_on_left == PipetteNameType.P1000_96
258
+
259
+ return validation.ensure_axis_map_type(
260
+ axis_map, self._protocol_core.robot_type, is_96_channel
261
+ )
@@ -11,7 +11,7 @@ from typing import (
11
11
  NamedTuple,
12
12
  TYPE_CHECKING,
13
13
  )
14
-
14
+ from math import isinf, isnan
15
15
  from typing_extensions import TypeGuard
16
16
 
17
17
  from opentrons_shared_data.labware.labware_definition import LabwareRole
@@ -21,7 +21,16 @@ from opentrons_shared_data.robot.types import RobotType
21
21
  from opentrons.protocols.api_support.types import APIVersion, ThermocyclerStep
22
22
  from opentrons.protocols.api_support.util import APIVersionError
23
23
  from opentrons.protocols.models import LabwareDefinition
24
- from opentrons.types import Mount, DeckSlotName, StagingSlotName, Location
24
+ from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2
25
+ from opentrons.types import (
26
+ Mount,
27
+ DeckSlotName,
28
+ StagingSlotName,
29
+ Location,
30
+ AxisType,
31
+ AxisMapType,
32
+ StringAxisMap,
33
+ )
25
34
  from opentrons.hardware_control.modules.types import (
26
35
  ModuleModel,
27
36
  MagneticModuleModel,
@@ -44,6 +53,9 @@ _COORDINATE_DECK_LABEL_VERSION_GATE = APIVersion(2, 15)
44
53
  # The first APIVersion where Python protocols can specify staging deck slots (e.g. "D4")
45
54
  _STAGING_DECK_SLOT_VERSION_GATE = APIVersion(2, 16)
46
55
 
56
+ # The first APIVersion where Python protocols can load lids as stacks and treat them as attributes of a parent labware.
57
+ LID_STACK_VERSION_GATE = APIVersion(2, 23)
58
+
47
59
  # Mapping of public Python Protocol API pipette load names
48
60
  # to names used by the internal Opentrons system
49
61
  _PIPETTE_NAMES_MAP = {
@@ -63,7 +75,9 @@ _PIPETTE_NAMES_MAP = {
63
75
  "flex_8channel_50": PipetteNameType.P50_MULTI_FLEX,
64
76
  "flex_1channel_1000": PipetteNameType.P1000_SINGLE_FLEX,
65
77
  "flex_8channel_1000": PipetteNameType.P1000_MULTI_FLEX,
78
+ "flex_8channel_1000_em": PipetteNameType.P1000_MULTI_EM,
66
79
  "flex_96channel_1000": PipetteNameType.P1000_96,
80
+ "flex_96channel_200": PipetteNameType.P200_96,
67
81
  }
68
82
 
69
83
 
@@ -75,6 +89,14 @@ class PipetteMountTypeError(TypeError):
75
89
  """An error raised when an invalid mount type is used for loading pipettes."""
76
90
 
77
91
 
92
+ class InstrumentMountTypeError(TypeError):
93
+ """An error raised when an invalid mount type is used for any available instruments."""
94
+
95
+
96
+ class IncorrectAxisError(TypeError):
97
+ """An error raised when an invalid axis key is provided in an axis map."""
98
+
99
+
78
100
  class LabwareDefinitionIsNotAdapterError(ValueError):
79
101
  """An error raised when an adapter is attempted to be loaded as a labware."""
80
102
 
@@ -95,7 +117,7 @@ def ensure_mount_for_pipette(
95
117
  mount: Union[str, Mount, None], pipette: PipetteNameType
96
118
  ) -> Mount:
97
119
  """Ensure that an input value represents a valid mount, and is valid for the given pipette."""
98
- if pipette == PipetteNameType.P1000_96:
120
+ if pipette in [PipetteNameType.P1000_96, PipetteNameType.P200_96]:
99
121
  # Always validate the raw mount input, even if the pipette is a 96-channel and we're not going
100
122
  # to use the mount value.
101
123
  if mount is not None:
@@ -146,6 +168,25 @@ def _ensure_mount(mount: Union[str, Mount]) -> Mount:
146
168
  )
147
169
 
148
170
 
171
+ def ensure_instrument_mount(mount: Union[str, Mount]) -> Mount:
172
+ """Ensure that an input value represents a valid Mount for all instruments."""
173
+ if isinstance(mount, Mount):
174
+ return mount
175
+
176
+ if isinstance(mount, str):
177
+ if mount == "gripper":
178
+ # TODO (lc 08-02-2024) We should decide on the user facing name for
179
+ # the gripper mount axis.
180
+ mount = "extension"
181
+ try:
182
+ return Mount[mount.upper()]
183
+ except KeyError as e:
184
+ raise InstrumentMountTypeError(
185
+ "If mount is specified as a string, it must be 'left', 'right', 'gripper', or 'extension';"
186
+ f" instead, {mount} was given."
187
+ ) from e
188
+
189
+
149
190
  def ensure_pipette_name(pipette_name: str) -> PipetteNameType:
150
191
  """Ensure that an input value represents a valid pipette name."""
151
192
  pipette_name = ensure_lowercase_name(pipette_name)
@@ -158,6 +199,79 @@ def ensure_pipette_name(pipette_name: str) -> PipetteNameType:
158
199
  ) from None
159
200
 
160
201
 
202
+ def _check_ot2_axis_type(
203
+ robot_type: RobotType, axis_map_keys: Union[List[str], List[AxisType]]
204
+ ) -> None:
205
+ if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], AxisType):
206
+ if any(k not in AxisType.ot2_axes() for k in axis_map_keys):
207
+ raise IncorrectAxisError(
208
+ f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}"
209
+ )
210
+ if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], str):
211
+ if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [union-attr]
212
+ raise IncorrectAxisError(
213
+ f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}"
214
+ )
215
+
216
+
217
+ def _check_96_channel_axis_type(
218
+ is_96_channel: bool, axis_map_keys: Union[List[str], List[AxisType]]
219
+ ) -> None:
220
+ if is_96_channel and any(
221
+ key_variation in axis_map_keys for key_variation in ["Z_R", "z_r", AxisType.Z_R]
222
+ ):
223
+ raise IncorrectAxisError(
224
+ "A 96 channel is attached. You cannot move the `Z_R` mount."
225
+ )
226
+ if not is_96_channel and any(
227
+ key_variation in axis_map_keys for key_variation in ["Q", "q", AxisType.Q]
228
+ ):
229
+ raise IncorrectAxisError(
230
+ "A 96 channel is not attached. The clamp `Q` motor does not exist."
231
+ )
232
+
233
+
234
+ def ensure_axis_map_type(
235
+ axis_map: Union[AxisMapType, StringAxisMap],
236
+ robot_type: RobotType,
237
+ is_96_channel: bool = False,
238
+ ) -> AxisMapType:
239
+ """Ensure that the axis map provided is in the correct shape and contains the correct keys."""
240
+ axis_map_keys: Union[List[str], List[AxisType]] = list(axis_map.keys()) # type: ignore
241
+ key_type = set(type(k) for k in axis_map_keys)
242
+
243
+ if len(key_type) > 1:
244
+ raise IncorrectAxisError(
245
+ "Please provide an `axis_map` with only string or only AxisType keys."
246
+ )
247
+ _check_ot2_axis_type(robot_type, axis_map_keys)
248
+ _check_96_channel_axis_type(is_96_channel, axis_map_keys)
249
+
250
+ if all(isinstance(k, AxisType) for k in axis_map_keys):
251
+ return_map: AxisMapType = axis_map # type: ignore
252
+ return return_map
253
+ try:
254
+ return {AxisType[k.upper()]: v for k, v in axis_map.items()} # type: ignore [union-attr]
255
+ except KeyError as e:
256
+ raise IncorrectAxisError(f"{e} is not a supported `AxisMapType`")
257
+
258
+
259
+ def ensure_only_gantry_axis_map_type(
260
+ axis_map: AxisMapType, robot_type: RobotType
261
+ ) -> None:
262
+ """Ensure that the axis map provided is in the correct shape and matches the gantry axes for the robot."""
263
+ if robot_type == "OT-2 Standard":
264
+ if any(k not in AxisType.ot2_gantry_axes() for k in axis_map.keys()):
265
+ raise IncorrectAxisError(
266
+ f"A critical point only accepts OT-2 gantry axes which are {AxisType.ot2_gantry_axes()}"
267
+ )
268
+ else:
269
+ if any(k not in AxisType.flex_gantry_axes() for k in axis_map.keys()):
270
+ raise IncorrectAxisError(
271
+ f"A critical point only accepts Flex gantry axes which are {AxisType.flex_gantry_axes()}"
272
+ )
273
+
274
+
161
275
  # TODO(jbl 11-17-2023) this function's original purpose was ensure a valid deck slot for a given robot type
162
276
  # With deck configuration, the shape of this should change to better represent it checking if a deck slot
163
277
  # (and maybe any addressable area) being valid for that deck configuration
@@ -253,6 +367,27 @@ def ensure_definition_is_labware(definition: LabwareDefinition) -> None:
253
367
  )
254
368
 
255
369
 
370
+ def ensure_definition_is_lid(definition: LabwareDefinition) -> None:
371
+ """Ensure that one of the definition's allowed roles is `lid` or that that field is empty."""
372
+ if LabwareRole.lid not in definition.allowedRoles:
373
+ raise LabwareDefinitionIsNotLabwareError(
374
+ f"Labware {definition.parameters.loadName} is not a lid."
375
+ )
376
+
377
+
378
+ def ensure_definition_is_not_lid_after_api_version(
379
+ api_version: APIVersion, definition: LabwareDefinition
380
+ ) -> None:
381
+ """Ensure that one of the definition's allowed roles is not `lid` or that the API Version is below the release where lid loading was seperated."""
382
+ if (
383
+ LabwareRole.lid in definition.allowedRoles
384
+ and api_version >= LID_STACK_VERSION_GATE
385
+ ):
386
+ raise APIVersionError(
387
+ f"Labware Lids cannot be loaded like standard labware in Protocols written with an API version greater than {LID_STACK_VERSION_GATE}."
388
+ )
389
+
390
+
256
391
  _MODULE_ALIASES: Dict[str, ModuleModel] = {
257
392
  "magdeck": MagneticModuleModel.MAGNETIC_V1,
258
393
  "magnetic module": MagneticModuleModel.MAGNETIC_V1,
@@ -483,3 +618,127 @@ def validate_location(
483
618
  if well is not None
484
619
  else PointTarget(location=target_location, in_place=in_place)
485
620
  )
621
+
622
+
623
+ def ensure_boolean(value: bool) -> bool:
624
+ """Ensure value is a boolean."""
625
+ if not isinstance(value, bool):
626
+ raise ValueError("Value must be a boolean.")
627
+ return value
628
+
629
+
630
+ def ensure_float(value: Union[int, float]) -> float:
631
+ """Ensure value is a float (or an integer) and return it as a float."""
632
+ if not isinstance(value, (int, float)):
633
+ raise ValueError("Value must be a floating point number.")
634
+ return float(value)
635
+
636
+
637
+ def ensure_positive_float(value: Union[int, float]) -> float:
638
+ """Ensure value is a positive and real float value."""
639
+ float_value = ensure_float(value)
640
+ if isnan(float_value) or isinf(float_value):
641
+ raise ValueError("Value must be a defined, non-infinite number.")
642
+ if float_value < 0:
643
+ raise ValueError("Value must be a positive float.")
644
+ return float_value
645
+
646
+
647
+ def ensure_positive_int(value: int) -> int:
648
+ """Ensure value is a positive integer."""
649
+ if not isinstance(value, int):
650
+ raise ValueError("Value must be an integer.")
651
+ if value < 0:
652
+ raise ValueError("Value must be a positive integer.")
653
+ return value
654
+
655
+
656
+ def validate_coordinates(value: Sequence[float]) -> Tuple[float, float, float]:
657
+ """Ensure value is a valid sequence of 3 floats and return a tuple of 3 floats."""
658
+ if len(value) != 3:
659
+ raise ValueError("Coordinates must be a sequence of exactly three numbers")
660
+ if not all(isinstance(v, (float, int)) for v in value):
661
+ raise ValueError("All values in coordinates must be floats.")
662
+ return float(value[0]), float(value[1]), float(value[2])
663
+
664
+
665
+ def ensure_new_tip_policy(value: str) -> TransferTipPolicyV2:
666
+ """Ensure that new_tip value is a valid TransferTipPolicy value."""
667
+ try:
668
+ return TransferTipPolicyV2(value.lower())
669
+ except ValueError:
670
+ raise ValueError(
671
+ f"'{value}' is invalid value for 'new_tip'."
672
+ f" Acceptable value is either 'never', 'once', 'always' or 'per source'."
673
+ )
674
+
675
+
676
+ def _verify_each_list_element_is_valid_location(locations: Sequence[Well]) -> None:
677
+ from .labware import Well
678
+
679
+ for loc in locations:
680
+ if not isinstance(loc, Well):
681
+ raise ValueError(
682
+ f"'{loc}' is not a valid location for transfer."
683
+ f" Location should be a well instance."
684
+ )
685
+
686
+
687
+ def ensure_valid_flat_wells_list_for_transfer_v2(
688
+ target: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
689
+ ) -> List[Well]:
690
+ """Ensure that the given target(s) for a liquid transfer are valid and in a flat list."""
691
+ from .labware import Well
692
+
693
+ if isinstance(target, Well):
694
+ return [target]
695
+
696
+ if isinstance(target, (list, tuple)):
697
+ if len(target) == 0:
698
+ raise ValueError("No target well(s) specified for transfer.")
699
+ if isinstance(target[0], (list, tuple)):
700
+ for sub_sequence in target:
701
+ _verify_each_list_element_is_valid_location(sub_sequence)
702
+ return [loc for sub_sequence in target for loc in sub_sequence]
703
+ else:
704
+ _verify_each_list_element_is_valid_location(target)
705
+ return list(target)
706
+ else:
707
+ raise ValueError(
708
+ f"'{target}' is not a valid location for transfer."
709
+ f" Location should be a well instance, or a 1-dimensional or"
710
+ f" 2-dimensional sequence of well instances."
711
+ )
712
+
713
+
714
+ def ensure_valid_tip_drop_location_for_transfer_v2(
715
+ tip_drop_location: Union[Location, Well, TrashBin, WasteChute]
716
+ ) -> Union[Location, Well, TrashBin, WasteChute]:
717
+ """Ensure that the tip drop location is valid for v2 transfer."""
718
+ from .labware import Well
719
+
720
+ if (
721
+ isinstance(tip_drop_location, Well)
722
+ or isinstance(tip_drop_location, TrashBin)
723
+ or isinstance(tip_drop_location, WasteChute)
724
+ ):
725
+ return tip_drop_location
726
+ elif isinstance(tip_drop_location, Location):
727
+ _, maybe_well = tip_drop_location.labware.get_parent_labware_and_well()
728
+
729
+ if maybe_well is None:
730
+ raise TypeError(
731
+ "If a location is specified as a `types.Location`"
732
+ " (for instance, as the result of a call to `Well.top()`),"
733
+ " it must be a location relative to a well,"
734
+ " since that is where a tip is dropped."
735
+ " However, the given location doesn't refer to any well."
736
+ )
737
+ return tip_drop_location
738
+ else:
739
+ raise TypeError(
740
+ f"If specified, location should be an instance of"
741
+ f" `types.Location` (e.g. the return value from `Well.top()`)"
742
+ f" or `Well` (e.g. `reservoir.wells()[0]`) or an instance of `TrashBin` or `WasteChute`."
743
+ f" However, it is '{tip_drop_location}'."
744
+ )
@@ -57,6 +57,8 @@ from .types import (
57
57
  ModuleModel,
58
58
  ModuleDefinition,
59
59
  Liquid,
60
+ LiquidClassRecord,
61
+ LiquidClassRecordWithId,
60
62
  AllNozzleLayoutConfiguration,
61
63
  SingleNozzleLayoutConfiguration,
62
64
  RowNozzleLayoutConfiguration,
@@ -122,6 +124,8 @@ __all__ = [
122
124
  "ModuleModel",
123
125
  "ModuleDefinition",
124
126
  "Liquid",
127
+ "LiquidClassRecord",
128
+ "LiquidClassRecordWithId",
125
129
  "AllNozzleLayoutConfiguration",
126
130
  "SingleNozzleLayoutConfiguration",
127
131
  "RowNozzleLayoutConfiguration",
@@ -27,7 +27,6 @@ from ..types import (
27
27
  ModuleDefinition,
28
28
  Liquid,
29
29
  DeckConfigurationType,
30
- AddressableAreaLocation,
31
30
  )
32
31
 
33
32
 
@@ -235,12 +234,12 @@ class SetDeckConfigurationAction:
235
234
  class AddAddressableAreaAction:
236
235
  """Add a single addressable area to state.
237
236
 
238
- This differs from the deck configuration in ProvideDeckConfigurationAction which
237
+ This differs from the deck configuration in SetDeckConfigurationAction which
239
238
  sends over a mapping of cutout fixtures. This action will only load one addressable
240
239
  area and that should be pre-validated before being sent via the action.
241
240
  """
242
241
 
243
- addressable_area: AddressableAreaLocation
242
+ addressable_area_name: str
244
243
 
245
244
 
246
245
  @dataclasses.dataclass(frozen=True)
@@ -77,6 +77,18 @@ class SyncClient:
77
77
  ) -> commands.LoadPipetteResult:
78
78
  pass
79
79
 
80
+ @overload
81
+ def execute_command_without_recovery(
82
+ self, params: commands.LoadLidStackParams
83
+ ) -> commands.LoadLidStackResult:
84
+ pass
85
+
86
+ @overload
87
+ def execute_command_without_recovery(
88
+ self, params: commands.LoadLidParams
89
+ ) -> commands.LoadLidResult:
90
+ pass
91
+
80
92
  @overload
81
93
  def execute_command_without_recovery(
82
94
  self, params: commands.LiquidProbeParams
@@ -89,6 +101,12 @@ class SyncClient:
89
101
  ) -> commands.TryLiquidProbeResult:
90
102
  pass
91
103
 
104
+ @overload
105
+ def execute_command_without_recovery(
106
+ self, params: commands.LoadLiquidClassParams
107
+ ) -> commands.LoadLiquidClassResult:
108
+ pass
109
+
92
110
  def execute_command_without_recovery(
93
111
  self, params: commands.CommandParams
94
112
  ) -> commands.CommandResult: