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
@@ -6,7 +6,6 @@ from opentrons.protocol_engine.actions.actions import (
6
6
  ResumeFromRecoveryAction,
7
7
  SetErrorRecoveryPolicyAction,
8
8
  )
9
- from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy
10
9
 
11
10
  from opentrons.protocols.models import LabwareDefinition
12
11
  from opentrons.hardware_control import HardwareControlAPI
@@ -19,8 +18,9 @@ from opentrons_shared_data.errors import (
19
18
 
20
19
  from .errors import ProtocolCommandFailedError, ErrorOccurrence, CommandNotAllowedError
21
20
  from .errors.exceptions import EStopActivatedError
21
+ from .error_recovery_policy import ErrorRecoveryPolicy
22
22
  from . import commands, slot_standardization
23
- from .resources import ModelUtils, ModuleDataProvider
23
+ from .resources import ModelUtils, ModuleDataProvider, FileProvider
24
24
  from .types import (
25
25
  LabwareOffset,
26
26
  LabwareOffsetCreate,
@@ -38,7 +38,8 @@ from .execution import (
38
38
  DoorWatcher,
39
39
  HardwareStopper,
40
40
  )
41
- from .state import StateStore, StateView
41
+ from .state.state import StateStore, StateView
42
+ from .state.update_types import StateUpdate
42
43
  from .plugins import AbstractPlugin, PluginStarter
43
44
  from .actions import (
44
45
  ActionDispatcher,
@@ -87,41 +88,31 @@ class ProtocolEngine:
87
88
  self,
88
89
  hardware_api: HardwareControlAPI,
89
90
  state_store: StateStore,
90
- action_dispatcher: Optional[ActionDispatcher] = None,
91
- plugin_starter: Optional[PluginStarter] = None,
91
+ action_dispatcher: ActionDispatcher,
92
+ plugin_starter: PluginStarter,
93
+ model_utils: ModelUtils,
94
+ hardware_stopper: HardwareStopper,
95
+ door_watcher: DoorWatcher,
96
+ module_data_provider: ModuleDataProvider,
97
+ file_provider: FileProvider,
92
98
  queue_worker: Optional[QueueWorker] = None,
93
- model_utils: Optional[ModelUtils] = None,
94
- hardware_stopper: Optional[HardwareStopper] = None,
95
- door_watcher: Optional[DoorWatcher] = None,
96
- module_data_provider: Optional[ModuleDataProvider] = None,
97
99
  ) -> None:
98
100
  """Initialize a ProtocolEngine instance.
99
101
 
100
102
  Must be called while an event loop is active.
101
103
 
102
- This constructor does not inject provider implementations.
104
+ This constructor is only for `ProtocolEngine` unit tests.
103
105
  Prefer the `create_protocol_engine()` factory function.
104
106
  """
105
107
  self._hardware_api = hardware_api
108
+ self._file_provider = file_provider
106
109
  self._state_store = state_store
107
- self._model_utils = model_utils or ModelUtils()
108
- self._action_dispatcher = action_dispatcher or ActionDispatcher(
109
- sink=self._state_store
110
- )
111
- self._plugin_starter = plugin_starter or PluginStarter(
112
- state=self._state_store,
113
- action_dispatcher=self._action_dispatcher,
114
- )
115
- self._hardware_stopper = hardware_stopper or HardwareStopper(
116
- hardware_api=hardware_api,
117
- state_store=state_store,
118
- )
119
- self._door_watcher = door_watcher or DoorWatcher(
120
- state_store=state_store,
121
- hardware_api=hardware_api,
122
- action_dispatcher=self._action_dispatcher,
123
- )
124
- self._module_data_provider = module_data_provider or ModuleDataProvider()
110
+ self._model_utils = model_utils
111
+ self._action_dispatcher = action_dispatcher
112
+ self._plugin_starter = plugin_starter
113
+ self._hardware_stopper = hardware_stopper
114
+ self._door_watcher = door_watcher
115
+ self._module_data_provider = module_data_provider
125
116
  self._queue_worker = queue_worker
126
117
  if self._queue_worker:
127
118
  self._queue_worker.start()
@@ -183,11 +174,35 @@ class ProtocolEngine:
183
174
  self._action_dispatcher.dispatch(action)
184
175
  self._hardware_api.pause(HardwarePauseType.PAUSE)
185
176
 
186
- def resume_from_recovery(self) -> None:
187
- """Resume normal protocol execution after the engine was `AWAITING_RECOVERY`."""
177
+ def resume_from_recovery(self, reconcile_false_positive: bool) -> None:
178
+ """Resume normal protocol execution after the engine was `AWAITING_RECOVERY`.
179
+
180
+ If `reconcile_false_positive` is `False`, the engine will continue naively from
181
+ whatever state the error left it in. (Each defined error individually documents
182
+ exactly how it affects state.) This is appropriate for client-driven error
183
+ recovery, where the client wants predictable behavior from the engine.
184
+
185
+ If `reconcile_false_positive` is `True`, the engine may apply additional fixups
186
+ to its state to try to get the rest of the run to just work, assuming the error
187
+ was a false-positive.
188
+
189
+ For example, a `tipPhysicallyMissing` error from a `pickUpTip` would normally
190
+ leave the engine state without a tip on the pipette. If `reconcile_false_positive=True`,
191
+ the engine will set the pipette to have that missing tip before continuing, so
192
+ subsequent path planning, aspirates, dispenses, etc. will work as if nothing
193
+ went wrong.
194
+ """
195
+ if reconcile_false_positive:
196
+ state_update = (
197
+ self._state_store.commands.get_state_update_for_false_positive()
198
+ )
199
+ else:
200
+ state_update = StateUpdate() # Empty/no-op.
201
+
188
202
  action = self._state_store.commands.validate_action_allowed(
189
- ResumeFromRecoveryAction()
203
+ ResumeFromRecoveryAction(state_update)
190
204
  )
205
+
191
206
  self._action_dispatcher.dispatch(action)
192
207
 
193
208
  def add_command(
@@ -609,6 +624,7 @@ class ProtocolEngine:
609
624
  assert self._queue_worker is None
610
625
  self._queue_worker = create_queue_worker(
611
626
  hardware_api=self._hardware_api,
627
+ file_provider=self._file_provider,
612
628
  state_store=self._state_store,
613
629
  action_dispatcher=self._action_dispatcher,
614
630
  command_generator=command_generator,
@@ -9,6 +9,7 @@ from .model_utils import ModelUtils
9
9
  from .deck_data_provider import DeckDataProvider, DeckFixedLabware
10
10
  from .labware_data_provider import LabwareDataProvider
11
11
  from .module_data_provider import ModuleDataProvider
12
+ from .file_provider import FileProvider
12
13
  from .ot3_validation import ensure_ot3_hardware
13
14
 
14
15
 
@@ -18,6 +19,7 @@ __all__ = [
18
19
  "DeckDataProvider",
19
20
  "DeckFixedLabware",
20
21
  "ModuleDataProvider",
22
+ "FileProvider",
21
23
  "ensure_ot3_hardware",
22
24
  "pipette_data_provider",
23
25
  "labware_validation",
@@ -13,7 +13,12 @@ from opentrons_shared_data.deck.types import DeckDefinitionV5
13
13
  from opentrons.protocols.models import LabwareDefinition
14
14
  from opentrons.types import DeckSlotName
15
15
 
16
- from ..types import DeckSlotLocation, DeckType
16
+ from ..types import (
17
+ DeckSlotLocation,
18
+ DeckType,
19
+ LabwareLocation,
20
+ DeckConfigurationType,
21
+ )
17
22
  from .labware_data_provider import LabwareDataProvider
18
23
 
19
24
 
@@ -23,7 +28,7 @@ class DeckFixedLabware:
23
28
  """A labware fixture that is always present on a deck."""
24
29
 
25
30
  labware_id: str
26
- location: DeckSlotLocation
31
+ location: LabwareLocation
27
32
  definition: LabwareDefinition
28
33
 
29
34
 
@@ -51,7 +56,9 @@ class DeckDataProvider:
51
56
 
52
57
  async def get_deck_fixed_labware(
53
58
  self,
59
+ load_fixed_trash: bool,
54
60
  deck_definition: DeckDefinitionV5,
61
+ deck_configuration: Optional[DeckConfigurationType] = None,
55
62
  ) -> List[DeckFixedLabware]:
56
63
  """Get a list of all labware fixtures from a given deck definition."""
57
64
  labware: List[DeckFixedLabware] = []
@@ -61,8 +68,15 @@ class DeckDataProvider:
61
68
  load_name = cast(Optional[str], fixture.get("labware"))
62
69
  slot = cast(Optional[str], fixture.get("slot"))
63
70
 
64
- if load_name is not None and slot is not None:
65
- location = DeckSlotLocation(slotName=DeckSlotName.from_primitive(slot))
71
+ if (
72
+ load_fixed_trash
73
+ and load_name is not None
74
+ and slot is not None
75
+ and slot in DeckSlotName._value2member_map_
76
+ ):
77
+ deck_slot_location = DeckSlotLocation(
78
+ slotName=DeckSlotName.from_primitive(slot)
79
+ )
66
80
  definition = await self._labware_data.get_labware_definition(
67
81
  load_name=load_name,
68
82
  namespace="opentrons",
@@ -73,7 +87,7 @@ class DeckDataProvider:
73
87
  DeckFixedLabware(
74
88
  labware_id=labware_id,
75
89
  definition=definition,
76
- location=location,
90
+ location=deck_slot_location,
77
91
  )
78
92
  )
79
93
 
@@ -0,0 +1,161 @@
1
+ """File interaction resource provider."""
2
+ from datetime import datetime
3
+ from typing import List, Optional, Callable, Awaitable, Dict
4
+ from pydantic import BaseModel
5
+ from ..errors import StorageLimitReachedError
6
+
7
+
8
+ MAXIMUM_CSV_FILE_LIMIT = 400
9
+
10
+
11
+ class GenericCsvTransform:
12
+ """Generic CSV File Type data for rows of data to be seperated by a delimeter."""
13
+
14
+ filename: str
15
+ rows: List[List[str]]
16
+ delimiter: str = ","
17
+
18
+ @staticmethod
19
+ def build(
20
+ filename: str, rows: List[List[str]], delimiter: str = ","
21
+ ) -> "GenericCsvTransform":
22
+ """Build a Generic CSV datatype class."""
23
+ if "." in filename and not filename.endswith(".csv"):
24
+ raise ValueError(
25
+ f"Provided filename {filename} invalid. Only CSV file format is accepted."
26
+ )
27
+ elif "." not in filename:
28
+ filename = f"{filename}.csv"
29
+ csv = GenericCsvTransform()
30
+ csv.filename = filename
31
+ csv.rows = rows
32
+ csv.delimiter = delimiter
33
+ return csv
34
+
35
+
36
+ class ReadData(BaseModel):
37
+ """Read Data type containing the wavelength for a Plate Reader read alongside the Measurement Data of that read."""
38
+
39
+ wavelength: int
40
+ data: Dict[str, float]
41
+
42
+
43
+ class PlateReaderData(BaseModel):
44
+ """Data from a Opentrons Plate Reader Read. Can be converted to CSV template format."""
45
+
46
+ read_results: List[ReadData]
47
+ reference_wavelength: Optional[int] = None
48
+ start_time: datetime
49
+ finish_time: datetime
50
+ serial_number: str
51
+
52
+ def build_generic_csv( # noqa: C901
53
+ self, filename: str, measurement: ReadData
54
+ ) -> GenericCsvTransform:
55
+ """Builds a CSV compatible object containing Plate Reader Measurements.
56
+
57
+ This will also automatically reformat the provided filename to include the wavelength of those measurements.
58
+ """
59
+ plate_alpharows = ["A", "B", "C", "D", "E", "F", "G", "H"]
60
+ rows = []
61
+
62
+ rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"])
63
+ for i in range(8):
64
+ row = [plate_alpharows[i]]
65
+ for j in range(12):
66
+ row.append(str(measurement.data[f"{plate_alpharows[i]}{j+1}"]))
67
+ rows.append(row)
68
+ for i in range(3):
69
+ rows.append([])
70
+ rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"])
71
+ for i in range(8):
72
+ row = [plate_alpharows[i]]
73
+ for j in range(12):
74
+ row.append("")
75
+ rows.append(row)
76
+ for i in range(3):
77
+ rows.append([])
78
+ rows.append(
79
+ [
80
+ "",
81
+ "ID",
82
+ "Well",
83
+ "Absorbance (OD)",
84
+ "Mean Absorbance (OD)",
85
+ "Absorbance %CV",
86
+ ]
87
+ )
88
+ for i in range(3):
89
+ rows.append([])
90
+ rows.append(
91
+ [
92
+ "",
93
+ "ID",
94
+ "Well",
95
+ "Absorbance (OD)",
96
+ "Mean Absorbance (OD)",
97
+ "Dilution Factor",
98
+ "Absorbance %CV",
99
+ ]
100
+ )
101
+ rows.append(["1", "Sample 1", "", "", "", "1", "", "", "", "", "", ""])
102
+ for i in range(3):
103
+ rows.append([])
104
+
105
+ # end of file metadata
106
+ rows.append(["Protocol"])
107
+ rows.append(["Assay"])
108
+ rows.append(["Sample Wavelength (nm)", str(measurement.wavelength)])
109
+ if self.reference_wavelength is not None:
110
+ rows.append(["Reference Wavelength (nm)", str(self.reference_wavelength)])
111
+ rows.append(["Serial No.", self.serial_number])
112
+ rows.append(
113
+ ["Measurement started at", self.start_time.strftime("%m %d %H:%M:%S %Y")]
114
+ )
115
+ rows.append(
116
+ ["Measurement finished at", self.finish_time.strftime("%m %d %H:%M:%S %Y")]
117
+ )
118
+
119
+ # Ensure the filename adheres to ruleset contains the wavelength for a given measurement
120
+ if filename.endswith(".csv"):
121
+ filename = filename[:-4]
122
+ filename = filename + str(measurement.wavelength) + "nm.csv"
123
+
124
+ return GenericCsvTransform.build(
125
+ filename=filename,
126
+ rows=rows,
127
+ delimiter=",",
128
+ )
129
+
130
+
131
+ class FileProvider:
132
+ """Provider class to wrap file read write interactions to the data files directory in the engine."""
133
+
134
+ def __init__(
135
+ self,
136
+ data_files_write_csv_callback: Optional[
137
+ Callable[[GenericCsvTransform], Awaitable[str]]
138
+ ] = None,
139
+ data_files_filecount: Optional[Callable[[], Awaitable[int]]] = None,
140
+ ) -> None:
141
+ """Initialize the interface callbacks of the File Provider for data file handling within the Protocol Engine.
142
+
143
+ Params:
144
+ data_files_write_csv_callback: Callback to write a CSV file to the data files directory and add it to the database.
145
+ data_files_filecount: Callback to check the amount of data files already present in the data files directory.
146
+ """
147
+ self._data_files_write_csv_callback = data_files_write_csv_callback
148
+ self._data_files_filecount = data_files_filecount
149
+
150
+ async def write_csv(self, write_data: GenericCsvTransform) -> str:
151
+ """Writes the provided CSV object to a file in the Data Files directory. Returns the File ID of the file created."""
152
+ if self._data_files_filecount is not None:
153
+ file_count = await self._data_files_filecount()
154
+ if file_count >= MAXIMUM_CSV_FILE_LIMIT:
155
+ raise StorageLimitReachedError(
156
+ f"Not enough space to store file {write_data.filename}."
157
+ )
158
+ if self._data_files_write_csv_callback is not None:
159
+ return await self._data_files_write_csv_callback(write_data)
160
+ # If we are in an analysis or simulation state, return an empty file ID
161
+ return ""
@@ -29,7 +29,12 @@ def is_drop_tip_waste_chute(addressable_area_name: str) -> bool:
29
29
 
30
30
  def is_trash(addressable_area_name: str) -> bool:
31
31
  """Check if an addressable area is a trash bin."""
32
- return addressable_area_name in {"movableTrash", "fixedTrash", "shortFixedTrash"}
32
+ return any(
33
+ [
34
+ s in addressable_area_name
35
+ for s in {"movableTrash", "fixedTrash", "shortFixedTrash"}
36
+ ]
37
+ )
33
38
 
34
39
 
35
40
  def is_staging_slot(addressable_area_name: str) -> bool:
@@ -46,3 +51,8 @@ def is_deck_slot(addressable_area_name: str) -> bool:
46
51
  except ValueError:
47
52
  return False
48
53
  return True
54
+
55
+
56
+ def is_abs_reader(addressable_area_name: str) -> bool:
57
+ """Check if an addressable area is an absorbance plate reader area."""
58
+ return "absorbanceReaderV1" in addressable_area_name
@@ -9,6 +9,11 @@ def is_flex_trash(load_name: str) -> bool:
9
9
  return load_name == "opentrons_1_trash_3200ml_fixed"
10
10
 
11
11
 
12
+ def is_absorbance_reader_lid(load_name: str) -> bool:
13
+ """Check if a labware is an absorbance reader lid."""
14
+ return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
15
+
16
+
12
17
  def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
13
18
  """Validate that one of the definition's allowed roles is `labware`.
14
19
 
@@ -22,6 +27,11 @@ def validate_definition_is_adapter(definition: LabwareDefinition) -> bool:
22
27
  return LabwareRole.adapter in definition.allowedRoles
23
28
 
24
29
 
30
+ def validate_definition_is_lid(definition: LabwareDefinition) -> bool:
31
+ """Validate that one of the definition's allowed roles is `lid`."""
32
+ return LabwareRole.lid in definition.allowedRoles
33
+
34
+
25
35
  def validate_labware_can_be_stacked(
26
36
  top_labware_definition: LabwareDefinition, below_labware_load_name: str
27
37
  ) -> bool:
@@ -1,71 +1 @@
1
1
  """Protocol engine state module."""
2
-
3
- from .state import State, StateStore, StateView
4
- from .state_summary import StateSummary
5
- from .config import Config
6
- from .commands import (
7
- CommandState,
8
- CommandView,
9
- CommandSlice,
10
- CommandErrorSlice,
11
- CommandPointer,
12
- )
13
- from .command_history import CommandEntry
14
- from .labware import LabwareState, LabwareView
15
- from .pipettes import PipetteState, PipetteView, HardwarePipette
16
- from .modules import ModuleState, ModuleView, HardwareModule
17
- from .module_substates import (
18
- MagneticModuleId,
19
- MagneticModuleSubState,
20
- HeaterShakerModuleId,
21
- HeaterShakerModuleSubState,
22
- TemperatureModuleId,
23
- TemperatureModuleSubState,
24
- ThermocyclerModuleId,
25
- ThermocyclerModuleSubState,
26
- ModuleSubStateType,
27
- )
28
- from .geometry import GeometryView
29
- from .motion import MotionView, PipetteLocationData
30
-
31
- __all__ = [
32
- # top level state value and interfaces
33
- "State",
34
- "StateStore",
35
- "StateView",
36
- "StateSummary",
37
- # static engine configuration
38
- "Config",
39
- # command state and values
40
- "CommandState",
41
- "CommandView",
42
- "CommandSlice",
43
- "CommandErrorSlice",
44
- "CommandPointer",
45
- "CommandEntry",
46
- # labware state and values
47
- "LabwareState",
48
- "LabwareView",
49
- # pipette state and values
50
- "PipetteState",
51
- "PipetteView",
52
- "HardwarePipette",
53
- # module state and values
54
- "ModuleState",
55
- "ModuleView",
56
- "HardwareModule",
57
- "MagneticModuleId",
58
- "MagneticModuleSubState",
59
- "HeaterShakerModuleId",
60
- "HeaterShakerModuleSubState",
61
- "TemperatureModuleId",
62
- "TemperatureModuleSubState",
63
- "ThermocyclerModuleId",
64
- "ThermocyclerModuleSubState",
65
- "ModuleSubStateType",
66
- # computed geometry state
67
- "GeometryView",
68
- # computed motion state
69
- "MotionView",
70
- "PipetteLocationData",
71
- ]
@@ -43,7 +43,7 @@ from ..actions import (
43
43
  AddAddressableAreaAction,
44
44
  )
45
45
  from .config import Config
46
- from .abstract_store import HasState, HandlesActions
46
+ from ._abstract_store import HasState, HandlesActions
47
47
 
48
48
 
49
49
  @dataclass
@@ -24,6 +24,9 @@ class CommandHistory:
24
24
  _all_command_ids: List[str]
25
25
  """All command IDs, in insertion order."""
26
26
 
27
+ _all_command_ids_but_fixit_command_ids: List[str]
28
+ """All command IDs besides fixit command intents, in insertion order."""
29
+
27
30
  _commands_by_id: Dict[str, CommandEntry]
28
31
  """All command resources, in insertion order, mapped by their unique IDs."""
29
32
 
@@ -44,6 +47,7 @@ class CommandHistory:
44
47
 
45
48
  def __init__(self) -> None:
46
49
  self._all_command_ids = []
50
+ self._all_command_ids_but_fixit_command_ids = []
47
51
  self._queued_command_ids = OrderedSet()
48
52
  self._queued_setup_command_ids = OrderedSet()
49
53
  self._queued_fixit_command_ids = OrderedSet()
@@ -97,13 +101,26 @@ class CommandHistory:
97
101
  for command_id in self._all_command_ids
98
102
  ]
99
103
 
104
+ def get_filtered_command_ids(self, include_fixit_commands: bool) -> List[str]:
105
+ """Get all fixit command IDs."""
106
+ if include_fixit_commands:
107
+ return self._all_command_ids
108
+ else:
109
+ return self._all_command_ids_but_fixit_command_ids
110
+
100
111
  def get_all_ids(self) -> List[str]:
101
112
  """Get all command IDs."""
102
113
  return self._all_command_ids
103
114
 
104
- def get_slice(self, start: int, stop: int) -> List[Command]:
105
- """Get a list of commands between start and stop."""
115
+ def get_slice(
116
+ self, start: int, stop: int, command_ids: Optional[list[str]] = None
117
+ ) -> List[Command]:
118
+ """Get a list of commands between start and stop.""" """Get a list of commands between start and stop."""
106
119
  commands = self._all_command_ids[start:stop]
120
+ selected_command_ids = (
121
+ command_ids if command_ids is not None else self._all_command_ids
122
+ )
123
+ commands = selected_command_ids[start:stop]
107
124
  return [self._commands_by_id[command].command for command in commands]
108
125
 
109
126
  def get_tail_command(self) -> Optional[CommandEntry]:
@@ -230,6 +247,8 @@ class CommandHistory:
230
247
  """Create or update a command entry."""
231
248
  if command_id not in self._commands_by_id:
232
249
  self._all_command_ids.append(command_id)
250
+ if command_entry.command.intent != CommandIntent.FIXIT:
251
+ self._all_command_ids_but_fixit_command_ids.append(command_id)
233
252
  self._commands_by_id[command_id] = command_entry
234
253
 
235
254
  def _add_to_queue(self, command_id: str) -> None: