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
@@ -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]):
@@ -5,6 +5,7 @@ from typing_extensions import Literal
5
5
  from pydantic import BaseModel, Field
6
6
 
7
7
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
8
+ from ..errors import ModuleNotLoadedError
8
9
  from ..errors.error_occurrence import ErrorOccurrence
9
10
  from ..types import (
10
11
  DeckSlotLocation,
@@ -16,8 +17,10 @@ from opentrons.types import DeckSlotName
16
17
 
17
18
  from opentrons.protocol_engine.resources import deck_configuration_provider
18
19
 
20
+ from opentrons.drivers.types import AbsorbanceReaderLidStatus
21
+
19
22
  if TYPE_CHECKING:
20
- from ..state import StateView
23
+ from ..state.state import StateView
21
24
  from ..execution import EquipmentHandler
22
25
 
23
26
 
@@ -103,7 +106,7 @@ class LoadModuleResult(BaseModel):
103
106
 
104
107
 
105
108
  class LoadModuleImplementation(
106
- AbstractCommandImpl[LoadModuleParams, SuccessData[LoadModuleResult, None]]
109
+ AbstractCommandImpl[LoadModuleParams, SuccessData[LoadModuleResult]]
107
110
  ):
108
111
  """The implementation of the load module command."""
109
112
 
@@ -113,9 +116,7 @@ class LoadModuleImplementation(
113
116
  self._equipment = equipment
114
117
  self._state_view = state_view
115
118
 
116
- async def execute(
117
- self, params: LoadModuleParams
118
- ) -> SuccessData[LoadModuleResult, None]:
119
+ async def execute(self, params: LoadModuleParams) -> SuccessData[LoadModuleResult]:
119
120
  """Check that the requested module is attached and assign its identifier."""
120
121
  module_type = params.model.as_type()
121
122
  self._ensure_module_location(params.location.slotName, module_type)
@@ -151,6 +152,43 @@ class LoadModuleImplementation(
151
152
  module_id=params.moduleId,
152
153
  )
153
154
 
155
+ # Handle lid position update for loaded Plate Reader module on deck
156
+ if (
157
+ not self._state_view.config.use_virtual_modules
158
+ and params.model == ModuleModel.ABSORBANCE_READER_V1
159
+ and params.moduleId is not None
160
+ ):
161
+ try:
162
+ abs_reader = self._equipment.get_module_hardware_api(
163
+ self._state_view.modules.get_absorbance_reader_substate(
164
+ params.moduleId
165
+ ).module_id
166
+ )
167
+ except ModuleNotLoadedError:
168
+ abs_reader = None
169
+
170
+ if abs_reader is not None:
171
+ result = await abs_reader.get_current_lid_status()
172
+ if (
173
+ isinstance(result, AbsorbanceReaderLidStatus)
174
+ and result is not AbsorbanceReaderLidStatus.ON
175
+ ):
176
+ reader_area = self._state_view.modules.ensure_and_convert_module_fixture_location(
177
+ params.location.slotName,
178
+ self._state_view.config.deck_type,
179
+ params.model,
180
+ )
181
+ lid_labware = self._state_view.labware.get_by_addressable_area(
182
+ reader_area
183
+ )
184
+
185
+ if lid_labware is not None:
186
+ self._state_view.labware._state.labware_by_id[
187
+ lid_labware.id
188
+ ].location = self._state_view.modules.absorbance_reader_dock_location(
189
+ params.moduleId
190
+ )
191
+
154
192
  return SuccessData(
155
193
  public=LoadModuleResult(
156
194
  moduleId=loaded_module.module_id,
@@ -158,7 +196,6 @@ class LoadModuleImplementation(
158
196
  model=loaded_module.definition.model,
159
197
  definition=loaded_module.definition,
160
198
  ),
161
- private=None,
162
199
  )
163
200
 
164
201
  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]):
@@ -1,12 +1,19 @@
1
1
  """Models and implementation for the ``moveLabware`` command."""
2
2
 
3
3
  from __future__ import annotations
4
+ from opentrons_shared_data.errors.exceptions import (
5
+ FailedGripperPickupError,
6
+ LabwareDroppedError,
7
+ StallOrCollisionDetectedError,
8
+ )
4
9
  from pydantic import BaseModel, Field
5
10
  from typing import TYPE_CHECKING, Optional, Type
6
11
  from typing_extensions import Literal
7
12
 
13
+ from opentrons.protocol_engine.resources.model_utils import ModelUtils
8
14
  from opentrons.types import Point
9
15
  from ..types import (
16
+ CurrentWell,
10
17
  LabwareLocation,
11
18
  DeckSlotLocation,
12
19
  OnLabwareLocation,
@@ -17,13 +24,20 @@ from ..types import (
17
24
  )
18
25
  from ..errors import LabwareMovementNotAllowedError, NotSupportedOnRobotType
19
26
  from ..resources import labware_validation, fixture_validation
20
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
27
+ from .command import (
28
+ AbstractCommandImpl,
29
+ BaseCommand,
30
+ BaseCommandCreate,
31
+ DefinedErrorData,
32
+ SuccessData,
33
+ )
21
34
  from ..errors.error_occurrence import ErrorOccurrence
35
+ from ..state.update_types import StateUpdate
22
36
  from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH
23
37
 
24
38
  if TYPE_CHECKING:
25
39
  from ..execution import EquipmentHandler, RunControlHandler, LabwareMovementHandler
26
- from ..state import StateView
40
+ from ..state.state import StateView
27
41
 
28
42
 
29
43
  MoveLabwareCommandType = Literal["moveLabware"]
@@ -33,7 +47,6 @@ MoveLabwareCommandType = Literal["moveLabware"]
33
47
  _TRASH_CHUTE_DROP_BUFFER_MM = 8
34
48
 
35
49
 
36
- # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237
37
50
  class MoveLabwareParams(BaseModel):
38
51
  """Input parameters for a ``moveLabware`` command."""
39
52
 
@@ -74,28 +87,42 @@ class MoveLabwareResult(BaseModel):
74
87
  )
75
88
 
76
89
 
77
- class MoveLabwareImplementation(
78
- AbstractCommandImpl[MoveLabwareParams, SuccessData[MoveLabwareResult, None]]
79
- ):
90
+ class GripperMovementError(ErrorOccurrence):
91
+ """Returned when something physically goes wrong when the gripper moves labware.
92
+
93
+ When this error happens, the engine will leave the labware in its original place.
94
+ """
95
+
96
+ isDefined: bool = True
97
+
98
+ errorType: Literal["gripperMovement"] = "gripperMovement"
99
+
100
+
101
+ _ExecuteReturn = SuccessData[MoveLabwareResult] | DefinedErrorData[GripperMovementError]
102
+
103
+
104
+ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteReturn]):
80
105
  """The execution implementation for ``moveLabware`` commands."""
81
106
 
82
107
  def __init__(
83
108
  self,
109
+ model_utils: ModelUtils,
84
110
  state_view: StateView,
85
111
  equipment: EquipmentHandler,
86
112
  labware_movement: LabwareMovementHandler,
87
113
  run_control: RunControlHandler,
88
114
  **kwargs: object,
89
115
  ) -> None:
116
+ self._model_utils = model_utils
90
117
  self._state_view = state_view
91
118
  self._equipment = equipment
92
119
  self._labware_movement = labware_movement
93
120
  self._run_control = run_control
94
121
 
95
- async def execute( # noqa: C901
96
- self, params: MoveLabwareParams
97
- ) -> SuccessData[MoveLabwareResult, None]:
122
+ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C901
98
123
  """Move a loaded labware to a new location."""
124
+ state_update = StateUpdate()
125
+
99
126
  # Allow propagation of LabwareNotLoadedError.
100
127
  current_labware = self._state_view.labware.get(labware_id=params.labwareId)
101
128
  current_labware_definition = self._state_view.labware.get_definition(
@@ -157,6 +184,10 @@ class MoveLabwareImplementation(
157
184
  top_labware_definition=current_labware_definition,
158
185
  bottom_labware_id=available_new_location.labwareId,
159
186
  )
187
+ if params.labwareId == available_new_location.labwareId:
188
+ raise LabwareMovementNotAllowedError(
189
+ "Cannot move a labware onto itself."
190
+ )
160
191
 
161
192
  # Allow propagation of ModuleNotLoadedError.
162
193
  new_offset_id = self._equipment.find_applicable_labware_offset_id(
@@ -201,24 +232,80 @@ class MoveLabwareImplementation(
201
232
  dropOffset=params.dropOffset or LabwareOffsetVector(x=0, y=0, z=0),
202
233
  )
203
234
 
204
- # Skips gripper moves when using virtual gripper
205
- await self._labware_movement.move_labware_with_gripper(
206
- labware_id=params.labwareId,
207
- current_location=validated_current_loc,
208
- new_location=validated_new_loc,
209
- user_offset_data=user_offset_data,
210
- post_drop_slide_offset=post_drop_slide_offset,
211
- )
235
+ try:
236
+ # Skips gripper moves when using virtual gripper
237
+ await self._labware_movement.move_labware_with_gripper(
238
+ labware_id=params.labwareId,
239
+ current_location=validated_current_loc,
240
+ new_location=validated_new_loc,
241
+ user_offset_data=user_offset_data,
242
+ post_drop_slide_offset=post_drop_slide_offset,
243
+ )
244
+ except (
245
+ FailedGripperPickupError,
246
+ LabwareDroppedError,
247
+ StallOrCollisionDetectedError,
248
+ # todo(mm, 2024-09-26): Catch LabwareNotPickedUpError when that exists and
249
+ # move_labware_with_gripper() raises it.
250
+ ) as exception:
251
+ gripper_movement_error: GripperMovementError | None = (
252
+ GripperMovementError(
253
+ id=self._model_utils.generate_id(),
254
+ createdAt=self._model_utils.get_timestamp(),
255
+ errorCode=exception.code.value.code,
256
+ detail=exception.code.value.detail,
257
+ wrappedErrors=[
258
+ ErrorOccurrence.from_failed(
259
+ id=self._model_utils.generate_id(),
260
+ createdAt=self._model_utils.get_timestamp(),
261
+ error=exception,
262
+ )
263
+ ],
264
+ )
265
+ )
266
+ else:
267
+ gripper_movement_error = None
268
+
269
+ # All mounts will have been retracted as part of the gripper move.
270
+ state_update.clear_all_pipette_locations()
271
+
272
+ if gripper_movement_error:
273
+ return DefinedErrorData(
274
+ public=gripper_movement_error,
275
+ state_update=state_update,
276
+ )
277
+
212
278
  elif params.strategy == LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE:
213
279
  # Pause to allow for manual labware movement
214
280
  await self._run_control.wait_for_resume()
215
281
 
282
+ # We may have just moved the labware that contains the current well out from
283
+ # under the pipette. Clear the current location to reflect the fact that the
284
+ # pipette is no longer over any labware. This is necessary for safe path
285
+ # planning in case the next movement goes to the same labware (now in a new
286
+ # place).
287
+ pipette_location = self._state_view.pipettes.get_current_location()
288
+ if (
289
+ isinstance(pipette_location, CurrentWell)
290
+ and pipette_location.labware_id == params.labwareId
291
+ ):
292
+ state_update.clear_all_pipette_locations()
293
+
294
+ state_update.set_labware_location(
295
+ labware_id=params.labwareId,
296
+ new_location=available_new_location,
297
+ new_offset_id=new_offset_id,
298
+ )
299
+
216
300
  return SuccessData(
217
- public=MoveLabwareResult(offsetId=new_offset_id), private=None
301
+ public=MoveLabwareResult(offsetId=new_offset_id),
302
+ state_update=state_update,
218
303
  )
219
304
 
220
305
 
221
- class MoveLabware(BaseCommand[MoveLabwareParams, MoveLabwareResult, ErrorOccurrence]):
306
+ class MoveLabware(
307
+ BaseCommand[MoveLabwareParams, MoveLabwareResult, GripperMovementError]
308
+ ):
222
309
  """A ``moveLabware`` command."""
223
310
 
224
311
  commandType: MoveLabwareCommandType = "moveLabware"
@@ -4,6 +4,8 @@ from pydantic import BaseModel, Field
4
4
  from typing import TYPE_CHECKING, Optional, Type
5
5
  from typing_extensions import Literal
6
6
 
7
+
8
+ from ..state import update_types
7
9
  from ..types import MovementAxis, DeckPoint
8
10
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
9
11
  from ..errors.error_occurrence import ErrorOccurrence
@@ -37,7 +39,7 @@ class MoveRelativeResult(DestinationPositionResult):
37
39
 
38
40
 
39
41
  class MoveRelativeImplementation(
40
- AbstractCommandImpl[MoveRelativeParams, SuccessData[MoveRelativeResult, None]]
42
+ AbstractCommandImpl[MoveRelativeParams, SuccessData[MoveRelativeResult]]
41
43
  ):
42
44
  """Move relative command implementation."""
43
45
 
@@ -46,16 +48,26 @@ class MoveRelativeImplementation(
46
48
 
47
49
  async def execute(
48
50
  self, params: MoveRelativeParams
49
- ) -> SuccessData[MoveRelativeResult, None]:
51
+ ) -> SuccessData[MoveRelativeResult]:
50
52
  """Move (jog) a given pipette a relative distance."""
53
+ state_update = update_types.StateUpdate()
54
+
51
55
  x, y, z = await self._movement.move_relative(
52
56
  pipette_id=params.pipetteId,
53
57
  axis=params.axis,
54
58
  distance=params.distance,
55
59
  )
60
+ deck_point = DeckPoint.construct(x=x, y=y, z=z)
61
+ state_update.pipette_location = update_types.PipetteLocationUpdate(
62
+ pipette_id=params.pipetteId,
63
+ # TODO(jbl 2023-02-14): Need to investigate whether move relative should clear current location
64
+ new_location=update_types.NO_CHANGE,
65
+ new_deck_point=deck_point,
66
+ )
56
67
 
57
68
  return SuccessData(
58
- public=MoveRelativeResult(position=DeckPoint(x=x, y=y, z=z)), private=None
69
+ public=MoveRelativeResult(position=deck_point),
70
+ state_update=state_update,
59
71
  )
60
72
 
61
73
 
@@ -4,7 +4,10 @@ from pydantic import Field
4
4
  from typing import TYPE_CHECKING, Optional, Type
5
5
  from typing_extensions import Literal
6
6
 
7
+ from opentrons_shared_data.pipette.types import PipetteNameType
8
+
7
9
  from ..errors import LocationNotAccessibleByPipetteError
10
+ from ..state import update_types
8
11
  from ..types import DeckPoint, AddressableOffsetVector
9
12
  from ..resources import fixture_validation
10
13
  from .pipetting_common import (
@@ -17,7 +20,7 @@ from ..errors.error_occurrence import ErrorOccurrence
17
20
 
18
21
  if TYPE_CHECKING:
19
22
  from ..execution import MovementHandler
20
- from ..state import StateView
23
+ from ..state.state import StateView
21
24
 
22
25
  MoveToAddressableAreaCommandType = Literal["moveToAddressableArea"]
23
26
 
@@ -73,7 +76,7 @@ class MoveToAddressableAreaResult(DestinationPositionResult):
73
76
 
74
77
  class MoveToAddressableAreaImplementation(
75
78
  AbstractCommandImpl[
76
- MoveToAddressableAreaParams, SuccessData[MoveToAddressableAreaResult, None]
79
+ MoveToAddressableAreaParams, SuccessData[MoveToAddressableAreaResult]
77
80
  ]
78
81
  ):
79
82
  """Move to addressable area command implementation."""
@@ -86,11 +89,26 @@ class MoveToAddressableAreaImplementation(
86
89
 
87
90
  async def execute(
88
91
  self, params: MoveToAddressableAreaParams
89
- ) -> SuccessData[MoveToAddressableAreaResult, None]:
92
+ ) -> SuccessData[MoveToAddressableAreaResult]:
90
93
  """Move the requested pipette to the requested addressable area."""
94
+ state_update = update_types.StateUpdate()
95
+
91
96
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
92
97
  params.addressableAreaName
93
98
  )
99
+ loaded_pipette = self._state_view.pipettes.get(params.pipetteId)
100
+ if loaded_pipette.pipetteName in (
101
+ PipetteNameType.P10_SINGLE,
102
+ PipetteNameType.P10_MULTI,
103
+ PipetteNameType.P50_MULTI,
104
+ PipetteNameType.P50_SINGLE,
105
+ PipetteNameType.P300_SINGLE,
106
+ PipetteNameType.P300_MULTI,
107
+ PipetteNameType.P1000_SINGLE,
108
+ ):
109
+ extra_z_offset: Optional[float] = 5.0
110
+ else:
111
+ extra_z_offset = None
94
112
 
95
113
  if fixture_validation.is_staging_slot(params.addressableAreaName):
96
114
  raise LocationNotAccessibleByPipetteError(
@@ -105,11 +123,18 @@ class MoveToAddressableAreaImplementation(
105
123
  minimum_z_height=params.minimumZHeight,
106
124
  speed=params.speed,
107
125
  stay_at_highest_possible_z=params.stayAtHighestPossibleZ,
126
+ highest_possible_z_extra_offset=extra_z_offset,
127
+ )
128
+ deck_point = DeckPoint.construct(x=x, y=y, z=z)
129
+ state_update.set_pipette_location(
130
+ pipette_id=params.pipetteId,
131
+ new_addressable_area_name=params.addressableAreaName,
132
+ new_deck_point=deck_point,
108
133
  )
109
134
 
110
135
  return SuccessData(
111
136
  public=MoveToAddressableAreaResult(position=DeckPoint(x=x, y=y, z=z)),
112
- private=None,
137
+ state_update=state_update,
113
138
  )
114
139
 
115
140
 
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional, Type
5
5
  from typing_extensions import Literal
6
6
 
7
7
  from ..errors import LocationNotAccessibleByPipetteError
8
+ from ..state import update_types
8
9
  from ..types import DeckPoint, AddressableOffsetVector
9
10
  from ..resources import fixture_validation
10
11
  from .pipetting_common import (
@@ -17,7 +18,7 @@ from ..errors.error_occurrence import ErrorOccurrence
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from ..execution import MovementHandler
20
- from ..state import StateView
21
+ from ..state.state import StateView
21
22
 
22
23
  MoveToAddressableAreaForDropTipCommandType = Literal["moveToAddressableAreaForDropTip"]
23
24
 
@@ -85,7 +86,7 @@ class MoveToAddressableAreaForDropTipResult(DestinationPositionResult):
85
86
  class MoveToAddressableAreaForDropTipImplementation(
86
87
  AbstractCommandImpl[
87
88
  MoveToAddressableAreaForDropTipParams,
88
- SuccessData[MoveToAddressableAreaForDropTipResult, None],
89
+ SuccessData[MoveToAddressableAreaForDropTipResult],
89
90
  ]
90
91
  ):
91
92
  """Move to addressable area for drop tip command implementation."""
@@ -98,8 +99,10 @@ class MoveToAddressableAreaForDropTipImplementation(
98
99
 
99
100
  async def execute(
100
101
  self, params: MoveToAddressableAreaForDropTipParams
101
- ) -> SuccessData[MoveToAddressableAreaForDropTipResult, None]:
102
+ ) -> SuccessData[MoveToAddressableAreaForDropTipResult]:
102
103
  """Move the requested pipette to the requested addressable area in preperation of a drop tip."""
104
+ state_update = update_types.StateUpdate()
105
+
103
106
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
104
107
  params.addressableAreaName
105
108
  )
@@ -126,12 +129,18 @@ class MoveToAddressableAreaForDropTipImplementation(
126
129
  speed=params.speed,
127
130
  ignore_tip_configuration=params.ignoreTipConfiguration,
128
131
  )
132
+ deck_point = DeckPoint.construct(x=x, y=y, z=z)
133
+ state_update.set_pipette_location(
134
+ pipette_id=params.pipetteId,
135
+ new_addressable_area_name=params.addressableAreaName,
136
+ new_deck_point=deck_point,
137
+ )
129
138
 
130
139
  return SuccessData(
131
140
  public=MoveToAddressableAreaForDropTipResult(
132
141
  position=DeckPoint(x=x, y=y, z=z)
133
142
  ),
134
- private=None,
143
+ state_update=state_update,
135
144
  )
136
145
 
137
146