qlsdk2 0.2.0__tar.gz → 0.3.0a1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: qlsdk2
3
- Version: 0.2.0
3
+ Version: 0.3.0a1
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -12,6 +12,7 @@ Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: loguru>=0.6.0
14
14
  Requires-Dist: numpy>=1.23.5
15
+ Requires-Dist: pyedflib>=0.1.40
15
16
  Provides-Extra: dev
16
17
  Requires-Dist: pytest>=6.0; extra == "dev"
17
18
  Requires-Dist: twine>=3.0; extra == "dev"
@@ -6,7 +6,7 @@ with open("README.md", "r") as fh:
6
6
 
7
7
  setuptools.setup(
8
8
  name="qlsdk2",
9
- version="0.2.0",
9
+ version="0.3.0a1",
10
10
  author="hehuajun",
11
11
  author_email="hehuajun@eegion.com",
12
12
  description="SDK for quanlan device",
@@ -1,9 +1,12 @@
1
1
  # __version__ = "0.1.1"
2
2
 
3
3
  # 暴露公共接口
4
- from .ar4m import AR4M, AR4, AR4Packet
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
@@ -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
@@ -0,0 +1,21 @@
1
+ from qlsdk.sdk import Hub
2
+ from qlsdk.ar4 import AR4
3
+ from loguru import logger
4
+
5
+
6
+ class AR4M(Hub):
7
+ def __init__(self):
8
+ super().__init__()
9
+ self._devices: dict[str, AR4] = {}
10
+ self._search_running = False
11
+ self._search_timer = None
12
+
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}")
@@ -0,0 +1 @@
1
+ from .edf import EdfHandler
@@ -0,0 +1,186 @@
1
+ from datetime import datetime
2
+ from multiprocessing import Lock, Queue
3
+ from pyedflib import FILETYPE_BDFPLUS, FILETYPE_EDFPLUS, EdfWriter
4
+ from threading import Thread
5
+ from loguru import logger
6
+ import numpy as np
7
+ import os
8
+
9
+ class EdfHandler(object):
10
+ def __init__(self, sample_frequency, physical_max, physical_min, digital_max, digital_min, resolution=16, storage_path = None):
11
+ self.physical_max = physical_max
12
+ self.physical_min = physical_min
13
+ self.digital_max = digital_max
14
+ self.digital_min = digital_min
15
+ self.eeg_channels = None
16
+ self.eeg_sample_rate = 500
17
+ self.acc_channels = None
18
+ self.acc_sample_rate = 50
19
+ self._cache = Queue()
20
+ self.resolution = resolution
21
+ self.sample_frequency = sample_frequency
22
+ # bytes per second
23
+ self.bytes_per_second = 0
24
+ self._edf_writer = None
25
+ self._cache2 = tuple()
26
+ self._recording = False
27
+ self._edf_writer = None
28
+ self.annotations = None
29
+ # 每个数据块大小
30
+ self._chunk = np.array([])
31
+ self._Lock = Lock()
32
+ self._duration = 0
33
+ self._points = 0
34
+ self._first_pkg_id = None
35
+ self._last_pkg_id = None
36
+ self._first_timestamp = None
37
+ self._end_time = None
38
+ self._patient_code = "patient_code"
39
+ self._patient_name = "patient_name"
40
+ self._device_type = None
41
+ self._total_packets = 0
42
+ self._lost_packets = 0
43
+ self._storage_path = storage_path
44
+
45
+ @property
46
+ def file_name(self):
47
+ if self._storage_path:
48
+ try:
49
+ os.makedirs(self._storage_path, exist_ok=True) # 自动创建目录,存在则忽略
50
+ return f"{self._storage_path}/{self._device_type}_{self._first_timestamp}.edf"
51
+ except Exception as e:
52
+ logger.error(f"创建目录[{self._storage_path}]失败: {e}")
53
+
54
+ return f"{self._device_type}_{self._first_timestamp}.edf"
55
+
56
+ @property
57
+ def file_type(self):
58
+ return FILETYPE_BDFPLUS if self.resolution == 24 else FILETYPE_EDFPLUS
59
+
60
+ def set_device_type(self, device_type):
61
+ self._device_type = device_type
62
+
63
+ def set_storage_path(self, storage_path):
64
+ self._storage_path = storage_path
65
+
66
+ def set_patient_code(self, patient_code):
67
+ self._patient_code = patient_code
68
+
69
+ def set_patient_name(self, patient_name):
70
+ self._patient_name = patient_name
71
+
72
+ def append(self, data):
73
+ if data:
74
+ # 通道数
75
+ if self._first_pkg_id is None:
76
+ self.eeg_channels = data.eeg_ch_count
77
+ self.acc_channels = data.acc_ch_count
78
+ self._first_pkg_id = data.pkg_id
79
+ self._first_timestamp = data.time_stamp
80
+
81
+ if self._last_pkg_id and self._last_pkg_id != data.pkg_id - 1:
82
+ self._lost_packets += data.pkg_id - self._last_pkg_id - 1
83
+ logger.warning(f"数据包丢失: {self._last_pkg_id} -> {data.pkg_id}, 丢包数: {data.pkg_id - self._last_pkg_id - 1}")
84
+
85
+ self._last_pkg_id = data.pkg_id
86
+ self._total_packets += 1
87
+
88
+ # 数据
89
+ self._cache.put(data)
90
+ if not self._recording:
91
+ self.start()
92
+
93
+ def trigger(self, data):
94
+ pass
95
+
96
+ def start(self):
97
+ self._recording = True
98
+ record_thread = Thread(target=self._consumer)
99
+ record_thread.start()
100
+
101
+ def _consumer(self):
102
+ logger.debug(f"开始消费数据 _consumer: {self._cache.qsize()}")
103
+ while True:
104
+ if self._recording or (not self._cache.empty()):
105
+ try:
106
+ data = self._cache.get(timeout=10)
107
+ if data is None:
108
+ break
109
+ # 处理数据
110
+ self._points += len(data.eeg[0])
111
+ self._write_file(data.eeg, data.acc)
112
+ except Exception as e:
113
+ logger.error("数据队列为空,超时(10s)结束")
114
+ break
115
+ else:
116
+ break
117
+
118
+ self.close()
119
+
120
+ def _write_file(self, eeg_data, acc_data):
121
+ try:
122
+ if self._edf_writer is None:
123
+ self.initialize_edf()
124
+
125
+ if (self._chunk.size == 0):
126
+ self._chunk = np.asarray(eeg_data)
127
+ else:
128
+ self._chunk = np.hstack((self._chunk, eeg_data))
129
+
130
+ if self._chunk.size >= self.eeg_sample_rate * self.eeg_channels:
131
+ self._write_chunk(self._chunk[:self.sample_frequency])
132
+ self._chunk = self._chunk[self.sample_frequency:]
133
+
134
+ except Exception as e:
135
+ logger.error(f"写入数据异常: {str(e)}")
136
+
137
+ def close(self):
138
+ self._recording = False
139
+ if self._edf_writer:
140
+ self._end_time = datetime.now().timestamp()
141
+ self._edf_writer.writeAnnotation(0, 1, "start recording ")
142
+ self._edf_writer.writeAnnotation(self._duration, 1, "recording end")
143
+ self._edf_writer.close()
144
+
145
+ logger.info(f"文件: {self.file_name}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
146
+
147
+
148
+
149
+ def initialize_edf(self):
150
+ # 创建EDF+写入器
151
+ self._edf_writer = EdfWriter(
152
+ self.file_name,
153
+ self.eeg_channels,
154
+ file_type=self.file_type
155
+ )
156
+
157
+ # 设置头信息
158
+ self._edf_writer.setPatientCode(self._patient_code)
159
+ self._edf_writer.setPatientName(self._patient_name)
160
+ self._edf_writer.setEquipment(self._device_type)
161
+ self._edf_writer.setStartdatetime(datetime.now())
162
+
163
+ # 配置通道参数
164
+ signal_headers = []
165
+ for ch in range(self.eeg_channels):
166
+ signal_headers.append({
167
+ "label": f'channels {ch + 1}',
168
+ "dimension": 'uV',
169
+ "sample_frequency": self.sample_frequency,
170
+ "physical_min": self.physical_min,
171
+ "physical_max": self.physical_max,
172
+ "digital_min": self.digital_min,
173
+ "digital_max": self.digital_max
174
+ })
175
+
176
+ self._edf_writer.setSignalHeaders(signal_headers)
177
+
178
+ def _write_chunk(self, chunk):
179
+ logger.debug(f"写入数据: {chunk}")
180
+ # 转换数据类型为float64(pyedflib要求)
181
+ data_float64 = chunk.astype(np.float64)
182
+ # 写入时转置为(样本数, 通道数)格式
183
+ self._edf_writer.writeSamples(data_float64)
184
+ self._duration += 1
185
+
186
+
@@ -0,0 +1,2 @@
1
+ from .ar4sdk import LMDevice, LMPacket, AR4SDK
2
+ from .hub import Hub
@@ -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,17 +163,64 @@ class AR4SDK:
162
163
 
163
164
  def __exit__(self, exc_type, exc_val, exc_tb):
164
165
  pass
165
-
166
166
 
167
+
168
+ # 枚举后才能连接上
169
+ AR4SDK.enum_devices()
167
170
  # 读取系统当前时间(ms)
168
171
  def _get_time():
169
172
  cur_time = int(round(time()) * 1000)
170
- logger.debug(f"_get_time is {cur_time}")
171
173
  return cur_time
174
+
175
+ class Packet(object):
176
+ pass
177
+
178
+ class LMPacket(Packet):
179
+ def __init__(self):
180
+ self.time_stamp = None
181
+ self.pkg_id = None
182
+ self.notify_id = None
183
+ self.eeg_ch_count = None
184
+ self.eeg_count = None
185
+ self.eeg = None
186
+ self.acc_ch_count = None
187
+ self.acc_count = None
188
+ self.acc = None
172
189
 
173
- # ar4设备对象
174
- class AR4(object):
175
- def __init__(self, box_mac:str, slot:int, hub_name:str):
190
+ def transfer(self, data: Ar4NotifyData):
191
+ self.time_stamp = data.time_stamp
192
+ self.pkg_id = data.pkg_id
193
+ self.notify_id = data.notify_id
194
+ self.eeg_ch_count = data.eeg_ch_count
195
+ self.eeg_count = data.eeg_count
196
+ self.acc_ch_count = data.acc_ch_count
197
+ self.acc_count = data.acc_count
198
+ # 读eeg数据
199
+ if self.eeg_ch_count and self.eeg_count:
200
+ self.eeg = [data.eeg[i:self.eeg_count*self.eeg_ch_count:self.eeg_ch_count] for i in range(self.eeg_ch_count)]
201
+ # 读acc数据
202
+ if self.acc_ch_count and self.acc_count:
203
+ self.acc = [[] for _ in range(self.acc_ch_count)]
204
+ for i in range(self.acc_ch_count):
205
+ self.acc[i] = [data.acc[j + (i * self.acc_count)] for j in range(self.acc_count)]
206
+
207
+ return self
208
+
209
+ def __str__(self):
210
+ return f"""
211
+ time_stamp: {self.time_stamp}
212
+ pkg_id: {self.pkg_id}
213
+ notify_id: {self.notify_id}
214
+ eeg_ch_count: {self.eeg_ch_count}
215
+ eeg_count: {self.eeg_count}
216
+ acc_ch_count: {self.acc_ch_count}
217
+ acc_count: {self.acc_count}
218
+ eeg: {self.eeg}
219
+ acc: {self.acc}
220
+ """
221
+
222
+ class LMDevice(object):
223
+ def __init__(self, box_mac:str):
176
224
  # 设备句柄
177
225
  self._handle = None
178
226
  # 设备基本信息
@@ -188,8 +236,8 @@ class AR4(object):
188
236
  self._head_conn_state = None
189
237
  self._head_soc = None
190
238
  self._net_state = None
191
- self._hub_name = hub_name
192
- self._slot = slot
239
+ # self._hub_name = hub_name
240
+ # self._slot = slot
193
241
  self._connected = False
194
242
  self._conn_time = None
195
243
  self._last_time = None
@@ -214,11 +262,9 @@ class AR4(object):
214
262
  self._acc_phy_range = None
215
263
  self._acc_dig_range = None
216
264
 
265
+ self._sample_frequency = 500
266
+
217
267
  self._acq_info = {}
218
- # 订阅者列表,数值为数字信号值
219
- self._dig_subscriber: dict[str, Queue] = {}
220
- # 订阅者列表,数值为物理信号值
221
- self._phy_subscriber: dict[str, Queue] = {}
222
268
 
223
269
  # 回调函数
224
270
  self._data_callback = FuncAr4DataNotify(self._wrap_data_accept())
@@ -230,6 +276,16 @@ class AR4(object):
230
276
  self._start_record_notify_callback = FuncAr4RecorderConnected(self._wrap_start_record_notify())
231
277
  self._stop_record_notify_callback = FuncAr4RecorderDisconnected(self._wrap_stop_record_notify())
232
278
 
279
+ self.init()
280
+
281
+ @property
282
+ def connected(self):
283
+ return self._connected
284
+
285
+ @property
286
+ def sample_frequency(self):
287
+ return self._sample_frequency
288
+
233
289
  @property
234
290
  def box_mac(self):
235
291
  return self._box_mac
@@ -239,47 +295,185 @@ class AR4(object):
239
295
  @property
240
296
  def hub_name(self):
241
297
  return self._hub_name
242
- def init(self):
243
- # logger.info(f"init ar4 {self.box_mac}")
244
- if not self._handle:
245
- self._connected = self.connect()
298
+
299
+ @property
300
+ def eeg_phy_max(self):
301
+ return self._eeg_phy_max
302
+
303
+ @property
304
+ def eeg_phy_min(self):
305
+ return self._eeg_phy_min
306
+
307
+ @property
308
+ def eeg_dig_max(self):
309
+ return self._eeg_dig_max
310
+
311
+ @property
312
+ def eeg_dig_min(self):
313
+ return self._eeg_dig_min
314
+
315
+ @property
316
+ def acc_phy_max(self):
317
+ return self._acc_phy_max
318
+
319
+ @property
320
+ def acc_phy_min(self):
321
+ return self._acc_phy_min
322
+
323
+ @property
324
+ def acc_dig_max(self):
325
+ return self._acc_dig_max
326
+
327
+ @property
328
+ def acc_dig_min(self):
329
+ return self._acc_dig_min
330
+
331
+ @property
332
+ def eeg_phy_unit(self):
333
+ return self._eeg_phy_unit
334
+
335
+ @property
336
+ def acc_phy_unit(self):
337
+ return self._acc_phy_unit
338
+
339
+ @property
340
+ def eeg_phy_range(self):
341
+ return self._eeg_phy_range
342
+
343
+ @property
344
+ def eeg_dig_range(self):
345
+ return self._eeg_dig_range
346
+
347
+ @property
348
+ def acc_phy_range(self):
349
+ return self._acc_phy_range
350
+
351
+ @property
352
+ def acc_dig_range(self):
353
+ return self._acc_dig_range
354
+
355
+ def init(self):
356
+ if self.connect():
357
+ self.get_sample_rate()
246
358
  ## 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)
359
+ self.get_eeg_phy_max()
360
+ self.get_eeg_phy_min()
361
+ self.get_eeg_phy_unit()
362
+ self.get_eeg_digital_max()
363
+ self.get_eeg_digital_min()
251
364
  self._eeg_phy_range = self._eeg_phy_max - self._eeg_phy_min
252
365
  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
366
 
260
367
  ## 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)
368
+ self.get_acc_phy_max()
369
+ self.get_acc_phy_min()
370
+ self.get_acc_phy_unit()
371
+ self.get_acc_digital_max()
372
+ self.get_acc_digital_min()
265
373
  self._acc_phy_range = self._acc_phy_max - self._acc_phy_min
266
374
  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
375
 
274
- if self._connected:
275
- self._conn_time = _get_time()
276
- self.get_box_name()
277
- self.get_head_mac()
278
- self.get_record_conn_state()
279
- self._last_time = _get_time()
280
376
  logger.debug(self)
281
377
  self._register_callback()
282
- return True
378
+ return True
379
+ else:
380
+ logger.error(f"设备[{self._box_mac}]连接失败")
381
+ return False
382
+
383
+ def get_eeg_phy_max(self):
384
+ if self._handle:
385
+ try:
386
+ self._eeg_phy_max = _dll.ar4_sdk_get_eeg_phy_max(self._handle)
387
+ except Exception as e:
388
+ logger.error(f"设备[{self._box_mac}]获取eeg物理最大值异常: {str(e)}")
389
+
390
+ return self._eeg_phy_max
391
+
392
+ def get_eeg_phy_min(self):
393
+ if self._handle:
394
+ try:
395
+ self._eeg_phy_min = _dll.ar4_sdk_get_eeg_phy_min(self._handle)
396
+ except Exception as e:
397
+ logger.error(f"设备[{self._box_mac}]获取eeg物理最小值异常: {str(e)}")
398
+
399
+ return self._eeg_phy_min
400
+
401
+ def get_eeg_phy_unit(self):
402
+ if self._handle:
403
+ try:
404
+ eeg_unit = _dll.ar4_sdk_get_eeg_phy_unit(self._handle)
405
+ if eeg_unit:
406
+ self._eeg_phy_unit = eeg_unit.decode("utf-8")
407
+ except Exception as e:
408
+ logger.error(f"设备[{self._box_mac}]获取eeg物理单位异常: {str(e)}")
409
+
410
+ return self._eeg_phy_unit
411
+
412
+ def get_eeg_digital_max(self):
413
+ if self._handle:
414
+ try:
415
+ self._eeg_dig_max = _dll.ar4_sdk_get_eeg_digtal_max(self._handle)
416
+ except Exception as e:
417
+ logger.error(f"设备[{self._box_mac}]获取eeg数字最大值异常: {str(e)}")
418
+
419
+ return self._eeg_dig_max
420
+
421
+ def get_eeg_digital_min(self):
422
+ if self._handle:
423
+ try:
424
+ self._eeg_dig_min = _dll.ar4_sdk_get_eeg_digtal_min(self._handle)
425
+ except Exception as e:
426
+ logger.error(f"设备[{self._box_mac}]获取eeg数字最小值异常: {str(e)}")
427
+
428
+ return self._eeg_dig_min
429
+
430
+
431
+ def get_acc_phy_max(self):
432
+ if self._handle:
433
+ try:
434
+ self._acc_phy_max = _dll.ar4_sdk_get_acc_phy_max(self._handle)
435
+ except Exception as e:
436
+ logger.error(f"设备[{self._box_mac}]获取acc物理最大值异常: {str(e)}")
437
+
438
+ return self._acc_phy_max
439
+
440
+ def get_acc_phy_min(self):
441
+ if self._handle:
442
+ try:
443
+ self._acc_phy_min = _dll.ar4_sdk_get_acc_phy_min(self._handle)
444
+ except Exception as e:
445
+ logger.error(f"设备[{self._box_mac}]获取acc物理最小值异常: {str(e)}")
446
+
447
+ return self._eeg_phy_min
448
+
449
+ def get_acc_phy_unit(self):
450
+ if self._handle:
451
+ try:
452
+ acc_unit = _dll.ar4_sdk_get_acc_phy_unit(self._handle)
453
+ if acc_unit:
454
+ self._acc_phy_unit = acc_unit.decode("utf-8")
455
+ except Exception as e:
456
+ logger.error(f"设备[{self._box_mac}]获取acc物理单位异常: {str(e)}")
457
+
458
+ return self._acc_phy_unit
459
+
460
+ def get_acc_digital_max(self):
461
+ if self._handle:
462
+ try:
463
+ self._acc_dig_max = _dll.ar4_sdk_get_acc_digtal_max(self._handle)
464
+ except Exception as e:
465
+ logger.error(f"设备[{self._box_mac}]获取acc数字最大值异常: {str(e)}")
466
+
467
+ return self._acc_dig_max
468
+
469
+ def get_acc_digital_min(self):
470
+ if self._handle:
471
+ try:
472
+ self._acc_dig_min = _dll.ar4_sdk_get_acc_digtal_min(self._handle)
473
+ except Exception as e:
474
+ logger.error(f"设备[{self._box_mac}]获取acc数字最小值异常: {str(e)}")
475
+
476
+ return self._acc_dig_min
283
477
 
284
478
  def _register_callback(self):
285
479
  try:
@@ -289,7 +483,7 @@ class AR4(object):
289
483
  _dll.ar4_sdk_register_record_state_notify(self._handle, self._start_record_notify_callback, self._stop_record_notify_callback)
290
484
  _dll.ar4_sdk_register_box_conn_notify(self._handle, self._box_connected_notify_callback, self._box_disconnected_notify_callback)
291
485
  except Exception as e:
292
- logger.error(f"回调函数注册异常: {str(e)}")
486
+ logger.error(f"设备[{self._box_mac}]回调函数注册异常: {str(e)}")
293
487
 
294
488
  def update_info(self):
295
489
  self._last_time = _get_time()
@@ -297,17 +491,28 @@ class AR4(object):
297
491
  # 设备连接
298
492
  def connect(self)-> bool:
299
493
 
300
- if self._box_mac:
301
- """连接设备"""
302
- # callback = FuncAr4GetTime(self._get_time)
303
- try:
304
- self._handle = _dll.ar4_sdk_connect(int(self._box_mac, 16), c_void_p(0))
305
- # logger.info(f"conn handle is {self._handle}")
306
- self._handle = c_void_p(self._handle)
307
- logger.debug(f"ar4 {self._box_mac} 连接: {self._handle}")
308
- except Exception as e:
309
- logger.error(f"ar4 {self._box_mac} 连接异常: {str(e)}")
310
- return self._handle is not None
494
+ if not self._box_mac:
495
+ raise Exception("设备MAC地址不能为空")
496
+
497
+ try:
498
+ self._handle = _dll.ar4_sdk_connect(int(self._box_mac, 16), c_void_p(0))
499
+ # logger.info(f"conn handle is {self._handle}")
500
+ self._connected = self._handle is not None
501
+ if self._handle:
502
+ self._connected = True
503
+ self._handle = c_void_p(self._handle)
504
+ self._conn_time = _get_time()
505
+ self.get_box_name()
506
+ self.get_head_mac()
507
+ self.get_record_conn_state()
508
+ self._last_time = _get_time()
509
+ else:
510
+ raise Exception(f"设备 {self._box_mac} 连接失败: {self._handle}")
511
+ logger.debug(f"ar4 {self._box_mac} 连接: {self._handle}")
512
+ except Exception as e:
513
+ logger.error(f"ar4 {self._box_mac} 连接异常: {str(e)}")
514
+
515
+ return self._connected
311
516
 
312
517
  # 读取盒子名称
313
518
  def get_box_name(self):
@@ -333,23 +538,27 @@ class AR4(object):
333
538
 
334
539
  # 数据采集启动
335
540
  def start_acquisition(self):
336
- logger.info(f"ar4 {self._box_mac} 启动数据采集...")
337
- """启动数据采集"""
541
+ logger.info(f"device {self._box_mac} 启动数据采集...")
542
+ if not self._handle:
543
+ self.init()
338
544
 
339
545
  self._acq_info["start_time"] = _get_time()
340
546
 
341
547
  if self._handle:
342
548
  # 启动采集
343
549
  try:
344
- logger.debug(f"ar4 {self._box_mac} 启动采集: {self._handle}")
550
+ logger.debug(f"device {self._box_mac} 启动采集: {self._handle}")
345
551
  ret = _dll.ar4_sdk_start_acq(self._handle)
346
- logger.debug(f"ar4 {self._box_mac} 启动采集结果: {ret}")
347
-
552
+ if ret == 0:
553
+ logger.info(f"device {self._box_mac} 启动数据采集成功")
554
+ self.start_record()
555
+ else:
556
+ logger.error(f"device {self._box_mac} 启动数据采集失败, ret: {ret}")
348
557
  return ret == 0
349
558
  except Exception as e:
350
- logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
559
+ logger.error(f"device {self._box_mac} 停止采集异常: {str(e)}")
351
560
  else:
352
- logger.info(f"ar4 {self._box_mac} 启动数据采集失败, 设备未连接")
561
+ logger.info(f"device {self._box_mac} 启动数据采集失败, 设备未连接")
353
562
  return False
354
563
 
355
564
  def stop_acquisition(self):
@@ -360,11 +569,23 @@ class AR4(object):
360
569
  except Exception as e:
361
570
  logger.error(f"ar4 {self._box_mac} 获取开始采集时间异常: {str(e)}")
362
571
  try:
363
- return _dll.ar4_sdk_stop_acq(self._handle) == 0
572
+ ret = _dll.ar4_sdk_stop_acq(self._handle)
573
+ if ret == 0:
574
+ logger.info(f"device {self._box_mac} 停止采集成功")
575
+ self.stop_record()
576
+ else:
577
+ logger.error(f"device {self._box_mac} 停止采集失败, ret: {ret}")
578
+
579
+ return ret == 0
364
580
  except Exception as e:
365
581
  logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
366
582
  else:
367
583
  return False
584
+
585
+ def start_record(self):
586
+ pass
587
+ def stop_record(self):
588
+ pass
368
589
 
369
590
  def disconnect(self):
370
591
  """断开连接"""
@@ -374,7 +595,9 @@ class AR4(object):
374
595
  def get_sample_rate(self):
375
596
  try:
376
597
  ret = _dll.ar4_sdk_get_record_sample_rate(self._handle)
377
- logger.debug(f"ar4 {self._box_mac} 获取采样率: {ret.contents.decode('utf-8')}")
598
+ logger.debug(f"ar4 {self._box_mac} 获取采样率: {ret}")
599
+ if ret > 1:
600
+ self._sample_frequency = ret
378
601
  except Exception as e:
379
602
  logger.error(f"ar4 {self._box_mac} 获取采样率异常: {str(e)}")
380
603
 
@@ -387,24 +610,6 @@ class AR4(object):
387
610
  logger.debug(f"ar4 {self._box_mac} 获取采样开始时间: {ret}")
388
611
  except Exception as e:
389
612
  logger.error(f"ar4 {self._box_mac} 获取采样开始时间异常: {str(e)}")
390
-
391
- # 订阅推送消息
392
- def subscribe(self, topic: str = None, q: Queue = Queue(), value_type: Literal['phy', 'dig'] = 'phy') -> tuple[str, Queue]:
393
- if topic is None:
394
- topic = f"msg_{_get_time()}"
395
-
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
408
613
 
409
614
  def _wrap_data_accept(self):
410
615
 
@@ -414,23 +619,10 @@ class AR4(object):
414
619
 
415
620
  return data_accept
416
621
  def _data_accept(self, data_ptr):
417
- # logger.info(f"_eeg_accept 被调用")
418
- # logger.debug(f'handle:{self}, data_ptr:{data_ptr}')
419
- # data = cast(data_ptr, POINTER(Ar4NotifyData)).contents
420
- if len(self._dig_subscriber) > 0 or len(self._phy_subscriber) > 0:
421
- packet = AR4Packet().transfer(data_ptr.contents)
422
-
423
- for consumer in self._dig_subscriber.values():
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
-
432
- # logger.debug(f"EEG数据: {packet}")
622
+ self.eeg_accept(LMPacket().transfer(data_ptr.contents))
433
623
 
624
+ def eeg_accept(self, packet: LMPacket):
625
+ pass
434
626
  def eeg2phy(self, digtal):
435
627
  # 向量化计算(自动支持广播)
436
628
  return ((digtal - self._eeg_dig_min) / self._eeg_dig_range) * self._eeg_phy_range + self._eeg_phy_min
@@ -506,8 +698,7 @@ class AR4(object):
506
698
  logger.info(f"_start_record_notify 被调用 handle: {handle}")
507
699
  self._recording = True
508
700
  self._record_start_time = time()
509
- logger.info(self)
510
-
701
+ logger.info(self)
511
702
 
512
703
  def _wrap_stop_record_notify(self):
513
704
  @FuncAr4RecorderDisconnected
@@ -545,56 +736,7 @@ class AR4(object):
545
736
  acc dig min: {self._acc_dig_min}
546
737
  acc phy unit: {self._acc_phy_unit}
547
738
  ]
