qlsdk2 0.6.0a6__py3-none-any.whl → 0.6.0a8__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/persist/ars_edf.py +5 -30
- qlsdk/persist/rsc_edf.py +1 -1
- qlsdk/rsc/command/__init__.py +6 -4
- qlsdk/rsc/device/arskindling.py +3 -294
- qlsdk/rsc/device/base.py +56 -38
- qlsdk/rsc/device/c16_rs.py +1 -5
- qlsdk/rsc/device/c256_rs.py +13 -1
- qlsdk/rsc/device/c64s1.py +4 -340
- qlsdk/rsc/interface/device.py +9 -1
- qlsdk/rsc/parser/base.py +1 -1
- {qlsdk2-0.6.0a6.dist-info → qlsdk2-0.6.0a8.dist-info}/METADATA +1 -1
- {qlsdk2-0.6.0a6.dist-info → qlsdk2-0.6.0a8.dist-info}/RECORD +14 -14
- {qlsdk2-0.6.0a6.dist-info → qlsdk2-0.6.0a8.dist-info}/WHEEL +0 -0
- {qlsdk2-0.6.0a6.dist-info → qlsdk2-0.6.0a8.dist-info}/top_level.txt +0 -0
qlsdk/persist/ars_edf.py
CHANGED
|
@@ -195,39 +195,14 @@ class ARSKindlingEDFHandler(object):
|
|
|
195
195
|
if packet is None:
|
|
196
196
|
# self._edf_writer_thread.stop_recording()
|
|
197
197
|
for k in self._edf_handler.keys():
|
|
198
|
-
self._edf_handler[k].
|
|
198
|
+
self._edf_handler[k].write(None)
|
|
199
199
|
return
|
|
200
200
|
|
|
201
|
-
#按分区写入数据
|
|
202
|
-
|
|
201
|
+
#按分区写入数据
|
|
203
202
|
for k in self._channel_spilt.keys():
|
|
204
|
-
logger.
|
|
203
|
+
logger.trace(f'分区{k}, {self._channel_spilt[k]}')
|
|
205
204
|
p = packet
|
|
206
205
|
self.writeA(p, self._channel_spilt[k], k)
|
|
207
|
-
|
|
208
|
-
# with self._lock:
|
|
209
|
-
# if self.channels is None:
|
|
210
|
-
# logger.info(f"开始记录数据到文件...")
|
|
211
|
-
# self.channels = packet.channels
|
|
212
|
-
# self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
|
|
213
|
-
# self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
|
|
214
|
-
# self._start_time = datetime.now()
|
|
215
|
-
# logger.info(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
|
|
216
|
-
|
|
217
|
-
# if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
|
|
218
|
-
# self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
|
|
219
|
-
# logger.warning(f"数据包丢失: {self._last_pkg_id} -> {packet.pkg_id}, 丢包数: {packet.pkg_id - self._last_pkg_id - 1}")
|
|
220
|
-
|
|
221
|
-
# self._last_pkg_id = packet.pkg_id
|
|
222
|
-
# self._total_packets += 1
|
|
223
|
-
|
|
224
|
-
# if self._edf_writer_thread is None:
|
|
225
|
-
# self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name)
|
|
226
|
-
# self._edf_writer_thread.set_start_time(self._start_time)
|
|
227
|
-
# self._edf_writer_thread.start()
|
|
228
|
-
# logger.info(f"开始写入数据: {self.file_name}")
|
|
229
|
-
|
|
230
|
-
# self._edf_writer_thread.append(packet.eeg)
|
|
231
206
|
|
|
232
207
|
def writeA(self, packet: RscPacket, channel_filter, name='A'):
|
|
233
208
|
# 参数检查
|
|
@@ -238,12 +213,12 @@ class ARSKindlingEDFHandler(object):
|
|
|
238
213
|
channel_pos = intersection_positions(packet.channels, channel_filter)
|
|
239
214
|
|
|
240
215
|
if channel_pos is None or len(channel_pos) == 0 :
|
|
241
|
-
logger.
|
|
216
|
+
logger.trace(f"没有指定分区{name}的通道,跳过")
|
|
242
217
|
pass
|
|
243
218
|
|
|
244
219
|
# 分区数据包写入
|
|
245
220
|
if name not in self._edf_handler.keys():
|
|
246
|
-
edf_handler = RscEDFHandler(self.sample_rate, self.
|
|
221
|
+
edf_handler = RscEDFHandler(self.sample_rate, self.physical_max , self.physical_min, self.resolution)
|
|
247
222
|
edf_handler.set_device_type(self._device_type)
|
|
248
223
|
edf_handler.set_device_no(self._device_no)
|
|
249
224
|
edf_handler.set_storage_path(self._storage_path)
|
qlsdk/persist/rsc_edf.py
CHANGED
qlsdk/rsc/command/__init__.py
CHANGED
|
@@ -243,8 +243,9 @@ class StartStimulationCommand(DeviceCommand):
|
|
|
243
243
|
# error_channel - 8B
|
|
244
244
|
# error_channel= int.from_bytes(body[9:17], 'big')
|
|
245
245
|
channels = to_channels(body[9:17])
|
|
246
|
-
logger.
|
|
247
|
-
self.device
|
|
246
|
+
logger.info(f"通道 {channels} 刺激开始")
|
|
247
|
+
if self.device:
|
|
248
|
+
self.device.trigger(f"通道 {channels} 刺激开始")
|
|
248
249
|
# error_type - 1B
|
|
249
250
|
error_type = body[17]
|
|
250
251
|
|
|
@@ -268,8 +269,9 @@ class StopStimulationNotifyCommand(DeviceCommand):
|
|
|
268
269
|
# error_channel - 8B
|
|
269
270
|
# error_channel= int.from_bytes(body[9:17], 'big')
|
|
270
271
|
channels = to_channels(body[9:17])
|
|
271
|
-
logger.
|
|
272
|
-
self.device
|
|
272
|
+
logger.info(f"通道 {channels} 刺激结束")
|
|
273
|
+
if self.device:
|
|
274
|
+
self.device.trigger(f"通道 {channels} 刺激结束", time)
|
|
273
275
|
# error_type - 1B
|
|
274
276
|
error_type = body[17]
|
|
275
277
|
# 刺激信息
|
qlsdk/rsc/device/arskindling.py
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
from multiprocessing import Queue
|
|
3
|
-
from threading import Thread
|
|
4
|
-
from time import sleep, time_ns
|
|
5
|
-
from typing import Any, Dict, Literal
|
|
6
|
-
|
|
7
1
|
from loguru import logger
|
|
8
2
|
from qlsdk.persist import ARSKindlingEDFHandler
|
|
9
|
-
from qlsdk.rsc.interface import IDevice
|
|
3
|
+
from qlsdk.rsc.interface import IDevice
|
|
10
4
|
from qlsdk.rsc.command import *
|
|
11
|
-
from qlsdk.rsc.parser.base import TcpMessageParser
|
|
12
5
|
from qlsdk.rsc.device.base import QLBaseDevice
|
|
13
6
|
|
|
14
7
|
class ARSKindling(QLBaseDevice):
|
|
@@ -17,117 +10,6 @@ class ARSKindling(QLBaseDevice):
|
|
|
17
10
|
|
|
18
11
|
def __init__(self, socket):
|
|
19
12
|
super().__init__(socket)
|
|
20
|
-
# self.socket = socket
|
|
21
|
-
|
|
22
|
-
self._id = None
|
|
23
|
-
|
|
24
|
-
# 设备信息
|
|
25
|
-
# self.device_id = None
|
|
26
|
-
# self.device_name = None
|
|
27
|
-
self._device_no = None
|
|
28
|
-
# self.software_version = None
|
|
29
|
-
# self.hardware_version = None
|
|
30
|
-
# self.connect_time = None
|
|
31
|
-
# self.current_time = None
|
|
32
|
-
# # mV
|
|
33
|
-
# self.voltage = None
|
|
34
|
-
# # %
|
|
35
|
-
# self.battery_remain = None
|
|
36
|
-
# # %
|
|
37
|
-
# self.battery_total = None
|
|
38
|
-
# persist
|
|
39
|
-
self._recording = False
|
|
40
|
-
self._storage_path = None
|
|
41
|
-
self._file_prefix = None
|
|
42
|
-
|
|
43
|
-
# 可设置参数
|
|
44
|
-
# 采集:采样量程、采样率、采样通道
|
|
45
|
-
# 刺激:刺激电流、刺激频率、刺激时间、刺激通道
|
|
46
|
-
# 采样量程(mV):188、375、563、750、1125、2250、4500
|
|
47
|
-
self._sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188
|
|
48
|
-
# 采样率(Hz):250、500、1000、2000、4000、8000、16000、32000
|
|
49
|
-
self._sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500
|
|
50
|
-
self._physical_max = self._sample_range * 1000 # 物理最大值(uV)
|
|
51
|
-
self._physical_min = -self._sample_range * 1000 # 物理最小值(uV)
|
|
52
|
-
self._digital_max = 8388607
|
|
53
|
-
self._digital_min = -8388608
|
|
54
|
-
self._physical_range = self._physical_max - self._physical_min
|
|
55
|
-
self._digital_range = 16777215
|
|
56
|
-
self._acq_channels = None
|
|
57
|
-
self._acq_param = {
|
|
58
|
-
"sample_range": 188,
|
|
59
|
-
"sample_rate": 500,
|
|
60
|
-
"channels": [],
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
self._stim_param = {
|
|
64
|
-
"stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
|
|
65
|
-
"channels": [],
|
|
66
|
-
"param": [{
|
|
67
|
-
"channel_id": 0, #通道号 从0开始 -- 必填
|
|
68
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
69
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
70
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
71
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
72
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
73
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
74
|
-
"phase_position": 0, #相位 -- 默认0
|
|
75
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
76
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"channel_id": 1, #通道号 从0开始 -- 必填
|
|
80
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
81
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
82
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
83
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
84
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
85
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
86
|
-
"phase_position": 0, #相位 -- 默认0
|
|
87
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
88
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
self.stim_paradigm = None
|
|
94
|
-
|
|
95
|
-
signal_info = {
|
|
96
|
-
"param" : None,
|
|
97
|
-
"start_time" : None,
|
|
98
|
-
"finished_time" : None,
|
|
99
|
-
"packet_total" : None,
|
|
100
|
-
"last_packet_time" : None,
|
|
101
|
-
"state" : 0
|
|
102
|
-
}
|
|
103
|
-
stim_info = {
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
Impedance_info = {
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
# 信号采集状态
|
|
110
|
-
# 信号数据包总数(一个信号采集周期内)
|
|
111
|
-
# 信号采集参数
|
|
112
|
-
# 电刺激状态
|
|
113
|
-
# 电刺激开始时间(最近一次)
|
|
114
|
-
# 电刺激结束时间(最近一次)
|
|
115
|
-
# 电刺激参数
|
|
116
|
-
# 启动数据解析线程
|
|
117
|
-
# 数据存储状态
|
|
118
|
-
# 存储目录
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
self.__signal_consumer: Dict[str, Queue[Any]]={}
|
|
122
|
-
self.__impedance_consumer: Dict[str, Queue[Any]]={}
|
|
123
|
-
|
|
124
|
-
# EDF文件处理器
|
|
125
|
-
self._edf_handler = None
|
|
126
|
-
self.storage_enable = True
|
|
127
|
-
|
|
128
|
-
# self.start_message_listening()
|
|
129
|
-
# self.start()
|
|
130
|
-
# self.parser.set_device(self)
|
|
131
13
|
|
|
132
14
|
self.channel_mapping = {
|
|
133
15
|
"A1": 55, "A2": 56, "A3": 53, "A4": 54, "A5": 51, "A6": 52, "A7": 49, "A8": 50,
|
|
@@ -143,18 +25,6 @@ class ARSKindling(QLBaseDevice):
|
|
|
143
25
|
"D9": 9, "D10": 10, "D11": 11, "D12": 12, "D13": 13, "D14": 14,
|
|
144
26
|
}
|
|
145
27
|
|
|
146
|
-
@property
|
|
147
|
-
def device_no(self):
|
|
148
|
-
return self._device_no
|
|
149
|
-
|
|
150
|
-
@device_no.setter
|
|
151
|
-
def device_no(self, value: str):
|
|
152
|
-
self._device_no = value
|
|
153
|
-
|
|
154
|
-
@property
|
|
155
|
-
def parser(self) -> IParser:
|
|
156
|
-
return self._parser
|
|
157
|
-
|
|
158
28
|
@classmethod
|
|
159
29
|
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
160
30
|
rlt = cls(parent.socket)
|
|
@@ -168,68 +38,8 @@ class ARSKindling(QLBaseDevice):
|
|
|
168
38
|
self._edf_handler.set_device_no(self.device_no)
|
|
169
39
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
170
40
|
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'ARS')
|
|
41
|
+
logger.debug(f"EDF Handler initialized")
|
|
171
42
|
|
|
172
|
-
@property
|
|
173
|
-
def edf_handler(self):
|
|
174
|
-
if not self.storage_enable:
|
|
175
|
-
return None
|
|
176
|
-
|
|
177
|
-
if self._edf_handler is None:
|
|
178
|
-
self.init_edf_handler()
|
|
179
|
-
|
|
180
|
-
return self._edf_handler
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def acq_channels(self):
|
|
184
|
-
if self._acq_channels is None:
|
|
185
|
-
self._acq_channels = [i for i in range(1, 63)]
|
|
186
|
-
return self._acq_channels
|
|
187
|
-
@property
|
|
188
|
-
def sample_range(self):
|
|
189
|
-
return self._sample_range if self._sample_range else 188
|
|
190
|
-
@property
|
|
191
|
-
def sample_rate(self):
|
|
192
|
-
return self._sample_rate if self._sample_rate else 500
|
|
193
|
-
@property
|
|
194
|
-
def resolution(self):
|
|
195
|
-
return 24
|
|
196
|
-
|
|
197
|
-
@property
|
|
198
|
-
def signal_consumers(self):
|
|
199
|
-
return self.__signal_consumer
|
|
200
|
-
|
|
201
|
-
@property
|
|
202
|
-
def impedance_consumers(self):
|
|
203
|
-
return self.__impedance_consumer
|
|
204
|
-
|
|
205
|
-
# 设置记录文件路径
|
|
206
|
-
def set_storage_path(self, path):
|
|
207
|
-
self._storage_path = path
|
|
208
|
-
|
|
209
|
-
# 设置记录文件名称前缀
|
|
210
|
-
def set_file_prefix(self, prefix):
|
|
211
|
-
self._file_prefix = prefix
|
|
212
|
-
|
|
213
|
-
# 接收socket消息
|
|
214
|
-
def accept(self):
|
|
215
|
-
while True:
|
|
216
|
-
# 缓冲去4M
|
|
217
|
-
data = self.socket.recv(4096*1024)
|
|
218
|
-
if not data:
|
|
219
|
-
logger.warning(f"设备{self.device_no}连接结束")
|
|
220
|
-
break
|
|
221
|
-
|
|
222
|
-
self._parser.append(data)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# socket发送数据
|
|
226
|
-
def send(self, data):
|
|
227
|
-
self.socket.sendall(data)
|
|
228
|
-
|
|
229
|
-
# 设置刺激参数
|
|
230
|
-
def set_stim_param(self, param):
|
|
231
|
-
self.stim_paradigm = param
|
|
232
|
-
|
|
233
43
|
# 设置采集参数
|
|
234
44
|
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
235
45
|
self._acq_param["original_channels"] = channels
|
|
@@ -249,108 +59,7 @@ class ARSKindling(QLBaseDevice):
|
|
|
249
59
|
self._acq_param["sample_range"] = sample_range
|
|
250
60
|
self._acq_channels = channels
|
|
251
61
|
self._sample_rate = sample_rate
|
|
252
|
-
self._sample_range = sample_range
|
|
253
|
-
|
|
254
|
-
# 通用配置-TODO
|
|
255
|
-
def set_config(self, key:str, val: str):
|
|
256
|
-
pass
|
|
257
|
-
|
|
258
|
-
def start_impedance(self):
|
|
259
|
-
logger.info("启动阻抗测量")
|
|
260
|
-
msg = StartImpedanceCommand.build(self).pack()
|
|
261
|
-
logger.debug(f"start_impedance message is {msg.hex()}")
|
|
262
|
-
self.socket.sendall(msg)
|
|
263
|
-
|
|
264
|
-
def stop_impedance(self):
|
|
265
|
-
logger.info("停止阻抗测量")
|
|
266
|
-
msg = StopImpedanceCommand.build(self).pack()
|
|
267
|
-
logger.debug(f"stop_impedance message is {msg.hex()}")
|
|
268
|
-
self.socket.sendall(msg)
|
|
269
|
-
|
|
270
|
-
def start_stimulation(self):
|
|
271
|
-
if self.stim_paradigm is None:
|
|
272
|
-
logger.warning("刺激参数未设置,请先设置刺激参数")
|
|
273
|
-
return
|
|
274
|
-
logger.info("启动电刺激")
|
|
275
|
-
msg = StartStimulationCommand.build(self).pack()
|
|
276
|
-
logger.debug(f"start_stimulation message is {msg.hex()}")
|
|
277
|
-
self.socket.sendall(msg)
|
|
278
|
-
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
|
|
279
|
-
t.start()
|
|
280
|
-
|
|
281
|
-
def _stop_stimulation_trigger(self, duration):
|
|
282
|
-
delay = duration
|
|
283
|
-
while delay > 0:
|
|
284
|
-
sleep(1)
|
|
285
|
-
delay -= 1
|
|
286
|
-
logger.info(f"_stop_stimulation_trigger duration: {duration}")
|
|
287
|
-
if self._edf_handler:
|
|
288
|
-
self._edf_handler.trigger("stimulation should be stopped")
|
|
289
|
-
else:
|
|
290
|
-
logger.warning("stop stim trigger fail. no edf writer alive")
|
|
291
|
-
|
|
292
|
-
def stop_stimulation(self):
|
|
293
|
-
logger.info("停止电刺激")
|
|
294
|
-
msg = StopStimulationCommand.pack()
|
|
295
|
-
logger.debug(f"stop_stimulation message is {msg.hex()}")
|
|
296
|
-
self.socket.sendall(msg)
|
|
297
|
-
|
|
298
|
-
# 启动采集
|
|
299
|
-
def start_acquisition(self, recording = True):
|
|
300
|
-
logger.info("启动信号采集")
|
|
301
|
-
self._recording = recording
|
|
302
|
-
# 设置数据采集参数
|
|
303
|
-
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
304
|
-
# 启动数据采集
|
|
305
|
-
start_bytes = StartAcquisitionCommand.build(self).pack()
|
|
306
|
-
msg = param_bytes + start_bytes
|
|
307
|
-
logger.debug(f"start_acquisition message is {msg.hex()}")
|
|
308
|
-
self.socket.sendall(msg)
|
|
309
|
-
|
|
310
|
-
# 停止采集
|
|
311
|
-
def stop_acquisition(self):
|
|
312
|
-
logger.info("停止信号采集")
|
|
313
|
-
msg = StopAcquisitionCommand.build(self).pack()
|
|
314
|
-
logger.debug(f"stop_acquisition message is {msg.hex()}")
|
|
315
|
-
self.socket.sendall(msg)
|
|
316
|
-
if self._edf_handler:
|
|
317
|
-
# 发送结束标识
|
|
318
|
-
self.edf_handler.write(None)
|
|
319
|
-
|
|
320
|
-
# 订阅实时数据
|
|
321
|
-
def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
|
|
322
|
-
logger.info(f"订阅{self.device_no}的数据流")
|
|
323
|
-
# 数据队列
|
|
324
|
-
if q is None:
|
|
325
|
-
q = Queue(maxsize=1000)
|
|
326
|
-
|
|
327
|
-
# 队列名称
|
|
328
|
-
if topic is None:
|
|
329
|
-
topic = f"{type}_{time_ns()}"
|
|
330
|
-
|
|
331
|
-
# 订阅生理电信号数据
|
|
332
|
-
if type == "signal":
|
|
333
|
-
# topic唯一,用来区分不同的订阅队列(下同)
|
|
334
|
-
if topic in list(self.__signal_consumer.keys()):
|
|
335
|
-
logger.warning(f"exists {type} subscribe of {topic}")
|
|
336
|
-
else:
|
|
337
|
-
self.__signal_consumer[topic] = q
|
|
338
|
-
|
|
339
|
-
# 订阅阻抗数据
|
|
340
|
-
if type == "impedance":
|
|
341
|
-
if topic in list(self.__signal_consumer.keys()):
|
|
342
|
-
logger.warning(f"exists {type} subscribe of {topic}")
|
|
343
|
-
else:
|
|
344
|
-
self.__impedance_consumer[topic] = q
|
|
345
|
-
|
|
346
|
-
return topic, q
|
|
347
|
-
|
|
348
|
-
def trigger(self, desc):
|
|
349
|
-
if self._edf_handler:
|
|
350
|
-
self.edf_handler.trigger(desc)
|
|
351
|
-
else:
|
|
352
|
-
logger.warning("no edf handler, no place to recording trigger")
|
|
353
|
-
|
|
62
|
+
self._sample_range = sample_range
|
|
354
63
|
|
|
355
64
|
def gen_set_acquirement_param(self) -> bytes:
|
|
356
65
|
|
qlsdk/rsc/device/base.py
CHANGED
|
@@ -93,21 +93,6 @@ class QLBaseDevice(IDevice):
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
self.stim_paradigm: StimulationParadigm = None
|
|
96
|
-
|
|
97
|
-
signal_info = {
|
|
98
|
-
"param" : None,
|
|
99
|
-
"start_time" : None,
|
|
100
|
-
"finished_time" : None,
|
|
101
|
-
"packet_total" : None,
|
|
102
|
-
"last_packet_time" : None,
|
|
103
|
-
"state" : 0
|
|
104
|
-
}
|
|
105
|
-
stim_info = {
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
Impedance_info = {
|
|
109
|
-
|
|
110
|
-
}
|
|
111
96
|
# 信号采集状态
|
|
112
97
|
# 信号数据包总数(一个信号采集周期内)
|
|
113
98
|
# 信号采集参数
|
|
@@ -128,6 +113,8 @@ class QLBaseDevice(IDevice):
|
|
|
128
113
|
self.storage_enable = True
|
|
129
114
|
self._listening = False
|
|
130
115
|
# self.ready()
|
|
116
|
+
self._signal_cache: Queue = None
|
|
117
|
+
self._recording = False
|
|
131
118
|
|
|
132
119
|
def parser(self) -> IParser:
|
|
133
120
|
return self._parser
|
|
@@ -166,9 +153,16 @@ class QLBaseDevice(IDevice):
|
|
|
166
153
|
for pos in trigger_positions:
|
|
167
154
|
self.trigger(data.trigger[pos])
|
|
168
155
|
# 存储
|
|
169
|
-
if self.storage_enable:
|
|
170
|
-
|
|
171
|
-
|
|
156
|
+
if self.storage_enable:
|
|
157
|
+
# 确保记录线程启动
|
|
158
|
+
if self._recording is False:
|
|
159
|
+
self._start_recording()
|
|
160
|
+
|
|
161
|
+
# 写入文件的缓存队列
|
|
162
|
+
if self._signal_cache is None:
|
|
163
|
+
self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
|
|
164
|
+
self._signal_cache.put(data)
|
|
165
|
+
|
|
172
166
|
if len(self.signal_consumers) > 0 :
|
|
173
167
|
logger.trace(f"dg eeg: {data.eeg}")
|
|
174
168
|
# 信号数字值转物理值
|
|
@@ -187,14 +181,35 @@ class QLBaseDevice(IDevice):
|
|
|
187
181
|
def _signal_wrapper(self, body: bytes):
|
|
188
182
|
return RscPacket().transfer(body)
|
|
189
183
|
|
|
190
|
-
def _write_signal(self
|
|
184
|
+
def _write_signal(self):
|
|
191
185
|
# 文件写入到edf
|
|
192
186
|
if self._edf_handler is None:
|
|
193
187
|
logger.debug("Initializing EDF handler for data storage")
|
|
194
188
|
self.init_edf_handler()
|
|
195
189
|
|
|
196
|
-
|
|
190
|
+
while self._recording:
|
|
191
|
+
data = self._signal_cache.get()
|
|
197
192
|
self._edf_handler.write(data)
|
|
193
|
+
if data is None:
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
self._recording = False
|
|
197
|
+
def _start_recording(self):
|
|
198
|
+
if self.storage_enable is False:
|
|
199
|
+
logger.trace("Storage is disabled, will not start recording")
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
if self._signal_cache is None:
|
|
203
|
+
self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
self._recording = True
|
|
207
|
+
t = Thread(target=self._write_signal, daemon=True)
|
|
208
|
+
t.start()
|
|
209
|
+
logger.info(f"开启记录")
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"开启记录失败: {str(e)}")
|
|
212
|
+
return
|
|
198
213
|
|
|
199
214
|
def start_listening(self):
|
|
200
215
|
|
|
@@ -305,7 +320,7 @@ class QLBaseDevice(IDevice):
|
|
|
305
320
|
@property
|
|
306
321
|
def edf_handler(self):
|
|
307
322
|
if not self.storage_enable:
|
|
308
|
-
logger.
|
|
323
|
+
logger.info("已关闭文件记录,不会生成edf/bdf文件")
|
|
309
324
|
return None
|
|
310
325
|
|
|
311
326
|
if self._edf_handler is None:
|
|
@@ -339,8 +354,12 @@ class QLBaseDevice(IDevice):
|
|
|
339
354
|
return self._impedance_consumer
|
|
340
355
|
|
|
341
356
|
# 设置记录文件路径
|
|
342
|
-
def set_storage_path(self,
|
|
343
|
-
|
|
357
|
+
def set_storage_path(self, dir: str):
|
|
358
|
+
import os
|
|
359
|
+
|
|
360
|
+
abs_path = os.path.abspath(dir)
|
|
361
|
+
os.makedirs(abs_path, exist_ok=True)
|
|
362
|
+
self._storage_path = abs_path
|
|
344
363
|
|
|
345
364
|
# 设置记录文件名称前缀
|
|
346
365
|
def set_file_prefix(self, prefix):
|
|
@@ -362,10 +381,6 @@ class QLBaseDevice(IDevice):
|
|
|
362
381
|
self._acq_channels = channels
|
|
363
382
|
self._sample_rate = sample_rate
|
|
364
383
|
self._sample_range = sample_range
|
|
365
|
-
|
|
366
|
-
# 通用配置-TODO
|
|
367
|
-
def set_config(self, key:str, val: str):
|
|
368
|
-
pass
|
|
369
384
|
|
|
370
385
|
def start_impedance(self):
|
|
371
386
|
logger.info(f"[设备-{self.device_no}]启动阻抗测量")
|
|
@@ -399,8 +414,8 @@ class QLBaseDevice(IDevice):
|
|
|
399
414
|
sleep(1)
|
|
400
415
|
delay -= 1
|
|
401
416
|
logger.debug(f"_stop_stimulation_trigger duration: {duration}")
|
|
402
|
-
if self.
|
|
403
|
-
self.
|
|
417
|
+
if self.edf_handler:
|
|
418
|
+
self.edf_handler.trigger("stimulation should be stopped")
|
|
404
419
|
else:
|
|
405
420
|
logger.warning("stop stim trigger fail. no edf writer alive")
|
|
406
421
|
|
|
@@ -411,11 +426,10 @@ class QLBaseDevice(IDevice):
|
|
|
411
426
|
self.socket.sendall(msg)
|
|
412
427
|
|
|
413
428
|
# 启动采集
|
|
414
|
-
def start_acquisition(self
|
|
429
|
+
def start_acquisition(self):
|
|
415
430
|
logger.info(f"[设备-{self.device_no}]启动信号采集")
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
self.init_edf_handler()
|
|
431
|
+
# 记录准备
|
|
432
|
+
self._start_recording()
|
|
419
433
|
# 设置数据采集参数
|
|
420
434
|
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
421
435
|
# 启动数据采集
|
|
@@ -430,9 +444,10 @@ class QLBaseDevice(IDevice):
|
|
|
430
444
|
msg = StopAcquisitionCommand.build(self).pack()
|
|
431
445
|
logger.trace(f"stop_acquisition message is {msg.hex()}")
|
|
432
446
|
self.socket.sendall(msg)
|
|
433
|
-
|
|
447
|
+
# 结束标识
|
|
448
|
+
if self._signal_cache:
|
|
434
449
|
# 发送结束标识
|
|
435
|
-
self.
|
|
450
|
+
self._signal_cache.put(None)
|
|
436
451
|
|
|
437
452
|
'''
|
|
438
453
|
订阅数据
|
|
@@ -450,7 +465,7 @@ class QLBaseDevice(IDevice):
|
|
|
450
465
|
|
|
451
466
|
# 数据队列
|
|
452
467
|
if q is None:
|
|
453
|
-
q = Queue(maxsize=1024 * 1024)
|
|
468
|
+
q = Queue(maxsize=100 * 1024 * 1024)
|
|
454
469
|
|
|
455
470
|
# 订阅生理电信号数据
|
|
456
471
|
if type == "signal":
|
|
@@ -470,10 +485,10 @@ class QLBaseDevice(IDevice):
|
|
|
470
485
|
return topic, q
|
|
471
486
|
|
|
472
487
|
def trigger(self, desc):
|
|
473
|
-
if self.
|
|
488
|
+
if self.edf_handler:
|
|
474
489
|
self.edf_handler.trigger(desc)
|
|
475
490
|
else:
|
|
476
|
-
logger.
|
|
491
|
+
logger.info("已关闭文件记录,不会记录trigger信息")
|
|
477
492
|
|
|
478
493
|
def gen_set_acquirement_param(self) -> bytes:
|
|
479
494
|
|
|
@@ -499,6 +514,9 @@ class QLBaseDevice(IDevice):
|
|
|
499
514
|
|
|
500
515
|
# 关闭解析器
|
|
501
516
|
self._parser.stop()
|
|
517
|
+
|
|
518
|
+
def enable_storage(self, enable: bool = True):
|
|
519
|
+
self.storage_enable = enable
|
|
502
520
|
|
|
503
521
|
def __str__(self):
|
|
504
522
|
return f'''
|
qlsdk/rsc/device/c16_rs.py
CHANGED
|
@@ -115,11 +115,7 @@ class C16RS(QLBaseDevice):
|
|
|
115
115
|
self._edf_handler.set_device_no(self.device_no)
|
|
116
116
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
117
117
|
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C16R')
|
|
118
|
-
|
|
119
|
-
# 设置刺激参数
|
|
120
|
-
def set_stim_param(self, param):
|
|
121
|
-
self.stim_paradigm = param
|
|
122
|
-
|
|
118
|
+
|
|
123
119
|
# 设置采集参数
|
|
124
120
|
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
125
121
|
# 保存原始通道参数
|
qlsdk/rsc/device/c256_rs.py
CHANGED
|
@@ -13,6 +13,13 @@ class C256RS(QLBaseDevice):
|
|
|
13
13
|
def __init__(self, socket):
|
|
14
14
|
super().__init__(socket)
|
|
15
15
|
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
18
|
+
rlt = cls(parent.socket)
|
|
19
|
+
rlt.device_id = parent.device_id
|
|
20
|
+
rlt._device_no = parent.device_no
|
|
21
|
+
return rlt
|
|
22
|
+
|
|
16
23
|
def init_edf_handler(self):
|
|
17
24
|
self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
|
|
18
25
|
self._edf_handler.set_device_type(self.device_type)
|
|
@@ -20,7 +27,12 @@ class C256RS(QLBaseDevice):
|
|
|
20
27
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
21
28
|
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C256RS')
|
|
22
29
|
logger.debug(f"EDF Handler initialized")
|
|
23
|
-
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def acq_channels(self):
|
|
33
|
+
if self._acq_channels is None:
|
|
34
|
+
self._acq_channels = [i for i in range(1, 256)]
|
|
35
|
+
return self._acq_channels
|
|
24
36
|
|
|
25
37
|
def _signal_wrapper(self, body: bytes):
|
|
26
38
|
return C256RSPacket().transfer(body)
|
qlsdk/rsc/device/c64s1.py
CHANGED
|
@@ -1,145 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
from multiprocessing import Queue
|
|
3
|
-
from threading import Thread
|
|
4
|
-
from time import sleep, time_ns
|
|
5
|
-
from typing import Any, Dict, Literal
|
|
6
|
-
|
|
7
1
|
from loguru import logger
|
|
8
2
|
from qlsdk.persist import RscEDFHandler
|
|
9
|
-
from qlsdk.rsc.interface import IDevice
|
|
3
|
+
from qlsdk.rsc.interface import IDevice
|
|
10
4
|
from qlsdk.rsc.command import *
|
|
11
|
-
from qlsdk.rsc.parser.base import TcpMessageParser
|
|
12
5
|
from qlsdk.rsc.device.base import QLBaseDevice
|
|
13
6
|
|
|
14
7
|
class C64S1(QLBaseDevice):
|
|
15
8
|
|
|
16
|
-
device_type = 0x40 # C64RS设备类型标识符
|
|
9
|
+
device_type = 0x40 # C64RS-S1设备类型标识符
|
|
17
10
|
|
|
18
11
|
def __init__(self, socket):
|
|
19
12
|
super().__init__(socket)
|
|
20
|
-
self.socket = socket
|
|
21
|
-
|
|
22
|
-
self._id = None
|
|
23
|
-
|
|
24
|
-
# 设备信息
|
|
25
|
-
self.device_id = None
|
|
26
|
-
self.device_name = None
|
|
27
|
-
self._device_no = None
|
|
28
|
-
self.software_version = None
|
|
29
|
-
self.hardware_version = None
|
|
30
|
-
self.connect_time = None
|
|
31
|
-
self.current_time = None
|
|
32
|
-
# mV
|
|
33
|
-
self.voltage = None
|
|
34
|
-
# %
|
|
35
|
-
self.battery_remain = None
|
|
36
|
-
# %
|
|
37
|
-
self.battery_total = None
|
|
38
|
-
# persist
|
|
39
|
-
self._recording = False
|
|
40
|
-
self._storage_path = None
|
|
41
|
-
self._file_prefix = None
|
|
42
|
-
|
|
43
|
-
# 可设置参数
|
|
44
|
-
# 采集:采样量程、采样率、采样通道
|
|
45
|
-
# 刺激:刺激电流、刺激频率、刺激时间、刺激通道
|
|
46
|
-
# 采样量程(mV):188、375、563、750、1125、2250、4500
|
|
47
|
-
self._sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188
|
|
48
|
-
# 采样率(Hz):250、500、1000、2000、4000、8000、16000、32000
|
|
49
|
-
self._sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500
|
|
50
|
-
self._physical_max = self._sample_range * 1000 # 物理最大值(uV)
|
|
51
|
-
self._physical_min = -self._sample_range * 1000 # 物理最小值(uV)
|
|
52
|
-
self._digital_max = 8388607
|
|
53
|
-
self._digital_min = -8388608
|
|
54
|
-
self._physical_range = self._physical_max - self._physical_min
|
|
55
|
-
self._digital_range = 16777215
|
|
56
|
-
self._acq_channels = None
|
|
57
|
-
self._acq_param = {
|
|
58
|
-
"sample_range": 188,
|
|
59
|
-
"sample_rate": 500,
|
|
60
|
-
"channels": [],
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
self._stim_param = {
|
|
64
|
-
"stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
|
|
65
|
-
"channels": [],
|
|
66
|
-
"param": [{
|
|
67
|
-
"channel_id": 0, #通道号 从0开始 -- 必填
|
|
68
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
69
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
70
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
71
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
72
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
73
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
74
|
-
"phase_position": 0, #相位 -- 默认0
|
|
75
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
76
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"channel_id": 1, #通道号 从0开始 -- 必填
|
|
80
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
81
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
82
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
83
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
84
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
85
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
86
|
-
"phase_position": 0, #相位 -- 默认0
|
|
87
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
88
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
self.stim_paradigm = None
|
|
94
|
-
|
|
95
|
-
signal_info = {
|
|
96
|
-
"param" : None,
|
|
97
|
-
"start_time" : None,
|
|
98
|
-
"finished_time" : None,
|
|
99
|
-
"packet_total" : None,
|
|
100
|
-
"last_packet_time" : None,
|
|
101
|
-
"state" : 0
|
|
102
|
-
}
|
|
103
|
-
stim_info = {
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
Impedance_info = {
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
# 信号采集状态
|
|
110
|
-
# 信号数据包总数(一个信号采集周期内)
|
|
111
|
-
# 信号采集参数
|
|
112
|
-
# 电刺激状态
|
|
113
|
-
# 电刺激开始时间(最近一次)
|
|
114
|
-
# 电刺激结束时间(最近一次)
|
|
115
|
-
# 电刺激参数
|
|
116
|
-
# 启动数据解析线程
|
|
117
|
-
# 数据存储状态
|
|
118
|
-
# 存储目录
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
self.__signal_consumer: Dict[str, Queue[Any]]={}
|
|
122
|
-
self.__impedance_consumer: Dict[str, Queue[Any]]={}
|
|
123
|
-
|
|
124
|
-
# EDF文件处理器
|
|
125
|
-
self._edf_handler = None
|
|
126
|
-
self.storage_enable = True
|
|
127
|
-
|
|
128
|
-
self._parser: IParser = TcpMessageParser(self)
|
|
129
|
-
self._parser.start()
|
|
130
|
-
|
|
131
|
-
# 启动数据接收线程
|
|
132
|
-
self._accept = Thread(target=self.accept)
|
|
133
|
-
self._accept.daemon = True
|
|
134
|
-
self._accept.start()
|
|
135
|
-
|
|
136
|
-
@property
|
|
137
|
-
def device_no(self):
|
|
138
|
-
return self._device_no
|
|
139
|
-
|
|
140
|
-
@device_no.setter
|
|
141
|
-
def device_no(self, value: str):
|
|
142
|
-
self._device_no = value
|
|
143
13
|
|
|
144
14
|
@classmethod
|
|
145
15
|
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
@@ -155,211 +25,5 @@ class C64S1(QLBaseDevice):
|
|
|
155
25
|
self._edf_handler.set_device_no(self.device_no)
|
|
156
26
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
157
27
|
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C64S1')
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def edf_handler(self):
|
|
161
|
-
if not self.storage_enable:
|
|
162
|
-
return None
|
|
163
|
-
|
|
164
|
-
if self._edf_handler is None:
|
|
165
|
-
self.init_edf_handler()
|
|
166
|
-
|
|
167
|
-
return self._edf_handler
|
|
168
|
-
|
|
169
|
-
@property
|
|
170
|
-
def acq_channels(self):
|
|
171
|
-
if self._acq_channels is None:
|
|
172
|
-
self._acq_channels = [i for i in range(1, 63)]
|
|
173
|
-
return self._acq_channels
|
|
174
|
-
@property
|
|
175
|
-
def sample_range(self):
|
|
176
|
-
return self._sample_range if self._sample_range else 188
|
|
177
|
-
@property
|
|
178
|
-
def sample_rate(self):
|
|
179
|
-
return self._sample_rate if self._sample_rate else 500
|
|
180
|
-
@property
|
|
181
|
-
def resolution(self):
|
|
182
|
-
return 24
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def signal_consumers(self):
|
|
186
|
-
return self.__signal_consumer
|
|
187
|
-
|
|
188
|
-
@property
|
|
189
|
-
def impedance_consumers(self):
|
|
190
|
-
return self.__impedance_consumer
|
|
191
|
-
|
|
192
|
-
# 设置记录文件路径
|
|
193
|
-
def set_storage_path(self, path):
|
|
194
|
-
self._storage_path = path
|
|
195
|
-
|
|
196
|
-
# 设置记录文件名称前缀
|
|
197
|
-
def set_file_prefix(self, prefix):
|
|
198
|
-
self._file_prefix = prefix
|
|
199
|
-
|
|
200
|
-
# 接收socket消息
|
|
201
|
-
def accept(self):
|
|
202
|
-
while True:
|
|
203
|
-
# 缓冲去4M
|
|
204
|
-
data = self.socket.recv(4096*1024)
|
|
205
|
-
if not data:
|
|
206
|
-
logger.warning(f"设备{self.device_name}连接结束")
|
|
207
|
-
break
|
|
208
|
-
|
|
209
|
-
self._parser.append(data)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# socket发送数据
|
|
213
|
-
def send(self, data):
|
|
214
|
-
self.socket.sendall(data)
|
|
215
|
-
|
|
216
|
-
# 设置刺激参数
|
|
217
|
-
def set_stim_param(self, param):
|
|
218
|
-
self.stim_paradigm = param
|
|
219
|
-
|
|
220
|
-
# 设置采集参数
|
|
221
|
-
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
222
|
-
self._acq_param["channels"] = channels
|
|
223
|
-
self._acq_param["sample_rate"] = sample_rate
|
|
224
|
-
self._acq_param["sample_range"] = sample_range
|
|
225
|
-
self._acq_channels = channels
|
|
226
|
-
self._sample_rate = sample_rate
|
|
227
|
-
self._sample_range = sample_range
|
|
228
|
-
|
|
229
|
-
# 通用配置-TODO
|
|
230
|
-
def set_config(self, key:str, val: str):
|
|
231
|
-
pass
|
|
232
|
-
|
|
233
|
-
def start_impedance(self):
|
|
234
|
-
logger.info("启动阻抗测量")
|
|
235
|
-
msg = StartImpedanceCommand.build(self).pack()
|
|
236
|
-
logger.debug(f"start_impedance message is {msg.hex()}")
|
|
237
|
-
self.socket.sendall(msg)
|
|
238
|
-
|
|
239
|
-
def stop_impedance(self):
|
|
240
|
-
logger.info("停止阻抗测量")
|
|
241
|
-
msg = StopImpedanceCommand.build(self).pack()
|
|
242
|
-
logger.debug(f"stop_impedance message is {msg.hex()}")
|
|
243
|
-
self.socket.sendall(msg)
|
|
244
|
-
|
|
245
|
-
def start_stimulation(self):
|
|
246
|
-
if self.stim_paradigm is None:
|
|
247
|
-
logger.warning("刺激参数未设置,请先设置刺激参数")
|
|
248
|
-
return
|
|
249
|
-
logger.info("启动电刺激")
|
|
250
|
-
msg = StartStimulationCommand.build(self).pack()
|
|
251
|
-
logger.debug(f"start_stimulation message is {msg.hex()}")
|
|
252
|
-
self.socket.sendall(msg)
|
|
253
|
-
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
|
|
254
|
-
t.start()
|
|
255
|
-
|
|
256
|
-
def _stop_stimulation_trigger(self, duration):
|
|
257
|
-
delay = duration
|
|
258
|
-
while delay > 0:
|
|
259
|
-
sleep(1)
|
|
260
|
-
delay -= 1
|
|
261
|
-
logger.info(f"_stop_stimulation_trigger duration: {duration}")
|
|
262
|
-
if self._edf_handler:
|
|
263
|
-
self._edf_handler.trigger("stimulation should be stopped")
|
|
264
|
-
else:
|
|
265
|
-
logger.warning("stop stim trigger fail. no edf writer alive")
|
|
266
|
-
|
|
267
|
-
def stop_stimulation(self):
|
|
268
|
-
logger.info("停止电刺激")
|
|
269
|
-
msg = StopStimulationCommand.pack()
|
|
270
|
-
logger.debug(f"stop_stimulation message is {msg.hex()}")
|
|
271
|
-
self.socket.sendall(msg)
|
|
272
|
-
|
|
273
|
-
# 启动采集
|
|
274
|
-
def start_acquisition(self, recording = True):
|
|
275
|
-
logger.info("启动信号采集")
|
|
276
|
-
self._recording = recording
|
|
277
|
-
# 设置数据采集参数
|
|
278
|
-
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
279
|
-
# 启动数据采集
|
|
280
|
-
start_bytes = StartAcquisitionCommand.build(self).pack()
|
|
281
|
-
msg = param_bytes + start_bytes
|
|
282
|
-
logger.debug(f"start_acquisition message is {msg.hex()}")
|
|
283
|
-
self.socket.sendall(msg)
|
|
284
|
-
|
|
285
|
-
# 停止采集
|
|
286
|
-
def stop_acquisition(self):
|
|
287
|
-
logger.info("停止信号采集")
|
|
288
|
-
msg = StopAcquisitionCommand.build(self).pack()
|
|
289
|
-
logger.debug(f"stop_acquisition message is {msg.hex()}")
|
|
290
|
-
self.socket.sendall(msg)
|
|
291
|
-
if self._edf_handler:
|
|
292
|
-
# 发送结束标识
|
|
293
|
-
self.edf_handler.write(None)
|
|
294
|
-
|
|
295
|
-
# 订阅实时数据
|
|
296
|
-
def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
|
|
297
|
-
|
|
298
|
-
# 数据队列
|
|
299
|
-
if q is None:
|
|
300
|
-
q = Queue(maxsize=1000)
|
|
301
|
-
|
|
302
|
-
# 队列名称
|
|
303
|
-
if topic is None:
|
|
304
|
-
topic = f"{type}_{time_ns()}"
|
|
305
|
-
|
|
306
|
-
# 订阅生理电信号数据
|
|
307
|
-
if type == "signal":
|
|
308
|
-
# topic唯一,用来区分不同的订阅队列(下同)
|
|
309
|
-
if topic in list(self.__signal_consumer.keys()):
|
|
310
|
-
logger.warning(f"exists {type} subscribe of {topic}")
|
|
311
|
-
else:
|
|
312
|
-
self.__signal_consumer[topic] = q
|
|
313
|
-
|
|
314
|
-
# 订阅阻抗数据
|
|
315
|
-
if type == "impedance":
|
|
316
|
-
if topic in list(self.__signal_consumer.keys()):
|
|
317
|
-
logger.warning(f"exists {type} subscribe of {topic}")
|
|
318
|
-
else:
|
|
319
|
-
self.__impedance_consumer[topic] = q
|
|
320
|
-
|
|
321
|
-
return topic, q
|
|
322
|
-
|
|
323
|
-
def trigger(self, desc):
|
|
324
|
-
if self._edf_handler:
|
|
325
|
-
self.edf_handler.trigger(desc)
|
|
326
|
-
else:
|
|
327
|
-
logger.warning("no edf handler, no place to recording trigger")
|
|
328
|
-
|
|
329
|
-
def __str__(self):
|
|
330
|
-
return f'''
|
|
331
|
-
Device:
|
|
332
|
-
Name: {self.device_name},
|
|
333
|
-
Type: {hex(self.device_type) if self.device_type else None},
|
|
334
|
-
ID: {self.device_id if self.device_id else None},
|
|
335
|
-
Software: {self.software_version},
|
|
336
|
-
Hardware: {self.hardware_version},
|
|
337
|
-
Connect Time: {self.connect_time},
|
|
338
|
-
Current Time: {self.current_time},
|
|
339
|
-
Voltage: {str(self.voltage) + "mV" if self.voltage else None},
|
|
340
|
-
Battery Remain: {str(self.battery_remain)+ "%" if self.battery_remain else None},
|
|
341
|
-
Battery Total: {str(self.battery_total) + "%" if self.battery_total else None}
|
|
342
|
-
'''
|
|
343
|
-
|
|
344
|
-
def __repr__(self):
|
|
345
|
-
return f'''
|
|
346
|
-
Device:
|
|
347
|
-
Name: {self.device_name},
|
|
348
|
-
Type: {hex(self.device_type)},
|
|
349
|
-
ID: {self.device_id},
|
|
350
|
-
Software: {self.software_version},
|
|
351
|
-
Hardware: {self.hardware_version},
|
|
352
|
-
Connect Time: {self.connect_time},
|
|
353
|
-
Current Time: {self.current_time},
|
|
354
|
-
Voltage: {self.voltage}mV,
|
|
355
|
-
Battery Remain: {self.battery_remain}%,
|
|
356
|
-
Battery Total: {self.battery_total}%
|
|
357
|
-
'''
|
|
358
|
-
|
|
359
|
-
def __eq__(self, other):
|
|
360
|
-
return self.device_name == other.device_name and self.device_type == other.device_type and self.device_id == other.device_id
|
|
361
|
-
|
|
362
|
-
def __hash__(self):
|
|
363
|
-
return hash((self.device_name, self.device_type, self.device_id))
|
|
364
|
-
|
|
365
|
-
|
|
28
|
+
logger.debug(f"EDF Handler initialized")
|
|
29
|
+
|
qlsdk/rsc/interface/device.py
CHANGED
|
@@ -61,4 +61,12 @@ class IDevice(ABC):
|
|
|
61
61
|
raise NotImplementedError("Not Supported")
|
|
62
62
|
def disconnect(self) -> None:
|
|
63
63
|
raise NotImplementedError("Not Supported")
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
def set_stim_param(self, param):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def trigger(self, desc):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def enable_storage(self, enable: bool = True):
|
|
72
|
+
pass
|
qlsdk/rsc/parser/base.py
CHANGED
|
@@ -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.
|
|
67
|
+
logger.trace(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
|
|
68
68
|
self.buffer.seek(start_pos + 1) # 移动到下一个字节
|
|
69
69
|
continue
|
|
70
70
|
|
|
@@ -28,9 +28,9 @@ 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=H5dhdoQyJps5WM23vZoY1EKj0PYx0OY9AZeui72TjaY,8930
|
|
32
32
|
qlsdk/persist/edf.py,sha256=ETngb86CfkIUJYWmw86QR445MvTFC7Edk_CH9nyNgtY,7857
|
|
33
|
-
qlsdk/persist/rsc_edf.py,sha256=
|
|
33
|
+
qlsdk/persist/rsc_edf.py,sha256=djR_qXScb_XOOXaoMsvnK_qO2NCPzqEeKxKJCm4xnpk,13989
|
|
34
34
|
qlsdk/persist/stream.py,sha256=TCVF1sqDrHiYBsJC27At66AaCs-_blXeXA_WXdJiIVA,5828
|
|
35
35
|
qlsdk/rsc/__init__.py,sha256=hOMiN0eYn4jYo7O4_0IPlQT0hD15SqqCQUihOVlTZvs,269
|
|
36
36
|
qlsdk/rsc/device_manager.py,sha256=1ucd-lzHkNeQPKPzXV6OBkAMqPp_vOcsLyS-9TJ7wRc,4448
|
|
@@ -39,19 +39,19 @@ qlsdk/rsc/eegion.py,sha256=lxrktO-3Z_MYdFIwc4NxvgLM5AL5kU3UItjH6tsKmHY,11670
|
|
|
39
39
|
qlsdk/rsc/entity.py,sha256=-fRWFkVWp9d8Y1uh6GiacXC5scdeEKNiNFf3aziGdCE,17751
|
|
40
40
|
qlsdk/rsc/paradigm.py,sha256=dYkm0BCpx7tkL-1OE3Io48JgKT0j-lP-n3uC1AcFiWs,17561
|
|
41
41
|
qlsdk/rsc/proxy.py,sha256=9CPdGNGWremwBUh4GvlXAykYB-x_BEPPLqsNvwuwIDE,2736
|
|
42
|
-
qlsdk/rsc/command/__init__.py,sha256=
|
|
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=
|
|
47
|
-
qlsdk/rsc/device/c16_rs.py,sha256=
|
|
48
|
-
qlsdk/rsc/device/c256_rs.py,sha256=
|
|
45
|
+
qlsdk/rsc/device/arskindling.py,sha256=tVih2D3Hhhal3WP0yqjKpzkwFPi6KLenshFStoP-vsw,4123
|
|
46
|
+
qlsdk/rsc/device/base.py,sha256=fCGzjAbagTTGRBfHhbiYOqN3TLOViEd8il8Qp6RiVJs,21106
|
|
47
|
+
qlsdk/rsc/device/c16_rs.py,sha256=qXt8m5vwcKQsN8JBllWnAsda5_Y6qkEhfHQQX101TMQ,5826
|
|
48
|
+
qlsdk/rsc/device/c256_rs.py,sha256=7vAEzf_ggNcwrXKmcZMylnKzLFD5ZqtAIfkkI3lQ1iI,1682
|
|
49
49
|
qlsdk/rsc/device/c64_rs.py,sha256=x8wHdwATKDU34j9vXNEXsNSJg23RAWmAKL8pgIGamG8,1091
|
|
50
|
-
qlsdk/rsc/device/c64s1.py,sha256=
|
|
50
|
+
qlsdk/rsc/device/c64s1.py,sha256=WwiKSjxYpUJVkHDMDzPgp7-klbaiZ2f8EOe3wV6d2WU,1098
|
|
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=LmTnoumG-nLG_H_Fq21XSdTUe_XUvWRWw-8ZNX5_cBU,2238
|
|
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=tH4r_1PVkYfF2alpxKdXiFHmhTDPW_NKbq6DUqU6LVg,5702
|
|
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.0a8.dist-info/METADATA,sha256=RweCHF8Y0Gv3rDVK58bgKl7dgQAmZreBAUcelU_rLbM,1882
|
|
74
|
+
qlsdk2-0.6.0a8.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
|
|
75
|
+
qlsdk2-0.6.0a8.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
|
|
76
|
+
qlsdk2-0.6.0a8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|