qlsdk2 0.1.7__py3-none-any.whl → 0.2.0__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.
qlsdk/ar4m/ar4sdk.py CHANGED
@@ -3,12 +3,14 @@ from multiprocessing import Queue
3
3
  import platform
4
4
  from ctypes import (
5
5
  c_int, c_int32, c_int64, c_uint32, c_uint64,
6
- c_char, c_char_p, c_void_p,
6
+ c_char, c_char_p, c_void_p, c_double,
7
7
  Structure, POINTER, CFUNCTYPE
8
8
  )
9
+ from typing import Literal
9
10
  from loguru import logger
10
11
  from time import sleep, time
11
12
  import os
13
+ import numpy as np
12
14
 
13
15
  real_path = os.path.realpath(__file__)
14
16
  dll_path = f'{os.path.dirname(real_path)}/libs/libAr4SDK.dll'
@@ -89,10 +91,34 @@ _dll.ar4_sdk_start_acq.restype = c_int
89
91
  _dll.ar4_sdk_stop_acq.argtypes = [c_void_p]
90
92
  _dll.ar4_sdk_stop_acq.restype = c_int
91
93
  _dll.ar4_sdk_get_record_sample_rate.argtypes = [c_void_p]
92
- _dll.ar4_sdk_get_record_sample_rate.restype = c_char_p
94
+ _dll.ar4_sdk_get_record_sample_rate.restype = c_double
93
95
  _dll.ar4_sdk_get_acq_start_time.argtypes = [c_void_p]
94
96
  _dll.ar4_sdk_get_acq_start_time.restype = c_int64
95
97
 
98
+ # eeg信号物理量转换
99
+ _dll.ar4_sdk_get_eeg_phy_max.argtypes = [c_void_p]
100
+ _dll.ar4_sdk_get_eeg_phy_max.restype = c_double
101
+ _dll.ar4_sdk_get_eeg_phy_min.argtypes = [c_void_p]
102
+ _dll.ar4_sdk_get_eeg_phy_min.restype = c_double
103
+ _dll.ar4_sdk_get_eeg_digtal_max.argtypes = [c_void_p]
104
+ _dll.ar4_sdk_get_eeg_digtal_max.restype = c_int32
105
+ _dll.ar4_sdk_get_eeg_digtal_min.argtypes = [c_void_p]
106
+ _dll.ar4_sdk_get_eeg_digtal_min.restype = c_int32
107
+ _dll.ar4_sdk_get_eeg_phy_unit.argtypes = [c_void_p]
108
+ _dll.ar4_sdk_get_eeg_phy_unit.restype = c_char_p
109
+
110
+ # acc信号物理量转换
111
+ _dll.ar4_sdk_get_acc_phy_max.argtypes = [c_void_p]
112
+ _dll.ar4_sdk_get_acc_phy_max.restype = c_double
113
+ _dll.ar4_sdk_get_acc_phy_min.argtypes = [c_void_p]
114
+ _dll.ar4_sdk_get_acc_phy_min.restype = c_double
115
+ _dll.ar4_sdk_get_acc_digtal_max.argtypes = [c_void_p]
116
+ _dll.ar4_sdk_get_acc_digtal_max.restype = c_int32
117
+ _dll.ar4_sdk_get_acc_digtal_min.argtypes = [c_void_p]
118
+ _dll.ar4_sdk_get_acc_digtal_min.restype = c_int32
119
+ _dll.ar4_sdk_get_acc_phy_unit.argtypes = [c_void_p]
120
+ _dll.ar4_sdk_get_acc_phy_unit.restype = c_char_p
121
+
96
122
  # 回调注册
97
123
  _dll.ar4_sdk_register_data_notify.argtypes = [c_void_p, FuncAr4DataNotify]
98
124
  _dll.ar4_sdk_register_data_notify.restype = None
@@ -147,7 +173,9 @@ def _get_time():
147
173
  # ar4设备对象
