qlsdk2 0.6.0a8__py3-none-any.whl → 0.6.0a10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qlsdk/core/entity/__init__.py +2 -1
- qlsdk/persist/ars_edf.py +77 -73
- qlsdk/rsc/device/arskindling.py +200 -27
- qlsdk/rsc/device/base.py +18 -6
- qlsdk/rsc/interface/device.py +7 -1
- qlsdk/rsc/paradigm.py +2 -1
- qlsdk/rsc/parser/base.py +11 -6
- {qlsdk2-0.6.0a8.dist-info → qlsdk2-0.6.0a10.dist-info}/METADATA +1 -1
- {qlsdk2-0.6.0a8.dist-info → qlsdk2-0.6.0a10.dist-info}/RECORD +11 -11
- {qlsdk2-0.6.0a8.dist-info → qlsdk2-0.6.0a10.dist-info}/WHEEL +0 -0
- {qlsdk2-0.6.0a8.dist-info → qlsdk2-0.6.0a10.dist-info}/top_level.txt +0 -0
qlsdk/core/entity/__init__.py
CHANGED
qlsdk/persist/ars_edf.py
CHANGED
|
@@ -85,67 +85,67 @@ class ARSKindlingEDFHandler(object):
|
|
|
85
85
|
"D" : [7, 8, 5, 6, 3, 4, 1, 2, 9, 10, 11, 12, 13, 14],
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
self._channel_mapping = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
88
|
+
# self._channel_mapping = {
|
|
89
|
+
# 1: "D7",
|
|
90
|
+
# 2: "D8",
|
|
91
|
+
# 3: "D5",
|
|
92
|
+
# 4: "D6",
|
|
93
|
+
# 5: "D3",
|
|
94
|
+
# 6: "D4",
|
|
95
|
+
# 7: "D1",
|
|
96
|
+
# 8: "D2",
|
|
97
|
+
# 9: "D9",
|
|
98
|
+
# 10: "D10",
|
|
99
|
+
# 11: "D11",
|
|
100
|
+
# 12: "D12",
|
|
101
|
+
# 13: "D13",
|
|
102
|
+
# 14: "D14",
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
104
|
+
# 17: "C7",
|
|
105
|
+
# 18: "C8",
|
|
106
|
+
# 19: "C5",
|
|
107
|
+
# 20: "C6",
|
|
108
|
+
# 21: "C3",
|
|
109
|
+
# 22: "C4",
|
|
110
|
+
# 23: "C1",
|
|
111
|
+
# 24: "C2",
|
|
112
|
+
# 25: "C9",
|
|
113
|
+
# 26: "C10",
|
|
114
|
+
# 27: "C11",
|
|
115
|
+
# 28: "C12",
|
|
116
|
+
# 29: "C13",
|
|
117
|
+
# 30: "C14",
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
# 33: "B7",
|
|
120
|
+
# 34: "B8",
|
|
121
|
+
# 35: "B5",
|
|
122
|
+
# 36: "B6",
|
|
123
|
+
# 37: "B3",
|
|
124
|
+
# 38: "B4",
|
|
125
|
+
# 39: "B1",
|
|
126
|
+
# 40: "B2",
|
|
127
|
+
# 41: "B9",
|
|
128
|
+
# 42: "B10",
|
|
129
|
+
# 43: "B11",
|
|
130
|
+
# 44: "B12",
|
|
131
|
+
# 45: "B13",
|
|
132
|
+
# 46: "B14",
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
134
|
+
# 49: "A7",
|
|
135
|
+
# 50: "A8",
|
|
136
|
+
# 51: "A5",
|
|
137
|
+
# 52: "A6",
|
|
138
|
+
# 53: "A3",
|
|
139
|
+
# 54: "A4",
|
|
140
|
+
# 55: "A1",
|
|
141
|
+
# 56: "A2",
|
|
142
|
+
# 57: "A9",
|
|
143
|
+
# 58: "A10",
|
|
144
|
+
# 59: "A11",
|
|
145
|
+
# 60: "A12",
|
|
146
|
+
# 61: "A13",
|
|
147
|
+
# 62: "A14"
|
|
148
|
+
# }
|
|
149
149
|
|
|
150
150
|
self._lock = Lock()
|
|
151
151
|
|
|
@@ -172,6 +172,8 @@ class ARSKindlingEDFHandler(object):
|
|
|
172
172
|
self._device_type = "C64RS"
|
|
173
173
|
elif device_type == 0x40:
|
|
174
174
|
self._device_type = "LJ64S1"
|
|
175
|
+
elif device_type == 0x60:
|
|
176
|
+
self._device_type = "ArsKindling"
|
|
175
177
|
else:
|
|
176
178
|
self._device_type = hex(device_type)
|
|
177
179
|
|
|
@@ -200,21 +202,23 @@ class ARSKindlingEDFHandler(object):
|
|
|
200
202
|
|
|
201
203
|
#按分区写入数据
|
|
202
204
|
for k in self._channel_spilt.keys():
|
|
203
|
-
logger.trace(f'分区{k}
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
logger.trace(f'分区{k}写入数据到文件')
|
|
206
|
+
if k in packet.channels.keys():
|
|
207
|
+
p = packet
|
|
208
|
+
self.writeA(p, k)
|
|
206
209
|
|
|
207
|
-
def writeA(self, packet: RscPacket,
|
|
210
|
+
def writeA(self, packet: RscPacket, name='A'):
|
|
208
211
|
# 参数检查
|
|
209
|
-
if packet is None
|
|
212
|
+
if packet is None:
|
|
210
213
|
logger.warning("空数据,忽略")
|
|
211
214
|
return
|
|
215
|
+
|
|
216
|
+
channels = packet.channels.get(name, None)
|
|
217
|
+
eeg = packet.eeg.get(name, None)
|
|
212
218
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
logger.trace(f"没有指定分区{name}的通道,跳过")
|
|
217
|
-
pass
|
|
219
|
+
if eeg is None :
|
|
220
|
+
logger.trace(f"分区{name}没有可用的数据,跳过文件写入")
|
|
221
|
+
return
|
|
218
222
|
|
|
219
223
|
# 分区数据包写入
|
|
220
224
|
if name not in self._edf_handler.keys():
|
|
@@ -226,15 +230,12 @@ class ARSKindlingEDFHandler(object):
|
|
|
226
230
|
logger.info(f"开始写入分区{name}的数据到文件")
|
|
227
231
|
self._edf_handler[name] = edf_handler
|
|
228
232
|
|
|
229
|
-
# 保留本分区的通道和数据
|
|
230
|
-
channels = [packet.channels[p] for p in channel_pos]
|
|
231
|
-
eeg = [packet.eeg[p] for p in channel_pos]
|
|
232
|
-
|
|
233
233
|
# 新建数据包实例
|
|
234
234
|
data = RscPacket()
|
|
235
235
|
data.time_stamp = packet.time_stamp
|
|
236
236
|
data.pkg_id = packet.pkg_id
|
|
237
|
-
data.channels = channels
|
|
237
|
+
# data.channels = channels
|
|
238
|
+
data.channels = self.channel_display(channels, name)
|
|
238
239
|
data.origin_sample_rate = packet.origin_sample_rate
|
|
239
240
|
data.sample_rate = packet.sample_rate
|
|
240
241
|
data.sample_num = packet.sample_num
|
|
@@ -251,3 +252,6 @@ class ARSKindlingEDFHandler(object):
|
|
|
251
252
|
# trigger现在(20250702)多个分区共享, 分发到所有分区文件中标记
|
|
252
253
|
for k in self._edf_handler.keys():
|
|
253
254
|
self._edf_handler[k].trigger(desc, cur_time)
|
|
255
|
+
|
|
256
|
+
def channel_display(self, channel, name: str):
|
|
257
|
+
return [f'{name}-{channel_id}' for channel_id in channel]
|
qlsdk/rsc/device/arskindling.py
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
|
+
from multiprocessing import Queue
|
|
1
2
|
from loguru import logger
|
|
2
3
|
from qlsdk.persist import ARSKindlingEDFHandler
|
|
3
4
|
from qlsdk.rsc.interface import IDevice
|
|
4
5
|
from qlsdk.rsc.command import *
|
|
5
6
|
from qlsdk.rsc.device.base import QLBaseDevice
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
def intersection_positions(A, B):
|
|
10
|
+
setB = set(B)
|
|
11
|
+
seen = set()
|
|
12
|
+
return [idx for idx, elem in enumerate(A)
|
|
13
|
+
if elem in setB and elem not in seen and not seen.add(elem)]
|
|
14
|
+
|
|
15
|
+
def get_sorted_indices_basic(A, B):
|
|
16
|
+
"""
|
|
17
|
+
找出数组A中存在于数组B的元素,并返回这些元素在A中的位置,且顺序与它们在B中的顺序一致。
|
|
18
|
+
|
|
19
|
+
参数:
|
|
20
|
+
A (list): 待查找的数组。
|
|
21
|
+
B (list): 作为参考的数组。
|
|
22
|
+
|
|
23
|
+
返回:
|
|
24
|
+
list: 一个列表,包含符合条件的元素在A中的索引,顺序与这些元素在B中的出现顺序一致。
|
|
25
|
+
"""
|
|
26
|
+
# 1. 创建B中元素到其索引的映射
|
|
27
|
+
b_index_map = {value: idx for idx, value in enumerate(B)}
|
|
28
|
+
|
|
29
|
+
# 2. 筛选A中存在于B的元素,并记录其在A中的索引及在B中的位置
|
|
30
|
+
# 使用列表推导式,同时避免重复元素干扰(如果B有重复,以第一次出现为准)
|
|
31
|
+
found_elements = []
|
|
32
|
+
for idx_a, value in enumerate(A):
|
|
33
|
+
if value in b_index_map:
|
|
34
|
+
# 记录: (该元素在A中的索引, 该元素在B中的索引)
|
|
35
|
+
found_elements.append((idx_a, b_index_map[value]))
|
|
36
|
+
|
|
37
|
+
# 3. 根据元素在B中的位置进行排序
|
|
38
|
+
# 排序的依据是元组的第二个元素,即 b_index_map[value]
|
|
39
|
+
found_elements_sorted = sorted(found_elements, key=lambda x: x[1])
|
|
40
|
+
|
|
41
|
+
# 4. 提取排序后在A中的索引,形成结果数组C
|
|
42
|
+
array_C = [item[0] for item in found_elements_sorted]
|
|
43
|
+
|
|
44
|
+
return array_C
|
|
45
|
+
|
|
7
46
|
class ARSKindling(QLBaseDevice):
|
|
8
47
|
|
|
9
48
|
device_type = 0x60 # C64RS设备类型标识符
|
|
@@ -25,6 +64,75 @@ class ARSKindling(QLBaseDevice):
|
|
|
25
64
|
"D9": 9, "D10": 10, "D11": 11, "D12": 12, "D13": 13, "D14": 14,
|
|
26
65
|
}
|
|
27
66
|
|
|
67
|
+
self._channel_spilt = {
|
|
68
|
+
"A" : [55, 56, 53, 54, 51, 52, 49, 50, 57, 58, 59, 60, 61, 62],
|
|
69
|
+
"B" : [39, 40, 37, 38, 35, 36, 33, 34, 41, 42, 43, 44, 45, 46],
|
|
70
|
+
"C" : [23, 24, 21, 22, 19, 20, 17, 18, 25, 26, 27, 28, 29, 30],
|
|
71
|
+
"D" : [7, 8, 5, 6, 3, 4, 1, 2, 9, 10, 11, 12, 13, 14],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
self._channel_mapping = {
|
|
75
|
+
1: "7",
|
|
76
|
+
2: "8",
|
|
77
|
+
3: "5",
|
|
78
|
+
4: "6",
|
|
79
|
+
5: "3",
|
|
80
|
+
6: "4",
|
|
81
|
+
7: "1",
|
|
82
|
+
8: "2",
|
|
83
|
+
9: "9",
|
|
84
|
+
10: "10",
|
|
85
|
+
11: "11",
|
|
86
|
+
12: "12",
|
|
87
|
+
13: "13",
|
|
88
|
+
14: "14",
|
|
89
|
+
|
|
90
|
+
17: "7",
|
|
91
|
+
18: "8",
|
|
92
|
+
19: "5",
|
|
93
|
+
20: "6",
|
|
94
|
+
21: "3",
|
|
95
|
+
22: "4",
|
|
96
|
+
23: "1",
|
|
97
|
+
24: "2",
|
|
98
|
+
25: "9",
|
|
99
|
+
26: "10",
|
|
100
|
+
27: "11",
|
|
101
|
+
28: "12",
|
|
102
|
+
29: "13",
|
|
103
|
+
30: "14",
|
|
104
|
+
|
|
105
|
+
33: "7",
|
|
106
|
+
34: "8",
|
|
107
|
+
35: "5",
|
|
108
|
+
36: "6",
|
|
109
|
+
37: "3",
|
|
110
|
+
38: "4",
|
|
111
|
+
39: "1",
|
|
112
|
+
40: "2",
|
|
113
|
+
41: "9",
|
|
114
|
+
42: "10",
|
|
115
|
+
43: "11",
|
|
116
|
+
44: "12",
|
|
117
|
+
45: "13",
|
|
118
|
+
46: "14",
|
|
119
|
+
|
|
120
|
+
49: "7",
|
|
121
|
+
50: "8",
|
|
122
|
+
51: "5",
|
|
123
|
+
52: "6",
|
|
124
|
+
53: "3",
|
|
125
|
+
54: "4",
|
|
126
|
+
55: "1",
|
|
127
|
+
56: "2",
|
|
128
|
+
57: "9",
|
|
129
|
+
58: "10",
|
|
130
|
+
59: "11",
|
|
131
|
+
60: "12",
|
|
132
|
+
61: "13",
|
|
133
|
+
62: "14"
|
|
134
|
+
}
|
|
135
|
+
|
|
28
136
|
@classmethod
|
|
29
137
|
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
30
138
|
rlt = cls(parent.socket)
|
|
@@ -61,35 +169,100 @@ class ARSKindling(QLBaseDevice):
|
|
|
61
169
|
self._sample_rate = sample_rate
|
|
62
170
|
self._sample_range = sample_range
|
|
63
171
|
|
|
64
|
-
|
|
65
|
-
|
|
172
|
+
@property
|
|
173
|
+
def acq_channels(self):
|
|
174
|
+
if self._acq_channels is None:
|
|
175
|
+
self._acq_channels = [i for i in range(1, 63)]
|
|
176
|
+
|
|
66
177
|
arr = []
|
|
67
|
-
for k in self.
|
|
68
|
-
arr = list(arr + self.
|
|
178
|
+
for k in self._acq_channels.keys():
|
|
179
|
+
arr = list(arr + self._acq_channels[k])
|
|
180
|
+
|
|
181
|
+
return list(set(arr))
|
|
182
|
+
|
|
183
|
+
def _produce_signal(self, body: bytes):
|
|
184
|
+
|
|
185
|
+
# 处理信号数据
|
|
186
|
+
data = self._signal_wrapper(body)
|
|
187
|
+
# logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
|
|
188
|
+
|
|
189
|
+
trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
|
|
190
|
+
if len(trigger_positions) > 0:
|
|
191
|
+
# logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[data.time_stamp + int(pos * 1000 / data.sample_rate) for pos in trigger_positions]}")
|
|
192
|
+
for pos in trigger_positions:
|
|
193
|
+
self.trigger(self.trigger_info(data.trigger[pos]))
|
|
194
|
+
|
|
195
|
+
if len(self.signal_consumers) > 0 :
|
|
196
|
+
# 信号数字值转物理值
|
|
197
|
+
data.eeg_p = self.eeg2phy(np.array(data.eeg))
|
|
198
|
+
|
|
199
|
+
real_data = self.__signal_transfer(data)
|
|
200
|
+
|
|
201
|
+
# 存储
|
|
202
|
+
if self.storage_enable:
|
|
203
|
+
# 确保记录线程启动
|
|
204
|
+
if self._recording is False:
|
|
205
|
+
self._start_recording()
|
|
206
|
+
|
|
207
|
+
# 写入文件的缓存队列
|
|
208
|
+
if self._signal_cache is None:
|
|
209
|
+
self._signal_cache = Queue(1000000) # 缓冲队列
|
|
210
|
+
self._signal_cache.put(real_data)
|
|
211
|
+
|
|
212
|
+
if len(self.signal_consumers) > 0 :
|
|
213
|
+
# 订阅只返回物理值
|
|
214
|
+
real_data.eeg = real_data.eeg_p
|
|
215
|
+
# 发送数据包到订阅者
|
|
216
|
+
for q in list(self.signal_consumers.values()):
|
|
217
|
+
# 队列满了就丢弃最早的数据
|
|
218
|
+
if q.full():
|
|
219
|
+
q.get()
|
|
220
|
+
|
|
221
|
+
q.put(real_data)
|
|
222
|
+
# 信号数据转换
|
|
223
|
+
def __signal_transfer(self, packet: RscPacket):
|
|
224
|
+
channels = {}
|
|
225
|
+
eeg = {}
|
|
226
|
+
eeg_p = {}
|
|
227
|
+
#按分区拆分数据格式
|
|
228
|
+
for k in self._channel_spilt.keys():
|
|
229
|
+
logger.trace(f'分区{k}, {self._channel_spilt[k]}')
|
|
230
|
+
c, d, p = self.__packet_filter(packet, self._channel_spilt[k])
|
|
231
|
+
if c is not None:
|
|
232
|
+
channels[k] = c
|
|
233
|
+
eeg[k] = d
|
|
234
|
+
if p is not None:
|
|
235
|
+
eeg_p[k] = p
|
|
236
|
+
packet.channels = channels
|
|
237
|
+
packet.eeg = eeg
|
|
238
|
+
#物理值
|
|
239
|
+
if packet.eeg_p is not None:
|
|
240
|
+
packet.eeg_p = eeg_p
|
|
69
241
|
|
|
70
|
-
|
|
71
|
-
body = to_bytes(arr)
|
|
72
|
-
body += self.sample_range.to_bytes(4, byteorder='little')
|
|
73
|
-
body += self.sample_rate.to_bytes(4, byteorder='little')
|
|
74
|
-
body += self.sample_num.to_bytes(4, byteorder='little')
|
|
75
|
-
body += self.resolution.to_bytes(1, byteorder='little')
|
|
76
|
-
body += bytes.fromhex('00')
|
|
77
|
-
|
|
78
|
-
return body
|
|
242
|
+
return packet
|
|
79
243
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
244
|
+
def __packet_filter(self, packet: RscPacket, channel_filter=None):
|
|
245
|
+
# 参数检查
|
|
246
|
+
if packet is None or channel_filter is None:
|
|
247
|
+
logger.warning("空数据,忽略")
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
channel_pos = get_sorted_indices_basic(packet.channels, channel_filter)
|
|
251
|
+
|
|
252
|
+
if channel_pos is None or len(channel_pos) == 0 :
|
|
253
|
+
logger.trace(f"没有指定分区的通道,跳过")
|
|
254
|
+
return None, None, None
|
|
255
|
+
|
|
256
|
+
# 分区数据包写入
|
|
257
|
+
|
|
258
|
+
# 保留本分区的通道和数据
|
|
259
|
+
channels = [packet.channels[p] for p in channel_pos]
|
|
260
|
+
eeg = [packet.eeg[p] for p in channel_pos]
|
|
261
|
+
eeg_p = None
|
|
262
|
+
if packet.eeg_p is not None:
|
|
263
|
+
eeg_p = [packet.eeg_p[p] for p in channel_pos]
|
|
264
|
+
|
|
265
|
+
return [self._channel_mapping.get(channel_id, []) for channel_id in channels], eeg, eeg_p
|
|
266
|
+
|
|
94
267
|
|
|
95
268
|
|
qlsdk/rsc/device/base.py
CHANGED
|
@@ -144,14 +144,14 @@ class QLBaseDevice(IDevice):
|
|
|
144
144
|
def _produce_signal(self, body: bytes):
|
|
145
145
|
|
|
146
146
|
# 处理信号数据
|
|
147
|
-
data = self._signal_wrapper(body)
|
|
147
|
+
data = self._signal_wrapper(body)
|
|
148
148
|
# logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
|
|
149
149
|
#
|
|
150
150
|
trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
|
|
151
151
|
if len(trigger_positions) > 0:
|
|
152
152
|
# logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[data.time_stamp + int(pos * 1000 / data.sample_rate) for pos in trigger_positions]}")
|
|
153
153
|
for pos in trigger_positions:
|
|
154
|
-
self.trigger(data.trigger[pos])
|
|
154
|
+
self.trigger(self.trigger_info(data.trigger[pos]))
|
|
155
155
|
# 存储
|
|
156
156
|
if self.storage_enable:
|
|
157
157
|
# 确保记录线程启动
|
|
@@ -402,8 +402,8 @@ class QLBaseDevice(IDevice):
|
|
|
402
402
|
msg = StartStimulationCommand.build(self).pack()
|
|
403
403
|
logger.trace(f"start_stimulation message is {msg.hex()}")
|
|
404
404
|
self.socket.sendall(msg)
|
|
405
|
-
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
|
|
406
|
-
t.start()
|
|
405
|
+
# t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
|
|
406
|
+
# t.start()
|
|
407
407
|
|
|
408
408
|
def get_stim_param(self) -> bytes:
|
|
409
409
|
return self.stim_paradigm.to_bytes()
|
|
@@ -421,7 +421,7 @@ class QLBaseDevice(IDevice):
|
|
|
421
421
|
|
|
422
422
|
def stop_stimulation(self):
|
|
423
423
|
logger.info(f"[设备-{self.device_no}]停止电刺激")
|
|
424
|
-
msg = StopStimulationCommand.pack()
|
|
424
|
+
msg = StopStimulationCommand.build(self).pack()
|
|
425
425
|
logger.trace(f"stop_stimulation message is {msg.hex()}")
|
|
426
426
|
self.socket.sendall(msg)
|
|
427
427
|
|
|
@@ -517,6 +517,9 @@ class QLBaseDevice(IDevice):
|
|
|
517
517
|
|
|
518
518
|
def enable_storage(self, enable: bool = True):
|
|
519
519
|
self.storage_enable = enable
|
|
520
|
+
|
|
521
|
+
def trigger_info(self, code: int) -> str:
|
|
522
|
+
return __TRIGGER_MAPPING.get(code, hex(code))
|
|
520
523
|
|
|
521
524
|
def __str__(self):
|
|
522
525
|
return f'''
|
|
@@ -537,4 +540,13 @@ class QLBaseDevice(IDevice):
|
|
|
537
540
|
return self.device_type == other.device_type and self.device_no == other.device_no
|
|
538
541
|
|
|
539
542
|
def __hash__(self):
|
|
540
|
-
return hash((self.device_type, self.device_no))
|
|
543
|
+
return hash((self.device_type, self.device_no))
|
|
544
|
+
|
|
545
|
+
__TRIGGER_MAPPING = {
|
|
546
|
+
0x3E8: "Start of stimulation",
|
|
547
|
+
0x3E9: "End of stimulation",
|
|
548
|
+
0x3EA: "Ascending end of stimulation",
|
|
549
|
+
0x3EB: "Descending start of stimulation ",
|
|
550
|
+
0x3EC: "刺激参数有误",
|
|
551
|
+
0x3ED: "End of stimulation (by force)",
|
|
552
|
+
}
|
qlsdk/rsc/interface/device.py
CHANGED
qlsdk/rsc/paradigm.py
CHANGED
|
@@ -191,7 +191,8 @@ class ACStimulation(StimulationChannel):
|
|
|
191
191
|
self.frequency = frequency
|
|
192
192
|
# self.frequency = frequency
|
|
193
193
|
self.phase_position = phase_position
|
|
194
|
-
|
|
194
|
+
if current < 0:
|
|
195
|
+
self.phase_position = (phase_position + 180) % 360
|
|
195
196
|
def to_json(self):
|
|
196
197
|
return {
|
|
197
198
|
"channel_id": self.channel_id,
|
qlsdk/rsc/parser/base.py
CHANGED
|
@@ -53,7 +53,7 @@ class TcpMessageParser(IParser):
|
|
|
53
53
|
buf_len = get_len(self.buffer)
|
|
54
54
|
|
|
55
55
|
if buf_len < self.header_len:
|
|
56
|
-
logger.trace(f"等待数据中...: expect: {self.header_len}, actual: {buf_len}")
|
|
56
|
+
# logger.trace(f"等待数据中...: expect: {self.header_len}, actual: {buf_len}")
|
|
57
57
|
if not self.__fill_from_cache():
|
|
58
58
|
time.sleep(0.1)
|
|
59
59
|
continue
|
|
@@ -64,7 +64,7 @@ class TcpMessageParser(IParser):
|
|
|
64
64
|
start_pos = self.buffer.tell()
|
|
65
65
|
head = self.buffer.read(2)
|
|
66
66
|
if head != self.header:
|
|
67
|
-
logger.trace(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
|
|
67
|
+
# logger.trace(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
|
|
68
68
|
self.buffer.seek(start_pos + 1) # 移动到下一个字节
|
|
69
69
|
continue
|
|
70
70
|
|
|
@@ -131,10 +131,15 @@ class TcpMessageParser(IParser):
|
|
|
131
131
|
|
|
132
132
|
def unpack(self, packet):
|
|
133
133
|
# 提取指令码
|
|
134
|
-
cmd_code =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
cmd_code = None
|
|
135
|
+
try:
|
|
136
|
+
cmd_code = int.from_bytes(packet[self.cmd_pos : self.cmd_pos + 2], 'little')
|
|
137
|
+
cmd_class = CommandFactory.create_command(cmd_code)
|
|
138
|
+
instance = cmd_class(self.device)
|
|
139
|
+
instance.parse_body(packet[self.header_len:-2])
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"指令[{cmd_code}]数据包[len={len(packet)}]解析异常: {e}")
|
|
142
|
+
|
|
138
143
|
return instance
|
|
139
144
|
|
|
140
145
|
def start(self):
|
|
@@ -8,7 +8,7 @@ qlsdk/core/local.py,sha256=vbison4XZtS4SNYLJ9CqBhetEcukdviTWmvtdA1efkQ,811
|
|
|
8
8
|
qlsdk/core/utils.py,sha256=yfCiLpufO96I68MLs6Drc6IECRjcQ-If8sXn7RaRHrk,4241
|
|
9
9
|
qlsdk/core/crc/__init__.py,sha256=kaYSr6KN5g4U49xlxAvT2lnEeGtwX4Dz1ArwKDvUIIY,143
|
|
10
10
|
qlsdk/core/crc/crctools.py,sha256=sDeE6CMccQX2cRAyMQK0SZUk1fa50XMuwqXau5UX5C8,4242
|
|
11
|
-
qlsdk/core/entity/__init__.py,sha256=
|
|
11
|
+
qlsdk/core/entity/__init__.py,sha256=hM694aCfVFMvs3OeX-CWszN6xfel8NQtfJjEdpy-5rk,8669
|
|
12
12
|
qlsdk/core/filter/__init__.py,sha256=YIWIzDUKN30mq2JTr53ZGblggZfC_rLUp2FSRrsQFgU,36
|
|
13
13
|
qlsdk/core/filter/norch.py,sha256=5RdIBX5eqs5w5nmVAnCB3ESSuAT_vVBZ2g-dg6HMZdY,1858
|
|
14
14
|
qlsdk/core/message/__init__.py,sha256=sHuavOyHf4bhH6VdDpTA1EsCh7Q-XsPHcFiItpVz3Rs,51
|
|
@@ -28,7 +28,7 @@ qlsdk/interface/parser.py,sha256=00Ds01Xp9eUPwx7okL4B1Bl0_fRbgaxfm_2GOeWoZiM,248
|
|
|
28
28
|
qlsdk/interface/stimulator.py,sha256=MJbgiI-4qTMjbdhcBAZe8zb10hV9x8yhFCPL3Iq_QyI,28
|
|
29
29
|
qlsdk/interface/store.py,sha256=694WQnnrtXThP44JkQlBXzghvqA0PwHKK3uA640aiUo,23
|
|
30
30
|
qlsdk/persist/__init__.py,sha256=b8qk1aOU6snEMCQNYDl1ijV3-2gwBmMt76fiAzNk1E8,107
|
|
31
|
-
qlsdk/persist/ars_edf.py,sha256=
|
|
31
|
+
qlsdk/persist/ars_edf.py,sha256=aY_bGM2QsqpKAAJqs6kR7hBVpq8PaayNtcqeLJlt1Fc,9138
|
|
32
32
|
qlsdk/persist/edf.py,sha256=ETngb86CfkIUJYWmw86QR445MvTFC7Edk_CH9nyNgtY,7857
|
|
33
33
|
qlsdk/persist/rsc_edf.py,sha256=djR_qXScb_XOOXaoMsvnK_qO2NCPzqEeKxKJCm4xnpk,13989
|
|
34
34
|
qlsdk/persist/stream.py,sha256=TCVF1sqDrHiYBsJC27At66AaCs-_blXeXA_WXdJiIVA,5828
|
|
@@ -37,13 +37,13 @@ qlsdk/rsc/device_manager.py,sha256=1ucd-lzHkNeQPKPzXV6OBkAMqPp_vOcsLyS-9TJ7wRc,4
|
|
|
37
37
|
qlsdk/rsc/discover.py,sha256=ONXN6YWY-OMU0sBoLqqKUyb-8drtAp1g_MvnpzaFvHQ,3124
|
|
38
38
|
qlsdk/rsc/eegion.py,sha256=lxrktO-3Z_MYdFIwc4NxvgLM5AL5kU3UItjH6tsKmHY,11670
|
|
39
39
|
qlsdk/rsc/entity.py,sha256=-fRWFkVWp9d8Y1uh6GiacXC5scdeEKNiNFf3aziGdCE,17751
|
|
40
|
-
qlsdk/rsc/paradigm.py,sha256=
|
|
40
|
+
qlsdk/rsc/paradigm.py,sha256=WH9kAez0wbci-B1z5BC-nw3nqqOtE_txFILue4TM-cE,17644
|
|
41
41
|
qlsdk/rsc/proxy.py,sha256=9CPdGNGWremwBUh4GvlXAykYB-x_BEPPLqsNvwuwIDE,2736
|
|
42
42
|
qlsdk/rsc/command/__init__.py,sha256=iPrPor7NwBIrkQDxz061gVbnlLYK9KE7VXy-NKJy1XQ,12277
|
|
43
43
|
qlsdk/rsc/command/message.py,sha256=nTdG-Vp4MBnltyrgedAWiKD6kzOaPrg58Z_hq6yjhys,12220
|
|
44
44
|
qlsdk/rsc/device/__init__.py,sha256=BzY9lRfssGPUlJ1ys-v3CWNgGihg7mPa2T4X0tl0Vg4,214
|
|
45
|
-
qlsdk/rsc/device/arskindling.py,sha256=
|
|
46
|
-
qlsdk/rsc/device/base.py,sha256=
|
|
45
|
+
qlsdk/rsc/device/arskindling.py,sha256=C67dDTFmytTI2tAAaWxwdXfQ-S1IHS2lIOH0B4UQLQA,9854
|
|
46
|
+
qlsdk/rsc/device/base.py,sha256=GZx09zp-T61VwRIH6zEj3LMh-YNupxu5O8tMEBHRzOQ,21533
|
|
47
47
|
qlsdk/rsc/device/c16_rs.py,sha256=qXt8m5vwcKQsN8JBllWnAsda5_Y6qkEhfHQQX101TMQ,5826
|
|
48
48
|
qlsdk/rsc/device/c256_rs.py,sha256=7vAEzf_ggNcwrXKmcZMylnKzLFD5ZqtAIfkkI3lQ1iI,1682
|
|
49
49
|
qlsdk/rsc/device/c64_rs.py,sha256=x8wHdwATKDU34j9vXNEXsNSJg23RAWmAKL8pgIGamG8,1091
|
|
@@ -51,7 +51,7 @@ qlsdk/rsc/device/c64s1.py,sha256=WwiKSjxYpUJVkHDMDzPgp7-klbaiZ2f8EOe3wV6d2WU,109
|
|
|
51
51
|
qlsdk/rsc/device/device_factory.py,sha256=6cPhm3pPGrVXA1s1HePFLjZqmhNI1vOAucFI0VRD_Y0,1317
|
|
52
52
|
qlsdk/rsc/interface/__init__.py,sha256=xeRzIlQSB7ZSf4r5kLfH5cDQLzCyWeJAReG8Xq5nOE0,70
|
|
53
53
|
qlsdk/rsc/interface/command.py,sha256=1s5Lxb_ejsd-JNvKMqU2aFSnOoW-_cx01VSD3czxmQI,199
|
|
54
|
-
qlsdk/rsc/interface/device.py,sha256=
|
|
54
|
+
qlsdk/rsc/interface/device.py,sha256=tOS9EzTC4nty8O4W4E-HR9gdn2hwfdj_2zvb87_bdfM,2417
|
|
55
55
|
qlsdk/rsc/interface/handler.py,sha256=ADDe_a2RAxGMuooLyivH0JBPTGBcFP2JaTVX41R1A4w,198
|
|
56
56
|
qlsdk/rsc/interface/parser.py,sha256=Z4PND5LXcJ_8CQ-OIq3KlOEVOceU1hKUuZkoFSIGGLM,334
|
|
57
57
|
qlsdk/rsc/manager/__init__.py,sha256=4ljT3mR8YPBDQur46B5xPqK5tjLKlsWfgCJVuA0gs-8,40
|
|
@@ -61,7 +61,7 @@ qlsdk/rsc/network/__init__.py,sha256=PfYiqXS2pZV__uegQ1TjaeYhY1pefZ_shwE_X5HNVbs
|
|
|
61
61
|
qlsdk/rsc/network/discover.py,sha256=GRXP0WxxIorDZWXq1X5CPAV60raSRvNiVwMQE8647XA,3044
|
|
62
62
|
qlsdk/rsc/parser/__init__.py,sha256=cVKk06bRYOnwE3XMoksGlatKJSaZE1GVrYypQm-9aro,69
|
|
63
63
|
qlsdk/rsc/parser/base-new.py,sha256=cAOy1V_1fAJyGq7bm7uLxpW41DbkllWOprnfWKpjtsQ,5116
|
|
64
|
-
qlsdk/rsc/parser/base.py,sha256=
|
|
64
|
+
qlsdk/rsc/parser/base.py,sha256=LbRpaa3z6kGL9HtevuEI8Q6O2R4WM5Gxvlx0ZYOndBg,5901
|
|
65
65
|
qlsdk/rsc/parser/rsc.py,sha256=RuBqsg5KZNio7mTyM14svQEeU0_0CLhnRmfbP0NBM2o,4724
|
|
66
66
|
qlsdk/sdk/__init__.py,sha256=v9LKP-5qXCqnAsCkiRE9LDb5Tagvl_Qd_fqrw7y9yd4,68
|
|
67
67
|
qlsdk/sdk/ar4sdk.py,sha256=tugH3UUeNebdka78AzLyrtAXbYQQE3iFJ227zUit6tY,27261
|
|
@@ -70,7 +70,7 @@ qlsdk/sdk/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,
|
|
|
70
70
|
qlsdk/sdk/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
|
|
71
71
|
qlsdk/x8/__init__.py,sha256=FDpDK7GAYL-g3vzfU9U_V03QzoYoxH9YLm93PjMlANg,4870
|
|
72
72
|
qlsdk/x8m/__init__.py,sha256=cLeUqEEj65qXw4Qa4REyxoLh6T24anSqPaKe9_lR340,634
|
|
73
|
-
qlsdk2-0.6.
|
|
74
|
-
qlsdk2-0.6.
|
|
75
|
-
qlsdk2-0.6.
|
|
76
|
-
qlsdk2-0.6.
|
|
73
|
+
qlsdk2-0.6.0a10.dist-info/METADATA,sha256=qhpDHdNop673s66JoRwTli4Gyaago7OzeNzVQT4CZ1I,1883
|
|
74
|
+
qlsdk2-0.6.0a10.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
|
|
75
|
+
qlsdk2-0.6.0a10.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
|
|
76
|
+
qlsdk2-0.6.0a10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|