opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.0a1__py2.py3-none-any.whl

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

Potentially problematic release.


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

Files changed (229) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +28 -20
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +60 -23
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  47. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/protocol_api/__init__.py +20 -1
  51. opentrons/protocol_api/_liquid.py +24 -49
  52. opentrons/protocol_api/_liquid_properties.py +754 -0
  53. opentrons/protocol_api/_types.py +24 -0
  54. opentrons/protocol_api/core/common.py +2 -0
  55. opentrons/protocol_api/core/engine/instrument.py +82 -10
  56. opentrons/protocol_api/core/engine/labware.py +29 -7
  57. opentrons/protocol_api/core/engine/protocol.py +130 -5
  58. opentrons/protocol_api/core/engine/robot.py +139 -0
  59. opentrons/protocol_api/core/engine/well.py +4 -1
  60. opentrons/protocol_api/core/instrument.py +46 -4
  61. opentrons/protocol_api/core/labware.py +13 -4
  62. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
  63. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  65. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
  67. opentrons/protocol_api/core/protocol.py +34 -1
  68. opentrons/protocol_api/core/robot.py +51 -0
  69. opentrons/protocol_api/instrument_context.py +158 -44
  70. opentrons/protocol_api/labware.py +231 -7
  71. opentrons/protocol_api/module_contexts.py +21 -17
  72. opentrons/protocol_api/protocol_context.py +125 -4
  73. opentrons/protocol_api/robot_context.py +204 -32
  74. opentrons/protocol_api/validation.py +262 -3
  75. opentrons/protocol_engine/__init__.py +4 -0
  76. opentrons/protocol_engine/actions/actions.py +2 -3
  77. opentrons/protocol_engine/clients/sync_client.py +18 -0
  78. opentrons/protocol_engine/commands/__init__.py +81 -0
  79. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  80. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  81. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  82. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  83. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  84. opentrons/protocol_engine/commands/aspirate.py +103 -53
  85. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  86. opentrons/protocol_engine/commands/blow_out.py +44 -39
  87. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  88. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  89. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  90. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  91. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  92. opentrons/protocol_engine/commands/command.py +73 -66
  93. opentrons/protocol_engine/commands/command_unions.py +101 -1
  94. opentrons/protocol_engine/commands/comment.py +1 -1
  95. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  96. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  97. opentrons/protocol_engine/commands/custom.py +6 -12
  98. opentrons/protocol_engine/commands/dispense.py +82 -48
  99. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  100. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  101. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  102. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  103. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  104. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  112. opentrons/protocol_engine/commands/home.py +13 -4
  113. opentrons/protocol_engine/commands/liquid_probe.py +67 -24
  114. opentrons/protocol_engine/commands/load_labware.py +29 -7
  115. opentrons/protocol_engine/commands/load_lid.py +146 -0
  116. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  117. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  118. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  119. opentrons/protocol_engine/commands/load_module.py +31 -10
  120. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  121. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  122. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  123. opentrons/protocol_engine/commands/move_labware.py +19 -6
  124. opentrons/protocol_engine/commands/move_relative.py +35 -25
  125. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  126. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  127. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  128. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  129. opentrons/protocol_engine/commands/movement_common.py +338 -0
  130. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  131. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  132. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  133. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  134. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  135. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  136. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  137. opentrons/protocol_engine/commands/robot/common.py +18 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  139. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  140. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  141. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  142. opentrons/protocol_engine/commands/save_position.py +14 -5
  143. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  144. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  147. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  148. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  152. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  153. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  154. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  155. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  158. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  159. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  160. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  161. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  162. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  163. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  164. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  165. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  166. opentrons/protocol_engine/errors/__init__.py +8 -0
  167. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  168. opentrons/protocol_engine/errors/exceptions.py +50 -0
  169. opentrons/protocol_engine/execution/command_executor.py +1 -1
  170. opentrons/protocol_engine/execution/equipment.py +73 -5
  171. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  172. opentrons/protocol_engine/execution/movement.py +27 -0
  173. opentrons/protocol_engine/execution/pipetting.py +5 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  175. opentrons/protocol_engine/notes/notes.py +1 -1
  176. opentrons/protocol_engine/protocol_engine.py +7 -6
  177. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  178. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  179. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  180. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  181. opentrons/protocol_engine/slot_standardization.py +9 -9
  182. opentrons/protocol_engine/state/_move_types.py +9 -5
  183. opentrons/protocol_engine/state/_well_math.py +193 -0
  184. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  185. opentrons/protocol_engine/state/command_history.py +12 -0
  186. opentrons/protocol_engine/state/commands.py +17 -13
  187. opentrons/protocol_engine/state/files.py +10 -12
  188. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  190. opentrons/protocol_engine/state/geometry.py +47 -1
  191. opentrons/protocol_engine/state/labware.py +79 -25
  192. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  193. opentrons/protocol_engine/state/liquids.py +16 -4
  194. opentrons/protocol_engine/state/modules.py +52 -70
  195. opentrons/protocol_engine/state/motion.py +6 -1
  196. opentrons/protocol_engine/state/pipettes.py +144 -58
  197. opentrons/protocol_engine/state/state.py +21 -2
  198. opentrons/protocol_engine/state/state_summary.py +4 -2
  199. opentrons/protocol_engine/state/tips.py +11 -44
  200. opentrons/protocol_engine/state/update_types.py +343 -48
  201. opentrons/protocol_engine/state/wells.py +19 -11
  202. opentrons/protocol_engine/types.py +176 -28
  203. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  204. opentrons/protocol_reader/file_format_validator.py +5 -5
  205. opentrons/protocol_runner/json_file_reader.py +9 -3
  206. opentrons/protocol_runner/json_translator.py +51 -25
  207. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  208. opentrons/protocol_runner/protocol_runner.py +35 -4
  209. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  210. opentrons/protocol_runner/run_orchestrator.py +13 -3
  211. opentrons/protocols/advanced_control/common.py +38 -0
  212. opentrons/protocols/advanced_control/mix.py +1 -1
  213. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  214. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  215. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  216. opentrons/protocols/api_support/definitions.py +1 -1
  217. opentrons/protocols/api_support/instrument.py +1 -1
  218. opentrons/protocols/api_support/util.py +10 -0
  219. opentrons/protocols/labware.py +39 -6
  220. opentrons/protocols/models/json_protocol.py +5 -9
  221. opentrons/simulate.py +3 -1
  222. opentrons/types.py +162 -2
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  225. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
  226. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  227. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  228. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,32 @@