148
174
  class AR4(object):
149
175
  def __init__(self, box_mac:str, slot:int, hub_name:str):
176
+ # 设备句柄
150
177
  self._handle = None
178
+ # 设备基本信息
151
179
  self._box_type = None
152
180
  self._box_mac = box_mac
153
181
  self._box_id = None
@@ -166,13 +194,41 @@ class AR4(object):
166
194
  self._conn_time = None
167
195
  self._last_time = None
168
196
 
197
+ self._recording = False
198
+ self._record_start_time = None
199
+
200
+ ## eeg 数据
201
+ self._eeg_phy_max = None
202
+ self._eeg_phy_min = None
203
+ self._eeg_dig_max = None
204
+ self._eeg_dig_min = None
205
+ self._eeg_phy_unit = 'uV'
206
+ self._eeg_phy_range = None
207
+ self._eeg_dig_range = None
208
+ ## acc 数据
209
+ self._acc_phy_max = None
210
+ self._acc_phy_min = None
211
+ self._acc_dig_max = None
212
+ self._acc_dig_min = None
213
+ self._acc_phy_unit = 'mG'
214
+ self._acc_phy_range = None
215
+ self._acc_dig_range = None
216
+
169
217
  self._acq_info = {}
218
+ # 订阅者列表,数值为数字信号值
219
+ self._dig_subscriber: dict[str, Queue] = {}
220
+ # 订阅者列表,数值为物理信号值
221
+ self._phy_subscriber: dict[str, Queue] = {}
222
+
170
223
  # 回调函数
171
224
  self._data_callback = FuncAr4DataNotify(self._wrap_data_accept())
172
225
  self._trigger_callback = FuncAr4TriggerNotify(self._wrap_trigger_accept())
173
-
174
- # 消息订阅
175
- self._consumers: dict[str, Queue] = {}
226
+ self._connected_notify_callback = FuncAr4RecorderConnected(self._wrap_connected_notify())
227
+ self._disconnected_notify_callback = FuncAr4RecorderDisconnected(self._wrap_disconnected_notify())
228
+ self._box_connected_notify_callback = FuncAr4RecorderConnected(self._wrap_box_connected_notify())
229
+ self._box_disconnected_notify_callback = FuncAr4RecorderDisconnected(self._wrap_box_disconnected_notify())
230
+ self._start_record_notify_callback = FuncAr4RecorderConnected(self._wrap_start_record_notify())
231
+ self._stop_record_notify_callback = FuncAr4RecorderDisconnected(self._wrap_stop_record_notify())
176
232
 
177
233
  @property
178
234
  def box_mac(self):
@@ -187,6 +243,34 @@ class AR4(object):
187
243
  # logger.info(f"init ar4 {self.box_mac}")
188
244
  if not self._handle:
189
245
  self._connected = self.connect()
246
+ ## eeg 参数
247
+ self._eeg_phy_max = _dll.ar4_sdk_get_eeg_phy_max(self._handle)
248
+ self._eeg_phy_min = _dll.ar4_sdk_get_eeg_phy_min(self._handle)
249
+ self._eeg_dig_max = _dll.ar4_sdk_get_eeg_digtal_max(self._handle)
250
+ self._eeg_dig_min = _dll.ar4_sdk_get_eeg_digtal_min(self._handle)
251
+ self._eeg_phy_range = self._eeg_phy_max - self._eeg_phy_min
252
+ self._eeg_dig_range = self._eeg_dig_max - self._eeg_dig_min
253
+ eeg_unit = _dll.ar4_sdk_get_eeg_phy_unit(self._handle)
254
+ if eeg_unit:
255
+ try:
256
+ self._eeg_phy_unit = eeg_unit.decode("utf-8")
257
+ except Exception as e:
258
+ logger.error(f"ar4 {self._box_mac} 获取eeg物理单位异常: {str(e)}")
259
+
260
+ ## acc 参数
261
+ self._acc_phy_max = _dll.ar4_sdk_get_acc_phy_max(self._handle)
262
+ self._acc_phy_min = _dll.ar4_sdk_get_acc_phy_min(self._handle)
263
+ self._acc_dig_max = _dll.ar4_sdk_get_acc_digtal_max(self._handle)
264
+ self._acc_dig_min = _dll.ar4_sdk_get_acc_digtal_min(self._handle)
265
+ self._acc_phy_range = self._acc_phy_max - self._acc_phy_min
266
+ self._acc_dig_range = self._acc_dig_max - self._acc_dig_min
267
+ acc_unit = _dll.ar4_sdk_get_acc_phy_unit(self._handle)
268
+ if acc_unit:
269
+ try:
270
+ self._acc_phy_unit = eeg_unit.decode("utf-8")
271
+ except Exception as e:
272
+ logger.error(f"ar4 {self._box_mac} 获取acc物理单位异常: {str(e)}")
273
+
190
274
  if self._connected:
191
275
  self._conn_time = _get_time()
192
276
  self.get_box_name()
@@ -194,7 +278,18 @@ class AR4(object):
194
278
  self.get_record_conn_state()
195
279
  self._last_time = _get_time()
196
280
  logger.debug(self)
281
+ self._register_callback()
197
282
  return True
283
+
284
+ def _register_callback(self):
285
+ try:
286
+ _dll.ar4_sdk_register_data_notify(self._handle, self._data_callback)
287
+ _dll.ar4_sdk_register_trigger_notify(self._handle, self._trigger_callback)
288
+ _dll.ar4_sdk_register_conn_notify(self._handle, self._connected_notify_callback, self._disconnected_notify_callback)
289
+ _dll.ar4_sdk_register_record_state_notify(self._handle, self._start_record_notify_callback, self._stop_record_notify_callback)
290
+ _dll.ar4_sdk_register_box_conn_notify(self._handle, self._box_connected_notify_callback, self._box_disconnected_notify_callback)
291
+ except Exception as e:
292
+ logger.error(f"回调函数注册异常: {str(e)}")
198
293
 
199
294
  def update_info(self):
200
295
  self._last_time = _get_time()
@@ -244,17 +339,6 @@ class AR4(object):
244
339
  self._acq_info["start_time"] = _get_time()
245
340
 
246
341
  if self._handle:
247
- # 设置信号数据回调
248
- try:
249
- _dll.ar4_sdk_register_data_notify(self._handle, self._data_callback)
250
- except Exception as e:
251
- logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
252
-
253
- # 设置trigger数据回调
254
- # try:
255
- # _dll.ar4_sdk_register_trigger_notify(self._handle, self._trigger_callback)
256
- # except Exception as e:
257
- # logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
258
342
  # 启动采集
259
343
  try:
260
344
  logger.debug(f"ar4 {self._box_mac} 启动采集: {self._handle}")
@@ -305,15 +389,22 @@ class AR4(object):
305
389
  logger.error(f"ar4 {self._box_mac} 获取采样开始时间异常: {str(e)}")
306
390
 
307
391
  # 订阅推送消息
308
- def subscribe(self, topic: str = None, q: Queue = Queue()):
392
+ def subscribe(self, topic: str = None, q: Queue = Queue(), value_type: Literal['phy', 'dig'] = 'phy') -> tuple[str, Queue]:
309
393
  if topic is None:
310
394
  topic = f"msg_{_get_time()}"
311
- if topic in list(self._consumers.keys()):
312
- logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
313
- return topic, self._consumers[topic]
314
- self._consumers[topic] = q
315
395
 
316
- return topic, self._consumers[topic]
396
+ if value_type == 'dig':
397
+ if topic in list(self._dig_subscriber.keys()):
398
+ logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
399
+ return topic, self._dig_subscriber[topic]
400
+ self._dig_subscriber[topic] = q
401
+ else:
402
+ if topic in list(self._phy_subscriber.keys()):
403
+ logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
404
+ return topic, self._phy_subscriber[topic]
405
+ self._phy_subscriber[topic] = q
406
+
407
+ return topic, q
317
408
 
