qlsdk2 0.5.0__tar.gz → 0.5.1.1__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.1.1/PKG-INFO +62 -0
- qlsdk2-0.5.1.1/README.md +43 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/setup.py +2 -3
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/entity/__init__.py +8 -12
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/persist/rsc_edf.py +22 -8
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/command/__init__.py +6 -2
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/base.py +13 -2
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/c16_rs.py +51 -71
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/device_factory.py +1 -1
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/interface/device.py +11 -1
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/manager/container.py +13 -8
- qlsdk2-0.5.1.1/src/qlsdk/rsc/parser/base-new.py +135 -0
- qlsdk2-0.5.1.1/src/qlsdk/rsc/parser/base.py +157 -0
- qlsdk2-0.5.1.1/src/qlsdk2.egg-info/PKG-INFO +62 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk2.egg-info/SOURCES.txt +1 -0
- qlsdk2-0.5.0/PKG-INFO +0 -40
- qlsdk2-0.5.0/README.md +0 -12
- qlsdk2-0.5.0/src/qlsdk/rsc/parser/base.py +0 -150
- qlsdk2-0.5.0/src/qlsdk2.egg-info/PKG-INFO +0 -40
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/setup.cfg +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/ar4/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/ar4m/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/crc/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/crc/crctools.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/device.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/exception.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/filter/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/filter/norch.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/local.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/message/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/message/command.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/message/tcp.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/message/udp.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/network/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/network/monitor.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/core/utils.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/persist/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/persist/ars_edf.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/persist/edf.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/persist/stream.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/arskindling.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/c256_rs.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/c64_rs.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/device/c64s1.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/eegion.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/entity.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/interface/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/interface/command.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/interface/handler.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/interface/parser.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/manager/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/manager/search.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/network/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/network/discover.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/paradigm.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/parser/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/rsc/proxy.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/sdk/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/sdk/ar4sdk.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/sdk/hub.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/x8/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk/x8m/__init__.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk2.egg-info/requires.txt +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/src/qlsdk2.egg-info/top_level.txt +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/test/test.222.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/test/test.py +0 -0
- {qlsdk2-0.5.0 → qlsdk2-0.5.1.1}/test/test_ar4m.py +0 -0
qlsdk2-0.5.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qlsdk2
|
|
3
|
+
Version: 0.5.1.1
|
|
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 :: Microsoft :: Windows :: Windows 10
|
|
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
|
+
|
|
20
|
+
## **v0.5.1.1** (2025-08-24)
|
|
21
|
+
#### 🐛 Bug Fixed
|
|
22
|
+
- 修复仅选择一个通道时,自动保存功能异常的问题
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## **v0.5.1** (2025-08-04)
|
|
26
|
+
|
|
27
|
+
#### ⚙️ Changed
|
|
28
|
+
- C16R通道映射调整为按老的脑电帽位点映射(固件端做了映射)
|
|
29
|
+
|
|
30
|
+
- 开放底层参数,支持EDF/BDF文件中存储大量事件(>1个/秒)
|
|
31
|
+
|
|
32
|
+
- 优化实时信号数据订阅(发布/订阅模式),在调用者使用不规范时,多个订阅者之间不会互相干扰
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## **v0.5.0** (2025-07-29)
|
|
36
|
+
|
|
37
|
+
#### 🚀 Added
|
|
38
|
+
|
|
39
|
+
- *新增C16R搜索与连接*
|
|
40
|
+
支持C16R类型设备的搜索与连接
|
|
41
|
+
|
|
42
|
+
- *C16R信号采集/停止控制*
|
|
43
|
+
支持信号采集的参数配置
|
|
44
|
+
支持信号采集的启动与停止控制
|
|
45
|
+
|
|
46
|
+
- *C16R数据自动记录*
|
|
47
|
+
采集到的信号数据自动保存为bdf文件
|
|
48
|
+
|
|
49
|
+
- *C16R采集通道设置*
|
|
50
|
+
- 支持数字模式通道配置
|
|
51
|
+
- 支持名称模式通道配置
|
|
52
|
+
- 支持两种模式混合使用
|
|
53
|
+
|
|
54
|
+
#### ⚙️ Changed
|
|
55
|
+
|
|
56
|
+
- *性能提升*
|
|
57
|
+
- 信号接收效率优化
|
|
58
|
+
- 指令拆包方式优化
|
|
59
|
+
|
|
60
|
+
- *日志系统改进*
|
|
61
|
+
- 日志级别精细化调整,减少不必要的日志信息
|
|
62
|
+
- 日志文案清晰化
|
qlsdk2-0.5.1.1/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## **v0.5.1.1** (2025-08-24)
|
|
2
|
+
#### 🐛 Bug Fixed
|
|
3
|
+
- 修复仅选择一个通道时,自动保存功能异常的问题
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## **v0.5.1** (2025-08-04)
|
|
7
|
+
|
|
8
|
+
#### ⚙️ Changed
|
|
9
|
+
- C16R通道映射调整为按老的脑电帽位点映射(固件端做了映射)
|
|
10
|
+
|
|
11
|
+
- 开放底层参数,支持EDF/BDF文件中存储大量事件(>1个/秒)
|
|
12
|
+
|
|
13
|
+
- 优化实时信号数据订阅(发布/订阅模式),在调用者使用不规范时,多个订阅者之间不会互相干扰
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## **v0.5.0** (2025-07-29)
|
|
17
|
+
|
|
18
|
+
#### 🚀 Added
|
|
19
|
+
|
|
20
|
+
- *新增C16R搜索与连接*
|
|
21
|
+
支持C16R类型设备的搜索与连接
|
|
22
|
+
|
|
23
|
+
- *C16R信号采集/停止控制*
|
|
24
|
+
支持信号采集的参数配置
|
|
25
|
+
支持信号采集的启动与停止控制
|
|
26
|
+
|
|
27
|
+
- *C16R数据自动记录*
|
|
28
|
+
采集到的信号数据自动保存为bdf文件
|
|
29
|
+
|
|
30
|
+
- *C16R采集通道设置*
|
|
31
|
+
- 支持数字模式通道配置
|
|
32
|
+
- 支持名称模式通道配置
|
|
33
|
+
- 支持两种模式混合使用
|
|
34
|
+
|
|
35
|
+
#### ⚙️ Changed
|
|
36
|
+
|
|
37
|
+
- *性能提升*
|
|
38
|
+
- 信号接收效率优化
|
|
39
|
+
- 指令拆包方式优化
|
|
40
|
+
|
|
41
|
+
- *日志系统改进*
|
|
42
|
+
- 日志级别精细化调整,减少不必要的日志信息
|
|
43
|
+
- 日志文案清晰化
|
|
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8' ) as fh:
|
|
|
6
6
|
|
|
7
7
|
setuptools.setup(
|
|
8
8
|
name="qlsdk2",
|
|
9
|
-
version="0.5.
|
|
9
|
+
version="0.5.1.1",
|
|
10
10
|
author="hehuajun",
|
|
11
11
|
author_email="hehuajun@eegion.com",
|
|
12
12
|
description="SDK for quanlan device",
|
|
@@ -18,13 +18,12 @@ setuptools.setup(
|
|
|
18
18
|
classifiers=[
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"License :: OSI Approved :: MIT License",
|
|
21
|
-
"Operating System ::
|
|
21
|
+
"Operating System :: Microsoft :: Windows :: Windows 10",
|
|
22
22
|
],
|
|
23
23
|
python_requires='>=3.9',
|
|
24
24
|
install_requires=open("requirements.txt").read().splitlines(),
|
|
25
25
|
include_package_data=True,
|
|
26
26
|
package_data={
|
|
27
|
-
# "qlsdk2": ["/**/*.dll"],
|
|
28
27
|
"qlsdk": ["./**/*.dll"]
|
|
29
28
|
}
|
|
30
29
|
)
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
from qlsdk.core.utils import to_channels
|
|
2
2
|
from loguru import logger
|
|
3
3
|
|
|
4
|
-
class
|
|
5
|
-
def __init__(self, device_type, device_id, channels, data):
|
|
6
|
-
self.data = data
|
|
7
|
-
self.channels = None
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class RscPacket(object):
|
|
4
|
+
class Packet(object):
|
|
11
5
|
def __init__(self):
|
|
12
6
|
self.time_stamp = None
|
|
13
7
|
self.pkg_id = None
|
|
14
8
|
self.result = None
|
|
15
9
|
self.channels = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RscPacket(Packet):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
super().__init__()
|
|
16
15
|
self.origin_sample_rate = None
|
|
17
16
|
self.sample_rate = None
|
|
18
17
|
self.sample_num = None
|
|
@@ -65,12 +64,9 @@ class RscPacket(object):
|
|
|
65
64
|
eeg: {self.eeg}
|
|
66
65
|
"""
|
|
67
66
|
|
|
68
|
-
class ImpedancePacket(
|
|
67
|
+
class ImpedancePacket(Packet):
|
|
69
68
|
def __init__(self):
|
|
70
|
-
|
|
71
|
-
self.pkg_id = None
|
|
72
|
-
self.result = None
|
|
73
|
-
self.channels = None
|
|
69
|
+
super().__init__()
|
|
74
70
|
self.data_len = None
|
|
75
71
|
self.impedance = None
|
|
76
72
|
|
|
@@ -15,7 +15,8 @@ EDF_FILE_TYPE = {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
class EDFStreamWriter(Thread):
|
|
18
|
-
def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path):
|
|
18
|
+
def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path, record_duration=None):
|
|
19
|
+
|
|
19
20
|
super().__init__()
|
|
20
21
|
self._writer : EdfWriter = None
|
|
21
22
|
self.data_queue : Queue = Queue()
|
|
@@ -23,6 +24,8 @@ class EDFStreamWriter(Thread):
|
|
|
23
24
|
self._points = 0
|
|
24
25
|
self._duration = 0
|
|
25
26
|
self._buffer = None
|
|
27
|
+
# 设置edf/bdf文件参数,设置[0.001, 1)可以在1秒内记录多个事件(不建议开启)
|
|
28
|
+
self.record_duration = record_duration
|
|
26
29
|
|
|
27
30
|
# signal info
|
|
28
31
|
self._channels = channels
|
|
@@ -62,9 +65,10 @@ class EDFStreamWriter(Thread):
|
|
|
62
65
|
# 数据
|
|
63
66
|
self.data_queue.put(data)
|
|
64
67
|
|
|
65
|
-
def trigger(self, onset, desc):
|
|
68
|
+
def trigger(self, onset, desc: str):
|
|
66
69
|
if self._writer:
|
|
67
|
-
|
|
70
|
+
logger.trace(f"[{onset} : {desc}]")
|
|
71
|
+
self._writer.writeAnnotation(onset, -1, desc)
|
|
68
72
|
else:
|
|
69
73
|
logger.warning("未创建文件,无法写入Trigger标记")
|
|
70
74
|
|
|
@@ -87,7 +91,7 @@ class EDFStreamWriter(Thread):
|
|
|
87
91
|
logger.debug("收到结束信号,停止写入数据")
|
|
88
92
|
break
|
|
89
93
|
# 处理数据
|
|
90
|
-
self._points += len(data[
|
|
94
|
+
self._points += len(data[0])
|
|
91
95
|
logger.trace(f"已处理数据点数:{self._points}")
|
|
92
96
|
self._write_file(data)
|
|
93
97
|
# 有数据重置计数器
|
|
@@ -135,6 +139,8 @@ class EDFStreamWriter(Thread):
|
|
|
135
139
|
})
|
|
136
140
|
|
|
137
141
|
self._writer.setSignalHeaders(signal_headers)
|
|
142
|
+
if self.record_duration:
|
|
143
|
+
self._writer.setDatarecordDuration(self.record_duration) # 每个数据块1秒
|
|
138
144
|
|
|
139
145
|
def _write_file(self, eeg_data):
|
|
140
146
|
try:
|
|
@@ -189,7 +195,7 @@ class RscEDFHandler(object):
|
|
|
189
195
|
@author: qlsdk
|
|
190
196
|
@since: 0.4.0
|
|
191
197
|
'''
|
|
192
|
-
def __init__(self, eeg_sample_rate, physical_max, physical_min, resolution=24, storage_path = None):
|
|
198
|
+
def __init__(self, eeg_sample_rate, physical_max, physical_min, resolution=24, storage_path = None, record_duration=None):
|
|
193
199
|
# edf文件参数
|
|
194
200
|
self.physical_max = physical_max
|
|
195
201
|
self.physical_min = physical_min
|
|
@@ -225,6 +231,7 @@ class RscEDFHandler(object):
|
|
|
225
231
|
self._total_packets = 0
|
|
226
232
|
self._lost_packets = 0
|
|
227
233
|
self._storage_path = storage_path
|
|
234
|
+
self._record_duration = record_duration
|
|
228
235
|
self._edf_writer_thread = None
|
|
229
236
|
self._file_prefix = None
|
|
230
237
|
|
|
@@ -299,7 +306,7 @@ class RscEDFHandler(object):
|
|
|
299
306
|
self._total_packets += 1
|
|
300
307
|
|
|
301
308
|
if self._edf_writer_thread is None:
|
|
302
|
-
self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name)
|
|
309
|
+
self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name, self._record_duration)
|
|
303
310
|
self._edf_writer_thread.set_start_time(self._start_time)
|
|
304
311
|
self._edf_writer_thread.start()
|
|
305
312
|
logger.info(f"开始写入数据: {self.file_name}")
|
|
@@ -310,9 +317,16 @@ class RscEDFHandler(object):
|
|
|
310
317
|
# trigger标记
|
|
311
318
|
# desc: 标记内容
|
|
312
319
|
# cur_time: 设备时间时间戳,非设备发出的trigger不要设置
|
|
313
|
-
def trigger(self, desc, cur_time=None):
|
|
320
|
+
def trigger(self, desc: str, cur_time=None):
|
|
321
|
+
if self._edf_writer_thread is None:
|
|
322
|
+
logger.warning(f"File writing has not started, discarding trigger {desc}")
|
|
323
|
+
return
|
|
324
|
+
|
|
314
325
|
if cur_time is None:
|
|
315
|
-
|
|
326
|
+
# 计算trigger位置
|
|
327
|
+
if self._start_time:
|
|
328
|
+
onset = datetime.now().timestamp() - self._start_time.timestamp()
|
|
329
|
+
else: onset = 0
|
|
316
330
|
else:
|
|
317
331
|
onset = cur_time - self._first_timestamp
|
|
318
332
|
self._edf_writer_thread.trigger(onset, desc)
|
|
@@ -170,7 +170,7 @@ class QueryBatteryCommand(DeviceCommand):
|
|
|
170
170
|
self.device.battery_total = body[12]
|
|
171
171
|
# state - 1b
|
|
172
172
|
# state = body[13]
|
|
173
|
-
logger.
|
|
173
|
+
logger.trace(f"电量更新: {self.device}")
|
|
174
174
|
else:
|
|
175
175
|
logger.warning(f"QueryBatteryCommand message received but result is failed.")
|
|
176
176
|
|
|
@@ -214,6 +214,10 @@ class StartImpedanceCommand(DeviceCommand):
|
|
|
214
214
|
body += to_bytes(self.device.acq_channels)
|
|
215
215
|
body += bytes.fromhex('0000000000000000') # 8字节占位符
|
|
216
216
|
return body
|
|
217
|
+
|
|
218
|
+
def parse_body(self, body):
|
|
219
|
+
logger.info(f"Received StartImpedance response: {body.hex()}")
|
|
220
|
+
return super().parse_body(body)
|
|
217
221
|
|
|
218
222
|
|
|
219
223
|
# 停止阻抗测量
|
|
@@ -293,7 +297,7 @@ class ImpedanceDataCommand(DeviceCommand):
|
|
|
293
297
|
cmd_desc = "阻抗数据"
|
|
294
298
|
|
|
295
299
|
def parse_body(self, body: bytes):
|
|
296
|
-
logger.info(f"Received impedance data: {body.hex()}")
|
|
300
|
+
# logger.info(f"Received impedance data: {body.hex()}")
|
|
297
301
|
packet = ImpedancePacket().transfer(body)
|
|
298
302
|
|
|
299
303
|
# 信号数据
|
|
@@ -20,6 +20,9 @@ class QLBaseDevice(IDevice):
|
|
|
20
20
|
# 启动数据解析线程
|
|
21
21
|
self._parser: IParser = None
|
|
22
22
|
|
|
23
|
+
# 用在edf/bdf文件写入中,不建议使用
|
|
24
|
+
self._record_duration = None
|
|
25
|
+
|
|
23
26
|
# 设备信息
|
|
24
27
|
self.device_id = None
|
|
25
28
|
self.device_name = None
|
|
@@ -130,8 +133,11 @@ class QLBaseDevice(IDevice):
|
|
|
130
133
|
def parser(self) -> IParser:
|
|
131
134
|
return self._parser
|
|
132
135
|
|
|
136
|
+
def set_record_duration(self, record_duration: float):
|
|
137
|
+
self._record_duration = record_duration
|
|
138
|
+
|
|
133
139
|
# 数据包处理
|
|
134
|
-
def produce(self, data: RscPacket):
|
|
140
|
+
def produce(self, data: RscPacket, type:Literal['signal', 'impedance']="signal"):
|
|
135
141
|
if data is None: return
|
|
136
142
|
|
|
137
143
|
# 处理信号数据
|
|
@@ -147,6 +153,10 @@ class QLBaseDevice(IDevice):
|
|
|
147
153
|
|
|
148
154
|
# 发送数据包到订阅者
|
|
149
155
|
for q in list(self.signal_consumers.values()):
|
|
156
|
+
# 队列满了就丢弃最早的数据
|
|
157
|
+
if q.full():
|
|
158
|
+
q.get()
|
|
159
|
+
|
|
150
160
|
q.put(data)
|
|
151
161
|
|
|
152
162
|
# 信号数据转换(默认不处理)
|
|
@@ -176,7 +186,8 @@ class QLBaseDevice(IDevice):
|
|
|
176
186
|
|
|
177
187
|
def stop_listening(self):
|
|
178
188
|
logger.trace(f"设备{self.device_no}停止socket监听")
|
|
179
|
-
self._listening = False
|
|
189
|
+
self._listening = False
|
|
190
|
+
self._parser.stop()
|
|
180
191
|
|
|
181
192
|
@property
|
|
182
193
|
def device_type(self) -> int:
|
|
@@ -23,7 +23,8 @@ class C16RS(QLBaseDevice):
|
|
|
23
23
|
|
|
24
24
|
super().__init__(socket)
|
|
25
25
|
# 存储通道反向映射位置值
|
|
26
|
-
self._reverse_ch_pos = None
|
|
26
|
+
self._reverse_ch_pos = None
|
|
27
|
+
|
|
27
28
|
self.channel_name_mapping = {
|
|
28
29
|
"FP1": 1,
|
|
29
30
|
"FP2": 2,
|
|
@@ -45,79 +46,59 @@ class C16RS(QLBaseDevice):
|
|
|
45
46
|
"P3": 18,
|
|
46
47
|
"P7": 19,
|
|
47
48
|
"P4": 20,
|
|
48
|
-
"P8": 21
|
|
49
|
+
"P8": 21,
|
|
50
|
+
"T3": 8,
|
|
51
|
+
"T4": 9,
|
|
52
|
+
"A1": 10,
|
|
53
|
+
"A2": 11,
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
# self.channel_mapping = {
|
|
52
|
-
# "1": 61,
|
|
53
|
-
# "2": 62,
|
|
54
|
-
# "3": 63,
|
|
55
|
-
# "4": 64,
|
|
56
|
-
# "5": 65,
|
|
57
|
-
# "6": 66,
|
|
58
|
-
# "7": 68,
|
|
59
|
-
# "8": 67,
|
|
60
|
-
# "9": 53,
|
|
61
|
-
# "10": 54,
|
|
62
|
-
# "11": 55,
|
|
63
|
-
# "12": 57,
|
|
64
|
-
# "13": 59,
|
|
65
|
-
# "14": 58,
|
|
66
|
-
# "15": 60,
|
|
67
|
-
# "16": 56,
|
|
68
|
-
# "17": 26,
|
|
69
|
-
# "18": 25,
|
|
70
|
-
# "19": 24,
|
|
71
|
-
# "20": 23,
|
|
72
|
-
# "21": 22
|
|
73
|
-
# }
|
|
74
|
-
|
|
75
56
|
self.channel_mapping = {
|
|
76
|
-
"1":
|
|
77
|
-
"2":
|
|
78
|
-
"3":
|
|
79
|
-
"4":
|
|
80
|
-
"5":
|
|
81
|
-
"6":
|
|
82
|
-
"7":
|
|
83
|
-
"8":
|
|
84
|
-
"9":
|
|
85
|
-
"10":
|
|
86
|
-
"11":
|
|
87
|
-
"12":
|
|
88
|
-
"13":
|
|
89
|
-
"14":
|
|
90
|
-
"15":
|
|
91
|
-
"16":
|
|
92
|
-
"17":
|
|
93
|
-
"18":
|
|
94
|
-
"19":
|
|
95
|
-
"20":
|
|
96
|
-
"21":
|
|
57
|
+
"1": 59,
|
|
58
|
+
"2": 60,
|
|
59
|
+
"3": 53,
|
|
60
|
+
"4": 50,
|
|
61
|
+
"5": 51,
|
|
62
|
+
"6": 64,
|
|
63
|
+
"7": 49,
|
|
64
|
+
"8": 63,
|
|
65
|
+
"9": 24,
|
|
66
|
+
"10": 14,
|
|
67
|
+
"11": 10,
|
|
68
|
+
"12": 11,
|
|
69
|
+
"13": 57,
|
|
70
|
+
"14": 16,
|
|
71
|
+
"15": 55,
|
|
72
|
+
"16": 15,
|
|
73
|
+
"17": 40,
|
|
74
|
+
"18": 37,
|
|
75
|
+
"19": 34,
|
|
76
|
+
"20": 18,
|
|
77
|
+
"21": 47
|
|
97
78
|
}
|
|
98
79
|
|
|
99
80
|
self.channel_display_mapping = {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
81
|
+
59: 1,
|
|
82
|
+
60: 2,
|
|
83
|
+
53: 3,
|
|
84
|
+
50: 4,
|
|
85
|
+
51: 5,
|
|
86
|
+
64: 6,
|
|
87
|
+
49: 7,
|
|
88
|
+
63: 8,
|
|
89
|
+
24: 9,
|
|
90
|
+
14: 10,
|
|
91
|
+
10: 11,
|
|
92
|
+
11: 12,
|
|
93
|
+
57: 13,
|
|
94
|
+
16: 14,
|
|
95
|
+
55: 15,
|
|
96
|
+
15: 16,
|
|
97
|
+
40: 17,
|
|
98
|
+
37: 18,
|
|
99
|
+
34: 19,
|
|
100
|
+
18: 20,
|
|
101
|
+
47: 21
|
|
121
102
|
}
|
|
122
103
|
|
|
123
104
|
|
|
@@ -126,11 +107,10 @@ class C16RS(QLBaseDevice):
|
|
|
126
107
|
rlt = cls(parent.socket)
|
|
127
108
|
rlt.device_id = parent.device_id
|
|
128
109
|
rlt._device_no = parent.device_no
|
|
129
|
-
return rlt
|
|
130
|
-
|
|
110
|
+
return rlt
|
|
131
111
|
|
|
132
112
|
def init_edf_handler(self):
|
|
133
|
-
self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
|
|
113
|
+
self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution, record_duration = self._record_duration)
|
|
134
114
|
self._edf_handler.set_device_type(self.device_type)
|
|
135
115
|
self._edf_handler.set_device_no(self.device_no)
|
|
136
116
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
@@ -16,7 +16,7 @@ class DeviceFactory(object):
|
|
|
16
16
|
|
|
17
17
|
@classmethod
|
|
18
18
|
def create_device(cls, device: IDevice) -> Type[IDevice]:
|
|
19
|
-
logger.
|
|
19
|
+
logger.trace(f"Creating device for device type: {hex(device.device_type)}, support types: {cls._devices.keys()}")
|
|
20
20
|
if device.device_type not in cls._devices.keys():
|
|
21
21
|
logger.warning(f"不支持的设备类型: {hex(device.device_type)}")
|
|
22
22
|
raise ValueError(f"Unsupported device type: {hex(device.device_type)}")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
import abc
|
|
3
|
+
from typing import Literal
|
|
3
4
|
|
|
4
5
|
class IDevice(ABC):
|
|
5
6
|
|
|
@@ -11,11 +12,20 @@ class IDevice(ABC):
|
|
|
11
12
|
def set_device_type(self, value: int):
|
|
12
13
|
pass
|
|
13
14
|
|
|
15
|
+
def set_storage_path(self, path: str):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def set_file_prefix(self, pre: str):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
22
|
+
pass
|
|
23
|
+
|
|
14
24
|
@property
|
|
15
25
|
def device_no(self) -> str:
|
|
16
26
|
pass
|
|
17
27
|
|
|
18
|
-
def produce(self, data) -> None:
|
|
28
|
+
def produce(self, data, type:Literal['signal', 'impedance']="signal") -> None:
|
|
19
29
|
pass
|
|
20
30
|
|
|
21
31
|
def start_listening(self):
|
|
@@ -50,7 +50,7 @@ class DeviceContainer(object):
|
|
|
50
50
|
try:
|
|
51
51
|
# 绑定到所有接口的19216端口
|
|
52
52
|
tcp_socket.bind(('0.0.0.0', self._tcp_port))
|
|
53
|
-
tcp_socket.listen(
|
|
53
|
+
tcp_socket.listen(100)
|
|
54
54
|
logger.info(f"端口[{self._tcp_port}]监听服务已启动")
|
|
55
55
|
|
|
56
56
|
while True:
|
|
@@ -59,16 +59,21 @@ class DeviceContainer(object):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
# 为每个新连接创建线程处理
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
try:
|
|
63
|
+
client_handler = Thread(
|
|
64
|
+
target=self.client_handler,
|
|
65
|
+
args=(client_socket,),
|
|
66
|
+
daemon=False
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
client_handler.start()
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Handler Error: {e}")
|
|
69
72
|
|
|
70
73
|
except KeyboardInterrupt:
|
|
71
74
|
logger.error(f"端口[{self._tcp_port}]监听服务异常关闭")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"端口[{self._tcp_port}]监听服务异常: {e}")
|
|
72
77
|
finally:
|
|
73
78
|
logger.error(f"端口[{self._tcp_port}]监听服务关闭")
|
|
74
79
|
tcp_socket.close()
|