opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__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 (230) hide show
  1. opentrons/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  20. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  21. opentrons/hardware_control/module_control.py +43 -2
  22. opentrons/hardware_control/modules/__init__.py +7 -1
  23. opentrons/hardware_control/modules/absorbance_reader.py +230 -83
  24. opentrons/hardware_control/modules/errors.py +7 -0
  25. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  26. opentrons/hardware_control/modules/magdeck.py +12 -3
  27. opentrons/hardware_control/modules/mod_abc.py +27 -2
  28. opentrons/hardware_control/modules/tempdeck.py +15 -7
  29. opentrons/hardware_control/modules/thermocycler.py +69 -3
  30. opentrons/hardware_control/modules/types.py +11 -5
  31. opentrons/hardware_control/modules/update.py +11 -5
  32. opentrons/hardware_control/modules/utils.py +3 -1
  33. opentrons/hardware_control/ot3_calibration.py +6 -6
  34. opentrons/hardware_control/ot3api.py +126 -89
  35. opentrons/hardware_control/poller.py +15 -11
  36. opentrons/hardware_control/protocols/__init__.py +1 -7
  37. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  38. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  39. opentrons/motion_planning/__init__.py +2 -0
  40. opentrons/motion_planning/waypoints.py +32 -0
  41. opentrons/protocol_api/__init__.py +2 -1
  42. opentrons/protocol_api/_liquid.py +87 -1
  43. opentrons/protocol_api/_parameter_context.py +10 -1
  44. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  45. opentrons/protocol_api/core/engine/instrument.py +29 -25
  46. opentrons/protocol_api/core/engine/labware.py +10 -2
  47. opentrons/protocol_api/core/engine/module_core.py +129 -17
  48. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
  49. opentrons/protocol_api/core/engine/protocol.py +55 -2
  50. opentrons/protocol_api/core/instrument.py +2 -0
  51. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  52. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
  53. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  54. opentrons/protocol_api/core/module.py +22 -4
  55. opentrons/protocol_api/core/protocol.py +5 -2
  56. opentrons/protocol_api/instrument_context.py +52 -20
  57. opentrons/protocol_api/labware.py +13 -1
  58. opentrons/protocol_api/module_contexts.py +68 -13
  59. opentrons/protocol_api/protocol_context.py +38 -4
  60. opentrons/protocol_api/validation.py +5 -3
  61. opentrons/protocol_engine/__init__.py +10 -9
  62. opentrons/protocol_engine/actions/__init__.py +5 -0
  63. opentrons/protocol_engine/actions/actions.py +42 -25
  64. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  65. opentrons/protocol_engine/clients/sync_client.py +7 -1
  66. opentrons/protocol_engine/clients/transports.py +1 -1
  67. opentrons/protocol_engine/commands/__init__.py +0 -4
  68. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  69. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
  70. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
  71. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
  72. opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
  73. opentrons/protocol_engine/commands/aspirate.py +29 -16
  74. opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
  75. opentrons/protocol_engine/commands/blow_out.py +63 -14
  76. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  77. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  78. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  79. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  80. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  81. opentrons/protocol_engine/commands/command.py +28 -17
  82. opentrons/protocol_engine/commands/command_unions.py +37 -24
  83. opentrons/protocol_engine/commands/comment.py +5 -3
  84. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  85. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  86. opentrons/protocol_engine/commands/custom.py +5 -3
  87. opentrons/protocol_engine/commands/dispense.py +42 -20
  88. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  89. opentrons/protocol_engine/commands/drop_tip.py +68 -15
  90. opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
  91. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  92. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  93. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  94. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  95. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  96. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  97. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  98. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  99. opentrons/protocol_engine/commands/home.py +11 -5
  100. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  101. opentrons/protocol_engine/commands/load_labware.py +19 -5
  102. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  103. opentrons/protocol_engine/commands/load_module.py +43 -6
  104. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  105. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  106. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  107. opentrons/protocol_engine/commands/move_labware.py +106 -19
  108. opentrons/protocol_engine/commands/move_relative.py +15 -3
  109. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  110. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  111. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  112. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  113. opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
  114. opentrons/protocol_engine/commands/pipetting_common.py +39 -15
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  116. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  117. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  118. opentrons/protocol_engine/commands/save_position.py +2 -3
  119. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  120. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  121. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  122. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  123. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  124. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  125. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  126. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  127. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  128. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  129. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  130. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  131. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  132. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  133. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  135. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  136. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  137. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  138. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  139. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  140. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
  141. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
  142. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
  143. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  144. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  145. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  146. opentrons/protocol_engine/create_protocol_engine.py +41 -8
  147. opentrons/protocol_engine/engine_support.py +2 -1
  148. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  149. opentrons/protocol_engine/errors/__init__.py +18 -0
  150. opentrons/protocol_engine/errors/exceptions.py +114 -2
  151. opentrons/protocol_engine/execution/__init__.py +2 -0
  152. opentrons/protocol_engine/execution/command_executor.py +22 -13
  153. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  154. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  155. opentrons/protocol_engine/execution/equipment.py +2 -1
  156. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  157. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  158. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  159. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  160. opentrons/protocol_engine/execution/labware_movement.py +6 -3
  161. opentrons/protocol_engine/execution/movement.py +8 -3
  162. opentrons/protocol_engine/execution/pipetting.py +7 -4
  163. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  164. opentrons/protocol_engine/execution/run_control.py +1 -1
  165. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  166. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  167. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  168. opentrons/protocol_engine/notes/__init__.py +14 -2
  169. opentrons/protocol_engine/notes/notes.py +18 -1
  170. opentrons/protocol_engine/plugins.py +1 -1
  171. opentrons/protocol_engine/protocol_engine.py +54 -31
  172. opentrons/protocol_engine/resources/__init__.py +2 -0
  173. opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
  174. opentrons/protocol_engine/resources/file_provider.py +157 -0
  175. opentrons/protocol_engine/resources/fixture_validation.py +5 -0
  176. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  177. opentrons/protocol_engine/state/__init__.py +0 -70
  178. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  179. opentrons/protocol_engine/state/command_history.py +21 -2
  180. opentrons/protocol_engine/state/commands.py +110 -31
  181. opentrons/protocol_engine/state/files.py +59 -0
  182. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  183. opentrons/protocol_engine/state/geometry.py +359 -15
  184. opentrons/protocol_engine/state/labware.py +166 -63
  185. opentrons/protocol_engine/state/liquids.py +1 -1
  186. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
  187. opentrons/protocol_engine/state/modules.py +167 -85
  188. opentrons/protocol_engine/state/motion.py +16 -9
  189. opentrons/protocol_engine/state/pipettes.py +157 -317
  190. opentrons/protocol_engine/state/state.py +30 -1
  191. opentrons/protocol_engine/state/state_summary.py +3 -0
  192. opentrons/protocol_engine/state/tips.py +69 -114
  193. opentrons/protocol_engine/state/update_types.py +408 -0
  194. opentrons/protocol_engine/state/wells.py +236 -0
  195. opentrons/protocol_engine/types.py +90 -0
  196. opentrons/protocol_reader/file_format_validator.py +83 -15
  197. opentrons/protocol_runner/json_translator.py +21 -5
  198. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  199. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  200. opentrons/protocol_runner/protocol_runner.py +6 -3
  201. opentrons/protocol_runner/run_orchestrator.py +26 -6
  202. opentrons/protocols/advanced_control/mix.py +3 -5
  203. opentrons/protocols/advanced_control/transfers.py +125 -56
  204. opentrons/protocols/api_support/constants.py +1 -1
  205. opentrons/protocols/api_support/definitions.py +1 -1
  206. opentrons/protocols/api_support/labware_like.py +4 -4
  207. opentrons/protocols/api_support/tip_tracker.py +2 -2
  208. opentrons/protocols/api_support/types.py +15 -2
  209. opentrons/protocols/api_support/util.py +30 -42
  210. opentrons/protocols/duration/errors.py +1 -1
  211. opentrons/protocols/duration/estimator.py +50 -29
  212. opentrons/protocols/execution/dev_types.py +2 -2
  213. opentrons/protocols/execution/execute_json_v4.py +15 -10
  214. opentrons/protocols/execution/execute_python.py +8 -3
  215. opentrons/protocols/geometry/planning.py +12 -12
  216. opentrons/protocols/labware.py +17 -33
  217. opentrons/simulate.py +3 -3
  218. opentrons/types.py +30 -3
  219. opentrons/util/logging_config.py +34 -0
  220. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
  221. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
  222. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  223. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  224. opentrons/protocol_runner/thread_async_queue.py +0 -174
  225. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  226. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  227. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
  230. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,8 @@ from pydantic import Field
