opentrons 8.1.0a0__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.0a0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
  229. {opentrons-8.1.0a0.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.0a0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
  236. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
  237. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
@@ -1,26 +1,31 @@
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
+ # match semver V0.0.0 (old format) or one integer (latest format)
27
+ VERSION_PARSER = re.compile(r"(?P<version>(V\d+\.\d+\.\d+|^\d+$))")
28
+ SERIAL_PARSER = re.compile(r"(?P<serial>(OPT|BYO)[A-Z]{3}[0-9]+)")
24
29
 
25
30
 
26
31
  class AsyncByonoy:
@@ -54,7 +59,7 @@ class AsyncByonoy:
54
59
  port: str,
55
60
  usb_port: USBPort,
56
61
  loop: Optional[asyncio.AbstractEventLoop] = None,
57
- ) -> AsyncByonoy:
62
+ ) -> "AsyncByonoy":
58
63
  """
59
64
  Create an AsyncByonoy instance.
60
65
 
@@ -70,13 +75,13 @@ class AsyncByonoy:
70
75
  loop = loop or asyncio.get_running_loop()
71
76
  executor = ThreadPoolExecutor(max_workers=1)
72
77
 
73
- import pybyonoy_device_library as byonoy # type: ignore[import-not-found]
78
+ import byonoy_devices as byonoy # type: ignore[import-not-found]
74
79
 
75
80
  interface: AbsProtocol = byonoy
76
81
 
77
82
  device_sn = cls.serial_number_from_port(usb_port.name)
78
83
  found: List[AbsProtocol.Device] = await loop.run_in_executor(
79
- executor=executor, func=byonoy.byonoy_available_devices
84
+ executor=executor, func=byonoy.available_devices
80
85
  )
81
86
  device = cls.match_device_with_sn(device_sn, found)
82
87
 
@@ -108,191 +113,240 @@ class AsyncByonoy:
108
113
  self._loop = loop
109
114
  self._supported_wavelengths: Optional[list[int]] = None
110
115
  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
116
+ self._current_config: Optional[MeasurementConfig] = None
181
117
 
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:
118
+ async def open(self) -> bool:
212
119
  """
213
120
  Open the connection.
214
121
 
