qlsdk2 0.4.2__tar.gz → 0.5.0__tar.gz

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.
Files changed (74) hide show
  1. qlsdk2-0.5.0/PKG-INFO +40 -0
  2. qlsdk2-0.5.0/README.md +12 -0
  3. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/setup.py +2 -2
  4. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/rsc_edf.py +21 -6
  5. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/command/__init__.py +26 -22
  6. qlsdk2-0.5.0/src/qlsdk/rsc/device/__init__.py +7 -0
  7. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/arskindling.py +4 -2
  8. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/base.py +80 -38
  9. qlsdk2-0.5.0/src/qlsdk/rsc/device/c16_rs.py +205 -0
  10. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/c256_rs.py +1 -0
  11. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/c64_rs.py +3 -2
  12. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/c64s1.py +3 -2
  13. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/device/device_factory.py +1 -0
  14. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/device.py +3 -0
  15. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/manager/container.py +17 -18
  16. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/network/discover.py +25 -36
  17. qlsdk2-0.5.0/src/qlsdk/rsc/parser/base.py +150 -0
  18. qlsdk2-0.5.0/src/qlsdk2.egg-info/PKG-INFO +40 -0
  19. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/SOURCES.txt +1 -0
  20. qlsdk2-0.4.2/PKG-INFO +0 -121
  21. qlsdk2-0.4.2/README.md +0 -93
  22. qlsdk2-0.4.2/src/qlsdk/rsc/device/__init__.py +0 -2
  23. qlsdk2-0.4.2/src/qlsdk/rsc/parser/base.py +0 -69
  24. qlsdk2-0.4.2/src/qlsdk2.egg-info/PKG-INFO +0 -121
  25. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/setup.cfg +0 -0
  26. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/__init__.py +0 -0
  27. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/ar4/__init__.py +0 -0
  28. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/ar4m/__init__.py +0 -0
  29. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/__init__.py +0 -0
  30. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/crc/__init__.py +0 -0
  31. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/crc/crctools.py +0 -0
  32. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/device.py +0 -0
  33. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/entity/__init__.py +0 -0
  34. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/exception.py +0 -0
  35. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/filter/__init__.py +0 -0
  36. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/filter/norch.py +0 -0
  37. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/local.py +0 -0
  38. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/__init__.py +0 -0
  39. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/command.py +0 -0
  40. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/tcp.py +0 -0
  41. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/message/udp.py +0 -0
  42. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/network/__init__.py +0 -0
  43. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/network/monitor.py +0 -0
  44. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/core/utils.py +0 -0
  45. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/__init__.py +0 -0
  46. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/ars_edf.py +0 -0
  47. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/edf.py +0 -0
  48. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/persist/stream.py +0 -0
  49. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/__init__.py +0 -0
  50. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/eegion.py +0 -0
  51. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/entity.py +0 -0
  52. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/__init__.py +0 -0
  53. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/command.py +0 -0
  54. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/handler.py +0 -0
  55. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/interface/parser.py +0 -0
  56. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/manager/__init__.py +0 -0
  57. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/manager/search.py +0 -0
  58. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/network/__init__.py +0 -0
  59. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/paradigm.py +0 -0
  60. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/parser/__init__.py +0 -0
  61. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/rsc/proxy.py +0 -0
  62. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/__init__.py +0 -0
  63. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/ar4sdk.py +0 -0
  64. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/hub.py +0 -0
  65. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
  66. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
  67. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/x8/__init__.py +0 -0
  68. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk/x8m/__init__.py +0 -0
  69. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
  70. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/requires.txt +0 -0
  71. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/src/qlsdk2.egg-info/top_level.txt +0 -0
  72. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/test/test.222.py +0 -0
  73. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/test/test.py +0 -0
  74. {qlsdk2-0.4.2 → qlsdk2-0.5.0}/test/test_ar4m.py +0 -0
