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
@@ -1,29 +1,50 @@
1
1
  """Touch tip command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
- from pydantic import Field
4
- from typing import TYPE_CHECKING, Optional, Type
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
5
+
5
6
  from typing_extensions import Literal
7
+ from pydantic import Field
8
+ from pydantic.json_schema import SkipJsonSchema
6
9
 
7
- from opentrons.protocol_engine.state import update_types
10
+ from opentrons.types import Point
8
11
 
9
- from ..errors import TouchTipDisabledError, LabwareIsTipRackError
12
+ from ..errors import (
13
+ TouchTipDisabledError,
14
+ TouchTipIncompatibleArgumentsError,
15
+ LabwareIsTipRackError,
16
+ )
10
17
  from ..types import DeckPoint
11
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
12
- from ..errors.error_occurrence import ErrorOccurrence
18
+ from .command import (
19
+ AbstractCommandImpl,
20
+ BaseCommand,
21
+ BaseCommandCreate,
22
+ SuccessData,
23
+ DefinedErrorData,
24
+ )
13
25
  from .pipetting_common import (
14
26
  PipetteIdMixin,
27
+ )
28
+ from .movement_common import (
15
29
  WellLocationMixin,
16
30
  DestinationPositionResult,
31
+ StallOrCollisionError,
32
+ move_to_well,
17
33
  )
18
34
 
19
35
  if TYPE_CHECKING:
20
36
  from ..execution import MovementHandler, GantryMover
21
37
  from ..state.state import StateView
38
+ from ..resources.model_utils import ModelUtils
22
39
 
23
40
 
24
41
  TouchTipCommandType = Literal["touchTip"]
25
42
 
26
43
 
44
+ def _remove_default(s: dict[str, Any]) -> None:
45
+ s.pop("default", None)
46
+
47
+
27
48
  class TouchTipParams(PipetteIdMixin, WellLocationMixin):
28
49
  """Payload needed to touch a pipette tip the sides of a specific well."""
29
50
 
@@ -34,12 +55,20 @@ class TouchTipParams(PipetteIdMixin, WellLocationMixin):
34
55
  ),
35
56
  )
36
57
 
37
- speed: Optional[float] = Field(
58
+ mmFromEdge: float | SkipJsonSchema[None] = Field(
59
+ None,
60
+ description="Offset away from the the well edge, in millimeters."
61
+ "Incompatible when a radius is included as a non 1.0 value.",
62
+ json_schema_extra=_remove_default,
63
+ )
64
+
65
+ speed: float | SkipJsonSchema[None] = Field(
38
66
  None,
39
67
  description=(
40
68
  "Override the travel speed in mm/s."
41
69
  " This controls the straight linear speed of motion."
42
70
  ),
71
+ json_schema_extra=_remove_default,
43
72
  )
44
73
 
45
74
 
@@ -50,7 +79,10 @@ class TouchTipResult(DestinationPositionResult):
50
79
 
51
80
 
52
81
  class TouchTipImplementation(
53
- AbstractCommandImpl[TouchTipParams, SuccessData[TouchTipResult]]
82
+ AbstractCommandImpl[
83
+ TouchTipParams,
84
+ SuccessData[TouchTipResult] | DefinedErrorData[StallOrCollisionError],
85
+ ]
54
86
  ):
55
87
  """Touch tip command implementation."""
56
88
 
@@ -59,19 +91,26 @@ class TouchTipImplementation(
59
91
  state_view: StateView,
60
92
  movement: MovementHandler,
61
93
  gantry_mover: GantryMover,
94
+ model_utils: ModelUtils,
62
95
  **kwargs: object,
63
96
  ) -> None:
64
97
  self._state_view = state_view
65
98
  self._movement = movement
66
99
  self._gantry_mover = gantry_mover
100
+ self._model_utils = model_utils
67
101
 
68
- async def execute(self, params: TouchTipParams) -> SuccessData[TouchTipResult]:
102
+ async def execute(
103
+ self, params: TouchTipParams
104
+ ) -> SuccessData[TouchTipResult] | DefinedErrorData[StallOrCollisionError]:
69
105
  """Touch tip to sides of a well using the requested pipette."""
70
106
  pipette_id = params.pipetteId
71
107
  labware_id = params.labwareId
72
108
  well_name = params.wellName
73
109
 
74
- state_update = update_types.StateUpdate()
110
+ if params.radius != 1.0 and params.mmFromEdge is not None:
111
+ raise TouchTipIncompatibleArgumentsError(
112
+ "Cannot use mmFromEdge with a radius that is not 1.0"
113
+ )
75
114
 
76
115
  if self._state_view.labware.get_has_quirk(labware_id, "touchTipDisabled"):
77
116
  raise TouchTipDisabledError(
@@ -81,23 +120,33 @@ class TouchTipImplementation(
81
120
  if self._state_view.labware.is_tiprack(labware_id):
82
121
  raise LabwareIsTipRackError("Cannot touch tip on tip rack")
83
122
 
84
- center_point = await self._movement.move_to_well(
123
+ center_result = await move_to_well(
124
+ movement=self._movement,
125
+ model_utils=self._model_utils,
85
126
  pipette_id=pipette_id,
86
127
  labware_id=labware_id,
87
128
  well_name=well_name,
88
129
  well_location=params.wellLocation,
89
130
  )
131
+ if isinstance(center_result, DefinedErrorData):
132
+ return center_result
90
133
 
91
134
  touch_speed = self._state_view.pipettes.get_movement_speed(
92
135
  pipette_id, params.speed
93
136
  )
94
137
 
138
+ mm_from_edge = params.mmFromEdge if params.mmFromEdge is not None else 0
95
139
  touch_waypoints = self._state_view.motion.get_touch_tip_waypoints(
96
140
  pipette_id=pipette_id,
97
141
  labware_id=labware_id,
98
142
  well_name=well_name,
99
143
  radius=params.radius,
100
- center_point=center_point,
144
+ mm_from_edge=mm_from_edge,
145
+ center_point=Point(
146
+ center_result.public.position.x,
147
+ center_result.public.position.y,
148
+ center_result.public.position.z,
149
+ ),
101
150
  )
102
151
 
103
152
  final_point = await self._gantry_mover.move_to(
@@ -105,10 +154,10 @@ class TouchTipImplementation(
105
154
  waypoints=touch_waypoints,
106
155
  speed=touch_speed,
107
156
  )
108
- final_deck_point = DeckPoint.construct(
157
+ final_deck_point = DeckPoint.model_construct(
109
158
  x=final_point.x, y=final_point.y, z=final_point.z
110
159
  )
111
- state_update.set_pipette_location(
160
+ state_update = center_result.state_update.set_pipette_location(
112
161
  pipette_id=pipette_id,
113
162
  new_labware_id=labware_id,
114
163
  new_well_name=well_name,
@@ -121,12 +170,12 @@ class TouchTipImplementation(
121
170
  )
122
171
 
123
172
 
124
- class TouchTip(BaseCommand[TouchTipParams, TouchTipResult, ErrorOccurrence]):
173
+ class TouchTip(BaseCommand[TouchTipParams, TouchTipResult, StallOrCollisionError]):
125
174
  """Touch up tip command model."""
126
175
 
127
176
  commandType: TouchTipCommandType = "touchTip"
128
177
  params: TouchTipParams
129
- result: Optional[TouchTipResult]
178
+ result: Optional[TouchTipResult] = None
130
179
 
131
180
  _ImplementationCls: Type[TouchTipImplementation] = TouchTipImplementation
132
181
 
@@ -10,6 +10,7 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, Succe
10
10
  from ..pipetting_common import PipetteIdMixin, FlowRateMixin
11
11
  from ...resources import ensure_ot3_hardware
12
12
  from ...errors.error_occurrence import ErrorOccurrence
13
+ from ...state import update_types
13
14
 
14
15
  from opentrons.hardware_control import HardwareControlAPI
15
16
  from opentrons.hardware_control.types import Axis
@@ -66,9 +67,11 @@ class UnsafeBlowOutInPlaceImplementation(
66
67
  await self._pipetting.blow_out_in_place(
67
68
  pipette_id=params.pipetteId, flow_rate=params.flowRate
68
69
  )
70
+ state_update = update_types.StateUpdate()
71
+ state_update.set_fluid_empty(pipette_id=params.pipetteId)
69
72
 
70
73
  return SuccessData(
71
- public=UnsafeBlowOutInPlaceResult(),
74
+ public=UnsafeBlowOutInPlaceResult(), state_update=state_update
72
75
  )
73
76
 
74
77
 
@@ -1,13 +1,16 @@
1
1
  """Command models to drop tip in place while plunger positions are unknown."""
2
+
2
3
  from __future__ import annotations
3
- from opentrons.protocol_engine.state.update_types import StateUpdate
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
5
+
4
6
  from pydantic import Field, BaseModel
5
- from typing import TYPE_CHECKING, Optional, Type
7
+ from pydantic.json_schema import SkipJsonSchema
6
8
  from typing_extensions import Literal
7
9
 
8
10
  from opentrons.hardware_control import HardwareControlAPI
9
11
  from opentrons.hardware_control.types import Axis
10
12
 
13
+ from opentrons.protocol_engine.state.update_types import StateUpdate
11
14
  from ..pipetting_common import PipetteIdMixin
12
15
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
13
16
  from ...errors.error_occurrence import ErrorOccurrence
@@ -21,16 +24,21 @@ if TYPE_CHECKING:
21
24
  UnsafeDropTipInPlaceCommandType = Literal["unsafe/dropTipInPlace"]
22
25
 
23
26
 
27
+ def _remove_default(s: dict[str, Any]) -> None:
28
+ s.pop("default", None)
29
+
30
+
24
31
  class UnsafeDropTipInPlaceParams(PipetteIdMixin):
25
32
  """Payload required to drop a tip in place even if the plunger position is not known."""
26
33
 
27
- homeAfter: Optional[bool] = Field(
34
+ homeAfter: bool | SkipJsonSchema[None] = Field(
28
35
  None,
29
36
  description=(
30
37
  "Whether to home this pipette's plunger after dropping the tip."
31
38
  " You should normally leave this unspecified to let the robot choose"
32
39
  " a safe default depending on its hardware."
33
40
  ),
41
+ json_schema_extra=_remove_default,
34
42
  )
35
43
 
36
44
 
@@ -77,6 +85,7 @@ class UnsafeDropTipInPlaceImplementation(
77
85
  state_update.update_pipette_tip_state(
78
86
  pipette_id=params.pipetteId, tip_geometry=None
79
87
  )
88
+ state_update.set_fluid_unknown(pipette_id=params.pipetteId)
80
89
 
81
90
  return SuccessData(
82
91
  public=UnsafeDropTipInPlaceResult(), state_update=state_update
@@ -52,10 +52,7 @@ class UnsafeEngageAxesImplementation(
52
52
  """Enable exes."""
53
53
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
54
54
  await ot3_hardware_api.engage_axes(
55
- [
56
- self._gantry_mover.motor_axis_to_hardware_axis(axis)
57
- for axis in params.axes
58
- ]
55
+ self._gantry_mover.motor_axes_to_present_hardware_axes(params.axes)
59
56
  )
60
57
  return SuccessData(
61
58
  public=UnsafeEngageAxesResult(),
@@ -58,10 +58,7 @@ class UpdatePositionEstimatorsImplementation(
58
58
  """Update axis position estimators from their encoders."""
59
59
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
60
60
  await ot3_hardware_api.update_axis_position_estimations(
61
- [
62
- self._gantry_mover.motor_axis_to_hardware_axis(axis)
63
- for axis in params.axes
64
- ]
61
+ self._gantry_mover.motor_axes_to_present_hardware_axes(params.axes)
65
62
  )
66
63
  return SuccessData(
67
64
  public=UpdatePositionEstimatorsResult(),
@@ -1,8 +1,9 @@
1
1
  """Verify tip presence command request, result and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
