opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. opentrons/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  20. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  21. opentrons/hardware_control/module_control.py +43 -2
  22. opentrons/hardware_control/modules/__init__.py +7 -1
  23. opentrons/hardware_control/modules/absorbance_reader.py +230 -83
  24. opentrons/hardware_control/modules/errors.py +7 -0
  25. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  26. opentrons/hardware_control/modules/magdeck.py +12 -3
  27. opentrons/hardware_control/modules/mod_abc.py +27 -2
  28. opentrons/hardware_control/modules/tempdeck.py +15 -7
  29. opentrons/hardware_control/modules/thermocycler.py +69 -3
  30. opentrons/hardware_control/modules/types.py +11 -5
  31. opentrons/hardware_control/modules/update.py +11 -5
  32. opentrons/hardware_control/modules/utils.py +3 -1
  33. opentrons/hardware_control/ot3_calibration.py +6 -6
  34. opentrons/hardware_control/ot3api.py +126 -89
  35. opentrons/hardware_control/poller.py +15 -11
  36. opentrons/hardware_control/protocols/__init__.py +1 -7
  37. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  38. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  39. opentrons/motion_planning/__init__.py +2 -0
  40. opentrons/motion_planning/waypoints.py +32 -0
  41. opentrons/protocol_api/__init__.py +2 -1
  42. opentrons/protocol_api/_liquid.py +87 -1
  43. opentrons/protocol_api/_parameter_context.py +10 -1
  44. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  45. opentrons/protocol_api/core/engine/instrument.py +29 -25
  46. opentrons/protocol_api/core/engine/labware.py +10 -2
  47. opentrons/protocol_api/core/engine/module_core.py +129 -17
  48. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
  49. opentrons/protocol_api/core/engine/protocol.py +55 -2
  50. opentrons/protocol_api/core/instrument.py +2 -0
  51. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  52. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
  53. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  54. opentrons/protocol_api/core/module.py +22 -4
  55. opentrons/protocol_api/core/protocol.py +5 -2
  56. opentrons/protocol_api/instrument_context.py +52 -20
  57. opentrons/protocol_api/labware.py +13 -1
  58. opentrons/protocol_api/module_contexts.py +68 -13
  59. opentrons/protocol_api/protocol_context.py +38 -4
  60. opentrons/protocol_api/validation.py +5 -3
  61. opentrons/protocol_engine/__init__.py +10 -9
  62. opentrons/protocol_engine/actions/__init__.py +5 -0
  63. opentrons/protocol_engine/actions/actions.py +42 -25
  64. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  65. opentrons/protocol_engine/clients/sync_client.py +7 -1
  66. opentrons/protocol_engine/clients/transports.py +1 -1
  67. opentrons/protocol_engine/commands/__init__.py +0 -4
  68. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  69. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
  70. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
  71. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
  72. opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
  73. opentrons/protocol_engine/commands/aspirate.py +29 -16
  74. opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
  75. opentrons/protocol_engine/commands/blow_out.py +63 -14
  76. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  77. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  78. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  79. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  80. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  81. opentrons/protocol_engine/commands/command.py +28 -17
  82. opentrons/protocol_engine/commands/command_unions.py +37 -24
  83. opentrons/protocol_engine/commands/comment.py +5 -3
  84. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  85. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  86. opentrons/protocol_engine/commands/custom.py +5 -3
  87. opentrons/protocol_engine/commands/dispense.py +42 -20
  88. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  89. opentrons/protocol_engine/commands/drop_tip.py +68 -15
  90. opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
  91. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  92. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  93. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  94. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  95. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  96. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  97. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  98. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  99. opentrons/protocol_engine/commands/home.py +11 -5
  100. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  101. opentrons/protocol_engine/commands/load_labware.py +19 -5
  102. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  103. opentrons/protocol_engine/commands/load_module.py +43 -6
  104. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  105. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  106. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  107. opentrons/protocol_engine/commands/move_labware.py +106 -19
  108. opentrons/protocol_engine/commands/move_relative.py +15 -3
  109. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  110. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  111. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  112. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  113. opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
  114. opentrons/protocol_engine/commands/pipetting_common.py +39 -15
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  116. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  117. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  118. opentrons/protocol_engine/commands/save_position.py +2 -3
  119. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  120. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  121. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  122. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  123. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  124. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  125. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  126. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  127. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  128. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  129. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  130. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  131. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  132. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  133. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  135. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  136. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  137. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  138. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  139. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  140. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
  141. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
  142. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
  143. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  144. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  145. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  146. opentrons/protocol_engine/create_protocol_engine.py +41 -8
  147. opentrons/protocol_engine/engine_support.py +2 -1
  148. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  149. opentrons/protocol_engine/errors/__init__.py +18 -0
  150. opentrons/protocol_engine/errors/exceptions.py +114 -2
  151. opentrons/protocol_engine/execution/__init__.py +2 -0
  152. opentrons/protocol_engine/execution/command_executor.py +22 -13
  153. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  154. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  155. opentrons/protocol_engine/execution/equipment.py +2 -1
  156. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  157. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  158. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  159. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  160. opentrons/protocol_engine/execution/labware_movement.py +6 -3
  161. opentrons/protocol_engine/execution/movement.py +8 -3
  162. opentrons/protocol_engine/execution/pipetting.py +7 -4
  163. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  164. opentrons/protocol_engine/execution/run_control.py +1 -1
  165. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  166. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  167. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  168. opentrons/protocol_engine/notes/__init__.py +14 -2
  169. opentrons/protocol_engine/notes/notes.py +18 -1
  170. opentrons/protocol_engine/plugins.py +1 -1
  171. opentrons/protocol_engine/protocol_engine.py +54 -31
  172. opentrons/protocol_engine/resources/__init__.py +2 -0
  173. opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
  174. opentrons/protocol_engine/resources/file_provider.py +157 -0
  175. opentrons/protocol_engine/resources/fixture_validation.py +5 -0
  176. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  177. opentrons/protocol_engine/state/__init__.py +0 -70
  178. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  179. opentrons/protocol_engine/state/command_history.py +21 -2
  180. opentrons/protocol_engine/state/commands.py +110 -31
  181. opentrons/protocol_engine/state/files.py +59 -0
  182. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  183. opentrons/protocol_engine/state/geometry.py +359 -15
  184. opentrons/protocol_engine/state/labware.py +166 -63
  185. opentrons/protocol_engine/state/liquids.py +1 -1
  186. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
  187. opentrons/protocol_engine/state/modules.py +167 -85
  188. opentrons/protocol_engine/state/motion.py +16 -9
  189. opentrons/protocol_engine/state/pipettes.py +157 -317
  190. opentrons/protocol_engine/state/state.py +30 -1
  191. opentrons/protocol_engine/state/state_summary.py +3 -0
  192. opentrons/protocol_engine/state/tips.py +69 -114
  193. opentrons/protocol_engine/state/update_types.py +408 -0
  194. opentrons/protocol_engine/state/wells.py +236 -0
  195. opentrons/protocol_engine/types.py +90 -0
  196. opentrons/protocol_reader/file_format_validator.py +83 -15
  197. opentrons/protocol_runner/json_translator.py +21 -5
  198. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  199. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  200. opentrons/protocol_runner/protocol_runner.py +6 -3
  201. opentrons/protocol_runner/run_orchestrator.py +26 -6
  202. opentrons/protocols/advanced_control/mix.py +3 -5
  203. opentrons/protocols/advanced_control/transfers.py +125 -56
  204. opentrons/protocols/api_support/constants.py +1 -1
  205. opentrons/protocols/api_support/definitions.py +1 -1
  206. opentrons/protocols/api_support/labware_like.py +4 -4
  207. opentrons/protocols/api_support/tip_tracker.py +2 -2
  208. opentrons/protocols/api_support/types.py +15 -2
  209. opentrons/protocols/api_support/util.py +30 -42
  210. opentrons/protocols/duration/errors.py +1 -1
  211. opentrons/protocols/duration/estimator.py +50 -29
  212. opentrons/protocols/execution/dev_types.py +2 -2
  213. opentrons/protocols/execution/execute_json_v4.py +15 -10
  214. opentrons/protocols/execution/execute_python.py +8 -3
  215. opentrons/protocols/geometry/planning.py +12 -12
  216. opentrons/protocols/labware.py +17 -33
  217. opentrons/simulate.py +3 -3
  218. opentrons/types.py +30 -3
  219. opentrons/util/logging_config.py +34 -0
  220. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
  221. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
  222. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  223. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  224. opentrons/protocol_runner/thread_async_queue.py +0 -174
  225. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  226. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  227. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
  230. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,7 @@ from ..errors import (
13
13
  HeaterShakerLabwareLatchStatusUnknown,
14
14
  WrongModuleTypeError,
15
15
  )