318
409
  def _wrap_data_accept(self):
319
410
 
@@ -322,17 +413,30 @@ class AR4(object):
322
413
  self._data_accept(data_ptr)
323
414
 
324
415
  return data_accept
325
-
326
416
  def _data_accept(self, data_ptr):
327
417
  # logger.info(f"_eeg_accept 被调用")
328
418
  # logger.debug(f'handle:{self}, data_ptr:{data_ptr}')
329
419
  # data = cast(data_ptr, POINTER(Ar4NotifyData)).contents
330
- if len(self._consumers) > 0:
420
+ if len(self._dig_subscriber) > 0 or len(self._phy_subscriber) > 0:
331
421
  packet = AR4Packet().transfer(data_ptr.contents)
332
- for consumer in self._consumers.values():
422
+
423
+ for consumer in self._dig_subscriber.values():
333
424
  consumer.put(packet)
425
+
426
+ if len(self._phy_subscriber) > 0:
427
+ packet.eeg = self.eeg2phy(np.array(packet.eeg))
428
+ packet.acc = self.acc2phy(np.array(packet.acc))
429
+ for consumer2 in self._phy_subscriber.values():
430
+ consumer2.put(packet)
431
+
334
432
  # logger.debug(f"EEG数据: {packet}")
335
433
 
434
+ def eeg2phy(self, digtal):
435
+ # 向量化计算(自动支持广播)
436
+ return ((digtal - self._eeg_dig_min) / self._eeg_dig_range) * self._eeg_phy_range + self._eeg_phy_min
437
+
438
+ def acc2phy(self, digtal):
439
+ return ((digtal - self._acc_dig_min) / self._acc_dig_range) * self._acc_phy_range + self._acc_phy_min
336
440
  def _wrap_trigger_accept(self):
337
441
 
338
442
  @FuncAr4DataNotify
@@ -344,7 +448,78 @@ class AR4(object):
344
448
  def _trigger_accept(self, time_ms, trigger_type, trigger_value):
345
449
  logger.info(f"_trigger_accept 被调用")
346
450
  logger.info(f"触发时间: {time_ms}, 触发类型: {trigger_type}, 触发值: {trigger_value}")
347
-
451
+
452
+
453
+ def _wrap_connected_notify(self):
454
+
455
+ @FuncAr4RecorderConnected
456
+ def connected_notify(handle):
457
+ self._connected_notify(handle)
458
+
459
+ return connected_notify
460
+
461
+ def _connected_notify(self, handle):
462
+ logger.info(f"_connected_notify 被调用 handle: {handle}")
463
+
464
+
465
+ def _wrap_disconnected_notify(self):
466
+ @FuncAr4RecorderDisconnected
467
+ def disconnected_notify(handle):
468
+ self._disconnected_notify(handle)
469
+
470
+ return disconnected_notify
471
+
472
+ def _disconnected_notify(self, handle):
473
+ logger.info(f"_disconnected_notify 被调用 handle: {handle}")
474
+
475
+
476
+ def _wrap_box_connected_notify(self):
477
+
478
+ @FuncAr4RecorderConnected
479
+ def box_connected_notify(handle):
480
+ self._box_connected_notify(handle)
481
+
482
+ return box_connected_notify
483
+
484
+ def _box_connected_notify(self, handle):
485
+ logger.info(f"_box_connected_notify 被调用 handle: {handle}")
486
+
487
+ def _wrap_box_disconnected_notify(self):
488
+ @FuncAr4RecorderDisconnected
489
+ def box_disconnected_notify(handle):
490
+ self._box_disconnected_notify(handle)
491
+
492
+ return box_disconnected_notify
493
+
494
+ def _box_disconnected_notify(self, handle):
495
+ logger.info(f"_box_disconnected_notify 被调用 handle: {handle}")
496
+
497
+ def _wrap_start_record_notify(self):
498
+
499
+ @FuncAr4RecorderConnected
500
+ def start_record_notify(handle):
501
+ self._start_record_notify(handle)
502
+
503
+ return start_record_notify
504
+
505
+ def _start_record_notify(self, handle):
506
+ logger.info(f"_start_record_notify 被调用 handle: {handle}")
507
+ self._recording = True
508
+ self._record_start_time = time()
509
+ logger.info(self)
510
+
511
+
512
+ def _wrap_stop_record_notify(self):
513
+ @FuncAr4RecorderDisconnected
514
+ def stop_record_notify(handle):
515
+ self._stop_record_notify(handle)
516
+
517
+ return stop_record_notify
518
+
519
+ def _stop_record_notify(self, handle):
520
+ logger.info(f"_stop_record_notify 被调用 handle: {handle}")
521
+ self._recording = False
522
+
348
523
  def __str__(self):
