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
@@ -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,
@@ -54,6 +55,7 @@ from .exceptions import (
54
55
  InvalidTargetTemperatureError,
55
56
  InvalidBlockVolumeError,
56
57
  InvalidHoldTimeError,
58
+ InvalidWavelengthError,
57
59
  CannotPerformModuleAction,
58
60
  PauseNotAllowedError,
59
61
  ResumeFromRecoveryNotAllowedError,
@@ -65,9 +67,17 @@ from .exceptions import (
65
67
  LocationIsOccupiedError,
66
68
  LocationNotAccessibleByPipetteError,
67
69
  LocationIsStagingSlotError,
70
+ LocationIsLidDockSlotError,
68
71
  InvalidAxisForRobotType,
69
72
  NotSupportedOnRobotType,
70
73
  CommandNotAllowedError,
74
+ InvalidLiquidHeightFound,
75
+ LiquidHeightUnknownError,
76
+ IncompleteLabwareDefinitionError,
77
+ IncompleteWellDefinitionError,
78
+ OperationLocationNotInWellError,
79
+ InvalidDispenseVolumeError,
80
+ StorageLimitReachedError,
71
81
  )
72
82
 
73
83
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -81,6 +91,7 @@ __all__ = [
81
91
  "InvalidSpecificationForRobotTypeError",
82
92
  "InvalidLoadPipetteSpecsError",
83
93
  "TipNotAttachedError",
94
+ "PickUpTipTipNotAttachedError",
84
95
  "TipAttachedError",
85
96
  "CommandDoesNotExistError",
86
97
  "LabwareNotLoadedError",
@@ -127,6 +138,7 @@ __all__ = [
127
138
  "InvalidTargetSpeedError",
128
139
  "InvalidBlockVolumeError",
129
140
  "InvalidHoldTimeError",
141
+ "InvalidWavelengthError",
130
142
  "CannotPerformModuleAction",
131
143
  "ResumeFromRecoveryNotAllowedError",
132
144
  "PauseNotAllowedError",
@@ -139,9 +151,17 @@ __all__ = [
139
151
  "LocationIsOccupiedError",
140
152
  "LocationNotAccessibleByPipetteError",
141
153
  "LocationIsStagingSlotError",
154
+ "LocationIsLidDockSlotError",
142
155
  "InvalidAxisForRobotType",
143
156
  "NotSupportedOnRobotType",
144
157
  # error occurrence models
145
158
  "ErrorOccurrence",
146
159
  "CommandNotAllowedError",
160
+ "InvalidLiquidHeightFound",
161
+ "LiquidHeightUnknownError",
162
+ "IncompleteLabwareDefinitionError",
163
+ "IncompleteWellDefinitionError",
164
+ "OperationLocationNotInWellError",
165
+ "InvalidDispenseVolumeError",
166
+ "StorageLimitReachedError",
147
167
  ]
@@ -12,8 +12,6 @@ from opentrons_shared_data.errors.exceptions import EnumeratedError
12
12
  log = getLogger(__name__)
13
13
 
14
14
 
15
- # TODO(mc, 2021-11-12): flesh this model out with structured error data
16
- # for each error type so client may produce better error messages
17
15
  class ErrorOccurrence(BaseModel):
18
16
  """An occurrence of a specific error during protocol execution."""
19
17
 
@@ -44,8 +42,15 @@ class ErrorOccurrence(BaseModel):
44
42
  id: str = Field(..., description="Unique identifier of this error occurrence.")
45
43
  createdAt: datetime = Field(..., description="When the error occurred.")
46
44
 
45
+ # Our Python should probably always set this to False--if we want it to be True,
46
+ # we should probably be using a more specific subclass of ErrorOccurrence anyway.
47
+ # However, we can't make this Literal[False], because we want this class to be able
48
+ # to act as a catch-all for parsing defined errors that might be missing some
49
+ # `errorInfo` fields because they were serialized by older software.
47
50
  isDefined: bool = Field(
48
- default=False, # default=False for database backwards compatibility.
51
+ # default=False for database backwards compatibility, so we can parse objects
52
+ # serialized before isDefined existed.
53
+ default=False,
49
54
  description=dedent(
50
55
  """\
51
56
  Whether this error is *defined.*
@@ -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
 
@@ -752,6 +773,19 @@ class InvalidBlockVolumeError(ProtocolEngineError):
752
773
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
753
774
 
754
775
 
776
+ class InvalidWavelengthError(ProtocolEngineError):
777
+ """Raised when attempting to set an invalid absorbance wavelength."""
778
+
779
+ def __init__(
780
+ self,
781
+ message: Optional[str] = None,
782
+ details: Optional[Dict[str, Any]] = None,
783
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
784
+ ) -> None:
785
+ """Build a InvalidWavelengthError."""
786
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
787
+
788
+
755
789
  class InvalidHoldTimeError(ProtocolEngineError):
756
790
  """An error raised when attempting to set an invalid temperature hold time."""
757
791
 
@@ -897,6 +931,19 @@ class LocationIsStagingSlotError(ProtocolEngineError):
897
931
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
898
932
 
899
933
 
934
+ class LocationIsLidDockSlotError(ProtocolEngineError):
935
+ """Raised when referencing a labware on a lid dock slot when trying to get standard deck slot."""
936
+
937
+ def __init__(
938
+ self,
939
+ message: Optional[str] = None,
940
+ details: Optional[Dict[str, Any]] = None,
941
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
942
+ ) -> None:
943
+ """Build a LocationIsLidDockSlotError."""
944
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
945
+
946
+
900
947
  class FirmwareUpdateRequired(ProtocolEngineError):
901
948
  """Raised when the firmware needs to be updated."""
902
949
 
@@ -939,7 +986,7 @@ class InvalidAspirateVolumeError(ProtocolEngineError):
939
986
  """Build a InvalidPipettingVolumeError."""
940
987
  message = (
941
988
  f"Cannot aspirate {attempted_aspirate_volume} µL when only"
942
- f" {available_volume} is available."
989
+ f" {available_volume} is available in the tip."
943
990
  )
944
991
  details = {
945
992
  "attempted_aspirate_volume": attempted_aspirate_volume,
@@ -989,6 +1036,32 @@ class InvalidAxisForRobotType(ProtocolEngineError):
989
1036
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
990
1037
 
991
1038
 
1039
+ class InvalidLiquidHeightFound(ProtocolEngineError):
1040
+ """Raised when attempting to estimate liquid height based on volume fails."""
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 an InvalidLiquidHeightFound error."""
1049
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1050
+
1051
+
1052
+ class LiquidHeightUnknownError(ProtocolEngineError):
1053
+ """Raised when attempting to specify WellOrigin.MENISCUS before liquid probing has been done."""
1054
+
1055
+ def __init__(
1056
+ self,
1057
+ message: Optional[str] = None,
1058
+ details: Optional[Dict[str, Any]] = None,
1059
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1060
+ ) -> None:
1061
+ """Build a LiquidHeightUnknownError."""
1062
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1063
+
1064
+
992
1065
  class EStopActivatedError(ProtocolEngineError):
993
1066
  """Represents an E-stop event."""
994
1067
 
@@ -1030,3 +1103,55 @@ class TipNotEmptyError(ProtocolEngineError):
1030
1103
  ) -> None:
1031
1104
  """Build a TipNotEmptyError."""
1032
1105
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1106
+
1107
+
1108
+ class IncompleteLabwareDefinitionError(ProtocolEngineError):
1109
+ """Raised when a labware definition lacks innerLabwareGeometry in general or for a specific well_id."""
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 IncompleteLabwareDefinitionError."""
1118
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1119
+
1120
+
1121
+ class IncompleteWellDefinitionError(ProtocolEngineError):
1122
+ """Raised when a well definition lacks a geometryDefinitionId."""
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 IncompleteWellDefinitionError."""
1131
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1132
+
1133
+
1134
+ class OperationLocationNotInWellError(ProtocolEngineError):
1135
+ """Raised when a calculated operation location is not within a well."""
1136
+
1137
+ def __init__(
1138
+ self,
1139
+ message: Optional[str] = None,
1140
+ details: Optional[Dict[str, Any]] = None,
1141
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1142
+ ) -> None:
1143
+ """Build an OperationLocationNotInWellError."""
1144
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1145
+
1146
+
1147
+ class StorageLimitReachedError(ProtocolEngineError):
1148
+ """Raised to indicate that a file cannot be created due to storage limitations."""
1149
+
1150
+ def __init__(
1151
+ self,
1152
+ message: Optional[str] = None,
1153
+ detail: Optional[Dict[str, str]] = None,
1154
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1155
+ ) -> None:
1156
+ """Build an StorageLimitReached."""
1157
+ 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",
@@ -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
  )