16
- from ..state import StateStore
16
+ from ..state.state import StateStore
17
17
  from ..state.module_substates import HeaterShakerModuleSubState
18
18
  from ..types import (
19
19
  HeaterShakerMovementRestrictors,
@@ -61,9 +61,6 @@ class HeaterShakerMovementFlagger:
61
61
  return # Labware on a module, but not a Heater-Shaker.
62
62
 
63
63
  if hs_substate.labware_latch_status == HeaterShakerLatchStatus.CLOSED:
64
- # TODO (spp, 2022-10-27): This only raises if latch status is 'idle_closed'.
65
- # We need to update the flagger to raise if latch status is anything other
66
- # than 'idle_open'
67
64
  raise HeaterShakerLabwareLatchNotOpenError(
68
65
  "Heater-Shaker labware latch must be open when moving labware to/from it."
69
66
  )
@@ -9,7 +9,7 @@ from opentrons.hardware_control import HardwareControlAPI
9
9
  from opentrons.hardware_control.types import OT3Mount, Axis
10
10
  from opentrons.motion_planning import get_gripper_labware_movement_waypoints
11
11
 
12
- from opentrons.protocol_engine.state import StateStore
12
+ from opentrons.protocol_engine.state.state import StateStore
13
13
  from opentrons.protocol_engine.resources.ot3_validation import ensure_ot3_hardware
14
14
 
15
15
  from .thermocycler_movement_flagger import ThermocyclerMovementFlagger
@@ -37,8 +37,6 @@ if TYPE_CHECKING:
37
37
  _GRIPPER_HOMED_POSITION_Z = 166.125 # Height of the center of the gripper critical point from the deck when homed
38
38
 
39
39
 
40
- # TODO (spp, 2022-10-20): name this GripperMovementHandler if it doesn't handle
41
- # any non-gripper implementations
42
40
  class LabwareMovementHandler:
43
41
  """Implementation logic for labware movement."""
44
42
 
@@ -128,6 +126,7 @@ class LabwareMovementHandler:
128
126
  current_location=current_location,
129
127
  )
