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
@@ -1,5 +1,6 @@
1
1
  """Command models to drop tip in place while plunger positions are unknown."""
2
2
  from __future__ import annotations
3
+ from opentrons.protocol_engine.state.update_types import StateUpdate
3
4
  from pydantic import Field, BaseModel
4
5
  from typing import TYPE_CHECKING, Optional, Type
5
6
  from typing_extensions import Literal
@@ -14,7 +15,7 @@ from ...resources import ensure_ot3_hardware
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from ...execution import TipHandler
17
- from ...state import StateView
18
+ from ...state.state import StateView
18
19
 
19
20
 
20
21
  UnsafeDropTipInPlaceCommandType = Literal["unsafe/dropTipInPlace"]
@@ -41,7 +42,7 @@ class UnsafeDropTipInPlaceResult(BaseModel):
41
42
 
42
43
  class UnsafeDropTipInPlaceImplementation(
43
44
  AbstractCommandImpl[
44
- UnsafeDropTipInPlaceParams, SuccessData[UnsafeDropTipInPlaceResult, None]
45
+ UnsafeDropTipInPlaceParams, SuccessData[UnsafeDropTipInPlaceResult]
45
46
  ]
46
47
  ):
47
48
  """Unsafe drop tip in place command implementation."""
@@ -59,7 +60,7 @@ class UnsafeDropTipInPlaceImplementation(
59
60
 
60
61
  async def execute(
61
62
  self, params: UnsafeDropTipInPlaceParams
62
- ) -> SuccessData[UnsafeDropTipInPlaceResult, None]:
63
+ ) -> SuccessData[UnsafeDropTipInPlaceResult]:
63
64
  """Drop a tip using the requested pipette, even if the plunger position is not known."""