qlsdk2-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.2
2
+ Name: qlsdk2
3
+ Version: 0.5.0
4
+ Summary: SDK for quanlan device
5
+ Home-page: https://github.com/hehuajun/qlsdk
6
+ Author: hehuajun
7
+ Author-email: hehuajun@eegion.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: loguru>=0.6.0
14
+ Requires-Dist: numpy>=1.23.5
15
+ Requires-Dist: bitarray>=1.5.3
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=6.0; extra == "dev"
18
+ Requires-Dist: twine>=3.0; extra == "dev"
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+ 版本:v0.5.0
30
+ 时间:2025-07-29
31
+ [新特性]
32
+ 1. C16R设备搜索
33
+ 2、C16R设备连接
34
+ 3、C16R信号采集/停止
35
+ 4、C16R数据自动记录到文件
36
+ 5、C16R采集通道设置支持数字和名称两种模式(可混用)
37
+
38
+ [优化]
39
+ 1、提升信号接收及指令解析性能
40
+ 2、日志级别及文案优化
qlsdk2-0.5.0/README.md ADDED
@@ -0,0 +1,12 @@
1
+ 版本:v0.5.0
2
+ 时间:2025-07-29
3
+ [新特性]
4
+ 1. C16R设备搜索
5
+ 2、C16R设备连接
6
+ 3、C16R信号采集/停止
7
+ 4、C16R数据自动记录到文件
8
+ 5、C16R采集通道设置支持数字和名称两种模式(可混用)
9
+
10
+ [优化]
11
+ 1、提升信号接收及指令解析性能
12
+ 2、日志级别及文案优化
@@ -1,12 +1,12 @@
1
1
  # setup.py
2
2
  import setuptools
3
3
 
4
- with open("README.md", "r") as fh:
4
+ with open("README.md", "r", encoding='utf-8' ) as fh:
5
5
  long_description = fh.read()
6
6
 
7
7
  setuptools.setup(
8
8
  name="qlsdk2",
9
- version="0.4.2",
9
+ version="0.5.0",
10
10
  author="hehuajun",
11
11
  author_email="hehuajun@eegion.com",
12
12
  description="SDK for quanlan device",
@@ -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
@@ -77,8 +78,9 @@ class EDFStreamWriter(Thread):
77
78
  if self._writer is None:
78
79
  self._init_writer()
79
80
 
80
- while True:
81
- if self._recording or (not self.data_queue.empty()):
81
+ waits = 300
82
+ while waits > 0:
83
+ if not self.data_queue.empty():
82
84
  try:
83
85
  data = self.data_queue.get(timeout=30)
84
86
  if data is None:
@@ -88,13 +90,20 @@ class EDFStreamWriter(Thread):
88
90
  self._points += len(data[1])
89
91
  logger.trace(f"已处理数据点数:{self._points}")
90
92
  self._write_file(data)
93
+ # 有数据重置计数器
94
+ waits = 100 # 重置等待计数器
91
95
  except Exception as e:
92
96
  logger.error(f"异常或超时(30s)结束: {str(e)}")
93
97
  break
94
98
  else:
95
- logger.debug("数据记录完成")
96
- break
99
+ time.sleep(0.1)
100
+ # 记录状态等待30s、非记录状态等待3s
101
+ if self._recording:
102
+ waits -= 1
103
+ else:
104
+ waits -= 10
97
105
 
106
+ logger.info(f"数据记录完成:{self.file_path}")
98
107
  self.close()
99
108
 
100
109
  def _init_writer(self):
@@ -159,6 +168,9 @@ class EDFStreamWriter(Thread):
159
168
  # 写入时转置为(样本数, 通道数)格式
160
169
  self._writer.writeSamples(data_float64)
161
170
  self._duration += 1
171
+
172
+ if self._duration % 10 == 0: # 每10秒打印一次进度
173
+ logger.info(f"数据记录中... 文件名:{self.file_path}, 已记录时长: {self._duration}秒")
162
174
 
163
175
  # 用作数据结构一致化处理,通过调用公共类写入edf文件
164
176
  # 入参包含写入edf的全部前置参数
@@ -243,6 +255,8 @@ class RscEDFHandler(object):
243
255
  self._device_type = "LJ64S1"
244
256
  elif device_type == 0x60:
245
257
  self._device_type = "ARSKindling"
258
+ elif device_type == 0x339:
259
+ self._device_type = "C16R"
246
260
  else:
247
261
  self._device_type = device_type
248
262
 
@@ -264,17 +278,18 @@ class RscEDFHandler(object):
264
278
  def write(self, packet: RscPacket):
265
279
  # logger.trace(f"packet: {packet}")
266
280
  if packet is None:
281
+ logger.info(f"收到结束信号,即将停止写入数据:{self.file_name}")
267
282
  self._edf_writer_thread.stop_recording()
268
283
  return
269
284
 
270
285
  with self._lock:
271
286
  if self.channels is None:
272
- logger.info(f"开始记录数据到文件...")
287
+ logger.debug(f"开始记录数据到文件...")
273
288
  self.channels = packet.channels
274
289
  self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
275
290
  self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
276
291
  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}")