130
128
 
129
+ current_labware = self._state_store.labware.get_definition(labware_id)
131
130
  async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement(
132
131
  labware_location=current_location
133
132
  ):
@@ -136,6 +135,7 @@ class LabwareMovementHandler:
136
135
  from_location=current_location,
137
136
  to_location=new_location,
138
137
  additional_offset_vector=user_offset_data,
138
+ current_labware=current_labware,
139
139
  )
140
140
  )
141
141
  from_labware_center = self._state_store.geometry.get_labware_grip_point(
@@ -177,6 +177,9 @@ class LabwareMovementHandler:
177
177
  labware_id
178
178
  )
179
179
  well_bbox = self._state_store.labware.get_well_bbox(labware_id)
180
+ # todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError.
181
+ # Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError,
182
+ # depending on what waypoint we're at, to propagate a more specific error code to users.
180
183
  ot3api.raise_error_if_gripper_pickup_failed(
181
184
  expected_grip_width=labware_bbox.y,
182
185
  grip_width_uncertainty_wider=abs(
@@ -2,7 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import logging
5
- from typing import Optional, List
5
+ from typing import Optional, List, Union
6
6
 
7
7
  from opentrons.types import Point, MountType
8
8
  from opentrons.hardware_control import HardwareControlAPI
@@ -10,13 +10,14 @@ from opentrons_shared_data.errors.exceptions import PositionUnknownError
10
10
 
11
11
  from ..types import (
12
12
  WellLocation,
13
+ LiquidHandlingWellLocation,
13
14
  DeckPoint,
14
15
  MovementAxis,
15
16
  MotorAxis,
16
17
  CurrentWell,
17
18
  AddressableOffsetVector,
18
19
  )
19
- from ..state import StateStore
20
+ from ..state.state import StateStore
20
21
  from ..resources import ModelUtils
21
22
  from .thermocycler_movement_flagger import ThermocyclerMovementFlagger
22
23
  from .heater_shaker_movement_flagger import HeaterShakerMovementFlagger
@@ -66,11 +67,12 @@ class MovementHandler:
66
67
  pipette_id: str,
67
68
  labware_id: str,
68
69
  well_name: str,
69
- well_location: Optional[WellLocation] = None,
70
+ well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]] = None,
70
71
  current_well: Optional[CurrentWell] = None,
71
72
  force_direct: bool = False,
72
73
  minimum_z_height: Optional[float] = None,
73
74
  speed: Optional[float] = None,
75
+ operation_volume: Optional[float] = None,
74
76
  ) -> Point:
75
77
  """Move to a specific well."""
76
78
  self._state_store.labware.raise_if_labware_inaccessible_by_pipette(
@@ -129,6 +131,7 @@ class MovementHandler:
129
131
  current_well=current_well,
130
132
  force_direct=force_direct,
131
133
  minimum_z_height=minimum_z_height,
134
+ operation_volume=operation_volume,
132
135
  )
133
136
 
134
137
  speed = self._state_store.pipettes.get_movement_speed(
@@ -151,6 +154,7 @@ class MovementHandler:
151
154
  speed: Optional[float] = None,
152
155
  stay_at_highest_possible_z: bool = False,
153
156
  ignore_tip_configuration: Optional[bool] = True,
157
+ highest_possible_z_extra_offset: Optional[float] = None,
154
158
  ) -> Point:
155
159
  """Move to a specific addressable area."""
156
160
  # Check for presence of heater shakers on deck, and if planned
@@ -201,6 +205,7 @@ class MovementHandler:
201
205
  minimum_z_height=minimum_z_height,
202
206
  stay_at_max_travel_z=stay_at_highest_possible_z,
203
207
  ignore_tip_configuration=ignore_tip_configuration,
208
+ max_travel_z_extra_margin=highest_possible_z_extra_offset,
204
209
  )
205
210
 
