qlsdk2 0.3.0a2__py3-none-any.whl → 0.3.0a3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: qlsdk2
3
- Version: 0.3.0a2
3
+ Version: 0.3.0a3
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -1,18 +1,16 @@
1
1
  qlsdk/__init__.py,sha256=w0UCnZJekAq4orDahQXRL8s5JJGrWMz0De5LVIf4X_c,426
2
2
  qlsdk/ar4/__init__.py,sha256=iR0bFanDwqSxSEM1nCcge1l-GSIWjwnMeRQa47wGrds,4752
3
3
  qlsdk/ar4m/__init__.py,sha256=Z13X53WKmKp6MW_fA1FUPdQgJxhxxuyzemJWPS_CEHA,639
4
- qlsdk/ar4m/ar4sdk.py,sha256=OtmlvbO7XAgk5FNPFYIv_rXGj14maaWtgi2lQ7zQkvY,41418
5
- qlsdk/ar4m/persist.py,sha256=YrISKVfY-r1m2GZOw-JLuqTbEajA5wYa31MejGlz14I,6696
6
- qlsdk/ar4m/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,15402132
7
- qlsdk/ar4m/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
8
4
  qlsdk/persist/__init__.py,sha256=UYaNL_RSczIZL_YrBWAifoAnNxpgZHEeSp-goHqyqv4,27
9
5
  qlsdk/persist/edf.py,sha256=usgFjZJVcfAUwSy6cJtRdgjdqMgOS4viLCDGcwAGMZE,7106
10
6
  qlsdk/sdk/__init__.py,sha256=v9LKP-5qXCqnAsCkiRE9LDb5Tagvl_Qd_fqrw7y9yd4,68
11
7
  qlsdk/sdk/ar4sdk.py,sha256=ZILuUVgUZN7-LNQm4xdItxmZWGwvFt4oWUKGez1BCLI,27101
12
8
  qlsdk/sdk/hub.py,sha256=uEOGZBZtMDCWlV8G2TZe6FAo6eTPcwHAW8zdqr1eq_0,1571
9
+ qlsdk/sdk/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,15402132
10
+ qlsdk/sdk/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
13
11
  qlsdk/x8/__init__.py,sha256=XlQn3Af_HA7bGrxhv3RZKWMob_SnmissXiso7OH1UgI,4750
14
12
  qlsdk/x8m/__init__.py,sha256=cLeUqEEj65qXw4Qa4REyxoLh6T24anSqPaKe9_lR340,634