739
+ dig -> ((dig - {self._eeg_dig_min}) / {self._eeg_dig_range}) * {self._eeg_phy_range} + {self._eeg_phy_min}
740
+ dig -> ((dig - {self._acc_dig_min}) / {self._acc_dig_range}) * {self._acc_phy_range} + {self._acc_phy_min}
548
741
  """
549
-
550
- class Packet(object):
551
- pass
552
-
553
- class AR4Packet(Packet):
554
- def __init__(self):
555
- self.time_stamp = None
556
- self.pkg_id = None
557
- self.notify_id = None
558
- self.eeg_ch_count = None
559
- self.eeg_count = None
560
- self.eeg = None
561
- self.acc_ch_count = None
562
- self.acc_count = None
563
- self.acc = None
564
-
565
- def transfer(self, data: Ar4NotifyData):
566
- self.time_stamp = data.time_stamp
567
- self.pkg_id = data.pkg_id
568
- self.notify_id = data.notify_id
569
- self.eeg_ch_count = data.eeg_ch_count
570
- self.eeg_count = data.eeg_count
571
- self.acc_ch_count = data.acc_ch_count
572
- self.acc_count = data.acc_count
573
- # 读eeg数据
574
- 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
- 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
- # 读acc数据
582
- if self.acc_ch_count and self.acc_count:
583
- self.acc = [[] for _ in range(self.acc_ch_count)]
584
- for i in range(self.acc_ch_count):
585
- self.acc[i] = [data.acc[j + (i * self.acc_count)] for j in range(self.acc_count)]
586
-
587
- return self
588
-
589
- def __str__(self):
590
- return f"""
591
- time_stamp: {self.time_stamp}
592
- pkg_id: {self.pkg_id}
593
- notify_id: {self.notify_id}
594
- eeg_ch_count: {self.eeg_ch_count}
595
- eeg_count: {self.eeg_count}
596
- acc_ch_count: {self.acc_ch_count}
597
- acc_count: {self.acc_count}
598
- eeg: {self.eeg}
599
- acc: {self.acc}
600
- """
742
+
@@ -0,0 +1,51 @@
1
+ from qlsdk.sdk.ar4sdk import AR4SDK, LMDevice
2
+
3
+
4
+ from time import sleep, time
5
+ from threading import Lock, Timer
6
+ from loguru import logger
7
+
8
+ class Hub(object):
9
+ def __init__(self):
10
+ self._lock = Lock()
11
+ self._search_timer = None
12
+ self._search_running = False
13
+ self._devices: dict[str, LMDevice] = {}
14
+
15
+ @property
16
+ def devices(self):
17
+ return self._devices
18
+
19
+ def search(self):
20
+ if not self._search_running:
21
+ self._search_running = True
22
+ self._search()
23
+
24
+ def _search(self):
25
+ if self._search_running:
26
+
27
+ self._search_timer = Timer(2, self._search_device)
28
+ self._search_timer.daemon = True
29
+ self._search_timer.start()
30
+
31
+
32
+ def _search_device(self):
33
+ try:
34
+ devices = AR4SDK.enum_devices()
35
+ # logger.debug(f"_search_ar4 devices size: {len(devices)}")
36
+ for dev in devices:
37
+ self.add_device(hex(dev.mac))
38
+ except Exception as e:
39
+ logger.error(f"_search_device 异常: {str(e)}")
40
+ finally:
41
+ self._search()
42
+
43
+ def add_device(self, mac: str):
44
+ if mac in list(self._devices.keys()):
45
+ self._devices[mac].update_info()
46
+ logger.info(f"update device mac: {mac}")
47
+ else:
48
+ dev = LMDevice(mac)
49
+ if dev.init():
50
+ self._devices[mac] = dev
51
+ logger.info(f"add device mac: {dev}")
@@ -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 X8(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 "x8"
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
@@ -0,0 +1,21 @@
1
+ from qlsdk.sdk import Hub
2
+ from qlsdk.x8 import X8
3
+ from loguru import logger
4
+
5
+
6
+ class X8M(Hub):
7
+ def __init__(self):
8
+ super().__init__()
9
+ self._devices: dict[str, X8] = {}
10
+ self._search_running = False
11
+ self._search_timer = None
12
+
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 = X8(mac)
19
+ if dev.connected:
20
+ self._devices[mac] = dev
21
+ logger.info(f"add x8 device mac: {dev}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: qlsdk2
3
- Version: 0.2.0
3
+ Version: 0.3.0a1
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -12,6 +12,7 @@ Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: loguru>=0.6.0
14
14
  Requires-Dist: numpy>=1.23.5
15
+ Requires-Dist: pyedflib>=0.1.40
15
16
  Provides-Extra: dev
16
17
  Requires-Dist: pytest>=6.0; extra == "dev"
17
18
  Requires-Dist: twine>=3.0; extra == "dev"
@@ -7,8 +7,13 @@ src/qlsdk2.egg-info/SOURCES.txt
7
7
  src/qlsdk2.egg-info/dependency_links.txt
8
8
  src/qlsdk2.egg-info/requires.txt
9
9
  src/qlsdk2.egg-info/top_level.txt
10
+ src/qlsdk/ar4/__init__.py
10
11
  src/qlsdk/ar4m/__init__.py
11
- src/qlsdk/ar4m/ar4sdk.py
12
- src/qlsdk/ar4m/libs/libAr4SDK.dll
13
- src/qlsdk/ar4m/libs/libwinpthread-1.dll
12
+ src/qlsdk/persist/__init__.py
13
+ src/qlsdk/persist/edf.py
14
+ src/qlsdk/sdk/__init__.py
15
+ src/qlsdk/sdk/ar4sdk.py
16
+ src/qlsdk/sdk/hub.py
17
+ src/qlsdk/x8/__init__.py
18
+ src/qlsdk/x8m/__init__.py
14
19
  test/test_ar4m.py
@@ -1,5 +1,6 @@
1
1
  loguru>=0.6.0
2
2
  numpy>=1.23.5
3
+ pyedflib>=0.1.40
3
4
 
4
5
  [dev]
5
6
  pytest>=6.0
@@ -1,50 +0,0 @@
1
- __path__ = __import__("pkgutil").extend_path(__path__, __name__)
2
-
3
- from .ar4sdk import AR4SDK, AR4, Packet, AR4Packet
4
-
5
-
6
- from time import sleep, time
7
- from threading import Lock, Timer
8
- from loguru import logger
9
-
10
- class AR4M(object):
11
- def __init__(self):
12
- self._lock = Lock()
13
- self._search_timer = None
14
- self._search_running = False
15
- self._devices: dict[str, AR4] = {}
16
-
17
- @property
18
- def devices(self):
19
- return self._devices
20
- def search(self):
21
- if not self._search_running:
22
- self._search_running = True
23
- self._search()
24
-
25
- def _search(self):
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()
File without changes
File without changes
File without changes