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.
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
@@ -7,7 +7,7 @@ from typing_extensions import Literal
7
7
  from .pipetting_common import PipetteIdMixin
8
8
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
9
9
  from ..errors.error_occurrence import ErrorOccurrence
10
- from .configuring_common import PipetteConfigUpdateResultMixin
10
+ from ..state.update_types import StateUpdate
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from ..execution import EquipmentHandler
@@ -34,12 +34,6 @@ class ConfigureForVolumeParams(PipetteIdMixin):
34
34
  )
35
35
 
36
36
 
37
- class ConfigureForVolumePrivateResult(PipetteConfigUpdateResultMixin):
38
- """Result sent to the store but not serialized."""
39
-
40
- pass
41
-
42
-
43
37
  class ConfigureForVolumeResult(BaseModel):
44
38
  """Result data from execution of an ConfigureForVolume command."""
45
39
 
@@ -49,7 +43,7 @@ class ConfigureForVolumeResult(BaseModel):
49
43
  class ConfigureForVolumeImplementation(
50
44
  AbstractCommandImpl[
51
45
  ConfigureForVolumeParams,
52
- SuccessData[ConfigureForVolumeResult, ConfigureForVolumePrivateResult],
46
+ SuccessData[ConfigureForVolumeResult],
53
47
  ]
54
48
  ):
55
49
  """Configure for volume command implementation."""
@@ -59,7 +53,7 @@ class ConfigureForVolumeImplementation(
59
53
 
60
54
  async def execute(
61
55
  self, params: ConfigureForVolumeParams
62
- ) -> SuccessData[ConfigureForVolumeResult, ConfigureForVolumePrivateResult]:
56
+ ) -> SuccessData[ConfigureForVolumeResult]:
63
57
  """Check that requested pipette can be configured for the given volume."""