349
524
  return f"""
350
525
  box mac: {self._box_mac},
@@ -356,6 +531,20 @@ class AR4(object):
356
531
  connected: {self._connected}
357
532
  connect time: {self._conn_time}
358
533
  last time: {self._last_time}
534
+ [
535
+ eeg phy max: {self._eeg_phy_max}
536
+ eeg phy min: {self._eeg_phy_min}
537
+ eeg dig max: {self._eeg_dig_max}
538
+ eeg dig min: {self._eeg_dig_min}
539
+ eeg phy unit: {self._eeg_phy_unit}
540
+ ]
541
+ [
542
+ acc phy max: {self._acc_phy_max}
543
+ acc phy min: {self._acc_phy_min}
544
+ acc dig max: {self._acc_dig_max}
545
+ acc dig min: {self._acc_dig_min}
546
+ acc phy unit: {self._acc_phy_unit}
547
+ ]
359
548
  """
360
549
 
361
550
  class Packet(object):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: qlsdk2
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: loguru>=0.6.0
14
+ Requires-Dist: numpy>=1.23.5
14
15
  Provides-Extra: dev
15
16
  Requires-Dist: pytest>=6.0; extra == "dev"
16
17
  Requires-Dist: twine>=3.0; extra == "dev"
@@ -1,9 +1,9 @@
1
1
  qlsdk/__init__.py,sha256=pHMMwFiz7AGYlWvxo-81Obd7gIkojhLAEUGe0yuiAJI,372
2
2
  qlsdk/ar4m/__init__.py,sha256=HCFv58t9vYoH2nNN9EiswwdRngiDmh_7h-uwhW-id1I,1784
3
- qlsdk/ar4m/ar4sdk.py,sha256=M244in6ytN8f-PI7lIbt8BfEE_DcjAsE06iLxI7xNeY,15358
3
+ qlsdk/ar4m/ar4sdk.py,sha256=DlPCVF5eFA500wXGk-gRqcQXGnoCdUhHe1yoLsOGu7c,23847
4
4
  qlsdk/ar4m/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,15402132
5
5
  qlsdk/ar4m/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
6
- qlsdk2-0.1.7.dist-info/METADATA,sha256=ynZ7zTy2_H7Hney0pXE-GgVERk9dgvaLoCierdAim8U,7001
7
- qlsdk2-0.1.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
- qlsdk2-0.1.7.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
9
- qlsdk2-0.1.7.dist-info/RECORD,,
6
+ qlsdk2-0.2.0.dist-info/METADATA,sha256=V6UWoXTs44Ulwtb_HvDLyv8A6o6ACgqDTr_dy2XFU6A,7031
7
+ qlsdk2-0.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
+ qlsdk2-0.2.0.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
9
+ qlsdk2-0.2.0.dist-info/RECORD,,
File without changes