qlsdk2 0.4.2__py3-none-any.whl → 0.5.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.
@@ -1,18 +1,17 @@
1
1
  from qlsdk.core.utils import to_channels
2
2
  from loguru import logger
3
3
 
4
- class DataPacket(object):
5
- def __init__(self, device_type, device_id, channels, data):
6
- self.data = data
7
- self.channels = None
8
-
9
-
10
- class RscPacket(object):
4
+ class Packet(object):
11
5
  def __init__(self):
12
6
  self.time_stamp = None
13
7
  self.pkg_id = None
14
8
  self.result = None
15
9
  self.channels = None
10
+
11
+
12
+ class RscPacket(Packet):
13
+ def __init__(self):
14
+ super().__init__()
16
15
  self.origin_sample_rate = None
17
16
  self.sample_rate = None
18
17
  self.sample_num = None
@@ -65,12 +64,9 @@ class RscPacket(object):
65
64
  eeg: {self.eeg}
66
65
  """
67
66
 
68
- class ImpedancePacket(object):
67
+ class ImpedancePacket(Packet):
69
68
  def __init__(self):
70
- self.time_stamp = None
71
- self.pkg_id = None
72
- self.result = None
73
- self.channels = None
69
+ super().__init__()
74
70
  self.data_len = None
75
71
  self.impedance = None
76
72
 
qlsdk/persist/rsc_edf.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime
2
+ import time
2
3
  from multiprocessing import Lock, Queue
3
4
  from time import time_ns
4
5
  from pyedflib import FILETYPE_BDFPLUS, FILETYPE_EDFPLUS, EdfWriter
@@ -14,7 +15,8 @@ EDF_FILE_TYPE = {
14
15
  }
15
16
 
16
17
  class EDFStreamWriter(Thread):
17
- def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path):
18
+ def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path, record_duration=None):
19
+
18
20
  super().__init__()
19
21
  self._writer : EdfWriter = None
20
22
  self.data_queue : Queue = Queue()
@@ -22,6 +24,8 @@ class EDFStreamWriter(Thread):
22
24
  self._points = 0
23
25
  self._duration = 0
24
26
  self._buffer = None
27
+ # 设置edf/bdf文件参数,设置[0.001, 1)可以在1秒内记录多个事件(不建议开启)
28
+ self.record_duration = record_duration
25
29
 
26
30
  # signal info
27
31
  self._channels = channels
@@ -61,9 +65,10 @@ class EDFStreamWriter(Thread):
61
65
  # 数据
62
66
  self.data_queue.put(data)
63
67
 
64
- def trigger(self, onset, desc):
68
+ def trigger(self, onset, desc: str):
65
69
  if self._writer:
66
- self._writer.writeAnnotation(onset, 1, desc)
70
+ logger.trace(f"[{onset} : {desc}]")
71
+ self._writer.writeAnnotation(onset, -1, desc)
67
72
  else:
68
73
  logger.warning("未创建文件,无法写入Trigger标记")
69
74
 
@@ -77,8 +82,9 @@ class EDFStreamWriter(Thread):
77
82
  if self._writer is None:
78
83
  self._init_writer()
79
84
 
80
- while True:
81
- if self._recording or (not self.data_queue.empty()):
85
+ waits = 300
86
+ while waits > 0:
87
+ if not self.data_queue.empty():
82
88
  try:
83
89
  data = self.data_queue.get(timeout=30)
84
90
  if data is None:
@@ -88,13 +94,20 @@ class EDFStreamWriter(Thread):
88
94
  self._points += len(data[1])
89
95
  logger.trace(f"已处理数据点数:{self._points}")
90
96
  self._write_file(data)
97
+ # 有数据重置计数器
98
+ waits = 100 # 重置等待计数器
91
99
  except Exception as e:
92
100
  logger.error(f"异常或超时(30s)结束: {str(e)}")
93
101
  break
94
102
  else:
95
- logger.debug("数据记录完成")
96
- break
103
+ time.sleep(0.1)
104
+ # 记录状态等待30s、非记录状态等待3s
105
+ if self._recording:
106
+ waits -= 1
107
+ else:
108
+ waits -= 10
97
109
 
110
+ logger.info(f"数据记录完成:{self.file_path}")
98
111
  self.close()
99
112
 
100
113
  def _init_writer(self):
@@ -126,6 +139,8 @@ class EDFStreamWriter(Thread):
126
139
  })
127
140
 
128
141
  self._writer.setSignalHeaders(signal_headers)
142
+ if self.record_duration:
143
+ self._writer.setDatarecordDuration(self.record_duration) # 每个数据块1秒
129
144
 
130
145
  def _write_file(self, eeg_data):
131
146
  try:
@@ -159,6 +174,9 @@ class EDFStreamWriter(Thread):
159
174
  # 写入时转置为(样本数, 通道数)格式
160
175
  self._writer.writeSamples(data_float64)
161
176
  self._duration += 1
177
+
178
+ if self._duration % 10 == 0: # 每10秒打印一次进度
179
+ logger.info(f"数据记录中... 文件名:{self.file_path}, 已记录时长: {self._duration}秒")
162
180
 
163
181
  # 用作数据结构一致化处理,通过调用公共类写入edf文件
164
182
  # 入参包含写入edf的全部前置参数
@@ -177,7 +195,7 @@ class RscEDFHandler(object):
177
195
  @author: qlsdk
178
196
  @since: 0.4.0
179
197
  '''
180
- def __init__(self, eeg_sample_rate, physical_max, physical_min, resolution=24, storage_path = None):
198
+ def __init__(self, eeg_sample_rate, physical_max, physical_min, resolution=24, storage_path = None, record_duration=None):
181
199
  # edf文件参数
182
200
  self.physical_max = physical_max
183
201
  self.physical_min = physical_min
@@ -213,6 +231,7 @@ class RscEDFHandler(object):
213
231
  self._total_packets = 0
214
232
  self._lost_packets = 0
215
233
  self._storage_path = storage_path
234
+ self._record_duration = record_duration
216
235
  self._edf_writer_thread = None
217
236
  self._file_prefix = None
218
237
 
@@ -243,6 +262,8 @@ class RscEDFHandler(object):
243
262
  self._device_type = "LJ64S1"
244
263
  elif device_type == 0x60:
245
264
  self._device_type = "ARSKindling"
265
+ elif device_type == 0x339:
266
+ self._device_type = "C16R"
246
267
  else:
247
268
  self._device_type = device_type
248
269
 
@@ -264,17 +285,18 @@ class RscEDFHandler(object):
264
285
  def write(self, packet: RscPacket):
265
286
  # logger.trace(f"packet: {packet}")
266
287
  if packet is None:
288
+ logger.info(f"收到结束信号,即将停止写入数据:{self.file_name}")
267
289
  self._edf_writer_thread.stop_recording()
268
290
  return
269
291
 
270
292
  with self._lock:
271
293
  if self.channels is None:
272
- logger.info(f"开始记录数据到文件...")
294
+ logger.debug(f"开始记录数据到文件...")
273
295
  self.channels = packet.channels
274
296
  self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
275
297
  self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
276
298
  self._start_time = datetime.now()
277
- logger.info(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
299
+ logger.debug(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
278
300
 
279
301
  if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
280
302
  self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
@@ -284,7 +306,7 @@ class RscEDFHandler(object):
284
306
  self._total_packets += 1
285
307
 
286
308
  if self._edf_writer_thread is None:
287
- self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name)
309
+ self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name, self._record_duration)
288
310
  self._edf_writer_thread.set_start_time(self._start_time)
289
311
  self._edf_writer_thread.start()
290
312
  logger.info(f"开始写入数据: {self.file_name}")
@@ -295,9 +317,16 @@ class RscEDFHandler(object):
295
317
  # trigger标记
296
318
  # desc: 标记内容
297
319
  # cur_time: 设备时间时间戳,非设备发出的trigger不要设置
298
- def trigger(self, desc, cur_time=None):
320
+ def trigger(self, desc: str, cur_time=None):
321
+ if self._edf_writer_thread is None:
322
+ logger.warning(f"File writing has not started, discarding trigger {desc}")
323
+ return
324
+
299
325
  if cur_time is None:
300
- onset = datetime.now().timestamp() - self._start_time.timestamp()
326
+ # 计算trigger位置
327
+ if self._start_time:
328
+ onset = datetime.now().timestamp() - self._start_time.timestamp()
329
+ else: onset = 0
301
330
  else:
302
331
  onset = cur_time - self._first_timestamp
303
332
  self._edf_writer_thread.trigger(onset, desc)
@@ -51,15 +51,29 @@ class DeviceCommand(abc.ABC):
51
51
  device_id = int(self.device.device_id) if self.device and self.device.device_id else 0
52
52
  device_type = int(self.device.device_type) if self.device and self.device.device_type else 0
53
53
 
54
- """构建消息头"""
55
- return (
56
- DeviceCommand.HEADER_PREFIX
57
- + int(2).to_bytes(1, 'little') # pkgType
58
- + device_type.to_bytes(1, 'little')
59
- + device_id.to_bytes(4, 'little')
60
- + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
61
- + self.cmd_code.to_bytes(2, 'little')
62
- )
54
+ #兼容设计
55
+ b_device_type = None
56
+ if device_type > 0xFF:
57
+ b_device_type = int.to_bytes(device_type, 2, 'little')
58
+
59
+ if b_device_type is None:
60
+ return (
61
+ DeviceCommand.HEADER_PREFIX
62
+ + int(2).to_bytes(1, 'little') # pkgType
63
+ + device_type.to_bytes(1, 'little')
64
+ + device_id.to_bytes(4, 'little')
65
+ + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
66
+ + self.cmd_code.to_bytes(2, 'little')
67
+ )
68
+ else:
69
+ return (
70
+ DeviceCommand.HEADER_PREFIX
71
+ + b_device_type[0].to_bytes(1, 'little') # pkgType
72
+ + b_device_type[1].to_bytes(1, 'little')
73
+ + device_id.to_bytes(4, 'little')
74
+ + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
75
+ + self.cmd_code.to_bytes(2, 'little')
76
+ )
63
77
 
64
78
  def unpack(self, payload: bytes) -> bytes:
65
79
  """解析消息体"""
@@ -70,7 +84,7 @@ class DeviceCommand(abc.ABC):
70
84
  time = int.from_bytes(body[0:8], 'little')
71
85
  # result - 1B
72
86
  result = body[8]
73
- logger.info(f"[{time}]{self.cmd_desc}{'成功' if result == 0 else '失败'}")
87
+ logger.debug(f"[{time}]{self.cmd_desc}{'成功' if result == 0 else '失败'}")
74
88
 
75
89
 
76
90
 
@@ -156,7 +170,7 @@ class QueryBatteryCommand(DeviceCommand):
156
170
  self.device.battery_total = body[12]
157
171
  # state - 1b
158
172
  # state = body[13]
159
- logger.debug(f"电量更新: {self.device}")
173
+ logger.trace(f"电量更新: {self.device}")
160
174
  else:
161
175
  logger.warning(f"QueryBatteryCommand message received but result is failed.")
162
176
 
@@ -200,6 +214,10 @@ class StartImpedanceCommand(DeviceCommand):
200
214
  body += to_bytes(self.device.acq_channels)
201
215
  body += bytes.fromhex('0000000000000000') # 8字节占位符
202
216
  return body
217
+
218
+ def parse_body(self, body):
219
+ logger.info(f"Received StartImpedance response: {body.hex()}")
220
+ return super().parse_body(body)
203
221
 
204
222
 
205
223
  # 停止阻抗测量
@@ -279,7 +297,7 @@ class ImpedanceDataCommand(DeviceCommand):
279
297
  cmd_desc = "阻抗数据"
280
298
 
281
299
  def parse_body(self, body: bytes):
282
- logger.info(f"Received impedance data: {body.hex()}")
300
+ # logger.info(f"Received impedance data: {body.hex()}")
283
301
  packet = ImpedancePacket().transfer(body)
284
302
 
285
303
  # 信号数据
@@ -294,18 +312,8 @@ class SignalDataCommand(DeviceCommand):
294
312
  # 解析数据包
295
313
  packet = RscPacket()
296
314
  packet.transfer(body)
297
-
298
- # 文件写入到edf
299
- if self.device.edf_handler:
300
- self.device.edf_handler.write(packet)
301
-
302
- if len(self.device.signal_consumers) > 0 :
303
- # 信号数字值转物理值
304
- packet.eeg = self.device.eeg2phy(np.array(packet.eeg))
305
-
306
- # 发送数据包到订阅者
307
- for q in list(self.device.signal_consumers.values()):
308
- q.put(packet)
315
+ # 将数据包传递给设备
316
+ self.device.produce(packet)
309
317
 
310
318
 
311
319
 
@@ -1,2 +1,7 @@
1
1
  from .base import QLBaseDevice
2
- from .device_factory import DeviceFactory
2
+ from .device_factory import DeviceFactory
3
+
4
+ # import devices
5
+ from .c64_rs import C64RS
6
+ from .c16_rs import C16RS
7
+ from .arskindling import ARSKindling
@@ -167,7 +167,7 @@ class ARSKindling(QLBaseDevice):
167
167
  self._edf_handler.set_device_type(self.device_type)
168
168
  self._edf_handler.set_device_no(self.device_no)
169
169
  self._edf_handler.set_storage_path(self._storage_path)
170
- self._edf_handler.set_file_prefix(self._file_prefix)
170
+ self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'ARS')
171
171
 
172
172
  @property
173
173
  def edf_handler(self):
@@ -233,6 +233,8 @@ class ARSKindling(QLBaseDevice):
233
233
  # 设置采集参数
234
234
  def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
235
235
  self._acq_param["original_channels"] = channels
236
+
237
+ # 根据映射关系做通道转换
236
238
  for k in channels.keys():
237
239
  if isinstance(channels[k], list):
238
240
  temp = [k + str(i) for i in channels[k]]
@@ -241,7 +243,7 @@ class ARSKindling(QLBaseDevice):
241
243
  channels[k] = [k + str(channels[k])]
242
244
 
243
245
 
244
-
246
+ # 更新采集参数
245
247
  self._acq_param["channels"] = channels
246
248
  self._acq_param["sample_rate"] = sample_rate
247
249
  self._acq_param["sample_range"] = sample_range
qlsdk/rsc/device/base.py CHANGED
@@ -5,12 +5,13 @@ from time import sleep, time_ns
5
5
  from typing import Any, Dict, Literal
6
6
 
7
7
  from loguru import logger
8
+ import numpy as np
9
+ from qlsdk.core.entity import RscPacket
8
10
  from qlsdk.core.utils import to_bytes
9
11
  from qlsdk.persist import RscEDFHandler
10
12
  from qlsdk.rsc.interface import IDevice, IParser
11
13
  from qlsdk.rsc.parser import TcpMessageParser
12
14
  from qlsdk.rsc.command import StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
13
- # from qlsdk.rsc.command import *
14
15
 
15
16
  class QLBaseDevice(IDevice):
16
17
  def __init__(self, socket):
@@ -19,6 +20,9 @@ class QLBaseDevice(IDevice):
19
20
  # 启动数据解析线程
20
21
  self._parser: IParser = None
21
22
 
23
+ # 用在edf/bdf文件写入中,不建议使用
24
+ self._record_duration = None
25
+
22
26
  # 设备信息
23
27
  self.device_id = None
24
28
  self.device_name = None
@@ -117,8 +121,8 @@ class QLBaseDevice(IDevice):
117
121
  # 存储目录
118
122
 
119
123
  #
120
- self.__signal_consumer: Dict[str, Queue[Any]]={}
121
- self.__impedance_consumer: Dict[str, Queue[Any]]={}
124
+ self._signal_consumer: Dict[str, Queue[Any]]={}
125
+ self._impedance_consumer: Dict[str, Queue[Any]]={}
122
126
 
123
127
  # EDF文件处理器
124
128
  self._edf_handler = None
@@ -128,6 +132,45 @@ class QLBaseDevice(IDevice):
128
132
 
129
133
  def parser(self) -> IParser:
130
134
  return self._parser
135
+
136
+ def set_record_duration(self, record_duration: float):
137
+ self._record_duration = record_duration
138
+
139
+ # 数据包处理
140
+ def produce(self, data: RscPacket, type:Literal['signal', 'impedance']="signal"):
141
+ if data is None: return
142
+
143
+ # 处理信号数据
144
+ self._signal_wrapper(data)
145
+
146
+ # 存储
147
+ if self.storage_enable:
148
+ self._write_signal(data)
149
+
150
+ if len(self.signal_consumers) > 0 :
151
+ # 信号数字值转物理值
152
+ data.eeg = self.eeg2phy(np.array(data.eeg))
153
+
154
+ # 发送数据包到订阅者
155
+ for q in list(self.signal_consumers.values()):
156
+ # 队列满了就丢弃最早的数据
157
+ if q.full():
158
+ q.get()
159
+
160
+ q.put(data)
161
+
162
+ # 信号数据转换(默认不处理)
163
+ def _signal_wrapper(self, data: RscPacket):
164
+ pass
165
+
166
+ def _write_signal(self, data: RscPacket):
167
+ # 文件写入到edf
168
+ if self._edf_handler is None:
169
+ logger.debug("Initializing EDF handler for data storage")
170
+ self.init_edf_handler()
171
+
172
+ if self._edf_handler:
173
+ self._edf_handler.write(data)
131
174
 
132
175
  def start_listening(self):
133
176
 
@@ -142,8 +185,9 @@ class QLBaseDevice(IDevice):
142
185
  return True
143
186
 
144
187
  def stop_listening(self):
145
- logger.info(f"设备{self.device_no}停止socket监听")
146
- self._listening = False
188
+ logger.trace(f"设备{self.device_no}停止socket监听")
189
+ self._listening = False
190
+ self._parser.stop()
147
191
 
148
192
  @property
149
193
  def device_type(self) -> int:
@@ -152,15 +196,15 @@ class QLBaseDevice(IDevice):
152
196
  def start_message_parser(self) -> None:
153
197
  self._parser = TcpMessageParser(self)
154
198
  self._parser.start()
155
- logger.info("TCP消息解析器已启动")
199
+ logger.debug("TCP消息解析器已启动")
156
200
 
157
201
  def start_message_listening(self) -> None:
158
202
  def _accept():
159
203
  while self._listening:
160
- # 缓冲去4M
204
+ # 缓冲区4M
161
205
  data = self.socket.recv(4096*1024)
162
206
  if not data:
163
- logger.warning(f"设备{self.device_name}连接结束")
207
+ logger.warning(f"设备[{self.device_name}]连接结束")
164
208
  break
165
209
 
166
210
  self._parser.append(data)
@@ -169,7 +213,7 @@ class QLBaseDevice(IDevice):
169
213
  self._listening = True
170
214
  message_accept = Thread(target=_accept, daemon=True)
171
215
  message_accept.start()
172
- logger.info(f"socket消息监听已启动")
216
+ logger.debug(f"socket消息监听已启动")
173
217
 
174
218
  def set_device_type(self, type: int):
175
219
  self._device_type = type
@@ -206,6 +250,7 @@ class QLBaseDevice(IDevice):
206
250
  return self._digital_range
207
251
 
208
252
  def init_edf_handler(self):
253
+ logger.warning("init_edf_handler not implemented in base class, should be overridden in subclass")
209
254
  pass
210
255
  # self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
211
256
  # self._edf_handler.set_device_type(self.device_type)
@@ -221,6 +266,7 @@ class QLBaseDevice(IDevice):
221
266
  @property
222
267
  def edf_handler(self):
223
268
  if not self.storage_enable:
269
+ logger.warning("EDF storage is disabled, no edf handler available")
224
270
  return None
225
271
 
226
272
  if self._edf_handler is None:
@@ -247,11 +293,11 @@ class QLBaseDevice(IDevice):
247
293
  return 10
248
294
  @property
249
295
  def signal_consumers(self):
250
- return self.__signal_consumer
296
+ return self._signal_consumer
251
297
 
252
298
  @property
253
299
  def impedance_consumers(self):
254
- return self.__impedance_consumer
300
+ return self._impedance_consumer
255
301
 
256
302
  # 设置记录文件路径
257
303
  def set_storage_path(self, path):
@@ -283,26 +329,26 @@ class QLBaseDevice(IDevice):
283
329
  pass
284
330
 
285
331
  def start_impedance(self):
286
- logger.info("启动阻抗测量")
332
+ logger.info(f"[设备-{self.device_no}]启动阻抗测量")
287
333
  msg = StartImpedanceCommand.build(self).pack()
288
- logger.debug(f"start_impedance message is {msg.hex()}")
334
+ logger.trace(f"start_impedance message is {msg.hex()}")
289
335
  self.socket.sendall(msg)
290
336
 
291
337
  def stop_impedance(self):
292
- logger.info("停止阻抗测量")
338
+ logger.info(f"[设备{self.device_no}]停止阻抗测量")
293
339
  msg = StopImpedanceCommand.build(self).pack()
294
- logger.debug(f"stop_impedance message is {msg.hex()}")
340
+ logger.trace(f"stop_impedance message is {msg.hex()}")
295
341
  self.socket.sendall(msg)
296
342
 
297
343
  def start_stimulation(self):
298
344
  if self.stim_paradigm is None:
299
345
  logger.warning("刺激参数未设置,请先设置刺激参数")
300
346
  return
301
- logger.info("启动电刺激")
347
+ logger.info(f"[设备-{self.device_no}]启动电刺激")
302
348
  msg = StartStimulationCommand.build(self).pack()
303
- logger.debug(f"start_stimulation message is {msg.hex()}")
349
+ logger.trace(f"start_stimulation message is {msg.hex()}")
304
350
  self.socket.sendall(msg)
305
- t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
351
+ t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
306
352
  t.start()
307
353
 
308
354
  def _stop_stimulation_trigger(self, duration):
@@ -310,67 +356,74 @@ class QLBaseDevice(IDevice):
310
356
  while delay > 0:
311
357
  sleep(1)
312
358
  delay -= 1
313
- logger.info(f"_stop_stimulation_trigger duration: {duration}")
359
+ logger.debug(f"_stop_stimulation_trigger duration: {duration}")
314
360
  if self._edf_handler:
315
361
  self._edf_handler.trigger("stimulation should be stopped")
316
362
  else:
317
363
  logger.warning("stop stim trigger fail. no edf writer alive")
318
364
 
319
365
  def stop_stimulation(self):
320
- logger.info("停止电刺激")
366
+ logger.info(f"[设备-{self.device_no}]停止电刺激")
321
367
  msg = StopStimulationCommand.pack()
322
- logger.debug(f"stop_stimulation message is {msg.hex()}")
368
+ logger.trace(f"stop_stimulation message is {msg.hex()}")
323
369
  self.socket.sendall(msg)
324
370
 
325
371
  # 启动采集
326
372
  def start_acquisition(self, recording = True):
327
- logger.info("启动信号采集")
373
+ logger.info(f"[设备-{self.device_no}]启动信号采集")
328
374
  self._recording = recording
375
+ # 初始化EDF处理器
376
+ self.init_edf_handler()
329
377
  # 设置数据采集参数
330
378
  param_bytes = SetAcquisitionParamCommand.build(self).pack()
331
379
  # 启动数据采集
332
380
  start_bytes = StartAcquisitionCommand.build(self).pack()
333
381
  msg = param_bytes + start_bytes
334
- logger.debug(f"start_acquisition message is {msg.hex()}")
382
+ logger.trace(f"start_acquisition message is {msg.hex()}")
335
383
  self.socket.sendall(msg)
336
384
 
337
385
  # 停止采集
338
386
  def stop_acquisition(self):
339
- logger.info("停止信号采集")
387
+ logger.info(f"[设备-{self.device_no}]停止信号采集")
340
388
  msg = StopAcquisitionCommand.build(self).pack()
341
- logger.debug(f"stop_acquisition message is {msg.hex()}")
389
+ logger.trace(f"stop_acquisition message is {msg.hex()}")
342
390
  self.socket.sendall(msg)
343
391
  if self._edf_handler:
344
392
  # 发送结束标识
345
393
  self.edf_handler.write(None)
346
394
 
347
- # 订阅实时数据
395
+ '''
396
+ 订阅数据
397
+ topic: 订阅主题
398
+ q: 数据队列
399
+ type: 数据类型,signal-信号数据,impedance-阻抗数据
400
+ '''
348
401
  def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
349
402
 
350
- logger.info(f"base订阅{self.device_name}的数据流")
351
- # 数据队列
403
+ # 队列名称
404
+ if topic is None:
405
+ topic = f"{self.device_no}_{type}_{time_ns()}"
406
+
407
+ logger.debug(f"[设备-{self.device_no}]订阅数据流: {topic}, type: {type}")
408
+
352
409
  # 数据队列
353
410
  if q is None:
354
411
  q = Queue(maxsize=1000)
355
-
356
- # 队列名称
357
- if topic is None:
358
- topic = f"{type}_{time_ns()}"
359
412
 
360
413
  # 订阅生理电信号数据
361
414
  if type == "signal":
362
415
  # topic唯一,用来区分不同的订阅队列(下同)
363
- if topic in list(self.__signal_consumer.keys()):
364
- logger.warning(f"exists {type} subscribe of {topic}")
416
+ if topic in list(self._signal_consumer.keys()):
417
+ logger.warning(f"已存在主题[{topic}]的信号数据订阅!")
365
418
  else:
366
- self.__signal_consumer[topic] = q
419
+ self._signal_consumer[topic] = q
367
420
 
368
421
  # 订阅阻抗数据
369
422
  if type == "impedance":
370
- if topic in list(self.__signal_consumer.keys()):
371
- logger.warning(f"exists {type} subscribe of {topic}")
423
+ if topic in list(self._impedance_consumer.keys()):
424
+ logger.warning(f"已存在主题[{topic}]的阻抗数据订阅!")
372
425
  else:
373
- self.__impedance_consumer[topic] = q
426
+ self._impedance_consumer[topic] = q
374
427
 
375
428
  return topic, q
376
429
 
@@ -378,7 +431,7 @@ class QLBaseDevice(IDevice):
378
431
  if self._edf_handler:
379
432
  self.edf_handler.trigger(desc)
380
433
  else:
381
- logger.warning("no edf handler, no place to recording trigger")
434
+ logger.warning("没有开启文件记录时,无法记录trigger信息")
382
435
 
383
436
  def gen_set_acquirement_param(self) -> bytes:
384
437