qlsdk2 0.4.2__tar.gz → 0.5.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/PKG-INFO +57 -0
- qlsdk2-0.5.1/README.md +38 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/setup.py +3 -4
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/entity/__init__.py +8 -12
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/persist/rsc_edf.py +42 -13
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/command/__init__.py +32 -24
- qlsdk2-0.5.1/src/qlsdk/rsc/device/__init__.py +7 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/device/arskindling.py +4 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/device/base.py +92 -39
- qlsdk2-0.5.1/src/qlsdk/rsc/device/c16_rs.py +185 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/device/c256_rs.py +1 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/device/c64_rs.py +3 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/device/c64s1.py +3 -2
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/device/device_factory.py +2 -1
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/interface/device.py +13 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/manager/container.py +27 -23
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/network/discover.py +25 -36
- qlsdk2-0.5.1/src/qlsdk/rsc/parser/base-new.py +135 -0
- qlsdk2-0.5.1/src/qlsdk/rsc/parser/base.py +157 -0
- qlsdk2-0.5.1/src/qlsdk2.egg-info/PKG-INFO +57 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk2.egg-info/SOURCES.txt +2 -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.1}/setup.cfg +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/ar4/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/ar4m/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/crc/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/crc/crctools.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/device.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/exception.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/filter/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/filter/norch.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/local.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/message/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/message/command.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/message/tcp.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/message/udp.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/network/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/network/monitor.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/core/utils.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/persist/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/persist/ars_edf.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/persist/edf.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/persist/stream.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/eegion.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/entity.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/interface/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/interface/command.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/interface/handler.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/interface/parser.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/manager/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/manager/search.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/network/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/paradigm.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/parser/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/rsc/proxy.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/sdk/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/sdk/ar4sdk.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/sdk/hub.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/x8/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk/x8m/__init__.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk2.egg-info/requires.txt +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/src/qlsdk2.egg-info/top_level.txt +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/test/test.222.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/test/test.py +0 -0
- {qlsdk2-0.4.2 → qlsdk2-0.5.1}/test/test_ar4m.py +0 -0
qlsdk2-0.5.1/PKG-INFO
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qlsdk2
|
|
3
|
+
Version: 0.5.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 (2025-08-04)
|
|
21
|
+
|
|
22
|
+
## ⚙️ 优化
|
|
23
|
+
#### 修复C16R通道映射的问题
|
|
24
|
+
|
|
25
|
+
#### 支持设置记录参数,在需要保存大量事件时使用
|
|
26
|
+
|
|
27
|
+
#### 订阅队列满的时候,丢弃最早的数据
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# 版本 v0.5.0 (2025-07-29)
|
|
31
|
+
|
|
32
|
+
## 🚀 新特性
|
|
33
|
+
|
|
34
|
+
1. **C16R设备连接**
|
|
35
|
+
支持C16R类型设备的搜索与连接
|
|
36
|
+
|
|
37
|
+
2. **C16R信号采集/停止控制**
|
|
38
|
+
支持信号采集的参数配置
|
|
39
|
+
支持信号采集的启动与停止控制
|
|
40
|
+
|
|
41
|
+
3. **C16R数据自动记录**
|
|
42
|
+
采集到的信号数据自动保存为bdf文件
|
|
43
|
+
|
|
44
|
+
4. **C16R采集通道设置**
|
|
45
|
+
- 支持数字模式通道配置
|
|
46
|
+
- 支持名称模式通道配置
|
|
47
|
+
- 支持两种模式混合使用
|
|
48
|
+
|
|
49
|
+
## ⚙️ 优化
|
|
50
|
+
|
|
51
|
+
1. **性能提升**
|
|
52
|
+
- 信号接收效率优化
|
|
53
|
+
- 指令拆包方式优化
|
|
54
|
+
|
|
55
|
+
2. **日志系统改进**
|
|
56
|
+
- 日志级别精细化调整,减少不必要的日志信息
|
|
57
|
+
- 日志文案清晰化
|
qlsdk2-0.5.1/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 版本 v0.5.1 (2025-08-04)
|
|
2
|
+
|
|
3
|
+
## ⚙️ 优化
|
|
4
|
+
#### 修复C16R通道映射的问题
|
|
5
|
+
|
|
6
|
+
#### 支持设置记录参数,在需要保存大量事件时使用
|
|
7
|
+
|
|
8
|
+
#### 订阅队列满的时候,丢弃最早的数据
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# 版本 v0.5.0 (2025-07-29)
|
|
12
|
+
|
|
13
|
+
## 🚀 新特性
|
|
14
|
+
|
|
15
|
+
1. **C16R设备连接**
|
|
16
|
+
支持C16R类型设备的搜索与连接
|
|
17
|
+
|
|
18
|
+
2. **C16R信号采集/停止控制**
|
|
19
|
+
支持信号采集的参数配置
|
|
20
|
+
支持信号采集的启动与停止控制
|
|
21
|
+
|
|
22
|
+
3. **C16R数据自动记录**
|
|
23
|
+
采集到的信号数据自动保存为bdf文件
|
|
24
|
+
|
|
25
|
+
4. **C16R采集通道设置**
|
|
26
|
+
- 支持数字模式通道配置
|
|
27
|
+
- 支持名称模式通道配置
|
|
28
|
+
- 支持两种模式混合使用
|
|
29
|
+
|
|
30
|
+
## ⚙️ 优化
|
|
31
|
+
|
|
32
|
+
1. **性能提升**
|
|
33
|
+
- 信号接收效率优化
|
|
34
|
+
- 指令拆包方式优化
|
|
35
|
+
|
|
36
|
+
2. **日志系统改进**
|
|
37
|
+
- 日志级别精细化调整,减少不必要的日志信息
|
|
38
|
+
- 日志文案清晰化
|
|
@@ -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.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
|
|
|
@@ -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
|
|
@@ -14,7 +15,8 @@ EDF_FILE_TYPE = {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
class EDFStreamWriter(Thread):
|
|
17
|
-
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
|
+
|
|
18
20
|
super().__init__()
|
|
19
21
|
self._writer : EdfWriter = None
|
|
20
22
|
self.data_queue : Queue = Queue()
|
|
@@ -22,6 +24,8 @@ class EDFStreamWriter(Thread):
|
|
|
22
24
|
self._points = 0
|
|
23
25
|
self._duration = 0
|
|
24
26
|
self._buffer = None
|
|
27
|
+
# 设置edf/bdf文件参数,设置[0.001, 1)可以在1秒内记录多个事件(不建议开启)
|
|
28
|
+
self.record_duration = record_duration
|
|
25
29
|
|
|
26
30
|
# signal info
|
|
27
31
|
self._channels = channels
|
|
@@ -61,9 +65,10 @@ class EDFStreamWriter(Thread):
|
|
|
61
65
|
# 数据
|
|
62
66
|
self.data_queue.put(data)
|
|
63
67
|
|
|
64
|
-
def trigger(self, onset, desc):
|
|
68
|
+
def trigger(self, onset, desc: str):
|
|
65
69
|
if self._writer:
|
|
66
|
-
|
|
70
|
+
logger.trace(f"[{onset} : {desc}]")
|
|
71
|
+
self._writer.writeAnnotation(onset, -1, desc)
|
|
67
72
|
else:
|
|
68
73
|
logger.warning("未创建文件,无法写入Trigger标记")
|
|
69
74
|
|
|
@@ -77,8 +82,9 @@ class EDFStreamWriter(Thread):
|
|
|
77
82
|
if self._writer is None:
|
|
78
83
|
self._init_writer()
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
waits = 300
|
|
86
|
+
while waits > 0:
|
|
87
|
+
if not self.data_queue.empty():
|
|
82
88
|
try:
|
|
83
89
|
data = self.data_queue.get(timeout=30)
|
|
84
90
|
if data is None:
|
|
@@ -88,13 +94,20 @@ class EDFStreamWriter(Thread):
|
|
|
88
94
|
self._points += len(data[1])
|
|
89
95
|
logger.trace(f"已处理数据点数:{self._points}")
|
|
90
96
|
self._write_file(data)
|
|
97
|
+
# 有数据重置计数器
|
|
98
|
+
waits = 100 # 重置等待计数器
|
|
91
99
|
except Exception as e:
|
|
92
100
|
logger.error(f"异常或超时(30s)结束: {str(e)}")
|
|
93
101
|
break
|
|
94
102
|
else:
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
time.sleep(0.1)
|
|
104
|
+
# 记录状态等待30s、非记录状态等待3s
|
|
105
|
+
if self._recording:
|
|
106
|
+
waits -= 1
|
|
107
|
+
else:
|
|
108
|
+
waits -= 10
|
|
97
109
|
|
|
110
|
+
logger.info(f"数据记录完成:{self.file_path}")
|
|
98
111
|
self.close()
|
|
99
112
|
|
|
100
113
|
def _init_writer(self):
|
|
@@ -126,6 +139,8 @@ class EDFStreamWriter(Thread):
|
|
|
126
139
|
})
|
|
127
140
|
|
|
128
141
|
self._writer.setSignalHeaders(signal_headers)
|
|
142
|
+
if self.record_duration:
|
|
143
|
+
self._writer.setDatarecordDuration(self.record_duration) # 每个数据块1秒
|
|
129
144
|
|
|
130
145
|
def _write_file(self, eeg_data):
|
|
131
146
|
try:
|
|
@@ -159,6 +174,9 @@ class EDFStreamWriter(Thread):
|
|
|
159
174
|
# 写入时转置为(样本数, 通道数)格式
|
|
160
175
|
self._writer.writeSamples(data_float64)
|
|
161
176
|
self._duration += 1
|
|
177
|
+
|
|
178
|
+
if self._duration % 10 == 0: # 每10秒打印一次进度
|
|
179
|
+
logger.info(f"数据记录中... 文件名:{self.file_path}, 已记录时长: {self._duration}秒")
|
|
162
180
|
|
|
163
181
|
# 用作数据结构一致化处理,通过调用公共类写入edf文件
|
|
164
182
|
# 入参包含写入edf的全部前置参数
|
|
@@ -177,7 +195,7 @@ class RscEDFHandler(object):
|
|
|
177
195
|
@author: qlsdk
|
|
178
196
|
@since: 0.4.0
|
|
179
197
|
'''
|
|
180
|
-
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):
|
|
181
199
|
# edf文件参数
|
|
182
200
|
self.physical_max = physical_max
|
|
183
201
|
self.physical_min = physical_min
|
|
@@ -213,6 +231,7 @@ class RscEDFHandler(object):
|
|
|
213
231
|
self._total_packets = 0
|
|
214
232
|
self._lost_packets = 0
|
|
215
233
|
self._storage_path = storage_path
|
|
234
|
+
self._record_duration = record_duration
|
|
216
235
|
self._edf_writer_thread = None
|
|
217
236
|
self._file_prefix = None
|
|
218
237
|
|
|
@@ -243,6 +262,8 @@ class RscEDFHandler(object):
|
|
|
243
262
|
self._device_type = "LJ64S1"
|
|
244
263
|
elif device_type == 0x60:
|
|
245
264
|
self._device_type = "ARSKindling"
|
|
265
|
+
elif device_type == 0x339:
|
|
266
|
+
self._device_type = "C16R"
|
|
246
267
|
else:
|
|
247
268
|
self._device_type = device_type
|
|
248
269
|
|
|
@@ -264,17 +285,18 @@ class RscEDFHandler(object):
|
|
|
264
285
|
def write(self, packet: RscPacket):
|
|
265
286
|
# logger.trace(f"packet: {packet}")
|
|
266
287
|
if packet is None:
|
|
288
|
+
logger.info(f"收到结束信号,即将停止写入数据:{self.file_name}")
|
|
267
289
|
self._edf_writer_thread.stop_recording()
|
|
268
290
|
return
|
|
269
291
|
|
|
270
292
|
with self._lock:
|
|
271
293
|
if self.channels is None:
|
|
272
|
-
logger.
|
|
294
|
+
logger.debug(f"开始记录数据到文件...")
|
|
273
295
|
self.channels = packet.channels
|
|
274
296
|
self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
|
|
275
297
|
self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
|
|
276
298
|
self._start_time = datetime.now()
|
|
277
|
-
logger.
|
|
299
|
+
logger.debug(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
|
|
278
300
|
|
|
279
301
|
if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
|
|
280
302
|
self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
|
|
@@ -284,7 +306,7 @@ class RscEDFHandler(object):
|
|
|
284
306
|
self._total_packets += 1
|
|
285
307
|
|
|
286
308
|
if self._edf_writer_thread is None:
|
|
287
|
-
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)
|
|
288
310
|
self._edf_writer_thread.set_start_time(self._start_time)
|
|
289
311
|
self._edf_writer_thread.start()
|
|
290
312
|
logger.info(f"开始写入数据: {self.file_name}")
|
|
@@ -295,9 +317,16 @@ class RscEDFHandler(object):
|
|
|
295
317
|
# trigger标记
|
|
296
318
|
# desc: 标记内容
|
|
297
319
|
# cur_time: 设备时间时间戳,非设备发出的trigger不要设置
|
|
298
|
-
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
|
+
|
|
299
325
|
if cur_time is None:
|
|
300
|
-
|
|
326
|
+
# 计算trigger位置
|
|
327
|
+
if self._start_time:
|
|
328
|
+
onset = datetime.now().timestamp() - self._start_time.timestamp()
|
|
329
|
+
else: onset = 0
|
|
301
330
|
else:
|
|
302
331
|
onset = cur_time - self._first_timestamp
|
|
303
332
|
self._edf_writer_thread.trigger(onset, desc)
|
|
@@ -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
|
|
|
@@ -156,7 +170,7 @@ class QueryBatteryCommand(DeviceCommand):
|
|
|
156
170
|
self.device.battery_total = body[12]
|
|
157
171
|
# state - 1b
|
|
158
172
|
# state = body[13]
|
|
159
|
-
logger.
|
|
173
|
+
logger.trace(f"电量更新: {self.device}")
|
|
160
174
|
else:
|
|
161
175
|
logger.warning(f"QueryBatteryCommand message received but result is failed.")
|
|
162
176
|
|
|
@@ -200,6 +214,10 @@ class StartImpedanceCommand(DeviceCommand):
|
|
|
200
214
|
body += to_bytes(self.device.acq_channels)
|
|
201
215
|
body += bytes.fromhex('0000000000000000') # 8字节占位符
|
|
202
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)
|
|
203
221
|
|
|
204
222
|
|
|
205
223
|
# 停止阻抗测量
|
|
@@ -279,7 +297,7 @@ class ImpedanceDataCommand(DeviceCommand):
|
|
|
279
297
|
cmd_desc = "阻抗数据"
|
|
280
298
|
|
|
281
299
|
def parse_body(self, body: bytes):
|
|
282
|
-
logger.info(f"Received impedance data: {body.hex()}")
|
|
300
|
+
# logger.info(f"Received impedance data: {body.hex()}")
|
|
283
301
|
packet = ImpedancePacket().transfer(body)
|
|
284
302
|
|
|
285
303
|
# 信号数据
|
|
@@ -294,18 +312,8 @@ class SignalDataCommand(DeviceCommand):
|
|
|
294
312
|
# 解析数据包
|
|
295
313
|
packet = RscPacket()
|
|
296
314
|
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)
|
|
315
|
+
# 将数据包传递给设备
|
|
316
|
+
self.device.produce(packet)
|
|
309
317
|
|
|
310
318
|
|
|
311
319
|
|
|
@@ -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
|