opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a2__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 (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/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 +70 -8
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/entrypoint_util.py +2 -5
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/METADATA +16 -15
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/RECORD +229 -202
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/WHEEL +1 -1
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/LICENSE +0 -0
  228. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/top_level.txt +0 -0
@@ -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 = {
@@ -65,6 +77,7 @@ _PIPETTE_NAMES_MAP = {
65
77
  "flex_8channel_1000": PipetteNameType.P1000_MULTI_FLEX,
66
78
  "flex_8channel_1000_em": PipetteNameType.P1000_MULTI_EM,
67
79
  "flex_96channel_1000": PipetteNameType.P1000_96,
80
+ "flex_96channel_200": PipetteNameType.P200_96,
68
81
  }
69
82
 
70
83
 
@@ -76,6 +89,14 @@ class PipetteMountTypeError(TypeError):
76
89
  """An error raised when an invalid mount type is used for loading pipettes."""
77
90
 
78
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
+
79
100
  class LabwareDefinitionIsNotAdapterError(ValueError):
80
101
  """An error raised when an adapter is attempted to be loaded as a labware."""
81
102
 
@@ -96,7 +117,7 @@ def ensure_mount_for_pipette(
96
117
  mount: Union[str, Mount, None], pipette: PipetteNameType
97
118
  ) -> Mount:
98
119
  """Ensure that an input value represents a valid mount, and is valid for the given pipette."""
99
- if pipette == PipetteNameType.P1000_96:
120
+ if pipette in [PipetteNameType.P1000_96, PipetteNameType.P200_96]:
100
121
  # Always validate the raw mount input, even if the pipette is a 96-channel and we're not going
101
122
  # to use the mount value.
102
123
  if mount is not None:
@@ -147,6 +168,25 @@ def _ensure_mount(mount: Union[str, Mount]) -> Mount:
147
168
  )
148
169
 
149
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
+
150
190
  def ensure_pipette_name(pipette_name: str) -> PipetteNameType:
151
191
  """Ensure that an input value represents a valid pipette name."""
152
192
  pipette_name = ensure_lowercase_name(pipette_name)
@@ -159,6 +199,79 @@ def ensure_pipette_name(pipette_name: str) -> PipetteNameType:
159
199
  ) from None
160
200
 
161
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
+
162
275
  # TODO(jbl 11-17-2023) this function's original purpose was ensure a valid deck slot for a given robot type
163
276
  # With deck configuration, the shape of this should change to better represent it checking if a deck slot
164
277
  # (and maybe any addressable area) being valid for that deck configuration
@@ -254,6 +367,27 @@ def ensure_definition_is_labware(definition: LabwareDefinition) -> None:
254
367
  )
255
368
 
