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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -6,14 +6,25 @@ from typing import Optional, Type, TYPE_CHECKING
6
6
  from typing_extensions import Literal
7
7
 
8
8
 
9
- from ..state import update_types
10
9
  from ..types import DeckPoint
11
- from .pipetting_common import PipetteIdMixin, MovementMixin, DestinationPositionResult
12
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
13
- from ..errors.error_occurrence import ErrorOccurrence
10
+ from .pipetting_common import PipetteIdMixin
11
+ from .movement_common import (
12
+ MovementMixin,
13
+ DestinationPositionResult,
14
+ move_to_coordinates,
15
+ StallOrCollisionError,
16
+ )
17
+ from .command import (
18
+ AbstractCommandImpl,
19
+ BaseCommand,
20
+ BaseCommandCreate,
21
+ SuccessData,
22
+ DefinedErrorData,
23
+ )
14
24
 
15
25
  if TYPE_CHECKING:
16
26
  from ..execution import MovementHandler
27
+ from ..resources.model_utils import ModelUtils
17
28
 
18
29
 
19
30
  MoveToCoordinatesCommandType = Literal["moveToCoordinates"]
@@ -34,50 +45,53 @@ class MoveToCoordinatesResult(DestinationPositionResult):
34
45
  pass
35
46
 
36
47
 
48
+ _ExecuteReturn = (
49
+ SuccessData[MoveToCoordinatesResult] | DefinedErrorData[StallOrCollisionError]
50
+ )
51
+
52
+
37
53
  class MoveToCoordinatesImplementation(
38
- AbstractCommandImpl[MoveToCoordinatesParams, SuccessData[MoveToCoordinatesResult]]
54
+ AbstractCommandImpl[MoveToCoordinatesParams, _ExecuteReturn]
39
55
  ):
40
56
  """Move to coordinates command implementation."""
41
57
 
42
58
  def __init__(
43
59
  self,
44
60
  movement: MovementHandler,
61
+ model_utils: ModelUtils,
45
62
  **kwargs: object,
46
63
  ) -> None:
47
64
  self._movement = movement
65
+ self._model_utils = model_utils
48
66
 
49
- async def execute(
50
- self, params: MoveToCoordinatesParams
51
- ) -> SuccessData[MoveToCoordinatesResult]:
67
+ async def execute(self, params: MoveToCoordinatesParams) -> _ExecuteReturn:
52
68
  """Move the requested pipette to the requested coordinates."""
53
- state_update = update_types.StateUpdate()
54
-
55
- x, y, z = await self._movement.move_to_coordinates(
69
+ result = await move_to_coordinates(
70
+ movement=self._movement,
71
+ model_utils=self._model_utils,
56
72
  pipette_id=params.pipetteId,
57
73
  deck_coordinates=params.coordinates,
58
74
  direct=params.forceDirect,
59
75
  additional_min_travel_z=params.minimumZHeight,
60
76
  speed=params.speed,
61
77
  )
62
- deck_point = DeckPoint.construct(x=x, y=y, z=z)
63
- state_update.pipette_location = update_types.PipetteLocationUpdate(
64
- pipette_id=params.pipetteId, new_location=None, new_deck_point=deck_point
65
- )
66
-
67
- return SuccessData(
68
- public=MoveToCoordinatesResult(position=DeckPoint(x=x, y=y, z=z)),
69
- state_update=state_update,
70
- )
78
+ if isinstance(result, DefinedErrorData):
79
+ return result
80
+ else:
81
+ return SuccessData(
82
+ public=MoveToCoordinatesResult(position=result.public.position),
83
+ state_update=result.state_update,
84
+ )
71
85
 
72
86
 
73
87
  class MoveToCoordinates(
74
- BaseCommand[MoveToCoordinatesParams, MoveToCoordinatesResult, ErrorOccurrence]
88
+ BaseCommand[MoveToCoordinatesParams, MoveToCoordinatesResult, StallOrCollisionError]
75
89
  ):
76
90
  """Move to well command model."""
77
91
 
78
92
  commandType: MoveToCoordinatesCommandType = "moveToCoordinates"
79
93
  params: MoveToCoordinatesParams
80
- result: Optional[MoveToCoordinatesResult]
94
+ result: Optional[MoveToCoordinatesResult] = None
81
95
 
82
96
  _ImplementationCls: Type[
83
97
  MoveToCoordinatesImplementation
@@ -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
+ )