5
5
  from typing import Optional, Type, TYPE_CHECKING
6
6
  from typing_extensions import Literal
7
7
 
8
+
9
+ from ..state import update_types
8
10
  from ..types import DeckPoint
9
11
  from .pipetting_common import PipetteIdMixin, MovementMixin, DestinationPositionResult
10
12
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
@@ -33,9 +35,7 @@ class MoveToCoordinatesResult(DestinationPositionResult):
33
35
 
34
36
 
35
37
  class MoveToCoordinatesImplementation(
36
- AbstractCommandImpl[
37
- MoveToCoordinatesParams, SuccessData[MoveToCoordinatesResult, None]
38
- ]
38
+ AbstractCommandImpl[MoveToCoordinatesParams, SuccessData[MoveToCoordinatesResult]]
39
39
  ):
40
40
  """Move to coordinates command implementation."""
41
41
 
@@ -48,8 +48,10 @@ class MoveToCoordinatesImplementation(
48
48
 
49
49
  async def execute(
50
50
  self, params: MoveToCoordinatesParams
51
- ) -> SuccessData[MoveToCoordinatesResult, None]:
51
+ ) -> SuccessData[MoveToCoordinatesResult]:
52
52
  """Move the requested pipette to the requested coordinates."""
53
+ state_update = update_types.StateUpdate()
54
+
53
55
  x, y, z = await self._movement.move_to_coordinates(
54
56
  pipette_id=params.pipetteId,
55
57
  deck_coordinates=params.coordinates,
@@ -57,10 +59,14 @@ class MoveToCoordinatesImplementation(
57
59
  additional_min_travel_z=params.minimumZHeight,
58
60
  speed=params.speed,
59
61
  )
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
+ )
60
66
 