256
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
+
257
391
  _MODULE_ALIASES: Dict[str, ModuleModel] = {
258
392
  "magdeck": MagneticModuleModel.MAGNETIC_V1,
259
393
  "magnetic module": MagneticModuleModel.MAGNETIC_V1,
@@ -484,3 +618,127 @@ def validate_location(
484
618
  if well is not None
485
619
  else PointTarget(location=target_location, in_place=in_place)
486
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:
@@ -20,6 +20,7 @@ from . import temperature_module
20
20
  from . import thermocycler
21
21
  from . import calibration
22
22
  from . import unsafe
23
+ from . import robot
23
24
 
24
25
  from .hash_command_params import hash_protocol_command_params
25
26
  from .generate_command_schema import generate_command_schema
@@ -34,13 +35,23 @@ from .command import (
34
35
 
35
36
  from .command_unions import (
36
37
  Command,
38
+ CommandAdapter,
37
39
  CommandParams,
38
40
  CommandCreate,
41
+ CommandCreateAdapter,
39
42
  CommandResult,
40
43
  CommandType,
41
44
  CommandDefinedErrorData,
42
45
  )
43
46
 
47
+ from .air_gap_in_place import (
48
+ AirGapInPlace,
49
+ AirGapInPlaceParams,
50
+ AirGapInPlaceCreate,
51
+ AirGapInPlaceResult,
52
+ AirGapInPlaceCommandType,
53
+ )
54
+
44
55
  from .aspirate import (
45
56
  Aspirate,
46
57
  AspirateParams,
@@ -138,6 +149,15 @@ from .load_liquid import (
138
149
  LoadLiquidImplementation,
139
150
  )
140
151
 
152
+ from .load_liquid_class import (
153
+ LoadLiquidClass,
154
+ LoadLiquidClassParams,
155
+ LoadLiquidClassCreate,
156
+ LoadLiquidClassResult,
157
+ LoadLiquidClassCommandType,
158
+ LoadLiquidClassImplementation,
159
+ )
160
+
141
161
  from .load_module import (
142
162
  LoadModule,
143
163
  LoadModuleParams,
@@ -154,6 +174,22 @@ from .load_pipette import (
154
174
  LoadPipetteCommandType,
155
175
  )
156
176
 
177
+ from .load_lid_stack import (
178
+ LoadLidStack,
179
+ LoadLidStackParams,
180
+ LoadLidStackCreate,
181
+ LoadLidStackResult,
182
+ LoadLidStackCommandType,
183
+ )
184
+
185
+ from .load_lid import (
186
+ LoadLid,
187
+ LoadLidParams,
188
+ LoadLidCreate,
189
+ LoadLidResult,
190
+ LoadLidCommandType,
191
+ )
192
+
157
193
  from .move_labware import (
158
194
  MoveLabware,
159
195
  MoveLabwareParams,
@@ -323,6 +359,14 @@ from .verify_tip_presence import (
323
359
  VerifyTipPresenceCommandType,
324
360
  )
325
361
 
362
+ from .get_next_tip import (
363
+ GetNextTip,
364
+ GetNextTipCreate,
365
+ GetNextTipParams,
366
+ GetNextTipResult,
367
+ GetNextTipCommandType,
368
+ )
369
+
326
370
  from .liquid_probe import (
327
371
  LiquidProbe,
328
372
  LiquidProbeParams,
@@ -339,8 +383,10 @@ from .liquid_probe import (
339
383
  __all__ = [
340
384
  # command type unions
341
385
  "Command",
386
+ "CommandAdapter",
342
387
  "CommandParams",
343
388
  "CommandCreate",
389
+ "CommandCreateAdapter",
344
390
  "CommandResult",
345
391
  "CommandType",
346
392
  "CommandPrivateResult",
@@ -355,6 +401,12 @@ __all__ = [
355
401
  "hash_protocol_command_params",
356
402
  # command schema generation
357
403
  "generate_command_schema",
404
+ # air gap command models
405
+ "AirGapInPlace",
406
+ "AirGapInPlaceCreate",
407
+ "AirGapInPlaceParams",
408
+ "AirGapInPlaceResult",
409
+ "AirGapInPlaceCommandType",
358
410
  # aspirate command models
359
411
  "Aspirate",
360
412
  "AspirateCreate",
@@ -440,6 +492,20 @@ __all__ = [
440
492
  "LoadPipetteResult",
441
493
  "LoadPipetteCommandType",
442
494
  "LoadPipettePrivateResult",
495
+ # load lid stack command models
496
+ "LoadLidStack",
497
+ "LoadLidStackCreate",
498
+ "LoadLidStackParams",
499
+ "LoadLidStackResult",
500
+ "LoadLidStackCommandType",
501
+ "LoadLidStackPrivateResult",
502
+ # load lid command models
503
+ "LoadLid",
504
+ "LoadLidCreate",
505
+ "LoadLidParams",
506
+ "LoadLidResult",
507
+ "LoadLidCommandType",
508
+ "LoadLidPrivateResult",
443
509
  # move labware command models
444
510
  "MoveLabware",
445
511
  "MoveLabwareCreate",
@@ -538,6 +604,14 @@ __all__ = [
538
604
  "LoadLiquidParams",
539
605
  "LoadLiquidResult",
540
606
  "LoadLiquidCommandType",
607
+ # load liquid class command models
608
+ "LoadLiquidClass",
609
+ "LoadLiquidClassParams",
610
+ "LoadLiquidClassCreate",
611
+ "LoadLiquidClassResult",
612
+ "LoadLiquidClassImplementation",
613
+ "LoadLiquidClassCommandType",
614
+ # hardware control command models
541
615
  # hardware module command bundles
542
616
  "absorbance_reader",
543
617
  "heater_shaker",
@@ -548,6 +622,7 @@ __all__ = [
548
622
  "calibration",
549
623
  # unsafe command bundle
550
624
  "unsafe",
625
+ "robot",
551
626
  # configure pipette volume command bundle
552
627
  "ConfigureForVolume",
553
628
  "ConfigureForVolumeCreate",
@@ -578,6 +653,12 @@ __all__ = [
578
653
  "VerifyTipPresenceParams",
579
654
  "VerifyTipPresenceResult",
580
655
  "VerifyTipPresenceCommandType",
656
+ # get next tip command bundle
657
+ "GetNextTip",
658
+ "GetNextTipCreate",
659
+ "GetNextTipParams",
660
+ "GetNextTipResult",
661
+ "GetNextTipCommandType",
581
662
  # liquid probe command bundle
582
663
  "LiquidProbe",
583
664
  "LiquidProbeParams",
@@ -60,7 +60,6 @@ class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResul
60
60
  hardware_lid_status = AbsorbanceReaderLidStatus.OFF
61
61
  if not self._state_view.config.use_virtual_modules:
62
62
  abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
63
-
64
63
  if abs_reader is not None:
65
64
  hardware_lid_status = await abs_reader.get_current_lid_status()
66
65
  else:
@@ -95,7 +94,6 @@ class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResul
95
94
  deck_slot=self._state_view.modules.get_location(
96
95
  params.moduleId
97
96
  ).slotName,
98
- deck_type=self._state_view.config.deck_type,
99
97
  model=absorbance_model,
100
98
  )
101
99
  )
@@ -1,9 +1,10 @@
1
1
  """Command models to initialize an Absorbance Reader."""
2
2
  from __future__ import annotations
3
- from typing import List, Optional, Literal, TYPE_CHECKING
3
+ from typing import List, Optional, Literal, TYPE_CHECKING, Any
4
4
  from typing_extensions import Type
5
5
 
6
6
  from pydantic import BaseModel, Field
7
+ from pydantic.json_schema import SkipJsonSchema
7
8
 
8
9
  from opentrons.drivers.types import ABSMeasurementMode
9
10
  from opentrons.protocol_engine.types import ABSMeasureMode
@@ -11,6 +12,7 @@ from opentrons.protocol_engine.types import ABSMeasureMode
11
12
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
12
13
  from ...errors.error_occurrence import ErrorOccurrence
13
14
  from ...errors import InvalidWavelengthError
15
+ from ...state import update_types
14
16
 
15
17
  if TYPE_CHECKING:
16
18
  from opentrons.protocol_engine.state.state import StateView
@@ -20,6 +22,10 @@ if TYPE_CHECKING:
20
22
  InitializeCommandType = Literal["absorbanceReader/initialize"]
21
23
 
22
24
 
25
+ def _remove_default(s: dict[str, Any]) -> None:
26
+ s.pop("default", None)
27
+
28
+
23
29
  class InitializeParams(BaseModel):
24
30
  """Input parameters to initialize an absorbance reading."""
25
31
 
@@ -28,8 +34,10 @@ class InitializeParams(BaseModel):
28
34
  ..., description="Initialize single or multi measurement mode."
29
35
  )
30
36
  sampleWavelengths: List[int] = Field(..., description="Sample wavelengths in nm.")
31
- referenceWavelength: Optional[int] = Field(
32
- None, description="Optional reference wavelength in nm."
37
+ referenceWavelength: int | SkipJsonSchema[None] = Field(
38
+ None,
39
+ description="Optional reference wavelength in nm.",
40
+ json_schema_extra=_remove_default,
33
41
  )
34
42
 
35
43
 
@@ -53,6 +61,7 @@ class InitializeImpl(
53
61
 
54
62
  async def execute(self, params: InitializeParams) -> SuccessData[InitializeResult]:
55
63
  """Initiate a single absorbance measurement."""
64
+ state_update = update_types.StateUpdate()
56
65
  abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate(
57
66
  module_id=params.moduleId
58
67
  )
@@ -113,10 +122,15 @@ class InitializeImpl(
113
122
  reference_wavelength=params.referenceWavelength,
114
123
  )
115
124
 
116
- return SuccessData(
117
- public=InitializeResult(),
125
+ state_update.initialize_absorbance_reader(
126
+ abs_reader_substate.module_id,
127
+ params.measureMode,
128
+ params.sampleWavelengths,
129
+ params.referenceWavelength,
118
130
  )
119
131
 
132
+ return SuccessData(public=InitializeResult(), state_update=state_update)
133
+
120
134
 
121
135
  class Initialize(BaseCommand[InitializeParams, InitializeResult, ErrorOccurrence]):
122
136
  """A command to initialize an Absorbance Reader."""
@@ -91,7 +91,6 @@ class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]
91
91
  deck_slot=self._state_view.modules.get_location(
92
92
  params.moduleId
93
93
  ).slotName,
94
- deck_type=self._state_view.config.deck_type,
95
94
  model=absorbance_model,
96
95
  )
97
96
  )