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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (230) hide show
  1. opentrons/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  20. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  21. opentrons/hardware_control/module_control.py +43 -2
  22. opentrons/hardware_control/modules/__init__.py +7 -1
  23. opentrons/hardware_control/modules/absorbance_reader.py +230 -83
  24. opentrons/hardware_control/modules/errors.py +7 -0
  25. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  26. opentrons/hardware_control/modules/magdeck.py +12 -3
  27. opentrons/hardware_control/modules/mod_abc.py +27 -2
  28. opentrons/hardware_control/modules/tempdeck.py +15 -7
  29. opentrons/hardware_control/modules/thermocycler.py +69 -3
  30. opentrons/hardware_control/modules/types.py +11 -5
  31. opentrons/hardware_control/modules/update.py +11 -5
  32. opentrons/hardware_control/modules/utils.py +3 -1
  33. opentrons/hardware_control/ot3_calibration.py +6 -6
  34. opentrons/hardware_control/ot3api.py +126 -89
  35. opentrons/hardware_control/poller.py +15 -11
  36. opentrons/hardware_control/protocols/__init__.py +1 -7
  37. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  38. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  39. opentrons/motion_planning/__init__.py +2 -0
  40. opentrons/motion_planning/waypoints.py +32 -0
  41. opentrons/protocol_api/__init__.py +2 -1
  42. opentrons/protocol_api/_liquid.py +87 -1
  43. opentrons/protocol_api/_parameter_context.py +10 -1
  44. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  45. opentrons/protocol_api/core/engine/instrument.py +29 -25
  46. opentrons/protocol_api/core/engine/labware.py +10 -2
  47. opentrons/protocol_api/core/engine/module_core.py +129 -17
  48. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
  49. opentrons/protocol_api/core/engine/protocol.py +55 -2
  50. opentrons/protocol_api/core/instrument.py +2 -0
  51. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  52. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
  53. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  54. opentrons/protocol_api/core/module.py +22 -4
  55. opentrons/protocol_api/core/protocol.py +5 -2
  56. opentrons/protocol_api/instrument_context.py +52 -20
  57. opentrons/protocol_api/labware.py +13 -1
  58. opentrons/protocol_api/module_contexts.py +68 -13
  59. opentrons/protocol_api/protocol_context.py +38 -4
  60. opentrons/protocol_api/validation.py +5 -3
  61. opentrons/protocol_engine/__init__.py +10 -9
  62. opentrons/protocol_engine/actions/__init__.py +5 -0
  63. opentrons/protocol_engine/actions/actions.py +42 -25
  64. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  65. opentrons/protocol_engine/clients/sync_client.py +7 -1
  66. opentrons/protocol_engine/clients/transports.py +1 -1
  67. opentrons/protocol_engine/commands/__init__.py +0 -4
  68. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  69. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
  70. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
  71. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
  72. opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
  73. opentrons/protocol_engine/commands/aspirate.py +29 -16
  74. opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
  75. opentrons/protocol_engine/commands/blow_out.py +63 -14
  76. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  77. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  78. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  79. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  80. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  81. opentrons/protocol_engine/commands/command.py +28 -17
  82. opentrons/protocol_engine/commands/command_unions.py +37 -24
  83. opentrons/protocol_engine/commands/comment.py +5 -3
  84. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  85. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  86. opentrons/protocol_engine/commands/custom.py +5 -3
  87. opentrons/protocol_engine/commands/dispense.py +42 -20
  88. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  89. opentrons/protocol_engine/commands/drop_tip.py +68 -15
  90. opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
  91. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  92. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  93. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  94. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  95. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  96. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  97. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  98. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  99. opentrons/protocol_engine/commands/home.py +11 -5
  100. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  101. opentrons/protocol_engine/commands/load_labware.py +19 -5
  102. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  103. opentrons/protocol_engine/commands/load_module.py +43 -6
  104. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  105. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  106. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  107. opentrons/protocol_engine/commands/move_labware.py +106 -19
  108. opentrons/protocol_engine/commands/move_relative.py +15 -3
  109. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  110. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  111. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  112. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  113. opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
  114. opentrons/protocol_engine/commands/pipetting_common.py +39 -15
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  116. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  117. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  118. opentrons/protocol_engine/commands/save_position.py +2 -3
  119. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  120. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  121. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  122. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  123. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  124. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  125. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  126. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  127. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  128. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  129. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  130. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  131. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  132. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  133. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  135. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  136. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  137. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  138. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  139. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  140. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
  141. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
  142. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
  143. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  144. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  145. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  146. opentrons/protocol_engine/create_protocol_engine.py +41 -8
  147. opentrons/protocol_engine/engine_support.py +2 -1
  148. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  149. opentrons/protocol_engine/errors/__init__.py +18 -0
  150. opentrons/protocol_engine/errors/exceptions.py +114 -2
  151. opentrons/protocol_engine/execution/__init__.py +2 -0
  152. opentrons/protocol_engine/execution/command_executor.py +22 -13
  153. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  154. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  155. opentrons/protocol_engine/execution/equipment.py +2 -1
  156. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  157. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  158. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  159. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  160. opentrons/protocol_engine/execution/labware_movement.py +6 -3
  161. opentrons/protocol_engine/execution/movement.py +8 -3
  162. opentrons/protocol_engine/execution/pipetting.py +7 -4
  163. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  164. opentrons/protocol_engine/execution/run_control.py +1 -1
  165. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  166. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  167. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  168. opentrons/protocol_engine/notes/__init__.py +14 -2
  169. opentrons/protocol_engine/notes/notes.py +18 -1
  170. opentrons/protocol_engine/plugins.py +1 -1
  171. opentrons/protocol_engine/protocol_engine.py +54 -31
  172. opentrons/protocol_engine/resources/__init__.py +2 -0
  173. opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
  174. opentrons/protocol_engine/resources/file_provider.py +157 -0
  175. opentrons/protocol_engine/resources/fixture_validation.py +5 -0
  176. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  177. opentrons/protocol_engine/state/__init__.py +0 -70
  178. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  179. opentrons/protocol_engine/state/command_history.py +21 -2
  180. opentrons/protocol_engine/state/commands.py +110 -31
  181. opentrons/protocol_engine/state/files.py +59 -0
  182. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  183. opentrons/protocol_engine/state/geometry.py +359 -15
  184. opentrons/protocol_engine/state/labware.py +166 -63
  185. opentrons/protocol_engine/state/liquids.py +1 -1
  186. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
  187. opentrons/protocol_engine/state/modules.py +167 -85
  188. opentrons/protocol_engine/state/motion.py +16 -9
  189. opentrons/protocol_engine/state/pipettes.py +157 -317
  190. opentrons/protocol_engine/state/state.py +30 -1
  191. opentrons/protocol_engine/state/state_summary.py +3 -0
  192. opentrons/protocol_engine/state/tips.py +69 -114
  193. opentrons/protocol_engine/state/update_types.py +408 -0
  194. opentrons/protocol_engine/state/wells.py +236 -0
  195. opentrons/protocol_engine/types.py +90 -0
  196. opentrons/protocol_reader/file_format_validator.py +83 -15
  197. opentrons/protocol_runner/json_translator.py +21 -5
  198. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  199. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  200. opentrons/protocol_runner/protocol_runner.py +6 -3
  201. opentrons/protocol_runner/run_orchestrator.py +26 -6
  202. opentrons/protocols/advanced_control/mix.py +3 -5
  203. opentrons/protocols/advanced_control/transfers.py +125 -56
  204. opentrons/protocols/api_support/constants.py +1 -1
  205. opentrons/protocols/api_support/definitions.py +1 -1
  206. opentrons/protocols/api_support/labware_like.py +4 -4
  207. opentrons/protocols/api_support/tip_tracker.py +2 -2
  208. opentrons/protocols/api_support/types.py +15 -2
  209. opentrons/protocols/api_support/util.py +30 -42
  210. opentrons/protocols/duration/errors.py +1 -1
  211. opentrons/protocols/duration/estimator.py +50 -29
  212. opentrons/protocols/execution/dev_types.py +2 -2
  213. opentrons/protocols/execution/execute_json_v4.py +15 -10
  214. opentrons/protocols/execution/execute_python.py +8 -3
  215. opentrons/protocols/geometry/planning.py +12 -12
  216. opentrons/protocols/labware.py +17 -33
  217. opentrons/simulate.py +3 -3
  218. opentrons/types.py +30 -3
  219. opentrons/util/logging_config.py +34 -0
  220. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
  221. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
  222. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  223. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  224. opentrons/protocol_runner/thread_async_queue.py +0 -174
  225. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  226. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  227. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
  230. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