64
58
  pipette_result = await self._equipment.configure_for_volume(
65
59
  pipette_id=params.pipetteId,
@@ -67,13 +61,16 @@ class ConfigureForVolumeImplementation(
67
61
  tip_overlap_version=params.tipOverlapNotAfterVersion,
68
62
  )
69
63
 
64
+ state_update = StateUpdate()
65
+ state_update.update_pipette_config(
66
+ pipette_id=pipette_result.pipette_id,
67
+ config=pipette_result.static_config,
68
+ serial_number=pipette_result.serial_number,
69
+ )
70
+
70
71
  return SuccessData(
71
72
  public=ConfigureForVolumeResult(),
72
- private=ConfigureForVolumePrivateResult(
73
- pipette_id=pipette_result.pipette_id,
74
- serial_number=pipette_result.serial_number,
75
- config=pipette_result.static_config,
76
- ),
73
+ state_update=state_update,
77
74
  )
78
75
 
79
76
 
@@ -1,5 +1,6 @@
1
1
  """Configure nozzle layout command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from opentrons.protocol_engine.state.update_types import StateUpdate
3
4
  from pydantic import BaseModel
4
5
  from typing import TYPE_CHECKING, Optional, Type, Union
5
6
  from typing_extensions import Literal
@@ -9,9 +10,6 @@ from .pipetting_common import (
9
10
  )
10
11
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
11
12
  from ..errors.error_occurrence import ErrorOccurrence
12
- from .configuring_common import (
13
- PipetteNozzleLayoutResultMixin,
14
- )
15
13
  from ..types import (
16
14
  AllNozzleLayoutConfiguration,
17
15
  SingleNozzleLayoutConfiguration,
@@ -39,12 +37,6 @@ class ConfigureNozzleLayoutParams(PipetteIdMixin):
39
37
  ]
40
38
 
41
39
 
42
- class ConfigureNozzleLayoutPrivateResult(PipetteNozzleLayoutResultMixin):
43
- """Result sent to the store but not serialized."""
44
-
45
- pass
46
-
47
-
48
40
  class ConfigureNozzleLayoutResult(BaseModel):
49
41
  """Result data from execution of an configureNozzleLayout command."""
50
42
 
@@ -54,7 +46,7 @@ class ConfigureNozzleLayoutResult(BaseModel):
54
46
  class ConfigureNozzleLayoutImplementation(
55
47
  AbstractCommandImpl[
56
48
  ConfigureNozzleLayoutParams,
57
- SuccessData[ConfigureNozzleLayoutResult, ConfigureNozzleLayoutPrivateResult],
49
+ SuccessData[ConfigureNozzleLayoutResult],
58
50
  ]
59
51
  ):
60
52
  """Configure nozzle layout command implementation."""
@@ -67,7 +59,7 @@ class ConfigureNozzleLayoutImplementation(
67
59
 
68
60
  async def execute(
69
61
  self, params: ConfigureNozzleLayoutParams
70
- ) -> SuccessData[ConfigureNozzleLayoutResult, ConfigureNozzleLayoutPrivateResult]:
62
+ ) -> SuccessData[ConfigureNozzleLayoutResult]:
71
63
  """Check that requested pipette can support the requested nozzle layout."""
72
64
  primary_nozzle = params.configurationParams.dict().get("primaryNozzle")
73
65
  front_right_nozzle = params.configurationParams.dict().get("frontRightNozzle")
@@ -85,12 +77,14 @@ class ConfigureNozzleLayoutImplementation(
85
77
  **nozzle_params,
86
78
  )
87
79
 
80
+ update_state = StateUpdate()
81
+ update_state.update_pipette_nozzle(
82
+ pipette_id=params.pipetteId, nozzle_map=nozzle_map
83
+ )
84
+
88
85
  return SuccessData(
89
86
  public=ConfigureNozzleLayoutResult(),
90
- private=ConfigureNozzleLayoutPrivateResult(
91
- pipette_id=params.pipetteId,
92
- nozzle_map=nozzle_map,
93
- ),
87
+ state_update=update_state,
94
88
  )
95
89
 
96
90
 
@@ -40,16 +40,18 @@ class CustomResult(BaseModel):
40
40
 
41
41
 
42
42
  class CustomImplementation(
43
- AbstractCommandImpl[CustomParams, SuccessData[CustomResult, None]]
43
+ AbstractCommandImpl[CustomParams, SuccessData[CustomResult]]
44
44
  ):
45
45
  """Custom command implementation."""
46
46
 
47
47
  # TODO(mm, 2022-11-09): figure out how a plugin can specify a custom command
48
48
  # implementation. For now, always no-op, so we can use custom commands as containers
49
49
  # for legacy RPC (pre-ProtocolEngine) payloads.
50
- async def execute(self, params: CustomParams) -> SuccessData[CustomResult, None]:
50
+ async def execute(self, params: CustomParams) -> SuccessData[CustomResult]:
51
51
  """A custom command does nothing when executed directly."""
52
- return SuccessData(public=CustomResult.construct(), private=None)
52
+ return SuccessData(
53
+ public=CustomResult.construct(),
54
+ )
53
55
 
54
56
 
55
57
  class Custom(BaseCommand[CustomParams, CustomResult, ErrorOccurrence]):
@@ -8,15 +8,15 @@ from opentrons_shared_data.errors.exceptions import PipetteOverpressureError
8
8
  from pydantic import Field
9
9
 
10
10
  from ..types import DeckPoint
11
+ from ..state.update_types import StateUpdate, CLEAR
11
12
  from .pipetting_common import (
12
13
  PipetteIdMixin,
13
14
  DispenseVolumeMixin,
14
15
  FlowRateMixin,
15
- WellLocationMixin,
16
+ LiquidHandlingWellLocationMixin,
16
17
  BaseLiquidHandlingResult,
17
18
  DestinationPositionResult,
18
19
  OverpressureError,
19
- OverpressureErrorInternalData,
20
20
  )
21
21
  from .command import (
22
22
  AbstractCommandImpl,
@@ -30,13 +30,14 @@ from ..errors.error_occurrence import ErrorOccurrence
30
30
  if TYPE_CHECKING:
31
31
  from ..execution import MovementHandler, PipettingHandler
32
32
  from ..resources import ModelUtils
33
+ from ..state.state import StateView
33
34
 
34
35
 
35
36
  DispenseCommandType = Literal["dispense"]
36
37
 
37
38
 
38
39
  class DispenseParams(
39
- PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, WellLocationMixin
40
+ PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
40
41
  ):
41
42
  """Payload required to dispense to a specific well."""
42
43
 
@@ -53,8 +54,8 @@ class DispenseResult(BaseLiquidHandlingResult, DestinationPositionResult):
53
54
 
54
55
 
55
56
  _ExecuteReturn = Union[
56
- SuccessData[DispenseResult, None],
57
- DefinedErrorData[OverpressureError, OverpressureErrorInternalData],
57
+ SuccessData[DispenseResult],
58
+ DefinedErrorData[OverpressureError],
58
59
  ]
59
60
 
60
61
 
@@ -63,31 +64,54 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn]
63
64
 
64
65
  def __init__(
65
66
  self,
67
+ state_view: StateView,
66
68
  movement: MovementHandler,
67
69
  pipetting: PipettingHandler,
68
70
  model_utils: ModelUtils,
69
71
  **kwargs: object,
70
72
  ) -> None:
73
+ self._state_view = state_view
71
74
  self._movement = movement
72
75
  self._pipetting = pipetting
73
76
  self._model_utils = model_utils
74
77
 
75
78
  async def execute(self, params: DispenseParams) -> _ExecuteReturn:
76
79
  """Move to and dispense to the requested well."""
80
+ state_update = StateUpdate()
81
+ well_location = params.wellLocation
82
+ labware_id = params.labwareId
83
+ well_name = params.wellName
84
+ volume = params.volume
85
+
86
+ # TODO(pbm, 10-15-24): call self._state_view.geometry.validate_dispense_volume_into_well()
87
+
77
88
  position = await self._movement.move_to_well(
78
89
  pipette_id=params.pipetteId,
79
- labware_id=params.labwareId,
80
- well_name=params.wellName,
81
- well_location=params.wellLocation,
90
+ labware_id=labware_id,
91
+ well_name=well_name,
92
+ well_location=well_location,
82
93
  )
94
+ deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
95
+ state_update.set_pipette_location(
96
+ pipette_id=params.pipetteId,
97
+ new_labware_id=labware_id,
98
+ new_well_name=well_name,
99
+ new_deck_point=deck_point,
100
+ )
101
+
83
102
  try:
84
103
  volume = await self._pipetting.dispense_in_place(
85
104
  pipette_id=params.pipetteId,
86
- volume=params.volume,
105
+ volume=volume,
87
106
  flow_rate=params.flowRate,
88
107
  push_out=params.pushOut,
89
108
  )
90
109
  except PipetteOverpressureError as e:
110
+ state_update.set_liquid_operated(
111
+ labware_id=labware_id,
112
+ well_name=well_name,
113
+ volume_added=CLEAR,
114
+ )
91
115
  return DefinedErrorData(
92
116
  public=OverpressureError(
93
117
  id=self._model_utils.generate_id(),
@@ -101,23 +125,21 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn]
101
125
  ],
102
126
  errorInfo={"retryLocation": (position.x, position.y, position.z)},
103
127
  ),
104
- private=OverpressureErrorInternalData(
105
- position=DeckPoint.construct(
106
- x=position.x, y=position.y, z=position.z
107
- )
108
- ),
128
+ state_update=state_update,
109
129
  )
110
130
  else:
131
+ state_update.set_liquid_operated(
132
+ labware_id=labware_id,
133
+ well_name=well_name,
134
+ volume_added=volume,
135
+ )
111
136
  return SuccessData(
112
- public=DispenseResult(
113
- volume=volume,
114
- position=DeckPoint(x=position.x, y=position.y, z=position.z),
115
- ),
116
- private=None,
137
+ public=DispenseResult(volume=volume, position=deck_point),
138
+ state_update=state_update,
117
139
  )
118
140
 
119
141
 
120
- class Dispense(BaseCommand[DispenseParams, DispenseResult, ErrorOccurrence]):
142
+ class Dispense(BaseCommand[DispenseParams, DispenseResult, OverpressureError]):
121
143
  """Dispense command model."""
122
144
 
123
145
  commandType: DispenseCommandType = "dispense"
@@ -12,7 +12,6 @@ from .pipetting_common import (
12
12
  FlowRateMixin,
13
13
  BaseLiquidHandlingResult,
14
14
  OverpressureError,
15
- OverpressureErrorInternalData,
16
15
  )
17
16
  from .command import (
18
17
  AbstractCommandImpl,
@@ -22,11 +21,13 @@ from .command import (
22
21
  DefinedErrorData,
23
22
  )
24
23
  from ..errors.error_occurrence import ErrorOccurrence
25
- from ..types import DeckPoint
24
+ from ..state.update_types import StateUpdate, CLEAR
25
+ from ..types import CurrentWell
26
26
 
27
27
  if TYPE_CHECKING:
28
28
  from ..execution import PipettingHandler, GantryMover
29
29
  from ..resources import ModelUtils
30
+ from ..state.state import StateView
30
31
 
31
32
 
32
33
  DispenseInPlaceCommandType = Literal["dispenseInPlace"]
@@ -48,8 +49,8 @@ class DispenseInPlaceResult(BaseLiquidHandlingResult):
48
49
 
49
50
 
50
51
  _ExecuteReturn = Union[
51
- SuccessData[DispenseInPlaceResult, None],
52
- DefinedErrorData[OverpressureError, OverpressureErrorInternalData],
52
+ SuccessData[DispenseInPlaceResult],
53
+ DefinedErrorData[OverpressureError],
53
54
  ]
54
55
 
55
56
 
@@ -61,17 +62,22 @@ class DispenseInPlaceImplementation(
61
62
  def __init__(
62
63
  self,
63
64
  pipetting: PipettingHandler,
65
+ state_view: StateView,
64
66
  gantry_mover: GantryMover,
65
67
  model_utils: ModelUtils,
66
68
  **kwargs: object,
67
69
  ) -> None:
68
70
  self._pipetting = pipetting
71
+ self._state_view = state_view
69
72
  self._gantry_mover = gantry_mover
70
73
  self._model_utils = model_utils
71
74
 
72
75
  async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn:
73
76
  """Dispense without moving the pipette."""
77
+ state_update = StateUpdate()
78
+ current_location = self._state_view.pipettes.get_current_location()
74
79
  try:
80
+ current_position = await self._gantry_mover.get_position(params.pipetteId)
75
81
  volume = await self._pipetting.dispense_in_place(
76
82
  pipette_id=params.pipetteId,
77
83
  volume=params.volume,
@@ -79,7 +85,15 @@ class DispenseInPlaceImplementation(
79
85
  push_out=params.pushOut,
80
86
  )
81
87
  except PipetteOverpressureError as e:
82
- current_position = await self._gantry_mover.get_position(params.pipetteId)
88
+ if (
89
+ isinstance(current_location, CurrentWell)
90
+ and current_location.pipette_id == params.pipetteId
91
+ ):
92
+ state_update.set_liquid_operated(
93
+ labware_id=current_location.labware_id,
94
+ well_name=current_location.well_name,
95
+ volume_added=CLEAR,
96
+ )
83
97
  return DefinedErrorData(
84
98
  public=OverpressureError(
85
99
  id=self._model_utils.generate_id(),
@@ -101,22 +115,26 @@ class DispenseInPlaceImplementation(
101
115
  }
102
116
  ),
103
117
  ),
104
- private=OverpressureErrorInternalData(
105
- position=DeckPoint(
106
- x=current_position.x,
107
- y=current_position.y,
108
- z=current_position.z,
109
- ),
110
- ),
118
+ state_update=state_update,
111
119
  )
112
120
  else:
121
+ if (
122
+ isinstance(current_location, CurrentWell)
123
+ and current_location.pipette_id == params.pipetteId
124
+ ):
125
+ state_update.set_liquid_operated(
126
+ labware_id=current_location.labware_id,
127
+ well_name=current_location.well_name,
128
+ volume_added=volume,
129
+ )
113
130
  return SuccessData(
114
- public=DispenseInPlaceResult(volume=volume), private=None
131
+ public=DispenseInPlaceResult(volume=volume),
132
+ state_update=state_update,
115
133
  )
116
134
 
117
135
 
118
136
  class DispenseInPlace(
119
- BaseCommand[DispenseInPlaceParams, DispenseInPlaceResult, ErrorOccurrence]
137
+ BaseCommand[DispenseInPlaceParams, DispenseInPlaceResult, OverpressureError]
120
138
  ):
121
139
  """DispenseInPlace command model."""
122
140
 
@@ -5,13 +5,27 @@ from pydantic import Field
5
5
  from typing import TYPE_CHECKING, Optional, Type
6
6
  from typing_extensions import Literal
7
7
 
8
+ from opentrons.protocol_engine.errors.exceptions import TipAttachedError
9
+ from opentrons.protocol_engine.resources.model_utils import ModelUtils
10
+
11
+ from ..state import update_types
8
12
  from ..types import DropTipWellLocation, DeckPoint
9
- from .pipetting_common import PipetteIdMixin, DestinationPositionResult
10
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
13
+ from .pipetting_common import (
14
+ PipetteIdMixin,
15
+ DestinationPositionResult,
16
+ TipPhysicallyAttachedError,
17
+ )
18
+ from .command import (
19
+ AbstractCommandImpl,
20
+ BaseCommand,
21
+ BaseCommandCreate,
22
+ DefinedErrorData,
23
+ SuccessData,
24
+ )
11
25
  from ..errors.error_occurrence import ErrorOccurrence
12
26
 
13
27
  if TYPE_CHECKING:
14
- from ..state import StateView
28
+ from ..state.state import StateView
15
29
  from ..execution import MovementHandler, TipHandler
16
30
 
17
31
 
@@ -53,9 +67,12 @@ class DropTipResult(DestinationPositionResult):
53
67
  pass
54
68
 
55
69
 
56
- class DropTipImplementation(
57
- AbstractCommandImpl[DropTipParams, SuccessData[DropTipResult, None]]
58
- ):
70
+ _ExecuteReturn = (
71
+ SuccessData[DropTipResult] | DefinedErrorData[TipPhysicallyAttachedError]
72
+ )
73
+
74
+
75
+ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
59
76
  """Drop tip command implementation."""
60
77
 
61
78
  def __init__(
@@ -63,19 +80,23 @@ class DropTipImplementation(
63
80
  state_view: StateView,
64
81
  tip_handler: TipHandler,
65
82
  movement: MovementHandler,
83
+ model_utils: ModelUtils,
66
84
  **kwargs: object,
67
85
  ) -> None:
68
86
  self._state_view = state_view
69
87
  self._tip_handler = tip_handler
70
88
  self._movement_handler = movement
89
+ self._model_utils = model_utils
71
90
 
72
- async def execute(self, params: DropTipParams) -> SuccessData[DropTipResult, None]:
91
+ async def execute(self, params: DropTipParams) -> _ExecuteReturn:
73
92
  """Move to and drop a tip using the requested pipette."""
74
93
  pipette_id = params.pipetteId
75
94
  labware_id = params.labwareId
76
95
  well_name = params.wellName
77
96
  home_after = params.homeAfter
78
97
 
98
+ state_update = update_types.StateUpdate()
99
+
79
100
  if params.alternateDropLocation:
80
101
  well_location = self._state_view.geometry.get_next_tip_drop_location(
81
102
  labware_id=labware_id,
@@ -101,16 +122,48 @@ class DropTipImplementation(
101
122
  well_name=well_name,
102
123
  well_location=tip_drop_location,
103
124
  )
104
-
105
- await self._tip_handler.drop_tip(pipette_id=pipette_id, home_after=home_after)
106
-
107
- return SuccessData(
108
- public=DropTipResult(
109
- position=DeckPoint(x=position.x, y=position.y, z=position.z)
110
- ),
111
- private=None,
125
+ deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
126
+ state_update.set_pipette_location(
127
+ pipette_id=pipette_id,
128
+ new_labware_id=labware_id,
129
+ new_well_name=well_name,
130
+ new_deck_point=deck_point,
112
131
  )
113
132
 
133
+ try:
134
+ await self._tip_handler.drop_tip(
135
+ pipette_id=pipette_id, home_after=home_after
136
+ )
137
+ except TipAttachedError as exception:
138
+ error = TipPhysicallyAttachedError(
139
+ id=self._model_utils.generate_id(),
140
+ createdAt=self._model_utils.get_timestamp(),
141
+ wrappedErrors=[
142
+ ErrorOccurrence.from_failed(
143
+ id=self._model_utils.generate_id(),
144
+ createdAt=self._model_utils.get_timestamp(),
145
+ error=exception,
146
+ )
147
+ ],
148
+ )
149
+ state_update_if_false_positive = update_types.StateUpdate()
150
+ state_update_if_false_positive.update_pipette_tip_state(
151
+ pipette_id=params.pipetteId, tip_geometry=None
152
+ )
153
+ return DefinedErrorData(
154
+ public=error,
155
+ state_update=state_update,
156
+ state_update_if_false_positive=state_update_if_false_positive,
157
+ )
158
+ else:
159
+ state_update.update_pipette_tip_state(
160
+ pipette_id=params.pipetteId, tip_geometry=None
161
+ )
162
+ return SuccessData(
163
+ public=DropTipResult(position=deck_point),
164
+ state_update=state_update,
165
+ )
166
+
114
167
 
115
168
  class DropTip(BaseCommand[DropTipParams, DropTipResult, ErrorOccurrence]):
116
169
  """Drop tip command model."""
@@ -4,9 +4,18 @@ from pydantic import Field, BaseModel
4
4
  from typing import TYPE_CHECKING, Optional, Type
5
5
  from typing_extensions import Literal
6
6
 
7
- from .pipetting_common import PipetteIdMixin
8
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
7
+ from .command import (
8
+ AbstractCommandImpl,
9
+ BaseCommand,
10
+ BaseCommandCreate,
11
+ DefinedErrorData,
12
+ SuccessData,
13
+ )
14
+ from .pipetting_common import PipetteIdMixin, TipPhysicallyAttachedError
15
+ from ..errors.exceptions import TipAttachedError
9
16
  from ..errors.error_occurrence import ErrorOccurrence
17
+ from ..resources.model_utils import ModelUtils
18
+ from ..state import update_types
10
19
 
11
20
  if TYPE_CHECKING:
12
21
  from ..execution import TipHandler
@@ -34,27 +43,59 @@ class DropTipInPlaceResult(BaseModel):
34
43
  pass
35
44
 
36
45
 
46
+ _ExecuteReturn = (
47
+ SuccessData[DropTipInPlaceResult] | DefinedErrorData[TipPhysicallyAttachedError]
48
+ )
49
+
50
+
37
51
  class DropTipInPlaceImplementation(
38
- AbstractCommandImpl[DropTipInPlaceParams, SuccessData[DropTipInPlaceResult, None]]
52
+ AbstractCommandImpl[DropTipInPlaceParams, _ExecuteReturn]
39
53
  ):
40
54
  """Drop tip in place command implementation."""
41
55
 
42
56
  def __init__(
43
57
  self,
44
58
  tip_handler: TipHandler,
59
+ model_utils: ModelUtils,
45
60
  **kwargs: object,
46
61
  ) -> None:
47
62
  self._tip_handler = tip_handler
63
+ self._model_utils = model_utils
48
64
 
49
- async def execute(
50
- self, params: DropTipInPlaceParams
51
- ) -> SuccessData[DropTipInPlaceResult, None]:
65
+ async def execute(self, params: DropTipInPlaceParams) -> _ExecuteReturn:
52
66
  """Drop a tip using the requested pipette."""
53
- await self._tip_handler.drop_tip(
54
- pipette_id=params.pipetteId, home_after=params.homeAfter
55
- )
56
-
57
- return SuccessData(public=DropTipInPlaceResult(), private=None)
67
+ state_update = update_types.StateUpdate()
68
+
69
+ try:
70
+ await self._tip_handler.drop_tip(
71
+ pipette_id=params.pipetteId, home_after=params.homeAfter
72
+ )
73
+ except TipAttachedError as exception:
74
+ state_update_if_false_positive = update_types.StateUpdate()
75
+ state_update_if_false_positive.update_pipette_tip_state(
76
+ pipette_id=params.pipetteId, tip_geometry=None
77
+ )
78
+ error = TipPhysicallyAttachedError(
79
+ id=self._model_utils.generate_id(),
80
+ createdAt=self._model_utils.get_timestamp(),
81
+ wrappedErrors=[
82
+ ErrorOccurrence.from_failed(
83
+ id=self._model_utils.generate_id(),
84
+ createdAt=self._model_utils.get_timestamp(),
85
+ error=exception,
86
+ )
87
+ ],
88
+ )
89
+ return DefinedErrorData(
90
+ public=error,
91
+ state_update=state_update,
92
+ state_update_if_false_positive=state_update_if_false_positive,
93
+ )
94
+ else:
95
+ state_update.update_pipette_tip_state(
96
+ pipette_id=params.pipetteId, tip_geometry=None
97
+ )
98
+ return SuccessData(public=DropTipInPlaceResult(), state_update=state_update)
58
99
 
59
100
 
60
101
  class DropTipInPlace(
@@ -38,7 +38,7 @@ class GetTipPresenceResult(BaseModel):
38
38
 
39
39
 
40
40
  class GetTipPresenceImplementation(
41
- AbstractCommandImpl[GetTipPresenceParams, SuccessData[GetTipPresenceResult, None]]
41
+ AbstractCommandImpl[GetTipPresenceParams, SuccessData[GetTipPresenceResult]]
42
42
  ):
43
43
  """GetTipPresence command implementation."""
44
44
 
@@ -51,7 +51,7 @@ class GetTipPresenceImplementation(
51
51
 
52
52
  async def execute(
53
53
  self, params: GetTipPresenceParams
54
- ) -> SuccessData[GetTipPresenceResult, None]:
54
+ ) -> SuccessData[GetTipPresenceResult]:
55
55
  """Verify if tip presence is as expected for the requested pipette."""
56
56
  pipette_id = params.pipetteId
57
57
 
@@ -59,7 +59,9 @@ class GetTipPresenceImplementation(
59
59
  pipette_id=pipette_id,
60
60
  )
61
61
 
62
- return SuccessData(public=GetTipPresenceResult(status=result), private=None)
62
+ return SuccessData(
63
+ public=GetTipPresenceResult(status=result),
64
+ )
63
65
 
64
66
 
65
67
  class GetTipPresence(
@@ -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
 
@@ -27,9 +27,7 @@ class CloseLabwareLatchResult(BaseModel):
27
27
 
28
28
 
29
29
  class CloseLabwareLatchImpl(
30
- AbstractCommandImpl[
31
- CloseLabwareLatchParams, SuccessData[CloseLabwareLatchResult, None]
32
- ]
30
+ AbstractCommandImpl[CloseLabwareLatchParams, SuccessData[CloseLabwareLatchResult]]
33
31
  ):
34
32
  """Execution implementation of a Heater-Shaker's close labware latch command."""
35
33
 
@@ -44,7 +42,7 @@ class CloseLabwareLatchImpl(
44
42
 
45
43
  async def execute(
46
44
  self, params: CloseLabwareLatchParams
47
- ) -> SuccessData[CloseLabwareLatchResult, None]:
45
+ ) -> SuccessData[CloseLabwareLatchResult]:
48
46
  """Close a Heater-Shaker's labware latch."""
49
47
  # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError.
50
48
  hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate(
@@ -59,7 +57,9 @@ class CloseLabwareLatchImpl(
59
57
  if hs_hardware_module is not None:
60
58
  await hs_hardware_module.close_labware_latch()
61
59
 
62
- return SuccessData(public=CloseLabwareLatchResult(), private=None)
60
+ return SuccessData(
61
+ public=CloseLabwareLatchResult(),
62
+ )
63
63
 
64
64
 
65
65
  class CloseLabwareLatch(