206
211
  speed = self._state_store.pipettes.get_movement_speed(
@@ -5,7 +5,8 @@ from contextlib import contextmanager
5
5
 
6
6
  from opentrons.hardware_control import HardwareControlAPI
7
7
 
8
- from ..state import StateView, HardwarePipette
8
+ from ..state.state import StateView
9
+ from ..state.pipettes import HardwarePipette
9
10
  from ..notes import CommandNoteAdder, CommandNote
10
11
  from ..errors.exceptions import (
11
12
  TipNotAttachedError,
@@ -186,7 +187,9 @@ class HardwarePipettingHandler(PipettingHandler):
186
187
  mount=hw_pipette.mount,
187
188
  max_z_dist=well_depth - lld_min_height + well_location.offset.z,
188
189
  )
189
- return float(z_pos)
190
+ labware_pos = self._state_view.geometry.get_labware_position(labware_id)
191
+ relative_height = z_pos - labware_pos.z - well_def.z
192
+ return float(relative_height)
190
193
 
191
194
  @contextmanager
192
195
  def _set_flow_rate(
@@ -285,8 +288,8 @@ class VirtualPipettingHandler(PipettingHandler):
285
288
  well_location: WellLocation,
286
289
  ) -> float:
287
290
  """Detect liquid level."""
288
- # TODO (pm, 6-18-24): return a value of worth if needed
289
- return 0.0
291
+ well_def = self._state_view.labware.get_well_definition(labware_id, well_name)
292
+ return well_def.depth
290
293
 
291
294
  def _validate_tip_attached(self, pipette_id: str, command_name: str) -> None:
292
295
  """Validate if there is a tip attached."""
@@ -3,7 +3,7 @@ import asyncio
3
3
  from logging import getLogger
4
4
  from typing import Optional, AsyncGenerator, Callable
5
5
 
6
- from ..state import StateStore
6
+ from ..state.state import StateStore
7
7
  from .command_executor import CommandExecutor
8
8
 
9
9
  log = getLogger(__name__)
@@ -69,7 +69,11 @@ class QueueWorker:
69
69
 
70
70
  async def _run_commands(self) -> None:
71
71
  async for command_id in self._command_generator():
72
- await self._command_executor.execute(command_id=command_id)
72
+ try:
73
+ await self._command_executor.execute(command_id=command_id)
74
+ except BaseException:
75
+ log.exception("Unhandled failure in command executor")
76
+ raise
73
77
  # Yield to the event loop in case we're executing a long sequence of commands
74
78
  # that never yields internally. For example, a long sequence of comment commands.
75
79
  await asyncio.sleep(0)
@@ -1,7 +1,7 @@
1
1
  """Run control command side-effect logic."""
2
2
  import asyncio
3
3
 
4
- from ..state import StateStore
4
+ from ..state.state import StateStore
5
5
  from ..actions import ActionDispatcher, PauseAction, PauseSource
6
6
 
7
7
 
@@ -7,7 +7,7 @@ from opentrons.hardware_control import HardwareControlAPI
7
7
  from opentrons.hardware_control.modules import Thermocycler as HardwareThermocycler
8
8
 
9
9
  from ..types import ModuleLocation, LabwareLocation
10
- from ..state import StateStore
10
+ from ..state.state import StateStore
11
11
  from ..errors import ThermocyclerNotOpenError, WrongModuleTypeError
12
12
 
13
13
 
@@ -5,7 +5,8 @@ import asyncio
5
5
  from typing import TYPE_CHECKING, AsyncGenerator, Optional
6
6
  from opentrons.hardware_control.modules.thermocycler import Thermocycler
7
7
  from opentrons.protocol_engine.types import LabwareLocation, ModuleLocation, ModuleModel
8
- from opentrons.protocol_engine.state import StateStore, ThermocyclerModuleId
8
+ from opentrons.protocol_engine.state.state import StateStore
9
+ from opentrons.protocol_engine.state.module_substates import ThermocyclerModuleId
9
10
  from contextlib import asynccontextmanager
10
11
 
11
12
  if TYPE_CHECKING:
@@ -4,6 +4,9 @@ from typing_extensions import Protocol as TypingProtocol
4
4
 
5
5
  from opentrons.hardware_control import HardwareControlAPI
6
6
  from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType
7
+ from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError
8
+ from opentrons.types import Mount
9
+
7
10
  from opentrons_shared_data.errors.exceptions import (
8
11
  CommandPreconditionViolated,
9
12
  CommandParameterLimitViolated,
@@ -11,7 +14,7 @@ from opentrons_shared_data.errors.exceptions import (
11
14
  )
12
15
 
13
16
  from ..resources import LabwareDataProvider, ensure_ot3_hardware
14
- from ..state import StateView
17
+ from ..state.state import StateView
15
18
  from ..types import TipGeometry, TipPresenceStatus
16
19
  from ..errors import (
17
20
  HardwareNotSupportedError,
@@ -68,18 +71,27 @@ class TipHandler(TypingProtocol):
68
71
 
69
72
  Returns:
70
73
  Tip geometry of the picked up tip.
74
+
75
+ Raises:
76
+ PickUpTipTipNotAttachedError
71
77
  """
72
78
  ...
73
79
 
74
80
  async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
75
- """Drop the attached tip into the named location.
81
+ """Drop the attached tip into the current location.
76
82
 
77
83
  Pipette should be in place over the destination prior to calling this method.
84
+
85
+ Raises:
86
+ TipAttachedError
78
87
  """
79
88
 
80
- async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
89
+ def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None:
81
90
  """Tell the Hardware API that a tip is attached."""
82
91
 
92
+ def remove_tip(self, pipette_id: str) -> None:
93
+ """Tell the hardware API that no tip is attached."""
94
+
83
95
  async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus:
84
96
  """Get tip presence status on the pipette."""
85
97
 
@@ -89,7 +101,12 @@ class TipHandler(TypingProtocol):
89
101
  expected: TipPresenceStatus,
90
102
  follow_singular_sensor: Optional[InstrumentProbeType] = None,
91
103
  ) -> None:
92
- """Verify the expected tip presence status."""
104
+ """Use sensors to verify that a tip is or is not physically attached.
105
+
106
+ Raises:
107
+ TipNotAttachedError or TipAttachedError, as appropriate, if the physical
108
+ status doesn't match what was expected.
109
+ """
93
110
 
94
111
 
95
112
  async def _available_for_nozzle_layout( # noqa: C901
@@ -187,6 +204,11 @@ class HardwareTipHandler(TipHandler):
187
204
  self._labware_data_provider = labware_data_provider or LabwareDataProvider()
188
205
  self._state_view = state_view
189
206
 
207
+ # WARNING: ErrorRecoveryHardwareStateSynchronizer can currently construct several
208
+ # instances of this class per run, in addition to the main instance used
209
+ # for command execution. We're therefore depending on this class being
210
+ # stateless, so consider that before adding additional attributes here.
211
+
190
212
  async def available_for_nozzle_layout(
191
213
  self,
192
214
  pipette_id: str,
@@ -195,7 +217,7 @@ class HardwareTipHandler(TipHandler):
195
217
  front_right_nozzle: Optional[str] = None,
196
218
  back_left_nozzle: Optional[str] = None,
197
219
  ) -> Dict[str, str]:
198
- """Returns configuration for nozzle layout to pass to configure_nozzle_layout."""
220
+ """See documentation on abstract base class."""
199
221
  if self._state_view.pipettes.get_attached_tip(pipette_id):
200
222
  raise CommandPreconditionViolated(
201
223
  message=f"Cannot configure nozzle layout of {str(self)} while it has tips attached."
@@ -211,8 +233,8 @@ class HardwareTipHandler(TipHandler):
211
233
  labware_id: str,
212
234
  well_name: str,
213
235
  ) -> TipGeometry:
214
- """Pick up a tip at the current location using the Hardware API."""
215
- hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
236
+ """See documentation on abstract base class."""
237
+ hw_mount = self._get_hw_mount(pipette_id)
216
238
 
217
239
  nominal_tip_geometry = self._state_view.geometry.get_nominal_tip_geometry(
218
240
  pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
@@ -224,33 +246,29 @@ class HardwareTipHandler(TipHandler):
224
246
  nominal_fallback=nominal_tip_geometry.length,
225
247
  )
226
248
 
249
+ tip_geometry = TipGeometry(
250
+ length=actual_tip_length,
251
+ diameter=nominal_tip_geometry.diameter,
252
+ volume=nominal_tip_geometry.volume,
253
+ )
254
+
227
255
  await self._hardware_api.tip_pickup_moves(
228
256
  mount=hw_mount, presses=None, increment=None
229
257
  )
230
- await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
258
+ try:
259
+ await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
260
+ except TipNotAttachedError as e:
261
+ raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
231
262
 
232
- self._hardware_api.cache_tip(hw_mount, actual_tip_length)
233
- await self._hardware_api.prepare_for_aspirate(hw_mount)
263
+ self.cache_tip(pipette_id, tip_geometry)
234
264
 
235
- self._hardware_api.set_current_tiprack_diameter(
236
- mount=hw_mount,
237
- tiprack_diameter=nominal_tip_geometry.diameter,
238
- )
239
-
240
- self._hardware_api.set_working_volume(
241
- mount=hw_mount,
242
- tip_volume=nominal_tip_geometry.volume,
243
- )
265
+ await self._hardware_api.prepare_for_aspirate(hw_mount)
244
266
 
245
- return TipGeometry(
246
- length=actual_tip_length,
247
- diameter=nominal_tip_geometry.diameter,
248
- volume=nominal_tip_geometry.volume,
249
- )
267
+ return tip_geometry
250
268
 
251
269
  async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
252
- """Drop a tip at the current location using the Hardware API."""
253
- hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
270
+ """See documentation on abstract base class."""
271
+ hw_mount = self._get_hw_mount(pipette_id)
254
272
 
255
273
  # Let the hardware controller handle defaulting home_after since its behavior
256
274
  # differs between machines
@@ -259,14 +277,18 @@ class HardwareTipHandler(TipHandler):
259
277
  else:
260
278
  kwargs = {}
261
279
 
262
- await self._hardware_api.drop_tip(mount=hw_mount, **kwargs)
280
+ await self._hardware_api.tip_drop_moves(mount=hw_mount, **kwargs)
281
+
282
+ # Allow TipNotAttachedError to propagate.
263
283
  await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)
264
284
 
265
- async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
266
- """Tell the Hardware API that a tip is attached."""
267
- hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
285
+ self.remove_tip(pipette_id)
268
286
 
269
- await self._hardware_api.add_tip(mount=hw_mount, tip_length=tip.length)
287
+ def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None:
288
+ """See documentation on abstract base class."""
289
+ hw_mount = self._get_hw_mount(pipette_id)
290
+
291
+ self._hardware_api.cache_tip(mount=hw_mount, tip_length=tip.length)
270
292
 
271
293
  self._hardware_api.set_current_tiprack_diameter(
272
294
  mount=hw_mount,
@@ -278,12 +300,18 @@ class HardwareTipHandler(TipHandler):
278
300
  tip_volume=tip.volume,
279
301
  )
280
302
 
303
+ def remove_tip(self, pipette_id: str) -> None:
304
+ """See documentation on abstract base class."""
305
+ hw_mount = self._get_hw_mount(pipette_id)
306
+ self._hardware_api.remove_tip(hw_mount)
307
+ self._hardware_api.set_current_tiprack_diameter(hw_mount, 0)
308
+
281
309
  async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus:
282
- """Get the tip presence status of the pipette."""
310
+ """See documentation on abstract base class."""
283
311
  try:
284
312
  ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)
285
313
 
286
- hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
314
+ hw_mount = self._get_hw_mount(pipette_id)
287
315
 
288
316
  status = await ot3api.get_tip_presence_status(hw_mount)
289
317
  return TipPresenceStatus.from_hw_state(status)
@@ -297,11 +325,7 @@ class HardwareTipHandler(TipHandler):
297
325
  expected: TipPresenceStatus,
298
326
  follow_singular_sensor: Optional[InstrumentProbeType] = None,
299
327
  ) -> None:
