qlsdk2 0.5.1__py3-none-any.whl → 0.6.0__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 +175 -42
- qlsdk/core/message/udp.py +9 -1
- qlsdk/entity/__init__.py +0 -0
- qlsdk/entity/message.py +0 -0
- qlsdk/entity/signal.py +0 -0
- qlsdk/interface/__init__.py +10 -0
- qlsdk/interface/analyzer.py +2 -0
- qlsdk/interface/collector.py +10 -0
- qlsdk/interface/device.py +2 -0
- qlsdk/interface/parser.py +13 -0
- qlsdk/interface/stimulator.py +2 -0
- qlsdk/interface/store.py +2 -0
- qlsdk/persist/ars_edf.py +80 -101
- qlsdk/persist/rsc_edf.py +23 -13
- qlsdk/rsc/command/__init__.py +30 -16
- qlsdk/rsc/device/__init__.py +2 -1
- qlsdk/rsc/device/arskindling.py +248 -310
- qlsdk/rsc/device/base.py +214 -105
- qlsdk/rsc/device/c16_rs.py +1 -5
- qlsdk/rsc/device/c256_rs.py +17 -338
- qlsdk/rsc/device/c64_rs.py +2 -332
- qlsdk/rsc/device/c64s1.py +4 -340
- qlsdk/rsc/device/device_factory.py +1 -0
- qlsdk/rsc/interface/device.py +38 -80
- qlsdk/rsc/interface/parser.py +6 -0
- qlsdk/rsc/manager/container.py +10 -2
- qlsdk/rsc/network/discover.py +2 -0
- qlsdk/rsc/paradigm.py +127 -6
- qlsdk/rsc/parser/__init__.py +2 -1
- qlsdk/rsc/parser/base.py +11 -6
- qlsdk/rsc/parser/rsc.py +130 -0
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/METADATA +25 -15
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/RECORD +35 -24
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/WHEEL +0 -0
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/top_level.txt +0 -0
qlsdk/rsc/device/base.py
CHANGED
|
@@ -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
|
|
@@ -6,14 +5,30 @@ from typing import Any, Dict, Literal
|
|
|
6
5
|
|
|
7
6
|
from loguru import logger
|
|
8
7
|
import numpy as np
|
|
9
|
-
from qlsdk.core.entity import RscPacket
|
|
8
|
+
from qlsdk.core.entity import RscPacket, ImpedancePacket
|
|
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.
|
|
14
|
-
from qlsdk.rsc.
|
|
11
|
+
from qlsdk.rsc.command import SetImpedanceParamCommand, StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
|
|
12
|
+
from qlsdk.rsc.paradigm import StimulationParadigm
|
|
13
|
+
from qlsdk.rsc.parser.base import TcpMessageParser
|
|
15
14
|
|
|
15
|
+
|
|
16
|
+
def intersection_positions(A, B):
|
|
17
|
+
setB = set(B)
|
|
18
|
+
seen = set()
|
|
19
|
+
return [idx for idx, elem in enumerate(A)
|
|
20
|
+
if elem in setB and elem not in seen and not seen.add(elem)]
|
|
21
|
+
|
|
16
22
|
class QLBaseDevice(IDevice):
|
|
23
|
+
|
|
24
|
+
__TRIGGER_MAPPING = {
|
|
25
|
+
0x3E8: "Start of stimulation",
|
|
26
|
+
0x3E9: "End of stimulation",
|
|
27
|
+
0x3EA: "Ascending end of stimulation",
|
|
28
|
+
0x3EB: "Descending start of stimulation ",
|
|
29
|
+
0x3EC: "刺激参数有误",
|
|
30
|
+
0x3ED: "End of stimulation (by force)",
|
|
31
|
+
}
|
|
17
32
|
def __init__(self, socket):
|
|
18
33
|
self.socket = socket
|
|
19
34
|
|
|
@@ -63,52 +78,11 @@ class QLBaseDevice(IDevice):
|
|
|
63
78
|
"channels": [],
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
self._stim_param =
|
|
67
|
-
"stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
|
|
68
|
-
"channels": [],
|
|
69
|
-
"param": [{
|
|
70
|
-
"channel_id": 0, #通道号 从0开始 -- 必填
|
|
71
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
72
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
73
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
74
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
75
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
76
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
77
|
-
"phase_position": 0, #相位 -- 默认0
|
|
78
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
79
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"channel_id": 1, #通道号 从0开始 -- 必填
|
|
83
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
84
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
85
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
86
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
87
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
88
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
89
|
-
"phase_position": 0, #相位 -- 默认0
|
|
90
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
91
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}
|
|
81
|
+
self._stim_param = None
|
|
95
82
|
|
|
96
|
-
self.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"param" : None,
|
|
100
|
-
"start_time" : None,
|
|
101
|
-
"finished_time" : None,
|
|
102
|
-
"packet_total" : None,
|
|
103
|
-
"last_packet_time" : None,
|
|
104
|
-
"state" : 0
|
|
105
|
-
}
|
|
106
|
-
stim_info = {
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
Impedance_info = {
|
|
110
|
-
|
|
111
|
-
}
|
|
83
|
+
self._impedance_channels = []
|
|
84
|
+
|
|
85
|
+
self.stim_paradigm: StimulationParadigm = None
|
|
112
86
|
# 信号采集状态
|
|
113
87
|
# 信号数据包总数(一个信号采集周期内)
|
|
114
88
|
# 信号采集参数
|
|
@@ -129,6 +103,9 @@ class QLBaseDevice(IDevice):
|
|
|
129
103
|
self.storage_enable = True
|
|
130
104
|
self._listening = False
|
|
131
105
|
# self.ready()
|
|
106
|
+
self._signal_cache: Queue = None
|
|
107
|
+
self._recording = False
|
|
108
|
+
|
|
132
109
|
|
|
133
110
|
def parser(self) -> IParser:
|
|
134
111
|
return self._parser
|
|
@@ -137,19 +114,55 @@ class QLBaseDevice(IDevice):
|
|
|
137
114
|
self._record_duration = record_duration
|
|
138
115
|
|
|
139
116
|
# 数据包处理
|
|
140
|
-
def produce(self,
|
|
141
|
-
if
|
|
117
|
+
def produce(self, body: bytes, type:Literal['signal', 'impedance']="signal"):
|
|
118
|
+
if body is None: return
|
|
142
119
|
|
|
143
|
-
|
|
144
|
-
|
|
120
|
+
if type == "signal":
|
|
121
|
+
self._produce_signal(body)
|
|
122
|
+
elif type == "impedance":
|
|
123
|
+
self._produce_impedance(body)
|
|
145
124
|
|
|
125
|
+
def _produce_impedance(self, body: bytes):
|
|
126
|
+
# 分发阻抗数据包给订阅者
|
|
127
|
+
if len(self._impedance_consumer) > 0:
|
|
128
|
+
packet = self._impedance_wrapper(body)
|
|
129
|
+
for topic, q in self._impedance_consumer.items():
|
|
130
|
+
try:
|
|
131
|
+
# 队列满了就丢弃最早的数据
|
|
132
|
+
if q.full():
|
|
133
|
+
q.get()
|
|
134
|
+
q.put(packet, timeout=1)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"impedance data put to queue exception: {str(e)}")
|
|
137
|
+
|
|
138
|
+
def _produce_signal(self, body: bytes):
|
|
139
|
+
|
|
140
|
+
# 处理信号数据
|
|
141
|
+
data = self._signal_wrapper(body)
|
|
142
|
+
# logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
|
|
143
|
+
#
|
|
144
|
+
trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
|
|
145
|
+
if len(trigger_positions) > 0:
|
|
146
|
+
# logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[data.time_stamp + int(pos * 1000 / data.sample_rate) for pos in trigger_positions]}")
|
|
147
|
+
for pos in trigger_positions:
|
|
148
|
+
self.trigger(self.trigger_info(data.trigger[pos]))
|
|
146
149
|
# 存储
|
|
147
|
-
if self.storage_enable:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
if self.storage_enable:
|
|
151
|
+
# 确保记录线程启动
|
|
152
|
+
if self._recording is False:
|
|
153
|
+
self._start_recording()
|
|
154
|
+
|
|
155
|
+
# 写入文件的缓存队列
|
|
156
|
+
if self._signal_cache is None:
|
|
157
|
+
self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
|
|
158
|
+
tmp = data.copy()
|
|
159
|
+
self._signal_cache.put(tmp)
|
|
160
|
+
|
|
150
161
|
if len(self.signal_consumers) > 0 :
|
|
162
|
+
logger.trace(f"dg eeg: {data.eeg}")
|
|
151
163
|
# 信号数字值转物理值
|
|
152
164
|
data.eeg = self.eeg2phy(np.array(data.eeg))
|
|
165
|
+
logger.trace(f"ph eeg: {data.eeg}")
|
|
153
166
|
|
|
154
167
|
# 发送数据包到订阅者
|
|
155
168
|
for q in list(self.signal_consumers.values()):
|
|
@@ -157,26 +170,59 @@ class QLBaseDevice(IDevice):
|
|
|
157
170
|
if q.full():
|
|
158
171
|
q.get()
|
|
159
172
|
|
|
160
|
-
q.put(data)
|
|
173
|
+
q.put(data)
|
|
161
174
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
def _impedance_wrapper(self, body: bytes):
|
|
176
|
+
packet = ImpedancePacket().transfer(body)
|
|
177
|
+
if self._impedance_channels is not None and len(self._impedance_channels) > 0:
|
|
178
|
+
# 只保留设置的阻抗通道
|
|
179
|
+
channel_pos = intersection_positions(packet.channels, self._impedance_channels)
|
|
180
|
+
packet.impedance = [packet.impedance[i] for i in channel_pos]
|
|
181
|
+
packet.channels = [packet.channels[i] for i in channel_pos]
|
|
182
|
+
|
|
183
|
+
return packet
|
|
184
|
+
|
|
185
|
+
# 信号数据转换
|
|
186
|
+
def _signal_wrapper(self, body: bytes):
|
|
187
|
+
return RscPacket().transfer(body)
|
|
165
188
|
|
|
166
|
-
def _write_signal(self
|
|
189
|
+
def _write_signal(self):
|
|
167
190
|
# 文件写入到edf
|
|
168
191
|
if self._edf_handler is None:
|
|
169
192
|
logger.debug("Initializing EDF handler for data storage")
|
|
170
193
|
self.init_edf_handler()
|
|
171
194
|
|
|
172
|
-
|
|
195
|
+
while self._recording:
|
|
196
|
+
data = self._signal_cache.get()
|
|
173
197
|
self._edf_handler.write(data)
|
|
198
|
+
if data is None:
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
self._recording = False
|
|
202
|
+
def _start_recording(self):
|
|
203
|
+
if self.storage_enable is False:
|
|
204
|
+
logger.trace("Storage is disabled, will not start recording")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
if self._signal_cache is None:
|
|
208
|
+
self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
self._recording = True
|
|
212
|
+
t = Thread(target=self._write_signal, daemon=True)
|
|
213
|
+
t.start()
|
|
214
|
+
logger.info(f"开启记录")
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.error(f"开启记录失败: {str(e)}")
|
|
217
|
+
return
|
|
174
218
|
|
|
175
219
|
def start_listening(self):
|
|
176
220
|
|
|
177
221
|
try:
|
|
222
|
+
# 启动消息解析器
|
|
178
223
|
self.start_message_parser()
|
|
179
224
|
|
|
225
|
+
# 启动消息监听器
|
|
180
226
|
self.start_message_listening()
|
|
181
227
|
except Exception as e:
|
|
182
228
|
logger.error(f"设备{self.device_no}准备失败: {str(e)}")
|
|
@@ -188,26 +234,50 @@ class QLBaseDevice(IDevice):
|
|
|
188
234
|
logger.trace(f"设备{self.device_no}停止socket监听")
|
|
189
235
|
self._listening = False
|
|
190
236
|
self._parser.stop()
|
|
191
|
-
|
|
237
|
+
|
|
238
|
+
def read_msg(self, size: int) -> bytes:
|
|
239
|
+
try:
|
|
240
|
+
self.socket.settimeout(2.0)
|
|
241
|
+
return self.socket.recv(size)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error(f"read_msg exception: {str(e)}")
|
|
244
|
+
raise ValueError("read_msg exception") from e
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
248
|
+
rlt = cls(parent.socket)
|
|
249
|
+
rlt.device_id = parent.device_id
|
|
250
|
+
rlt._device_no = parent.device_no
|
|
251
|
+
return rlt
|
|
252
|
+
|
|
192
253
|
@property
|
|
193
254
|
def device_type(self) -> int:
|
|
194
255
|
return self._device_type
|
|
195
256
|
|
|
257
|
+
def set_impedance_channels(self, channels):
|
|
258
|
+
self._impedance_channels = channels
|
|
259
|
+
|
|
260
|
+
def get_impedance_channels(self):
|
|
261
|
+
return self._impedance_channels
|
|
262
|
+
|
|
196
263
|
def start_message_parser(self) -> None:
|
|
197
264
|
self._parser = TcpMessageParser(self)
|
|
198
265
|
self._parser.start()
|
|
199
|
-
logger.debug("
|
|
266
|
+
logger.debug("RSC消息解析器已启动")
|
|
200
267
|
|
|
201
268
|
def start_message_listening(self) -> None:
|
|
202
269
|
def _accept():
|
|
203
270
|
while self._listening:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
271
|
+
try:
|
|
272
|
+
# 缓冲区4M
|
|
273
|
+
data = self.socket.recv(4096*1024)
|
|
274
|
+
if not data:
|
|
275
|
+
logger.warning(f"设备[{self.device_name}]连接结束")
|
|
276
|
+
break
|
|
277
|
+
self._parser.append(data)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.debug(f"设备[{self.device_name}]接收数据异常: {str(e)}")
|
|
208
280
|
break
|
|
209
|
-
|
|
210
|
-
self._parser.append(data)
|
|
211
281
|
|
|
212
282
|
# 启动数据接收线程
|
|
213
283
|
self._listening = True
|
|
@@ -252,11 +322,6 @@ class QLBaseDevice(IDevice):
|
|
|
252
322
|
def init_edf_handler(self):
|
|
253
323
|
logger.warning("init_edf_handler not implemented in base class, should be overridden in subclass")
|
|
254
324
|
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
325
|
|
|
261
326
|
# eeg数字值转物理值
|
|
262
327
|
def eeg2phy(self, digital:int):
|
|
@@ -266,7 +331,7 @@ class QLBaseDevice(IDevice):
|
|
|
266
331
|
@property
|
|
267
332
|
def edf_handler(self):
|
|
268
333
|
if not self.storage_enable:
|
|
269
|
-
logger.
|
|
334
|
+
logger.info("已关闭文件记录,不会生成edf/bdf文件")
|
|
270
335
|
return None
|
|
271
336
|
|
|
272
337
|
if self._edf_handler is None:
|
|
@@ -300,8 +365,12 @@ class QLBaseDevice(IDevice):
|
|
|
300
365
|
return self._impedance_consumer
|
|
301
366
|
|
|
302
367
|
# 设置记录文件路径
|
|
303
|
-
def set_storage_path(self,
|
|
304
|
-
|
|
368
|
+
def set_storage_path(self, dir: str):
|
|
369
|
+
import os
|
|
370
|
+
|
|
371
|
+
abs_path = os.path.abspath(dir)
|
|
372
|
+
os.makedirs(abs_path, exist_ok=True)
|
|
373
|
+
self._storage_path = abs_path
|
|
305
374
|
|
|
306
375
|
# 设置记录文件名称前缀
|
|
307
376
|
def set_file_prefix(self, prefix):
|
|
@@ -316,23 +385,27 @@ class QLBaseDevice(IDevice):
|
|
|
316
385
|
self.stim_paradigm = param
|
|
317
386
|
|
|
318
387
|
# 设置采集参数
|
|
319
|
-
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
388
|
+
def set_acq_param(self, channels, sample_rate:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 500, sample_range:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 188):
|
|
320
389
|
self._acq_param["channels"] = channels
|
|
321
390
|
self._acq_param["sample_rate"] = sample_rate
|
|
322
391
|
self._acq_param["sample_range"] = sample_range
|
|
323
392
|
self._acq_channels = channels
|
|
324
393
|
self._sample_rate = sample_rate
|
|
325
394
|
self._sample_range = sample_range
|
|
326
|
-
|
|
327
|
-
# 通用配置-TODO
|
|
328
|
-
def set_config(self, key:str, val: str):
|
|
329
|
-
pass
|
|
330
395
|
|
|
331
396
|
def start_impedance(self):
|
|
332
397
|
logger.info(f"[设备-{self.device_no}]启动阻抗测量")
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
self.
|
|
398
|
+
# 设置数据采集参数
|
|
399
|
+
# set_param_msg = SetImpedanceParamCommand.build(self).pack()
|
|
400
|
+
device_id = bytes.fromhex(self.device_id)[::-1].hex() if self.device_id else '00000000'
|
|
401
|
+
set_param_msg = bytes.fromhex(f'5aa50239{device_id}3f0000001104ffffffffffffffff000000000000000000000000000000000000000000000000e8030000fa00000010000164000000745c5aa50239390045243a00000012040000000000000000000000000000000000000000000000000000000000000000000001000000000000004c2a')
|
|
402
|
+
logger.debug(f"set_param_msg message is {set_param_msg.hex()}")
|
|
403
|
+
self.socket.sendall(set_param_msg)
|
|
404
|
+
sleep(0.5)
|
|
405
|
+
|
|
406
|
+
impedance_start_msg = StartImpedanceCommand.build(self).pack()
|
|
407
|
+
logger.debug(f"start_impedance message is {impedance_start_msg.hex()}")
|
|
408
|
+
self.socket.sendall(impedance_start_msg)
|
|
336
409
|
|
|
337
410
|
def stop_impedance(self):
|
|
338
411
|
logger.info(f"[设备{self.device_no}]停止阻抗测量")
|
|
@@ -348,8 +421,11 @@ class QLBaseDevice(IDevice):
|
|
|
348
421
|
msg = StartStimulationCommand.build(self).pack()
|
|
349
422
|
logger.trace(f"start_stimulation message is {msg.hex()}")
|
|
350
423
|
self.socket.sendall(msg)
|
|
351
|
-
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
|
|
352
|
-
t.start()
|
|
424
|
+
# t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
|
|
425
|
+
# t.start()
|
|
426
|
+
|
|
427
|
+
def get_stim_param(self) -> bytes:
|
|
428
|
+
return self.stim_paradigm.to_bytes()
|
|
353
429
|
|
|
354
430
|
def _stop_stimulation_trigger(self, duration):
|
|
355
431
|
delay = duration
|
|
@@ -357,23 +433,22 @@ class QLBaseDevice(IDevice):
|
|
|
357
433
|
sleep(1)
|
|
358
434
|
delay -= 1
|
|
359
435
|
logger.debug(f"_stop_stimulation_trigger duration: {duration}")
|
|
360
|
-
if self.
|
|
361
|
-
self.
|
|
436
|
+
if self.edf_handler:
|
|
437
|
+
self.edf_handler.trigger("stimulation should be stopped")
|
|
362
438
|
else:
|
|
363
439
|
logger.warning("stop stim trigger fail. no edf writer alive")
|
|
364
440
|
|
|
365
441
|
def stop_stimulation(self):
|
|
366
442
|
logger.info(f"[设备-{self.device_no}]停止电刺激")
|
|
367
|
-
msg = StopStimulationCommand.pack()
|
|
443
|
+
msg = StopStimulationCommand.build(self).pack()
|
|
368
444
|
logger.trace(f"stop_stimulation message is {msg.hex()}")
|
|
369
445
|
self.socket.sendall(msg)
|
|
370
446
|
|
|
371
447
|
# 启动采集
|
|
372
|
-
def start_acquisition(self
|
|
448
|
+
def start_acquisition(self):
|
|
373
449
|
logger.info(f"[设备-{self.device_no}]启动信号采集")
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
self.init_edf_handler()
|
|
450
|
+
# 记录准备
|
|
451
|
+
self._start_recording()
|
|
377
452
|
# 设置数据采集参数
|
|
378
453
|
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
379
454
|
# 启动数据采集
|
|
@@ -388,9 +463,10 @@ class QLBaseDevice(IDevice):
|
|
|
388
463
|
msg = StopAcquisitionCommand.build(self).pack()
|
|
389
464
|
logger.trace(f"stop_acquisition message is {msg.hex()}")
|
|
390
465
|
self.socket.sendall(msg)
|
|
391
|
-
|
|
466
|
+
# 结束标识
|
|
467
|
+
if self._signal_cache:
|
|
392
468
|
# 发送结束标识
|
|
393
|
-
self.
|
|
469
|
+
self._signal_cache.put(None)
|
|
394
470
|
|
|
395
471
|
'''
|
|
396
472
|
订阅数据
|
|
@@ -408,7 +484,7 @@ class QLBaseDevice(IDevice):
|
|
|
408
484
|
|
|
409
485
|
# 数据队列
|
|
410
486
|
if q is None:
|
|
411
|
-
q = Queue(maxsize=
|
|
487
|
+
q = Queue(maxsize=100 * 1024 * 1024)
|
|
412
488
|
|
|
413
489
|
# 订阅生理电信号数据
|
|
414
490
|
if type == "signal":
|
|
@@ -428,11 +504,12 @@ class QLBaseDevice(IDevice):
|
|
|
428
504
|
return topic, q
|
|
429
505
|
|
|
430
506
|
def trigger(self, desc):
|
|
431
|
-
if self.
|
|
507
|
+
if self.edf_handler:
|
|
432
508
|
self.edf_handler.trigger(desc)
|
|
433
509
|
else:
|
|
434
|
-
logger.
|
|
510
|
+
logger.info("已关闭文件记录,不会记录trigger信息")
|
|
435
511
|
|
|
512
|
+
# 设置信号采集参数
|
|
436
513
|
def gen_set_acquirement_param(self) -> bytes:
|
|
437
514
|
|
|
438
515
|
body = to_bytes(self.acq_channels)
|
|
@@ -443,13 +520,45 @@ class QLBaseDevice(IDevice):
|
|
|
443
520
|
body += bytes.fromhex('00')
|
|
444
521
|
|
|
445
522
|
return body
|
|
523
|
+
# 设置阻抗测量参数
|
|
524
|
+
def gen_set_impedance_param(self) -> bytes:
|
|
525
|
+
|
|
526
|
+
# 仅通道生效 32字节,其他不生效-272字节,实际73字节
|
|
527
|
+
body = to_bytes(self._impedance_channels)
|
|
528
|
+
# 100 bytes
|
|
529
|
+
# body += bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
|
|
530
|
+
# # 100 bytes
|
|
531
|
+
# body += bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
|
|
532
|
+
# 73 bytes
|
|
533
|
+
body += bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
|
|
534
|
+
|
|
535
|
+
return bytes.fromhex('ffffffffffffffff000000000000000000000000000000000000000000000000e8030000fa00000010000164000000745c5aa50239390045243a0000001204000000000000000000000000000000000000000000000000000000000000000000000100000000000000')
|
|
536
|
+
def disconnect(self):
|
|
537
|
+
logger.info(f"[断开设备-{self.device_no}]的连接...")
|
|
538
|
+
self._listening = False
|
|
539
|
+
try:
|
|
540
|
+
sleep(0.1)
|
|
541
|
+
self.socket.shutdown(2)
|
|
542
|
+
self.socket.close()
|
|
543
|
+
logger.info(f"[设备-{self.device_no}]设备连接已断开")
|
|
544
|
+
except Exception as e:
|
|
545
|
+
logger.error(f"断开设备连接异常: {str(e)}")
|
|
546
|
+
|
|
547
|
+
# 关闭解析器
|
|
548
|
+
self._parser.stop()
|
|
549
|
+
|
|
550
|
+
def enable_storage(self, enable: bool = True):
|
|
551
|
+
self.storage_enable = enable
|
|
552
|
+
|
|
553
|
+
def trigger_info(self, code: int) -> str:
|
|
554
|
+
return QLBaseDevice.__TRIGGER_MAPPING.get(code, hex(code))
|
|
446
555
|
|
|
447
556
|
def __str__(self):
|
|
448
557
|
return f'''
|
|
449
558
|
Device:
|
|
450
559
|
Name: {self.device_name},
|
|
451
560
|
Type: {hex(self.device_type) if self.device_type else None},
|
|
452
|
-
ID: {self.
|
|
561
|
+
ID: {self.device_no if self.device_no else None},
|
|
453
562
|
Software: {self.software_version},
|
|
454
563
|
Hardware: {self.hardware_version},
|
|
455
564
|
Connect Time: {self.connect_time},
|
|
@@ -459,8 +568,8 @@ class QLBaseDevice(IDevice):
|
|
|
459
568
|
Battery Total: {str(self.battery_total) + "%" if self.battery_total else None}
|
|
460
569
|
'''
|
|
461
570
|
|
|
462
|
-
def __eq__(self, other):
|
|
463
|
-
return self.
|
|
571
|
+
def __eq__(self, other:IDevice):
|
|
572
|
+
return self.device_type == other.device_type and self.device_no == other.device_no
|
|
464
573
|
|
|
465
574
|
def __hash__(self):
|
|
466
|
-
return hash((self.
|
|
575
|
+
return hash((self.device_type, self.device_no))
|
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
|
# 保存原始通道参数
|