qlsdk2 0.5.1.1__tar.gz → 0.6.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/PKG-INFO +11 -2
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/setup.py +1 -1
- qlsdk2-0.6.0a1/src/qlsdk/core/entity/__init__.py +164 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/message/udp.py +9 -1
- qlsdk2-0.6.0a1/src/qlsdk/entity/message.py +0 -0
- qlsdk2-0.6.0a1/src/qlsdk/entity/signal.py +0 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/__init__.py +10 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/analyzer.py +2 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/collector.py +10 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/device.py +2 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/parser.py +13 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/stimulator.py +2 -0
- qlsdk2-0.6.0a1/src/qlsdk/interface/store.py +2 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/persist/rsc_edf.py +2 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/command/__init__.py +4 -4
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/__init__.py +2 -1
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/base.py +60 -27
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/c256_rs.py +25 -32
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/device_factory.py +1 -0
- qlsdk2-0.6.0a1/src/qlsdk/rsc/interface/device.py +64 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/interface/parser.py +6 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/manager/container.py +10 -2
- qlsdk2-0.6.0a1/src/qlsdk/rsc/manager/search.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/network/discover.py +2 -0
- qlsdk2-0.6.0a1/src/qlsdk/rsc/parser/__init__.py +2 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/parser/base.py +1 -1
- qlsdk2-0.6.0a1/src/qlsdk/rsc/parser/rsc.py +130 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk2.egg-info/PKG-INFO +11 -2
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk2.egg-info/SOURCES.txt +11 -0
- qlsdk2-0.5.1.1/src/qlsdk/core/entity/__init__.py +0 -89
- qlsdk2-0.5.1.1/src/qlsdk/rsc/interface/device.py +0 -127
- qlsdk2-0.5.1.1/src/qlsdk/rsc/parser/__init__.py +0 -1
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/README.md +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/setup.cfg +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/ar4/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/ar4m/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/crc/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/crc/crctools.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/device.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/exception.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/filter/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/filter/norch.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/local.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/message/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/message/command.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/message/tcp.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/network/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/network/monitor.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/core/utils.py +0 -0
- /qlsdk2-0.5.1.1/src/qlsdk/rsc/manager/search.py → /qlsdk2-0.6.0a1/src/qlsdk/entity/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/persist/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/persist/ars_edf.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/persist/edf.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/persist/stream.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/arskindling.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/c16_rs.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/c64_rs.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/device/c64s1.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/eegion.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/entity.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/interface/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/interface/command.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/interface/handler.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/manager/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/network/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/paradigm.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/parser/base-new.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/rsc/proxy.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/sdk/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/sdk/ar4sdk.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/sdk/hub.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/x8/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk/x8m/__init__.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk2.egg-info/requires.txt +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/src/qlsdk2.egg-info/top_level.txt +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/test/test.222.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/test/test.py +0 -0
- {qlsdk2-0.5.1.1 → qlsdk2-0.6.0a1}/test/test_ar4m.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qlsdk2
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0a1
|
|
4
4
|
Summary: SDK for quanlan device
|
|
5
5
|
Home-page: https://github.com/hehuajun/qlsdk
|
|
6
6
|
Author: hehuajun
|
|
@@ -16,6 +16,15 @@ Requires-Dist: bitarray>=1.5.3
|
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
18
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
|
|
19
28
|
|
|
20
29
|
## **v0.5.1.1** (2025-08-24)
|
|
21
30
|
#### 🐛 Bug Fixed
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from qlsdk.core.utils import to_channels
|
|
2
|
+
from loguru import logger
|
|
3
|
+
|
|
4
|
+
class Packet(object):
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.time_stamp = None
|
|
7
|
+
self.pkg_id = None
|
|
8
|
+
self.result = None
|
|
9
|
+
self.channels = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RscPacket(Packet):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.origin_sample_rate = None
|
|
16
|
+
self.sample_rate = None
|
|
17
|
+
self.sample_num = None
|
|
18
|
+
self.resolution = None
|
|
19
|
+
self.filter = None
|
|
20
|
+
self.data_len = None
|
|
21
|
+
self.trigger = None
|
|
22
|
+
self.eeg = None
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def transfer(body: bytes) -> 'RscPacket':
|
|
26
|
+
packet = RscPacket()
|
|
27
|
+
packet.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
28
|
+
packet.result = body[8]
|
|
29
|
+
packet.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
30
|
+
logger.trace(f"pkg_id: {packet.pkg_id}")
|
|
31
|
+
packet.channels = to_channels(body[13: 45])
|
|
32
|
+
packet.origin_sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
33
|
+
packet.sample_rate = int.from_bytes(body[49: 53], 'little')
|
|
34
|
+
packet.sample_num = int.from_bytes(body[53: 57], 'little')
|
|
35
|
+
packet.resolution = int(int(body[57]) / 8)
|
|
36
|
+
packet.filter = body[58]
|
|
37
|
+
packet.data_len = int.from_bytes(body[59: 63], 'little')
|
|
38
|
+
# 步径 相同通道的点间隔
|
|
39
|
+
step = int(len(packet.channels) * packet.resolution + 4)
|
|
40
|
+
b_eeg = body[63:]
|
|
41
|
+
ch_num = len(packet.channels)
|
|
42
|
+
# 字节序列(4Cn{channel_size}){sample_num})
|
|
43
|
+
packet.trigger = [int.from_bytes(b_eeg[j * step : j * step + 4], 'little', signed=False) for j in range(packet.sample_num)]
|
|
44
|
+
|
|
45
|
+
packet.eeg = [
|
|
46
|
+
[
|
|
47
|
+
int.from_bytes(b_eeg[i * packet.resolution + 4 + j * step:i * packet.resolution + 4 + j * step + 3], 'big', signed=True)
|
|
48
|
+
for j in range(packet.sample_num)
|
|
49
|
+
]
|
|
50
|
+
for i in range(ch_num)
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# logger.trace(self)
|
|
54
|
+
return packet
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
return f"""
|
|
58
|
+
time_stamp: {self.time_stamp}
|
|
59
|
+
pkg_id: {self.pkg_id}
|
|
60
|
+
origin_sample_rate: {self.origin_sample_rate}
|
|
61
|
+
sample_rate: {self.sample_rate}
|
|
62
|
+
sample_num: {self.sample_num}
|
|
63
|
+
resolution: {self.resolution}
|
|
64
|
+
filter: {self.filter}
|
|
65
|
+
channels: {self.channels}
|
|
66
|
+
data len: {self.data_len}
|
|
67
|
+
trigger: {self.trigger}
|
|
68
|
+
eeg: {self.eeg}
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
class ImpedancePacket(Packet):
|
|
72
|
+
def __init__(self):
|
|
73
|
+
super().__init__()
|
|
74
|
+
self.data_len = None
|
|
75
|
+
self.impedance = None
|
|
76
|
+
|
|
77
|
+
def transfer(self, body:bytes) -> 'ImpedancePacket':
|
|
78
|
+
self.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
79
|
+
self.result = body[8]
|
|
80
|
+
self.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
81
|
+
self.channels = to_channels(body[13: 45])
|
|
82
|
+
|
|
83
|
+
logger.debug(f"impedance: {self}")
|
|
84
|
+
|
|
85
|
+
def __str__(self):
|
|
86
|
+
return f"""
|
|
87
|
+
time_stamp: {self.time_stamp}
|
|
88
|
+
pkg_id: {self.pkg_id}
|
|
89
|
+
result: {self.result}
|
|
90
|
+
channels: {self.channels}
|
|
91
|
+
data len: {self.data_len}
|
|
92
|
+
impedance: {self.impedance}
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class C256RSPacket(Packet):
|
|
97
|
+
def __init__(self):
|
|
98
|
+
super().__init__()
|
|
99
|
+
self.origin_sample_rate = None
|
|
100
|
+
self.sample_rate = None
|
|
101
|
+
self.sample_num = None
|
|
102
|
+
self.resolution = None
|
|
103
|
+
self.filter = None
|
|
104
|
+
self.data_len = None
|
|
105
|
+
self.trigger = None
|
|
106
|
+
self.eeg = None
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def transfer(body: bytes) -> 'RscPacket':
|
|
110
|
+
packet = RscPacket()
|
|
111
|
+
packet.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
112
|
+
packet.result = body[8]
|
|
113
|
+
packet.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
114
|
+
packet.channels = to_channels(body[13: 45])
|
|
115
|
+
logger.trace(f"pkg_id: {packet.pkg_id}, channels: {packet.channels}")
|
|
116
|
+
|
|
117
|
+
packet.origin_sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
118
|
+
packet.sample_rate = int.from_bytes(body[49: 53], 'little')
|
|
119
|
+
packet.sample_num = int.from_bytes(body[53: 57], 'little')
|
|
120
|
+
packet.resolution = int(int(body[57]) / 8)
|
|
121
|
+
packet.filter = body[58]
|
|
122
|
+
packet.data_len = int.from_bytes(body[59: 63], 'little')
|
|
123
|
+
|
|
124
|
+
# 数据块
|
|
125
|
+
b_eeg = body[63:]
|
|
126
|
+
# 根据值域分割数组-代表设备的4个模块
|
|
127
|
+
ranges = [(1, 64), (65, 128), (129, 192), (193, 256)]
|
|
128
|
+
sub_channels = [[x for x in packet.channels if low <= x <= high] for low, high in ranges]
|
|
129
|
+
# 只处理选中的模块
|
|
130
|
+
sub_channels = [_ for _ in sub_channels if len(_) > 0]
|
|
131
|
+
# 步径 相同通道的点间隔
|
|
132
|
+
step = int(len(packet.channels) * packet.resolution + 4 * len(sub_channels))
|
|
133
|
+
offset = 0
|
|
134
|
+
|
|
135
|
+
# 分按子模块处理
|
|
136
|
+
for channels in sub_channels:
|
|
137
|
+
logger.trace(f"子数组: {channels} 长度: {len(channels)}")
|
|
138
|
+
channel_size = len(channels)
|
|
139
|
+
|
|
140
|
+
# 模块没有选中通道的,跳过
|
|
141
|
+
if channel_size == 0:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
# 只保留第一个有效模块的trigger,其他的模块是冗余信息,无实际含义
|
|
145
|
+
if packet.trigger is None:
|
|
146
|
+
packet.trigger = [int.from_bytes(b_eeg[j * step : j * step + 4], 'little', signed=False) for j in range(packet.sample_num)]
|
|
147
|
+
logger.trace(f"trigger: {packet.trigger}")
|
|
148
|
+
trigger_positions = [index for index, value in enumerate(packet.trigger) if value != 0]
|
|
149
|
+
if len(trigger_positions) > 0:
|
|
150
|
+
logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[packet.time_stamp + int(pos * 1000 / packet.sample_rate) for pos in trigger_positions]}")
|
|
151
|
+
|
|
152
|
+
eeg = [
|
|
153
|
+
[
|
|
154
|
+
int.from_bytes(b_eeg[offset + step * j + 4 + k * packet.resolution : offset + step * j + 7 + k * packet.resolution], 'big', signed=True)
|
|
155
|
+
for j in range(packet.sample_num)
|
|
156
|
+
]
|
|
157
|
+
for k in range(channel_size)
|
|
158
|
+
]
|
|
159
|
+
packet.eeg = packet.eeg + eeg if packet.eeg else eeg
|
|
160
|
+
|
|
161
|
+
offset += 4 + channel_size * packet.resolution
|
|
162
|
+
|
|
163
|
+
logger.trace(packet)
|
|
164
|
+
return packet
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from time import timezone
|
|
4
4
|
from qlsdk.core.crc import check_crc, crc16
|
|
5
|
-
from qlsdk.core.local import get_ip
|
|
5
|
+
from qlsdk.core.local import get_ip, get_ips
|
|
6
6
|
|
|
7
7
|
from loguru import logger
|
|
8
8
|
|
|
@@ -72,6 +72,14 @@ class UDPMessage(object):
|
|
|
72
72
|
if server_ip is None:
|
|
73
73
|
server_ip = get_ip()
|
|
74
74
|
|
|
75
|
+
return UDPMessage._search_(device_id, server_ip, server_port)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _search_(device_id : str, server_ip : str, server_port : int=19216):
|
|
79
|
+
# 服务端Ip
|
|
80
|
+
if server_ip is None:
|
|
81
|
+
raise ValueError("server_ip is None")
|
|
82
|
+
|
|
75
83
|
logger.debug(f"search device {device_id} on {server_ip}:{server_port}")
|
|
76
84
|
|
|
77
85
|
message = bytearray(28)
|
|
File without changes
|
|
File without changes
|
|
@@ -260,6 +260,8 @@ class RscEDFHandler(object):
|
|
|
260
260
|
self._device_type = "C64RS"
|
|
261
261
|
elif device_type == 0x40:
|
|
262
262
|
self._device_type = "LJ64S1"
|
|
263
|
+
elif device_type == 0x51:
|
|
264
|
+
self._device_type = "C256RS"
|
|
263
265
|
elif device_type == 0x60:
|
|
264
266
|
self._device_type = "ARSKindling"
|
|
265
267
|
elif device_type == 0x339:
|
|
@@ -309,11 +309,11 @@ class SignalDataCommand(DeviceCommand):
|
|
|
309
309
|
return super().unpack(payload)
|
|
310
310
|
|
|
311
311
|
def parse_body(self, body: bytes):
|
|
312
|
-
# 解析数据包
|
|
313
|
-
packet = RscPacket()
|
|
314
|
-
packet.transfer(body)
|
|
312
|
+
# # 解析数据包
|
|
313
|
+
# packet = RscPacket()
|
|
314
|
+
# packet.transfer(body)
|
|
315
315
|
# 将数据包传递给设备
|
|
316
|
-
self.device.produce(
|
|
316
|
+
self.device.produce(body)
|
|
317
317
|
|
|
318
318
|
|
|
319
319
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
from multiprocessing import Queue
|
|
3
2
|
from threading import Thread
|
|
4
3
|
from time import sleep, time_ns
|
|
@@ -8,10 +7,9 @@ from loguru import logger
|
|
|
8
7
|
import numpy as np
|
|
9
8
|
from qlsdk.core.entity import RscPacket
|
|
10
9
|
from qlsdk.core.utils import to_bytes
|
|
11
|
-
from qlsdk.persist import RscEDFHandler
|
|
12
10
|
from qlsdk.rsc.interface import IDevice, IParser
|
|
13
|
-
from qlsdk.rsc.parser import TcpMessageParser
|
|
14
11
|
from qlsdk.rsc.command import StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
|
|
12
|
+
from qlsdk.rsc.parser.base import TcpMessageParser
|
|
15
13
|
|
|
16
14
|
class QLBaseDevice(IDevice):
|
|
17
15
|
def __init__(self, socket):
|
|
@@ -137,12 +135,18 @@ class QLBaseDevice(IDevice):
|
|
|
137
135
|
self._record_duration = record_duration
|
|
138
136
|
|
|
139
137
|
# 数据包处理
|
|
140
|
-
def produce(self,
|
|
141
|
-
if
|
|
138
|
+
def produce(self, body: bytes, type:Literal['signal', 'impedance']="signal"):
|
|
139
|
+
if body is None: return
|
|
142
140
|
|
|
143
141
|
# 处理信号数据
|
|
144
|
-
self._signal_wrapper(
|
|
145
|
-
|
|
142
|
+
data = self._signal_wrapper(body)
|
|
143
|
+
# logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
|
|
144
|
+
#
|
|
145
|
+
trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
|
|
146
|
+
if len(trigger_positions) > 0:
|
|
147
|
+
# logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[data.time_stamp + int(pos * 1000 / data.sample_rate) for pos in trigger_positions]}")
|
|
148
|
+
for pos in trigger_positions:
|
|
149
|
+
self.trigger(data.trigger[pos])
|
|
146
150
|
# 存储
|
|
147
151
|
if self.storage_enable:
|
|
148
152
|
self._write_signal(data)
|
|
@@ -159,9 +163,9 @@ class QLBaseDevice(IDevice):
|
|
|
159
163
|
|
|
160
164
|
q.put(data)
|
|
161
165
|
|
|
162
|
-
# 信号数据转换
|
|
163
|
-
def _signal_wrapper(self,
|
|
164
|
-
|
|
166
|
+
# 信号数据转换
|
|
167
|
+
def _signal_wrapper(self, body: bytes):
|
|
168
|
+
return RscPacket().transfer(body)
|
|
165
169
|
|
|
166
170
|
def _write_signal(self, data: RscPacket):
|
|
167
171
|
# 文件写入到edf
|
|
@@ -175,8 +179,10 @@ class QLBaseDevice(IDevice):
|
|
|
175
179
|
def start_listening(self):
|
|
176
180
|
|
|
177
181
|
try:
|
|
182
|
+
# 启动消息解析器
|
|
178
183
|
self.start_message_parser()
|
|
179
184
|
|
|
185
|
+
# 启动消息监听器
|
|
180
186
|
self.start_message_listening()
|
|
181
187
|
except Exception as e:
|
|
182
188
|
logger.error(f"设备{self.device_no}准备失败: {str(e)}")
|
|
@@ -188,7 +194,22 @@ class QLBaseDevice(IDevice):
|
|
|
188
194
|
logger.trace(f"设备{self.device_no}停止socket监听")
|
|
189
195
|
self._listening = False
|
|
190
196
|
self._parser.stop()
|
|
191
|
-
|
|
197
|
+
|
|
198
|
+
def read_msg(self, size: int) -> bytes:
|
|
199
|
+
try:
|
|
200
|
+
self.socket.settimeout(2.0)
|
|
201
|
+
return self.socket.recv(size)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"read_msg exception: {str(e)}")
|
|
204
|
+
raise ValueError("read_msg exception") from e
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
208
|
+
rlt = cls(parent.socket)
|
|
209
|
+
rlt.device_id = parent.device_id
|
|
210
|
+
rlt._device_no = parent.device_no
|
|
211
|
+
return rlt
|
|
212
|
+
|
|
192
213
|
@property
|
|
193
214
|
def device_type(self) -> int:
|
|
194
215
|
return self._device_type
|
|
@@ -196,18 +217,21 @@ class QLBaseDevice(IDevice):
|
|
|
196
217
|
def start_message_parser(self) -> None:
|
|
197
218
|
self._parser = TcpMessageParser(self)
|
|
198
219
|
self._parser.start()
|
|
199
|
-
logger.debug("
|
|
220
|
+
logger.debug("RSC消息解析器已启动")
|
|
200
221
|
|
|
201
222
|
def start_message_listening(self) -> None:
|
|
202
223
|
def _accept():
|
|
203
224
|
while self._listening:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
225
|
+
try:
|
|
226
|
+
# 缓冲区4M
|
|
227
|
+
data = self.socket.recv(4096*1024)
|
|
228
|
+
if not data:
|
|
229
|
+
logger.warning(f"设备[{self.device_name}]连接结束")
|
|
230
|
+
break
|
|
231
|
+
self._parser.append(data)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.debug(f"设备[{self.device_name}]接收数据异常: {str(e)}")
|
|
208
234
|
break
|
|
209
|
-
|
|
210
|
-
self._parser.append(data)
|
|
211
235
|
|
|
212
236
|
# 启动数据接收线程
|
|
213
237
|
self._listening = True
|
|
@@ -252,11 +276,6 @@ class QLBaseDevice(IDevice):
|
|
|
252
276
|
def init_edf_handler(self):
|
|
253
277
|
logger.warning("init_edf_handler not implemented in base class, should be overridden in subclass")
|
|
254
278
|
pass
|
|
255
|
-
# self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
|
|
256
|
-
# self._edf_handler.set_device_type(self.device_type)
|
|
257
|
-
# self._edf_handler.set_device_no(self.device_no)
|
|
258
|
-
# self._edf_handler.set_storage_path(self._storage_path)
|
|
259
|
-
# self._edf_handler.set_file_prefix(self._file_prefix)
|
|
260
279
|
|
|
261
280
|
# eeg数字值转物理值
|
|
262
281
|
def eeg2phy(self, digital:int):
|
|
@@ -444,12 +463,26 @@ class QLBaseDevice(IDevice):
|
|
|
444
463
|
|
|
445
464
|
return body
|
|
446
465
|
|
|
466
|
+
def disconnect(self):
|
|
467
|
+
logger.info(f"[断开设备-{self.device_no}]的连接...")
|
|
468
|
+
self._listening = False
|
|
469
|
+
try:
|
|
470
|
+
sleep(0.1)
|
|
471
|
+
self.socket.shutdown(2)
|
|
472
|
+
self.socket.close()
|
|
473
|
+
logger.info(f"[设备-{self.device_no}]设备连接已断开")
|
|
474
|
+
except Exception as e:
|
|
475
|
+
logger.error(f"断开设备连接异常: {str(e)}")
|
|
476
|
+
|
|
477
|
+
# 关闭解析器
|
|
478
|
+
self._parser.stop()
|
|
479
|
+
|
|
447
480
|
def __str__(self):
|
|
448
481
|
return f'''
|
|
449
482
|
Device:
|
|
450
483
|
Name: {self.device_name},
|
|
451
484
|
Type: {hex(self.device_type) if self.device_type else None},
|
|
452
|
-
ID: {self.
|
|
485
|
+
ID: {self.device_no if self.device_no else None},
|
|
453
486
|
Software: {self.software_version},
|
|
454
487
|
Hardware: {self.hardware_version},
|
|
455
488
|
Connect Time: {self.connect_time},
|
|
@@ -459,8 +492,8 @@ class QLBaseDevice(IDevice):
|
|
|
459
492
|
Battery Total: {str(self.battery_total) + "%" if self.battery_total else None}
|
|
460
493
|
'''
|
|
461
494
|
|
|
462
|
-
def __eq__(self, other):
|
|
463
|
-
return self.
|
|
495
|
+
def __eq__(self, other:IDevice):
|
|
496
|
+
return self.device_type == other.device_type and self.device_no == other.device_no
|
|
464
497
|
|
|
465
498
|
def __hash__(self):
|
|
466
|
-
return hash((self.
|
|
499
|
+
return hash((self.device_type, self.device_no))
|
|
@@ -5,15 +5,15 @@ from time import sleep, time_ns
|
|
|
5
5
|
from typing import Any, Dict, Literal
|
|
6
6
|
|
|
7
7
|
from loguru import logger
|
|
8
|
+
from qlsdk.core.entity import C256RSPacket
|
|
8
9
|
from qlsdk.persist import RscEDFHandler
|
|
9
|
-
from qlsdk.rsc.
|
|
10
|
+
from qlsdk.rsc.device.device_factory import DeviceFactory
|
|
10
11
|
from qlsdk.rsc.command import *
|
|
11
|
-
from qlsdk.rsc.parser.base import TcpMessageParser
|
|
12
12
|
from qlsdk.rsc.device.base import QLBaseDevice
|
|
13
13
|
|
|
14
14
|
class C256RS(QLBaseDevice):
|
|
15
15
|
|
|
16
|
-
device_type =
|
|
16
|
+
device_type = 0x51 # C64RS设备类型标识符
|
|
17
17
|
|
|
18
18
|
def __init__(self, socket):
|
|
19
19
|
super().__init__(socket)
|
|
@@ -125,13 +125,13 @@ class C256RS(QLBaseDevice):
|
|
|
125
125
|
self._edf_handler = None
|
|
126
126
|
self.storage_enable = True
|
|
127
127
|
|
|
128
|
-
self._parser: IParser =
|
|
129
|
-
self._parser.start()
|
|
128
|
+
# self._parser: IParser = RSCMessageParser(self)
|
|
129
|
+
# self._parser.start()
|
|
130
130
|
|
|
131
131
|
# 启动数据接收线程
|
|
132
|
-
self._accept = Thread(target=self.accept)
|
|
133
|
-
self._accept.daemon = True
|
|
134
|
-
self._accept.start()
|
|
132
|
+
# self._accept = Thread(target=self.accept)
|
|
133
|
+
# self._accept.daemon = True
|
|
134
|
+
# self._accept.start()
|
|
135
135
|
|
|
136
136
|
@property
|
|
137
137
|
def device_no(self):
|
|
@@ -140,21 +140,14 @@ class C256RS(QLBaseDevice):
|
|
|
140
140
|
@device_no.setter
|
|
141
141
|
def device_no(self, value: str):
|
|
142
142
|
self._device_no = value
|
|
143
|
-
|
|
144
|
-
@classmethod
|
|
145
|
-
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
146
|
-
rlt = cls(parent.socket)
|
|
147
|
-
rlt.device_id = parent.device_id
|
|
148
|
-
rlt._device_no = parent.device_no
|
|
149
|
-
return rlt
|
|
150
|
-
|
|
143
|
+
|
|
151
144
|
|
|
152
145
|
def init_edf_handler(self):
|
|
153
146
|
self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
|
|
154
147
|
self._edf_handler.set_device_type(self.device_type)
|
|
155
|
-
self._edf_handler.set_device_no(self.
|
|
148
|
+
self._edf_handler.set_device_no(self.device_no)
|
|
156
149
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
157
|
-
self._edf_handler.set_file_prefix(self._file_prefix)
|
|
150
|
+
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C256RS')
|
|
158
151
|
|
|
159
152
|
@property
|
|
160
153
|
def edf_handler(self):
|
|
@@ -169,7 +162,7 @@ class C256RS(QLBaseDevice):
|
|
|
169
162
|
@property
|
|
170
163
|
def acq_channels(self):
|
|
171
164
|
if self._acq_channels is None:
|
|
172
|
-
self._acq_channels = [i for i in range(1,
|
|
165
|
+
self._acq_channels = [i for i in range(1, 256)]
|
|
173
166
|
return self._acq_channels
|
|
174
167
|
@property
|
|
175
168
|
def sample_range(self):
|
|
@@ -199,15 +192,15 @@ class C256RS(QLBaseDevice):
|
|
|
199
192
|
|
|
200
193
|
# 接收socket消息
|
|
201
194
|
def accept(self):
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
self._parser.append(data)
|
|
195
|
+
pass
|
|
196
|
+
# while True:
|
|
197
|
+
# # 缓冲去4M
|
|
198
|
+
# data = self.socket.recv(4096*1024)
|
|
199
|
+
# if not data:
|
|
200
|
+
# logger.warning(f"设备{self.device_name}连接结束")
|
|
201
|
+
# break
|
|
210
202
|
|
|
203
|
+
# self._parser.append(data)
|
|
211
204
|
|
|
212
205
|
# socket发送数据
|
|
213
206
|
def send(self, data):
|
|
@@ -226,9 +219,8 @@ class C256RS(QLBaseDevice):
|
|
|
226
219
|
self._sample_rate = sample_rate
|
|
227
220
|
self._sample_range = sample_range
|
|
228
221
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
pass
|
|
222
|
+
def _signal_wrapper(self, body: bytes):
|
|
223
|
+
return C256RSPacket().transfer(body)
|
|
232
224
|
|
|
233
225
|
def start_impedance(self):
|
|
234
226
|
logger.info("启动阻抗测量")
|
|
@@ -297,7 +289,7 @@ class C256RS(QLBaseDevice):
|
|
|
297
289
|
|
|
298
290
|
# 数据队列
|
|
299
291
|
if q is None:
|
|
300
|
-
q = Queue(maxsize=
|
|
292
|
+
q = Queue(maxsize=10240000) # 10M缓存
|
|
301
293
|
|
|
302
294
|
# 队列名称
|
|
303
295
|
if topic is None:
|
|
@@ -362,4 +354,5 @@ class C256RS(QLBaseDevice):
|
|
|
362
354
|
def __hash__(self):
|
|
363
355
|
return hash((self.device_name, self.device_type, self.device_id))
|
|
364
356
|
|
|
365
|
-
|
|
357
|
+
|
|
358
|
+
DeviceFactory.register(C256RS.device_type, C256RS)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import abc
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
class IDevice(ABC):
|
|
6
|
+
|
|
7
|
+
@property
|
|
8
|
+
@abc.abstractmethod
|
|
9
|
+
def device_type(self) -> int:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def set_device_type(self, value: int):
|
|
13
|
+
raise NotImplementedError("Not Supported")
|
|
14
|
+
|
|
15
|
+
def set_storage_path(self, path: str):
|
|
16
|
+
raise NotImplementedError("Not Supported")
|
|
17
|
+
|
|
18
|
+
def set_file_prefix(self, pre: str):
|
|
19
|
+
raise NotImplementedError("Not Supported")
|
|
20
|
+
|
|
21
|
+
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
22
|
+
raise NotImplementedError("Not Supported")
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def device_no(self) -> str:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def read_msg(self, size: int) -> bytes:
|
|
29
|
+
raise NotImplementedError("Not Supported")
|
|
30
|
+
|
|
31
|
+
def produce(self, body: bytes, type:Literal['signal', 'impedance']="signal") -> None:
|
|
32
|
+
raise NotImplementedError("Not Supported")
|
|
33
|
+
|
|
34
|
+
def start_listening(self):
|
|
35
|
+
raise NotImplementedError("Not Supported")
|
|
36
|
+
|
|
37
|
+
def stop_listening(self):
|
|
38
|
+
raise NotImplementedError("Not Supported")
|
|
39
|
+
|
|
40
|
+
def set_device_no(self, value: str):
|
|
41
|
+
raise NotImplementedError("Not Supported")
|
|
42
|
+
|
|
43
|
+
def from_parent(cls, parent) :
|
|
44
|
+
raise NotImplementedError("Not Supported")
|
|
45
|
+
|
|
46
|
+
def start_acquisition(self) -> None:
|
|
47
|
+
raise NotImplementedError("Not Supported")
|
|
48
|
+
|
|
49
|
+
def stop_acquisition(self) -> None:
|
|
50
|
+
raise NotImplementedError("Not Supported")
|
|
51
|
+
|
|
52
|
+
def subscribe(self, type="signal") -> None:
|
|
53
|
+
raise NotImplementedError("Not Supported")
|
|
54
|
+
def unsubscribe(self, topic) -> None:
|
|
55
|
+
raise NotImplementedError("Not Supported")
|
|
56
|
+
|
|
57
|
+
def start_stimulation(self, type="signal", duration=0) -> None:
|
|
58
|
+
raise NotImplementedError("Not Supported")
|
|
59
|
+
|
|
60
|
+
def stop_stimulation(self) -> None:
|
|
61
|
+
raise NotImplementedError("Not Supported")
|
|
62
|
+
def disconnect(self) -> None:
|
|
63
|
+
raise NotImplementedError("Not Supported")
|
|
64
|
+
|