opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.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/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 +208 -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/backends/ot3utils.py +1 -0
  20. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  21. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  22. opentrons/hardware_control/module_control.py +43 -2
  23. opentrons/hardware_control/modules/__init__.py +7 -1
  24. opentrons/hardware_control/modules/absorbance_reader.py +232 -83
  25. opentrons/hardware_control/modules/errors.py +7 -0
  26. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  27. opentrons/hardware_control/modules/magdeck.py +12 -3
  28. opentrons/hardware_control/modules/mod_abc.py +27 -2
  29. opentrons/hardware_control/modules/tempdeck.py +15 -7
  30. opentrons/hardware_control/modules/thermocycler.py +69 -3
  31. opentrons/hardware_control/modules/types.py +11 -5
  32. opentrons/hardware_control/modules/update.py +11 -5
  33. opentrons/hardware_control/modules/utils.py +3 -1
  34. opentrons/hardware_control/ot3_calibration.py +6 -6
  35. opentrons/hardware_control/ot3api.py +131 -94
  36. opentrons/hardware_control/poller.py +15 -11
  37. opentrons/hardware_control/protocols/__init__.py +1 -7
  38. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  39. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  40. opentrons/hardware_control/protocols/position_estimator.py +3 -1
  41. opentrons/hardware_control/types.py +2 -0
  42. opentrons/legacy_commands/helpers.py +8 -2
  43. opentrons/motion_planning/__init__.py +2 -0
  44. opentrons/motion_planning/waypoints.py +32 -0
  45. opentrons/protocol_api/__init__.py +2 -1
  46. opentrons/protocol_api/_liquid.py +87 -1
  47. opentrons/protocol_api/_parameter_context.py +10 -1
  48. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  49. opentrons/protocol_api/core/engine/instrument.py +29 -25
  50. opentrons/protocol_api/core/engine/labware.py +20 -4
  51. opentrons/protocol_api/core/engine/module_core.py +166 -17
  52. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
  53. opentrons/protocol_api/core/engine/protocol.py +30 -2
  54. opentrons/protocol_api/core/instrument.py +2 -0
  55. opentrons/protocol_api/core/labware.py +4 -0
  56. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  57. opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
  58. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
  59. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  60. opentrons/protocol_api/core/module.py +22 -4
  61. opentrons/protocol_api/core/protocol.py +6 -2
  62. opentrons/protocol_api/instrument_context.py +52 -20
  63. opentrons/protocol_api/labware.py +13 -1
  64. opentrons/protocol_api/module_contexts.py +115 -17
  65. opentrons/protocol_api/protocol_context.py +49 -5
  66. opentrons/protocol_api/validation.py +5 -3
  67. opentrons/protocol_engine/__init__.py +10 -9
  68. opentrons/protocol_engine/actions/__init__.py +3 -0
  69. opentrons/protocol_engine/actions/actions.py +30 -25
  70. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  71. opentrons/protocol_engine/clients/sync_client.py +1 -1
  72. opentrons/protocol_engine/clients/transports.py +1 -1
  73. opentrons/protocol_engine/commands/__init__.py +0 -4
  74. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  75. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
  76. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
  77. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
  79. opentrons/protocol_engine/commands/aspirate.py +29 -16
  80. opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
  81. opentrons/protocol_engine/commands/blow_out.py +63 -14
  82. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  83. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  84. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  85. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  86. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  87. opentrons/protocol_engine/commands/command.py +31 -18
  88. opentrons/protocol_engine/commands/command_unions.py +37 -24
  89. opentrons/protocol_engine/commands/comment.py +5 -3
  90. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  91. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  92. opentrons/protocol_engine/commands/custom.py +5 -3
  93. opentrons/protocol_engine/commands/dispense.py +42 -20
  94. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  95. opentrons/protocol_engine/commands/drop_tip.py +70 -16
  96. opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
  97. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  98. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  99. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  100. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  101. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  102. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  103. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  104. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  105. opentrons/protocol_engine/commands/home.py +11 -5
  106. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  107. opentrons/protocol_engine/commands/load_labware.py +28 -5
  108. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  109. opentrons/protocol_engine/commands/load_module.py +4 -6
  110. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  111. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  112. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  113. opentrons/protocol_engine/commands/move_labware.py +155 -23
  114. opentrons/protocol_engine/commands/move_relative.py +15 -3
  115. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  116. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  117. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  118. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  119. opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
  120. opentrons/protocol_engine/commands/pipetting_common.py +47 -16
  121. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  122. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  123. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  124. opentrons/protocol_engine/commands/save_position.py +2 -3
  125. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  126. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  127. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  128. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  129. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  130. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  131. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  132. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  133. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  135. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  136. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  137. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  138. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  139. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  140. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  141. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  142. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  143. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  144. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  145. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  146. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  147. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  148. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
  149. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  150. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  151. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  152. opentrons/protocol_engine/create_protocol_engine.py +60 -10
  153. opentrons/protocol_engine/engine_support.py +2 -1
  154. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  155. opentrons/protocol_engine/errors/__init__.py +20 -0
  156. opentrons/protocol_engine/errors/error_occurrence.py +8 -3
  157. opentrons/protocol_engine/errors/exceptions.py +127 -2
  158. opentrons/protocol_engine/execution/__init__.py +2 -0
  159. opentrons/protocol_engine/execution/command_executor.py +22 -13
  160. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  161. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  162. opentrons/protocol_engine/execution/equipment.py +2 -1
  163. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  164. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  165. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  166. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  167. opentrons/protocol_engine/execution/labware_movement.py +73 -22
  168. opentrons/protocol_engine/execution/movement.py +17 -7
  169. opentrons/protocol_engine/execution/pipetting.py +7 -4
  170. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  171. opentrons/protocol_engine/execution/run_control.py +1 -1
  172. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  173. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  175. opentrons/protocol_engine/notes/__init__.py +14 -2
  176. opentrons/protocol_engine/notes/notes.py +18 -1
  177. opentrons/protocol_engine/plugins.py +1 -1
  178. opentrons/protocol_engine/protocol_engine.py +47 -31
  179. opentrons/protocol_engine/resources/__init__.py +2 -0
  180. opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
  181. opentrons/protocol_engine/resources/file_provider.py +161 -0
  182. opentrons/protocol_engine/resources/fixture_validation.py +11 -1
  183. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  184. opentrons/protocol_engine/state/__init__.py +0 -70
  185. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  186. opentrons/protocol_engine/state/command_history.py +21 -2
  187. opentrons/protocol_engine/state/commands.py +110 -31
  188. opentrons/protocol_engine/state/files.py +59 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  190. opentrons/protocol_engine/state/geometry.py +445 -59
  191. opentrons/protocol_engine/state/labware.py +264 -84
  192. opentrons/protocol_engine/state/liquids.py +1 -1
  193. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
  194. opentrons/protocol_engine/state/modules.py +145 -90
  195. opentrons/protocol_engine/state/motion.py +33 -14
  196. opentrons/protocol_engine/state/pipettes.py +157 -317
  197. opentrons/protocol_engine/state/state.py +30 -1
  198. opentrons/protocol_engine/state/state_summary.py +3 -0
  199. opentrons/protocol_engine/state/tips.py +69 -114
  200. opentrons/protocol_engine/state/update_types.py +424 -0
  201. opentrons/protocol_engine/state/wells.py +236 -0
  202. opentrons/protocol_engine/types.py +90 -0
  203. opentrons/protocol_reader/file_format_validator.py +83 -15
  204. opentrons/protocol_runner/json_translator.py +21 -5
  205. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  206. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  207. opentrons/protocol_runner/protocol_runner.py +6 -3
  208. opentrons/protocol_runner/run_orchestrator.py +41 -6
  209. opentrons/protocols/advanced_control/mix.py +3 -5
  210. opentrons/protocols/advanced_control/transfers.py +125 -56
  211. opentrons/protocols/api_support/constants.py +1 -1
  212. opentrons/protocols/api_support/definitions.py +1 -1
  213. opentrons/protocols/api_support/labware_like.py +4 -4
  214. opentrons/protocols/api_support/tip_tracker.py +2 -2
  215. opentrons/protocols/api_support/types.py +15 -2
  216. opentrons/protocols/api_support/util.py +30 -42
  217. opentrons/protocols/duration/errors.py +1 -1
  218. opentrons/protocols/duration/estimator.py +50 -29
  219. opentrons/protocols/execution/dev_types.py +2 -2
  220. opentrons/protocols/execution/execute_json_v4.py +15 -10
  221. opentrons/protocols/execution/execute_python.py +8 -3
  222. opentrons/protocols/geometry/planning.py +12 -12
  223. opentrons/protocols/labware.py +17 -33
  224. opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
  225. opentrons/simulate.py +3 -3
  226. opentrons/types.py +30 -3
  227. opentrons/util/logging_config.py +34 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
  230. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  231. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  232. opentrons/protocol_runner/thread_async_queue.py +0 -174
  233. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  234. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  235. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
  236. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
  237. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional, List, Type
