1
1
from __future__ import annotations
2
2
3
3
import asyncio
4
+ import logging
5
+ import os
4
6
import re
5
7
from concurrent .futures .thread import ThreadPoolExecutor
6
8
from functools import partial
7
- from typing import Optional , List , Dict
9
+ from sys import version
10
+ from typing import Optional , List , Dict , Tuple
11
+
12
+ # TODO: Make sure we move this back into the AsyncByonoy
13
+ import pybyonoy_device_library as byonoy # type: ignore[import-not-found]
14
+
15
+ # for debugging
16
+ byonoy .byonoy_enable_logging (False )
8
17
9
18
10
19
from .hid_protocol import (
20
29
)
21
30
from opentrons .drivers .rpi_drivers .types import USBPort
22
31
32
+ log = logging .getLogger (__name__ )
33
+
23
34
24
35
SN_PARSER = re .compile (r'ATTRS{serial}=="(?P<serial>.+?)"' )
36
+ VERSION_PARSER = re .compile (r'Absorbance (?P<version>V\d+\.\d+\.\d+)' )
25
37
26
38
27
39
class AsyncByonoy :
@@ -71,8 +83,6 @@ async def create(
71
83
loop = loop or asyncio .get_running_loop ()
72
84
executor = ThreadPoolExecutor (max_workers = 1 )
73
85
74
- import pybyonoy_device_library as byonoy # type: ignore[import-not-found]
75
-
76
86
interface : AbsProtocol = byonoy
77
87
78
88
device_sn = cls .serial_number_from_port (usb_port .name )
@@ -111,61 +121,139 @@ def __init__(
111
121
self ._device_handle : Optional [int ] = None
112
122
self ._current_config : Optional [AbsProtocol .MeasurementConfig ] = None
113
123
114
- def _cleanup (self ) -> None :
115
- self ._device_handle = None
124
+ async def open (self ) -> int :
125
+ """
126
+ Open the connection.
116
127
117
- def _open (self ) -> None :
118
- err , device_handle = self ._interface .byonoy_open_device (self ._device )
119
- if err .name != "BYONOY_ERROR_NO_ERROR" :
120
- raise RuntimeError (f"Error opening device: { err } " )
128
+ Returns: boolean denoting connection success.
129
+ """
130
+
131
+ log .warning ("Opening connection!" )
132
+ err , device_handle = await self ._loop .run_in_executor (
133
+ executor = self ._executor , func = partial (
134
+ self ._interface .byonoy_open_device , self ._device ),
135
+ )
136
+ self ._raise_if_error (err .name , f"Error opening device: { err } " )
121
137
self ._device_handle = device_handle
138
+ log .warning (f"Connection Opened: <{ device_handle } >" )
139
+ return bool (device_handle )
122
140
123
- def _free (self ) -> None :
124
- if self ._device_handle :
125
- self ._interface .byonoy_free_device (self ._device_handle )
126
- self ._cleanup ()
141
+ async def close (self ) -> None :
142
+ """Close the connection."""
143
+ handle = self ._verify_device_handle ()
144
+ await self ._loop .run_in_executor (
145
+ executor = self ._executor , func = partial (
146
+ self ._interface .byonoy_free_device , handle )
147
+ )
148
+ self ._device_handle = None
127
149
128
- def verify_device_handle (self ) -> int :
129
- assert self ._device_handle is not None , RuntimeError (
130
- "Device handle not set up."
150
+ async def is_open (self ) -> bool :
151
+ """True if connection is open."""
152
+ handle = self ._verify_device_handle ()
153
+ return await self ._loop .run_in_executor (
154
+ executor = self ._executor , func = partial (
155
+ self ._interface .byonoy_device_open , handle )
131
156
)
132
- return self ._device_handle
133
157
134
- def _raise_if_error (
135
- self ,
136
- err_name : ErrorCodeNames ,
137
- msg : str = "Error occurred: " ,
138
- ) -> None :
139
- if err_name != "BYONOY_ERROR_NO_ERROR" :
140
- raise RuntimeError (msg , err_name )
158
+ async def get_device_information (self ) -> Dict [str , str ]:
159
+ """Get serial number and version info."""
160
+ handle = self ._verify_device_handle ()
161
+ err , device_info = await self ._loop .run_in_executor (
162
+ executor = self ._executor , func = partial (
163
+ self ._interface .byonoy_get_device_information , handle )
164
+ )
165
+ self ._raise_if_error (err .name , f"Error getting device information: { err } " )
141
166
142
- def _get_device_information (self ) -> AbsProtocol .DeviceInfo :
143
- handle = self .verify_device_handle ()
144
- err , device_info = self ._interface .byonoy_get_device_information (handle )
145
- self ._raise_if_error (err .name , "Error getting device information: " )
146
- return device_info
167
+ match = VERSION_PARSER .match (device_info .version )
168
+ version = match ["version" ].lower () if match else "v0.0.0"
169
+ info = {
170
+ "serial" : self ._device .sn ,
171
+ "model" : "ABS96" ,
172
+ "version" : version ,
173
+ }
174
+ log .warning (f"ABS_DEVICE_INFO: { info } " )
175
+ log .warning (f"SERIAL_NUMBER: { self ._device .sn } " )
176
+ return info
147
177
148
- def _get_device_status (self ) -> AbsProtocol .DeviceState :
149
- handle = self .verify_device_handle ()
150
- err , status = self ._interface .byonoy_get_device_status (handle )
178
+ async def get_device_status (self ) -> AbsorbanceReaderDeviceState :
179
+ """Get state information of the device."""
180
+ handle = self ._verify_device_handle ()
181
+ err , status = await self ._loop .run_in_executor (
182
+ executor = self ._executor , func = partial (
183
+ self ._interface .byonoy_get_device_status , handle )
184
+ )
151
185
self ._raise_if_error (err .name , "Error getting device status: " )
152
- return status
186
+ return self .convert_device_state (status .name )
187
+
188
+ async def update_firmware (self , firmware_file_path : str ) -> Tuple [bool , str ]:
189
+ """Updates the firmware of the device."""
190
+ handle = self ._verify_device_handle ()
191
+ if not os .path .exists (firmware_file_path ):
192
+ return False , f"Firmware file not found: { firmware_file_path } "
193
+ # TODO: validate file somehow?
194
+
195
+ # TODO: verify if this needs to run in this context or executor thread
196
+ err = self ._interface .byonoy_update_device (handle , firmware_file_path )
197
+ if err .name != "BYONOY_ERROR_NO_ERROR" :
198
+ return False , f"Byonoy update failed with error: { err } "
199
+ return True , ""
200
+
201
+ async def get_device_uptime (self ) -> int :
202
+ """Get how long in seconds the device has been running for."""
203
+ handle = self ._verify_device_handle ()
204
+ err , uptime = await self ._loop .run_in_executor (
205
+ executor = self ._executor , func = partial (
206
+ self ._interface .byonoy_get_device_uptime , handle )
207
+ )
208
+ self ._raise_if_error (err .name , "Error getting device uptime: " )
209
+ return uptime
210
+
211
+ async def get_lid_status (self ) -> AbsorbanceReaderLidStatus :
212
+ """Get the state of the absorbance lid."""
213
+ handle = self ._verify_device_handle ()
214
+ err , lid_info = await self ._loop .run_in_executor (
215
+ executor = self ._executor , func = partial (
216
+ self ._interface .byonoy_get_device_parts_aligned , handle )
217
+ )
218
+ self ._raise_if_error (err .name , f"Error getting lid status: { err } " )
219
+ return (
220
+ AbsorbanceReaderLidStatus .ON if lid_info else AbsorbanceReaderLidStatus .OFF
221
+ )
222
+
223
+ async def get_supported_wavelengths (self ) -> list [int ]:
224
+ """Get a list of the wavelength readings this device supports."""
225
+ handle = self ._verify_device_handle ()
226
+ err , wavelengths = await self ._loop .run_in_executor (
227
+ executor = self ._executor , func = partial (
228
+ self ._interface .byonoy_abs96_get_available_wavelengths , handle )
229
+ )
230
+ self ._raise_if_error (err .name , "Error getting available wavelengths: " )
231
+ self ._supported_wavelengths = wavelengths
232
+ return wavelengths
153
233
154
- def _get_slot_status (self ) -> AbsProtocol .SlotState :
155
- handle = self .verify_device_handle ()
156
- err , slot_status = self ._interface .byonoy_get_device_slot_status (handle )
157
- self ._raise_if_error (err .name , "Error getting slot status: " )
158
- return slot_status
234
+ async def get_single_measurement (self , wavelength : int ) -> List [float ]:
235
+ """Get a single measurement based on the current configuration."""
236
+ handle = self ._verify_device_handle ()
237
+ assert self ._current_config and self ._current_config .sample_wavelength == wavelength
238
+ err , measurements = await self ._loop .run_in_executor (
239
+ executor = self ._executor , func = partial (
240
+ self ._interface .byonoy_abs96_single_measure , handle , self ._current_config )
241
+ )
242
+ self ._raise_if_error (err .name , f"Error getting single measurement: { err } " )
243
+ return measurements
159
244
160
- def _get_lid_status (self ) -> bool :
161
- handle = self .verify_device_handle ()
162
- lid_on : bool
163
- err , lid_on = self ._interface .byonoy_get_device_parts_aligned (handle )
164
- self ._raise_if_error (err .name , "Error getting lid status: " )
165
- return lid_on
245
+ async def get_plate_presence (self ) -> AbsorbanceReaderPlatePresence :
246
+ """Get the state of the plate for the reader."""
247
+ handle = self ._verify_device_handle ()
248
+ err , presence = await self ._loop .run_in_executor (
249
+ executor = self ._executor , func = partial (
250
+ self ._interface .byonoy_get_device_slot_status , handle )
251
+ )
252
+ self ._raise_if_error (err .name , f"Error getting slot status: { err } " )
253
+ return self .convert_plate_presence (presence .name )
166
254
167
255
def _get_supported_wavelengths (self ) -> List [int ]:
168
- handle = self .verify_device_handle ()
256
+ handle = self ._verify_device_handle ()
169
257
wavelengths : List [int ]
170
258
err , wavelengths = self ._interface .byonoy_abs96_get_available_wavelengths (
171
259
handle
@@ -175,18 +263,11 @@ def _get_supported_wavelengths(self) -> List[int]:
175
263
return wavelengths
176
264
177
265
def _initialize_measurement (self , conf : AbsProtocol .MeasurementConfig ) -> None :
178
- handle = self .verify_device_handle ()
266
+ handle = self ._verify_device_handle ()
179
267
err = self ._interface .byonoy_abs96_initialize_single_measurement (handle , conf )
180
268
self ._raise_if_error (err .name , "Error initializing measurement: " )
181
269
self ._current_config = conf
182
270
183
- def _single_measurement (self , conf : AbsProtocol .MeasurementConfig ) -> List [float ]:
184
- handle = self .verify_device_handle ()
185
- measurements : List [float ]
186
- err , measurements = self ._interface .byonoy_abs96_single_measure (handle , conf )
187
- self ._raise_if_error (err .name , "Error getting single measurement: " )
188
- return measurements
189
-
190
271
def _set_sample_wavelength (self , wavelength : int ) -> AbsProtocol .MeasurementConfig :
191
272
if not self ._supported_wavelengths :
192
273
self ._get_supported_wavelengths ()
@@ -204,100 +285,35 @@ def _initialize(self, wavelength: int) -> None:
204
285
conf = self ._set_sample_wavelength (wavelength )
205
286
self ._initialize_measurement (conf )
206
287
207
- def _get_single_measurement (self , wavelength : int ) -> List [float ]:
208
- initialized = self ._current_config
209
- assert initialized and initialized .sample_wavelength == wavelength
210
- return self ._single_measurement (initialized )
211
-
212
- async def open (self ) -> None :
213
- """
214
- Open the connection.
215
-
216
- Returns: None
217
- """
218
- return await self ._loop .run_in_executor (
219
- executor = self ._executor , func = self ._open
220
- )
221
-
222
- async def close (self ) -> None :
223
- """
224
- Close the connection
225
-
226
- Returns: None
227
- """
228
- await self ._loop .run_in_executor (executor = self ._executor , func = self ._free )
229
-
230
- async def is_open (self ) -> bool :
231
- """
232
- Check if connection is open.
233
-
234
- Returns: boolean
235
- """
236
- return self ._device_handle is not None
237
-
238
- async def get_device_static_info (self ) -> Dict [str , str ]:
239
- return {
240
- "serial" : self ._device .sn ,
241
- "model" : "ABS96" ,
242
- "version" : "1.0" ,
243
- }
244
-
245
- async def get_device_information (self ) -> Dict [str , str ]:
246
- device_info = await self ._loop .run_in_executor (
247
- executor = self ._executor , func = self ._get_device_information
248
- )
249
- return {
250
- "serial_number" : device_info .sn ,
251
- "reference_number" : device_info .ref_no ,
252
- "version" : device_info .version ,
253
- }
254
-
255
- async def get_lid_status (self ) -> AbsorbanceReaderLidStatus :
256
- lid_info = await self ._loop .run_in_executor (
257
- executor = self ._executor , func = self ._get_lid_status
258
- )
259
- return (
260
- AbsorbanceReaderLidStatus .ON if lid_info else AbsorbanceReaderLidStatus .OFF
261
- )
262
-
263
- async def get_supported_wavelengths (self ) -> list [int ]:
264
- return await self ._loop .run_in_executor (
265
- executor = self ._executor , func = self ._get_supported_wavelengths
266
- )
267
-
268
288
async def initialize (self , wavelength : int ) -> None :
269
- return await self ._loop .run_in_executor (
289
+ """???"""
290
+ await self ._loop .run_in_executor (
270
291
executor = self ._executor , func = partial (self ._initialize , wavelength )
271
292
)
272
293
273
- async def get_single_measurement (self , wavelength : int ) -> List [float ]:
274
- return await self ._loop .run_in_executor (
275
- executor = self ._executor ,
276
- func = partial (self ._get_single_measurement , wavelength ),
277
- )
278
-
279
- async def get_plate_presence (self ) -> AbsorbanceReaderPlatePresence :
280
- presence = await self ._loop .run_in_executor (
281
- executor = self ._executor , func = self ._get_slot_status
294
+ def _verify_device_handle (self ) -> int :
295
+ assert self ._device_handle is not None , RuntimeError (
296
+ "Device handle not set up."
282
297
)
283
- return self .convert_plate_presence ( presence . name )
298
+ return self ._device_handle
284
299
285
- async def get_device_status (self ) -> AbsorbanceReaderDeviceState :
286
- status = await self ._loop .run_in_executor (
287
- executor = self ._executor ,
288
- func = self ._get_device_status ,
289
- )
290
- return self .convert_device_state (status .name )
300
+ def _raise_if_error (
301
+ self ,
302
+ err_name : ErrorCodeNames ,
303
+ msg : str = "Error occurred: " ,
304
+ ) -> None :
305
+ if err_name != "BYONOY_ERROR_NO_ERROR" :
306
+ raise RuntimeError (msg , err_name )
291
307
292
308
@staticmethod
293
309
def convert_device_state (
294
310
device_state : DeviceStateNames ,
295
311
) -> AbsorbanceReaderDeviceState :
296
312
state_map : Dict [DeviceStateNames , AbsorbanceReaderDeviceState ] = {
297
- "BYONOY_DEVICE_STATE_UNKNOWN " : AbsorbanceReaderDeviceState .UNKNOWN ,
298
- "BYONOY_DEVICE_STATE_OK " : AbsorbanceReaderDeviceState .OK ,
299
- "BYONOY_DEVICE_STATE_BROKEN_FW " : AbsorbanceReaderDeviceState .BROKEN_FW ,
300
- "BYONOY_DEVICE_STATE_ERROR " : AbsorbanceReaderDeviceState .ERROR ,
313
+ "UNKNOWN " : AbsorbanceReaderDeviceState .UNKNOWN ,
314
+ "OK " : AbsorbanceReaderDeviceState .OK ,
315
+ "BROKEN_FW " : AbsorbanceReaderDeviceState .BROKEN_FW ,
316
+ "ERROR " : AbsorbanceReaderDeviceState .ERROR ,
301
317
}
302
318
return state_map [device_state ]
303
319
0 commit comments