61
67
  return SuccessData(
62
68
  public=MoveToCoordinatesResult(position=DeckPoint(x=x, y=y, z=z)),
63
- private=None,
69
+ state_update=state_update,
64
70
  )
65
71
 
66
72
 
@@ -12,9 +12,12 @@ from .pipetting_common import (
12
12
  )
13
13
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
14
14
  from ..errors.error_occurrence import ErrorOccurrence
15
+ from ..state import update_types
16
+ from ..errors import LabwareIsTipRackError
15
17
 
16
18
  if TYPE_CHECKING:
17
19
  from ..execution import MovementHandler
20
+ from ..state.state import StateView
18
21
 
19
22
  MoveToWellCommandType = Literal["moveToWell"]
20
23
 
@@ -32,29 +35,53 @@ class MoveToWellResult(DestinationPositionResult):
32
35
 
33
36
 
34
37
  class MoveToWellImplementation(
35
- AbstractCommandImpl[MoveToWellParams, SuccessData[MoveToWellResult, None]]
38
+ AbstractCommandImpl[MoveToWellParams, SuccessData[MoveToWellResult]]
36
39
  ):
37
40
  """Move to well command implementation."""
38
41
 
39
- def __init__(self, movement: MovementHandler, **kwargs: object) -> None:
42
+ def __init__(
43
+ self, state_view: StateView, movement: MovementHandler, **kwargs: object
44
+ ) -> None:
45
+ self._state_view = state_view
40
46
  self._movement = movement
41
47
 
42
- async def execute(
43
- self, params: MoveToWellParams
44
- ) -> SuccessData[MoveToWellResult, None]:
48
+ async def execute(self, params: MoveToWellParams) -> SuccessData[MoveToWellResult]:
45
49
  """Move the requested pipette to the requested well."""
50
+ pipette_id = params.pipetteId
51
+ labware_id = params.labwareId
52
+ well_name = params.wellName
53
+ well_location = params.wellLocation
54
+
55
+ state_update = update_types.StateUpdate()
56
+
57
+ if (
58
+ self._state_view.labware.is_tiprack(labware_id)
59
+ and well_location.volumeOffset
60
+ ):
61
+ raise LabwareIsTipRackError(
62
+ "Cannot specify a WellLocation with a volumeOffset with movement to a tip rack"
63
+ )
64
+
46
65
  x, y, z = await self._movement.move_to_well(
47
- pipette_id=params.pipetteId,
48
- labware_id=params.labwareId,
49
- well_name=params.wellName,
50
- well_location=params.wellLocation,
66
+ pipette_id=pipette_id,
67
+ labware_id=labware_id,
68
+ well_name=well_name,
69
+ well_location=well_location,
51
70
  force_direct=params.forceDirect,
52
71
  minimum_z_height=params.minimumZHeight,
53
72
  speed=params.speed,
54
73
  )
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
+ )
55
81
 
56
82
  return SuccessData(
57
- public=MoveToWellResult(position=DeckPoint(x=x, y=y, z=z)), private=None
83
+ public=MoveToWellResult(position=deck_point),
84
+ state_update=state_update,
58
85
  )
59
86
 
60
87
 
