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.
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
@@ -1,26 +1,30 @@
1
- from __future__ import annotations
2
-
3
1
  import asyncio
2
+ import os
4
3
  import re
5
4
  from concurrent.futures.thread import ThreadPoolExecutor
6
5
  from functools import partial
7
- from typing import Optional, List, Dict
8
-
6
+ from typing import Any, Optional, List, Dict, Tuple
9
7
 
10
8
  from .hid_protocol import (
11
9
  AbsorbanceHidInterface as AbsProtocol,
12
10
  ErrorCodeNames,
13
11
  DeviceStateNames,
12
+ SlotStateNames,
13
+ MeasurementConfig,
14
14
  )
15
15
  from opentrons.drivers.types import (
16
16
  AbsorbanceReaderLidStatus,
17
17
  AbsorbanceReaderPlatePresence,
18
18
  AbsorbanceReaderDeviceState,
19
+ ABSMeasurementMode,
19
20
  )
20
21
  from opentrons.drivers.rpi_drivers.types import USBPort
22
+ from opentrons.hardware_control.modules.errors import AbsorbanceReaderDisconnectedError
21
23
 
22
24
 
23
25
  SN_PARSER = re.compile(r'ATTRS{serial}=="(?P<serial>.+?)"')
26
+ VERSION_PARSER = re.compile(r"Absorbance (?P<version>V\d+\.\d+\.\d+)")
27
+ SERIAL_PARSER = re.compile(r"(?P<serial>BYO[A-Z]{3}[0-9]{5})")
24
28
 
25
29
 
26
30
  class AsyncByonoy:
@@ -54,7 +58,7 @@ class AsyncByonoy:
54
58
  port: str,
55
59
  usb_port: USBPort,
56
60
  loop: Optional[asyncio.AbstractEventLoop] = None,