292
+ logger.debug(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
278
293
 
279
294
  if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
280
295
  self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
@@ -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
 
@@ -294,18 +308,8 @@ class SignalDataCommand(DeviceCommand):
294
308
  # 解析数据包
295
309
  packet = RscPacket()
296
310
  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)
311
+ # 将数据包传递给设备
312
+ self.device.produce(packet)
309
313
 
310
314
 
311
315
 
@@ -0,0 +1,7 @@
1
+ from .base import QLBaseDevice
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
@@ -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):
@@ -117,8 +118,8 @@ class QLBaseDevice(IDevice):
117
118
  # 存储目录
118
119
 
119
120
  #
120
- self.__signal_consumer: Dict[str, Queue[Any]]={}
121
- self.__impedance_consumer: Dict[str, Queue[Any]]={}
121
+ self._signal_consumer: Dict[str, Queue[Any]]={}
122
+ self._impedance_consumer: Dict[str, Queue[Any]]={}
122
123
 
123
124
  # EDF文件处理器
124
125
  self._edf_handler = None
@@ -128,6 +129,38 @@ class QLBaseDevice(IDevice):
128
129
 
129
130
  def parser(self) -> IParser:
130
131
  return self._parser
132
+
133
+ # 数据包处理
134
+ def produce(self, data: RscPacket):
135
+ if data is None: return
136
+
137
+ # 处理信号数据
138
+ self._signal_wrapper(data)
139
+
140
+ # 存储
141
+ if self.storage_enable:
142
+ self._write_signal(data)
143
+
144
+ if len(self.signal_consumers) > 0 :
145
+ # 信号数字值转物理值
146
+ data.eeg = self.eeg2phy(np.array(data.eeg))
147
+
148
+ # 发送数据包到订阅者
149
+ for q in list(self.signal_consumers.values()):
150
+ q.put(data)
151
+
152
+ # 信号数据转换(默认不处理)
153
+ def _signal_wrapper(self, data: RscPacket):
154
+ pass
155
+
156
+ def _write_signal(self, data: RscPacket):
157
+ # 文件写入到edf
158
+ if self._edf_handler is None:
159
+ logger.debug("Initializing EDF handler for data storage")
160
+ self.init_edf_handler()
161
+
162
+ if self._edf_handler:
163
+ self._edf_handler.write(data)
131
164
 
132
165
  def start_listening(self):
133
166
 
@@ -142,7 +175,7 @@ class QLBaseDevice(IDevice):
142
175
  return True
143
176
 
144
177
  def stop_listening(self):
145
- logger.info(f"设备{self.device_no}停止socket监听")
178
+ logger.trace(f"设备{self.device_no}停止socket监听")
146
179
  self._listening = False
147
180
 
148
181
  @property
@@ -152,15 +185,15 @@ class QLBaseDevice(IDevice):
152
185
  def start_message_parser(self) -> None:
153
186
  self._parser = TcpMessageParser(self)
154
187
  self._parser.start()
155
- logger.info("TCP消息解析器已启动")
188
+ logger.debug("TCP消息解析器已启动")
156
189
 
157
190
  def start_message_listening(self) -> None:
158
191
  def _accept():
159
192
  while self._listening:
160
- # 缓冲去4M
193
+ # 缓冲区4M
161
194
  data = self.socket.recv(4096*1024)
162
195
  if not data:
163
- logger.warning(f"设备{self.device_name}连接结束")
196
+ logger.warning(f"设备[{self.device_name}]连接结束")
164
197
  break
165
198
 
166
199
  self._parser.append(data)
@@ -169,7 +202,7 @@ class QLBaseDevice(IDevice):
169
202
  self._listening = True
170
203
  message_accept = Thread(target=_accept, daemon=True)
171
204
  message_accept.start()
172
- logger.info(f"socket消息监听已启动")
205
+ logger.debug(f"socket消息监听已启动")
173
206
 
174
207
  def set_device_type(self, type: int):
175
208
  self._device_type = type
@@ -206,6 +239,7 @@ class QLBaseDevice(IDevice):
206
239
  return self._digital_range
207
240
 
208
241
  def init_edf_handler(self):
242
+ logger.warning("init_edf_handler not implemented in base class, should be overridden in subclass")
209
243
  pass
210
244
  # self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
211
245
  # self._edf_handler.set_device_type(self.device_type)
@@ -221,6 +255,7 @@ class QLBaseDevice(IDevice):
221
255
  @property
222
256
  def edf_handler(self):
223
257
  if not self.storage_enable:
258
+ logger.warning("EDF storage is disabled, no edf handler available")
224
259
  return None
225
260
 
226
261
  if self._edf_handler is None:
@@ -247,11 +282,11 @@ class QLBaseDevice(IDevice):
247
282
  return 10
248
283
  @property
249
284
  def signal_consumers(self):
250
- return self.__signal_consumer
285
+ return self._signal_consumer
251
286
 
252
287
  @property
253
288
  def impedance_consumers(self):
254
- return self.__impedance_consumer
289
+ return self._impedance_consumer
255
290
 
256
291
  # 设置记录文件路径
257
292
  def set_storage_path(self, path):
@@ -283,26 +318,26 @@ class QLBaseDevice(IDevice):
283
318
  pass
284
319
 
285
320
  def start_impedance(self):
286
- logger.info("启动阻抗测量")
321
+ logger.info(f"[设备-{self.device_no}]启动阻抗测量")
287
322
  msg = StartImpedanceCommand.build(self).pack()
288
- logger.debug(f"start_impedance message is {msg.hex()}")
323
+ logger.trace(f"start_impedance message is {msg.hex()}")
289
324
  self.socket.sendall(msg)
290
325
 
291
326
  def stop_impedance(self):
292
- logger.info("停止阻抗测量")
327
+ logger.info(f"[设备{self.device_no}]停止阻抗测量")
293
328
  msg = StopImpedanceCommand.build(self).pack()
294
- logger.debug(f"stop_impedance message is {msg.hex()}")
329
+ logger.trace(f"stop_impedance message is {msg.hex()}")
295
330
  self.socket.sendall(msg)
296
331
 
297
332
  def start_stimulation(self):
298
333
  if self.stim_paradigm is None:
299
334
  logger.warning("刺激参数未设置,请先设置刺激参数")
300
335
  return
301
- logger.info("启动电刺激")
336
+ logger.info(f"[设备-{self.device_no}]启动电刺激")
302
337
  msg = StartStimulationCommand.build(self).pack()
303
- logger.debug(f"start_stimulation message is {msg.hex()}")
338
+ logger.trace(f"start_stimulation message is {msg.hex()}")
304
339
  self.socket.sendall(msg)
305
- t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
340
+ t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
306
341
  t.start()
307
342
 
308
343
  def _stop_stimulation_trigger(self, duration):
@@ -310,67 +345,74 @@ class QLBaseDevice(IDevice):
310
345
  while delay > 0:
311
346
  sleep(1)
312
347
  delay -= 1
313
- logger.info(f"_stop_stimulation_trigger duration: {duration}")
348
+ logger.debug(f"_stop_stimulation_trigger duration: {duration}")
314
349
  if self._edf_handler:
315
350
  self._edf_handler.trigger("stimulation should be stopped")
316
351
  else:
317
352
  logger.warning("stop stim trigger fail. no edf writer alive")
318
353
 
319
354
  def stop_stimulation(self):
320
- logger.info("停止电刺激")
355
+ logger.info(f"[设备-{self.device_no}]停止电刺激")
321
356
  msg = StopStimulationCommand.pack()
322
- logger.debug(f"stop_stimulation message is {msg.hex()}")
357
+ logger.trace(f"stop_stimulation message is {msg.hex()}")
323
358
  self.socket.sendall(msg)
324
359
 
325
360
  # 启动采集
326
361
  def start_acquisition(self, recording = True):
327
- logger.info("启动信号采集")
362
+ logger.info(f"[设备-{self.device_no}]启动信号采集")
328
363
  self._recording = recording
364
+ # 初始化EDF处理器
365
+ self.init_edf_handler()
329
366
  # 设置数据采集参数
330
367
  param_bytes = SetAcquisitionParamCommand.build(self).pack()
331
368
  # 启动数据采集
332
369
  start_bytes = StartAcquisitionCommand.build(self).pack()
333
370
  msg = param_bytes + start_bytes
334
- logger.debug(f"start_acquisition message is {msg.hex()}")
371
+ logger.trace(f"start_acquisition message is {msg.hex()}")
335
372
  self.socket.sendall(msg)
336
373
 
337
374
  # 停止采集
338
375
  def stop_acquisition(self):
339
- logger.info("停止信号采集")
376
+ logger.info(f"[设备-{self.device_no}]停止信号采集")
340
377
  msg = StopAcquisitionCommand.build(self).pack()
341
- logger.debug(f"stop_acquisition message is {msg.hex()}")
378
+ logger.trace(f"stop_acquisition message is {msg.hex()}")
342
379
  self.socket.sendall(msg)
343
380
  if self._edf_handler:
344
381
  # 发送结束标识
345
382
  self.edf_handler.write(None)
346
383
 
347
- # 订阅实时数据
384
+ '''
385
+ 订阅数据
386
+ topic: 订阅主题
387
+ q: 数据队列
388
+ type: 数据类型,signal-信号数据,impedance-阻抗数据
389
+ '''
348
390
  def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
349
391
 
350
- logger.info(f"base订阅{self.device_name}的数据流")
351
- # 数据队列
392
+ # 队列名称
393
+ if topic is None:
394
+ topic = f"{self.device_no}_{type}_{time_ns()}"
395
+
396
+ logger.debug(f"[设备-{self.device_no}]订阅数据流: {topic}, type: {type}")
397
+
352
398
  # 数据队列
353
399
  if q is None:
354
400
  q = Queue(maxsize=1000)
355
-
356
- # 队列名称
357
- if topic is None:
358
- topic = f"{type}_{time_ns()}"
359
401
 
360
402
  # 订阅生理电信号数据
361
403
  if type == "signal":
362
404
  # topic唯一,用来区分不同的订阅队列(下同)
363
- if topic in list(self.__signal_consumer.keys()):
364
- logger.warning(f"exists {type} subscribe of {topic}")
405
+ if topic in list(self._signal_consumer.keys()):
406
+ logger.warning(f"已存在主题[{topic}]的信号数据订阅!")
365
407
  else:
366
- self.__signal_consumer[topic] = q
408
+ self._signal_consumer[topic] = q
367
409
 
368
410
  # 订阅阻抗数据
369
411
  if type == "impedance":
370
- if topic in list(self.__signal_consumer.keys()):
371
- logger.warning(f"exists {type} subscribe of {topic}")
412
+ if topic in list(self._impedance_consumer.keys()):
413
+ logger.warning(f"已存在主题[{topic}]的阻抗数据订阅!")
372
414
  else:
373
- self.__impedance_consumer[topic] = q
415
+ self._impedance_consumer[topic] = q
374
416
 
375
417
  return topic, q
376
418
 
@@ -378,7 +420,7 @@ class QLBaseDevice(IDevice):
378
420
  if self._edf_handler:
379
421
  self.edf_handler.trigger(desc)
380
422
  else:
381
- logger.warning("no edf handler, no place to recording trigger")
423
+ logger.warning("没有开启文件记录时,无法记录trigger信息")
382
424
 
383
425
  def gen_set_acquirement_param(self) -> bytes:
384
426