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
@@ -0,0 +1,160 @@
1
+ """Command models to close the lid on an Absorbance Reader."""
2
+ from __future__ import annotations
3
+ from typing import Optional, Literal, TYPE_CHECKING
4
+ from typing_extensions import Type
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
9
+ from ...errors.error_occurrence import ErrorOccurrence
10
+ from ...errors import CannotPerformModuleAction
11
+
12
+ from opentrons.protocol_engine.resources import labware_validation
13
+ from opentrons.protocol_engine.types import AddressableAreaLocation
14
+
15
+ from opentrons.drivers.types import AbsorbanceReaderLidStatus
16
+
17
+ from ...state.update_types import StateUpdate
18
+
19
+
20
+ if TYPE_CHECKING:
21
+ from opentrons.protocol_engine.state.state import StateView
22
+ from opentrons.protocol_engine.execution import (
23
+ EquipmentHandler,
24
+ LabwareMovementHandler,
25
+ )
26
+
27
+
28
+ OpenLidCommandType = Literal["absorbanceReader/openLid"]
29
+
30
+
31
+ class OpenLidParams(BaseModel):
32
+ """Input parameters to open the lid on an absorbance reading."""
33
+
34
+ moduleId: str = Field(..., description="Unique ID of the absorbance reader.")
35
+
36
+
37
+ class OpenLidResult(BaseModel):
38
+ """Result data from opening the lid on an aborbance reading."""
39
+
40
+
41
+ class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]):
42
+ """Execution implementation of opening the lid on an Absorbance Reader."""
43
+
44
+ def __init__(
45
+ self,
46
+ state_view: StateView,
47
+ equipment: EquipmentHandler,
48
+ labware_movement: LabwareMovementHandler,
49
+ **unused_dependencies: object,
50
+ ) -> None:
51
+ self._state_view = state_view
52
+ self._equipment = equipment
53
+ self._labware_movement = labware_movement
54
+
55
+ async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]:
56
+ """Move the absorbance reader lid from the module to the lid dock."""
57
+ mod_substate = self._state_view.modules.get_absorbance_reader_substate(
58
+ module_id=params.moduleId
59
+ )
60
+ # lid should currently be on the module
61
+ assert mod_substate.lid_id is not None
62
+ loaded_lid = self._state_view.labware.get(mod_substate.lid_id)
63
+ assert labware_validation.is_absorbance_reader_lid(loaded_lid.loadName)
64
+
65
+ hardware_lid_status = AbsorbanceReaderLidStatus.ON
66
+ # If the lid is closed, if the lid is open No-op out
67
+ if not self._state_view.config.use_virtual_modules:
68
+ abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
69
+
70
+ if abs_reader is not None:
71
+ result = await abs_reader.get_current_lid_status()
72
+ hardware_lid_status = result
73
+ else:
74
+ raise CannotPerformModuleAction(
75
+ "Could not reach the Hardware API for Opentrons Plate Reader Module."
76
+ )
77
+
78
+ # If the lid is already OFF, no-op the lid removal
79
+ if hardware_lid_status is AbsorbanceReaderLidStatus.OFF:
80
+ assert isinstance(loaded_lid.location, AddressableAreaLocation)
81
+ new_location = loaded_lid.location
82
+ new_offset_id = self._equipment.find_applicable_labware_offset_id(
83
+ labware_definition_uri=loaded_lid.definitionUri,
84
+ labware_location=loaded_lid.location,
85
+ )
86
+ else:
87
+ # Allow propagation of ModuleNotAttachedError.
88
+ _ = self._equipment.get_module_hardware_api(mod_substate.module_id)
89
+
90
+ absorbance_model = self._state_view.modules.get_requested_model(
91
+ params.moduleId
92
+ )
93
+ assert absorbance_model is not None
94
+ current_location = AddressableAreaLocation(
95
+ addressableAreaName=self._state_view.modules.ensure_and_convert_module_fixture_location(
96
+ deck_slot=self._state_view.modules.get_location(
97
+ params.moduleId
98
+ ).slotName,
99
+ deck_type=self._state_view.config.deck_type,
100
+ model=absorbance_model,
101
+ )
102
+ )
103
+
104
+ # we need to move the lid to the lid dock
105
+ new_location = self._state_view.modules.absorbance_reader_dock_location(
106
+ mod_substate.module_id
107
+ )
108
+
109
+ lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets(
110
+ loaded_lid.id, None
111
+ )
112
+ if lid_gripper_offsets is None:
113
+ raise ValueError(
114
+ "Gripper Offset values for Absorbance Reader Lid labware must not be None."
115
+ )
116
+
117
+ # Skips gripper moves when using virtual gripper
118
+ await self._labware_movement.move_labware_with_gripper(
119
+ labware_id=loaded_lid.id,
120
+ current_location=current_location,
121
+ new_location=new_location,
122
+ user_offset_data=lid_gripper_offsets,
123
+ post_drop_slide_offset=None,
124
+ )
125
+ new_offset_id = self._equipment.find_applicable_labware_offset_id(
126
+ labware_definition_uri=loaded_lid.definitionUri,
127
+ labware_location=new_location,
128
+ )
129
+
130
+ state_update = StateUpdate()
131
+
132
+ state_update.set_labware_location(
133
+ labware_id=loaded_lid.id,
134
+ new_location=new_location,
135
+ new_offset_id=new_offset_id,
136
+ )
137
+
138
+ return SuccessData(
139
+ public=OpenLidResult(),
140
+ state_update=state_update,
141
+ )
142
+
143
+
144
+ class OpenLid(BaseCommand[OpenLidParams, OpenLidResult, ErrorOccurrence]):
145
+ """A command to open the lid on an Absorbance Reader."""
146
+
147
+ commandType: OpenLidCommandType = "absorbanceReader/openLid"
148
+ params: OpenLidParams
149
+ result: Optional[OpenLidResult]
150
+
151
+ _ImplementationCls: Type[OpenLidImpl] = OpenLidImpl
152
+
153
+
154
+ class OpenLidCreate(BaseCommandCreate[OpenLidParams]):
155
+ """A request to execute an Absorbance Reader open lid command."""
156
+
157
+ commandType: OpenLidCommandType = "absorbanceReader/openLid"
158
+ params: OpenLidParams
159
+
160
+ _CommandCls: Type[OpenLid] = OpenLid
@@ -0,0 +1,196 @@
1
+ """Command models to read absorbance."""
2
+ from __future__ import annotations
3
+ from datetime import datetime
4
+ from typing import Optional, Dict, TYPE_CHECKING, List
5
+ from typing_extensions import Literal, Type
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
10
+ from ...errors import CannotPerformModuleAction, StorageLimitReachedError
11
+ from ...errors.error_occurrence import ErrorOccurrence
12
+
13
+ from ...resources.file_provider import (
14
+ PlateReaderData,
15
+ ReadData,
16
+ MAXIMUM_CSV_FILE_LIMIT,
17
+ )
18
+ from ...resources import FileProvider
19
+
20
+ if TYPE_CHECKING:
21
+ from opentrons.protocol_engine.state.state import StateView
22
+ from opentrons.protocol_engine.execution import EquipmentHandler
23
+
24
+
25
+ ReadAbsorbanceCommandType = Literal["absorbanceReader/read"]
26
+
27
+
28
+ class ReadAbsorbanceParams(BaseModel):
29
+ """Input parameters for an absorbance reading."""
30
+
31
+ moduleId: str = Field(..., description="Unique ID of the Absorbance Reader.")
32
+ fileName: Optional[str] = Field(
33
+ None,
34
+ description="Optional file name to use when storing the results of a measurement.",
35
+ )
36
+
37
+
38
+ class ReadAbsorbanceResult(BaseModel):
39
+ """Result data from running an aborbance reading, returned as a dictionary map of wavelengths containing a map of values by well name (eg. {450: {"A1": 0.0, ...}})."""
40
+
41
+ data: Optional[Dict[int, Dict[str, float]]] = Field(
42
+ ..., description="Absorbance data points per wavelength."
43
+ )
44
+ fileIds: Optional[List[str]] = Field(
45
+ ...,
46
+ description="List of file IDs for files output as a result of a Read action.",
47
+ )
48
+
49
+
50
+ class ReadAbsorbanceImpl(
51
+ AbstractCommandImpl[ReadAbsorbanceParams, SuccessData[ReadAbsorbanceResult]]
52
+ ):
53
+ """Execution implementation of an Absorbance Reader measurement."""
54
+
55
+ def __init__(
56
+ self,
57
+ state_view: StateView,
58
+ equipment: EquipmentHandler,
59
+ file_provider: FileProvider,
60
+ **unused_dependencies: object,
61
+ ) -> None:
62
+ self._state_view = state_view
63
+ self._equipment = equipment
64
+ self._file_provider = file_provider
65
+
66
+ async def execute( # noqa: C901
67
+ self, params: ReadAbsorbanceParams
68
+ ) -> SuccessData[ReadAbsorbanceResult]:
69
+ """Initiate an absorbance measurement."""
70
+ abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate(
71
+ module_id=params.moduleId
72
+ )
73
+
74
+ # Allow propagation of ModuleNotAttachedError.
75
+ abs_reader = self._equipment.get_module_hardware_api(
76
+ abs_reader_substate.module_id
77
+ )
78
+
79
+ if abs_reader_substate.configured is False:
80
+ raise CannotPerformModuleAction(
81
+ "Cannot perform Read action on Absorbance Reader without calling `.initialize(...)` first."
82
+ )
83
+
84
+ # TODO: we need to return a file ID and increase the file count even when a moduel is not attached
85
+ if (
86
+ params.fileName is not None
87
+ and abs_reader_substate.configured_wavelengths is not None
88
+ ):
89
+ # Validate that the amount of files we are about to generate does not put us higher than the limit
90
+ if (
91
+ self._state_view.files.get_filecount()
92
+ + len(abs_reader_substate.configured_wavelengths)
93
+ > MAXIMUM_CSV_FILE_LIMIT
94
+ ):
95
+ raise StorageLimitReachedError(
96
+ message=f"Attempt to write file {params.fileName} exceeds file creation limit of {MAXIMUM_CSV_FILE_LIMIT} files."
97
+ )
98
+
99
+ asbsorbance_result: Dict[int, Dict[str, float]] = {}
100
+ transform_results = []
101
+ # Handle the measurement and begin building data for return
102
+ if abs_reader is not None:
103
+ start_time = datetime.now()
104
+ results = await abs_reader.start_measure()
105
+ finish_time = datetime.now()
106
+ if abs_reader._measurement_config is not None:
107
+ sample_wavelengths = abs_reader._measurement_config.sample_wavelengths
108
+ for wavelength, result in zip(sample_wavelengths, results):
109
+ converted_values = (
110
+ self._state_view.modules.convert_absorbance_reader_data_points(
111
+ data=result
112
+ )
113
+ )
114
+ asbsorbance_result[wavelength] = converted_values
115
+ transform_results.append(
116
+ ReadData.construct(wavelength=wavelength, data=converted_values)
117
+ )
118
+ # Handle the virtual module case for data creation (all zeroes)
119
+ elif self._state_view.config.use_virtual_modules:
120
+ start_time = finish_time = datetime.now()
121
+ if abs_reader_substate.configured_wavelengths is not None:
122
+ for wavelength in abs_reader_substate.configured_wavelengths:
123
+ converted_values = (
124
+ self._state_view.modules.convert_absorbance_reader_data_points(
125
+ data=[0] * 96
126
+ )
127
+ )
128
+ asbsorbance_result[wavelength] = converted_values
129
+ transform_results.append(
130
+ ReadData.construct(wavelength=wavelength, data=converted_values)
131
+ )
132
+ else:
133
+ raise CannotPerformModuleAction(
134
+ "Plate Reader data cannot be requested with a module that has not been initialized."
135
+ )
136
+
137
+ # TODO (cb, 10-17-2024): FILE PROVIDER - Some day we may want to break the file provider behavior into a seperate API function.
138
+ # When this happens, we probably will to have the change the command results handler we utilize to track file IDs in engine.
139
+ # Today, the action handler for the FileStore looks for a ReadAbsorbanceResult command action, this will need to be delinked.
140
+
141
+ # Begin interfacing with the file provider if the user provided a filename
142
+ file_ids = []
143
+ if params.fileName is not None:
144
+ # Create the Plate Reader Transform
145
+ plate_read_result = PlateReaderData.construct(
146
+ read_results=transform_results,
147
+ reference_wavelength=abs_reader_substate.reference_wavelength,
148
+ start_time=start_time,
149
+ finish_time=finish_time,
150
+ serial_number=abs_reader.serial_number
151
+ if (abs_reader is not None and abs_reader.serial_number is not None)
152
+ else "VIRTUAL_SERIAL",
153
+ )
154
+
155
+ if isinstance(plate_read_result, PlateReaderData):
156
+ # Write a CSV file for each of the measurements taken
157
+ for measurement in plate_read_result.read_results:
158
+ file_id = await self._file_provider.write_csv(
159
+ write_data=plate_read_result.build_generic_csv(
160
+ filename=params.fileName,
161
+ measurement=measurement,
162
+ )
163
+ )
164
+ file_ids.append(file_id)
165
+
166
+ # Return success data to api
167
+ return SuccessData(
168
+ public=ReadAbsorbanceResult(
169
+ data=asbsorbance_result, fileIds=file_ids
170
+ ),
171
+ )
172
+
173
+ return SuccessData(
174
+ public=ReadAbsorbanceResult(data=asbsorbance_result, fileIds=file_ids),
175
+ )
176
+
177
+
178
+ class ReadAbsorbance(
179
+ BaseCommand[ReadAbsorbanceParams, ReadAbsorbanceResult, ErrorOccurrence]
180
+ ):
181
+ """A command to execute an Absorbance Reader measurement."""
182
+
183
+ commandType: ReadAbsorbanceCommandType = "absorbanceReader/read"
184
+ params: ReadAbsorbanceParams
185
+ result: Optional[ReadAbsorbanceResult]
186
+
187
+ _ImplementationCls: Type[ReadAbsorbanceImpl] = ReadAbsorbanceImpl
188
+
189
+
190
+ class ReadAbsorbanceCreate(BaseCommandCreate[ReadAbsorbanceParams]):
191
+ """A request to execute an Absorbance Reader measurement."""
192
+
193
+ commandType: ReadAbsorbanceCommandType = "absorbanceReader/read"
194
+ params: ReadAbsorbanceParams
195
+
196
+ _CommandCls: Type[ReadAbsorbance] = ReadAbsorbance
@@ -6,11 +6,10 @@ from typing_extensions import Literal
6
6
 
7
7
  from .pipetting_common import (
8
8
  OverpressureError,
9
- OverpressureErrorInternalData,
10
9
  PipetteIdMixin,
11
10
  AspirateVolumeMixin,
12
11
  FlowRateMixin,
13
- WellLocationMixin,
12
+ LiquidHandlingWellLocationMixin,
14
13
  BaseLiquidHandlingResult,
15
14
  DestinationPositionResult,
16
15
  )
@@ -25,12 +24,13 @@ from ..errors.error_occurrence import ErrorOccurrence
25
24
 
26
25
  from opentrons.hardware_control import HardwareControlAPI
27
26
 
27
+ from ..state.update_types import StateUpdate, CLEAR
28
28
  from ..types import WellLocation, WellOrigin, CurrentWell, DeckPoint
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  from ..execution import MovementHandler, PipettingHandler
32
32
  from ..resources import ModelUtils
33
- from ..state import StateView
33
+ from ..state.state import StateView
34
34
  from ..notes import CommandNoteAdder
35
35
 
36
36
 
@@ -38,7 +38,7 @@ AspirateCommandType = Literal["aspirate"]
38
38
 
39
39
 
40
40
  class AspirateParams(
41
- PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, WellLocationMixin
41
+ PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
42
42
  ):
43
43
  """Parameters required to aspirate from a specific well."""
44
44
 
@@ -52,8 +52,8 @@ class AspirateResult(BaseLiquidHandlingResult, DestinationPositionResult):
52
52
 
53
53
 
54
54
  _ExecuteReturn = Union[
55
- SuccessData[AspirateResult, None],
56
- DefinedErrorData[OverpressureError, OverpressureErrorInternalData],
55
+ SuccessData[AspirateResult],
56
+ DefinedErrorData[OverpressureError],
57
57
  ]
58
58
 
59
59
 
@@ -92,6 +92,7 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
92
92
  )
93
93
 
94
94
  current_well = None
95
+ state_update = StateUpdate()
95
96
 
96
97
  if not ready_to_aspirate:
97
98
  await self._movement.move_to_well(
@@ -117,6 +118,14 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
117
118
  well_name=well_name,
118
119
  well_location=params.wellLocation,
119
120
  current_well=current_well,
121
+ operation_volume=-params.volume,
122
+ )
123
+ deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
124
+ state_update.set_pipette_location(
125
+ pipette_id=pipette_id,
126
+ new_labware_id=labware_id,
127
+ new_well_name=well_name,
128
+ new_deck_point=deck_point,
120
129
  )
121
130
 
122
131
  try:
@@ -127,6 +136,11 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
127
136
  command_note_adder=self._command_note_adder,
128
137
  )
129
138
  except PipetteOverpressureError as e:
139
+ state_update.set_liquid_operated(
140
+ labware_id=labware_id,
141
+ well_name=well_name,
142
+ volume_added=CLEAR,
143
+ )
130
144
  return DefinedErrorData(
131
145
  public=OverpressureError(
132
146
  id=self._model_utils.generate_id(),
@@ -140,25 +154,24 @@ class AspirateImplementation(AbstractCommandImpl[AspirateParams, _ExecuteReturn]
140
154
  ],
141
155
  errorInfo={"retryLocation": (position.x, position.y, position.z)},
142
156
  ),
143
- private=OverpressureErrorInternalData(
144
- position=DeckPoint.construct(
145
- x=position.x, y=position.y, z=position.z
146
- )
147
- ),
157
+ state_update=state_update,
148
158
  )
149
159
  else:
160
+ state_update.set_liquid_operated(
161
+ labware_id=labware_id,
162
+ well_name=well_name,
163
+ volume_added=-volume_aspirated,
164
+ )
150
165
  return SuccessData(
151
166
  public=AspirateResult(
152
167
  volume=volume_aspirated,
153
- position=DeckPoint.construct(
154
- x=position.x, y=position.y, z=position.z
155
- ),
168
+ position=deck_point,
156
169
  ),
157
- private=None,
170
+ state_update=state_update,
158
171
  )