57
- ) -> AsyncByonoy:
61
+ ) -> "AsyncByonoy":
58
62
  """
59
63
  Create an AsyncByonoy instance.
60
64
 
@@ -70,13 +74,13 @@ class AsyncByonoy:
70
74
  loop = loop or asyncio.get_running_loop()
71
75
  executor = ThreadPoolExecutor(max_workers=1)
72
76
 
73
- import pybyonoy_device_library as byonoy # type: ignore[import-not-found]
77
+ import byonoy_devices as byonoy # type: ignore[import-not-found]
74
78
 
75
79
  interface: AbsProtocol = byonoy
76
80
 
77
81
  device_sn = cls.serial_number_from_port(usb_port.name)
78
82
  found: List[AbsProtocol.Device] = await loop.run_in_executor(
79
- executor=executor, func=byonoy.byonoy_available_devices
83
+ executor=executor, func=byonoy.available_devices
80
84
  )
81
85
  device = cls.match_device_with_sn(device_sn, found)
82
86
 
@@ -108,191 +112,240 @@ class AsyncByonoy:
108
112
  self._loop = loop
109
113
  self._supported_wavelengths: Optional[list[int]] = None
110
114
  self._device_handle: Optional[int] = None
111
- self._current_config: Optional[AbsProtocol.MeasurementConfig] = None
112
-
113
- def _cleanup(self) -> None:
114
- self._device_handle = None
115
-
116
- def _open(self) -> None:
117
- err, device_handle = self._interface.byonoy_open_device(self._device)
118
- if err.name != "BYONOY_ERROR_NO_ERROR":
119
- raise RuntimeError(f"Error opening device: {err}")
120
- self._device_handle = device_handle
121
-
122
- def _free(self) -> None:
123
- if self._device_handle:
124
- self._interface.byonoy_free_device(self._device_handle)
125
- self._cleanup()
126
-
127
- def verify_device_handle(self) -> int:
128
- assert self._device_handle is not None, RuntimeError(
129
- "Device handle not set up."
130
- )
131
- return self._device_handle
132
-
133
- def _raise_if_error(
134
- self,
135
- err_name: ErrorCodeNames,
136
- msg: str = "Error occurred: ",
137
- ) -> None:
138
- if err_name != "BYONOY_ERROR_NO_ERROR":
139
- raise RuntimeError(msg, err_name)
140
-
141
- def _get_device_information(self) -> AbsProtocol.DeviceInfo:
142
- handle = self.verify_device_handle()
143
- err, device_info = self._interface.byonoy_get_device_information(handle)
144
- self._raise_if_error(err.name, "Error getting device information: ")
145
- return device_info
146
-
147
- def _get_device_status(self) -> AbsProtocol.DeviceState:
148
- handle = self.verify_device_handle()
149
- err, status = self._interface.byonoy_get_device_status(handle)
150
- self._raise_if_error(err.name, "Error getting device status: ")
151
- return status
152
-
153
- def _get_slot_status(self) -> AbsProtocol.SlotState:
154
- handle = self.verify_device_handle()
155
- err, slot_status = self._interface.byonoy_get_device_slot_status(handle)
156
- self._raise_if_error(err.name, "Error getting slot status: ")
157
- return slot_status
158
-
159
- def _get_lid_status(self) -> bool:
160
- handle = self.verify_device_handle()
161
- lid_on: bool
162
- err, lid_on = self._interface.byonoy_get_device_parts_aligned(handle)
163
- self._raise_if_error(err.name, "Error getting lid status: ")
164
- return lid_on
165
-
166
- def _get_supported_wavelengths(self) -> List[int]:
167
- handle = self.verify_device_handle()
168
- wavelengths: List[int]
169
- err, wavelengths = self._interface.byonoy_abs96_get_available_wavelengths(
170
- handle
171
- )
172
- self._raise_if_error(err.name, "Error getting available wavelengths: ")
173
- self._supported_wavelengths = wavelengths
174
- return wavelengths
175
-
176
- def _initialize_measurement(self, conf: AbsProtocol.MeasurementConfig) -> None:
177
- handle = self.verify_device_handle()
178
- err = self._interface.byonoy_abs96_initialize_single_measurement(handle, conf)
179
- self._raise_if_error(err.name, "Error initializing measurement: ")
180
- self._current_config = conf
115
+ self._current_config: Optional[MeasurementConfig] = None
181
116
 
182
- def _single_measurement(self, conf: AbsProtocol.MeasurementConfig) -> List[float]:
183
- handle = self.verify_device_handle()
184
- measurements: List[float]
185
- err, measurements = self._interface.byonoy_abs96_single_measure(handle, conf)
186
- self._raise_if_error(err.name, "Error getting single measurement: ")
187
- return measurements
188
-
189
- def _set_sample_wavelength(self, wavelength: int) -> AbsProtocol.MeasurementConfig:
190
- if not self._supported_wavelengths:
191
- self._get_supported_wavelengths()
192
- assert self._supported_wavelengths
193
- if wavelength in self._supported_wavelengths:
194
- conf = self._interface.ByonoyAbs96SingleMeasurementConfig()
195
- conf.sample_wavelength = wavelength
196
- return conf
197
- else:
198
- raise ValueError(
199
- f"Unsupported wavelength: {wavelength}, expected: {self._supported_wavelengths}"
200
- )
201
-
202
- def _initialize(self, wavelength: int) -> None:
203
- conf = self._set_sample_wavelength(wavelength)
204
- self._initialize_measurement(conf)
205
-
206
- def _get_single_measurement(self, wavelength: int) -> List[float]:
207
- initialized = self._current_config
208
- assert initialized and initialized.sample_wavelength == wavelength
209
- return self._single_measurement(initialized)
210
-
211
- async def open(self) -> None:
117
+ async def open(self) -> bool:
212
118
  """
213
119
  Open the connection.
214
120
 