300
- """Verify the expecterd tip presence status of the pipette.
301
-
302
- This function will raise an exception if the specified tip presence status
303
- isn't matched.
304
- """
328
+ """See documentation on abstract base class."""
305
329
  nozzle_configuration = (
306
330
  self._state_view.pipettes.state.nozzle_configuration_by_id[pipette_id]
307
331
  )
@@ -328,7 +352,7 @@ class HardwareTipHandler(TipHandler):
328
352
  return
329
353
  try:
330
354
  ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)
331
- hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
355
+ hw_mount = self._get_hw_mount(pipette_id)
332
356
  await ot3api.verify_tip_presence(
333
357
  hw_mount, expected.to_hw_state(), follow_singular_sensor
334
358
  )
@@ -346,6 +370,9 @@ class HardwareTipHandler(TipHandler):
346
370
  wrapping=[PythonException(e)],
347
371
  )
348
372
 
373
+ def _get_hw_mount(self, pipette_id: str) -> Mount:
374
+ return self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
375
+
349
376
 
350
377
  class VirtualTipHandler(TipHandler):
351
378
  """Pick up and drop tips, using a virtual pipette."""
@@ -385,7 +412,7 @@ class VirtualTipHandler(TipHandler):
385
412
  front_right_nozzle: Optional[str] = None,
