qlsdk2 0.4.0a2__py3-none-any.whl → 0.4.1__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/ar4/__init__.py +9 -5
- qlsdk/core/__init__.py +2 -1
- qlsdk/core/device.py +10 -1
- qlsdk/core/entity/__init__.py +1 -1
- qlsdk/core/message/command.py +93 -53
- qlsdk/core/network/__init__.py +34 -0
- qlsdk/core/network/monitor.py +55 -0
- qlsdk/core/utils.py +2 -0
- qlsdk/persist/edf.py +21 -2
- qlsdk/persist/rsc_edf.py +190 -126
- qlsdk/rsc/__init__.py +4 -2
- qlsdk/rsc/command/__init__.py +183 -61
- qlsdk/rsc/command/message.py +171 -74
- qlsdk/rsc/device/__init__.py +2 -0
- qlsdk/rsc/device/base.py +388 -0
- qlsdk/rsc/device/c64_rs.py +364 -0
- qlsdk/rsc/device/device_factory.py +29 -0
- qlsdk/rsc/device_manager.py +2 -0
- qlsdk/rsc/entity.py +95 -179
- qlsdk/rsc/interface/__init__.py +3 -0
- qlsdk/rsc/interface/command.py +10 -0
- qlsdk/rsc/interface/device.py +107 -0
- qlsdk/rsc/interface/handler.py +9 -0
- qlsdk/rsc/interface/parser.py +9 -0
- qlsdk/rsc/manager/__init__.py +2 -0
- qlsdk/rsc/manager/container.py +121 -0
- qlsdk/rsc/manager/search.py +0 -0
- qlsdk/rsc/network/__init__.py +1 -0
- qlsdk/rsc/network/discover.py +87 -0
- qlsdk/rsc/paradigm.py +4 -3
- qlsdk/rsc/parser/__init__.py +1 -0
- qlsdk/rsc/parser/base.py +66 -0
- qlsdk/sdk/ar4sdk.py +13 -4
- qlsdk/x8/__init__.py +4 -0
- {qlsdk2-0.4.0a2.dist-info → qlsdk2-0.4.1.dist-info}/METADATA +2 -2
- qlsdk2-0.4.1.dist-info/RECORD +58 -0
- qlsdk2-0.4.0a2.dist-info/RECORD +0 -40
- {qlsdk2-0.4.0a2.dist-info → qlsdk2-0.4.1.dist-info}/WHEEL +0 -0
- {qlsdk2-0.4.0a2.dist-info → qlsdk2-0.4.1.dist-info}/top_level.txt +0 -0
qlsdk/rsc/entity.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from multiprocessing import Queue, Process
|
|
2
2
|
from typing import Any, Dict, Literal
|
|
3
|
-
# from rsc.command import *
|
|
4
|
-
from qlsdk.core import *
|
|
5
|
-
from qlsdk.core.device import BaseDevice
|
|
6
3
|
from threading import Thread
|
|
7
4
|
from loguru import logger
|
|
8
|
-
from time import time_ns
|
|
5
|
+
from time import time_ns, sleep
|
|
6
|
+
|
|
7
|
+
from qlsdk.core import *
|
|
8
|
+
from qlsdk.core.device import BaseDevice
|
|
9
|
+
from qlsdk.persist import RscEDFHandler
|
|
9
10
|
|
|
10
11
|
class QLDevice(BaseDevice):
|
|
11
12
|
def __init__(self, socket):
|
|
@@ -25,6 +26,10 @@ class QLDevice(BaseDevice):
|
|
|
25
26
|
self.battery_remain = None
|
|
26
27
|
# %
|
|
27
28
|
self.battery_total = None
|
|
29
|
+
# persist
|
|
30
|
+
self._recording = False
|
|
31
|
+
self._storage_path = None
|
|
32
|
+
self._file_prefix = None
|
|
28
33
|
|
|
29
34
|
# 可设置参数
|
|
30
35
|
# 采集:采样量程、采样率、采样通道
|
|
@@ -33,6 +38,12 @@ class QLDevice(BaseDevice):
|
|
|
33
38
|
self._sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188
|
|
34
39
|
# 采样率(Hz):250、500、1000、2000、4000、8000、16000、32000
|
|
35
40
|
self._sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500
|
|
41
|
+
self._physical_max = 188000
|
|
42
|
+
self._physical_min = -188000
|
|
43
|
+
self._digital_max = 8388607
|
|
44
|
+
self._digital_min = -8388608
|
|
45
|
+
self._physical_range = 376000
|
|
46
|
+
self._digital_range = 16777215
|
|
36
47
|
self._acq_channels = None
|
|
37
48
|
self._acq_param = {
|
|
38
49
|
"sample_range": 188,
|
|
@@ -101,6 +112,9 @@ class QLDevice(BaseDevice):
|
|
|
101
112
|
self.__signal_consumer: Dict[str, Queue[Any]]={}
|
|
102
113
|
self.__impedance_consumer: Dict[str, Queue[Any]]={}
|
|
103
114
|
|
|
115
|
+
# EDF文件处理器
|
|
116
|
+
self._edf_handler = None
|
|
117
|
+
self.storage_enable = True
|
|
104
118
|
|
|
105
119
|
self._parser = DeviceParser(self)
|
|
106
120
|
self._parser.start()
|
|
@@ -109,18 +123,45 @@ class QLDevice(BaseDevice):
|
|
|
109
123
|
self._accept = Thread(target=self.accept)
|
|
110
124
|
self._accept.daemon = True
|
|
111
125
|
self._accept.start()
|
|
126
|
+
|
|
127
|
+
def init_edf_handler(self):
|
|
128
|
+
self._edf_handler = RscEDFHandler(self.sample_rate, self._physical_max , self._physical_min, self.resolution)
|
|
129
|
+
self._edf_handler.set_device_type(self.device_type)
|
|
130
|
+
self._edf_handler.set_device_no(self.device_name)
|
|
131
|
+
self._edf_handler.set_storage_path(self._storage_path)
|
|
132
|
+
self._edf_handler.set_file_prefix(self._file_prefix)
|
|
133
|
+
|
|
134
|
+
# eeg数字值转物理值
|
|
135
|
+
def eeg2phy(self, digital:int):
|
|
136
|
+
# 向量化计算(自动支持广播)
|
|
137
|
+
return ((digital - self._digital_min) / self._digital_range) * self._physical_range + self._physical_min
|
|
112
138
|
|
|
139
|
+
@property
|
|
140
|
+
def edf_handler(self):
|
|
141
|
+
if not self.storage_enable:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
if self._edf_handler is None:
|
|
145
|
+
self.init_edf_handler()
|
|
146
|
+
|
|
147
|
+
return self._edf_handler
|
|
148
|
+
|
|
149
|
+
# 采集通道列表, 从1开始
|
|
113
150
|
@property
|
|
114
151
|
def acq_channels(self):
|
|
115
152
|
if self._acq_channels is None:
|
|
116
153
|
self._acq_channels = [i for i in range(1, 63)]
|
|
117
154
|
return self._acq_channels
|
|
155
|
+
|
|
156
|
+
# 量程范围
|
|
118
157
|
@property
|
|
119
158
|
def sample_range(self):
|
|
159
|
+
|
|
120
160
|
return self._sample_range if self._sample_range else 188
|
|
121
161
|
@property
|
|
122
162
|
def sample_rate(self):
|
|
123
163
|
return self._sample_rate if self._sample_rate else 500
|
|
164
|
+
|
|
124
165
|
@property
|
|
125
166
|
def resolution(self):
|
|
126
167
|
return 24
|
|
@@ -132,11 +173,18 @@ class QLDevice(BaseDevice):
|
|
|
132
173
|
@property
|
|
133
174
|
def impedance_consumers(self):
|
|
134
175
|
return self.__impedance_consumer
|
|
176
|
+
|
|
177
|
+
# 设置记录文件路径
|
|
178
|
+
def set_storage_path(self, path):
|
|
179
|
+
self._storage_path = path
|
|
180
|
+
|
|
181
|
+
# 设置记录文件名称前缀
|
|
182
|
+
def set_file_prefix(self, prefix):
|
|
183
|
+
self._file_prefix = prefix
|
|
135
184
|
|
|
136
185
|
def accept(self):
|
|
137
186
|
while True:
|
|
138
187
|
data = self.socket.recv(4096*1024)
|
|
139
|
-
# logger.debug(f"QLDevice接收到数据: {data.hex()}")
|
|
140
188
|
if not data:
|
|
141
189
|
logger.warning(f"设备{self.device_name}连接结束")
|
|
142
190
|
break
|
|
@@ -155,35 +203,31 @@ class QLDevice(BaseDevice):
|
|
|
155
203
|
self.stim_paradigm = param
|
|
156
204
|
|
|
157
205
|
# 设置采集参数
|
|
158
|
-
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
206
|
+
def set_acq_param(self, channels, sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500, sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188):
|
|
159
207
|
self._acq_param["channels"] = channels
|
|
160
208
|
self._acq_param["sample_rate"] = sample_rate
|
|
161
209
|
self._acq_param["sample_range"] = sample_range
|
|
162
210
|
self._acq_channels = channels
|
|
163
211
|
self._sample_rate = sample_rate
|
|
164
212
|
self._sample_range = sample_range
|
|
213
|
+
# 根据量程更新信号物理值范围
|
|
214
|
+
self._physical_max = self._sample_range * 1000
|
|
215
|
+
self._physical_min = -self._sample_range * 1000
|
|
216
|
+
self._physical_range = self._physical_max - self._physical_min
|
|
165
217
|
|
|
166
|
-
#
|
|
218
|
+
# 通用配置-TODO
|
|
167
219
|
def set_config(self, key:str, val: str):
|
|
168
220
|
pass
|
|
169
221
|
|
|
170
|
-
def set_impedance_param(self):
|
|
171
|
-
channels = bytes.fromhex("FFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000")
|
|
172
|
-
sample_rate = 1000
|
|
173
|
-
sample_len = 300
|
|
174
|
-
resolution = self.resolution
|
|
175
|
-
|
|
176
222
|
def start_impedance(self):
|
|
177
223
|
logger.info("启动阻抗测量")
|
|
178
224
|
msg = StartImpedanceCommand.build(self).pack()
|
|
179
|
-
# msg = bytes.fromhex("5aa50239320013243f0000001104ffffffffffffffff000000000000000000000000000000000000000000000000e8030000fa00000010000164000000a11e5aa50239320013243a00000012040000000000000000000000000000000000000000000000000000000000000000000001000000000000000325")
|
|
180
225
|
logger.debug(f"start_impedance message is {msg.hex()}")
|
|
181
226
|
self.socket.sendall(msg)
|
|
182
227
|
|
|
183
228
|
def stop_impedance(self):
|
|
184
229
|
logger.info("停止阻抗测量")
|
|
185
230
|
msg = StopImpedanceCommand.build(self).pack()
|
|
186
|
-
# msg = bytes.fromhex("5aa5023932001324100000001304e9df")
|
|
187
231
|
logger.debug(f"stop_impedance message is {msg.hex()}")
|
|
188
232
|
self.socket.sendall(msg)
|
|
189
233
|
|
|
@@ -192,20 +236,33 @@ class QLDevice(BaseDevice):
|
|
|
192
236
|
logger.warning("刺激参数未设置,请先设置刺激参数")
|
|
193
237
|
return
|
|
194
238
|
logger.info("启动电刺激")
|
|
195
|
-
# conf = SetStimulationParamCommand.build(self).pack()
|
|
196
239
|
msg = StartStimulationCommand.build(self).pack()
|
|
197
240
|
logger.debug(f"start_stimulation message is {msg.hex()}")
|
|
198
241
|
self.socket.sendall(msg)
|
|
242
|
+
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
|
|
243
|
+
t.start()
|
|
244
|
+
|
|
245
|
+
def _stop_stimulation_trigger(self, duration):
|
|
246
|
+
delay = duration
|
|
247
|
+
while delay > 0:
|
|
248
|
+
sleep(1)
|
|
249
|
+
delay -= 1
|
|
250
|
+
logger.info(f"_stop_stimulation_trigger duration: {duration}")
|
|
251
|
+
if self._edf_handler:
|
|
252
|
+
self._edf_handler.trigger("stimulation should be stopped")
|
|
253
|
+
else:
|
|
254
|
+
logger.warning("trigger fail for 'stop stim'. no recording file to write")
|
|
199
255
|
|
|
200
256
|
def stop_stimulation(self):
|
|
201
257
|
logger.info("停止电刺激")
|
|
202
|
-
msg = StopStimulationCommand.pack()
|
|
258
|
+
msg = StopStimulationCommand.build().pack()
|
|
203
259
|
logger.debug(f"stop_stimulation message is {msg.hex()}")
|
|
204
260
|
self.socket.sendall(msg)
|
|
205
261
|
|
|
206
262
|
# 启动采集
|
|
207
|
-
def start_acquisition(self):
|
|
263
|
+
def start_acquisition(self, recording = True):
|
|
208
264
|
logger.info("启动信号采集")
|
|
265
|
+
self._recording = recording
|
|
209
266
|
# 设置数据采集参数
|
|
210
267
|
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
211
268
|
# 启动数据采集
|
|
@@ -218,15 +275,18 @@ class QLDevice(BaseDevice):
|
|
|
218
275
|
def stop_acquisition(self):
|
|
219
276
|
logger.info("停止信号采集")
|
|
220
277
|
msg = StopAcquisitionCommand.build(self).pack()
|
|
221
|
-
logger.debug(f"stop_acquisition message is {msg}")
|
|
278
|
+
logger.debug(f"stop_acquisition message is {msg.hex()}")
|
|
222
279
|
self.socket.sendall(msg)
|
|
280
|
+
if self._edf_handler:
|
|
281
|
+
# 发送结束标识
|
|
282
|
+
self.edf_handler.write(None)
|
|
223
283
|
|
|
224
284
|
# 订阅实时数据
|
|
225
285
|
def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
|
|
226
286
|
|
|
227
287
|
# 数据队列
|
|
228
288
|
if q is None:
|
|
229
|
-
q = Queue()
|
|
289
|
+
q = Queue(maxsize=1000)
|
|
230
290
|
|
|
231
291
|
# 队列名称
|
|
232
292
|
if topic is None:
|
|
@@ -248,6 +308,12 @@ class QLDevice(BaseDevice):
|
|
|
248
308
|
self.__impedance_consumer[topic] = q
|
|
249
309
|
|
|
250
310
|
return topic, q
|
|
311
|
+
|
|
312
|
+
def trigger(self, desc):
|
|
313
|
+
if self._edf_handler:
|
|
314
|
+
self.edf_handler.trigger(desc)
|
|
315
|
+
else:
|
|
316
|
+
logger.warning("no edf handler, no place to recording trigger")
|
|
251
317
|
|
|
252
318
|
def __str__(self):
|
|
253
319
|
return f'''
|
|
@@ -303,10 +369,6 @@ class RSC64R(QLDevice):
|
|
|
303
369
|
class ARSKindling(QLDevice):
|
|
304
370
|
def __init__(self, socket):
|
|
305
371
|
super().__init__(socket)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
372
|
|
|
311
373
|
class DeviceParser(object):
|
|
312
374
|
def __init__(self, device : QLDevice):
|
|
@@ -318,7 +380,7 @@ class DeviceParser(object):
|
|
|
318
380
|
|
|
319
381
|
def append(self, buffer):
|
|
320
382
|
self.cache += buffer
|
|
321
|
-
logger.debug(f"
|
|
383
|
+
logger.debug(f"已缓存的数据长度: {len(self.cache)}")
|
|
322
384
|
|
|
323
385
|
# if not self.running:
|
|
324
386
|
# self.start()
|
|
@@ -326,14 +388,13 @@ class DeviceParser(object):
|
|
|
326
388
|
def __parser__(self):
|
|
327
389
|
logger.info("数据解析开始")
|
|
328
390
|
while self.running:
|
|
329
|
-
# logger.debug(f" cache len: {len(self.cache)}")
|
|
330
391
|
if len(self.cache) < 14:
|
|
331
392
|
continue
|
|
332
393
|
if self.cache[0] != 0x5A or self.cache[1] != 0xA5:
|
|
333
394
|
self.cache = self.cache[1:]
|
|
334
395
|
continue
|
|
335
396
|
pkg_len = int.from_bytes(self.cache[8:12], 'little')
|
|
336
|
-
logger.
|
|
397
|
+
logger.trace(f" cache len: {len(self.cache)}, pkg_len len: {len(self.cache)}")
|
|
337
398
|
# 一次取整包数据
|
|
338
399
|
if len(self.cache) < pkg_len:
|
|
339
400
|
continue
|
|
@@ -368,11 +429,13 @@ class TCPMessage(object):
|
|
|
368
429
|
TCPMessage._validate_packet(data)
|
|
369
430
|
# 提取指令码
|
|
370
431
|
cmd_code = int.from_bytes(data[TCPMessage.CMD_POS:TCPMessage.CMD_POS+2], 'little')
|
|
371
|
-
logger.debug(f"收到指令:{hex(cmd_code)}")
|
|
372
432
|
cmd_class = CommandFactory.create_command(cmd_code)
|
|
373
|
-
logger.debug(f"
|
|
433
|
+
logger.debug(f"收到指令:{cmd_class.cmd_desc}[{hex(cmd_code)}]")
|
|
374
434
|
instance = cmd_class(device)
|
|
435
|
+
start = time_ns()
|
|
436
|
+
logger.debug(f"开始解析: {start}")
|
|
375
437
|
instance.parse_body(data[TCPMessage.HEADER_LEN:-2])
|
|
438
|
+
logger.debug(f"解析完成:{time_ns()}, 解析耗时:{time_ns() - start}ns")
|
|
376
439
|
return instance
|
|
377
440
|
|
|
378
441
|
@staticmethod
|
|
@@ -388,9 +451,9 @@ class TCPMessage(object):
|
|
|
388
451
|
if len(data) != expected_len:
|
|
389
452
|
raise ValueError(f"Length mismatch: {len(data)} vs {expected_len}")
|
|
390
453
|
|
|
391
|
-
logger.
|
|
392
|
-
checksum = crc16(data[:-2])
|
|
393
|
-
logger.
|
|
454
|
+
# logger.trace(f"checksum: {int.from_bytes(data[-2:], 'little')}")
|
|
455
|
+
# checksum = crc16(data[:-2])
|
|
456
|
+
# logger.trace(f"checksum recv: {checksum}")
|
|
394
457
|
|
|
395
458
|
|
|
396
459
|
|
|
@@ -402,150 +465,3 @@ class DataPacket(object):
|
|
|
402
465
|
|
|
403
466
|
def parse_body(self, body: bytes):
|
|
404
467
|
raise NotImplementedError("Subclasses should implement this method")
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
class C64Channel(Enum):
|
|
409
|
-
CH0 = 0
|
|
410
|
-
CH1 = 1
|
|
411
|
-
CH2 = 2
|
|
412
|
-
CH3 = 3
|
|
413
|
-
CH4 = 4
|
|
414
|
-
CH5 = 5
|
|
415
|
-
CH6 = 6
|
|
416
|
-
CH7 = 7
|
|
417
|
-
CH8 = 8
|
|
418
|
-
CH9 = 9
|
|
419
|
-
CH10 = 10
|
|
420
|
-
CH11 = 11
|
|
421
|
-
CH12 = 12
|
|
422
|
-
CH13 = 13
|
|
423
|
-
CH14 = 14
|
|
424
|
-
CH15 = 15
|
|
425
|
-
|
|
426
|
-
class WaveForm(Enum):
|
|
427
|
-
DC = 0
|
|
428
|
-
SQUARE = 1
|
|
429
|
-
AC = 2
|
|
430
|
-
CUSTOM = 3
|
|
431
|
-
PULSE = 4
|
|
432
|
-
|
|
433
|
-
# # 刺激通道
|
|
434
|
-
# class StimulationChannel(object):
|
|
435
|
-
# def __init__(self, channel_id: int, waveform: int, current: float, duration: float, ramp_up: float = None, ramp_down: float = None,
|
|
436
|
-
# frequency: float = None, phase_position: int = None, duration_delay: float = None, pulse_width: int = None, pulse_width_rate: int = 1):
|
|
437
|
-
# self.channel_id = channel_id
|
|
438
|
-
# self.waveform = waveform
|
|
439
|
-
# self.current_max = current
|
|
440
|
-
# self.current_min = current
|
|
441
|
-
# self.duration = duration
|
|
442
|
-
# self.ramp_up = ramp_up
|
|
443
|
-
# self.ramp_down = ramp_down
|
|
444
|
-
# self.frequency = frequency
|
|
445
|
-
# self.phase_position = phase_position
|
|
446
|
-
# self.duration_delay = duration_delay
|
|
447
|
-
# self.pulse_width = pulse_width
|
|
448
|
-
# self.delay_time = 0
|
|
449
|
-
# self.pulse_interval = 0
|
|
450
|
-
# self.with_group_repeats = 1
|
|
451
|
-
# self.pulse_width_rate = 1065353216
|
|
452
|
-
# self.pulse_time_f = 0
|
|
453
|
-
# self.pulse_time_out = 0
|
|
454
|
-
# self.pulse_time_idle = 0
|
|
455
|
-
|
|
456
|
-
# def to_bytes(self):
|
|
457
|
-
# # Convert the object to bytes for transmission
|
|
458
|
-
# result = self.channel_id.to_bytes(1, 'little')
|
|
459
|
-
# wave_form = WaveForm.SQUARE.value if self.waveform == WaveForm.PULSE.value else self.waveform
|
|
460
|
-
# result += wave_form.to_bytes(1, 'little')
|
|
461
|
-
# result += int(self.current_max * 1000 * 1000).to_bytes(4, 'little')
|
|
462
|
-
# # result += int(self.current_min * 1000).to_bytes(2, 'little')
|
|
463
|
-
# result += int(self.frequency).to_bytes(2, 'little')
|
|
464
|
-
# result += int(self.pulse_width).to_bytes(2, 'little')
|
|
465
|
-
# result += int(self.pulse_width_rate).to_bytes(4, 'little')
|
|
466
|
-
|
|
467
|
-
# result += int(self.pulse_interval).to_bytes(2, 'little')
|
|
468
|
-
# result += int(self.with_group_repeats).to_bytes(2, 'little')
|
|
469
|
-
# result += int(self.pulse_time_f).to_bytes(4, 'little')
|
|
470
|
-
# result += int(self.pulse_time_out).to_bytes(4, 'little')
|
|
471
|
-
# result += int(self.pulse_time_idle).to_bytes(4, 'little')
|
|
472
|
-
|
|
473
|
-
# result += int(self.delay_time).to_bytes(4, 'little')
|
|
474
|
-
# result += int(self.ramp_up * 1000).to_bytes(4, 'little')
|
|
475
|
-
# result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
|
|
476
|
-
# result += int(self.ramp_down * 1000).to_bytes(4, 'little')
|
|
477
|
-
|
|
478
|
-
# return result
|
|
479
|
-
|
|
480
|
-
# def to_json(self):
|
|
481
|
-
# return {
|
|
482
|
-
# "channel_id": self.channel_id,
|
|
483
|
-
# "waveform": self.waveform,
|
|
484
|
-
# "current_max": self.current_max,
|
|
485
|
-
# "current_min": self.current_min,
|
|
486
|
-
# "duration": self.duration,
|
|
487
|
-
# "ramp_up": self.ramp_up,
|
|
488
|
-
# "ramp_down": self.ramp_down,
|
|
489
|
-
# "frequency": self.frequency,
|
|
490
|
-
# "phase_position": self.phase_position,
|
|
491
|
-
# "duration_delay": self.duration_delay,
|
|
492
|
-
# "pulse_width": self.pulse_width,
|
|
493
|
-
# "delay_time": self.delay_time,
|
|
494
|
-
# "pulse_interval": self.pulse_interval,
|
|
495
|
-
# "with_group_repeats": self.with_group_repeats
|
|
496
|
-
# }
|
|
497
|
-
|
|
498
|
-
# # 刺激范式
|
|
499
|
-
# class StimulationParadigm(object):
|
|
500
|
-
# def __init__(self):
|
|
501
|
-
# self.channels = None
|
|
502
|
-
# self.duration = None
|
|
503
|
-
# self.interval_time = 0
|
|
504
|
-
# self.characteristic = 0
|
|
505
|
-
# self.mode = 0
|
|
506
|
-
# self.repeats = 0
|
|
507
|
-
|
|
508
|
-
# def add_channel(self, channel: StimulationChannel, update=False):
|
|
509
|
-
# if self.channels is None:
|
|
510
|
-
# self.channels = {}
|
|
511
|
-
# channel_id = channel.channel_id + 1
|
|
512
|
-
# if channel_id in self.channels.keys():
|
|
513
|
-
# logger.warning(f"Channel {channel_id} already exists")
|
|
514
|
-
# if update:
|
|
515
|
-
# self.channels[channel_id] = channel
|
|
516
|
-
# else:
|
|
517
|
-
# self.channels[channel_id] = channel
|
|
518
|
-
|
|
519
|
-
# # 计算刺激时间
|
|
520
|
-
# duration = channel.duration + channel.ramp_up + channel.ramp_down
|
|
521
|
-
# if self.duration is None or duration > self.duration:
|
|
522
|
-
# self.duration = duration
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
# def to_bytes(self):
|
|
526
|
-
# result = to_bytes(list(self.channels.keys()), 64)
|
|
527
|
-
# result += int(self.duration * 1000).to_bytes(4, 'little')
|
|
528
|
-
# result += int(self.interval_time).to_bytes(4, 'little')
|
|
529
|
-
# result += int(self.characteristic).to_bytes(4, 'little')
|
|
530
|
-
# result += int(self.mode).to_bytes(1, 'little')
|
|
531
|
-
# result += int(self.repeats).to_bytes(4, 'little')
|
|
532
|
-
# for channel in self.channels.values():
|
|
533
|
-
# result += channel.to_bytes()
|
|
534
|
-
# return result
|
|
535
|
-
|
|
536
|
-
# def to_json(self):
|
|
537
|
-
# # Convert the object to JSON for transmission
|
|
538
|
-
# return {
|
|
539
|
-
# "channels": list(self.channels.keys()),
|
|
540
|
-
# "duration": self.duration,
|
|
541
|
-
# "interval_time": self.interval_time,
|
|
542
|
-
# "characteristic": self.characteristic,
|
|
543
|
-
# "mode": self.mode,
|
|
544
|
-
# "repeats": self.repeats,
|
|
545
|
-
# "stim": [channel.to_json() for channel in self.channels.values()]
|
|
546
|
-
# }
|
|
547
|
-
|
|
548
|
-
# @staticmethod
|
|
549
|
-
# def from_json(param: Dict[str, Any]):
|
|
550
|
-
# pass
|
|
551
|
-
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import abc
|
|
3
|
+
|
|
4
|
+
class IDevice(ABC):
|
|
5
|
+
|
|
6
|
+
@property
|
|
7
|
+
@abc.abstractmethod
|
|
8
|
+
def device_type(self) -> int:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def set_device_type(self, value: int):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def device_no(self) -> str:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def set_device_no(self, value: str):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def from_parent(cls, parent) :
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def start_implementation(self) -> None:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def stop_implementation(self) -> None:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def start_acquisition(self) -> None:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def stop_acquisition(self) -> None:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def subscribe(self, type="signal") -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def unsubscribe(self, topic) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def start_stimulation(self, type="signal", duration=0) -> None:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def stop_stimulation(self) -> None:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def __eq__(self, other):
|
|
49
|
+
return self.device_id == other.device_id
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RscDevice(IDevice):
|
|
53
|
+
def __init__(self, socket):
|
|
54
|
+
super().__init__(socket)
|
|
55
|
+
|
|
56
|
+
def start_implementation(self):
|
|
57
|
+
"""
|
|
58
|
+
Start the device implementation
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
61
|
+
|
|
62
|
+
def stop_implementation(self):
|
|
63
|
+
"""
|
|
64
|
+
Stop the device implementation
|
|
65
|
+
"""
|
|
66
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
67
|
+
|
|
68
|
+
def start_acquisition(self):
|
|
69
|
+
"""
|
|
70
|
+
Start data acquisition
|
|
71
|
+
"""
|
|
72
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
73
|
+
|
|
74
|
+
def stop_acquisition(self):
|
|
75
|
+
"""
|
|
76
|
+
Stop data acquisition
|
|
77
|
+
"""
|
|
78
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
79
|
+
|
|
80
|
+
def subscribe(self, type="signal"):
|
|
81
|
+
"""
|
|
82
|
+
Subscribe to data of a specific type (default is "signal")
|
|
83
|
+
"""
|
|
84
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
85
|
+
|
|
86
|
+
def unsubscribe(self, topic):
|
|
87
|
+
"""
|
|
88
|
+
Unsubscribe from a specific topic
|
|
89
|
+
"""
|
|
90
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
91
|
+
|
|
92
|
+
def start_stimulation(self, type="signal", duration=0):
|
|
93
|
+
"""
|
|
94
|
+
Start stimulation of a specific type (default is "signal") for a given duration
|
|
95
|
+
"""
|
|
96
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
97
|
+
|
|
98
|
+
def stop_stimulation(self):
|
|
99
|
+
"""
|
|
100
|
+
Stop stimulation
|
|
101
|
+
"""
|
|
102
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
|
103
|
+
|
|
104
|
+
class ProxyDevice(IDevice):
|
|
105
|
+
def __init__(self, socket, proxy_socket):
|
|
106
|
+
super().__init__(socket)
|
|
107
|
+
self.proxy_socket = proxy_socket
|