@@ -5,13 +5,22 @@ 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
+ )
9
11
  from opentrons.util.async_helpers import async_context_manager_in_thread
12
+
10
13
  from opentrons_shared_data.robot import load as load_robot
11
14
 
15
+ from .actions.action_dispatcher import ActionDispatcher
16
+ from .error_recovery_policy import ErrorRecoveryPolicy
17
+ from .execution.door_watcher import DoorWatcher
18
+ from .execution.hardware_stopper import HardwareStopper
19
+ from .plugins import PluginStarter
12
20
  from .protocol_engine import ProtocolEngine
13
- from .resources import DeckDataProvider, ModuleDataProvider
14
- from .state import Config, StateStore
21
+ from .resources import DeckDataProvider, ModuleDataProvider, FileProvider, ModelUtils
22
+ from .state.config import Config
23
+ from .state.state import StateStore
15
24
  from .types import PostRunHardwareState, DeckConfigurationType
16
25
 
17
26
  from .engine_support import create_run_orchestrator
@@ -25,6 +34,7 @@ async def create_protocol_engine(
25
34
  error_recovery_policy: ErrorRecoveryPolicy,
26
35
  load_fixed_trash: bool = False,
27
36
  deck_configuration: typing.Optional[DeckConfigurationType] = None,
37
+ file_provider: typing.Optional[FileProvider] = None,
28
38
  notify_publishers: typing.Optional[typing.Callable[[], None]] = None,
29
39
  ) -> ProtocolEngine:
30
40
  """Create a ProtocolEngine instance.
@@ -36,17 +46,18 @@ async def create_protocol_engine(
36
46
  See documentation on `ErrorRecoveryPolicy`.
37
47
  load_fixed_trash: Automatically load fixed trash labware in engine.
38
48
  deck_configuration: The initial deck configuration the engine will be instantiated with.
49
+ file_provider: Provides access to robot server file writing procedures for protocol output.
39
50
  notify_publishers: Notifies robot server publishers of internal state change.
40
51
  """
41
52
  deck_data = DeckDataProvider(config.deck_type)
42
53
  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 []
54
+ deck_fixed_labware = await deck_data.get_deck_fixed_labware(
55
+ load_fixed_trash, deck_definition, deck_configuration
47
56
  )
57
+
48
58
  module_calibration_offsets = ModuleDataProvider.load_module_calibrations()
49
59
  robot_definition = load_robot(config.robot_type)
60
+
50
61
  state_store = StateStore(
51
62
  config=config,
52
63
  deck_definition=deck_definition,
@@ -58,10 +69,28 @@ async def create_protocol_engine(
58
69
  deck_configuration=deck_configuration,
59
70
  notify_publishers=notify_publishers,
60
71
  )
72
+ hardware_state_synchronizer = ErrorRecoveryHardwareStateSynchronizer(
73
+ hardware_api, state_store
74
+ )
75
+ action_dispatcher = ActionDispatcher(state_store)
76
+ action_dispatcher.add_handler(hardware_state_synchronizer)
77
+ plugin_starter = PluginStarter(state_store, action_dispatcher)
78
+ model_utils = ModelUtils()
79
+ hardware_stopper = HardwareStopper(hardware_api, state_store)
80
+ door_watcher = DoorWatcher(state_store, hardware_api, action_dispatcher)
81
+ module_data_provider = ModuleDataProvider()
82
+ file_provider = file_provider or FileProvider()
61
83
 
62
84
  return ProtocolEngine(
63
- state_store=state_store,
64
85
  hardware_api=hardware_api,
86
+ state_store=state_store,
87
+ action_dispatcher=action_dispatcher,
88
+ plugin_starter=plugin_starter,
89
+ model_utils=model_utils,
90
+ hardware_stopper=hardware_stopper,
91
+ door_watcher=door_watcher,
92
+ module_data_provider=module_data_provider,
93
+ file_provider=file_provider,
65
94
  )
66
95
 
67
96
 
@@ -70,6 +99,7 @@ def create_protocol_engine_in_thread(
70
99
  hardware_api: HardwareControlAPI,
71
100
  config: Config,
72
101
  deck_configuration: typing.Optional[DeckConfigurationType],
102
+ file_provider: typing.Optional[FileProvider],
73
103
  error_recovery_policy: ErrorRecoveryPolicy,
74
104
  drop_tips_after_run: bool,
75
105
  post_run_hardware_state: PostRunHardwareState,
@@ -97,6 +127,7 @@ def create_protocol_engine_in_thread(
97
127
  with async_context_manager_in_thread(
98
128
  _protocol_engine(
99
129
  hardware_api,
130
+ file_provider,
100
131
  config,
101
132
  deck_configuration,
102
133
  error_recovery_policy,
@@ -114,6 +145,7 @@ def create_protocol_engine_in_thread(
114
145
  @contextlib.asynccontextmanager
115
146
  async def _protocol_engine(
116
147
  hardware_api: HardwareControlAPI,
148
+ file_provider: typing.Optional[FileProvider],
117
149
  config: Config,
118
150
  deck_configuration: typing.Optional[DeckConfigurationType],
119
151
  error_recovery_policy: ErrorRecoveryPolicy,
@@ -123,6 +155,7 @@ async def _protocol_engine(
123
155
  ) -> typing.AsyncGenerator[ProtocolEngine, None]:
124
156
  protocol_engine = await create_protocol_engine(
125
157
  hardware_api=hardware_api,
158
+ file_provider=file_provider,
126
159
  config=config,
127
160
  error_recovery_policy=error_recovery_policy,
128
161
  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`.
@@ -8,6 +8,7 @@ from .exceptions import (
8
8
  InvalidSpecificationForRobotTypeError,
9
9
  InvalidLoadPipetteSpecsError,
10
10
  TipNotAttachedError,
11
+ PickUpTipTipNotAttachedError,
11
12
  TipAttachedError,
12
13
  CommandDoesNotExistError,
13
14
  LabwareNotLoadedError,
@@ -65,9 +66,17 @@ from .exceptions import (
65
66
  LocationIsOccupiedError,
66
67
  LocationNotAccessibleByPipetteError,
67
68
  LocationIsStagingSlotError,
69
+ LocationIsLidDockSlotError,
68
70
  InvalidAxisForRobotType,
69
71
  NotSupportedOnRobotType,
70
72
  CommandNotAllowedError,
73
+ InvalidLiquidHeightFound,
74
+ LiquidHeightUnknownError,
75
+ IncompleteLabwareDefinitionError,
76
+ IncompleteWellDefinitionError,
77
+ OperationLocationNotInWellError,
78
+ InvalidDispenseVolumeError,
79
+ StorageLimitReachedError,
71
80
  )
72
81
 
73
82
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -81,6 +90,7 @@ __all__ = [
81
90
  "InvalidSpecificationForRobotTypeError",
82
91
  "InvalidLoadPipetteSpecsError",
83
92
  "TipNotAttachedError",
93
+ "PickUpTipTipNotAttachedError",
84
94
  "TipAttachedError",
85
95
  "CommandDoesNotExistError",
86
96
  "LabwareNotLoadedError",
@@ -139,9 +149,17 @@ __all__ = [
139
149
  "LocationIsOccupiedError",
140
150
  "LocationNotAccessibleByPipetteError",
141
151
  "LocationIsStagingSlotError",
152
+ "LocationIsLidDockSlotError",
142
153
  "InvalidAxisForRobotType",
143
154
  "NotSupportedOnRobotType",
144
155
  # error occurrence models
145
156
  "ErrorOccurrence",
146
157
  "CommandNotAllowedError",
158
+ "InvalidLiquidHeightFound",
159
+ "LiquidHeightUnknownError",
160
+ "IncompleteLabwareDefinitionError",
161
+ "IncompleteWellDefinitionError",
162
+ "OperationLocationNotInWellError",
163
+ "InvalidDispenseVolumeError",
164
+ "StorageLimitReachedError",
147
165
  ]
@@ -1,11 +1,17 @@
1
1
  """Protocol engine exceptions."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from logging import getLogger
4
- from typing import Any, Dict, Optional, Union, Iterator, Sequence
6
+ from typing import Any, Dict, Final, Optional, Union, Iterator, Sequence, TYPE_CHECKING
5
7
 
6
8
  from opentrons_shared_data.errors import ErrorCodes
7
9
  from opentrons_shared_data.errors.exceptions import EnumeratedError, PythonException
8
10
 
11
+ if TYPE_CHECKING:
12
+ from opentrons.protocol_engine.types import TipGeometry
13
+
14
+
9
15
  log = getLogger(__name__)
10
16
 
11
17
 
@@ -132,6 +138,21 @@ class TipNotAttachedError(ProtocolEngineError):
132
138
  super().__init__(ErrorCodes.UNEXPECTED_TIP_REMOVAL, message, details, wrapping)
133
139
 
134
140
 
141
+ class PickUpTipTipNotAttachedError(TipNotAttachedError):
142
+ """Raised from TipHandler.pick_up_tip().
143
+
144
+ This is like TipNotAttachedError except that it carries some extra information
145
+ about the attempted operation.
146
+ """
147
+
148
+ tip_geometry: Final[TipGeometry]
149
+ """The tip geometry that would have been on the pipette, had the operation succeeded."""
150
+
151
+ def __init__(self, tip_geometry: TipGeometry) -> None:
152
+ super().__init__()
153
+ self.tip_geometry = tip_geometry
154
+
155
+
135
156
  class TipAttachedError(ProtocolEngineError):
136
157
  """Raised when a tip shouldn't be attached, but is."""
137
158
 
@@ -897,6 +918,19 @@ class LocationIsStagingSlotError(ProtocolEngineError):
897
918
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
898
919
 
899
920
 
921
+ class LocationIsLidDockSlotError(ProtocolEngineError):
922
+ """Raised when referencing a labware on a lid dock slot when trying to get standard deck slot."""
923
+
924
+ def __init__(
925
+ self,
926
+ message: Optional[str] = None,
927
+ details: Optional[Dict[str, Any]] = None,
928
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
929
+ ) -> None:
930
+ """Build a LocationIsLidDockSlotError."""
931
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
932
+
933
+
900
934
  class FirmwareUpdateRequired(ProtocolEngineError):
901
935
  """Raised when the firmware needs to be updated."""
902
936
 
@@ -939,7 +973,7 @@ class InvalidAspirateVolumeError(ProtocolEngineError):
939
973
  """Build a InvalidPipettingVolumeError."""
940
974
  message = (
941
975
  f"Cannot aspirate {attempted_aspirate_volume} µL when only"
942
- f" {available_volume} is available."
976
+ f" {available_volume} is available in the tip."
943
977
  )
944
978
  details = {
945
979
  "attempted_aspirate_volume": attempted_aspirate_volume,
@@ -989,6 +1023,32 @@ class InvalidAxisForRobotType(ProtocolEngineError):
989
1023
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
990
1024
 
991
1025
 
1026
+ class InvalidLiquidHeightFound(ProtocolEngineError):
1027
+ """Raised when attempting to estimate liquid height based on volume fails."""
1028
+
1029
+ def __init__(
1030
+ self,
1031
+ message: Optional[str] = None,
1032
+ details: Optional[Dict[str, Any]] = None,
1033
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1034
+ ) -> None:
1035
+ """Build an InvalidLiquidHeightFound error."""
1036
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1037
+
1038
+
1039
+ class LiquidHeightUnknownError(ProtocolEngineError):
1040
+ """Raised when attempting to specify WellOrigin.MENISCUS before liquid probing has been done."""
1041
+
1042
+ def __init__(
1043
+ self,
1044
+ message: Optional[str] = None,
1045
+ details: Optional[Dict[str, Any]] = None,
1046
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1047
+ ) -> None:
1048
+ """Build a LiquidHeightUnknownError."""
1049
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1050
+
1051
+
992
1052
  class EStopActivatedError(ProtocolEngineError):
993
1053
  """Represents an E-stop event."""
994
1054
 
@@ -1030,3 +1090,55 @@ class TipNotEmptyError(ProtocolEngineError):
1030
1090
  ) -> None:
1031
1091
  """Build a TipNotEmptyError."""
1032
1092
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1093
+
1094
+
1095
+ class IncompleteLabwareDefinitionError(ProtocolEngineError):
1096
+ """Raised when a labware definition lacks innerLabwareGeometry in general or for a specific well_id."""
1097
+
1098
+ def __init__(
1099
+ self,
1100
+ message: Optional[str] = None,
1101
+ details: Optional[Dict[str, Any]] = None,
1102
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1103
+ ) -> None:
1104
+ """Build an IncompleteLabwareDefinitionError."""
1105
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1106
+
1107
+
1108
+ class IncompleteWellDefinitionError(ProtocolEngineError):
1109
+ """Raised when a well definition lacks a geometryDefinitionId."""
1110
+
1111
+ def __init__(
1112
+ self,
1113
+ message: Optional[str] = None,
1114
+ details: Optional[Dict[str, Any]] = None,
1115
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1116
+ ) -> None:
1117
+ """Build an IncompleteWellDefinitionError."""
1118
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1119
+
1120
+
1121
+ class OperationLocationNotInWellError(ProtocolEngineError):
1122
+ """Raised when a calculated operation location is not within a well."""
1123
+
1124
+ def __init__(
1125
+ self,
1126
+ message: Optional[str] = None,
1127
+ details: Optional[Dict[str, Any]] = None,
1128
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1129
+ ) -> None:
1130
+ """Build an OperationLocationNotInWellError."""
1131
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1132
+
1133
+
1134
+ class StorageLimitReachedError(ProtocolEngineError):
1135
+ """Raised to indicate that a file cannot be created due to storage limitations."""
1136
+
1137
+ def __init__(
1138
+ self,
1139
+ message: Optional[str] = None,
1140
+ detail: Optional[Dict[str, str]] = None,
1141
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1142
+ ) -> None:
1143
+ """Build an StorageLimitReached."""
1144
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, detail, wrapping)
@@ -21,6 +21,7 @@ from .run_control import RunControlHandler
21
21
  from .hardware_stopper import HardwareStopper
22
22
  from .door_watcher import DoorWatcher
23
23
  from .status_bar import StatusBarHandler
24
+ from ..resources.file_provider import FileProvider
24
25
 
25
26
  # .thermocycler_movement_flagger omitted from package's public interface.
26
27
 
@@ -45,4 +46,5 @@ __all__ = [
45
46
  "DoorWatcher",
46
47
  "RailLightsHandler",
47
48
  "StatusBarHandler",
49
+ "FileProvider",
48
50
  ]
@@ -12,9 +12,10 @@ from opentrons_shared_data.errors.exceptions import (
12
12
  )
13
13
 
14
14
  from opentrons.protocol_engine.commands.command import SuccessData
15
+ from opentrons.protocol_engine.notes import make_error_recovery_debug_note
15
16
 
16
- from ..state import StateStore
17
- from ..resources import ModelUtils
17
+ from ..state.state import StateStore
18
+ from ..resources import ModelUtils, FileProvider
18
19
  from ..commands import CommandStatus
19
20
  from ..actions import (
20
21
  ActionDispatcher,
@@ -72,6 +73,7 @@ class CommandExecutor:
72
73
  def __init__(
73
74
  self,
74
75
  hardware_api: HardwareControlAPI,
76
+ file_provider: FileProvider,
75
77
  state_store: StateStore,
76
78
  action_dispatcher: ActionDispatcher,
77
79
  equipment: EquipmentHandler,
@@ -88,6 +90,7 @@ class CommandExecutor:
88
90
  ) -> None:
89
91
  """Initialize the CommandExecutor with access to its dependencies."""
90
92
  self._hardware_api = hardware_api
93
+ self._file_provider = file_provider
91
94
  self._state_store = state_store
92
95
  self._action_dispatcher = action_dispatcher
93
96
  self._equipment = equipment
@@ -116,6 +119,7 @@ class CommandExecutor:
116
119
  command_impl = queued_command._ImplementationCls(
117
120
  state_view=self._state_store,
118
121
  hardware_api=self._hardware_api,
122
+ file_provider=self._file_provider,
119
123
  equipment=self._equipment,
120
124
  movement=self._movement,
121
125
  gantry_mover=self._gantry_mover,
@@ -158,6 +162,12 @@ class CommandExecutor:
158
162
  elif not isinstance(error, EnumeratedError):
159
163
  error = PythonException(error)
160
164
 
165
+ error_recovery_type = error_recovery_policy(
166
+ self._state_store.config,
167
+ running_command,
168
+ None,
169
+ )
170
+ note_tracker(make_error_recovery_debug_note(error_recovery_type))
161
171
  self._action_dispatcher.dispatch(
162
172
  FailCommandAction(
163
173
  error=error,
@@ -166,11 +176,7 @@ class CommandExecutor:
166
176
  error_id=self._model_utils.generate_id(),
167
177
  failed_at=self._model_utils.get_timestamp(),
168
178
  notes=note_tracker.get_notes(),
169
- type=error_recovery_policy(
170
- self._state_store.config,
171
- running_command,
172
- None,
173
- ),
179
+ type=error_recovery_type,
174
180
  )
175
181
  )
176
182
 
@@ -185,11 +191,18 @@ class CommandExecutor:
185
191
  succeeded_command = running_command.copy(update=update)
186
192
  self._action_dispatcher.dispatch(
187
193
  SucceedCommandAction(
188
- command=succeeded_command, private_result=result.private
194
+ command=succeeded_command,
195
+ state_update=result.state_update,
189
196
  ),
190
197
  )
191
198
  else:
192
199
  # The command encountered a defined error.
200
+ error_recovery_type = error_recovery_policy(
201
+ self._state_store.config,
202
+ running_command,
203
+ result,
204
+ )
205
+ note_tracker(make_error_recovery_debug_note(error_recovery_type))
193
206
  self._action_dispatcher.dispatch(
194
207
  FailCommandAction(
195
208
  error=result,
@@ -198,10 +211,6 @@ class CommandExecutor:
198
211
  error_id=result.public.id,
199
212
  failed_at=result.public.createdAt,
200
213
  notes=note_tracker.get_notes(),
201
- type=error_recovery_policy(
202
- self._state_store.config,
203
- running_command,
204
- result,
205
- ),
214
+ type=error_recovery_type,
206
215
  )
207
216
  )
@@ -4,8 +4,9 @@ from typing import AsyncGenerator, Callable
4
4
  from opentrons.hardware_control import HardwareControlAPI
5
5
  from opentrons.protocol_engine.execution.rail_lights import RailLightsHandler
6
6
 
7
- from ..state import StateStore
7
+ from ..state.state import StateStore
8
8
  from ..actions import ActionDispatcher
9
+ from ..resources import FileProvider
9
10
  from .equipment import EquipmentHandler
10
11
  from .movement import MovementHandler
11
12
  from .gantry_mover import create_gantry_mover
@@ -20,6 +21,7 @@ from .status_bar import StatusBarHandler
20
21
 
21
22
  def create_queue_worker(
22
23
  hardware_api: HardwareControlAPI,
24
+ file_provider: FileProvider,
23
25
  state_store: StateStore,
24
26
  action_dispatcher: ActionDispatcher,
25
27
  command_generator: Callable[[], AsyncGenerator[str, None]],
@@ -28,6 +30,7 @@ def create_queue_worker(
28
30
 
29
31
  Arguments:
30
32
  hardware_api: Hardware control API to pass down to dependencies.
33
+ file_provider: Provides access to robot server file writing procedures for protocol output.
31
34
  state_store: StateStore to pass down to dependencies.
32
35
  action_dispatcher: ActionDispatcher to pass down to dependencies.
33
36
  error_recovery_policy: ErrorRecoveryPolicy to pass down to dependencies.
@@ -78,6 +81,7 @@ def create_queue_worker(
78
81
 
79
82
  command_executor = CommandExecutor(
80
83
  hardware_api=hardware_api,
84
+ file_provider=file_provider,
81
85
  state_store=state_store,
82
86
  action_dispatcher=action_dispatcher,
83
87
  equipment=equipment_handler,
@@ -15,7 +15,7 @@ from opentrons.hardware_control.types import (
15
15
 
16
16
  from opentrons.protocol_engine.actions import ActionDispatcher, DoorChangeAction
17
17
 
18
- from ..state import StateStore
18
+ from ..state.state import StateStore
19
19
 
20
20
 
21
21
  _UnsubscribeCallback = Callable[[], None]
@@ -35,7 +35,8 @@ from ..resources import (
35
35
  ModelUtils,
36
36
  pipette_data_provider,
37
37
  )
38
- from ..state import StateStore, HardwareModule
38
+ from ..state.state import StateStore
39
+ from ..state.modules import HardwareModule
39
40
  from ..types import (
40
41
  LabwareLocation,
41
42
  DeckSlotLocation,
@@ -0,0 +1,101 @@
1
+ # noqa: D100
2
+
3
+
4
+ from opentrons.hardware_control import HardwareControlAPI
5
+ from opentrons.protocol_engine.actions.action_handler import ActionHandler
6
+ from opentrons.protocol_engine.actions.actions import (
7
+ Action,
8
+ FailCommandAction,
9
+ ResumeFromRecoveryAction,
10
+ )
11
+ from opentrons.protocol_engine.commands.command import DefinedErrorData
12
+ from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType
13
+ from opentrons.protocol_engine.execution.tip_handler import HardwareTipHandler
14
+ from opentrons.protocol_engine.state import update_types
15
+ from opentrons.protocol_engine.state.state import StateView
16
+
17
+
18
+ class ErrorRecoveryHardwareStateSynchronizer(ActionHandler):
19
+ """A hack to keep the hardware API's state correct through certain error recovery flows.
20
+
21
+ BACKGROUND:
22
+
23
+ Certain parts of robot state are duplicated between `opentrons.protocol_engine` and
24
+ `opentrons.hardware_control`. Stuff like "is there a tip attached."
25
+
26
+ Normally, Protocol Engine command implementations (`opentrons.protocol_engine.commands`)
27
+ mutate hardware API state when they execute; and then when they finish executing,
28
+ the Protocol Engine state stores (`opentrons.protocol_engine.state`) update Protocol
29
+ Engine state accordingly. So both halves are accounted for. This generally works fine.
30
+
31
+ However, we need to go out of our way to support
32
+ `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`.
33
+ It wants to apply a second set of state updates to "fix things up" with the
34
+ new knowledge that some error was a false positive. The Protocol Engine half of that
35
+ is easy for us to apply the normal way, through the state stores; but the
36
+ hardware API half of that cannot be applied the normal way, from the command
37
+ implementation, because the command in question is no longer running.
38
+
39
+ THE HACK:
40
+
41
+ This listens for the same error recovery state updates that the state stores do,
42
+ figures out what hardware API state mutations ought to go along with them,
43
+ and then does those mutations.
44
+
45
+ The problem is that hardware API state is now mutated from two different places
46
+ (sometimes the command implementations, and sometimes here), which are bound
47
+ to grow accidental differences.
48
+
49
+ TO FIX:
50
+
51
+ Make Protocol Engine's use of the hardware API less stateful. e.g. supply
52
+ tip geometry every time we call a hardware API movement method, instead of
53
+ just once when we pick up a tip. Use Protocol Engine state as the single source
54
+ of truth.
55
+ """
56
+
57
+ def __init__(self, hardware_api: HardwareControlAPI, state_view: StateView) -> None:
58
+ self._hardware_api = hardware_api
59
+ self._state_view = state_view
60
+
61
+ def handle_action(self, action: Action) -> None:
62
+ """Modify hardware API state in reaction to a Protocol Engine action."""
63
+ state_update = _get_state_update(action)
64
+ if state_update:
65
+ self._synchronize(state_update)
66
+
67
+ def _synchronize(self, state_update: update_types.StateUpdate) -> None:
68
+ tip_handler = HardwareTipHandler(self._state_view, self._hardware_api)
69
+
70
+ if state_update.pipette_tip_state != update_types.NO_CHANGE:
71
+ pipette_id = state_update.pipette_tip_state.pipette_id
72
+ tip_geometry = state_update.pipette_tip_state.tip_geometry
73
+ if tip_geometry is None:
74
+ tip_handler.remove_tip(pipette_id)
75
+ else:
76
+ tip_handler.cache_tip(pipette_id=pipette_id, tip=tip_geometry)
77
+
78
+
79
+ def _get_state_update(action: Action) -> update_types.StateUpdate | None:
80
+ """Get the mutations that we need to do on the hardware API to stay in sync with an engine action.
81
+
82
+ The mutations are returned in Protocol Engine terms, as a StateUpdate.
83
+ They then need to be converted to hardware API terms.
84
+ """
85
+ match action:
86
+ case ResumeFromRecoveryAction(state_update=state_update):
87
+ return state_update
88
+
89
+ case FailCommandAction(
90
+ error=DefinedErrorData(
91
+ state_update_if_false_positive=state_update_if_false_positive
92
+ )
93
+ ):
94
+ return (
95
+ state_update_if_false_positive
96
+ if action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE
97
+ else None
98
+ )
99
+
100
+ case _:
101
+ return None
@@ -10,7 +10,7 @@ from opentrons_shared_data.errors.exceptions import PositionUnknownError
10
10
 
11
11
  from opentrons.motion_planning import Waypoint
12
12
 
13
- from ..state import StateView
13
+ from ..state.state import StateView
14
14
  from ..types import MotorAxis, CurrentWell
15
15
  from ..errors import MustHomeError, InvalidAxisForRobotType
16
16
 
@@ -273,7 +273,9 @@ class VirtualGantryMover(GantryMover):
273
273
  )
274
274
  else:
275
275
  instrument_height = VIRTUAL_MAX_OT3_HEIGHT
276
- tip_length = self._state_view.tips.get_tip_length(pipette_id)
276
+
277
+ tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette_id)
278
+ tip_length = tip.length if tip is not None else 0
277
279
  return instrument_height - tip_length
278
280
 
279
281
  async def move_to(
@@ -6,7 +6,7 @@ from opentrons.hardware_control import HardwareControlAPI
6
6
  from opentrons.types import PipetteNotAttachedError as HwPipetteNotAttachedError
7
7
 
8
8
  from ..resources.ot3_validation import ensure_ot3_hardware
9
- from ..state import StateStore
9
+ from ..state.state import StateStore
10
10
  from ..types import MotorAxis, PostRunHardwareState
11
11
  from ..errors import HardwareNotSupportedError
12
12
 
@@ -78,7 +78,7 @@ class HardwareStopper:
78
78
  try:
79
79
  if self._state_store.labware.get_fixed_trash_id() == FIXED_TRASH_ID:
80
80
  # OT-2 and Flex 2.15 protocols will default to the Fixed Trash Labware
81
- await self._tip_handler.add_tip(pipette_id=pipette_id, tip=tip)
81
+ self._tip_handler.cache_tip(pipette_id=pipette_id, tip=tip)
82
82
  await self._movement_handler.move_to_well(
83
83
  pipette_id=pipette_id,
84
84
  labware_id=FIXED_TRASH_ID,
@@ -90,7 +90,7 @@ class HardwareStopper:
90
90
  )
91
91
  elif self._state_store.config.robot_type == "OT-2 Standard":
92
92
  # API 2.16 and above OT2 protocols use addressable areas
93
- await self._tip_handler.add_tip(pipette_id=pipette_id, tip=tip)
93
+ self._tip_handler.cache_tip(pipette_id=pipette_id, tip=tip)
94
94
  await self._movement_handler.move_to_addressable_area(
95
95
  pipette_id=pipette_id,
96
96
  addressable_area_name="fixedTrash",