386
413
  back_left_nozzle: Optional[str] = None,
387
414
  ) -> Dict[str, str]:
388
- """Returns configuration for nozzle layout to pass to configure_nozzle_layout."""
415
+ """See documentation on abstract base class."""
389
416
  if self._state_view.pipettes.get_attached_tip(pipette_id):
390
417
  raise CommandPreconditionViolated(
391
418
  message=f"Cannot configure nozzle layout of {str(self)} while it has tips attached."
@@ -409,12 +436,19 @@ class VirtualTipHandler(TipHandler):
409
436
  expected_has_tip=True,
410
437
  )
411
438
 
412
- async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
413
- """Add a tip using a virtual pipette.
439
+ def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None:
440
+ """See documentation on abstract base class.
441
+
442
+ This should not be called when using virtual pipettes.
443
+ """
444
+ assert False, "TipHandler.cache_tip should not be used with virtual pipettes"
445
+
446
+ def remove_tip(self, pipette_id: str) -> None:
447
+ """See documentation on abstract base class.
414
448
 
415
449
  This should not be called when using virtual pipettes.
416
450
  """
417
- assert False, "TipHandler.add_tip should not be used with virtual pipettes"
451
+ assert False, "TipHandler.remove_tip should not be used with virtual pipettes"
418
452
 
419
453
  async def verify_tip_presence(
420
454
  self,
@@ -1,5 +1,17 @@
1
1
  """Protocol engine notes module."""
2
2
 
3
- from .notes import NoteKind, CommandNote, CommandNoteAdder, CommandNoteTracker
3
+ from .notes import (
4
+ NoteKind,
5
+ CommandNote,
6
+ CommandNoteAdder,
7
+ CommandNoteTracker,
8
+ make_error_recovery_debug_note,
9
+ )
4
10
 
5
- __all__ = ["NoteKind", "CommandNote", "CommandNoteAdder", "CommandNoteTracker"]
11
+ __all__ = [
12
+ "NoteKind",
13
+ "CommandNote",
14
+ "CommandNoteAdder",
15
+ "CommandNoteTracker",
16
+ "make_error_recovery_debug_note",
17
+ ]
@@ -1,7 +1,10 @@
1
1
  """Definitions of data and interface shapes for notes."""
2
- from typing import Union, Literal, Protocol, List
2
+ from typing import Union, Literal, Protocol, List, TYPE_CHECKING
3
3
  from pydantic import BaseModel, Field
4
4
 
5
+ if TYPE_CHECKING:
6
+ from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType
7
+
5
8
  NoteKind = Union[Literal["warning", "information"], str]
6
9
 
7
10
 
@@ -26,6 +29,20 @@ class CommandNote(BaseModel):
26
29
  )
27
30
 
28
31
 
32
+ def make_error_recovery_debug_note(type: "ErrorRecoveryType") -> CommandNote:
33
+ """Return a note for debugging error recovery.
34
+
35
+ This is intended to be read by developers and support people, not computers.
36
+ """
37
+ message = f"Handling this command failure with {type.name}."
38
+ return CommandNote.construct(
39
+ noteKind="debugErrorRecovery",
40
+ shortMessage=message,
41
+ longMessage=message,
42
+ source="execution",
43
+ )
44
+
45
+
29
46
  class CommandNoteAdder(Protocol):
30
47
  """The shape of a function that something can use to add a command note."""
31
48
 
@@ -5,7 +5,7 @@ from typing import List
5
5
  from typing_extensions import final
6
6
 
7
7
  from .actions import Action, ActionDispatcher, ActionHandler
8
- from .state import StateView
8
+ from .state.state import StateView
9
9
 
10
10
 
11
11
  class AbstractPlugin(ActionHandler, ABC):