215
- Returns: None
122
+ Returns: boolean denoting connection success.
216
123
  """
217
- return await self._loop.run_in_executor(
218
- executor=self._executor, func=self._open
124
+
125
+ err, device_handle = await self._loop.run_in_executor(
126
+ executor=self._executor,
127
+ func=partial(self._interface.open_device, self._device),
219
128
  )
129
+ self._raise_if_error(err.name, f"Error opening device: {err}")
130
+ self._device_handle = device_handle
131
+ return bool(device_handle)
220
132
 
221
133
  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)
134
+ """Close the connection."""
135
+ handle = self._verify_device_handle()
136
+ await self._loop.run_in_executor(
137
+ executor=self._executor,
138
+ func=partial(self._interface.free_device, handle),
139
+ )
140
+ self._device_handle = None
228
141
 
229
142
  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
143
+ """True if connection is open."""
144
+ if self._device_handle is None:
145
+ return False
146
+ handle = self._verify_device_handle()
147
+ return await self._loop.run_in_executor(
148
+ executor=self._executor,
149
+ func=partial(self._interface.device_open, handle),
150
+ )
236
151
 
237
- async def get_device_static_info(self) -> Dict[str, str]:
238
- return {
239
- "serial": self._device.sn,
152
+ async def get_device_information(self) -> Dict[str, str]:
153
+ """Get serial number and version info."""
154
+ handle = self._verify_device_handle()
155
+ err, device_info = await self._loop.run_in_executor(
156
+ executor=self._executor,
157
+ func=partial(self._interface.get_device_information, handle),
158
+ )
159
+ self._raise_if_error(err.name, f"Error getting device information: {err}")
160
+ serial_match = SERIAL_PARSER.match(device_info.sn)
161
+ version_match = VERSION_PARSER.search(device_info.version)
162
+ serial = serial_match["serial"].strip() if serial_match else "OPTMAA00000"
163
+ version = version_match["version"].lower() if version_match else "v0"
164
+ info = {
165
+ "serial": serial,
166
+ "version": version,
240
167
  "model": "ABS96",
241
- "version": "1.0",
242
168
  }
169
+ return info
243
170
 
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
171
+ async def get_device_status(self) -> AbsorbanceReaderDeviceState:
172
+ """Get state information of the device."""
173
+ handle = self._verify_device_handle()
174
+ err, status = await self._loop.run_in_executor(
175
+ executor=self._executor,
176
+ func=partial(self._interface.get_device_status, handle),
247
177
  )
248
- return {
249
- "serial_number": device_info.sn,
250
- "reference_number": device_info.ref_no,
251
- "version": device_info.version,
252
- }
178
+ self._raise_if_error(err.name, f"Error getting device status: {err}")
179
+ return self.convert_device_state(status.name)
180
+
181
+ async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]:
182
+ """Updates the firmware of the device."""
183
+ handle = self._verify_device_handle()
184
+ if not os.path.exists(firmware_file_path):
185
+ return False, f"Firmware file not found: {firmware_file_path}"
186
+ err = await self._loop.run_in_executor(
187
+ executor=self._executor,
188
+ func=partial(self._interface.update_device, handle, firmware_file_path),
189
+ )
190
+ if err.name != "NO_ERROR":
191
+ return False, f"Byonoy update failed with error: {err}"
192
+ return True, ""
193
+
194
+ async def get_device_uptime(self) -> int:
195
+ """Get how long in seconds the device has been running for."""
196
+ handle = self._verify_device_handle()
197
+ err, uptime = await self._loop.run_in_executor(
198
+ executor=self._executor,
199
+ func=partial(self._interface.get_device_uptime, handle),
200
+ )
201
+ self._raise_if_error(err.name, "Error getting device uptime: ")
202
+ return uptime
253
203
 
254
204
  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
205
+ """Get the state of the absorbance lid."""
206
+ handle = self._verify_device_handle()
207
+ err, lid_info = await self._loop.run_in_executor(
208
+ executor=self._executor,
209
+ func=partial(self._interface.get_device_parts_aligned, handle),
257
210
  )
211
+ self._raise_if_error(err.name, f"Error getting lid status: {err}")
258
212
  return (
259
213
  AbsorbanceReaderLidStatus.ON if lid_info else AbsorbanceReaderLidStatus.OFF
260
214
  )
261
215
 
262
216
  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
217
+ """Get a list of the wavelength readings this device supports."""
218
+ handle = self._verify_device_handle()
219
+ err, wavelengths = await self._loop.run_in_executor(
220
+ executor=self._executor,
221
+ func=partial(self._interface.abs96_get_available_wavelengths, handle),
265
222
  )
223
+ self._raise_if_error(err.name, "Error getting available wavelengths: ")
224
+ self._supported_wavelengths = wavelengths
225
+ return wavelengths
266
226
 
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)
227
+ async def get_measurement(self) -> List[List[float]]:
228
+ """Gets one or more measurements based on the current configuration."""
229
+ handle = self._verify_device_handle()
230
+ assert (
231
+ self._current_config is not None
232
+ ), "Cannot get measurement without initializing."
233
+ measure_func: Any = self._interface.abs96_single_measure
234
+ if isinstance(self._current_config, AbsProtocol.MultiMeasurementConfig):
235
+ measure_func = self._interface.abs96_multiple_measure
236
+ err, measurements = await self._loop.run_in_executor(
237
+ executor=self._executor,
238
+ func=partial(
239
+ measure_func,
240
+ handle,
241
+ self._current_config,
242
+ ),
270
243
  )
244
+ self._raise_if_error(err.name, f"Error getting measurement: {err}")
245
+ return measurements if isinstance(measurements[0], List) else [measurements] # type: ignore
271
246
 
272
- async def get_single_measurement(self, wavelength: int) -> List[float]:
273
- return await self._loop.run_in_executor(
247
+ async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence:
248
+ """Get the state of the plate for the reader."""
249
+ handle = self._verify_device_handle()
250
+ err, presence = await self._loop.run_in_executor(
274
251
  executor=self._executor,
275
- func=partial(self._get_single_measurement, wavelength),
252
+ func=partial(self._interface.get_device_slot_status, handle),
276
253
  )
254
+ self._raise_if_error(err.name, f"Error getting slot status: {err}")
255
+ return self.convert_plate_presence(presence.name)
277
256
 
278
- async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence:
279
- return AbsorbanceReaderPlatePresence.UNKNOWN
257
+ def _get_supported_wavelengths(self) -> List[int]:
258
+ handle = self._verify_device_handle()
259
+ wavelengths: List[int]
260
+ err, wavelengths = self._interface.abs96_get_available_wavelengths(handle)
261
+ self._raise_if_error(err.name, f"Error getting available wavelengths: {err}")
262
+ self._supported_wavelengths = wavelengths
263
+ return wavelengths
280
264
 
281
- async def get_device_status(self) -> AbsorbanceReaderDeviceState:
282
- status = await self._loop.run_in_executor(
265
+ def _initialize_measurement(self, conf: MeasurementConfig) -> None:
266
+ handle = self._verify_device_handle()
267
+ if isinstance(conf, AbsProtocol.SingleMeasurementConfig):
268
+ err = self._interface.abs96_initialize_single_measurement(handle, conf)
269
+ else:
270
+ err = self._interface.abs96_initialize_multiple_measurement(handle, conf)
271
+ self._raise_if_error(err.name, f"Error initializing measurement: {err}")
272
+ self._current_config = conf
273
+
274
+ def _initialize(
275
+ self,
276
+ mode: ABSMeasurementMode,
277
+ wavelengths: List[int],
278
+ reference_wavelength: Optional[int] = None,
279
+ ) -> None:
280
+ if not self._supported_wavelengths:
281
+ self._get_supported_wavelengths()
282
+ assert self._supported_wavelengths
283
+ conf: MeasurementConfig
284
+ if set(wavelengths).issubset(self._supported_wavelengths):
285
+ if mode == ABSMeasurementMode.SINGLE:
286
+ conf = self._interface.Abs96SingleMeasurementConfig()
287
+ conf.sample_wavelength = wavelengths[0] or 0
288
+ conf.reference_wavelength = reference_wavelength or 0
289
+ else:
290
+ conf = self._interface.Abs96MultipleMeasurementConfig()
291
+ conf.sample_wavelengths = wavelengths
292
+ else:
293
+ raise ValueError(
294
+ f"Unsupported wavelength: {wavelengths}, expected: {self._supported_wavelengths}"
295
+ )
296
+ self._initialize_measurement(conf)
297
+
298
+ async def initialize(
299
+ self,
300
+ mode: ABSMeasurementMode,
301
+ wavelengths: List[int],
302
+ reference_wavelength: Optional[int] = None,
303
+ ) -> None:
304
+ """initialize the device so we can start reading samples from it."""
305
+ await self._loop.run_in_executor(
283
306
  executor=self._executor,
284
- func=self._get_device_status,
307
+ func=partial(self._initialize, mode, wavelengths, reference_wavelength),
285
308
  )
286
- return self.convert_device_state(status.name)
309
+
310
+ def _verify_device_handle(self) -> int:
311
+ assert self._device_handle is not None, RuntimeError(
312
+ "Device handle not set up."
313
+ )
314
+ return self._device_handle
315
+
316
+ def _raise_if_error(
317
+ self,
318
+ err_name: ErrorCodeNames,
319
+ msg: str = "Error occurred: ",
320
+ ) -> None:
321
+ if err_name in [
322
+ "DEVICE_CLOSED",
323
+ "DEVICE_COMMUNICATION_FAILURE",
324
+ "UNSUPPORTED_OPERATION",
325
+ ]:
326
+ raise AbsorbanceReaderDisconnectedError(self._device.sn)
327
+ if err_name != "NO_ERROR":
328
+ raise RuntimeError(msg, err_name)
287
329
 
288
330
  @staticmethod
289
331
  def convert_device_state(
290
332
  device_state: DeviceStateNames,
291
333
  ) -> AbsorbanceReaderDeviceState:
292
334
  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,
335
+ "UNKNOWN": AbsorbanceReaderDeviceState.UNKNOWN,
336
+ "OK": AbsorbanceReaderDeviceState.OK,
337
+ "BROKEN_FW": AbsorbanceReaderDeviceState.BROKEN_FW,
338
+ "ERROR": AbsorbanceReaderDeviceState.ERROR,
297
339
  }
298
340
  return state_map[device_state]
341
+
342
+ @staticmethod
343
+ def convert_plate_presence(
344
+ slot_state: SlotStateNames,
345
+ ) -> AbsorbanceReaderPlatePresence:
346
+ state_map: Dict[SlotStateNames, AbsorbanceReaderPlatePresence] = {
347
+ "UNKNOWN": AbsorbanceReaderPlatePresence.UNKNOWN,
348
+ "EMPTY": AbsorbanceReaderPlatePresence.ABSENT,
349
+ "OCCUPIED": AbsorbanceReaderPlatePresence.PRESENT,
350
+ "UNDETERMINED": AbsorbanceReaderPlatePresence.UNKNOWN,
351
+ }
352
+ 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()