15
- qlsdk2-0.3.0a2.dist-info/METADATA,sha256=o6ms9sTAOlaWyWyGuLc0IWYvFYu1oXXVDhqNMixvpqI,7066
16
- qlsdk2-0.3.0a2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
17
- qlsdk2-0.3.0a2.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
18
- qlsdk2-0.3.0a2.dist-info/RECORD,,
13
+ qlsdk2-0.3.0a3.dist-info/METADATA,sha256=FE9C1YckDtBRsYXY6VInPhcf_WT5X1D-t_FO7WL-MUc,7066
14
+ qlsdk2-0.3.0a3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
15
+ qlsdk2-0.3.0a3.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
16
+ qlsdk2-0.3.0a3.dist-info/RECORD,,
qlsdk/ar4m/ar4sdk.py DELETED
@@ -1,1011 +0,0 @@
1
- import ctypes
2
- from multiprocessing import Queue
3
- import platform
4
- from ctypes import (
5
- c_int, c_int32, c_int64, c_uint32, c_uint64,
6
- c_char, c_char_p, c_void_p, c_double,
7
- Structure, POINTER, CFUNCTYPE
8
- )
9
- from typing import Literal
10
- from loguru import logger
11
- from time import sleep, time
12
- import os
13
- import numpy as np
14
- from .persist import EdfHandler
15
-
16
- real_path = os.path.realpath(__file__)
17
- dll_path = f'{os.path.dirname(real_path)}/libs/libAr4SDK.dll'
18
-
19
- # 加载 DLL
20
- if platform.system() == 'Windows':
21
- _dll = ctypes.CDLL(dll_path)
22
- else:
23
- raise NotImplementedError(f"不支持非Windows平台:{platform.system()}")
24
-
25
- #------------------------------------------------------------------
26
- # 基础类型定义
27
- #------------------------------------------------------------------
28
- class Ar4MacStr(Structure):
29
- _fields_ = [("str", c_char * 17)]
30
-
31
- class Ar4Name(Structure):
32
- _fields_ = [("str", c_char * 21)]
33
-
34
- class Ar4Device(Structure):
35
- _fields_ = [
36
- ("slot", c_int32),
37
- ("mac", c_uint64),
38
- ("hub_name", Ar4Name)
39
- ]
40
-
41
- class Ar4NotifyData(Structure):
42
- _fields_ = [
43
- ("time_stamp", c_int64),
44
- ("pkg_id", c_int64),
45
- ("notify_id", c_int64),
46
- ("eeg", POINTER(c_int32)),
47
- ("eeg_ch_count", c_int32),
48
- ("eeg_count", c_int32),
49
- ("acc", POINTER(c_int32)),
50
- ("acc_ch_count", c_int32),
51
- ("acc_count", c_int32)
52
- ]
53
-
54
- #------------------------------------------------------------------
55
- # 回调函数类型
56
- #------------------------------------------------------------------
57
- FuncAr4DataNotify = CFUNCTYPE(None, c_void_p, POINTER(Ar4NotifyData))
58
- FuncAr4TriggerNotify = CFUNCTYPE(None, c_void_p, c_int64, c_int32, c_uint32)
59
- FuncAr4RecorderDisconnected = CFUNCTYPE(None, c_void_p)
60
- FuncAr4RecorderConnected = CFUNCTYPE(None, c_void_p)
61
- FuncAr4RecorderTimeout = CFUNCTYPE(None, c_void_p, c_int64)
62
- FuncAr4RecorderPkgLost = CFUNCTYPE(c_int, c_void_p, c_int64, c_int64, c_int64)
63
- FuncAr4GetTime = CFUNCTYPE(c_int64)
64
- FuncAr4SDKPrint = CFUNCTYPE(None, c_char_p, ctypes.c_size_t)
65
-
66
- #------------------------------------------------------------------
67
- # SDK 函数定义
68
- #------------------------------------------------------------------
69
- # 设备枚举
70
- _dll.ar4_sdk_enum_device.restype = POINTER(Ar4Device)
71
- _dll.ar4_sdk_enum_hub.restype = POINTER(Ar4Name)
72
- _dll.ar4_sdk_enum_in_hub.argtypes = [Ar4Name]
73
-
74
- # MAC地址转换
75
- _dll.ar4_mac_to_str.argtypes = [c_uint64]
76
- _dll.ar4_mac_to_str.restype = Ar4MacStr
77
- _dll.ar4_str_to_mac.argtypes = [Ar4MacStr]
78
- _dll.ar4_str_to_mac.restype = c_uint64
79
-
80
- # 连接管理
81
- _dll.ar4_sdk_connect.argtypes = [c_uint64, c_void_p]
82
- _dll.ar4_sdk_connect.restype = c_void_p
83
- _dll.ar4_sdk_disconnect.argtypes = [c_void_p]
84
-
85
- # AR4信息查询
86
- _dll.ar4_sdk_get_box_name.argtypes = [c_void_p]
87
- _dll.ar4_sdk_get_box_name.restype = c_uint64
88
-
89
- # 数据采集控制
90
- _dll.ar4_sdk_start_acq.argtypes = [c_void_p]
91
- _dll.ar4_sdk_start_acq.restype = c_int
92
- _dll.ar4_sdk_stop_acq.argtypes = [c_void_p]
93
- _dll.ar4_sdk_stop_acq.restype = c_int
94
- _dll.ar4_sdk_get_record_sample_rate.argtypes = [c_void_p]
95
- _dll.ar4_sdk_get_record_sample_rate.restype = c_double
96
- _dll.ar4_sdk_get_acq_start_time.argtypes = [c_void_p]
97
- _dll.ar4_sdk_get_acq_start_time.restype = c_int64
98
-
99
- # eeg信号物理量转换
100
- _dll.ar4_sdk_get_eeg_phy_max.argtypes = [c_void_p]
101
- _dll.ar4_sdk_get_eeg_phy_max.restype = c_double
102
- _dll.ar4_sdk_get_eeg_phy_min.argtypes = [c_void_p]
103
- _dll.ar4_sdk_get_eeg_phy_min.restype = c_double
104
- _dll.ar4_sdk_get_eeg_digtal_max.argtypes = [c_void_p]
105
- _dll.ar4_sdk_get_eeg_digtal_max.restype = c_int32
106
- _dll.ar4_sdk_get_eeg_digtal_min.argtypes = [c_void_p]
107
- _dll.ar4_sdk_get_eeg_digtal_min.restype = c_int32
108
- _dll.ar4_sdk_get_eeg_phy_unit.argtypes = [c_void_p]
109
- _dll.ar4_sdk_get_eeg_phy_unit.restype = c_char_p
110
-
111
- # acc信号物理量转换
112
- _dll.ar4_sdk_get_acc_phy_max.argtypes = [c_void_p]
113
- _dll.ar4_sdk_get_acc_phy_max.restype = c_double
114
- _dll.ar4_sdk_get_acc_phy_min.argtypes = [c_void_p]
115
- _dll.ar4_sdk_get_acc_phy_min.restype = c_double
116
- _dll.ar4_sdk_get_acc_digtal_max.argtypes = [c_void_p]
117
- _dll.ar4_sdk_get_acc_digtal_max.restype = c_int32
118
- _dll.ar4_sdk_get_acc_digtal_min.argtypes = [c_void_p]
119
- _dll.ar4_sdk_get_acc_digtal_min.restype = c_int32
120
- _dll.ar4_sdk_get_acc_phy_unit.argtypes = [c_void_p]
121
- _dll.ar4_sdk_get_acc_phy_unit.restype = c_char_p
122
-
123
- # 回调注册
124
- _dll.ar4_sdk_register_data_notify.argtypes = [c_void_p, FuncAr4DataNotify]
125
- _dll.ar4_sdk_register_data_notify.restype = None
126
- _dll.ar4_sdk_register_trigger_notify.argtypes = [c_void_p, c_void_p]
127
- _dll.ar4_sdk_register_conn_notify.argtypes = [c_void_p, FuncAr4RecorderConnected, FuncAr4RecorderDisconnected]
128
- _dll.ar4_sdk_register_record_state_notify.argtypes = [c_void_p, FuncAr4RecorderConnected, FuncAr4RecorderDisconnected]
129
- _dll.ar4_sdk_register_box_conn_notify.argtypes = [c_void_p, FuncAr4RecorderConnected, FuncAr4RecorderDisconnected]
130
-
131
- # _dll对外封装类
132
- class AR4SDK:
133
- @classmethod
134
- def enum_devices(cls):
135
- """枚举可用设备"""
136
- devices = []
137
- ptr = _dll.ar4_sdk_enum_device()
138
- if not ptr:
139
- logger.info("没有找到ar4设备")
140
- return devices
141
-
142
- index = 0
143
- while True:
144
- device = ptr[index]
145
- if not device or device.mac == 0:
146
- break
147
- # logger.debug(f"get device: {device.slot}, mac: {hex(device.mac)}, hub_name: {device.hub_name.str}")
148
- devices.append(device)
149
- index += 1
150
-
151
- return devices
152
-
153
- @classmethod
154
- def enum_hubs(cls):
155
- """枚举可用设备"""
156
- devices = []
157
- ptr = _dll.ar4_sdk_enum_hub()
158
- sleep(5)
159
- logger.debug(f"enum_hubs: {ptr}")
160
-
161
- def __enter__(self):
162
- return self
163
-
164
- def __exit__(self, exc_type, exc_val, exc_tb):
165
- pass
166
-
167
- # 读取系统当前时间(ms)
168
- def _get_time():
169
- cur_time = int(round(time()) * 1000)
170
- return cur_time
171
-
172
- class LMDevice(object):
173
- def __init__(self, box_mac:str, slot:int, hub_name:str):
174
- # 设备句柄
175
- self._handle = None
176
- # 设备基本信息
177
- self._box_type = None
178
- self._box_mac = box_mac
179
- self._box_id = None
180
- self._box_soc = None
181
- self._box_name = None
182
- self._box_version = None
183
- self._head_type = None
184
- self._head_mac = None
185
- self._head_version = None
186
- self._head_conn_state = None
187
- self._head_soc = None
188
- self._net_state = None
189
- self._hub_name = hub_name
190
- self._slot = slot
191
- self._connected = False
192
- self._conn_time = None
193
- self._last_time = None
194
-
195
- self._recording = False
196
- self._record_start_time = None
197
-
198
- ## eeg 数据
199
- self._eeg_phy_max = None
200
- self._eeg_phy_min = None
201
- self._eeg_dig_max = None
202
- self._eeg_dig_min = None
203
- self._eeg_phy_unit = 'uV'
204
- self._eeg_phy_range = None
205
- self._eeg_dig_range = None
206
- ## acc 数据
207
- self._acc_phy_max = None
208
- self._acc_phy_min = None
209
- self._acc_dig_max = None
210
- self._acc_dig_min = None
211
- self._acc_phy_unit = 'mG'
212
- self._acc_phy_range = None
213
- self._acc_dig_range = None
214
-
215
- self._acq_info = {}
216
- # 订阅者列表,数值为数字信号值
217
- self._dig_subscriber: dict[str, Queue] = {}
218
- # 订阅者列表,数值为物理信号值
219
- self._phy_subscriber: dict[str, Queue] = {}
220
-
221
- # 回调函数
222
- self._data_callback = FuncAr4DataNotify(self._wrap_data_accept())
223
- self._trigger_callback = FuncAr4TriggerNotify(self._wrap_trigger_accept())
224
- self._connected_notify_callback = FuncAr4RecorderConnected(self._wrap_connected_notify())
225
- self._disconnected_notify_callback = FuncAr4RecorderDisconnected(self._wrap_disconnected_notify())
226
- self._box_connected_notify_callback = FuncAr4RecorderConnected(self._wrap_box_connected_notify())
227
- self._box_disconnected_notify_callback = FuncAr4RecorderDisconnected(self._wrap_box_disconnected_notify())
228
- self._start_record_notify_callback = FuncAr4RecorderConnected(self._wrap_start_record_notify())
229
- self._stop_record_notify_callback = FuncAr4RecorderDisconnected(self._wrap_stop_record_notify())
230
-
231
- @property
232
- def box_mac(self):
233
- return self._box_mac
234
- @property
235
- def slot(self):
236
- return self._slot
237
- @property
238
- def hub_name(self):
239
- return self._hub_name
240
- def init(self):
241
- # logger.info(f"init ar4 {self.box_mac}")
242
- if not self._handle:
243
- self._connected = self.connect()
244
- ## eeg 参数
245
- self._eeg_phy_max = _dll.ar4_sdk_get_eeg_phy_max(self._handle)
246
- self._eeg_phy_min = _dll.ar4_sdk_get_eeg_phy_min(self._handle)
247
- self._eeg_dig_max = _dll.ar4_sdk_get_eeg_digtal_max(self._handle)
248
- self._eeg_dig_min = _dll.ar4_sdk_get_eeg_digtal_min(self._handle)
249
- self._eeg_phy_range = self._eeg_phy_max - self._eeg_phy_min
250
- self._eeg_dig_range = self._eeg_dig_max - self._eeg_dig_min
251
- eeg_unit = _dll.ar4_sdk_get_eeg_phy_unit(self._handle)
252
- if eeg_unit:
253
- try:
254
- self._eeg_phy_unit = eeg_unit.decode("utf-8")
255
- except Exception as e:
256
- logger.error(f"ar4 {self._box_mac} 获取eeg物理单位异常: {str(e)}")
257
-
258
- ## acc 参数
259
- self._acc_phy_max = _dll.ar4_sdk_get_acc_phy_max(self._handle)
260
- self._acc_phy_min = _dll.ar4_sdk_get_acc_phy_min(self._handle)
261
- self._acc_dig_max = _dll.ar4_sdk_get_acc_digtal_max(self._handle)
262
- self._acc_dig_min = _dll.ar4_sdk_get_acc_digtal_min(self._handle)
263
- self._acc_phy_range = self._acc_phy_max - self._acc_phy_min
264
- self._acc_dig_range = self._acc_dig_max - self._acc_dig_min
265
- acc_unit = _dll.ar4_sdk_get_acc_phy_unit(self._handle)
266
- if acc_unit:
267
- try:
268
- self._acc_phy_unit = eeg_unit.decode("utf-8")
269
- except Exception as e:
270
- logger.error(f"ar4 {self._box_mac} 获取acc物理单位异常: {str(e)}")
271
-
272
- if self._connected:
273
- self._conn_time = _get_time()
274
- self.get_box_name()
275
- self.get_head_mac()
276
- self.get_record_conn_state()
277
- self._last_time = _get_time()
278
- logger.debug(self)
279
- self._register_callback()
280
- return True
281
-
282
- def _register_callback(self):
283
- try:
284
- _dll.ar4_sdk_register_data_notify(self._handle, self._data_callback)
285
- _dll.ar4_sdk_register_trigger_notify(self._handle, self._trigger_callback)
286
- _dll.ar4_sdk_register_conn_notify(self._handle, self._connected_notify_callback, self._disconnected_notify_callback)
287
- _dll.ar4_sdk_register_record_state_notify(self._handle, self._start_record_notify_callback, self._stop_record_notify_callback)
288
- _dll.ar4_sdk_register_box_conn_notify(self._handle, self._box_connected_notify_callback, self._box_disconnected_notify_callback)
289
- except Exception as e:
290
- logger.error(f"回调函数注册异常: {str(e)}")
291
-
292
- def update_info(self):
293
- self._last_time = _get_time()
294
-
295
- # 设备连接
296
- def connect(self)-> bool:
297
-
298
- if self._box_mac:
299
- """连接设备"""
300
- # callback = FuncAr4GetTime(self._get_time)
301
- try:
302
- self._handle = _dll.ar4_sdk_connect(int(self._box_mac, 16), c_void_p(0))
303
- # logger.info(f"conn handle is {self._handle}")
304
- self._handle = c_void_p(self._handle)
305
- logger.debug(f"ar4 {self._box_mac} 连接: {self._handle}")
306
- except Exception as e:
307
- logger.error(f"ar4 {self._box_mac} 连接异常: {str(e)}")
308
- return self._handle is not None
309
-
310
- # 读取盒子名称
311
- def get_box_name(self):
312
- if self._handle:
313
- try:
314
- self._head_mac = _dll.ar4_sdk_get_recoder_mac(self._handle)
315
- except Exception as e:
316
- logger.error(f"ar4 {self._box_mac} 获取盒子记录子mac异常: {str(e)}")
317
- # 读取记录子mac
318
- def get_head_mac(self):
319
- if self._handle:
320
- try:
321
- self._box_name = _dll.ar4_sdk_get_record_conn_state(self._handle)
322
- except Exception as e:
323
- logger.error(f"ar4 {self._box_mac} 获取盒子名称异常: {str(e)}")
324
- # 读取记录子连接状态
325
- def get_record_conn_state(self):
326
- if self._handle:
327
- try:
328
- self._head_conn_state = _dll.ar4_sdk_get_record_conn_state(self._handle)
329
- except Exception as e:
330
- logger.error(f"ar4 {self._box_mac} 获取盒子记录子连接状态异常: {str(e)}")
331
-
332
- # 数据采集启动
333
- def start_acquisition(self):
334
- logger.info(f"ar4 {self._box_mac} 启动数据采集...")
335
- """启动数据采集"""
336
-
337
- self._acq_info["start_time"] = _get_time()
338
-
339
- if self._handle:
340
- # 启动采集
341
- try:
342
- logger.debug(f"ar4 {self._box_mac} 启动采集: {self._handle}")
343
- ret = _dll.ar4_sdk_start_acq(self._handle)
344
- logger.debug(f"ar4 {self._box_mac} 启动采集结果: {ret}")
345
-
346
- return ret == 0
347
- except Exception as e:
348
- logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
349
- else:
350
- logger.info(f"ar4 {self._box_mac} 启动数据采集失败, 设备未连接")
351
- return False
352
-
353
- def stop_acquisition(self):
354
- """停止采集"""
355
- if self._handle:
356
- try:
357
- self.get_acq_start_time()
358
- except Exception as e:
359
- logger.error(f"ar4 {self._box_mac} 获取开始采集时间异常: {str(e)}")
360
- try:
361
- return _dll.ar4_sdk_stop_acq(self._handle) == 0
362
- except Exception as e:
363
- logger.error(f"ar4 {self._box_mac} 停止采集异常: {str(e)}")
364
- else:
365
- return False
366
-
367
- def disconnect(self):
368
- """断开连接"""
369
- if self._handle:
370
- _dll.ar4_sdk_disconnect(self._handle)
371
-
372
- def get_sample_rate(self):
373
- try:
374
- ret = _dll.ar4_sdk_get_record_sample_rate(self._handle)
375
- logger.debug(f"ar4 {self._box_mac} 获取采样率: {ret.contents.decode('utf-8')}")
376
- except Exception as e:
377
- logger.error(f"ar4 {self._box_mac} 获取采样率异常: {str(e)}")
378
-
379
- def get_acq_start_time(self):
380
- try:
381
- ret = _dll.ar4_sdk_get_acq_start_time(self._handle)
382
- # 更新采样开始时间
383
- if ret:
384
- self._acq_info["start_time"] = ret
385
- logger.debug(f"ar4 {self._box_mac} 获取采样开始时间: {ret}")
386
- except Exception as e:
387
- logger.error(f"ar4 {self._box_mac} 获取采样开始时间异常: {str(e)}")
388
-
389
- # 订阅推送消息
390
- def subscribe(self, topic: str = None, q: Queue = Queue(), value_type: Literal['phy', 'dig'] = 'phy') -> tuple[str, Queue]:
391
- if topic is None:
392
- topic = f"msg_{_get_time()}"
393
-
394
- if value_type == 'dig':
395
- if topic in list(self._dig_subscriber.keys()):
396
- logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
397
- return topic, self._dig_subscriber[topic]
398
- self._dig_subscriber[topic] = q
399
- else:
400
- if topic in list(self._phy_subscriber.keys()):
401
- logger.warning(f"ar4 {self._box_mac} 订阅主题已存在: {topic}")
402
- return topic, self._phy_subscriber[topic]
403
- self._phy_subscriber[topic] = q
404
-
405
- return topic, q
406
-
407
- def _wrap_data_accept(self):
408
-
409
- @FuncAr4DataNotify
410
- def data_accept(handle, data_ptr):
411
- self._data_accept(data_ptr)
412
-
413
- return data_accept
414
- def _data_accept(self, data_ptr):
415
- # logger.info(f"_eeg_accept 被调用")
416
- # logger.debug(f'handle:{self}, data_ptr:{data_ptr}')
417
- # data = cast(data_ptr, POINTER(Ar4NotifyData)).contents
418
- if len(self._dig_subscriber) > 0 or len(self._phy_subscriber) > 0:
419
- packet = AR4Packet().transfer(data_ptr.contents)
420
-
421
- for consumer in self._dig_subscriber.values():
422
- consumer.put(packet)
423
-
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
- packet.eeg = self.eeg2phy(np.array(packet.eeg))
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}")
431
- for consumer2 in self._phy_subscriber.values():
432
- consumer2.put(packet)
433
-
434
- # logger.debug(f"EEG数据: {packet}")
435
-
436
- def eeg2phy(self, digtal):
437
- # 向量化计算(自动支持广播)
438
- return ((digtal - self._eeg_dig_min) / self._eeg_dig_range) * self._eeg_phy_range + self._eeg_phy_min
439
-
440
- def acc2phy(self, digtal):
441
- return ((digtal - self._acc_dig_min) / self._acc_dig_range) * self._acc_phy_range + self._acc_phy_min
442
- def _wrap_trigger_accept(self):
443
-
444
- @FuncAr4DataNotify
445
- def trigger_accept(handle, time_ms, trigger_type, trigger_value):
446
- self._trigger_accept(time_ms, trigger_type, trigger_value)
447
-
448
- return trigger_accept
449
-
450
- def _trigger_accept(self, time_ms, trigger_type, trigger_value):
451
- logger.info(f"_trigger_accept 被调用")
452
- logger.info(f"触发时间: {time_ms}, 触发类型: {trigger_type}, 触发值: {trigger_value}")
453
-
454
-
455
- def _wrap_connected_notify(self):
456
-
457
- @FuncAr4RecorderConnected
458
- def connected_notify(handle):
459
- self._connected_notify(handle)
460
-
461
- return connected_notify
462
-
463
- def _connected_notify(self, handle):
464
- logger.info(f"_connected_notify 被调用 handle: {handle}")
465
-
466
-
467
- def _wrap_disconnected_notify(self):
468
- @FuncAr4RecorderDisconnected
469
- def disconnected_notify(handle):
470
- self._disconnected_notify(handle)
471
-
472
- return disconnected_notify
473
-
474
- def _disconnected_notify(self, handle):
475
- logger.info(f"_disconnected_notify 被调用 handle: {handle}")
476
-
477
-
478
- def _wrap_box_connected_notify(self):
479
-
480
- @FuncAr4RecorderConnected
481
- def box_connected_notify(handle):
482
- self._box_connected_notify(handle)
483
-
484
- return box_connected_notify
485
-
486
- def _box_connected_notify(self, handle):
487
- logger.info(f"_box_connected_notify 被调用 handle: {handle}")
488
-
489
- def _wrap_box_disconnected_notify(self):
490
- @FuncAr4RecorderDisconnected
491
- def box_disconnected_notify(handle):
492
- self._box_disconnected_notify(handle)
493
-
494
- return box_disconnected_notify
495
-
496
- def _box_disconnected_notify(self, handle):
497
- logger.info(f"_box_disconnected_notify 被调用 handle: {handle}")
498
-
499
- def _wrap_start_record_notify(self):
500
-
501
- @FuncAr4RecorderConnected
502
- def start_record_notify(handle):
503
- self._start_record_notify(handle)
504
-
505
- return start_record_notify
506
-
507
- def _start_record_notify(self, handle):
508
- logger.info(f"_start_record_notify 被调用 handle: {handle}")
509
- self._recording = True
510
- self._record_start_time = time()
511
- logger.info(self)
512
-
513
-
514
- def _wrap_stop_record_notify(self):
515
- @FuncAr4RecorderDisconnected
516
- def stop_record_notify(handle):
517
- self._stop_record_notify(handle)
518
-
519
- return stop_record_notify
520
-
521
- def _stop_record_notify(self, handle):
522
- logger.info(f"_stop_record_notify 被调用 handle: {handle}")
523
- self._recording = False
524
-
525
- def __str__(self):
526
- return f"""
527
- box mac: {self._box_mac},
528
- box name: {self._box_name},
529
- box soc: {self._box_soc}
530
- head conn state: {self._head_conn_state}
531
- head mac: {self._head_mac},
532
- head soc: {self._head_soc}
533
- connected: {self._connected}
534
- connect time: {self._conn_time}
535
- last time: {self._last_time}
536
- [
537
- eeg phy max: {self._eeg_phy_max}
538
- eeg phy min: {self._eeg_phy_min}
539
- eeg dig max: {self._eeg_dig_max}
540
- eeg dig min: {self._eeg_dig_min}
541
- eeg phy unit: {self._eeg_phy_unit}
542
- ]
543
- [
544
- acc phy max: {self._acc_phy_max}
545
- acc phy min: {self._acc_phy_min}
546
- acc dig max: {self._acc_dig_max}
547
- acc dig min: {self._acc_dig_min}
548
- acc phy unit: {self._acc_phy_unit}
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}
964
- """
965
-
966
- class Packet(object):
967
- pass
968
-
969
- class AR4Packet(Packet):
970
- def __init__(self):
971
- self.time_stamp = None
972
- self.pkg_id = None
973
- self.notify_id = None
974
- self.eeg_ch_count = None
975
- self.eeg_count = None
976
- self.eeg = None
977
- self.acc_ch_count = None
978
- self.acc_count = None
979
- self.acc = None
980
-
981
- def transfer(self, data: Ar4NotifyData):
982
- self.time_stamp = data.time_stamp
983
- self.pkg_id = data.pkg_id
984
- self.notify_id = data.notify_id
985
- self.eeg_ch_count = data.eeg_ch_count
986
- self.eeg_count = data.eeg_count
987
- self.acc_ch_count = data.acc_ch_count
988
- self.acc_count = data.acc_count
989
- # 读eeg数据
990
- if self.eeg_ch_count and self.eeg_count:
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)]
992
- # 读acc数据
993
- if self.acc_ch_count and self.acc_count:
994
- self.acc = [[] for _ in range(self.acc_ch_count)]
995
- for i in range(self.acc_ch_count):
996
- self.acc[i] = [data.acc[j + (i * self.acc_count)] for j in range(self.acc_count)]
997
-
998
- return self
999
-
1000
- def __str__(self):
1001
- return f"""
1002
- time_stamp: {self.time_stamp}
1003
- pkg_id: {self.pkg_id}
1004
- notify_id: {self.notify_id}
1005
- eeg_ch_count: {self.eeg_ch_count}
1006
- eeg_count: {self.eeg_count}
1007
- acc_ch_count: {self.acc_ch_count}
1008
- acc_count: {self.acc_count}
1009
- eeg: {self.eeg}
1010
- acc: {self.acc}
1011
- """
qlsdk/ar4m/persist.py DELETED
@@ -1,178 +0,0 @@
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.channels = None
16
- self._cache = Queue()
17
- self.resolution = resolution
18
- self.sample_frequency = sample_frequency
19
- # bytes per second
20
- self.bytes_per_second = 0
21
- self._edf_writer = None
22
- self._cache2 = tuple()
23
- self._recording = False
24
- self._edf_writer = None
25
- self.annotations = None
26
- # 每个数据块大小
27
- self._chunk = np.array([])
28
- self._Lock = Lock()
29
- self._duration = 0
30
- self._points = 0
31
- self._first_pkg_id = None
32
- self._last_pkg_id = None
33
- self._first_timestamp = None
34
- self._end_time = None
35
- self._patient_code = "patient_code"
36
- self._patient_name = "patient_name"
37
- self._device_type = None
38
- self._total_packets = 0
39
- self._lost_packets = 0
40
- self._storage_path = storage_path
41
-
42
- @property
43
- def file_name(self):
44
- if self._storage_path:
45
- try:
46
- os.makedirs(self._storage_path, exist_ok=True) # 自动创建目录,存在则忽略
47
- return f"{self._storage_path}/{self._device_type}_{self._first_timestamp}.edf"
48
- except Exception as e:
49
- logger.error(f"创建目录[{self._storage_path}]失败: {e}")
50
-
51
- return f"{self._device_type}_{self._first_timestamp}.edf"
52
-
53
- @property
54
- def file_type(self):
55
- return FILETYPE_BDFPLUS if self.resolution == 24 else FILETYPE_EDFPLUS
56
-
57
- def set_device_type(self, device_type):
58
- self._device_type = device_type
59
-
60
- def set_storage_path(self, storage_path):
61
- self._storage_path = storage_path
62
-
63
- def set_patient_code(self, patient_code):
64
- self._patient_code = patient_code
65
-
66
- def set_patient_name(self, patient_name):
67
- self._patient_name = patient_name
68
-
69
- def append(self, data):
70
- if data:
71
- # 通道数
72
- if self._first_pkg_id is None:
73
- self.channels = data.eeg_ch_count
74
- self._first_pkg_id = data.pkg_id
75
- self._first_timestamp = data.time_stamp
76
-
77
- if self._last_pkg_id and self._last_pkg_id != data.pkg_id - 1:
78
- self._lost_packets += data.pkg_id - self._last_pkg_id - 1
79
- logger.warning(f"数据包丢失: {self._last_pkg_id} -> {data.pkg_id}, 丢包数: {data.pkg_id - self._last_pkg_id - 1}")
80
-
81
- self._last_pkg_id = data.pkg_id
82
- self._total_packets += 1
83
-
84
- # 数据
85
- self._cache.put(data)
86
- if not self._recording:
87
- self.start()
88
-
89
- def trigger(self, data):
90
- pass
91
-
92
- def start(self):
93
- self._recording = True
94
- record_thread = Thread(target=self._consumer)
95
- record_thread.start()
96
-
97
- def _consumer(self):
98
- logger.debug(f"开始消费数据 _consumer: {self._cache.qsize()}")
99
- while True:
100
- if self._recording or (not self._cache.empty()):
101
- data = self._cache.get()
102
- if data is None:
103
- break
104
- # 处理数据
105
- self._points += len(data.eeg[0])
106
- self._write_file(data.eeg)
107
- else:
108
- break
109
-
110
- self.close()
111
-
112
- def _write_file(self, data):
113
- try:
114
- if self._edf_writer is None:
115
- self.initialize_edf()
116
-
117
- if (self._chunk.size == 0):
118
- self._chunk = np.asarray(data)
119
- else:
120
- self._chunk = np.hstack((self._chunk, data))
121
-
122
- if self._chunk.size >= self.sample_frequency * self.channels:
123
- self._write_chunk(self._chunk[:self.sample_frequency])
124
- self._chunk = self._chunk[self.sample_frequency:]
125
-
126
- except Exception as e:
127
- logger.error(f"写入数据异常: {str(e)}")
128
-
129
- def close(self):
130
- self._recording = False
131
- if self._edf_writer:
132
- self._end_time = datetime.now().timestamp()
133
- self._edf_writer.writeAnnotation(0, 1, "start recording ")
134
- self._edf_writer.writeAnnotation(self._duration, 1, "recording end")
135
- self._edf_writer.close()
136
-
137
- logger.info(f"文件: {self.file_name}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
138
-
139
-
140
-
141
- def initialize_edf(self):
142
- # 创建EDF+写入器
143
- self._edf_writer = EdfWriter(
144
- self.file_name,
145
- self.channels,
146
- file_type=self.file_type
147
- )
148
-
149
- # 设置头信息
150
- self._edf_writer.setPatientCode(self._patient_code)
151
- self._edf_writer.setPatientName(self._patient_name)
152
- self._edf_writer.setEquipment(self._device_type)
153
- self._edf_writer.setStartdatetime(datetime.now())
154
-
155
- # 配置通道参数
156
- signal_headers = []
157
- for ch in range(self.channels):
158
- signal_headers.append({
159
- "label": f'channels {ch + 1}',
160
- "dimension": 'uV',
161
- "sample_frequency": self.sample_frequency,
162
- "physical_min": self.physical_min,
163
- "physical_max": self.physical_max,
164
- "digital_min": self.digital_min,
165
- "digital_max": self.digital_max
166
- })
167
-
168
- self._edf_writer.setSignalHeaders(signal_headers)
169
-
170
- def _write_chunk(self, chunk):
171
- logger.debug(f"写入数据: {chunk}")
172
- # 转换数据类型为float64(pyedflib要求)
173
- data_float64 = chunk.astype(np.float64)
174
- # 写入时转置为(样本数, 通道数)格式
175
- self._edf_writer.writeSamples(data_float64)
176
- self._duration += 1
177
-
178
-
File without changes
File without changes