3
4
 
4
5
  from pydantic import Field, BaseModel
5
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
6
7
  from typing_extensions import Literal
7
8
 
8
9
  from .pipetting_common import PipetteIdMixin
@@ -18,14 +19,20 @@ if TYPE_CHECKING:
18
19
  VerifyTipPresenceCommandType = Literal["verifyTipPresence"]
19
20
 
20
21
 
22
+ def _remove_default(s: dict[str, Any]) -> None:
23
+ s.pop("default", None)
24
+
25
+
21
26
  class VerifyTipPresenceParams(PipetteIdMixin):
22
27
  """Payload required for a VerifyTipPresence command."""
23
28
 
24
29
  expectedState: TipPresenceStatus = Field(
25
30
  ..., description="The expected tip presence status on the pipette."
26
31
  )
27
- followSingularSensor: Optional[InstrumentSensorId] = Field(
28
- default=None, description="The sensor id to follow if the other can be ignored."
32
+ followSingularSensor: InstrumentSensorId | SkipJsonSchema[None] = Field(
33
+ default=None,
34
+ description="The sensor id to follow if the other can be ignored.",
35
+ json_schema_extra=_remove_default,
29
36
  )
30
37
 
31
38
 
@@ -77,7 +84,7 @@ class VerifyTipPresence(
77
84
 
78
85
  commandType: VerifyTipPresenceCommandType = "verifyTipPresence"
79
86
  params: VerifyTipPresenceParams
80
- result: Optional[VerifyTipPresenceResult]
87
+ result: Optional[VerifyTipPresenceResult] = None
81
88
 
82
89
  _ImplementationCls: Type[
83
90
  VerifyTipPresenceImplementation
@@ -1,7 +1,9 @@
1
1
  """Wait for duration command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
+
3
5
  from pydantic import BaseModel, Field
4
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
@@ -14,13 +16,18 @@ if TYPE_CHECKING:
14
16
  WaitForDurationCommandType = Literal["waitForDuration"]
15
17
 
16
18
 
19
+ def _remove_default(s: dict[str, Any]) -> None:
20
+ s.pop("default", None)
21
+
22
+
17
23
  class WaitForDurationParams(BaseModel):
18
24
  """Payload required to pause the protocol."""
19
25
 
20
26
  seconds: float = Field(..., description="Duration, in seconds, to wait for.")
21
- message: Optional[str] = Field(
27
+ message: str | SkipJsonSchema[None] = Field(
22
28
  None,
23
29
  description="A user-facing message associated with the pause",
30
+ json_schema_extra=_remove_default,
24
31
  )
25
32
 
26
33
 
@@ -53,7 +60,7 @@ class WaitForDuration(
53
60
 
54
61
  commandType: WaitForDurationCommandType = "waitForDuration"
55
62
  params: WaitForDurationParams
56
- result: Optional[WaitForDurationResult]
63
+ result: Optional[WaitForDurationResult] = None
57
64
 
58
65
  _ImplementationCls: Type[
59
66
  WaitForDurationImplementation
@@ -1,7 +1,9 @@
1
1
  """Wait for resume command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
+
3
5
  from pydantic import BaseModel, Field
4
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
@@ -16,12 +18,17 @@ if TYPE_CHECKING:
16
18
  WaitForResumeCommandType = Literal["waitForResume", "pause"]
17
19
 
18
20
 
21
+ def _remove_default(s: dict[str, Any]) -> None:
22
+ s.pop("default", None)
23
+
24
+
19
25
  class WaitForResumeParams(BaseModel):
20
26
  """Payload required to pause the protocol."""
21
27
 
22
- message: Optional[str] = Field(
28
+ message: str | SkipJsonSchema[None] = Field(
23
29
  None,
24
30
  description="A user-facing message associated with the pause",
31
+ json_schema_extra=_remove_default,
25
32
  )
26
33
 
27
34
 
@@ -54,7 +61,7 @@ class WaitForResume(
54
61
 
55
62
  commandType: WaitForResumeCommandType = "waitForResume"
56
63
  params: WaitForResumeParams
57
- result: Optional[WaitForResumeResult]
64
+ result: Optional[WaitForResumeResult] = None
58
65
 
59
66
  _ImplementationCls: Type[WaitForResumeImplementation] = WaitForResumeImplementation
60
67
 
@@ -24,6 +24,7 @@ from .exceptions import (
24
24
  LabwareIsTipRackError,
25
25
  LabwareIsAdapterError,
26
26
  TouchTipDisabledError,
27
+ TouchTipIncompatibleArgumentsError,
27
28
  WellDoesNotExistError,
28
29
  PipetteNotLoadedError,
29
30
  ModuleNotLoadedError,
@@ -78,6 +79,9 @@ from .exceptions import (
78
79
  OperationLocationNotInWellError,
79
80
  InvalidDispenseVolumeError,
80
81
  StorageLimitReachedError,
82
+ InvalidLiquidError,
83
+ LiquidClassDoesNotExistError,
84
+ LiquidClassRedefinitionError,
81
85
  )
82
86
 
83
87
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -107,6 +111,7 @@ __all__ = [
107
111
  "LabwareIsTipRackError",
108
112
  "LabwareIsAdapterError",
109
113
  "TouchTipDisabledError",
114
+ "TouchTipIncompatibleArgumentsError",
110
115
  "WellDoesNotExistError",
111
116
  "PipetteNotLoadedError",
112
117
  "ModuleNotLoadedError",
@@ -138,6 +143,7 @@ __all__ = [
138
143
  "InvalidTargetSpeedError",
139
144
  "InvalidBlockVolumeError",
140
145
  "InvalidHoldTimeError",
146
+ "InvalidLiquidError",
141
147
  "InvalidWavelengthError",
142
148
  "CannotPerformModuleAction",
143
149
  "ResumeFromRecoveryNotAllowedError",
@@ -164,4 +170,6 @@ __all__ = [
164
170
  "OperationLocationNotInWellError",
165
171
  "InvalidDispenseVolumeError",
166
172
  "StorageLimitReachedError",
173
+ "LiquidClassDoesNotExistError",
174
+ "LiquidClassRedefinitionError",
167
175
  ]
@@ -4,7 +4,7 @@ from logging import getLogger
4
4
  from datetime import datetime
5
5
  from textwrap import dedent
6
6
  from typing import Any, Dict, Mapping, List, Type, Union, Optional, Sequence
7
- from pydantic import BaseModel, Field
7
+ from pydantic import BaseModel, Field, ConfigDict
8
8
  from opentrons_shared_data.errors.codes import ErrorCodes
9
9
  from .exceptions import ProtocolEngineError
10
10
  from opentrons_shared_data.errors.exceptions import EnumeratedError
@@ -29,7 +29,7 @@ class ErrorOccurrence(BaseModel):
29
29
  wrappedErrors = [
30
30
  cls.from_failed(id, createdAt, err) for err in error.wrapping
31
31
  ]
32
- return cls.construct(
32
+ return cls.model_construct(
33
33
  id=id,
34
34
  createdAt=createdAt,
35
35
  errorType=type(error).__name__,
@@ -39,6 +39,21 @@ class ErrorOccurrence(BaseModel):
39
39
  wrappedErrors=wrappedErrors,
40
40
  )
41
41
 
42
+ @staticmethod
43
+ def schema_extra(schema: Dict[str, Any], model: object) -> None:
44
+ """Append the schema to make the errorCode appear required.
45
+
46
+ `errorCode`, `wrappedErrors`, and `errorInfo` have defaults because they are not included in earlier
47
+ versions of this model, _and_ this model is loaded directly from
48
+ the on-robot store. That means that, without a default, it will
49
+ fail to parse. Once a default is defined, the automated schema will
50
+ mark this as a non-required field, which is misleading as this is
51
+ a response from the server to the client and it will always have an
52
+ errorCode defined. This hack is required because it informs the client
53
+ that it does not, in fact, have to account for a missing errorCode, wrappedError, or errorInfo.
54
+ """
55
+ schema["required"].extend(["errorCode", "wrappedErrors", "errorInfo"])
56
+
42
57
  id: str = Field(..., description="Unique identifier of this error occurrence.")
43
58
  createdAt: datetime = Field(..., description="When the error occurred.")
44
59
 
@@ -145,23 +160,7 @@ class ErrorOccurrence(BaseModel):
145
160
  default=[], description="Errors that may have caused this one."
146
161
  )
147
162
 
148
- class Config:
149
- """Customize configuration for this model."""
150
-
151
- @staticmethod
152
- def schema_extra(schema: Dict[str, Any], model: object) -> None:
153
- """Append the schema to make the errorCode appear required.
154
-
155
- `errorCode`, `wrappedErrors`, and `errorInfo` have defaults because they are not included in earlier
156
- versions of this model, _and_ this model is loaded directly from
157
- the on-robot store. That means that, without a default, it will
158
- fail to parse. Once a default is defined, the automated schema will
159
- mark this as a non-required field, which is misleading as this is
160
- a response from the server to the client and it will always have an
161
- errorCode defined. This hack is required because it informs the client
162
- that it does not, in fact, have to account for a missing errorCode, wrappedError, or errorInfo.
163
- """
164
- schema["required"].extend(["errorCode", "wrappedErrors", "errorInfo"])
163
+ model_config = ConfigDict(json_schema_extra=schema_extra)
165
164
 
166
165
 
167
166
  # TODO (tz, 7-12-23): move this to exceptions.py when we stop relaying on ErrorOccurrence.
@@ -180,4 +179,4 @@ class ProtocolCommandFailedError(ProtocolEngineError):
180
179
  self.original_error = original_error
181
180
 
182
181
 
183
- ErrorOccurrence.update_forward_refs()
182
+ ErrorOccurrence.model_rebuild()
@@ -244,6 +244,19 @@ class LiquidDoesNotExistError(ProtocolEngineError):
244
244
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
245
245
 
246
246
 
247
+ class InvalidLiquidError(ProtocolEngineError):
248
+ """Raised when attempting to add a liquid with an invalid property."""
249
+
250
+ def __init__(
251
+ self,
252
+ message: Optional[str] = None,
253
+ details: Optional[Dict[str, Any]] = None,
254
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
255
+ ) -> None:
256
+ """Build an InvalidLiquidError."""
257
+ super().__init__(ErrorCodes.INVALID_PROTOCOL_DATA, message, details, wrapping)
258
+
259
+
247
260
  class LabwareDefinitionDoesNotExistError(ProtocolEngineError):
248
261
  """Raised when referencing a labware definition that does not exist."""
249
262
 
@@ -348,6 +361,19 @@ class TouchTipDisabledError(ProtocolEngineError):
348
361
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
349
362
 
350
363
 
364
+ class TouchTipIncompatibleArgumentsError(ProtocolEngineError):
365
+ """Raised when touch tip is used with both a custom radius and a mmFromEdge argument."""
366
+
367
+ def __init__(
368
+ self,
369
+ message: Optional[str] = None,
370
+ details: Optional[Dict[str, Any]] = None,
371
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
372
+ ) -> None:
373
+ """Build a TouchTipIncompatibleArgumentsError."""
374
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
375
+
376
+
351
377
  class WellDoesNotExistError(ProtocolEngineError):
352
378
  """Raised when referencing a well that does not exist."""
353
379
 
@@ -1155,3 +1181,27 @@ class StorageLimitReachedError(ProtocolEngineError):
1155
1181
  ) -> None:
1156
1182
  """Build an StorageLimitReached."""
1157
1183
  super().__init__(ErrorCodes.GENERAL_ERROR, message, detail, wrapping)
1184
+
1185
+
1186
+ class LiquidClassDoesNotExistError(ProtocolEngineError):
1187
+ """Raised when referencing a liquid class that has not been loaded."""
1188
+
1189
+ def __init__(
1190
+ self,
1191
+ message: Optional[str] = None,
1192
+ details: Optional[Dict[str, Any]] = None,
1193
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1194
+ ) -> None:
1195
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1196
+
1197
+
1198
+ class LiquidClassRedefinitionError(ProtocolEngineError):
1199
+ """Raised when attempting to load a liquid class that conflicts with a liquid class already loaded."""
1200
+
1201
+ def __init__(
1202
+ self,
1203
+ message: Optional[str] = None,
1204
+ details: Optional[Dict[str, Any]] = None,
1205
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1206
+ ) -> None:
1207
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
@@ -188,7 +188,7 @@ class CommandExecutor:
188
188
  "completedAt": self._model_utils.get_timestamp(),
189
189
  "notes": note_tracker.get_notes(),
190
190
  }
191
- succeeded_command = running_command.copy(update=update)
191
+ succeeded_command = running_command.model_copy(update=update)
192
192
  self._action_dispatcher.dispatch(
193
193
  SucceedCommandAction(
194
194
  command=succeeded_command,