@@ -1,19 +1,17 @@
1
1
  """Pick up tip command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
- from dataclasses import dataclass
4
3
  from opentrons_shared_data.errors import ErrorCodes
5
4
  from pydantic import Field
6
5
  from typing import TYPE_CHECKING, Optional, Type, Union
7
6
  from typing_extensions import Literal
8
7
 
9
- from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
10
8
 
11
- from ..errors import ErrorOccurrence
9
+ from ..errors import ErrorOccurrence, PickUpTipTipNotAttachedError
12
10
  from ..resources import ModelUtils
13
- from ..types import DeckPoint
11
+ from ..state import update_types
12
+ from ..types import PickUpTipWellLocation, DeckPoint
14
13
  from .pipetting_common import (
15
14
  PipetteIdMixin,
16
- WellLocationMixin,
17
15
  DestinationPositionResult,
18
16
  )
19
17
  from .command import (
@@ -25,17 +23,22 @@ from .command import (
25
23
  )
26
24
 
27
25
  if TYPE_CHECKING:
28
- from ..state import StateView
26
+ from ..state.state import StateView
29
27
  from ..execution import MovementHandler, TipHandler
30
28
 
31
29
 
32
30
  PickUpTipCommandType = Literal["pickUpTip"]
33
31
 
34
32
 
35
- class PickUpTipParams(PipetteIdMixin, WellLocationMixin):
33
+ class PickUpTipParams(PipetteIdMixin):
36
34
  """Payload needed to move a pipette to a specific well."""
37
35
 
38
- pass
36
+ labwareId: str = Field(..., description="Identifier of labware to use.")
37
+ wellName: str = Field(..., description="Name of well to use in labware.")
38
+ wellLocation: PickUpTipWellLocation = Field(
39
+ default_factory=PickUpTipWellLocation,
40
+ description="Relative well location at which to pick up the tip.",
41
+ )
39
42
 
40
43
 
41
44
  class PickUpTipResult(DestinationPositionResult):
@@ -72,24 +75,19 @@ class TipPhysicallyMissingError(ErrorOccurrence):
72
75
  of the pipette.
73
76
  """
74
77
 
78
+ # The thing above about marking the tips as used makes it so that
79
+ # when the protocol is resumed and the Python Protocol API calls
80
+ # `get_next_tip()`, we'll move on to other tips as expected.
81
+
75
82
  isDefined: bool = True
76
83
  errorType: Literal["tipPhysicallyMissing"] = "tipPhysicallyMissing"
77
84
  errorCode: str = ErrorCodes.TIP_PICKUP_FAILED.value.code
78
85
  detail: str = "No tip detected."
79
86
 
80
87
 
81
- @dataclass(frozen=True)
82
- class TipPhysicallyMissingErrorInternalData:
83
- """Internal-to-ProtocolEngine data about a TipPhysicallyMissingError."""
84
-
85
- pipette_id: str
86
- labware_id: str
87
- well_name: str
88
-
89
-
90
88
  _ExecuteReturn = Union[
91
- SuccessData[PickUpTipResult, None],
92
- DefinedErrorData[TipPhysicallyMissingError, TipPhysicallyMissingErrorInternalData],
89
+ SuccessData[PickUpTipResult],
90
+ DefinedErrorData[TipPhysicallyMissingError],
93
91
  ]
94
92
 
95
93
 
@@ -111,19 +109,30 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
111
109
 
112
110
  async def execute(
113
111
  self, params: PickUpTipParams
114
- ) -> Union[SuccessData[PickUpTipResult, None], _ExecuteReturn]:
112
+ ) -> Union[SuccessData[PickUpTipResult], _ExecuteReturn]:
115
113
  """Move to and pick up a tip using the requested pipette."""
116
114
  pipette_id = params.pipetteId
117
115
  labware_id = params.labwareId
118
116
  well_name = params.wellName
119
- well_location = params.wellLocation
120
117
 
118
+ state_update = update_types.StateUpdate()
119
+
120
+ well_location = self._state_view.geometry.convert_pick_up_tip_well_location(
121
+ well_location=params.wellLocation
122
+ )
121
123
  position = await self._movement.move_to_well(
122
124
  pipette_id=pipette_id,
123
125
  labware_id=labware_id,
124
126
  well_name=well_name,
125
127
  well_location=well_location,
126
128
  )
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
+ )
127
136
 
128
137
  try:
129
138
  tip_geometry = await self._tip_handler.pick_up_tip(
@@ -131,7 +140,15 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
131
140
  labware_id=labware_id,
132
141
  well_name=well_name,
133
142
  )
