qlsdk2 0.2.0__py3-none-any.whl → 0.3.0a1__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/__init__.py +5 -2
- qlsdk/ar4/__init__.py +128 -0
- qlsdk/ar4m/__init__.py +16 -45
- qlsdk/ar4m/ar4sdk.py +421 -10
- qlsdk/ar4m/persist.py +178 -0
- qlsdk/persist/__init__.py +1 -0
- qlsdk/persist/edf.py +186 -0
- qlsdk/sdk/__init__.py +2 -0
- qlsdk/sdk/ar4sdk.py +742 -0
- qlsdk/sdk/hub.py +51 -0
- qlsdk/x8/__init__.py +128 -0
- qlsdk/x8m/__init__.py +21 -0
- {qlsdk2-0.2.0.dist-info → qlsdk2-0.3.0a1.dist-info}/METADATA +2 -1
- qlsdk2-0.3.0a1.dist-info/RECORD +18 -0
- qlsdk2-0.2.0.dist-info/RECORD +0 -9
- {qlsdk2-0.2.0.dist-info → qlsdk2-0.3.0a1.dist-info}/WHEEL +0 -0
- {qlsdk2-0.2.0.dist-info → qlsdk2-0.3.0a1.dist-info}/top_level.txt +0 -0
qlsdk/__init__.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# __version__ = "0.1.1"
|
|
2
2
|
|
|
3
3
|
# 暴露公共接口
|
|
4
|
-
from .ar4m import AR4M
|
|
4
|
+
from .ar4m import AR4M
|
|
5
|
+
from .ar4 import AR4
|
|
6
|
+
from .x8 import X8
|
|
7
|
+
from .x8m import X8M
|
|
5
8
|
|
|
6
|
-
__all__ = ['AR4M', 'AR4', 'AR4Packet']
|
|
9
|
+
__all__ = ['AR4M', 'AR4', 'AR4Packet', 'X8']
|
|
7
10
|
|
|
8
11
|
# from importlib import import_module
|
|
9
12
|
# from pathlib import Path
|
qlsdk/ar4/__init__.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from multiprocessing import Queue
|
|
2
|
+
from typing import Literal
|
|
3
|
+
from loguru import logger
|
|
4
|
+
from qlsdk.sdk import LMDevice, LMPacket
|
|
5
|
+
import numpy as np
|
|
6
|
+
from qlsdk.persist import EdfHandler
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
# 设备对象
|
|
10
|
+
class AR4(LMDevice):
|
|
11
|
+
def __init__(self, box_mac:str, is_persist:bool=True, storage_path:str=None):
|
|
12
|
+
# 是否持久化-保存为文件
|
|
13
|
+
self._is_persist = is_persist
|
|
14
|
+
self._storage_path = storage_path
|
|
15
|
+
self._edf_handler = None
|
|
16
|
+
|
|
17
|
+
self._recording = False
|
|
18
|
+
self._record_start_time = None
|
|
19
|
+
|
|
20
|
+
self._acq_info = {}
|
|
21
|
+
# 订阅者列表,数值为数字信号值
|
|
22
|
+
self._dig_subscriber: dict[str, Queue] = {}
|
|
23
|
+
# 订阅者列表,数值为物理信号值
|
|
24
|
+
self._phy_subscriber: dict[str, Queue] = {}
|
|
25
|
+
|
|
26
|
+
super().__init__(box_mac)
|
|
27
|
+
|
|
28
|
+
def set_storage_path(self, storage_path):
|
|
29
|
+
self._storage_path = storage_path
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def device_type(self):
|
|
33
|
+
return "AR4"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def eeg_accept(self, packet):
|
|
37
|
+
if len(self._dig_subscriber) > 0 or len(self._phy_subscriber) > 0:
|
|
38
|
+
for consumer in self._dig_subscriber.values():
|
|
39
|
+
consumer.put(packet)
|
|
40
|
+
|
|
41
|
+
if len(self._phy_subscriber) > 0:
|
|
42
|
+
logger.info(f"dig data eeg: {packet.eeg}")
|
|
43
|
+
logger.info(f"dig data acc: {packet.acc}")
|
|
44
|
+
packet.eeg = self.eeg2phy(np.array(packet.eeg))
|
|
45
|
+
packet.acc = self.acc2phy(np.array(packet.acc))
|
|
46
|
+
logger.info(f"phy data eeg: {packet.eeg}")
|
|
47
|
+
logger.info(f"phy data acc: {packet.acc}")
|
|
48
|
+
for consumer2 in self._phy_subscriber.values():
|
|
49
|
+
consumer2.put(packet)
|
|
50
|
+
|
|
51
|
+
if self._is_persist:
|
|
52
|
+
if self._edf_handler is None:
|
|
53
|
+
self.start_record()
|
|
54
|
+
self._recording = True
|
|
55
|
+
self._record_start_time = packet.time_stamp
|
|
56
|
+
logger.info(f"开始记录数据: {self.box_mac}")
|
|
57
|
+
|
|
58
|
+
# 处理数据包
|
|
59
|
+
self._edf_handler.append(packet)
|
|
60
|
+
|
|
61
|
+
def start_record(self):
|
|
62
|
+
if self._is_persist:
|
|
63
|
+
if self._edf_handler is None:
|
|
64
|
+
self._edf_handler = EdfHandler(self.sample_frequency, self.eeg_phy_max, self.eeg_phy_min, self.eeg_dig_max, self.eeg_dig_min, storage_path=self._storage_path)
|
|
65
|
+
self._edf_handler.set_device_type(self.device_type)
|
|
66
|
+
self._edf_handler.set_storage_path(self._storage_path)
|
|
67
|
+
|
|
68
|
+
def stop_record(self):
|
|
69
|
+
if self._edf_handler:
|
|
70
|
+
# 等待设备端数据传输完成
|
|
71
|
+
time.sleep(0.5)
|
|
72
|
+
# 添加结束标识
|
|
73
|
+
self._edf_handler.append(None)
|
|
74
|
+
self._edf_handler = None
|
|
75
|
+
self._recording = False
|
|
76
|
+
logger.info(f"停止记录数据: {self.box_mac}")
|
|
77
|
+
|
|
78
|
+
# 订阅推送消息
|
|
79
|
+
def subscribe(self, topic: str = None, q: Queue = Queue(), value_type: Literal['phy', 'dig'] = 'phy') -> tuple[str, Queue]:
|
|
80
|
+
if topic is None:
|
|
81
|
+
topic = f"msg_{self.box_mac}"
|
|
82
|
+
|
|
83
|
+
if value_type == 'dig':
|
|
84
|
+
if topic in list(self._dig_subscriber.keys()):
|
|
85
|
+
logger.warning(f"ar4 {self.box_mac} 订阅主题已存在: {topic}")
|
|
86
|
+
return topic, self._dig_subscriber[topic]
|
|
87
|
+
self._dig_subscriber[topic] = q
|
|
88
|
+
else:
|
|
89
|
+
if topic in list(self._phy_subscriber.keys()):
|
|
90
|
+
logger.warning(f"ar4 {self.box_mac} 订阅主题已存在: {topic}")
|
|
91
|
+
return topic, self._phy_subscriber[topic]
|
|
92
|
+
self._phy_subscriber[topic] = q
|
|
93
|
+
|
|
94
|
+
return topic, q
|
|
95
|
+
|
|
96
|
+
def eeg2phy(self, digtal):
|
|
97
|
+
# 向量化计算(自动支持广播)
|
|
98
|
+
return super().eeg2phy(digtal)
|
|
99
|
+
|
|
100
|
+
def acc2phy(self, digtal):
|
|
101
|
+
return super().eeg2phy(digtal)
|
|
102
|
+
|
|
103
|
+
class X8Packet(LMPacket):
|
|
104
|
+
def __init__(self, data: bytes):
|
|
105
|
+
super().__init__(data)
|
|
106
|
+
self._data = data
|
|
107
|
+
self._head = None
|
|
108
|
+
self._body = None
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def head(self):
|
|
112
|
+
return self._head
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def body(self):
|
|
116
|
+
return self._body
|
|
117
|
+
|
|
118
|
+
def parse(self):
|
|
119
|
+
# 解析数据包头部和数据体
|
|
120
|
+
if len(self._data) < 4:
|
|
121
|
+
logger.error(f"数据包长度不足: {len(self._data)}")
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
# 解析头部和数据体
|
|
125
|
+
self._head = self._data[:4]
|
|
126
|
+
self._body = self._data[4:]
|
|
127
|
+
|
|
128
|
+
return True
|
qlsdk/ar4m/__init__.py
CHANGED
|
@@ -1,50 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from .ar4sdk import AR4SDK, AR4, Packet, AR4Packet
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from time import sleep, time
|
|
7
|
-
from threading import Lock, Timer
|
|
1
|
+
from qlsdk.sdk import Hub
|
|
2
|
+
from qlsdk.ar4 import AR4
|
|
8
3
|
from loguru import logger
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
|
|
6
|
+
class AR4M(Hub):
|
|
11
7
|
def __init__(self):
|
|
12
|
-
|
|
13
|
-
self._search_timer = None
|
|
14
|
-
self._search_running = False
|
|
8
|
+
super().__init__()
|
|
15
9
|
self._devices: dict[str, AR4] = {}
|
|
10
|
+
self._search_running = False
|
|
11
|
+
self._search_timer = None
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if self._search_running:
|
|
27
|
-
|
|
28
|
-
self._search_timer = Timer(2, self._search_ar4)
|
|
29
|
-
self._search_timer.daemon = True
|
|
30
|
-
self._search_timer.start()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _search_ar4(self):
|
|
34
|
-
try:
|
|
35
|
-
devices = AR4SDK.enum_devices()
|
|
36
|
-
# logger.debug(f"_search_ar4 devices size: {len(devices)}")
|
|
37
|
-
for dev in devices:
|
|
38
|
-
# logger.debug(f"slot: {dev.slot}, mac: {dev.mac}-{hex(dev.mac)}, hub_name: {dev.hub_name.str}")
|
|
39
|
-
if dev.mac in list(self._devices.keys()):
|
|
40
|
-
ar4 = self._devices[dev.mac]
|
|
41
|
-
ar4.update_info()
|
|
42
|
-
else:
|
|
43
|
-
ar4 = AR4(hex(dev.mac), dev.slot, dev.hub_name.str.decode("utf-8"))
|
|
44
|
-
if ar4.init():
|
|
45
|
-
self._devices[dev.mac] = ar4
|
|
46
|
-
logger.info(f"add device mac: {ar4.box_mac} slot: {ar4.slot} hub_name: {ar4.hub_name}")
|
|
47
|
-
except Exception as e:
|
|
48
|
-
logger.error(f"_search_ar4 异常: {str(e)}")
|
|
49
|
-
finally:
|
|
50
|
-
self._search()
|
|
13
|
+
def add_device(self, mac: str):
|
|
14
|
+
if mac in list(self._devices.keys()):
|
|
15
|
+
self._devices[mac].update_info()
|
|
16
|
+
logger.debug(f"update x8 device mac: {mac}")
|
|
17
|
+
else:
|
|
18
|
+
dev = AR4(mac)
|
|
19
|
+
if dev.connected:
|
|
20
|
+
self._devices[mac] = dev
|
|
21
|
+
logger.info(f"add x8 device mac: {dev}")
|
qlsdk/ar4m/ar4sdk.py
CHANGED
|
@@ -11,6 +11,7 @@ from loguru import logger
|
|
|
11
11
|
from time import sleep, time
|
|
12
12
|
import os
|
|
13
13
|
import numpy as np
|
|
14
|
+
from .persist import EdfHandler
|
|
14
15
|
|
|
15
16
|
real_path = os.path.realpath(__file__)
|
|
16
17
|
dll_path = f'{os.path.dirname(real_path)}/libs/libAr4SDK.dll'
|
|
@@ -162,16 +163,13 @@ class AR4SDK:
|
|
|
162
163
|
|
|
163
164
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
164
165
|
pass
|
|
165
|
-
|
|
166
166
|
|
|
167
167
|
# 读取系统当前时间(ms)
|
|
168
168
|
def _get_time():
|
|
169
169
|
cur_time = int(round(time()) * 1000)
|
|
170
|
-
logger.debug(f"_get_time is {cur_time}")
|
|
171
170
|
return cur_time
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
class AR4(object):
|
|
171
|
+
|
|
172
|
+
class LMDevice(object):
|
|
175
173
|
def __init__(self, box_mac:str, slot:int, hub_name:str):
|
|
176
174
|
# 设备句柄
|
|
177
175
|
self._handle = None
|
|
@@ -424,8 +422,12 @@ class AR4(object):
|
|
|
424
422
|
consumer.put(packet)
|
|
425
423
|
|
|
426
424
|
if len(self._phy_subscriber) > 0:
|
|
425
|
+
logger.info(f"dig data eeg: {packet.eeg}")
|
|
426
|
+
logger.info(f"dig data acc: {packet.acc}")
|
|
427
427
|
packet.eeg = self.eeg2phy(np.array(packet.eeg))
|
|
428
428
|
packet.acc = self.acc2phy(np.array(packet.acc))
|
|
429
|
+
logger.info(f"phy data eeg: {packet.eeg}")
|
|
430
|
+
logger.info(f"phy data acc: {packet.acc}")
|
|
429
431
|
for consumer2 in self._phy_subscriber.values():
|
|
430
432
|
consumer2.put(packet)
|
|
431
433
|
|
|
@@ -545,6 +547,420 @@ class AR4(object):
|
|
|
545
547
|
acc dig min: {self._acc_dig_min}
|
|
546
548
|
acc phy unit: {self._acc_phy_unit}
|
|
547
549
|
]
|
|
550
|
+
dig -> ((dig - {self._eeg_dig_min}) / {self._eeg_dig_range}) * {self._eeg_phy_range} + {self._eeg_phy_min}
|
|
551
|
+
dig -> ((dig - {self._acc_dig_min}) / {self._acc_dig_range}) * {self._acc_phy_range} + {self._acc_phy_min}
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
# ar4设备对象
|
|
557
|
+
class AR4(object):
|
|
558
|
+
def __init__(self, box_mac:str, slot:int=None, hub_name:str=None, is_persist:bool=True, storage_path:str=None):
|
|
559
|
+
# 是否持久化-保存为文件
|
|
560
|
+
self._is_persist = is_persist
|
|
561
|
+
self._storage_path = storage_path
|
|
562
|
+
self._edf_handler = None
|
|
563
|
+
# 设备句柄
|
|
564
|
+
self._handle = None
|
|
565
|
+
|
|
566
|
+
self.box_connected = False
|
|
567
|
+
self.sample_frequency = 500
|
|
568
|
+
# 设备基本信息
|
|
569
|
+
self._box_type = None
|
|
570
|
+
self._box_mac = box_mac
|
|
571
|
+
self._box_id = None
|
|
572
|
+
self._box_soc = None
|
|
573
|
+
self._box_name = None
|
|
574
|
+
self._box_version = None
|
|
575
|
+
self._head_type = None
|
|
576
|
+
self._head_mac = None
|
|
577
|
+
self._head_version = None
|
|
578
|
+
self._head_conn_state = None
|
|
579
|
+
self._head_soc = None
|
|
580
|
+
self._net_state = None
|
|
581
|
+
self._hub_name = hub_name
|
|
582
|
+
self._slot = slot
|
|
583
|
+
self._connected = False
|
|
584
|
+
self._conn_time = None
|
|
585
|
+
self._last_time = None
|
|
586
|
+
|
|
587
|
+
self._recording = False
|
|
588
|
+
self._record_start_time = None
|
|
589
|
+
|
|
590
|
+
## eeg 数据
|
|
591
|
+
self._eeg_phy_max = None
|
|
592
|
+
self._eeg_phy_min = None
|
|
593
|
+
self._eeg_dig_max = None
|
|
594
|
+
self._eeg_dig_min = None
|
|
595
|
+
self._eeg_phy_unit = 'uV'
|
|
596
|
+
self._eeg_phy_range = None
|
|
597
|
+
self._eeg_dig_range = None
|
|
598
|
+
## acc 数据
|
|
599
|
+
self._acc_phy_max = None
|
|
600
|
+
self._acc_phy_min = None
|
|
601
|
+
self._acc_dig_max = None
|
|
602
|
+
self._acc_dig_min = None
|
|
603
|
+
self._acc_phy_unit = 'mG'
|
|
604
|
+
self._acc_phy_range = None
|
|
605
|
+
self._acc_dig_range = None
|
|
606
|
+
|
|
607
|
+
self._acq_info = {}
|
|
608
|
+
# 订阅者列表,数值为数字信号值
|
|
609
|
+
self._dig_subscriber: dict[str, Queue] = {}
|
|
610
|
+
# 订阅者列表,数值为物理信号值
|
|
611
|
+
self._phy_subscriber: dict[str, Queue] = {}
|
|
612
|
+
|
|
613
|
+
# 回调函数
|
|
614
|
+
self._data_callback = FuncAr4DataNotify(self._wrap_data_accept())
|
|
615
|
+
self._trigger_callback = FuncAr4TriggerNotify(self._wrap_trigger_accept())
|
|
616
|
+
self._connected_notify_callback = FuncAr4RecorderConnected(self._wrap_connected_notify())
|
|
617
|
+
self._disconnected_notify_callback = FuncAr4RecorderDisconnected(self._wrap_disconnected_notify())
|
|
618
|
+
self._box_connected_notify_callback = FuncAr4RecorderConnected(self._wrap_box_connected_notify())
|
|
619
|
+
self._box_disconnected_notify_callback = FuncAr4RecorderDisconnected(self._wrap_box_disconnected_notify())
|
|
620
|
+
self._start_record_notify_callback = FuncAr4RecorderConnected(self._wrap_start_record_notify())
|
|
621
|
+
self._stop_record_notify_callback = FuncAr4RecorderDisconnected(self._wrap_stop_record_notify())
|
|
622
|
+
|
|
623
|
+
@property
|
|
624
|
+
def box_mac(self):
|
|
625
|
+
return self._box_mac
|
|
626
|
+
@property
|
|
627
|
+
def slot(self):
|
|
628
|
+
return self._slot
|
|
629
|
+
@property
|
|
630
|
+
def hub_name(self):
|
|
631
|
+
return self._hub_name
|
|
632
|
+
|
|
633
|
+
def set_storage_path(self, storage_path):
|
|
634
|
+
self._storage_path = storage_path
|
|
635
|
+
|
|
636
|
+
def init(self):
|
|
637
|
+
# logger.info(f"init ar4 {self.box_mac}")
|
|
638
|
+
if not self._handle:
|
|
639
|
+
self._connected = self.connect()
|
|
640
|
+
## eeg 参数
|
|
641
|
+
self._eeg_phy_max = _dll.ar4_sdk_get_eeg_phy_max(self._handle)
|
|
642
|
+
self._eeg_phy_min = _dll.ar4_sdk_get_eeg_phy_min(self._handle)
|
|
643
|
+
self._eeg_dig_max = _dll.ar4_sdk_get_eeg_digtal_max(self._handle)
|
|
644
|
+
self._eeg_dig_min = _dll.ar4_sdk_get_eeg_digtal_min(self._handle)
|
|
645
|
+
self._eeg_phy_range = self._eeg_phy_max - self._eeg_phy_min
|
|
646
|
+
self._eeg_dig_range = self._eeg_dig_max - self._eeg_dig_min
|
|
647
|
+
eeg_unit = _dll.ar4_sdk_get_eeg_phy_unit(self._handle)
|
|
648
|
+
if eeg_unit:
|
|
649
|
+
try:
|
|
650
|
+
self._eeg_phy_unit = eeg_unit.decode("utf-8")
|
|
651
|
+
except Exception as e:
|
|
652
|
+
logger.error(f"ar4 {self._box_mac} 获取eeg物理单位异常: {str(e)}")
|
|
653
|
+
|
|
654
|
+
## acc 参数
|
|
655
|
+
self._acc_phy_max = _dll.ar4_sdk_get_acc_phy_max(self._handle)
|
|
656
|
+
self._acc_phy_min = _dll.ar4_sdk_get_acc_phy_min(self._handle)
|
|
657
|
+
self._acc_dig_max = _dll.ar4_sdk_get_acc_digtal_max(self._handle)
|
|
658
|
+
self._acc_dig_min = _dll.ar4_sdk_get_acc_digtal_min(self._handle)
|
|
659
|
+
self._acc_phy_range = self._acc_phy_max - self._acc_phy_min
|
|
660
|
+
self._acc_dig_range = self._acc_dig_max - self._acc_dig_min
|
|
661
|
+
acc_unit = _dll.ar4_sdk_get_acc_phy_unit(self._handle)
|
|
662
|
+
if acc_unit:
|
|
663
|
+
try:
|
|
664
|
+
self._acc_phy_unit = eeg_unit.decode("utf-8")
|
|
665
|
+
except Exception as e:
|
|
666
|
+
logger.error(f"ar4 {self._box_mac} 获取acc物理单位异常: {str(e)}")
|
|
667
|
+
|
|
668
|
+
if self._connected:
|
|
669
|
+
self._conn_time = _get_time()
|
|
670
|
+
self.get_box_name()
|
|
671
|
+
self.get_head_mac()
|
|
672
|
+
self.get_record_conn_state()
|
|
673
|
+
self._last_time = _get_time()
|
|
674
|
+
logger.debug(self)
|
|
675
|
+
self._register_callback()
|
|
676
|
+
return True
|
|
677
|
+
|
|
678
|
+
def _register_callback(self):
|
|
679
|
+
try:
|
|
680
|
+
_dll.ar4_sdk_register_data_notify(self._handle, self._data_callback)
|
|
681
|
+
_dll.ar4_sdk_register_trigger_notify(self._handle, self._trigger_callback)
|
|
682
|
+
_dll.ar4_sdk_register_conn_notify(self._handle, self._connected_notify_callback, self._disconnected_notify_callback)
|
|
683
|
+
_dll.ar4_sdk_register_record_state_notify(self._handle, self._start_record_notify_callback, self._stop_record_notify_callback)
|
|
684
|
+
_dll.ar4_sdk_register_box_conn_notify(self._handle, self._box_connected_notify_callback, self._box_disconnected_notify_callback)
|
|
685
|
+
except Exception as e:
|
|
686
|
+
logger.error(f"回调函数注册异常: {str(e)}")
|
|
687
|
+
|
|
688
|
+
@property
|
|
689
|
+
def device_type(self):
|
|
690
|
+
return "ar4" if self._box_type == 4 else "x8" if self._box_type == 8 else "lm"
|
|
691
|
+
|
|
692
|
+
def update_info(self):
|
|
693
|
+
logger.debug(f"update info {self._box_mac}")
|
|
694
|
+
self._last_time = _get_time()
|
|
695
|
+
|
|
696
|
+
# 设备连接
|
|
697
|
+
def connect(self)-> bool:
|
|
698
|
+
|
|
699
|
+
if self._box_mac:
|
|
700
|
+
"""连接设备"""
|
|
701
|
+
# callback = FuncAr4GetTime(self._get_time)
|
|
702
|
+
try:
|
|
703
|
+
self._handle = _dll.ar4_sdk_connect(int(self._box_mac, 16), c_void_p(0))
|
|
704
|
+
# logger.info(f"conn handle is {self._handle}")
|
|
705
|
+
self._handle = c_void_p(self._handle)
|
|
706
|
+
logger.debug(f"ar4 {self._box_mac} 连接: {self._handle}")
|
|
707
|
+
except Exception as e:
|
|
708
|
+
logger.error(f"ar4 {self._box_mac} 连接异常: {str(e)}")
|
|
709
|
+
return self._handle is not None
|
|
710
|
+
|
|
711
|
+
# 读取盒子名称
|
|
712
|
+
def get_box_name(self):
|
|
713
|
+
if self._handle:
|
|
714
|
+
try:
|
|
715
|
+
self._head_mac = _dll.ar4_sdk_get_recoder_mac(self._handle)
|
|
716
|
+
except Exception as e:
|
|
717
|
+
logger.error(f"ar4 {self._box_mac} 获取盒子记录子mac异常: {str(e)}")
|
|
718
|
+
# 读取记录子mac
|
|
719
|
+
def get_head_mac(self):
|
|
720
|
+
if self._handle:
|
|
721
|
+
try:
|
|
722
|
+
self._box_name = _dll.ar4_sdk_get_record_conn_state(self._handle)
|
|
723
|
+
except Exception as e:
|
|
724
|
+
logger.error(f"ar4 {self._box_mac} 获取盒子名称异常: {str(e)}")
|
|
725
|
+
# 读取记录子连接状态
|
|
726
|
+
def get_record_conn_state(self):
|
|
727
|
+
if self._handle:
|
|
728
|
+
try:
|
|
729
|
+
self._head_conn_state = _dll.ar4_sdk_get_record_conn_state(self._handle)
|
|
730
|
+
except Exception as e:
|
|
731
|
+
logger.error(f"ar4 {self._box_mac} 获取盒子记录子连接状态异常: {str(e)}")
|
|
732
|
+
|
|
733
|
+
# 数据采集启动
|
|
734
|
+
def start_acquisition(self):
|
|
735
|
+
logger.info(f"ar4 {self._box_mac} 启动数据采集...")
|
|
736
|
+
"""启动数据采集"""
|
|
737
|
+
|
|
738
|
+
self._acq_info["start_time"] = _get_time()
|
|
739
|
+
|
|
740
|
+
if self._handle:
|
|
741
|
+
# 启动采集
|
|
742
|
+
try:
|
|
743
|
+
logger.debug(f"ar4 {self._box_mac} 启动采集: {self._handle}")
|
|
744
|
+
ret = _dll.ar4_sdk_start_acq(self._handle)
|
|
745
|
+
logger.debug(f"ar4 {self._box_mac} 启动采集结果: {ret}")
|
|
746
|
+
if (self._is_persist):
|
|
747
|
+
self._edf_handler = EdfHandler(self.sample_frequency, self._eeg_phy_max, self._eeg_phy_min, self._eeg_dig_max, self._eeg_dig_min, storage_path = self._storage_path)
|
|
748
|
+
self._edf_handler.set_device_type(self.device_type)
|
|
749
|
+
|
|
750
|
+
return ret == 0
|
|
751
|
+
except Exception as e:
|
|
752
|
+
logger.error(f"ar4 {self._box_mac} 启动数据采集异常: {str(e)}")
|
|
753
|
+
else:
|
|
754
|
+
logger.info(f"ar4 {self._box_mac} 启动数据采集失败, 设备未连接")
|
|
755
|
+
return False
|
|
756
|
+
|
|
757
|
+
def stop_acquisition(self):
|
|
758
|
+
"""停止采集"""
|
|
759
|
+
if self._handle:
|
|
760
|
+
try:
|
|
761
|
+
self.get_acq_start_time()
|
|
762
|
+
except Exception as e:
|
|
763
|
+
logger.error(f"ar4 {self._box_mac} 获取开始采集时间异常: {str(e)}")
|
|
764
|
+
try:
|
|
765
|
+
ret = _dll.ar4_sdk_stop_acq(self._handle)
|
|
766
|
+
if ret == 0 and self._edf_handler:
|
|
767
|
+
self._edf_handler.append(None)
|
|
768
|
+
|
|
769
|
+
return ret == 0
|
|
770
|
+
except Exception as e:
|
|
771
|
+
logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
|
|
772
|
+
else:
|
|
773
|
+
return False
|
|
774
|
+
|
|
775
|
+
def disconnect(self):
|
|
776
|
+
"""断开连接"""
|
|
777
|
+
if self._handle:
|
|
778
|
+
_dll.ar4_sdk_disconnect(self._handle)
|
|
779
|
+
|
|
780
|
+
def get_sample_rate(self):
|
|
781
|
+
try:
|
|
782
|
+
ret = _dll.ar4_sdk_get_record_sample_rate(self._handle)
|
|
783
|
+
logger.debug(f"ar4 {self._box_mac} 获取采样率: {ret.contents.decode('utf-8')}")
|
|
784
|
+
except Exception as e:
|
|
785
|
+
logger.error(f"ar4 {self._box_mac} 获取采样率异常: {str(e)}")
|
|
786
|
+
|
|
787
|
+
def get_acq_start_time(self):
|
|
788
|
+
try:
|
|
789
|
+
ret = _dll.ar4_sdk_get_acq_start_time(self._handle)
|
|
790
|
+
# 更新采样开始时间
|
|
791
|
+
if ret:
|
|
792
|
+
self._acq_info["start_time"] = ret
|
|
793
|
+
logger.debug(f"ar4 {self._box_mac} 获取采样开始时间: {ret}")
|
|
794
|
+
except Exception as e:
|
|
795
|
+
logger.error(f"ar4 {self._box_mac} 获取采样开始时间异常: {str(e)}")
|
|
796
|
+
|
|
797
|
+
# 订阅推送消息
|
|
798
|
+
def subscribe(self, topic: str = None, q: Queue = Queue(), value_type: Literal['phy', 'dig'] = 'phy') -> tuple[str, Queue]:
|
|
799
|
+
if topic is None:
|
|
800
|
+
topic = f"msg_{_get_time()}"
|
|
801
|
+
|
|
802
|
+
if value_type == 'dig':
|
|
803
|
+
if topic in list(self._dig_subscriber.keys()):
|
|
804
|
+
logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
|
|
805
|
+
return topic, self._dig_subscriber[topic]
|
|
806
|
+
self._dig_subscriber[topic] = q
|
|
807
|
+
else:
|
|
808
|
+
if topic in list(self._phy_subscriber.keys()):
|
|
809
|
+
logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
|
|
810
|
+
return topic, self._phy_subscriber[topic]
|
|
811
|
+
self._phy_subscriber[topic] = q
|
|
812
|
+
|
|
813
|
+
return topic, q
|
|
814
|
+
|
|
815
|
+
def _wrap_data_accept(self):
|
|
816
|
+
|
|
817
|
+
@FuncAr4DataNotify
|
|
818
|
+
def data_accept(handle, data_ptr):
|
|
819
|
+
self._data_accept(data_ptr)
|
|
820
|
+
|
|
821
|
+
return data_accept
|
|
822
|
+
def _data_accept(self, data_ptr):
|
|
823
|
+
# logger.info(f"_eeg_accept 被调用")
|
|
824
|
+
# logger.debug(f'handle:{self}, data_ptr:{data_ptr}')
|
|
825
|
+
# data = cast(data_ptr, POINTER(Ar4NotifyData)).contents
|
|
826
|
+
# if len(self._dig_subscriber) > 0 or len(self._phy_subscriber) > 0:
|
|
827
|
+
packet = AR4Packet().transfer(data_ptr.contents)
|
|
828
|
+
if self._edf_handler:
|
|
829
|
+
self._edf_handler.append(packet)
|
|
830
|
+
|
|
831
|
+
for consumer in self._dig_subscriber.values():
|
|
832
|
+
consumer.put(packet)
|
|
833
|
+
|
|
834
|
+
if len(self._phy_subscriber) > 0:
|
|
835
|
+
logger.info(f"dig data eeg: {packet.eeg}")
|
|
836
|
+
logger.info(f"dig data acc: {packet.acc}")
|
|
837
|
+
packet.eeg = self.eeg2phy(np.array(packet.eeg))
|
|
838
|
+
packet.acc = self.acc2phy(np.array(packet.acc))
|
|
839
|
+
logger.info(f"phy data eeg: {packet.eeg}")
|
|
840
|
+
logger.info(f"phy data acc: {packet.acc}")
|
|
841
|
+
for consumer2 in self._phy_subscriber.values():
|
|
842
|
+
consumer2.put(packet)
|
|
843
|
+
|
|
844
|
+
# logger.debug(f"EEG数据: {packet}")
|
|
845
|
+
|
|
846
|
+
def eeg2phy(self, digtal):
|
|
847
|
+
# 向量化计算(自动支持广播)
|
|
848
|
+
return ((digtal - self._eeg_dig_min) / self._eeg_dig_range) * self._eeg_phy_range + self._eeg_phy_min
|
|
849
|
+
|
|
850
|
+
def acc2phy(self, digtal):
|
|
851
|
+
return ((digtal - self._acc_dig_min) / self._acc_dig_range) * self._acc_phy_range + self._acc_phy_min
|
|
852
|
+
def _wrap_trigger_accept(self):
|
|
853
|
+
|
|
854
|
+
@FuncAr4DataNotify
|
|
855
|
+
def trigger_accept(handle, time_ms, trigger_type, trigger_value):
|
|
856
|
+
self._trigger_accept(time_ms, trigger_type, trigger_value)
|
|
857
|
+
|
|
858
|
+
return trigger_accept
|
|
859
|
+
|
|
860
|
+
def _trigger_accept(self, time_ms, trigger_type, trigger_value):
|
|
861
|
+
logger.info(f"_trigger_accept 被调用")
|
|
862
|
+
logger.info(f"触发时间: {time_ms}, 触发类型: {trigger_type}, 触发值: {trigger_value}")
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _wrap_connected_notify(self):
|
|
866
|
+
|
|
867
|
+
@FuncAr4RecorderConnected
|
|
868
|
+
def connected_notify(handle):
|
|
869
|
+
self._connected_notify(handle)
|
|
870
|
+
|
|
871
|
+
return connected_notify
|
|
872
|
+
|
|
873
|
+
def _connected_notify(self, handle):
|
|
874
|
+
logger.info(f"_connected_notify 被调用 handle: {handle}")
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def _wrap_disconnected_notify(self):
|
|
878
|
+
@FuncAr4RecorderDisconnected
|
|
879
|
+
def disconnected_notify(handle):
|
|
880
|
+
self._disconnected_notify(handle)
|
|
881
|
+
|
|
882
|
+
return disconnected_notify
|
|
883
|
+
|
|
884
|
+
def _disconnected_notify(self, handle):
|
|
885
|
+
logger.info(f"_disconnected_notify 被调用 handle: {handle}")
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def _wrap_box_connected_notify(self):
|
|
889
|
+
|
|
890
|
+
@FuncAr4RecorderConnected
|
|
891
|
+
def box_connected_notify(handle):
|
|
892
|
+
self._box_connected_notify(handle)
|
|
893
|
+
|
|
894
|
+
return box_connected_notify
|
|
895
|
+
|
|
896
|
+
def _box_connected_notify(self, handle):
|
|
897
|
+
self.box_connected = True
|
|
898
|
+
logger.info(f"_box_connected_notify 被调用 handle: {handle}")
|
|
899
|
+
|
|
900
|
+
def _wrap_box_disconnected_notify(self):
|
|
901
|
+
@FuncAr4RecorderDisconnected
|
|
902
|
+
def box_disconnected_notify(handle):
|
|
903
|
+
self._box_disconnected_notify(handle)
|
|
904
|
+
|
|
905
|
+
return box_disconnected_notify
|
|
906
|
+
|
|
907
|
+
def _box_disconnected_notify(self, handle):
|
|
908
|
+
self.box_connected = False
|
|
909
|
+
logger.info(f"_box_disconnected_notify 被调用 handle: {handle}")
|
|
910
|
+
|
|
911
|
+
def _wrap_start_record_notify(self):
|
|
912
|
+
|
|
913
|
+
@FuncAr4RecorderConnected
|
|
914
|
+
def start_record_notify(handle):
|
|
915
|
+
self._start_record_notify(handle)
|
|
916
|
+
|
|
917
|
+
return start_record_notify
|
|
918
|
+
|
|
919
|
+
def _start_record_notify(self, handle):
|
|
920
|
+
logger.info(f"_start_record_notify 被调用 handle: {handle}")
|
|
921
|
+
self._recording = True
|
|
922
|
+
self._record_start_time = time()
|
|
923
|
+
logger.info(self)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def _wrap_stop_record_notify(self):
|
|
927
|
+
@FuncAr4RecorderDisconnected
|
|
928
|
+
def stop_record_notify(handle):
|
|
929
|
+
self._stop_record_notify(handle)
|
|
930
|
+
|
|
931
|
+
return stop_record_notify
|
|
932
|
+
|
|
933
|
+
def _stop_record_notify(self, handle):
|
|
934
|
+
logger.info(f"_stop_record_notify 被调用 handle: {handle}")
|
|
935
|
+
self._recording = False
|
|
936
|
+
|
|
937
|
+
def __str__(self):
|
|
938
|
+
return f"""
|
|
939
|
+
box mac: {self._box_mac},
|
|
940
|
+
box name: {self._box_name},
|
|
941
|
+
box soc: {self._box_soc}
|
|
942
|
+
head conn state: {self._head_conn_state}
|
|
943
|
+
head mac: {self._head_mac},
|
|
944
|
+
head soc: {self._head_soc}
|
|
945
|
+
connected: {self._connected}
|
|
946
|
+
connect time: {self._conn_time}
|
|
947
|
+
last time: {self._last_time}
|
|
948
|
+
[
|
|
949
|
+
eeg phy max: {self._eeg_phy_max}
|
|
950
|
+
eeg phy min: {self._eeg_phy_min}
|
|
951
|
+
eeg dig max: {self._eeg_dig_max}
|
|
952
|
+
eeg dig min: {self._eeg_dig_min}
|
|
953
|
+
eeg phy unit: {self._eeg_phy_unit}
|
|
954
|
+
]
|
|
955
|
+
[
|
|
956
|
+
acc phy max: {self._acc_phy_max}
|
|
957
|
+
acc phy min: {self._acc_phy_min}
|
|
958
|
+
acc dig max: {self._acc_dig_max}
|
|
959
|
+
acc dig min: {self._acc_dig_min}
|
|
960
|
+
acc phy unit: {self._acc_phy_unit}
|
|
961
|
+
]
|
|
962
|
+
dig -> ((dig - {self._eeg_dig_min}) / {self._eeg_dig_range}) * {self._eeg_phy_range} + {self._eeg_phy_min}
|
|
963
|
+
dig -> ((dig - {self._acc_dig_min}) / {self._acc_dig_range}) * {self._acc_phy_range} + {self._acc_phy_min}
|
|
548
964
|
"""
|
|
549
965
|
|
|
550
966
|
class Packet(object):
|
|
@@ -572,11 +988,6 @@ class AR4Packet(Packet):
|
|
|
572
988
|
self.acc_count = data.acc_count
|
|
573
989
|
# 读eeg数据
|
|
574
990
|
if self.eeg_ch_count and self.eeg_count:
|
|
575
|
-
# self.eeg = [[] for _ in range(self.eeg_ch_count)]
|
|
576
|
-
# for i in range(self.eeg_ch_count):
|
|
577
|
-
# self.eeg[i] = [data.eeg[j + (i * self.eeg_count)] for j in range(self.eeg_count)]
|
|
578
|
-
# tmp = data.eeg[:self.eeg_ch_count * self.eeg_count]
|
|
579
|
-
# logger.info(tmp)
|
|
580
991
|
self.eeg = [data.eeg[i:self.eeg_count*self.eeg_ch_count:self.eeg_ch_count] for i in range(self.eeg_ch_count)]
|
|
581
992
|
# 读acc数据
|
|
582
993
|
if self.acc_ch_count and self.acc_count:
|