215
- Returns: None
121
+ Returns: boolean denoting connection success.
216
122
  """
217
- return await self._loop.run_in_executor(
218
- executor=self._executor, func=self._open
123
+
124
+ err, device_handle = await self._loop.run_in_executor(
125
+ executor=self._executor,
126
+ func=partial(self._interface.open_device, self._device),
219
127
  )
128
+ self._raise_if_error(err.name, f"Error opening device: {err}")
129
+ self._device_handle = device_handle
130
+ return bool(device_handle)
220
131
 
221
132
  async def close(self) -> None:
222
- """
223
- Close the connection
224
-
225
- Returns: None
226
- """
227
- await self._loop.run_in_executor(executor=self._executor, func=self._free)
133
+ """Close the connection."""
134
+ handle = self._verify_device_handle()
135
+ await self._loop.run_in_executor(
136
+ executor=self._executor,
137
+ func=partial(self._interface.free_device, handle),
138
+ )
139
+ self._device_handle = None
228
140
 
229
141
  async def is_open(self) -> bool:
230
- """
231
- Check if connection is open.
232
-
233
- Returns: boolean
234
- """
235
- return self._device_handle is not None
142
+ """True if connection is open."""
143
+ if self._device_handle is None:
144
+ return False
145
+ handle = self._verify_device_handle()
146
+ return await self._loop.run_in_executor(
147
+ executor=self._executor,
148
+ func=partial(self._interface.device_open, handle),
149
+ )
236
150
 
237
- async def get_device_static_info(self) -> Dict[str, str]:
238
- return {
239
- "serial": self._device.sn,
151
+ async def get_device_information(self) -> Dict[str, str]:
152
+ """Get serial number and version info."""
153
+ handle = self._verify_device_handle()
154
+ err, device_info = await self._loop.run_in_executor(
155
+ executor=self._executor,
156
+ func=partial(self._interface.get_device_information, handle),
157
+ )
158
+ self._raise_if_error(err.name, f"Error getting device information: {err}")
159
+ serial_match = SERIAL_PARSER.match(device_info.sn)
160
+ version_match = VERSION_PARSER.match(device_info.version)
161
+ serial = serial_match["serial"] if serial_match else "BYOMAA00000"
162
+ version = version_match["version"].lower() if version_match else "v0.0.0"
163
+ info = {
164
+ "serial": serial,
165
+ "version": version,
240
166
  "model": "ABS96",
241
- "version": "1.0",
242
167
  }
168
+ return info
243
169
 
244
- async def get_device_information(self) -> Dict[str, str]:
245
- device_info = await self._loop.run_in_executor(
246
- executor=self._executor, func=self._get_device_information
170
+ async def get_device_status(self) -> AbsorbanceReaderDeviceState:
171
+ """Get state information of the device."""
172
+ handle = self._verify_device_handle()
173
+ err, status = await self._loop.run_in_executor(
174
+ executor=self._executor,
175
+ func=partial(self._interface.get_device_status, handle),
247
176
  )
248
- return {
249
- "serial_number": device_info.sn,
250
- "reference_number": device_info.ref_no,
251
- "version": device_info.version,
252
- }
177
+ self._raise_if_error(err.name, f"Error getting device status: {err}")
178
+ return self.convert_device_state(status.name)
179
+
180
+ async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]:
181
+ """Updates the firmware of the device."""
182
+ handle = self._verify_device_handle()
183
+ if not os.path.exists(firmware_file_path):
184
+ return False, f"Firmware file not found: {firmware_file_path}"
185
+ err = await self._loop.run_in_executor(
186
+ executor=self._executor,
187
+ func=partial(self._interface.update_device, handle, firmware_file_path),
188
+ )
189
+ if err.name != "NO_ERROR":
190
+ return False, f"Byonoy update failed with error: {err}"
191
+ return True, ""
192
+
193
+ async def get_device_uptime(self) -> int:
194
+ """Get how long in seconds the device has been running for."""
195
+ handle = self._verify_device_handle()
196
+ err, uptime = await self._loop.run_in_executor(
197
+ executor=self._executor,
198
+ func=partial(self._interface.get_device_uptime, handle),
199
+ )
200
+ self._raise_if_error(err.name, "Error getting device uptime: ")
201
+ return uptime
253
202
 
254
203
  async def get_lid_status(self) -> AbsorbanceReaderLidStatus:
255
- lid_info = await self._loop.run_in_executor(
256
- executor=self._executor, func=self._get_lid_status
204
+ """Get the state of the absorbance lid."""
205
+ handle = self._verify_device_handle()
206
+ err, lid_info = await self._loop.run_in_executor(
207
+ executor=self._executor,
208
+ func=partial(self._interface.get_device_parts_aligned, handle),
257
209
  )
210
+ self._raise_if_error(err.name, f"Error getting lid status: {err}")
258
211
  return (
259
212
  AbsorbanceReaderLidStatus.ON if lid_info else AbsorbanceReaderLidStatus.OFF
260
213
  )
261
214
 
262
215
  async def get_supported_wavelengths(self) -> list[int]:
263
- return await self._loop.run_in_executor(
264
- executor=self._executor, func=self._get_supported_wavelengths
216
+ """Get a list of the wavelength readings this device supports."""
217
+ handle = self._verify_device_handle()
218
+ err, wavelengths = await self._loop.run_in_executor(
219
+ executor=self._executor,
220
+ func=partial(self._interface.abs96_get_available_wavelengths, handle),
265
221
  )
222
+ self._raise_if_error(err.name, "Error getting available wavelengths: ")
223
+ self._supported_wavelengths = wavelengths
224
+ return wavelengths
266
225
 
267
- async def initialize(self, wavelength: int) -> None:
268
- return await self._loop.run_in_executor(
269
- executor=self._executor, func=partial(self._initialize, wavelength)
226
+ async def get_measurement(self) -> List[List[float]]:
227
+ """Gets one or more measurements based on the current configuration."""
228
+ handle = self._verify_device_handle()
229
+ assert (
230
+ self._current_config is not None
231
+ ), "Cannot get measurement without initializing."
232
+ measure_func: Any = self._interface.abs96_single_measure
233
+ if isinstance(self._current_config, AbsProtocol.MultiMeasurementConfig):
234
+ measure_func = self._interface.abs96_multiple_measure
235
+ err, measurements = await self._loop.run_in_executor(
236
+ executor=self._executor,
237
+ func=partial(
238
+ measure_func,
239
+ handle,
240
+ self._current_config,
241
+ ),
270
242
  )
243
+ self._raise_if_error(err.name, f"Error getting measurement: {err}")
244
+ return measurements if isinstance(measurements[0], List) else [measurements] # type: ignore
271
245
 
272
- async def get_single_measurement(self, wavelength: int) -> List[float]:
273
- return await self._loop.run_in_executor(
246
+ async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence:
247
+ """Get the state of the plate for the reader."""
248
+ handle = self._verify_device_handle()
249
+ err, presence = await self._loop.run_in_executor(
274
250
  executor=self._executor,
275
- func=partial(self._get_single_measurement, wavelength),
251
+ func=partial(self._interface.get_device_slot_status, handle),
276
252
  )
253
+ self._raise_if_error(err.name, f"Error getting slot status: {err}")
254
+ return self.convert_plate_presence(presence.name)
277
255
 
278
- async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence:
279
- return AbsorbanceReaderPlatePresence.UNKNOWN
256
+ def _get_supported_wavelengths(self) -> List[int]:
257
+ handle = self._verify_device_handle()
258
+ wavelengths: List[int]
259
+ err, wavelengths = self._interface.abs96_get_available_wavelengths(handle)
260
+ self._raise_if_error(err.name, f"Error getting available wavelengths: {err}")
261
+ self._supported_wavelengths = wavelengths
262
+ return wavelengths
280
263
 
281
- async def get_device_status(self) -> AbsorbanceReaderDeviceState:
282
- status = await self._loop.run_in_executor(
264
+ def _initialize_measurement(self, conf: MeasurementConfig) -> None:
265
+ handle = self._verify_device_handle()
266
+ if isinstance(conf, AbsProtocol.SingleMeasurementConfig):
267
+ err = self._interface.abs96_initialize_single_measurement(handle, conf)
268
+ else:
269
+ err = self._interface.abs96_initialize_multiple_measurement(handle, conf)
270
+ self._raise_if_error(err.name, f"Error initializing measurement: {err}")
271
+ self._current_config = conf
272
+
273
+ def _initialize(
274
+ self,
275
+ mode: ABSMeasurementMode,
276
+ wavelengths: List[int],
277
+ reference_wavelength: Optional[int] = None,
278
+ ) -> None:
279
+ if not self._supported_wavelengths:
280
+ self._get_supported_wavelengths()
281
+ assert self._supported_wavelengths
282
+ conf: MeasurementConfig
283
+ if set(wavelengths).issubset(self._supported_wavelengths):
284
+ if mode == ABSMeasurementMode.SINGLE:
285
+ conf = self._interface.Abs96SingleMeasurementConfig()
286
+ conf.sample_wavelength = wavelengths[0] or 0
287
+ conf.reference_wavelength = reference_wavelength or 0
288
+ else:
289
+ conf = self._interface.Abs96MultipleMeasurementConfig()
290
+ conf.sample_wavelengths = wavelengths
291
+ else:
292
+ raise ValueError(
293
+ f"Unsupported wavelength: {wavelengths}, expected: {self._supported_wavelengths}"
294
+ )
295
+ self._initialize_measurement(conf)
296
+
297
+ async def initialize(
298
+ self,
299
+ mode: ABSMeasurementMode,
300
+ wavelengths: List[int],
301
+ reference_wavelength: Optional[int] = None,
302
+ ) -> None:
303
+ """initialize the device so we can start reading samples from it."""
304
+ await self._loop.run_in_executor(
283
305
  executor=self._executor,
284
- func=self._get_device_status,
306
+ func=partial(self._initialize, mode, wavelengths, reference_wavelength),
285
307
  )
286
- return self.convert_device_state(status.name)
308
+
309
+ def _verify_device_handle(self) -> int:
310
+ assert self._device_handle is not None, RuntimeError(
311
+ "Device handle not set up."
312
+ )
313
+ return self._device_handle
314
+
315
+ def _raise_if_error(
316
+ self,
317
+ err_name: ErrorCodeNames,
318
+ msg: str = "Error occurred: ",
319
+ ) -> None:
320
+ if err_name in [
321
+ "DEVICE_CLOSED",
322
+ "DEVICE_COMMUNICATION_FAILURE",
323
+ "UNSUPPORTED_OPERATION",
324
+ ]:
325
+ raise AbsorbanceReaderDisconnectedError(self._device.sn)
326
+ if err_name != "NO_ERROR":
327
+ raise RuntimeError(msg, err_name)
287
328
 
288
329
  @staticmethod
289
330
  def convert_device_state(
290
331
  device_state: DeviceStateNames,
291
332
  ) -> AbsorbanceReaderDeviceState:
292
333
  state_map: Dict[DeviceStateNames, AbsorbanceReaderDeviceState] = {
293
- "BYONOY_DEVICE_STATE_UNKNOWN": AbsorbanceReaderDeviceState.UNKNOWN,
294
- "BYONOY_DEVICE_STATE_OK": AbsorbanceReaderDeviceState.OK,
295
- "BYONOY_DEVICE_STATE_BROKEN_FW": AbsorbanceReaderDeviceState.BROKEN_FW,
296
- "BYONOY_DEVICE_STATE_ERROR": AbsorbanceReaderDeviceState.ERROR,
334
+ "UNKNOWN": AbsorbanceReaderDeviceState.UNKNOWN,
335
+ "OK": AbsorbanceReaderDeviceState.OK,
336
+ "BROKEN_FW": AbsorbanceReaderDeviceState.BROKEN_FW,
337
+ "ERROR": AbsorbanceReaderDeviceState.ERROR,
297
338
  }
298
339
  return state_map[device_state]
340
+
341
+ @staticmethod
342
+ def convert_plate_presence(
343
+ slot_state: SlotStateNames,
344
+ ) -> AbsorbanceReaderPlatePresence:
345
+ state_map: Dict[SlotStateNames, AbsorbanceReaderPlatePresence] = {
346
+ "UNKNOWN": AbsorbanceReaderPlatePresence.UNKNOWN,
347
+ "EMPTY": AbsorbanceReaderPlatePresence.ABSENT,
348
+ "OCCUPIED": AbsorbanceReaderPlatePresence.PRESENT,
349
+ "UNDETERMINED": AbsorbanceReaderPlatePresence.UNKNOWN,
350
+ }
351
+ return state_map[slot_state]
@@ -1,14 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- from typing import Dict, Optional, List, TYPE_CHECKING
4
+ from typing import Dict, Optional, List, Tuple, TYPE_CHECKING
5
5
 
6
6
  from opentrons.drivers.types import (
7
+ ABSMeasurementMode,
7
8
  AbsorbanceReaderLidStatus,
8
9
  AbsorbanceReaderDeviceState,
9
10
  AbsorbanceReaderPlatePresence,
10
11
  )
11
- from opentrons.drivers.absorbance_reader.abstract import AbstractAbsorbanceReaderDriver
12
+ from opentrons.drivers.absorbance_reader.abstract import (
13
+ AbstractAbsorbanceReaderDriver,
14
+ )
12
15
  from opentrons.drivers.rpi_drivers.types import USBPort
13
16
 
14
17
  if TYPE_CHECKING:
@@ -32,15 +35,6 @@ class AbsorbanceReaderDriver(AbstractAbsorbanceReaderDriver):
32
35
  def __init__(self, connection: AsyncByonoyType) -> None:
33
36
  self._connection = connection
34
37
 
35
- async def get_device_info(self) -> Dict[str, str]:
36
- """Get device info"""
37
- connected = await self.is_connected()
38
- if not connected:
39
- info = await self._connection.get_device_static_info()
40
- else:
41
- info = await self._connection.get_device_information()
42
- return info
43
-
44
38
  async def connect(self) -> None:
45
39
  """Connect to absorbance reader"""
46
40
  await self._connection.open()
@@ -53,17 +47,32 @@ class AbsorbanceReaderDriver(AbstractAbsorbanceReaderDriver):
53
47
  """Check connection to absorbance reader"""
54
48
  return await self._connection.is_open()
55
49
 
50
+ async def get_device_info(self) -> Dict[str, str]:
51
+ """Get device info"""
52
+ return await self._connection.get_device_information()
53
+
54
+ async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]:
55
+ return await self._connection.update_firmware(firmware_file_path)
56
+
57
+ async def get_uptime(self) -> int:
58
+ return await self._connection.get_device_uptime()
59
+
56
60
  async def get_lid_status(self) -> AbsorbanceReaderLidStatus:
57
61
  return await self._connection.get_lid_status()
58
62
 
59
63
  async def get_available_wavelengths(self) -> List[int]:
60
64
  return await self._connection.get_supported_wavelengths()
61
65
 
62
- async def get_single_measurement(self, wavelength: int) -> List[float]:
63
- return await self._connection.get_single_measurement(wavelength)
66
+ async def initialize_measurement(
67
+ self,
68
+ wavelengths: List[int],
69
+ mode: ABSMeasurementMode = ABSMeasurementMode.SINGLE,
70
+ reference_wavelength: Optional[int] = None,
71
+ ) -> None:
72
+ await self._connection.initialize(mode, wavelengths, reference_wavelength)
64
73
 
65
- async def initialize_measurement(self, wavelength: int) -> None:
66
- await self._connection.initialize(wavelength)
74
+ async def get_measurement(self) -> List[List[float]]:
75
+ return await self._connection.get_measurement()
67
76
 
68
77
  async def get_status(self) -> AbsorbanceReaderDeviceState:
69
78
  return await self._connection.get_device_status()