134
- except TipNotAttachedError as e:
143
+ 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,
148
+ )
149
+ state_update.mark_tips_as_used(
150
+ pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
151
+ )
135
152
  return DefinedErrorData(
136
153
  public=TipPhysicallyMissingError(
137
154
  id=self._model_utils.generate_id(),
@@ -144,21 +161,25 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
144
161
  )
145
162
  ],
146
163
  ),
147
- private=TipPhysicallyMissingErrorInternalData(
148
- pipette_id=pipette_id,
149
- labware_id=labware_id,
150
- well_name=well_name,
151
- ),
164
+ state_update=state_update,
165
+ state_update_if_false_positive=state_update_if_false_positive,
152
166
  )
153
167
  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
174
+ )
154
175
  return SuccessData(
155
176
  public=PickUpTipResult(
156
177
  tipVolume=tip_geometry.volume,
157
178
  tipLength=tip_geometry.length,
158
179
  tipDiameter=tip_geometry.diameter,
159
- position=DeckPoint(x=position.x, y=position.y, z=position.z),
180
+ position=deck_point,
160
181
  ),
161
- private=None,
182
+ state_update=state_update,
162
183
  )
163
184
 
164
185
 
@@ -1,12 +1,11 @@
1
1
  """Common pipetting command base models."""
2
- from dataclasses import dataclass
3
2
  from opentrons_shared_data.errors import ErrorCodes
4
3
  from pydantic import BaseModel, Field
5
4
  from typing import Literal, Optional, Tuple, TypedDict
6
5
 
7
6
  from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence
8
7
 
9
- from ..types import WellLocation, DeckPoint
8
+ from ..types import WellLocation, LiquidHandlingWellLocation, DeckPoint
10
9
 
11
10
 
12
11
  class PipetteIdMixin(BaseModel):
@@ -69,6 +68,23 @@ class WellLocationMixin(BaseModel):
69
68
  )
70
69
 
71
70
 
71
+ class LiquidHandlingWellLocationMixin(BaseModel):
72
+ """Mixin for command requests that take a location that's somewhere in a well."""
73
+
74
+ labwareId: str = Field(
75
+ ...,
76
+ description="Identifier of labware to use.",
77
+ )
78
+ wellName: str = Field(
79
+ ...,
80
+ description="Name of well to use in labware.",
81
+ )
82
+ wellLocation: LiquidHandlingWellLocation = Field(
83
+ default_factory=LiquidHandlingWellLocation,
84
+ description="Relative well location at which to perform the operation",
85
+ )
86
+
87
+
72
88
  class MovementMixin(BaseModel):
73
89
  """Mixin for command requests that move a pipette."""
74
90
 
@@ -114,6 +130,14 @@ class BaseLiquidHandlingResult(BaseModel):
114
130
  class DestinationPositionResult(BaseModel):
115
131
  """Mixin for command results that move a pipette."""
116
132
 
133
+ # todo(mm, 2024-08-02): Consider deprecating or redefining this.
134
+ #
135
+ # This is here because opentrons.protocol_engine needed it for internal bookkeeping
136
+ # and, at the time, we didn't have a way to do that without adding this to the
137
+ # public command results. Its usefulness to callers outside
138
+ # opentrons.protocol_engine is questionable because they would need to know which
139
+ # critical point is in play, and I think that can change depending on obscure
140
+ # things like labware quirks.
117
141
  position: DeckPoint = Field(
118
142
  DeckPoint(x=0, y=0, z=0),
119
143
  description=(
@@ -149,14 +173,6 @@ class OverpressureError(ErrorOccurrence):
149
173
  errorInfo: ErrorLocationInfo
150
174
 
151
175
 
152
- @dataclass(frozen=True)
153
- class OverpressureErrorInternalData:
154
- """Internal-to-ProtocolEngine data about an OverpressureError."""
155
-
156
- position: DeckPoint
157
- """Same meaning as DestinationPositionResult.position."""
158
-
159
-
160
176
  class LiquidNotFoundError(ErrorOccurrence):
161
177
  """Returned when no liquid is detected during the liquid probe process/move.
162
178
 
@@ -171,9 +187,17 @@ class LiquidNotFoundError(ErrorOccurrence):
171
187
  detail: str = ErrorCodes.PIPETTE_LIQUID_NOT_FOUND.value.detail
172
188
 
173
189
 
174
- @dataclass(frozen=True)
175
- class LiquidNotFoundErrorInternalData:
176
- """Internal-to-ProtocolEngine data about a LiquidNotFoundError."""
190
+ class TipPhysicallyAttachedError(ErrorOccurrence):
191
+ """Returned when sensors determine that a tip remains on the pipette after a drop attempt.
192
+
193
+ The pipette will act as if the tip was not dropped. So, you won't be able to pick
194
+ up a new tip without dropping the current one, and movement commands will assume
195
+ there is a tip hanging off the bottom of the pipette.
196
+ """
197
+
198
+ isDefined: bool = True
199
+
200
+ errorType: Literal["tipPhysicallyAttached"] = "tipPhysicallyAttached"
177
201
 
178
- position: DeckPoint
179
- """Same meaning as DestinationPositionResult.position."""
202
+ errorCode: str = ErrorCodes.TIP_DROP_FAILED.value.code
203
+ detail: str = ErrorCodes.TIP_DROP_FAILED.value.detail
@@ -1,18 +1,28 @@
1
1
  """Prepare to aspirate command request, result, and implementation models."""
2
2
 
3
3
  from __future__ import annotations
4
+ from opentrons_shared_data.errors.exceptions import PipetteOverpressureError
4
5
  from pydantic import BaseModel
5
- from typing import TYPE_CHECKING, Optional, Type
6
+ from typing import TYPE_CHECKING, Optional, Type, Union
6
7
  from typing_extensions import Literal
7
8
 
8
9
  from .pipetting_common import (
10
+ OverpressureError,
9
11
  PipetteIdMixin,
10
12
  )
11
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
13
+ from .command import (
14
+ AbstractCommandImpl,
15
+ BaseCommand,
16
+ BaseCommandCreate,
17
+ DefinedErrorData,
18
+ SuccessData,
19
+ )
12
20
  from ..errors.error_occurrence import ErrorOccurrence
13
21
 
14
22
  if TYPE_CHECKING:
15
- from ..execution.pipetting import PipettingHandler
23
+ from ..execution import PipettingHandler, GantryMover
24
+ from ..resources import ModelUtils
25
+
16
26
 
17
27
  PrepareToAspirateCommandType = Literal["prepareToAspirate"]
18
28
 
@@ -29,25 +39,62 @@ class PrepareToAspirateResult(BaseModel):
29
39
  pass
30
40
 
31
41
 
42
+ _ExecuteReturn = Union[
43
+ SuccessData[PrepareToAspirateResult],
44
+ DefinedErrorData[OverpressureError],
45
+ ]
46
+
47
+
32
48
  class PrepareToAspirateImplementation(
33
- AbstractCommandImpl[
34
- PrepareToAspirateParams, SuccessData[PrepareToAspirateResult, None]
35
- ]
49
+ AbstractCommandImpl[PrepareToAspirateParams, _ExecuteReturn]
36
50
  ):
37
51
  """Prepare for aspirate command implementation."""
38
52
 
39
- def __init__(self, pipetting: PipettingHandler, **kwargs: object) -> None:
53
+ def __init__(
54
+ self,
55
+ pipetting: PipettingHandler,
56
+ model_utils: ModelUtils,
57
+ gantry_mover: GantryMover,
58
+ **kwargs: object,
59
+ ) -> None:
40
60
  self._pipetting_handler = pipetting
61
+ self._model_utils = model_utils
62
+ self._gantry_mover = gantry_mover
41
63
 
42
- async def execute(
43
- self, params: PrepareToAspirateParams
44
- ) -> SuccessData[PrepareToAspirateResult, None]:
64
+ async def execute(self, params: PrepareToAspirateParams) -> _ExecuteReturn:
45
65
  """Prepare the pipette to aspirate."""
46
- await self._pipetting_handler.prepare_for_aspirate(
47
- pipette_id=params.pipetteId,
48
- )
49
-
50
- return SuccessData(public=PrepareToAspirateResult(), private=None)
66
+ current_position = await self._gantry_mover.get_position(params.pipetteId)
67
+ try:
68
+ await self._pipetting_handler.prepare_for_aspirate(
69
+ pipette_id=params.pipetteId,
70
+ )
71
+ except PipetteOverpressureError as e:
72
+ return DefinedErrorData(
73
+ public=OverpressureError(
74
+ id=self._model_utils.generate_id(),
75
+ createdAt=self._model_utils.get_timestamp(),
76
+ wrappedErrors=[
77
+ ErrorOccurrence.from_failed(
78
+ id=self._model_utils.generate_id(),
79
+ createdAt=self._model_utils.get_timestamp(),
80
+ error=e,
81
+ )
82
+ ],
83
+ errorInfo=(
84
+ {
85
+ "retryLocation": (
86
+ current_position.x,
87
+ current_position.y,
88
+ current_position.z,
89
+ )
90
+ }
91
+ ),
92
+ ),
93
+ )
94
+ else:
95
+ return SuccessData(
96
+ public=PrepareToAspirateResult(),
97
+ )
51
98
 
52
99
 
53
100
  class PrepareToAspirate(
@@ -6,9 +6,10 @@ from typing_extensions import Literal
6
6
 
7
7
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
8
8
  from ..errors.error_occurrence import ErrorOccurrence
9
+ from ..state.update_types import StateUpdate
9
10
 
10
11
  if TYPE_CHECKING:
11
- from ..state import StateView
12
+ from ..state.state import StateView
12
13
  from ..execution import EquipmentHandler
13
14
 
14
15
 
@@ -46,7 +47,7 @@ class ReloadLabwareResult(BaseModel):
46
47
 
47
48
 
48
49
  class ReloadLabwareImplementation(
49
- AbstractCommandImpl[ReloadLabwareParams, SuccessData[ReloadLabwareResult, None]]
50
+ AbstractCommandImpl[ReloadLabwareParams, SuccessData[ReloadLabwareResult]]
50
51
  ):
51
52
  """Reload labware command implementation."""
52
53
 
@@ -58,18 +59,26 @@ class ReloadLabwareImplementation(
58
59
 
59
60
  async def execute(
60
61
  self, params: ReloadLabwareParams
61
- ) -> SuccessData[ReloadLabwareResult, None]:
62
+ ) -> SuccessData[ReloadLabwareResult]:
62
63
  """Reload the definition and calibration data for a specific labware."""
63
64
  reloaded_labware = await self._equipment.reload_labware(
64
65
  labware_id=params.labwareId,
65
66
  )
66
67
 
68
+ state_update = StateUpdate()
69
+
70
+ state_update.set_labware_location(
71
+ labware_id=params.labwareId,
72
+ new_location=reloaded_labware.location,
73
+ new_offset_id=reloaded_labware.offsetId,
74
+ )
75
+
67
76
  return SuccessData(
68
77
  public=ReloadLabwareResult(
69
78
  labwareId=params.labwareId,
70
79
  offsetId=reloaded_labware.offsetId,
71
80
  ),
72
- private=None,
81
+ state_update=state_update,
73
82
  )
74
83
 
75
84
 
@@ -4,6 +4,7 @@ from pydantic import BaseModel, Field
4
4
  from typing import TYPE_CHECKING, Optional, Type
5
5
  from typing_extensions import Literal
6
6
 
7
+ from ..state import update_types
7
8
  from ..types import MotorAxis
8
9
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
9
10
  from ..errors.error_occurrence import ErrorOccurrence
@@ -38,7 +39,7 @@ class RetractAxisResult(BaseModel):
38
39
 
39
40
 
40
41
  class RetractAxisImplementation(
41
- AbstractCommandImpl[RetractAxisParams, SuccessData[RetractAxisResult, None]]
42
+ AbstractCommandImpl[RetractAxisParams, SuccessData[RetractAxisResult]]
42
43
  ):
43
44
  """Retract Axis command implementation."""
44
45
 
@@ -47,10 +48,12 @@ class RetractAxisImplementation(
47
48
 
48
49
  async def execute(
49
50
  self, params: RetractAxisParams
50
- ) -> SuccessData[RetractAxisResult, None]:
51
+ ) -> SuccessData[RetractAxisResult]:
51
52
  """Retract the specified axis."""
53
+ state_update = update_types.StateUpdate()
52
54
  await self._movement.retract_axis(axis=params.axis)
53
- return SuccessData(public=RetractAxisResult(), private=None)
55
+ state_update.clear_all_pipette_locations()
56
+ return SuccessData(public=RetractAxisResult(), state_update=state_update)
54
57
 
55
58
 
56
59
  class RetractAxis(BaseCommand[RetractAxisParams, RetractAxisResult, ErrorOccurrence]):
@@ -46,7 +46,7 @@ class SavePositionResult(BaseModel):
46
46
 
47
47
 
48
48
  class SavePositionImplementation(
49
- AbstractCommandImpl[SavePositionParams, SuccessData[SavePositionResult, None]]
49
+ AbstractCommandImpl[SavePositionParams, SuccessData[SavePositionResult]]
50
50
  ):
51
51
  """Save position command implementation."""
52
52
 
@@ -61,7 +61,7 @@ class SavePositionImplementation(
61
61
 
62
62
  async def execute(
63
63
  self, params: SavePositionParams
64
- ) -> SuccessData[SavePositionResult, None]:
64
+ ) -> SuccessData[SavePositionResult]:
65
65
  """Check the requested pipette's current position."""
66
66
  position_id = self._model_utils.ensure_id(params.positionId)
67
67
  fail_on_not_homed = (
@@ -76,7 +76,6 @@ class SavePositionImplementation(
76
76
  positionId=position_id,
77
77
  position=DeckPoint(x=x, y=y, z=z),
78
78
  ),
79
- private=None,
80
79
  )
81
80
 
82
81
 
@@ -29,7 +29,7 @@ class SetRailLightsResult(BaseModel):
29
29
 
30
30
 
31
31
  class SetRailLightsImplementation(
32
- AbstractCommandImpl[SetRailLightsParams, SuccessData[SetRailLightsResult, None]]
32
+ AbstractCommandImpl[SetRailLightsParams, SuccessData[SetRailLightsResult]]
33
33
  ):
34
34
  """setRailLights command implementation."""
35
35
 
@@ -38,10 +38,12 @@ class SetRailLightsImplementation(
38
38
 
39
39
  async def execute(
40
40
  self, params: SetRailLightsParams
41
- ) -> SuccessData[SetRailLightsResult, None]:
41
+ ) -> SuccessData[SetRailLightsResult]:
42
42
  """Dispatch a set lights command setting the state of the rail lights."""
43
43
  await self._rail_lights.set_rail_lights(params.on)
44
- return SuccessData(public=SetRailLightsResult(), private=None)
44
+ return SuccessData(
45
+ public=SetRailLightsResult(),
46
+ )
45
47
 
46
48
 
47
49
  class SetRailLights(
@@ -49,7 +49,7 @@ class SetStatusBarResult(BaseModel):
49
49
 
50
50
 
51
51
  class SetStatusBarImplementation(
52
- AbstractCommandImpl[SetStatusBarParams, SuccessData[SetStatusBarResult, None]]
52
+ AbstractCommandImpl[SetStatusBarParams, SuccessData[SetStatusBarResult]]
53
53
  ):
54
54
  """setStatusBar command implementation."""
55
55
 
@@ -58,12 +58,14 @@ class SetStatusBarImplementation(
58
58
 
59
59
  async def execute(
60
60
  self, params: SetStatusBarParams
61
- ) -> SuccessData[SetStatusBarResult, None]:
61
+ ) -> SuccessData[SetStatusBarResult]:
62
62
  """Execute the setStatusBar command."""
63
63
  if not self._status_bar.status_bar_should_not_be_changed():
64
64
  state = _animation_to_status_bar_state(params.animation)
65
65
  await self._status_bar.set_status_bar(state)
66
- return SuccessData(public=SetStatusBarResult(), private=None)
66
+ return SuccessData(
67
+ public=SetStatusBarResult(),
68
+ )
67
69
 
68
70
 
69
71
  class SetStatusBar(
@@ -9,7 +9,7 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, Succe
9
9
  from ...errors.error_occurrence import ErrorOccurrence
10
10
 
11
11
  if TYPE_CHECKING:
12
- from opentrons.protocol_engine.state import StateView
12
+ from opentrons.protocol_engine.state.state import StateView
13
13
  from opentrons.protocol_engine.execution import EquipmentHandler
14
14
 
15
15
  DeactivateTemperatureCommandType = Literal["temperatureModule/deactivate"]
@@ -27,7 +27,7 @@ class DeactivateTemperatureResult(BaseModel):
27
27
 
28
28
  class DeactivateTemperatureImpl(
29
29
  AbstractCommandImpl[
30
- DeactivateTemperatureParams, SuccessData[DeactivateTemperatureResult, None]
30
+ DeactivateTemperatureParams, SuccessData[DeactivateTemperatureResult]
31
31
  ]
32
32
  ):
33
33
  """Execution implementation of a Temperature Module's deactivate command."""
@@ -43,7 +43,7 @@ class DeactivateTemperatureImpl(
43
43
 
44
44
  async def execute(
45
45
  self, params: DeactivateTemperatureParams
46
- ) -> SuccessData[DeactivateTemperatureResult, None]:
46
+ ) -> SuccessData[DeactivateTemperatureResult]:
47
47
  """Deactivate a Temperature Module."""
48
48
  # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError.
49
49
  module_substate = self._state_view.modules.get_temperature_module_substate(
@@ -57,7 +57,9 @@ class DeactivateTemperatureImpl(
57
57
 
58
58
  if temp_hardware_module is not None:
59
59
  await temp_hardware_module.deactivate()
60
- return SuccessData(public=DeactivateTemperatureResult(), private=None)
60
+ return SuccessData(
61
+ public=DeactivateTemperatureResult(),
62
+ )
61
63
 
62
64
 
63
65
  class DeactivateTemperature(