1
1
  """Move to well command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
4
  from typing import TYPE_CHECKING, Optional, Type
4
5
  from typing_extensions import Literal
5
6
 
6
- from ..types import DeckPoint
7
7
  from .pipetting_common import (
8
8
  PipetteIdMixin,
9
+ )
10
+ from .movement_common import (
9
11
  WellLocationMixin,
10
12
  MovementMixin,
11
13
  DestinationPositionResult,
14
+ StallOrCollisionError,
15
+ move_to_well,
16
+ )
17
+ from .command import (
18
+ AbstractCommandImpl,
19
+ BaseCommand,
20
+ BaseCommandCreate,
21
+ SuccessData,
22
+ DefinedErrorData,
12
23
  )
13
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
14
- from ..errors.error_occurrence import ErrorOccurrence
15
- from ..state import update_types
16
24
  from ..errors import LabwareIsTipRackError
17
25
 
18
26
  if TYPE_CHECKING:
19
27
  from ..execution import MovementHandler
20
28
  from ..state.state import StateView
29
+ from ..resources.model_utils import ModelUtils
21
30
 
22
31
  MoveToWellCommandType = Literal["moveToWell"]
23
32
 
@@ -35,25 +44,33 @@ class MoveToWellResult(DestinationPositionResult):
35
44
 
36
45
 
37
46
  class MoveToWellImplementation(
38
- AbstractCommandImpl[MoveToWellParams, SuccessData[MoveToWellResult]]
47
+ AbstractCommandImpl[
48
+ MoveToWellParams,
49
+ SuccessData[MoveToWellResult] | DefinedErrorData[StallOrCollisionError],
50
+ ]
39
51
  ):
40
52
  """Move to well command implementation."""
41
53
 
42
54
  def __init__(
43
- self, state_view: StateView, movement: MovementHandler, **kwargs: object
55
+ self,
56
+ state_view: StateView,
57
+ movement: MovementHandler,
58
+ model_utils: ModelUtils,
59
+ **kwargs: object,
44
60
  ) -> None:
45
61
  self._state_view = state_view
46
62
  self._movement = movement
63
+ self._model_utils = model_utils
47
64
 
48
- async def execute(self, params: MoveToWellParams) -> SuccessData[MoveToWellResult]:
65
+ async def execute(
66
+ self, params: MoveToWellParams
67
+ ) -> SuccessData[MoveToWellResult] | DefinedErrorData[StallOrCollisionError]:
49
68
  """Move the requested pipette to the requested well."""
50
69
  pipette_id = params.pipetteId
51
70
  labware_id = params.labwareId
52
71
  well_name = params.wellName
53
72
  well_location = params.wellLocation
54
73
 
55
- state_update = update_types.StateUpdate()
56
-
57
74
  if (
58
75
  self._state_view.labware.is_tiprack(labware_id)
59
76
  and well_location.volumeOffset
@@ -62,7 +79,9 @@ class MoveToWellImplementation(
62
79
  "Cannot specify a WellLocation with a volumeOffset with movement to a tip rack"
63
80
  )
64
81
 
65
- x, y, z = await self._movement.move_to_well(
82
+ move_result = await move_to_well(
83
+ model_utils=self._model_utils,
84
+ movement=self._movement,
66
85
  pipette_id=pipette_id,
67
86
  labware_id=labware_id,
68
87
  well_name=well_name,
@@ -71,26 +90,23 @@ class MoveToWellImplementation(
71
90
  minimum_z_height=params.minimumZHeight,
72
91
  speed=params.speed,
73
92
  )
74
- deck_point = DeckPoint.construct(x=x, y=y, z=z)
75
- state_update.set_pipette_location(
76
- pipette_id=pipette_id,
77
- new_labware_id=labware_id,
78
- new_well_name=well_name,
79
- new_deck_point=deck_point,
80
- )
81
-
82
- return SuccessData(
83
- public=MoveToWellResult(position=deck_point),
84
- state_update=state_update,
85
- )
93
+ if isinstance(move_result, DefinedErrorData):
94
+ return move_result
95
+ else:
96
+ return SuccessData(
97
+ public=MoveToWellResult(position=move_result.public.position),
98
+ state_update=move_result.state_update,
99
+ )
86
100
 
87
101
 
88
- class MoveToWell(BaseCommand[MoveToWellParams, MoveToWellResult, ErrorOccurrence]):
102
+ class MoveToWell(
103
+ BaseCommand[MoveToWellParams, MoveToWellResult, StallOrCollisionError]
104
+ ):
89
105
  """Move to well command model."""
90
106
 
91
107
  commandType: MoveToWellCommandType = "moveToWell"
92
108
  params: MoveToWellParams
93
- result: Optional[MoveToWellResult]
109
+ result: Optional[MoveToWellResult] = None
94
110
 
95
111
  _ImplementationCls: Type[MoveToWellImplementation] = MoveToWellImplementation
96
112
 
@@ -0,0 +1,338 @@
1
+ """Common movement base models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional, Union, TYPE_CHECKING, Literal, Any
6
+
7
+ from pydantic import BaseModel, Field
8
+ from pydantic.json_schema import SkipJsonSchema
9
+
10
+ from opentrons_shared_data.errors import ErrorCodes
11
+ from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError
12
+ from ..errors import ErrorOccurrence
13
+ from ..types import (
14
+ WellLocation,
15
+ LiquidHandlingWellLocation,
16
+ DeckPoint,
17
+ CurrentWell,
18
+ MovementAxis,
19
+ AddressableOffsetVector,
20
+ )
21
+ from ..state.update_types import StateUpdate, PipetteLocationUpdate
22
+ from .command import SuccessData, DefinedErrorData
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from ..execution.movement import MovementHandler
27
+ from ..resources.model_utils import ModelUtils
28
+
29
+
30
+ def _remove_default(s: dict[str, Any]) -> None:
31
+ s.pop("default", None)
32
+
33
+
34
+ class WellLocationMixin(BaseModel):
35
+ """Mixin for command requests that take a location that's somewhere in a well."""
36
+
37
+ labwareId: str = Field(
38
+ ...,
39
+ description="Identifier of labware to use.",
40
+ )
41
+ wellName: str = Field(
42
+ ...,
43
+ description="Name of well to use in labware.",
44
+ )
45
+ wellLocation: WellLocation = Field(
46
+ default_factory=WellLocation,
47
+ description="Relative well location at which to perform the operation",
48
+ )
49
+
50
+
51
+ class LiquidHandlingWellLocationMixin(BaseModel):
52
+ """Mixin for command requests that take a location that's somewhere in a well."""
53
+
54
+ labwareId: str = Field(
55
+ ...,
56
+ description="Identifier of labware to use.",
57
+ )
58
+ wellName: str = Field(
59
+ ...,
60
+ description="Name of well to use in labware.",
61
+ )
62
+ wellLocation: LiquidHandlingWellLocation = Field(
63
+ default_factory=LiquidHandlingWellLocation,
64
+ description="Relative well location at which to perform the operation",
65
+ )
66
+
67
+
68
+ class MovementMixin(BaseModel):
69
+ """Mixin for command requests that move a pipette."""
70
+
71
+ minimumZHeight: float | SkipJsonSchema[None] = Field(
72
+ None,
73
+ description=(
74
+ "Optional minimal Z margin in mm."
75
+ " If this is larger than the API's default safe Z margin,"
76
+ " it will make the arc higher. If it's smaller, it will have no effect."
77
+ ),
78
+ json_schema_extra=_remove_default,
79
+ )
80
+
81
+ forceDirect: bool = Field(
82
+ False,
83
+ description=(
84
+ "If true, moving from one labware/well to another"
85
+ " will not arc to the default safe z,"
86
+ " but instead will move directly to the specified location."
87
+ " This will also force the `minimumZHeight` param to be ignored."
88
+ " A 'direct' movement is in X/Y/Z simultaneously."
89
+ ),
90
+ )
91
+
92
+ speed: float | SkipJsonSchema[None] = Field(
93
+ None,
94
+ description=(
95
+ "Override the travel speed in mm/s."
96
+ " This controls the straight linear speed of motion."
97
+ ),
98
+ json_schema_extra=_remove_default,
99
+ )
100
+
101
+
102
+ class StallOrCollisionError(ErrorOccurrence):
103
+ """Returned when the machine detects that axis encoders are reading a different position than expected.
104
+
105
+ All axes are stopped at the point where the error was encountered.
106
+
107
+ The next thing to move the machine must account for the robot not having a valid estimate
108
+ of its position. It should be a `home` or `unsafe/updatePositionEstimators`.
109
+ """
110
+
111
+ isDefined: bool = True
112
+ errorType: Literal["stallOrCollision"] = "stallOrCollision"
113
+
114
+ errorCode: str = ErrorCodes.STALL_OR_COLLISION_DETECTED.value.code
115
+ detail: str = ErrorCodes.STALL_OR_COLLISION_DETECTED.value.detail
116
+
117
+
118
+ class DestinationPositionResult(BaseModel):
119
+ """Mixin for command results that move a pipette."""
120
+
121
+ # todo(mm, 2024-08-02): Consider deprecating or redefining this.
122
+ #
123
+ # This is here because opentrons.protocol_engine needed it for internal bookkeeping
124
+ # and, at the time, we didn't have a way to do that without adding this to the
125
+ # public command results. Its usefulness to callers outside
126
+ # opentrons.protocol_engine is questionable because they would need to know which
127
+ # critical point is in play, and I think that can change depending on obscure
128
+ # things like labware quirks.
129
+ position: DeckPoint = Field(
130
+ DeckPoint(x=0, y=0, z=0),
131
+ description=(
132
+ "The (x,y,z) coordinates of the pipette's critical point in deck space"
133
+ " after the move was completed."
134
+ ),
135
+ )
136
+
137
+
138
+ MoveToWellOperationReturn = (
139
+ SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]
140
+ )
141
+
142
+
143
+ async def move_to_well(
144
+ movement: MovementHandler,
145
+ model_utils: ModelUtils,
146
+ pipette_id: str,
147
+ labware_id: str,
148
+ well_name: str,
149
+ well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]] = None,
150
+ current_well: Optional[CurrentWell] = None,
151
+ force_direct: bool = False,
152
+ minimum_z_height: Optional[float] = None,
153
+ speed: Optional[float] = None,
154
+ operation_volume: Optional[float] = None,
155
+ ) -> MoveToWellOperationReturn:
156
+ """Execute a move to well microoperation."""
157
+ try:
158
+ position = await movement.move_to_well(
159
+ pipette_id=pipette_id,
160
+ labware_id=labware_id,
161
+ well_name=well_name,
162
+ well_location=well_location,
163
+ current_well=current_well,
164
+ force_direct=force_direct,
165
+ minimum_z_height=minimum_z_height,
166
+ speed=speed,
167
+ operation_volume=operation_volume,
168
+ )
169
+ except StallOrCollisionDetectedError as e:
170
+ return DefinedErrorData(
171
+ public=StallOrCollisionError(
172
+ id=model_utils.generate_id(),
173
+ createdAt=model_utils.get_timestamp(),
174
+ wrappedErrors=[
175
+ ErrorOccurrence.from_failed(
176
+ id=model_utils.generate_id(),
177
+ createdAt=model_utils.get_timestamp(),
178
+ error=e,
179
+ )
180
+ ],
181
+ ),
182
+ state_update=StateUpdate().clear_all_pipette_locations(),
183
+ )
184
+ else:
185
+ deck_point = DeckPoint.model_construct(x=position.x, y=position.y, z=position.z)
186
+ return SuccessData(
187
+ public=DestinationPositionResult(
188
+ position=deck_point,
189
+ ),
190
+ state_update=StateUpdate().set_pipette_location(
191
+ pipette_id=pipette_id,
192
+ new_labware_id=labware_id,
193
+ new_well_name=well_name,
194
+ new_deck_point=deck_point,
195
+ ),
196
+ )
197
+
198
+
199
+ async def move_relative(
200
+ movement: MovementHandler,
201
+ model_utils: ModelUtils,
202
+ pipette_id: str,
203
+ axis: MovementAxis,
204
+ distance: float,
205
+ ) -> SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]:
206
+ """Move by a fixed displacement from the current position."""
207
+ try:
208
+ position = await movement.move_relative(pipette_id, axis, distance)
209
+ except StallOrCollisionDetectedError as e:
210
+ return DefinedErrorData(
211
+ public=StallOrCollisionError(
212
+ id=model_utils.generate_id(),
213
+ createdAt=model_utils.get_timestamp(),
214
+ wrappedErrors=[
215
+ ErrorOccurrence.from_failed(
216
+ id=model_utils.generate_id(),
217
+ createdAt=model_utils.get_timestamp(),
218
+ error=e,
219
+ )
220
+ ],
221
+ ),
222
+ state_update=StateUpdate().clear_all_pipette_locations(),
223
+ )
224
+ else:
225
+ deck_point = DeckPoint.model_construct(x=position.x, y=position.y, z=position.z)
226
+ return SuccessData(
227
+ public=DestinationPositionResult(
228
+ position=deck_point,
229
+ ),
230
+ state_update=StateUpdate().set_pipette_location(
231
+ pipette_id=pipette_id, new_deck_point=deck_point
232
+ ),
233
+ )
234
+
235
+
236
+ async def move_to_addressable_area(
237
+ movement: MovementHandler,
238
+ model_utils: ModelUtils,
239
+ pipette_id: str,
240
+ addressable_area_name: str,
241
+ offset: AddressableOffsetVector,
242
+ force_direct: bool = False,
243
+ minimum_z_height: float | None = None,
244
+ speed: float | None = None,
245
+ stay_at_highest_possible_z: bool = False,
246
+ ignore_tip_configuration: bool | None = True,
247
+ highest_possible_z_extra_offset: float | None = None,
248
+ ) -> SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]:
249
+ """Move to an addressable area identified by name."""
250
+ try:
251
+ x, y, z = await movement.move_to_addressable_area(
252
+ pipette_id=pipette_id,
253
+ addressable_area_name=addressable_area_name,
254
+ offset=offset,
255
+ force_direct=force_direct,
256
+ minimum_z_height=minimum_z_height,
257
+ speed=speed,
258
+ stay_at_highest_possible_z=stay_at_highest_possible_z,
259
+ ignore_tip_configuration=ignore_tip_configuration,
260
+ highest_possible_z_extra_offset=highest_possible_z_extra_offset,
261
+ )
262
+ except StallOrCollisionDetectedError as e:
263
+ return DefinedErrorData(
264
+ public=StallOrCollisionError(
265
+ id=model_utils.generate_id(),
266
+ createdAt=model_utils.get_timestamp(),
267
+ wrappedErrors=[
268
+ ErrorOccurrence.from_failed(
269
+ id=model_utils.generate_id(),
270
+ createdAt=model_utils.get_timestamp(),
271
+ error=e,
272
+ )
273
+ ],
274
+ ),
275
+ state_update=StateUpdate()
276
+ .clear_all_pipette_locations()
277
+ .set_addressable_area_used(addressable_area_name=addressable_area_name),
278
+ )
279
+ else:
280
+ deck_point = DeckPoint.model_construct(x=x, y=y, z=z)
281
+ return SuccessData(
282
+ public=DestinationPositionResult(position=deck_point),
283
+ state_update=StateUpdate()
284
+ .set_pipette_location(
285
+ pipette_id=pipette_id,
286
+ new_addressable_area_name=addressable_area_name,
287
+ new_deck_point=deck_point,
288
+ )
289
+ .set_addressable_area_used(addressable_area_name=addressable_area_name),
290
+ )
291
+
292
+
293
+ async def move_to_coordinates(
294
+ movement: MovementHandler,
295
+ model_utils: ModelUtils,
296
+ pipette_id: str,
297
+ deck_coordinates: DeckPoint,
298
+ direct: bool,
299
+ additional_min_travel_z: float | None,
300
+ speed: float | None = None,
301
+ ) -> SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]:
302
+ """Move to a set of coordinates."""
303
+ try:
304
+ x, y, z = await movement.move_to_coordinates(
305
+ pipette_id=pipette_id,
306
+ deck_coordinates=deck_coordinates,
307
+ direct=direct,
308
+ additional_min_travel_z=additional_min_travel_z,
309
+ speed=speed,
310
+ )
311
+ except StallOrCollisionDetectedError as e:
312
+ return DefinedErrorData(
313
+ public=StallOrCollisionError(
314
+ id=model_utils.generate_id(),
315
+ createdAt=model_utils.get_timestamp(),
316
+ wrappedErrors=[
317
+ ErrorOccurrence.from_failed(
318
+ id=model_utils.generate_id(),
319
+ createdAt=model_utils.get_timestamp(),
320
+ error=e,
321
+ )
322
+ ],
323
+ ),
324
+ state_update=StateUpdate().clear_all_pipette_locations(),
325
+ )
326
+ else:
327
+ deck_point = DeckPoint.model_construct(x=x, y=y, z=z)
328
+
329
+ return SuccessData(
330
+ public=DestinationPositionResult(position=DeckPoint(x=x, y=y, z=z)),
331
+ state_update=StateUpdate(
332
+ pipette_location=PipetteLocationUpdate(
333
+ pipette_id=pipette_id,
334
+ new_location=None,
335
+ new_deck_point=deck_point,
336
+ )
337
+ ),
338
+ )
@@ -1,4 +1,5 @@
1
1
  """Pick up tip command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
4
  from opentrons_shared_data.errors import ErrorCodes
4
5
  from pydantic import Field
@@ -9,10 +10,14 @@ from typing_extensions import Literal
9
10
  from ..errors import ErrorOccurrence, PickUpTipTipNotAttachedError
10
11
  from ..resources import ModelUtils
11
12
  from ..state import update_types
12
- from ..types import PickUpTipWellLocation, DeckPoint
13
+ from ..types import PickUpTipWellLocation
13
14
  from .pipetting_common import (
14
15
  PipetteIdMixin,
16
+ )
17
+ from .movement_common import (
15
18
  DestinationPositionResult,
19
+ StallOrCollisionError,
20
+ move_to_well,
16
21
  )
17
22
  from .command import (
18
23
  AbstractCommandImpl,
@@ -87,7 +92,8 @@ class TipPhysicallyMissingError(ErrorOccurrence):
87
92
 
88
93
  _ExecuteReturn = Union[
89
94
  SuccessData[PickUpTipResult],
90
- DefinedErrorData[TipPhysicallyMissingError],
95
+ DefinedErrorData[TipPhysicallyMissingError]
96
+ | DefinedErrorData[StallOrCollisionError],
91
97
  ]
92
98
 
93
99
 
@@ -115,24 +121,19 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
115
121
  labware_id = params.labwareId
116
122
  well_name = params.wellName
117
123
 
118
- state_update = update_types.StateUpdate()
119
-
120
124
  well_location = self._state_view.geometry.convert_pick_up_tip_well_location(
121
125
  well_location=params.wellLocation
122
126
  )
123
- position = await self._movement.move_to_well(
127
+ move_result = await move_to_well(
128
+ movement=self._movement,
129
+ model_utils=self._model_utils,
124
130
  pipette_id=pipette_id,
125
131
  labware_id=labware_id,
126
132
  well_name=well_name,
127
133
  well_location=well_location,
128
134
  )
129
- deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
130
- state_update.set_pipette_location(
131
- pipette_id=pipette_id,
132
- new_labware_id=labware_id,
133
- new_well_name=well_name,
134
- new_deck_point=deck_point,
135
- )
135
+ if isinstance(move_result, DefinedErrorData):
136
+ return move_result
136
137
 
137
138
  try:
138
139
  tip_geometry = await self._tip_handler.pick_up_tip(
@@ -141,13 +142,27 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
141
142
  well_name=well_name,
142
143
  )
143
144
  except PickUpTipTipNotAttachedError as e:
144
- state_update_if_false_positive = update_types.StateUpdate()
145
- state_update_if_false_positive.update_pipette_tip_state(
146
- pipette_id=pipette_id,
147
- tip_geometry=e.tip_geometry,
145
+ state_update_if_false_positive = (
146
+ update_types.StateUpdate.reduce(
147
+ update_types.StateUpdate(), move_result.state_update
148
+ )
149
+ .update_pipette_tip_state(
150
+ pipette_id=pipette_id,
151
+ tip_geometry=e.tip_geometry,
152
+ )
153
+ .set_fluid_empty(pipette_id=pipette_id)
154
+ .mark_tips_as_used(
155
+ pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
156
+ )
148
157
  )
149
- state_update.mark_tips_as_used(
150
- pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
158
+ state_update = (
159
+ update_types.StateUpdate.reduce(
160
+ update_types.StateUpdate(), move_result.state_update
161
+ )
162
+ .mark_tips_as_used(
163
+ pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
164
+ )
165
+ .set_fluid_unknown(pipette_id=pipette_id)
151
166
  )
152
167
  return DefinedErrorData(
153
168
  public=TipPhysicallyMissingError(
@@ -165,32 +180,39 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
165
180
  state_update_if_false_positive=state_update_if_false_positive,
166
181
  )
167
182
  else:
168
- state_update.update_pipette_tip_state(
169
- pipette_id=pipette_id,
170
- tip_geometry=tip_geometry,
171
- )
172
- state_update.mark_tips_as_used(
173
- pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
183
+ state_update = (
184
+ move_result.state_update.update_pipette_tip_state(
185
+ pipette_id=pipette_id,
186
+ tip_geometry=tip_geometry,
187
+ )
188
+ .mark_tips_as_used(
189
+ pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
190
+ )
191
+ .set_fluid_empty(pipette_id=pipette_id)
174
192
  )
175
193
  return SuccessData(
176
194
  public=PickUpTipResult(
177
195
  tipVolume=tip_geometry.volume,
178
196
  tipLength=tip_geometry.length,
179
197
  tipDiameter=tip_geometry.diameter,
180
- position=deck_point,
198
+ position=move_result.public.position,
181
199
  ),
182
200
  state_update=state_update,
183
201
  )
184
202
 
185
203
 
186
204
  class PickUpTip(
187
- BaseCommand[PickUpTipParams, PickUpTipResult, TipPhysicallyMissingError]
205
+ BaseCommand[
206
+ PickUpTipParams,
207
+ PickUpTipResult,
208
+ TipPhysicallyMissingError | StallOrCollisionError,
209
+ ]
188
210
  ):
189
211
  """Pick up tip command model."""
190
212
 
191
213
  commandType: PickUpTipCommandType = "pickUpTip"
192
214
  params: PickUpTipParams
193
- result: Optional[PickUpTipResult]
215
+ result: Optional[PickUpTipResult] = None
194
216
 
195
217
  _ImplementationCls: Type[PickUpTipImplementation] = PickUpTipImplementation
196
218