5
5
  from typing_extensions import Literal
6
6
 
7
7
  from opentrons.types import MountType
8
+ from ..state import update_types
8
9
  from ..types import MotorAxis
9
10
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
10
11
  from ..errors.error_occurrence import ErrorOccurrence
@@ -41,16 +42,16 @@ class HomeResult(BaseModel):
41
42
  """Result data from the execution of a Home command."""
42
43
 
43
44
 
44
- class HomeImplementation(
45
- AbstractCommandImpl[HomeParams, SuccessData[HomeResult, None]]
46
- ):
45
+ class HomeImplementation(AbstractCommandImpl[HomeParams, SuccessData[HomeResult]]):
47
46
  """Home command implementation."""
48
47
 
49
48
  def __init__(self, movement: MovementHandler, **kwargs: object) -> None:
50
49
  self._movement = movement
51
50
 
52
- async def execute(self, params: HomeParams) -> SuccessData[HomeResult, None]:
51
+ async def execute(self, params: HomeParams) -> SuccessData[HomeResult]:
53
52
  """Home some or all motors to establish positional accuracy."""
53
+ state_update = update_types.StateUpdate()
54
+
54
55
  if (
55
56
  params.skipIfMountPositionOk is None
56
57
  or not await self._movement.check_for_valid_position(
@@ -58,7 +59,12 @@ class HomeImplementation(
58
59
  )
59
60
  ):
60
61
  await self._movement.home(axes=params.axes)
61
- return SuccessData(public=HomeResult(), private=None)
62
+
63
+ # todo(mm, 2024-09-17): Clearing all pipette locations *unconditionally* is to
64
+ # preserve prior behavior, but we might only want to do this if we actually home.
65
+ state_update.clear_all_pipette_locations()
66
+
67
+ return SuccessData(public=HomeResult(), state_update=state_update)
62
68
 
63
69
 
64
70
  class Home(BaseCommand[HomeParams, HomeResult, ErrorOccurrence]):
@@ -1,24 +1,26 @@
1
1
  """The liquidProbe and tryLiquidProbe commands."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import TYPE_CHECKING, Optional, Type, Union
4
+ from typing import TYPE_CHECKING, NamedTuple, Optional, Type, Union
5
+ from typing_extensions import Literal
6
+
7
+ from pydantic import Field
8
+
9
+ from opentrons.protocol_engine.state import update_types
5
10
  from opentrons.protocol_engine.errors.exceptions import (
6
11
  MustHomeError,
7
12
  PipetteNotReadyToAspirateError,
8
13
  TipNotEmptyError,
14
+ IncompleteLabwareDefinitionError,
9
15
  )
10
16
  from opentrons.types import MountType
11
17
  from opentrons_shared_data.errors.exceptions import (
12
18
  PipetteLiquidNotFoundError,
13
19
  )
14
- from typing_extensions import Literal
15
-
16
- from pydantic import Field
17
20
 
18
21
  from ..types import DeckPoint
19
22
  from .pipetting_common import (
20
23
  LiquidNotFoundError,
21
- LiquidNotFoundErrorInternalData,
22
24
  PipetteIdMixin,
23
25
  WellLocationMixin,
24
26
  DestinationPositionResult,
@@ -36,7 +38,7 @@ from ..errors.error_occurrence import ErrorOccurrence
36
38
  if TYPE_CHECKING:
37
39
  from ..execution import MovementHandler, PipettingHandler
38
40
  from ..resources import ModelUtils
39
- from ..state import StateView
41
+ from ..state.state import StateView
40
42
 
41
43
 
42
44
  LiquidProbeCommandType = Literal["liquidProbe"]
@@ -84,10 +86,86 @@ class TryLiquidProbeResult(DestinationPositionResult):
84
86
 
85
87
 
86
88
  _LiquidProbeExecuteReturn = Union[
87
- SuccessData[LiquidProbeResult, None],
88
- DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData],
89
+ SuccessData[LiquidProbeResult],
90
+ DefinedErrorData[LiquidNotFoundError],
89
91
  ]
90
- _TryLiquidProbeExecuteReturn = SuccessData[TryLiquidProbeResult, None]
92
+ _TryLiquidProbeExecuteReturn = SuccessData[TryLiquidProbeResult]
93
+
94
+
95
+ class _ExecuteCommonResult(NamedTuple):
96
+ # If the probe succeeded, the z_pos that it returned.
97
+ # Or, if the probe found no liquid, the error representing that,
98
+ # so calling code can propagate those details up.
99
+ z_pos_or_error: float | PipetteLiquidNotFoundError
100
+
101
+ state_update: update_types.StateUpdate
102
+ deck_point: DeckPoint
103
+
104
+
105
+ async def _execute_common(
106
+ state_view: StateView,
107
+ movement: MovementHandler,
108
+ pipetting: PipettingHandler,
109
+ params: _CommonParams,
110
+ ) -> _ExecuteCommonResult:
111
+ pipette_id = params.pipetteId
112
+ labware_id = params.labwareId
113
+ well_name = params.wellName
114
+
115
+ state_update = update_types.StateUpdate()
116
+
117
+ # May raise TipNotAttachedError.
118
+ aspirated_volume = state_view.pipettes.get_aspirated_volume(pipette_id)
119
+
120
+ if aspirated_volume is None:
121
+ # Theoretically, we could avoid raising an error by automatically preparing
122
+ # to aspirate above the well like AspirateImplementation does. However, the
123
+ # only way for this to happen is if someone tries to do a liquid probe with
124
+ # a tip that's previously held liquid, which they should avoid anyway.
125
+ raise PipetteNotReadyToAspirateError(
126
+ "The pipette cannot probe liquid because of a previous blow out."
127
+ " The plunger must be reset while the tip is somewhere away from liquid."
128
+ )
129
+ elif aspirated_volume != 0:
130
+ raise TipNotEmptyError(
131
+ message="The pipette cannot probe for liquid when the tip has liquid in it."
132
+ )
133
+
134
+ if await movement.check_for_valid_position(mount=MountType.LEFT) is False:
135
+ raise MustHomeError(
136
+ message="Current position of pipette is invalid. Please home."
137
+ )
138
+
139
+ # liquid_probe process start position
140
+ position = await movement.move_to_well(
141
+ pipette_id=pipette_id,
142
+ labware_id=labware_id,
143
+ well_name=well_name,
144
+ well_location=params.wellLocation,
145
+ )
146
+ deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
147
+ state_update.set_pipette_location(
148
+ pipette_id=pipette_id,
149
+ new_labware_id=labware_id,
150
+ new_well_name=well_name,
151
+ new_deck_point=deck_point,
152
+ )
153
+
154
+ try:
155
+ z_pos = await pipetting.liquid_probe_in_place(
156
+ pipette_id=pipette_id,
157
+ labware_id=labware_id,
158
+ well_name=well_name,
159
+ well_location=params.wellLocation,
160
+ )
161
+ except PipetteLiquidNotFoundError as exception:
162
+ return _ExecuteCommonResult(
163
+ z_pos_or_error=exception, state_update=state_update, deck_point=deck_point
164
+ )
165
+ else:
166
+ return _ExecuteCommonResult(
167
+ z_pos_or_error=z_pos, state_update=state_update, deck_point=deck_point
168
+ )
91
169
 
92
170
 
93
171
  class LiquidProbeImplementation(
@@ -124,48 +202,17 @@ class LiquidProbeImplementation(
124
202
  MustHomeError: as an undefined error, if the plunger is not in a valid
125
203
  position.
126
204
  """
127
- pipette_id = params.pipetteId
128
- labware_id = params.labwareId
129
- well_name = params.wellName
130
-
131
- # May raise TipNotAttachedError.
132
- aspirated_volume = self._state_view.pipettes.get_aspirated_volume(pipette_id)
133
-
134
- if aspirated_volume is None:
135
- # Theoretically, we could avoid raising an error by automatically preparing
136
- # to aspirate above the well like AspirateImplementation does. However, the
137
- # only way for this to happen is if someone tries to do a liquid probe with
138
- # a tip that's previously held liquid, which they should avoid anyway.
139
- raise PipetteNotReadyToAspirateError(
140
- "The pipette cannot probe liquid because of a previous blow out."
141
- " The plunger must be reset while the tip is somewhere away from liquid."
142
- )
143
- elif aspirated_volume != 0:
144
- raise TipNotEmptyError(
145
- message="The pipette cannot probe for liquid when the tip has liquid in it."
146
- )
147
-
148
- if await self._movement.check_for_valid_position(mount=MountType.LEFT) is False:
149
- raise MustHomeError(
150
- message="Current position of pipette is invalid. Please home."
151
- )
152
-
153
- # liquid_probe process start position
154
- position = await self._movement.move_to_well(
155
- pipette_id=pipette_id,
156
- labware_id=labware_id,
157
- well_name=well_name,
158
- well_location=params.wellLocation,
205
+ z_pos_or_error, state_update, deck_point = await _execute_common(
206
+ self._state_view, self._movement, self._pipetting, params
159
207
  )
160
-
161
- try:
162
- z_pos = await self._pipetting.liquid_probe_in_place(
163
- pipette_id=pipette_id,
164
- labware_id=labware_id,
165
- well_name=well_name,
166
- well_location=params.wellLocation,
208
+ if isinstance(z_pos_or_error, PipetteLiquidNotFoundError):
209
+ state_update.set_liquid_probed(
210
+ labware_id=params.labwareId,
211
+ well_name=params.wellName,
212
+ height=update_types.CLEAR,
213
+ volume=update_types.CLEAR,
214
+ last_probed=self._model_utils.get_timestamp(),
167
215
  )
168
- except PipetteLiquidNotFoundError as e:
169
216
  return DefinedErrorData(
170
217
  public=LiquidNotFoundError(
171
218
  id=self._model_utils.generate_id(),
@@ -174,21 +221,35 @@ class LiquidProbeImplementation(
174
221
  ErrorOccurrence.from_failed(
175
222
  id=self._model_utils.generate_id(),
176
223
  createdAt=self._model_utils.get_timestamp(),
177
- error=e,
224
+ error=z_pos_or_error,
178
225
  )
179
226
  ],
180
227
  ),
181
- private=LiquidNotFoundErrorInternalData(
182
- position=DeckPoint(x=position.x, y=position.y, z=position.z)
183
- ),
228
+ state_update=state_update,
184
229
  )
185
230
  else:
231
+ try:
232
+ well_volume: float | update_types.ClearType = (
233
+ self._state_view.geometry.get_well_volume_at_height(
234
+ labware_id=params.labwareId,
235
+ well_name=params.wellName,
236
+ height=z_pos_or_error,
237
+ )
238
+ )
239
+ except IncompleteLabwareDefinitionError:
240
+ well_volume = update_types.CLEAR
241
+ state_update.set_liquid_probed(
242
+ labware_id=params.labwareId,
243
+ well_name=params.wellName,
244
+ height=z_pos_or_error,
245
+ volume=well_volume,
246
+ last_probed=self._model_utils.get_timestamp(),
247
+ )
186
248
  return SuccessData(
187
249
  public=LiquidProbeResult(
188
- z_position=z_pos,
189
- position=DeckPoint(x=position.x, y=position.y, z=position.z),
250
+ z_position=z_pos_or_error, position=deck_point
190
251
  ),
191
- private=None,
252
+ state_update=state_update,
192
253
  )
193
254
 
194
255
 
@@ -217,40 +278,37 @@ class TryLiquidProbeImplementation(
217
278
  found, `tryLiquidProbe` returns a success result with `z_position=null` instead
218
279
  of a defined error.
219
280
  """
220
- # We defer to the `liquidProbe` implementation. If it returns a defined
221
- # `liquidNotFound` error, we remap that to a success result.
222
- # Otherwise, we return the result or propagate the exception unchanged.
223
-
224
- original_impl = LiquidProbeImplementation(
225
- state_view=self._state_view,
226
- movement=self._movement,
227
- pipetting=self._pipetting,
228
- model_utils=self._model_utils,
281
+ z_pos_or_error, state_update, deck_point = await _execute_common(
282
+ self._state_view, self._movement, self._pipetting, params
229
283
  )
230
- original_result = await original_impl.execute(params)
231
-
232
- match original_result:
233
- case DefinedErrorData(
234
- public=LiquidNotFoundError(),
235
- private=LiquidNotFoundErrorInternalData() as original_private,
236
- ):
237
- return SuccessData(
238
- public=TryLiquidProbeResult(
239
- z_position=None,
240
- position=original_private.position,
241
- ),
242
- private=None,
243
- )
244
- case SuccessData(
245
- public=LiquidProbeResult() as original_public, private=None
246
- ):
247
- return SuccessData(
248
- public=TryLiquidProbeResult(
249
- position=original_public.position,
250
- z_position=original_public.z_position,
251
- ),
252
- private=None,
284
+
285
+ if isinstance(z_pos_or_error, PipetteLiquidNotFoundError):
286
+ z_pos = None
287
+ well_volume: float | update_types.ClearType = update_types.CLEAR
288
+ else:
289
+ z_pos = z_pos_or_error
290
+ try:
291
+ well_volume = self._state_view.geometry.get_well_volume_at_height(
292
+ labware_id=params.labwareId, well_name=params.wellName, height=z_pos
253
293
  )
294
+ except IncompleteLabwareDefinitionError:
295
+ well_volume = update_types.CLEAR
296
+
297
+ state_update.set_liquid_probed(
298
+ labware_id=params.labwareId,
299
+ well_name=params.wellName,
300
+ height=z_pos if z_pos is not None else update_types.CLEAR,
301
+ volume=well_volume,
302
+ last_probed=self._model_utils.get_timestamp(),
303
+ )
304
+
305
+ return SuccessData(
306
+ public=TryLiquidProbeResult(
307
+ z_position=z_pos,
308
+ position=deck_point,
309
+ ),
310
+ state_update=state_update,
311
+ )
254
312
 
255
313
 
256
314
  class LiquidProbe(
@@ -10,6 +10,8 @@ from ..errors import LabwareIsNotAllowedInLocationError
10
10
  from ..resources import labware_validation, fixture_validation
11
11
  from ..types import (
12
12
  LabwareLocation,
13
+ ModuleLocation,
14
+ ModuleModel,
13
15
  OnLabwareLocation,
14
16
  DeckSlotLocation,
15
17
  AddressableAreaLocation,
@@ -17,9 +19,10 @@ from ..types import (
17
19
 
18
20
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
19
21
  from ..errors.error_occurrence import ErrorOccurrence
22
+ from ..state.update_types import StateUpdate
20
23
 
21
24
  if TYPE_CHECKING:
22
- from ..state import StateView
25
+ from ..state.state import StateView
23
26
  from ..execution import EquipmentHandler
24
27
 
25
28
 
@@ -87,7 +90,7 @@ class LoadLabwareResult(BaseModel):
87
90
 
88
91
 
89
92
  class LoadLabwareImplementation(
90
- AbstractCommandImpl[LoadLabwareParams, SuccessData[LoadLabwareResult, None]]
93
+ AbstractCommandImpl[LoadLabwareParams, SuccessData[LoadLabwareResult]]
91
94
  ):
92
95
  """Load labware command implementation."""
93
96
 
@@ -99,7 +102,7 @@ class LoadLabwareImplementation(
99
102
 
100
103
  async def execute(
101
104
  self, params: LoadLabwareParams
102
- ) -> SuccessData[LoadLabwareResult, None]:
105
+ ) -> SuccessData[LoadLabwareResult]:
103
106
  """Load definition and calibration data necessary for a labware."""
104
107
  # TODO (tz, 8-15-2023): extend column validation to column 1 when working
105
108
  # on https://opentrons.atlassian.net/browse/RSS-258 and completing
@@ -115,7 +118,10 @@ class LoadLabwareImplementation(
115
118
 
116
119
  if isinstance(params.location, AddressableAreaLocation):
117
120
  area_name = params.location.addressableAreaName
118
- if not fixture_validation.is_deck_slot(params.location.addressableAreaName):
121
+ if not (
122
+ fixture_validation.is_deck_slot(params.location.addressableAreaName)
123
+ or fixture_validation.is_abs_reader(params.location.addressableAreaName)
124
+ ):
119
125
  raise LabwareIsNotAllowedInLocationError(
120
126
  f"Cannot load {params.loadName} onto addressable area {area_name}"
121
127
  )
@@ -138,6 +144,16 @@ class LoadLabwareImplementation(
138
144
  labware_id=params.labwareId,
139
145
  )
140
146
 
147
+ state_update = StateUpdate()
148
+
149
+ state_update.set_loaded_labware(
150
+ labware_id=loaded_labware.labware_id,
151
+ offset_id=loaded_labware.offsetId,
152
+ definition=loaded_labware.definition,
153
+ location=verified_location,
154
+ display_name=params.displayName,
155
+ )
156
+
141
157
  # TODO(jbl 2023-06-23) these validation checks happen after the labware is loaded, because they rely on
142
158
  # on the definition. In practice this will not cause any issues since they will raise protocol ending
143
159
  # exception, but for correctness should be refactored to do this check beforehand.
@@ -146,6 +162,13 @@ class LoadLabwareImplementation(
146
162
  top_labware_definition=loaded_labware.definition,
147
163
  bottom_labware_id=verified_location.labwareId,
148
164
  )
165
+ # Validate labware for the absorbance reader
166
+ elif isinstance(params.location, ModuleLocation):
167
+ module = self._state_view.modules.get(params.location.moduleId)
168
+ if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1:
169
+ self._state_view.labware.raise_if_labware_incompatible_with_plate_reader(
170
+ loaded_labware.definition
171
+ )
149
172
 
150
173
  return SuccessData(
151
174
  public=LoadLabwareResult(
@@ -153,7 +176,7 @@ class LoadLabwareImplementation(
153
176
  definition=loaded_labware.definition,
154
177
  offsetId=loaded_labware.offsetId,
155
178
  ),
156
- private=None,
179
+ state_update=state_update,
157
180
  )
158
181
 
159
182
 
@@ -4,11 +4,14 @@ from pydantic import BaseModel, Field
4
4
  from typing import Optional, Type, Dict, TYPE_CHECKING
5
5
  from typing_extensions import Literal
6
6
 
7
+ from opentrons.protocol_engine.state.update_types import StateUpdate
8
+
7
9
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
8
10
  from ..errors.error_occurrence import ErrorOccurrence
9
11
 
10
12
  if TYPE_CHECKING:
11
- from ..state import StateView
13
+ from ..state.state import StateView
14
+ from ..resources import ModelUtils
12
15
 
13
16
  LoadLiquidCommandType = Literal["loadLiquid"]
14
17
 
@@ -37,16 +40,17 @@ class LoadLiquidResult(BaseModel):
37
40
 
38
41
 
39
42
  class LoadLiquidImplementation(
40
- AbstractCommandImpl[LoadLiquidParams, SuccessData[LoadLiquidResult, None]]
43
+ AbstractCommandImpl[LoadLiquidParams, SuccessData[LoadLiquidResult]]
41
44
  ):
42
45
  """Load liquid command implementation."""
43
46
 
44
- def __init__(self, state_view: StateView, **kwargs: object) -> None:
47
+ def __init__(
48
+ self, state_view: StateView, model_utils: ModelUtils, **kwargs: object
49
+ ) -> None:
45
50
  self._state_view = state_view
51
+ self._model_utils = model_utils
46
52
 
47
- async def execute(
48
- self, params: LoadLiquidParams
49
- ) -> SuccessData[LoadLiquidResult, None]:
53
+ async def execute(self, params: LoadLiquidParams) -> SuccessData[LoadLiquidResult]:
50
54
  """Load data necessary for a liquid."""
51
55
  self._state_view.liquid.validate_liquid_id(params.liquidId)
52
56
 
@@ -54,7 +58,14 @@ class LoadLiquidImplementation(
54
58
  labware_id=params.labwareId, wells=params.volumeByWell
55
59
  )
56
60
 
57
- return SuccessData(public=LoadLiquidResult(), private=None)
61
+ state_update = StateUpdate()
62
+ state_update.set_liquid_loaded(
63
+ labware_id=params.labwareId,
64
+ volumes=params.volumeByWell,
65
+ last_loaded=self._model_utils.get_timestamp(),
66
+ )
67
+
68
+ return SuccessData(public=LoadLiquidResult(), state_update=state_update)
58
69
 
59
70
 
60
71
  class LoadLiquid(BaseCommand[LoadLiquidParams, LoadLiquidResult, ErrorOccurrence]):
@@ -16,8 +16,9 @@ from opentrons.types import DeckSlotName
16
16
 
17
17
  from opentrons.protocol_engine.resources import deck_configuration_provider
18
18
 
19
+
19
20
  if TYPE_CHECKING:
20
- from ..state import StateView
21
+ from ..state.state import StateView
21
22
  from ..execution import EquipmentHandler
22
23
 
23
24
 
@@ -103,7 +104,7 @@ class LoadModuleResult(BaseModel):
103
104
 
104
105
 
105
106
  class LoadModuleImplementation(
106
- AbstractCommandImpl[LoadModuleParams, SuccessData[LoadModuleResult, None]]
107
+ AbstractCommandImpl[LoadModuleParams, SuccessData[LoadModuleResult]]
107
108
  ):
108
109
  """The implementation of the load module command."""
109
110
 
@@ -113,9 +114,7 @@ class LoadModuleImplementation(
113
114
  self._equipment = equipment
114
115
  self._state_view = state_view
115
116
 
116
- async def execute(
117
- self, params: LoadModuleParams
118
- ) -> SuccessData[LoadModuleResult, None]:
117
+ async def execute(self, params: LoadModuleParams) -> SuccessData[LoadModuleResult]:
119
118
  """Check that the requested module is attached and assign its identifier."""
120
119
  module_type = params.model.as_type()
121
120
  self._ensure_module_location(params.location.slotName, module_type)
@@ -158,7 +157,6 @@ class LoadModuleImplementation(
158
157
  model=loaded_module.definition.model,
159
158
  definition=loaded_module.definition,
160
159
  ),
161
- private=None,
162
160
  )
163
161
 
164
162
  def _ensure_module_location(
@@ -1,6 +1,7 @@
1
1
  """Load pipette command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
3
 
4
+ from opentrons.protocol_engine.state.update_types import StateUpdate
4
5
  from opentrons_shared_data.pipette.pipette_load_name_conversions import (
5
6
  convert_to_pipette_name_type,
6
7
  )
@@ -16,23 +17,16 @@ from opentrons.types import MountType
16
17
 
17
18
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
18
19
  from ..errors.error_occurrence import ErrorOccurrence
19
- from .configuring_common import PipetteConfigUpdateResultMixin
20
20
  from ..errors import InvalidSpecificationForRobotTypeError, InvalidLoadPipetteSpecsError
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from ..execution import EquipmentHandler
24
- from ..state import StateView
24
+ from ..state.state import StateView
25
25
 
26
26
 
27
27
  LoadPipetteCommandType = Literal["loadPipette"]
28
28
 
29
29
 
30
- class LoadPipettePrivateResult(PipetteConfigUpdateResultMixin):
31
- """The not-to-be-exposed results of a load pipette call."""
32
-
33
- ...
34
-
35
-
36
30
  class LoadPipetteParams(BaseModel):
37
31
  """Payload needed to load a pipette on to a mount."""
38
32
 
@@ -72,9 +66,7 @@ class LoadPipetteResult(BaseModel):
72
66
 
73
67
 
74
68
  class LoadPipetteImplementation(
75
- AbstractCommandImpl[
76
- LoadPipetteParams, SuccessData[LoadPipetteResult, LoadPipettePrivateResult]
77
- ]
69
+ AbstractCommandImpl[LoadPipetteParams, SuccessData[LoadPipetteResult]]
78
70
  ):
79
71
  """Load pipette command implementation."""
80
72
 
@@ -86,7 +78,7 @@ class LoadPipetteImplementation(
86
78
 
87
79
  async def execute(
88
80
  self, params: LoadPipetteParams
89
- ) -> SuccessData[LoadPipetteResult, LoadPipettePrivateResult]:
81
+ ) -> SuccessData[LoadPipetteResult]:
90
82
  """Check that requested pipette is attached and assign its identifier."""
91
83
  pipette_generation = convert_to_pipette_name_type(
92
84
  params.pipetteName.value
@@ -123,13 +115,22 @@ class LoadPipetteImplementation(
123
115
  tip_overlap_version=params.tipOverlapNotAfterVersion,
124
116
  )
125
117
 
118
+ state_update = StateUpdate()
119
+ state_update.set_load_pipette(
120
+ pipette_id=loaded_pipette.pipette_id,
121
+ pipette_name=params.pipetteName,
122
+ mount=params.mount,
123
+ liquid_presence_detection=params.liquidPresenceDetection,
124
+ )
125
+ state_update.update_pipette_config(
126
+ pipette_id=loaded_pipette.pipette_id,
127
+ serial_number=loaded_pipette.serial_number,
128
+ config=loaded_pipette.static_config,
129
+ )
130
+
126
131
  return SuccessData(
127
132
  public=LoadPipetteResult(pipetteId=loaded_pipette.pipette_id),
128
- private=LoadPipettePrivateResult(
129
- pipette_id=loaded_pipette.pipette_id,
130
- serial_number=loaded_pipette.serial_number,
131
- config=loaded_pipette.static_config,
132
- ),
133
+ state_update=state_update,
133
134
  )
134
135
 
135
136
 
@@ -13,7 +13,7 @@ from ...errors.error_occurrence import ErrorOccurrence
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from opentrons.protocol_engine.execution import EquipmentHandler
16
- from opentrons.protocol_engine.state import StateView
16
+ from opentrons.protocol_engine.state.state import StateView
17
17
 
18
18
 
19
19
  DisengageCommandType = Literal["magneticModule/disengage"]
@@ -38,7 +38,7 @@ class DisengageResult(BaseModel):
38
38
 
39
39
 
40
40
  class DisengageImplementation(
41
- AbstractCommandImpl[DisengageParams, SuccessData[DisengageResult, None]]
41
+ AbstractCommandImpl[DisengageParams, SuccessData[DisengageResult]]
42
42
  ):
43
43
  """The implementation of a Magnetic Module disengage command."""
44
44
 
@@ -51,9 +51,7 @@ class DisengageImplementation(
51
51
  self._state_view = state_view
52
52
  self._equipment = equipment
53
53
 
54
- async def execute(
55
- self, params: DisengageParams
56
- ) -> SuccessData[DisengageResult, None]:
54
+ async def execute(self, params: DisengageParams) -> SuccessData[DisengageResult]:
57
55
  """Execute a Magnetic Module disengage command.
58
56
 
59
57
  Raises:
@@ -75,7 +73,9 @@ class DisengageImplementation(
75
73
  if hardware_module is not None: # Not virtualizing modules.
76
74
  await hardware_module.deactivate()
77
75
 
78
- return SuccessData(public=DisengageResult(), private=None)
76
+ return SuccessData(
77
+ public=DisengageResult(),
78
+ )
79
79
 
80
80
 
81
81
  class Disengage(BaseCommand[DisengageParams, DisengageResult, ErrorOccurrence]):
@@ -10,7 +10,7 @@ from ...errors.error_occurrence import ErrorOccurrence
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from opentrons.protocol_engine.execution import EquipmentHandler
13
- from opentrons.protocol_engine.state import StateView
13
+ from opentrons.protocol_engine.state.state import StateView
14
14
 
15
15
 
16
16
  EngageCommandType = Literal["magneticModule/engage"]
@@ -54,7 +54,7 @@ class EngageResult(BaseModel):
54
54
 
55
55
 
56
56
  class EngageImplementation(
57
- AbstractCommandImpl[EngageParams, SuccessData[EngageResult, None]]
57
+ AbstractCommandImpl[EngageParams, SuccessData[EngageResult]]
58
58
  ):
59
59
  """The implementation of a Magnetic Module engage command."""
60
60
 
@@ -67,7 +67,7 @@ class EngageImplementation(
67
67
  self._state_view = state_view
68
68
  self._equipment = equipment
69
69
 
70
- async def execute(self, params: EngageParams) -> SuccessData[EngageResult, None]:
70
+ async def execute(self, params: EngageParams) -> SuccessData[EngageResult]:
71
71
  """Execute a Magnetic Module engage command.
72
72
 
73
73
  Raises:
@@ -95,7 +95,9 @@ class EngageImplementation(
95
95
  if hardware_module is not None: # Not virtualizing modules.
96
96
  await hardware_module.engage(height=hardware_height)
97
97
 
98
- return SuccessData(public=EngageResult(), private=None)
98
+ return SuccessData(
99
+ public=EngageResult(),
100
+ )
99
101
 
100
102
 
101
103
  class Engage(BaseCommand[EngageParams, EngageResult, ErrorOccurrence]):