159
172
 
160
173
 
161
- class Aspirate(BaseCommand[AspirateParams, AspirateResult, ErrorOccurrence]):
174
+ class Aspirate(BaseCommand[AspirateParams, AspirateResult, OverpressureError]):
162
175
  """Aspirate command model."""
163
176
 
164
177
  commandType: AspirateCommandType = "aspirate"
@@ -14,7 +14,6 @@ from .pipetting_common import (
14
14
  FlowRateMixin,
15
15
  BaseLiquidHandlingResult,
16
16
  OverpressureError,
17
- OverpressureErrorInternalData,
18
17
  )
19
18
  from .command import (
20
19
  AbstractCommandImpl,
@@ -25,12 +24,13 @@ from .command import (
25
24
  )
26
25
  from ..errors.error_occurrence import ErrorOccurrence
27
26
  from ..errors.exceptions import PipetteNotReadyToAspirateError
28
- from ..types import DeckPoint
27
+ from ..state.update_types import StateUpdate, CLEAR
28
+ from ..types import CurrentWell
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  from ..execution import PipettingHandler, GantryMover
32
32
  from ..resources import ModelUtils
33
- from ..state import StateView
33
+ from ..state.state import StateView
34
34
  from ..notes import CommandNoteAdder
35
35
 
36
36
  AspirateInPlaceCommandType = Literal["aspirateInPlace"]
@@ -49,8 +49,8 @@ class AspirateInPlaceResult(BaseLiquidHandlingResult):
49
49
 
50
50
 
51
51
  _ExecuteReturn = Union[
52
- SuccessData[AspirateInPlaceResult, None],
53
- DefinedErrorData[OverpressureError, OverpressureErrorInternalData],
52
+ SuccessData[AspirateInPlaceResult],
53
+ DefinedErrorData[OverpressureError],
54
54
  ]
55
55
 
56
56
 
@@ -93,7 +93,12 @@ class AspirateInPlaceImplementation(
93
93
  " The first aspirate following a blow-out must be from a specific well"
94
94
  " so the plunger can be reset in a known safe position."
95
95
  )
96
+
97
+ state_update = StateUpdate()
98
+ current_location = self._state_view.pipettes.get_current_location()
99
+
96
100
  try:
101
+ current_position = await self._gantry_mover.get_position(params.pipetteId)
97
102
  volume = await self._pipetting.aspirate_in_place(
98
103
  pipette_id=params.pipetteId,
99
104
  volume=params.volume,
@@ -101,7 +106,15 @@ class AspirateInPlaceImplementation(
101
106
  command_note_adder=self._command_note_adder,
102
107
  )
103
108
  except PipetteOverpressureError as e:
104
- current_position = await self._gantry_mover.get_position(params.pipetteId)
109
+ if (
110
+ isinstance(current_location, CurrentWell)
111
+ and current_location.pipette_id == params.pipetteId
112
+ ):
113
+ state_update.set_liquid_operated(
114
+ labware_id=current_location.labware_id,
115
+ well_name=current_location.well_name,
116
+ volume_added=CLEAR,
117
+ )
105
118
  return DefinedErrorData(
106
119
  public=OverpressureError(
107
120
  id=self._model_utils.generate_id(),
@@ -123,22 +136,26 @@ class AspirateInPlaceImplementation(
123
136
  }
124
137
  ),
125
138
  ),
126
- private=OverpressureErrorInternalData(
127
- position=DeckPoint(
128
- x=current_position.x,
129
- y=current_position.y,
130
- z=current_position.z,
131
- ),
132
- ),
139
+ state_update=state_update,
133
140
  )
134
141
  else:
142
+ if (
143
+ isinstance(current_location, CurrentWell)
144
+ and current_location.pipette_id == params.pipetteId
145
+ ):
146
+ state_update.set_liquid_operated(
147
+ labware_id=current_location.labware_id,
148
+ well_name=current_location.well_name,
149
+ volume_added=-volume,
150
+ )
135
151
  return SuccessData(
136
- public=AspirateInPlaceResult(volume=volume), private=None
152
+ public=AspirateInPlaceResult(volume=volume),
153
+ state_update=state_update,
137
154
  )
138
155
 
139
156
 
140
157
  class AspirateInPlace(
141
- BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult, ErrorOccurrence]
158
+ BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult, OverpressureError]
142
159
  ):
143
160
  """AspirateInPlace command model."""
144
161