64
65
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
65
66
  pipette_location = self._state_view.motion.get_pipette_location(
@@ -72,7 +73,14 @@ class UnsafeDropTipInPlaceImplementation(
72
73
  pipette_id=params.pipetteId, home_after=params.homeAfter
73
74
  )
74
75
 
75
- return SuccessData(public=UnsafeDropTipInPlaceResult(), private=None)
76
+ state_update = StateUpdate()
77
+ state_update.update_pipette_tip_state(
78
+ pipette_id=params.pipetteId, tip_geometry=None
79
+ )
80
+
81
+ return SuccessData(
82
+ public=UnsafeDropTipInPlaceResult(), state_update=state_update
83
+ )
76
84
 
77
85
 
78
86
  class UnsafeDropTipInPlace(
@@ -32,7 +32,7 @@ class UnsafeEngageAxesResult(BaseModel):
32
32
  class UnsafeEngageAxesImplementation(
33
33
  AbstractCommandImpl[
34
34
  UnsafeEngageAxesParams,
35
- SuccessData[UnsafeEngageAxesResult, None],
35
+ SuccessData[UnsafeEngageAxesResult],
36
36
  ]
37
37
  ):
38
38
  """Enable axes command implementation."""
@@ -48,7 +48,7 @@ class UnsafeEngageAxesImplementation(
48
48
 
49
49
  async def execute(
50
50
  self, params: UnsafeEngageAxesParams
51
- ) -> SuccessData[UnsafeEngageAxesResult, None]:
51
+ ) -> SuccessData[UnsafeEngageAxesResult]:
52
52
  """Enable exes."""
53
53
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
54
54
  await ot3_hardware_api.engage_axes(
@@ -57,7 +57,9 @@ class UnsafeEngageAxesImplementation(
57
57
  for axis in params.axes
58
58
  ]
59
59
  )
60
- return SuccessData(public=UnsafeEngageAxesResult(), private=None)
60
+ return SuccessData(
61
+ public=UnsafeEngageAxesResult(),
62
+ )
61
63
 
62
64
 
63
65
  class UnsafeEngageAxes(
@@ -0,0 +1,208 @@
1
+ """Place labware payload, result, and implementaiton."""
2
+
3
+ from __future__ import annotations
4
+ from typing import TYPE_CHECKING, Optional, Type
5
+ from typing_extensions import Literal
6
+
7
+ from opentrons_shared_data.labware.types import LabwareUri
8
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
9
+ from pydantic import BaseModel, Field
10
+
11
+ from opentrons.hardware_control.types import Axis, OT3Mount
12
+ from opentrons.motion_planning.waypoints import get_gripper_labware_placement_waypoints
13
+ from opentrons.protocol_engine.errors.exceptions import (
14
+ CannotPerformGripperAction,
15
+ GripperNotAttachedError,
16
+ )
17
+ from opentrons.types import Point
18
+
19
+ from ...types import (
20
+ DeckSlotLocation,
21
+ ModuleModel,
22
+ OnDeckLabwareLocation,
23
+ )
24
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
25
+ from ...errors.error_occurrence import ErrorOccurrence
26
+ from ...resources import ensure_ot3_hardware
27
+
28
+ from opentrons.hardware_control import HardwareControlAPI, OT3HardwareControlAPI
29
+
30
+ if TYPE_CHECKING:
31
+ from ...state.state import StateView
32
+ from ...execution.equipment import EquipmentHandler
33
+
34
+
35
+ UnsafePlaceLabwareCommandType = Literal["unsafe/placeLabware"]
36
+
37
+
38
+ class UnsafePlaceLabwareParams(BaseModel):
39
+ """Payload required for an UnsafePlaceLabware command."""
40
+
41
+ labwareURI: str = Field(..., description="Labware URI for labware.")
42
+ location: OnDeckLabwareLocation = Field(
43
+ ..., description="Where to place the labware."
44
+ )
45
+
46
+
47
+ class UnsafePlaceLabwareResult(BaseModel):
48
+ """Result data from the execution of an UnsafePlaceLabware command."""
49
+
50
+
51
+ class UnsafePlaceLabwareImplementation(
52
+ AbstractCommandImpl[
53
+ UnsafePlaceLabwareParams,
54
+ SuccessData[UnsafePlaceLabwareResult],
55
+ ]
56
+ ):
57
+ """The UnsafePlaceLabware command implementation."""
58
+
59
+ def __init__(
60
+ self,
61
+ hardware_api: HardwareControlAPI,
62
+ state_view: StateView,
63
+ equipment: EquipmentHandler,
64
+ **kwargs: object,
65
+ ) -> None:
66
+ self._hardware_api = hardware_api
67
+ self._state_view = state_view
68
+ self._equipment = equipment
69
+
70
+ async def execute(
71
+ self, params: UnsafePlaceLabwareParams
72
+ ) -> SuccessData[UnsafePlaceLabwareResult]:
73
+ """Place Labware.
74
+
75
+ This command is used only when the gripper is in the middle of moving
76
+ labware but is interrupted before completing the move. (i.e., the e-stop
77
+ is pressed, get into error recovery, etc).
78
+
79
+ Unlike the `moveLabware` command, where you pick a source and destination
80
+ location, this command takes the labwareURI of the labware to be moved
81
+ and location to move it to.
82
+
83
+ """
84
+ ot3api = ensure_ot3_hardware(self._hardware_api)
85
+ if not ot3api.has_gripper():
86
+ raise GripperNotAttachedError("No gripper found to perform labware place.")
87
+
88
+ if ot3api.gripper_jaw_can_home():
89
+ raise CannotPerformGripperAction(
90
+ "Cannot place labware when gripper is not gripping."
91
+ )
92
+
93
+ location = self._state_view.geometry.ensure_valid_gripper_location(
94
+ params.location,
95
+ )
96
+
97
+ definition = self._state_view.labware.get_definition_by_uri(
98
+ # todo(mm, 2024-11-07): This is an unsafe cast from untrusted input.
99
+ # We need a str -> LabwareUri parse/validate function.
100
+ LabwareUri(params.labwareURI)
101
+ )
102
+
103
+ # todo(mm, 2024-11-06): This is only correct in the special case of an
104
+ # absorbance reader lid. Its definition currently puts the offsets for *itself*
105
+ # in the property that's normally meant for offsets for its *children.*
106
+ final_offsets = self._state_view.labware.get_child_gripper_offsets(
107
+ labware_definition=definition, slot_name=None
108
+ )
109
+ drop_offset = (
110
+ Point(
111
+ final_offsets.dropOffset.x,
112
+ final_offsets.dropOffset.y,
113
+ final_offsets.dropOffset.z,
114
+ )
115
+ if final_offsets
116
+ else None
117
+ )
118
+
119
+ if isinstance(params.location, DeckSlotLocation):
120
+ self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
121
+ params.location.slotName.id
122
+ )
123
+
124
+ # This is an absorbance reader, move the lid to its dock (staging area).
125
+ if isinstance(location, DeckSlotLocation):
126
+ module = self._state_view.modules.get_by_slot(location.slotName)
127
+ if module and module.model == ModuleModel.ABSORBANCE_READER_V1:
128
+ location = self._state_view.modules.absorbance_reader_dock_location(
129
+ module.id
130
+ )
131
+
132
+ # NOTE: When the estop is pressed, the gantry loses position, lets use
133
+ # the encoders to sync position.
134
+ # Ideally, we'd do a full home, but this command is used when
135
+ # the gripper is holding the plate reader, and a full home would
136
+ # bang it into the right window.
137
+ await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G])
138
+ await ot3api.engage_axes([Axis.X, Axis.Y])
139
+ await ot3api.update_axis_position_estimations([Axis.X, Axis.Y])
140
+
141
+ # Place the labware down
142
+ await self._start_movement(ot3api, definition, location, drop_offset)
143
+
144
+ return SuccessData(public=UnsafePlaceLabwareResult())
145
+
146
+ async def _start_movement(
147
+ self,
148
+ ot3api: OT3HardwareControlAPI,
149
+ labware_definition: LabwareDefinition,
150
+ location: OnDeckLabwareLocation,
151
+ drop_offset: Optional[Point],
152
+ ) -> None:
153
+ gripper_homed_position = await ot3api.gantry_position(
154
+ mount=OT3Mount.GRIPPER,
155
+ refresh=True,
156
+ )
157
+
158
+ to_labware_center = self._state_view.geometry.get_labware_grip_point(
159
+ labware_definition=labware_definition, location=location
160
+ )
161
+
162
+ movement_waypoints = get_gripper_labware_placement_waypoints(
163
+ to_labware_center=to_labware_center,
164
+ gripper_home_z=gripper_homed_position.z,
165
+ drop_offset=drop_offset,
166
+ )
167
+
168
+ # start movement
169
+ for waypoint_data in movement_waypoints:
170
+ if waypoint_data.jaw_open:
171
+ if waypoint_data.dropping:
172
+ # This `disengage_axes` step is important in order to engage
173
+ # the electronic brake on the Z axis of the gripper. The brake
174
+ # has a stronger holding force on the axis than the hold current,
175
+ # and prevents the axis from spuriously dropping when e.g. the notch
176
+ # on the side of a falling tiprack catches the jaw.
177
+ await ot3api.disengage_axes([Axis.Z_G])
178
+ await ot3api.ungrip()
179
+ if waypoint_data.dropping:
180
+ # We lost the position estimation after disengaging the axis, so
181
+ # it is necessary to home it next
182
+ await ot3api.home_z(OT3Mount.GRIPPER)
183
+ await ot3api.move_to(
184
+ mount=OT3Mount.GRIPPER, abs_position=waypoint_data.position
185
+ )
186
+
187
+
188
+ class UnsafePlaceLabware(
189
+ BaseCommand[UnsafePlaceLabwareParams, UnsafePlaceLabwareResult, ErrorOccurrence]
190
+ ):
191
+ """UnsafePlaceLabware command model."""
192
+
193
+ commandType: UnsafePlaceLabwareCommandType = "unsafe/placeLabware"
194
+ params: UnsafePlaceLabwareParams
195
+ result: Optional[UnsafePlaceLabwareResult]
196
+
197
+ _ImplementationCls: Type[
198
+ UnsafePlaceLabwareImplementation
199
+ ] = UnsafePlaceLabwareImplementation
200
+
201
+
202
+ class UnsafePlaceLabwareCreate(BaseCommandCreate[UnsafePlaceLabwareParams]):
203
+ """UnsafePlaceLabware command request model."""
204
+
205
+ commandType: UnsafePlaceLabwareCommandType = "unsafe/placeLabware"
206
+ params: UnsafePlaceLabwareParams
207
+
208
+ _CommandCls: Type[UnsafePlaceLabware] = UnsafePlaceLabware
@@ -0,0 +1,77 @@
1
+ """Ungrip labware payload, result, and implementaiton."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from opentrons.hardware_control.types import Axis
6
+ from opentrons.protocol_engine.errors.exceptions import GripperNotAttachedError
7
+ from pydantic import BaseModel
8
+ from typing import Optional, Type
9
+ from typing_extensions import Literal
10
+
11
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
12
+ from ...errors.error_occurrence import ErrorOccurrence
13
+ from ...resources import ensure_ot3_hardware
14
+
15
+ from opentrons.hardware_control import HardwareControlAPI
16
+
17
+
18
+ UnsafeUngripLabwareCommandType = Literal["unsafe/ungripLabware"]
19
+
20
+
21
+ class UnsafeUngripLabwareParams(BaseModel):
22
+ """Payload required for an UngripLabware command."""
23
+
24
+
25
+ class UnsafeUngripLabwareResult(BaseModel):
26
+ """Result data from the execution of an UngripLabware command."""
27
+
28
+
29
+ class UnsafeUngripLabwareImplementation(
30
+ AbstractCommandImpl[
31
+ UnsafeUngripLabwareParams,
32
+ SuccessData[UnsafeUngripLabwareResult],
33
+ ]
34
+ ):
35
+ """Ungrip labware command implementation."""
36
+
37
+ def __init__(
38
+ self,
39
+ hardware_api: HardwareControlAPI,
40
+ **kwargs: object,
41
+ ) -> None:
42
+ self._hardware_api = hardware_api
43
+
44
+ async def execute(
45
+ self, params: UnsafeUngripLabwareParams
46
+ ) -> SuccessData[UnsafeUngripLabwareResult]:
47
+ """Ungrip Labware."""
48
+ ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
49
+ if not ot3_hardware_api.has_gripper():
50
+ raise GripperNotAttachedError("No gripper found to perform ungrip.")
51
+ await ot3_hardware_api.home([Axis.G])
52
+ return SuccessData(
53
+ public=UnsafeUngripLabwareResult(),
54
+ )
55
+
56
+
57
+ class UnsafeUngripLabware(
58
+ BaseCommand[UnsafeUngripLabwareParams, UnsafeUngripLabwareResult, ErrorOccurrence]
59
+ ):
60
+ """UnsafeUngripLabware command model."""
61
+
62
+ commandType: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware"
63
+ params: UnsafeUngripLabwareParams
64
+ result: Optional[UnsafeUngripLabwareResult]
65
+
66
+ _ImplementationCls: Type[
67
+ UnsafeUngripLabwareImplementation
68
+ ] = UnsafeUngripLabwareImplementation
69
+
70
+
71
+ class UnsafeUngripLabwareCreate(BaseCommandCreate[UnsafeUngripLabwareParams]):
72
+ """UnsafeEngageAxes command request model."""
73
+
74
+ commandType: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware"
75
+ params: UnsafeUngripLabwareParams
76
+
77
+ _CommandCls: Type[UnsafeUngripLabware] = UnsafeUngripLabware
@@ -23,7 +23,11 @@ class UpdatePositionEstimatorsParams(BaseModel):
23
23
  """Payload required for an UpdatePositionEstimators command."""
24
24
 
25
25
  axes: List[MotorAxis] = Field(
26
- ..., description="The axes for which to update the position estimators."
26
+ ...,
27
+ description=(
28
+ "The axes for which to update the position estimators."
29
+ " Any axes that are not physically present will be ignored."
30
+ ),
27
31
  )
28
32
 
29
33
 
@@ -34,7 +38,7 @@ class UpdatePositionEstimatorsResult(BaseModel):
34
38
  class UpdatePositionEstimatorsImplementation(
35
39
  AbstractCommandImpl[
36
40
  UpdatePositionEstimatorsParams,
37
- SuccessData[UpdatePositionEstimatorsResult, None],
41
+ SuccessData[UpdatePositionEstimatorsResult],
38
42
  ]
39
43
  ):
40
44
  """Update position estimators command implementation."""
@@ -50,7 +54,7 @@ class UpdatePositionEstimatorsImplementation(
50
54
 
51
55
  async def execute(
52
56
  self, params: UpdatePositionEstimatorsParams
53
- ) -> SuccessData[UpdatePositionEstimatorsResult, None]:
57
+ ) -> SuccessData[UpdatePositionEstimatorsResult]:
54
58
  """Update axis position estimators from their encoders."""
55
59
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
56
60
  await ot3_hardware_api.update_axis_position_estimations(
@@ -59,7 +63,9 @@ class UpdatePositionEstimatorsImplementation(
59
63
  for axis in params.axes
60
64
  ]
61
65
  )
62
- return SuccessData(public=UpdatePositionEstimatorsResult(), private=None)
66
+ return SuccessData(
67
+ public=UpdatePositionEstimatorsResult(),
68
+ )
63
69
 
64
70
 
65
71
  class UpdatePositionEstimators(
@@ -36,9 +36,7 @@ class VerifyTipPresenceResult(BaseModel):
36
36
 
37
37
 
38
38
  class VerifyTipPresenceImplementation(
39
- AbstractCommandImpl[
40
- VerifyTipPresenceParams, SuccessData[VerifyTipPresenceResult, None]
41
- ]
39
+ AbstractCommandImpl[VerifyTipPresenceParams, SuccessData[VerifyTipPresenceResult]]
42
40
  ):
43
41
  """VerifyTipPresence command implementation."""
44
42
 
@@ -51,7 +49,7 @@ class VerifyTipPresenceImplementation(
51
49
 
52
50
  async def execute(
53
51
  self, params: VerifyTipPresenceParams
54
- ) -> SuccessData[VerifyTipPresenceResult, None]:
52
+ ) -> SuccessData[VerifyTipPresenceResult]:
55
53
  """Verify if tip presence is as expected for the requested pipette."""
56
54
  pipette_id = params.pipetteId
57
55
  expected_state = params.expectedState
@@ -67,7 +65,9 @@ class VerifyTipPresenceImplementation(
67
65
  follow_singular_sensor=follow_singular_sensor,
68
66
  )
69
67
 
70
- return SuccessData(public=VerifyTipPresenceResult(), private=None)
68
+ return SuccessData(
69
+ public=VerifyTipPresenceResult(),
70
+ )
71
71
 
72
72
 
73
73
  class VerifyTipPresence(
@@ -29,7 +29,7 @@ class WaitForDurationResult(BaseModel):
29
29
 
30
30
 
31
31
  class WaitForDurationImplementation(
32
- AbstractCommandImpl[WaitForDurationParams, SuccessData[WaitForDurationResult, None]]
32
+ AbstractCommandImpl[WaitForDurationParams, SuccessData[WaitForDurationResult]]
33
33
  ):
34
34
  """Wait for duration command implementation."""
35
35
 
@@ -38,10 +38,12 @@ class WaitForDurationImplementation(
38
38
 
39
39
  async def execute(
40
40
  self, params: WaitForDurationParams
41
- ) -> SuccessData[WaitForDurationResult, None]:
41
+ ) -> SuccessData[WaitForDurationResult]:
42
42
  """Wait for a duration of time."""
43
43
  await self._run_control.wait_for_duration(params.seconds)
44
- return SuccessData(public=WaitForDurationResult(), private=None)
44
+ return SuccessData(
45
+ public=WaitForDurationResult(),
46
+ )
45
47
 
46
48
 
47
49
  class WaitForDuration(
@@ -30,7 +30,7 @@ class WaitForResumeResult(BaseModel):
30
30
 
31
31
 
32
32
  class WaitForResumeImplementation(
33
- AbstractCommandImpl[WaitForResumeParams, SuccessData[WaitForResumeResult, None]]
33
+ AbstractCommandImpl[WaitForResumeParams, SuccessData[WaitForResumeResult]]
34
34
  ):
35
35
  """Wait for resume command implementation."""
36
36
 
@@ -39,10 +39,12 @@ class WaitForResumeImplementation(
39
39
 
40
40
  async def execute(
41
41
  self, params: WaitForResumeParams
42
- ) -> SuccessData[WaitForResumeResult, None]:
42
+ ) -> SuccessData[WaitForResumeResult]:
43
43
  """Dispatch a PauseAction to the store to pause the protocol."""
44
44
  await self._run_control.wait_for_resume()
45
- return SuccessData(public=WaitForResumeResult(), private=None)
45
+ return SuccessData(
46
+ public=WaitForResumeResult(),
47
+ )
46
48
 
47
49
 
48
50
  class WaitForResume(
@@ -5,13 +5,25 @@ import typing
5
5
 
6
6
  from opentrons.hardware_control import HardwareControlAPI
7
7
  from opentrons.hardware_control.types import DoorState
8
- from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy
8
+ from opentrons.protocol_engine.execution.error_recovery_hardware_state_synchronizer import (
9
+ ErrorRecoveryHardwareStateSynchronizer,
10
+ )
11
+ from opentrons.protocol_engine.resources.labware_data_provider import (
12
+ LabwareDataProvider,
13
+ )
9
14
  from opentrons.util.async_helpers import async_context_manager_in_thread
15
+
10
16
  from opentrons_shared_data.robot import load as load_robot
11
17
 
18
+ from .actions.action_dispatcher import ActionDispatcher
19
+ from .error_recovery_policy import ErrorRecoveryPolicy
20
+ from .execution.door_watcher import DoorWatcher
21
+ from .execution.hardware_stopper import HardwareStopper
22
+ from .plugins import PluginStarter
12
23
  from .protocol_engine import ProtocolEngine
13
- from .resources import DeckDataProvider, ModuleDataProvider
14
- from .state import Config, StateStore
24
+ from .resources import DeckDataProvider, ModuleDataProvider, FileProvider, ModelUtils
25
+ from .state.config import Config
26
+ from .state.state import StateStore
15
27
  from .types import PostRunHardwareState, DeckConfigurationType
16
28
 
17
29
  from .engine_support import create_run_orchestrator
@@ -25,6 +37,7 @@ async def create_protocol_engine(
25
37
  error_recovery_policy: ErrorRecoveryPolicy,
26
38
  load_fixed_trash: bool = False,
27
39
  deck_configuration: typing.Optional[DeckConfigurationType] = None,
40
+ file_provider: typing.Optional[FileProvider] = None,
28
41
  notify_publishers: typing.Optional[typing.Callable[[], None]] = None,
29
42
  ) -> ProtocolEngine:
30
43
  """Create a ProtocolEngine instance.
@@ -36,17 +49,18 @@ async def create_protocol_engine(
36
49
  See documentation on `ErrorRecoveryPolicy`.
37
50
  load_fixed_trash: Automatically load fixed trash labware in engine.
38
51
  deck_configuration: The initial deck configuration the engine will be instantiated with.
52
+ file_provider: Provides access to robot server file writing procedures for protocol output.
39
53
  notify_publishers: Notifies robot server publishers of internal state change.
40
54
  """
41
55
  deck_data = DeckDataProvider(config.deck_type)
42
56
  deck_definition = await deck_data.get_deck_definition()
43
- deck_fixed_labware = (
44
- await deck_data.get_deck_fixed_labware(deck_definition)
45
- if load_fixed_trash
46
- else []
57
+ deck_fixed_labware = await deck_data.get_deck_fixed_labware(
58
+ load_fixed_trash, deck_definition, deck_configuration
47
59
  )
60
+
48
61
  module_calibration_offsets = ModuleDataProvider.load_module_calibrations()
49
62
  robot_definition = load_robot(config.robot_type)
63
+
50
64
  state_store = StateStore(
51
65
  config=config,
52
66
  deck_definition=deck_definition,
@@ -58,18 +72,51 @@ async def create_protocol_engine(
58
72
  deck_configuration=deck_configuration,
59
73
  notify_publishers=notify_publishers,
60
74
  )
61
-
62
- return ProtocolEngine(
63
- state_store=state_store,
75
+ hardware_state_synchronizer = ErrorRecoveryHardwareStateSynchronizer(
76
+ hardware_api, state_store
77
+ )
78
+ action_dispatcher = ActionDispatcher(state_store)
79
+ action_dispatcher.add_handler(hardware_state_synchronizer)
80
+ plugin_starter = PluginStarter(state_store, action_dispatcher)
81
+ model_utils = ModelUtils()
82
+ hardware_stopper = HardwareStopper(hardware_api, state_store)
83
+ door_watcher = DoorWatcher(state_store, hardware_api, action_dispatcher)
84
+ module_data_provider = ModuleDataProvider()
85
+ file_provider = file_provider or FileProvider()
86
+
87
+ pe = ProtocolEngine(
64
88
  hardware_api=hardware_api,
89
+ state_store=state_store,
90
+ action_dispatcher=action_dispatcher,
91
+ plugin_starter=plugin_starter,
92
+ model_utils=model_utils,
93
+ hardware_stopper=hardware_stopper,
94
+ door_watcher=door_watcher,
95
+ module_data_provider=module_data_provider,
96
+ file_provider=file_provider,
65
97
  )
66
98
 
99
+ # todo(mm, 2024-11-08): This is a quick hack to support the absorbance reader, which
100
+ # expects the engine to have this special labware definition available. It would be
101
+ # cleaner for the `loadModule` command to do this I/O and insert the definition
102
+ # into state. That gets easier after https://opentrons.atlassian.net/browse/EXEC-756.
103
+ #
104
+ # NOTE: This needs to stay in sync with LabwareView.get_absorbance_reader_lid_definition().
105
+ pe.add_labware_definition(
106
+ await LabwareDataProvider().get_labware_definition(
107
+ "opentrons_flex_lid_absorbance_plate_reader_module", "opentrons", 1
108
+ )
109
+ )
110
+
111
+ return pe
112
+
67
113
 
68
114
  @contextlib.contextmanager
69
115
  def create_protocol_engine_in_thread(
70
116
  hardware_api: HardwareControlAPI,
71
117
  config: Config,
72
118
  deck_configuration: typing.Optional[DeckConfigurationType],
119
+ file_provider: typing.Optional[FileProvider],
73
120
  error_recovery_policy: ErrorRecoveryPolicy,
74
121
  drop_tips_after_run: bool,
75
122
  post_run_hardware_state: PostRunHardwareState,
@@ -97,6 +144,7 @@ def create_protocol_engine_in_thread(
97
144
  with async_context_manager_in_thread(
98
145
  _protocol_engine(
99
146
  hardware_api,
147
+ file_provider,
100
148
  config,
101
149
  deck_configuration,
102
150
  error_recovery_policy,
@@ -114,6 +162,7 @@ def create_protocol_engine_in_thread(
114
162
  @contextlib.asynccontextmanager
115
163
  async def _protocol_engine(
116
164
  hardware_api: HardwareControlAPI,
165
+ file_provider: typing.Optional[FileProvider],
117
166
  config: Config,
118
167
  deck_configuration: typing.Optional[DeckConfigurationType],
119
168
  error_recovery_policy: ErrorRecoveryPolicy,
@@ -123,6 +172,7 @@ async def _protocol_engine(
123
172
  ) -> typing.AsyncGenerator[ProtocolEngine, None]:
124
173
  protocol_engine = await create_protocol_engine(
125
174
  hardware_api=hardware_api,
175
+ file_provider=file_provider,
126
176
  config=config,
127
177
  error_recovery_policy=error_recovery_policy,
128
178
  load_fixed_trash=load_fixed_trash,
@@ -6,7 +6,8 @@ from opentrons.protocol_runner import protocol_runner, RunOrchestrator
6
6
 
7
7
 
8
8
  def create_run_orchestrator(
9
- hardware_api: HardwareControlAPI, protocol_engine: ProtocolEngine
9
+ hardware_api: HardwareControlAPI,
10
+ protocol_engine: ProtocolEngine,
10
11
  ) -> RunOrchestrator:
11
12
  """Create a RunOrchestrator instance."""
12
13
  return RunOrchestrator(
@@ -26,10 +26,20 @@ class ErrorRecoveryType(enum.Enum):
26
26
  """
27
27
 
28
28
  WAIT_FOR_RECOVERY = enum.auto()
29
- """Stop and wait for the error to be recovered from manually."""
29
+ """Enter interactive error recovery mode."""
30
30
 
31
- IGNORE_AND_CONTINUE = enum.auto()
32
- """Continue with the run, as if the command never failed."""
31
+ CONTINUE_WITH_ERROR = enum.auto()
32
+ """Continue without interruption, carrying on from whatever error state the failed
33
+ command left the engine in.
34
+
35
+ This is like `ProtocolEngine.resume_from_recovery(reconcile_false_positive=False)`.
36
+ """
37
+
38
+ ASSUME_FALSE_POSITIVE_AND_CONTINUE = enum.auto()
39
+ """Continue without interruption, acting as if the underlying error was a false positive.
40
+
41
+ This is like `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
42
+ """
33
43
 
34
44
 
35
45
  class ErrorRecoveryPolicy(Protocol):
@@ -40,6 +50,7 @@ class ErrorRecoveryPolicy(Protocol):
40
50
  and return an appropriate `ErrorRecoveryType`.
41
51
 
42
52
  Args:
53
+ config: The config of the calling `ProtocolEngine`.
43
54
  failed_command: The command that failed, in its final `status=="failed"` state.
44
55
  defined_error_data: If the command failed with a defined error, details about
45
56
  that error. If the command failed with an undefined error, `None`.