opentrons 8.1.0__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.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
  229. {opentrons-8.1.0.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.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
  236. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
  237. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  """Labware movement command handling."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Optional, TYPE_CHECKING
4
+ from typing import Optional, TYPE_CHECKING, overload
5
+
6
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
5
7
 
6
8
  from opentrons.types import Point
7
9
 
@@ -9,7 +11,7 @@ from opentrons.hardware_control import HardwareControlAPI
9
11
  from opentrons.hardware_control.types import OT3Mount, Axis
10
12
  from opentrons.motion_planning import get_gripper_labware_movement_waypoints
11
13
 
12
- from opentrons.protocol_engine.state import StateStore
14
+ from opentrons.protocol_engine.state.state import StateStore
13
15
  from opentrons.protocol_engine.resources.ot3_validation import ensure_ot3_hardware
14
16
 
15
17
  from .thermocycler_movement_flagger import ThermocyclerMovementFlagger
@@ -37,8 +39,6 @@ if TYPE_CHECKING:
37
39
  _GRIPPER_HOMED_POSITION_Z = 166.125 # Height of the center of the gripper critical point from the deck when homed
38
40
 
39
41
 
40
- # TODO (spp, 2022-10-20): name this GripperMovementHandler if it doesn't handle
41
- # any non-gripper implementations
42
42
  class LabwareMovementHandler:
43
43
  """Implementation logic for labware movement."""
44
44
 
@@ -81,24 +81,64 @@ class LabwareMovementHandler:
81
81
  )
82
82
  )
83
83
 
84
+ @overload
84
85
  async def move_labware_with_gripper(
85
86
  self,
87
+ *,
86
88
  labware_id: str,
87
89
  current_location: OnDeckLabwareLocation,
88
90
  new_location: OnDeckLabwareLocation,
89
91
  user_offset_data: LabwareMovementOffsetData,
90
92
  post_drop_slide_offset: Optional[Point],
91
93
  ) -> None:
92
- """Move a loaded labware from one location to another using gripper."""
94
+ ...
95
+
96
+ @overload
97
+ async def move_labware_with_gripper(
98
+ self,
99
+ *,
100
+ labware_definition: LabwareDefinition,
101
+ current_location: OnDeckLabwareLocation,
102
+ new_location: OnDeckLabwareLocation,
103
+ user_offset_data: LabwareMovementOffsetData,
104
+ post_drop_slide_offset: Optional[Point],
105
+ ) -> None:
106
+ ...
107
+
108
+ async def move_labware_with_gripper( # noqa: C901
109
+ self,
110
+ *,
111
+ labware_id: str | None = None,
112
+ labware_definition: LabwareDefinition | None = None,
113
+ current_location: OnDeckLabwareLocation,
114
+ new_location: OnDeckLabwareLocation,
115
+ user_offset_data: LabwareMovementOffsetData,
116
+ post_drop_slide_offset: Optional[Point],
117
+ ) -> None:
118
+ """Physically move a labware from one location to another using the gripper.
119
+
120
+ Generally, provide the `labware_id` of a loaded labware, and this method will
121
+ automatically look up its labware definition. If you're physically moving
122
+ something that has not been loaded as a labware (this is not common),
123
+ provide the `labware_definition` yourself instead.
124
+ """
93
125
  use_virtual_gripper = self._state_store.config.use_virtual_gripper
94
126
 
127
+ if labware_definition is None:
128
+ assert labware_id is not None # From this method's @typing.overloads.
129
+ labware_definition = self._state_store.labware.get_definition(labware_id)
130
+
95
131
  if use_virtual_gripper:
96
- # During Analysis we will pass in hard coded estimates for certain positions only accessible during execution
97
- self._state_store.geometry.check_gripper_labware_tip_collision(
98
- gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z,
99
- labware_id=labware_id,
100
- current_location=current_location,
101
- )
132
+ # todo(mm, 2024-11-07): We should do this collision checking even when we
133
+ # only have a `labware_definition`, not a `labware_id`. Resolve when
134
+ # `check_gripper_labware_tip_collision()` can be made independent of `labware_id`.
135
+ if labware_id is not None:
136
+ self._state_store.geometry.check_gripper_labware_tip_collision(
137
+ # During Analysis we will pass in hard coded estimates for certain positions only accessible during execution
138
+ gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z,
139
+ labware_id=labware_id,
140
+ current_location=current_location,
141
+ )
102
142
  return
103
143
 
104
144
  ot3api = ensure_ot3_hardware(
@@ -121,12 +161,15 @@ class LabwareMovementHandler:
121
161
  await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G])
122
162
  gripper_homed_position = await ot3api.gantry_position(mount=gripper_mount)
123
163
 
124
- # Verify that no tip collisions will occur during the move
125
- self._state_store.geometry.check_gripper_labware_tip_collision(
126
- gripper_homed_position_z=gripper_homed_position.z,
127
- labware_id=labware_id,
128
- current_location=current_location,
129
- )
164
+ # todo(mm, 2024-11-07): We should do this collision checking even when we
165
+ # only have a `labware_definition`, not a `labware_id`. Resolve when
166
+ # `check_gripper_labware_tip_collision()` can be made independent of `labware_id`.
167
+ if labware_id is not None:
168
+ self._state_store.geometry.check_gripper_labware_tip_collision(
169
+ gripper_homed_position_z=gripper_homed_position.z,
170
+ labware_id=labware_id,
171
+ current_location=current_location,
172
+ )
130
173
 
131
174
  async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement(
132
175
  labware_location=current_location
@@ -136,13 +179,14 @@ class LabwareMovementHandler:
136
179
  from_location=current_location,
137
180
  to_location=new_location,
138
181
  additional_offset_vector=user_offset_data,
182
+ current_labware=labware_definition,
139
183
  )
140
184
  )
141
185
  from_labware_center = self._state_store.geometry.get_labware_grip_point(
142
- labware_id=labware_id, location=current_location
186
+ labware_definition=labware_definition, location=current_location
143
187
  )
144
188
  to_labware_center = self._state_store.geometry.get_labware_grip_point(
145
- labware_id=labware_id, location=new_location
189
+ labware_definition=labware_definition, location=new_location
146
190
  )
147
191
  movement_waypoints = get_gripper_labware_movement_waypoints(
148
192
  from_labware_center=from_labware_center,
@@ -151,7 +195,9 @@ class LabwareMovementHandler:
151
195
  offset_data=final_offsets,
152
196
  post_drop_slide_offset=post_drop_slide_offset,
153
197
  )
154
- labware_grip_force = self._state_store.labware.get_grip_force(labware_id)
198
+ labware_grip_force = self._state_store.labware.get_grip_force(
199
+ labware_definition
200
+ )
155
201
  holding_labware = False
156
202
  for waypoint_data in movement_waypoints:
157
203
  if waypoint_data.jaw_open:
@@ -174,9 +220,14 @@ class LabwareMovementHandler:
174
220
  # should be holding labware
175
221
  if holding_labware:
176
222
  labware_bbox = self._state_store.labware.get_dimensions(
177
- labware_id
223
+ labware_definition=labware_definition
224
+ )
225
+ well_bbox = self._state_store.labware.get_well_bbox(
226
+ labware_definition=labware_definition
178
227
  )
179
- well_bbox = self._state_store.labware.get_well_bbox(labware_id)
228
+ # todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError.
229
+ # Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError,
230
+ # depending on what waypoint we're at, to propagate a more specific error code to users.
180
231
  ot3api.raise_error_if_gripper_pickup_failed(
181
232
  expected_grip_width=labware_bbox.y,
182
233
  grip_width_uncertainty_wider=abs(
@@ -2,21 +2,23 @@
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
- from opentrons.types import Point, MountType
7
+ from opentrons.types import Point, MountType, StagingSlotName
8
8
  from opentrons.hardware_control import HardwareControlAPI
9
9
  from opentrons_shared_data.errors.exceptions import PositionUnknownError
10
+ from opentrons.protocol_engine.errors import LocationIsStagingSlotError
10
11
 
11
12
  from ..types import (
12
13
  WellLocation,
14
+ LiquidHandlingWellLocation,
13
15
  DeckPoint,
14
16
  MovementAxis,
15
17
  MotorAxis,
16
18
  CurrentWell,
17
19
  AddressableOffsetVector,
18
20
  )
19
- from ..state import StateStore
21
+ from ..state.state import StateStore
20
22
  from ..resources import ModelUtils
21
23
  from .thermocycler_movement_flagger import ThermocyclerMovementFlagger
22
24
  from .heater_shaker_movement_flagger import HeaterShakerMovementFlagger
@@ -66,11 +68,12 @@ class MovementHandler:
66
68
  pipette_id: str,
67
69
  labware_id: str,
68
70
  well_name: str,
69
- well_location: Optional[WellLocation] = None,
71
+ well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]] = None,
70
72
  current_well: Optional[CurrentWell] = None,
71
73
  force_direct: bool = False,
72
74
  minimum_z_height: Optional[float] = None,
73
75
  speed: Optional[float] = None,
76
+ operation_volume: Optional[float] = None,
74
77
  ) -> Point:
75
78
  """Move to a specific well."""
76
79
  self._state_store.labware.raise_if_labware_inaccessible_by_pipette(
@@ -91,9 +94,13 @@ class MovementHandler:
91
94
  self._state_store.modules.get_heater_shaker_movement_restrictors()
92
95
  )
93
96
 
94
- dest_slot_int = self._state_store.geometry.get_ancestor_slot_name(
95
- labware_id
96
- ).as_int()
97
+ ancestor = self._state_store.geometry.get_ancestor_slot_name(labware_id)
98
+ if isinstance(ancestor, StagingSlotName):
99
+ raise LocationIsStagingSlotError(
100
+ "Cannot move to well on labware in Staging Area Slot."
101
+ )
102
+
103
+ dest_slot_int = ancestor.as_int()
97
104
 
98
105
  self._hs_movement_flagger.raise_if_movement_restricted(
99
106
  hs_movement_restrictors=hs_movement_restrictors,
@@ -129,6 +136,7 @@ class MovementHandler:
129
136
  current_well=current_well,
130
137
  force_direct=force_direct,
131
138
  minimum_z_height=minimum_z_height,
139
+ operation_volume=operation_volume,
132
140
  )
133
141
 
134
142
  speed = self._state_store.pipettes.get_movement_speed(
@@ -151,6 +159,7 @@ class MovementHandler:
151
159
  speed: Optional[float] = None,
152
160
  stay_at_highest_possible_z: bool = False,
153
161
  ignore_tip_configuration: Optional[bool] = True,
162
+ highest_possible_z_extra_offset: Optional[float] = None,
154
163
  ) -> Point:
155
164
  """Move to a specific addressable area."""
156
165
  # Check for presence of heater shakers on deck, and if planned
@@ -201,6 +210,7 @@ class MovementHandler:
201
210
  minimum_z_height=minimum_z_height,
202
211
  stay_at_max_travel_z=stay_at_highest_possible_z,
203
212
  ignore_tip_configuration=ignore_tip_configuration,
213
+ max_travel_z_extra_margin=highest_possible_z_extra_offset,
204
214
  )
205
215
 
206
216
  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):