qlsdk2 0.4.2__tar.gz → 0.5.0__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.
- qlsdk2-0.5.0/PKG-INFO +40 -0
- qlsdk2-0.5.0/README.md +12 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/setup.py +2 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/rsc_edf.py +21 -6
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/command/__init__.py +26 -22
- qlsdk2-0.5.0/src/qlsdk/rsc/device/__init__.py +7 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/arskindling.py +4 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/base.py +80 -38
- qlsdk2-0.5.0/src/qlsdk/rsc/device/c16_rs.py +205 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/c256_rs.py +1 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/c64_rs.py +3 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/c64s1.py +3 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/device_factory.py +1 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/device.py +3 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/manager/container.py +17 -18
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/network/discover.py +25 -36
- qlsdk2-0.5.0/src/qlsdk/rsc/parser/base.py +150 -0
- qlsdk2-0.5.0/src/qlsdk2.egg-info/PKG-INFO +40 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/SOURCES.txt +1 -0
- qlsdk2-0.4.2/PKG-INFO +0 -121
- qlsdk2-0.4.2/README.md +0 -93
- qlsdk2-0.4.2/src/qlsdk/rsc/device/__init__.py +0 -2
- qlsdk2-0.4.2/src/qlsdk/rsc/parser/base.py +0 -69
- qlsdk2-0.4.2/src/qlsdk2.egg-info/PKG-INFO +0 -121
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/setup.cfg +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/ar4/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/ar4m/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/crc/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/crc/crctools.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/device.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/entity/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/exception.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/filter/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/filter/norch.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/local.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/command.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/tcp.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/udp.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/network/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/network/monitor.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/utils.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/ars_edf.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/edf.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/stream.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/eegion.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/entity.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/command.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/handler.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/parser.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/manager/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/manager/search.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/network/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/paradigm.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/parser/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/proxy.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/ar4sdk.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/hub.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/x8/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/x8m/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/requires.txt +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/top_level.txt +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/test/test.222.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/test/test.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.0}/test/test_ar4m.py +0 -0
qlsdk2-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: qlsdk2
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: SDK for quanlan device
|
|
5
|
+
Home-page: https://github.com/hehuajun/qlsdk
|
|
6
|
+
Author: hehuajun
|
|
7
|
+
Author-email: hehuajun@eegion.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: loguru>=0.6.0
|
|
14
|
+
Requires-Dist: numpy>=1.23.5
|
|
15
|
+
Requires-Dist: bitarray>=1.5.3
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
18
|
+
Requires-Dist: twine>=3.0; extra == "dev"
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
版本:v0.5.0
|
|
30
|
+
时间:2025-07-29
|
|
31
|
+
[新特性]
|
|
32
|
+
1. C16R设备搜索
|
|
33
|
+
2、C16R设备连接
|
|
34
|
+
3、C16R信号采集/停止
|
|
35
|
+
4、C16R数据自动记录到文件
|
|
36
|
+
5、C16R采集通道设置支持数字和名称两种模式(可混用)
|
|
37
|
+
|
|
38
|
+
[优化]
|
|
39
|
+
1、提升信号接收及指令解析性能
|
|
40
|
+
2、日志级别及文案优化
|
qlsdk2-0.5.0/README.md
ADDED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# setup.py
|
|
2
2
|
import setuptools
|
|
3
3
|
|
|
4
|
-
with open("README.md", "r") as fh:
|
|
4
|
+
with open("README.md", "r", encoding='utf-8' ) as fh:
|
|
5
5
|
long_description = fh.read()
|
|
6
6
|
|
|
7
7
|
setuptools.setup(
|
|
8
8
|
name="qlsdk2",
|
|
9
|
-
version="0.
|
|
9
|
+
version="0.5.0",
|
|
10
10
|
author="hehuajun",
|
|
11
11
|
author_email="hehuajun@eegion.com",
|
|
12
12
|
description="SDK for quanlan device",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
import time
|
|
2
3
|
from multiprocessing import Lock, Queue
|
|
3
4
|
from time import time_ns
|
|
4
5
|
from pyedflib import FILETYPE_BDFPLUS, FILETYPE_EDFPLUS, EdfWriter
|
|
@@ -77,8 +78,9 @@ class EDFStreamWriter(Thread):
|
|
|
77
78
|
if self._writer is None:
|
|
78
79
|
self._init_writer()
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
waits = 300
|
|
82
|
+
while waits > 0:
|
|
83
|
+
if not self.data_queue.empty():
|
|
82
84
|
try:
|
|
83
85
|
data = self.data_queue.get(timeout=30)
|
|
84
86
|
if data is None:
|
|
@@ -88,13 +90,20 @@ class EDFStreamWriter(Thread):
|
|
|
88
90
|
self._points += len(data[1])
|
|
89
91
|
logger.trace(f"已处理数据点数:{self._points}")
|
|
90
92
|
self._write_file(data)
|
|
93
|
+
# 有数据重置计数器
|
|
94
|
+
waits = 100 # 重置等待计数器
|
|
91
95
|
except Exception as e:
|
|
92
96
|
logger.error(f"异常或超时(30s)结束: {str(e)}")
|
|
93
97
|
break
|
|
94
98
|
else:
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
time.sleep(0.1)
|
|
100
|
+
# 记录状态等待30s、非记录状态等待3s
|
|
101
|
+
if self._recording:
|
|
102
|
+
waits -= 1
|
|
103
|
+
else:
|
|
104
|
+
waits -= 10
|
|
97
105
|
|
|
106
|
+
logger.info(f"数据记录完成:{self.file_path}")
|
|
98
107
|
self.close()
|
|
99
108
|
|
|
100
109
|
def _init_writer(self):
|
|
@@ -159,6 +168,9 @@ class EDFStreamWriter(Thread):
|
|
|
159
168
|
# 写入时转置为(样本数, 通道数)格式
|
|
160
169
|
self._writer.writeSamples(data_float64)
|
|
161
170
|
self._duration += 1
|
|
171
|
+
|
|
172
|
+
if self._duration % 10 == 0: # 每10秒打印一次进度
|
|
173
|
+
logger.info(f"数据记录中... 文件名:{self.file_path}, 已记录时长: {self._duration}秒")
|
|
162
174
|
|
|
163
175
|
# 用作数据结构一致化处理,通过调用公共类写入edf文件
|
|
164
176
|
# 入参包含写入edf的全部前置参数
|
|
@@ -243,6 +255,8 @@ class RscEDFHandler(object):
|
|
|
243
255
|
self._device_type = "LJ64S1"
|
|
244
256
|
elif device_type == 0x60:
|
|
245
257
|
self._device_type = "ARSKindling"
|
|
258
|
+
elif device_type == 0x339:
|
|
259
|
+
self._device_type = "C16R"
|
|
246
260
|
else:
|
|
247
261
|
self._device_type = device_type
|
|
248
262
|
|
|
@@ -264,17 +278,18 @@ class RscEDFHandler(object):
|
|
|
264
278
|
def write(self, packet: RscPacket):
|
|
265
279
|
# logger.trace(f"packet: {packet}")
|
|
266
280
|
if packet is None:
|
|
281
|
+
logger.info(f"收到结束信号,即将停止写入数据:{self.file_name}")
|
|
267
282
|
self._edf_writer_thread.stop_recording()
|
|
268
283
|
return
|
|
269
284
|
|
|
270
285
|
with self._lock:
|
|
271
286
|
if self.channels is None:
|
|
272
|
-
logger.
|
|
287
|
+
logger.debug(f"开始记录数据到文件...")
|
|
273
288
|
self.channels = packet.channels
|
|
274
289
|
self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
|
|
275
290
|
self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
|
|
276
291
|
self._start_time = datetime.now()
|
|
277
|
-
logger.
|
|
292
|
+
logger.debug(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
|
|
278
293
|
|
|
279
294
|
if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
|
|
280
295
|
self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
|
|
@@ -51,15 +51,29 @@ class DeviceCommand(abc.ABC):
|
|
|
51
51
|
device_id = int(self.device.device_id) if self.device and self.device.device_id else 0
|
|
52
52
|
device_type = int(self.device.device_type) if self.device and self.device.device_type else 0
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
#兼容设计
|
|
55
|
+
b_device_type = None
|
|
56
|
+
if device_type > 0xFF:
|
|
57
|
+
b_device_type = int.to_bytes(device_type, 2, 'little')
|
|
58
|
+
|
|
59
|
+
if b_device_type is None:
|
|
60
|
+
return (
|
|
61
|
+
DeviceCommand.HEADER_PREFIX
|
|
62
|
+
+ int(2).to_bytes(1, 'little') # pkgType
|
|
63
|
+
+ device_type.to_bytes(1, 'little')
|
|
64
|
+
+ device_id.to_bytes(4, 'little')
|
|
65
|
+
+ (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
|
|
66
|
+
+ self.cmd_code.to_bytes(2, 'little')
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
return (
|
|
70
|
+
DeviceCommand.HEADER_PREFIX
|
|
71
|
+
+ b_device_type[0].to_bytes(1, 'little') # pkgType
|
|
72
|
+
+ b_device_type[1].to_bytes(1, 'little')
|
|
73
|
+
+ device_id.to_bytes(4, 'little')
|
|
74
|
+
+ (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
|
|
75
|
+
+ self.cmd_code.to_bytes(2, 'little')
|
|
76
|
+
)
|
|
63
77
|
|
|
64
78
|
def unpack(self, payload: bytes) -> bytes:
|
|
65
79
|
"""解析消息体"""
|
|
@@ -70,7 +84,7 @@ class DeviceCommand(abc.ABC):
|
|
|
70
84
|
time = int.from_bytes(body[0:8], 'little')
|
|
71
85
|
# result - 1B
|
|
72
86
|
result = body[8]
|
|
73
|
-
logger.
|
|
87
|
+
logger.debug(f"[{time}]{self.cmd_desc}{'成功' if result == 0 else '失败'}")
|
|
74
88
|
|
|
75
89
|
|
|
76
90
|
|
|
@@ -294,18 +308,8 @@ class SignalDataCommand(DeviceCommand):
|
|
|
294
308
|
# 解析数据包
|
|
295
309
|
packet = RscPacket()
|
|
296
310
|
packet.transfer(body)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if self.device.edf_handler:
|
|
300
|
-
self.device.edf_handler.write(packet)
|
|
301
|
-
|
|
302
|
-
if len(self.device.signal_consumers) > 0 :
|
|
303
|
-
# 信号数字值转物理值
|
|
304
|
-
packet.eeg = self.device.eeg2phy(np.array(packet.eeg))
|
|
305
|
-
|
|
306
|
-
# 发送数据包到订阅者
|
|
307
|
-
for q in list(self.device.signal_consumers.values()):
|
|
308
|
-
q.put(packet)
|
|
311
|
+
# 将数据包传递给设备
|
|
312
|
+
self.device.produce(packet)
|
|
309
313
|
|
|
310
314
|
|
|
311
315
|
|
|
@@ -167,7 +167,7 @@ class ARSKindling(QLBaseDevice):
|
|
|
167
167
|
self._edf_handler.set_device_type(self.device_type)
|
|
168
168
|
self._edf_handler.set_device_no(self.device_no)
|
|
169
169
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
170
|
-
self._edf_handler.set_file_prefix(self._file_prefix)
|
|
170
|
+
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'ARS')
|
|
171
171
|
|
|
172
172
|
@property
|
|
173
173
|
def edf_handler(self):
|
|
@@ -233,6 +233,8 @@ class ARSKindling(QLBaseDevice):
|
|
|
233
233
|
# 设置采集参数
|
|
234
234
|
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
235
235
|
self._acq_param["original_channels"] = channels
|
|
236
|
+
|
|
237
|
+
# 根据映射关系做通道转换
|
|
236
238
|
for k in channels.keys():
|
|
237
239
|
if isinstance(channels[k], list):
|
|
238
240
|
temp = [k + str(i) for i in channels[k]]
|
|
@@ -241,7 +243,7 @@ class ARSKindling(QLBaseDevice):
|
|
|
241
243
|
channels[k] = [k + str(channels[k])]
|
|
242
244
|
|
|
243
245
|
|
|
244
|
-
|
|
246
|
+
# 更新采集参数
|
|
245
247
|
self._acq_param["channels"] = channels
|
|
246
248
|
self._acq_param["sample_rate"] = sample_rate
|
|
247
249
|
self._acq_param["sample_range"] = sample_range
|
|
@@ -5,12 +5,13 @@ from time import sleep, time_ns
|
|
|
5
5
|
from typing import Any, Dict, Literal
|
|
6
6
|
|
|
7
7
|
from loguru import logger
|
|
8
|
+
import numpy as np
|
|
9
|
+
from qlsdk.core.entity import RscPacket
|
|
8
10
|
from qlsdk.core.utils import to_bytes
|
|
9
11
|
from qlsdk.persist import RscEDFHandler
|
|
10
12
|
from qlsdk.rsc.interface import IDevice, IParser
|
|
11
13
|
from qlsdk.rsc.parser import TcpMessageParser
|
|
12
14
|
from qlsdk.rsc.command import StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
|
|
13
|
-
# from qlsdk.rsc.command import *
|
|
14
15
|
|
|
15
16
|
class QLBaseDevice(IDevice):
|
|
16
17
|
def __init__(self, socket):
|
|
@@ -117,8 +118,8 @@ class QLBaseDevice(IDevice):
|
|
|
117
118
|
# 存储目录
|
|
118
119
|
|
|
119
120
|
#
|
|
120
|
-
self.
|
|
121
|
-
self.
|
|
121
|
+
self._signal_consumer: Dict[str, Queue[Any]]={}
|
|
122
|
+
self._impedance_consumer: Dict[str, Queue[Any]]={}
|
|
122
123
|
|
|
123
124
|
# EDF文件处理器
|
|
124
125
|
self._edf_handler = None
|
|
@@ -128,6 +129,38 @@ class QLBaseDevice(IDevice):
|
|
|
128
129
|
|
|
129
130
|
def parser(self) -> IParser:
|
|
130
131
|
return self._parser
|
|
132
|
+
|
|
133
|
+
# 数据包处理
|
|
134
|
+
def produce(self, data: RscPacket):
|
|
135
|
+
if data is None: return
|
|
136
|
+
|
|
137
|
+
# 处理信号数据
|
|
138
|
+
self._signal_wrapper(data)
|
|
139
|
+
|
|
140
|
+
# 存储
|
|
141
|
+
if self.storage_enable:
|
|
142
|
+
self._write_signal(data)
|
|
143
|
+
|
|
144
|
+
if len(self.signal_consumers) > 0 :
|
|
145
|
+
# 信号数字值转物理值
|
|
146
|
+
data.eeg = self.eeg2phy(np.array(data.eeg))
|
|
147
|
+
|
|
148
|
+
# 发送数据包到订阅者
|
|
149
|
+
for q in list(self.signal_consumers.values()):
|
|
150
|
+
q.put(data)
|
|
151
|
+
|
|
152
|
+
# 信号数据转换(默认不处理)
|
|
153
|
+
def _signal_wrapper(self, data: RscPacket):
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
def _write_signal(self, data: RscPacket):
|
|
157
|
+
# 文件写入到edf
|
|
158
|
+
if self._edf_handler is None:
|
|
159
|
+
logger.debug("Initializing EDF handler for data storage")
|
|
160
|
+
self.init_edf_handler()
|
|
161
|
+
|
|
162
|
+
if self._edf_handler:
|
|
163
|
+
self._edf_handler.write(data)
|
|
131
164
|
|
|
132
165
|
def start_listening(self):
|
|
133
166
|
|
|
@@ -142,7 +175,7 @@ class QLBaseDevice(IDevice):
|
|
|
142
175
|
return True
|
|
143
176
|
|
|
144
177
|
def stop_listening(self):
|
|
145
|
-
logger.
|
|
178
|
+
logger.trace(f"设备{self.device_no}停止socket监听")
|
|
146
179
|
self._listening = False
|
|
147
180
|
|
|
148
181
|
@property
|
|
@@ -152,15 +185,15 @@ class QLBaseDevice(IDevice):
|
|
|
152
185
|
def start_message_parser(self) -> None:
|
|
153
186
|
self._parser = TcpMessageParser(self)
|
|
154
187
|
self._parser.start()
|
|
155
|
-
logger.
|
|
188
|
+
logger.debug("TCP消息解析器已启动")
|
|
156
189
|
|
|
157
190
|
def start_message_listening(self) -> None:
|
|
158
191
|
def _accept():
|
|
159
192
|
while self._listening:
|
|
160
|
-
#
|
|
193
|
+
# 缓冲区4M
|
|
161
194
|
data = self.socket.recv(4096*1024)
|
|
162
195
|
if not data:
|
|
163
|
-
logger.warning(f"设备{self.device_name}连接结束")
|
|
196
|
+
logger.warning(f"设备[{self.device_name}]连接结束")
|
|
164
197
|
break
|
|
165
198
|
|
|
166
199
|
self._parser.append(data)
|
|
@@ -169,7 +202,7 @@ class QLBaseDevice(IDevice):
|
|
|
169
202
|
self._listening = True
|
|
170
203
|
message_accept = Thread(target=_accept, daemon=True)
|
|
171
204
|
message_accept.start()
|
|
172
|
-
logger.
|
|
205
|
+
logger.debug(f"socket消息监听已启动")
|
|
173
206
|
|
|
174
207
|
def set_device_type(self, type: int):
|
|
175
208
|
self._device_type = type
|
|
@@ -206,6 +239,7 @@ class QLBaseDevice(IDevice):
|
|
|
206
239
|
return self._digital_range
|
|
207
240
|
|
|
208
241
|
def init_edf_handler(self):
|
|
242
|
+
logger.warning("init_edf_handler not implemented in base class, should be overridden in subclass")
|
|
209
243
|
pass
|
|
210
244
|
# self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
|
|
211
245
|
# self._edf_handler.set_device_type(self.device_type)
|
|
@@ -221,6 +255,7 @@ class QLBaseDevice(IDevice):
|
|
|
221
255
|
@property
|
|
222
256
|
def edf_handler(self):
|
|
223
257
|
if not self.storage_enable:
|
|
258
|
+
logger.warning("EDF storage is disabled, no edf handler available")
|
|
224
259
|
return None
|
|
225
260
|
|
|
226
261
|
if self._edf_handler is None:
|
|
@@ -247,11 +282,11 @@ class QLBaseDevice(IDevice):
|
|
|
247
282
|
return 10
|
|
248
283
|
@property
|
|
249
284
|
def signal_consumers(self):
|
|
250
|
-
return self.
|
|
285
|
+
return self._signal_consumer
|
|
251
286
|
|
|
252
287
|
@property
|
|
253
288
|
def impedance_consumers(self):
|
|
254
|
-
return self.
|
|
289
|
+
return self._impedance_consumer
|
|
255
290
|
|
|
256
291
|
# 设置记录文件路径
|
|
257
292
|
def set_storage_path(self, path):
|
|
@@ -283,26 +318,26 @@ class QLBaseDevice(IDevice):
|
|
|
283
318
|
pass
|
|
284
319
|
|
|
285
320
|
def start_impedance(self):
|
|
286
|
-
logger.info("启动阻抗测量")
|
|
321
|
+
logger.info(f"[设备-{self.device_no}]启动阻抗测量")
|
|
287
322
|
msg = StartImpedanceCommand.build(self).pack()
|
|
288
|
-
logger.
|
|
323
|
+
logger.trace(f"start_impedance message is {msg.hex()}")
|
|
289
324
|
self.socket.sendall(msg)
|
|
290
325
|
|
|
291
326
|
def stop_impedance(self):
|
|
292
|
-
logger.info("停止阻抗测量")
|
|
327
|
+
logger.info(f"[设备{self.device_no}]停止阻抗测量")
|
|
293
328
|
msg = StopImpedanceCommand.build(self).pack()
|
|
294
|
-
logger.
|
|
329
|
+
logger.trace(f"stop_impedance message is {msg.hex()}")
|
|
295
330
|
self.socket.sendall(msg)
|
|
296
331
|
|
|
297
332
|
def start_stimulation(self):
|
|
298
333
|
if self.stim_paradigm is None:
|
|
299
334
|
logger.warning("刺激参数未设置,请先设置刺激参数")
|
|
300
335
|
return
|
|
301
|
-
logger.info("启动电刺激")
|
|
336
|
+
logger.info(f"[设备-{self.device_no}]启动电刺激")
|
|
302
337
|
msg = StartStimulationCommand.build(self).pack()
|
|
303
|
-
logger.
|
|
338
|
+
logger.trace(f"start_stimulation message is {msg.hex()}")
|
|
304
339
|
self.socket.sendall(msg)
|
|
305
|
-
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
|
|
340
|
+
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
|
|
306
341
|
t.start()
|
|
307
342
|
|
|
308
343
|
def _stop_stimulation_trigger(self, duration):
|
|
@@ -310,67 +345,74 @@ class QLBaseDevice(IDevice):
|
|
|
310
345
|
while delay > 0:
|
|
311
346
|
sleep(1)
|
|
312
347
|
delay -= 1
|
|
313
|
-
logger.
|
|
348
|
+
logger.debug(f"_stop_stimulation_trigger duration: {duration}")
|
|
314
349
|
if self._edf_handler:
|
|
315
350
|
self._edf_handler.trigger("stimulation should be stopped")
|
|
316
351
|
else:
|
|
317
352
|
logger.warning("stop stim trigger fail. no edf writer alive")
|
|
318
353
|
|
|
319
354
|
def stop_stimulation(self):
|
|
320
|
-
logger.info("停止电刺激")
|
|
355
|
+
logger.info(f"[设备-{self.device_no}]停止电刺激")
|
|
321
356
|
msg = StopStimulationCommand.pack()
|
|
322
|
-
logger.
|
|
357
|
+
logger.trace(f"stop_stimulation message is {msg.hex()}")
|
|
323
358
|
self.socket.sendall(msg)
|
|
324
359
|
|
|
325
360
|
# 启动采集
|
|
326
361
|
def start_acquisition(self, recording = True):
|
|
327
|
-
logger.info("启动信号采集")
|
|
362
|
+
logger.info(f"[设备-{self.device_no}]启动信号采集")
|
|
328
363
|
self._recording = recording
|
|
364
|
+
# 初始化EDF处理器
|
|
365
|
+
self.init_edf_handler()
|
|
329
366
|
# 设置数据采集参数
|
|
330
367
|
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
331
368
|
# 启动数据采集
|
|
332
369
|
start_bytes = StartAcquisitionCommand.build(self).pack()
|
|
333
370
|
msg = param_bytes + start_bytes
|
|
334
|
-
logger.
|
|
371
|
+
logger.trace(f"start_acquisition message is {msg.hex()}")
|
|
335
372
|
self.socket.sendall(msg)
|
|
336
373
|
|
|
337
374
|
# 停止采集
|
|
338
375
|
def stop_acquisition(self):
|
|
339
|
-
logger.info("停止信号采集")
|
|
376
|
+
logger.info(f"[设备-{self.device_no}]停止信号采集")
|
|
340
377
|
msg = StopAcquisitionCommand.build(self).pack()
|
|
341
|
-
logger.
|
|
378
|
+
logger.trace(f"stop_acquisition message is {msg.hex()}")
|
|
342
379
|
self.socket.sendall(msg)
|
|
343
380
|
if self._edf_handler:
|
|
344
381
|
# 发送结束标识
|
|
345
382
|
self.edf_handler.write(None)
|
|
346
383
|
|
|
347
|
-
|
|
384
|
+
'''
|
|
385
|
+
订阅数据
|
|
386
|
+
topic: 订阅主题
|
|
387
|
+
q: 数据队列
|
|
388
|
+
type: 数据类型,signal-信号数据,impedance-阻抗数据
|
|
389
|
+
'''
|
|
348
390
|
def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
|
|
349
391
|
|
|
350
|
-
|
|
351
|
-
|
|
392
|
+
# 队列名称
|
|
393
|
+
if topic is None:
|
|
394
|
+
topic = f"{self.device_no}_{type}_{time_ns()}"
|
|
395
|
+
|
|
396
|
+
logger.debug(f"[设备-{self.device_no}]订阅数据流: {topic}, type: {type}")
|
|
397
|
+
|
|
352
398
|
# 数据队列
|
|
353
399
|
if q is None:
|
|
354
400
|
q = Queue(maxsize=1000)
|
|
355
|
-
|
|
356
|
-
# 队列名称
|
|
357
|
-
if topic is None:
|
|
358
|
-
topic = f"{type}_{time_ns()}"
|
|
359
401
|
|
|
360
402
|
# 订阅生理电信号数据
|
|
361
403
|
if type == "signal":
|
|
362
404
|
# topic唯一,用来区分不同的订阅队列(下同)
|
|
363
|
-
if topic in list(self.
|
|
364
|
-
logger.warning(f"
|
|
405
|
+
if topic in list(self._signal_consumer.keys()):
|
|
406
|
+
logger.warning(f"已存在主题[{topic}]的信号数据订阅!")
|
|
365
407
|
else:
|
|
366
|
-
self.
|
|
408
|
+
self._signal_consumer[topic] = q
|
|
367
409
|
|
|
368
410
|
# 订阅阻抗数据
|
|
369
411
|
if type == "impedance":
|
|
370
|
-
if topic in list(self.
|
|
371
|
-
logger.warning(f"
|
|
412
|
+
if topic in list(self._impedance_consumer.keys()):
|
|
413
|
+
logger.warning(f"已存在主题[{topic}]的阻抗数据订阅!")
|
|
372
414
|
else:
|
|
373
|
-
self.
|
|
415
|
+
self._impedance_consumer[topic] = q
|
|
374
416
|
|
|
375
417
|
return topic, q
|
|
376
418
|
|
|
@@ -378,7 +420,7 @@ class QLBaseDevice(IDevice):
|
|
|
378
420
|
if self._edf_handler:
|
|
379
421
|
self.edf_handler.trigger(desc)
|
|
380
422
|
else:
|
|
381
|
-
logger.warning("
|
|
423
|
+
logger.warning("没有开启文件记录时,无法记录trigger信息")
|
|
382
424
|
|
|
383
425
|
def gen_set_acquirement_param(self) -> bytes:
|
|
384
426
|
|