qlsdk2 0.4.2__py3-none-any.whl → 0.5.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/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
@@ -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
 
@@ -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):
@@ -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
 
@@ -0,0 +1,205 @@
1
+
2
+ from multiprocessing import Queue
3
+ from threading import Thread
4
+ from time import sleep, time_ns
5
+ from typing import Any, Dict, Literal
6
+
7
+ from loguru import logger
8
+ from qlsdk.persist import RscEDFHandler
9
+ from qlsdk.rsc.device.device_factory import DeviceFactory
10
+ from qlsdk.rsc.interface import IDevice
11
+ from qlsdk.rsc.command import *
12
+ from qlsdk.rsc.device.base import QLBaseDevice
13
+
14
+ '''
15
+ C16RS设备类,继承自QLBaseDevice
16
+ 提供设备特定的属性和方法,包括设备类型、采集参数设置、电刺激参
17
+ '''
18
+ class C16RS(QLBaseDevice):
19
+
20
+ device_type = 0x339 # C16RS设备类型标识符
21
+
22
+ def __init__(self, socket):
23
+
24
+ super().__init__(socket)
25
+ # 存储通道反向映射位置值
26
+ self._reverse_ch_pos = None
27
+ self.channel_name_mapping = {
28
+ "FP1": 1,
29
+ "FP2": 2,
30
+ "C3": 3,
31
+ "C4": 4,
32
+ "O1": 5,
33
+ "O2": 6,
34
+ "CZ": 7,
35
+ "T7": 8,
36
+ "T8": 9,
37
+ "M1": 10,
38
+ "M2": 11,
39
+ "F3": 12,
40
+ "F4": 13,
41
+ "FZ": 14,
42
+ "F7": 15,
43
+ "F8": 16,
44
+ "PZ": 17,
45
+ "P3": 18,
46
+ "P7": 19,
47
+ "P4": 20,
48
+ "P8": 21
49
+ }
50
+
51
+ # self.channel_mapping = {
52
+ # "1": 61,
53
+ # "2": 62,
54
+ # "3": 63,
55
+ # "4": 64,
56
+ # "5": 65,
57
+ # "6": 66,
58
+ # "7": 68,
59
+ # "8": 67,
60
+ # "9": 53,
61
+ # "10": 54,
62
+ # "11": 55,
63
+ # "12": 57,
64
+ # "13": 59,
65
+ # "14": 58,
66
+ # "15": 60,
67
+ # "16": 56,
68
+ # "17": 26,
69
+ # "18": 25,
70
+ # "19": 24,
71
+ # "20": 23,
72
+ # "21": 22
73
+ # }
74
+
75
+ self.channel_mapping = {
76
+ "1": 2,
77
+ "2": 3,
78
+ "3": 27,
79
+ "4": 31,
80
+ "5": 60,
81
+ "6": 61,
82
+ "7": 25,
83
+ "8": 29,
84
+ "9": 33,
85
+ "10": 62,
86
+ "11": 63,
87
+ "12": 10,
88
+ "13": 14,
89
+ "14": 8,
90
+ "15": 12,
91
+ "16": 16,
92
+ "17": 43,
93
+ "18": 45,
94
+ "19": 47,
95
+ "20": 49,
96
+ "21": 51
97
+ }
98
+
99
+ self.channel_display_mapping = {
100
+ 2: 1,
101
+ 3: 2,
102
+ 27: 3,
103
+ 31: 4,
104
+ 60: 5,
105
+ 61: 6,
106
+ 25: 7,
107
+ 29: 8,
108
+ 33: 9,
109
+ 62: 10,
110
+ 63: 11,
111
+ 10: 12,
112
+ 14: 13,
113
+ 8: 14,
114
+ 12: 15,
115
+ 16: 16,
116
+ 43: 17,
117
+ 45: 18,
118
+ 47: 19,
119
+ 49: 20,
120
+ 51: 21
121
+ }
122
+
123
+
124
+ @classmethod
125
+ def from_parent(cls, parent:IDevice) -> IDevice:
126
+ rlt = cls(parent.socket)
127
+ rlt.device_id = parent.device_id
128
+ rlt._device_no = parent.device_no
129
+ return rlt
130
+
131
+
132
+ def init_edf_handler(self):
133
+ self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
134
+ self._edf_handler.set_device_type(self.device_type)
135
+ self._edf_handler.set_device_no(self.device_no)
136
+ self._edf_handler.set_storage_path(self._storage_path)
137
+ self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C16R')
138
+
139
+ # 设置刺激参数
140
+ def set_stim_param(self, param):
141
+ self.stim_paradigm = param
142
+
143
+ # 设置采集参数
144
+ def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
145
+ # 保存原始通道参数
146
+ self._acq_param["original_channels"] = channels
147
+
148
+ # 名称转换为数字通道
149
+ channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
150
+
151
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
152
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
153
+ channels = [self.channel_mapping.get(str(i), -1) for i in channels]
154
+ channels = [i if i != -1 else channels[0] for i in channels]
155
+
156
+ # 更新采集参数
157
+ self._acq_param["channels"] = channels
158
+ self._acq_param["sample_rate"] = sample_rate
159
+ self._acq_param["sample_range"] = sample_range
160
+ self._acq_channels = channels
161
+ self._sample_rate = sample_rate
162
+ self._sample_range = sample_range
163
+
164
+ logger.debug(f"C16RS: set_acq_param: {self._acq_param}")
165
+
166
+ # 参数改变后,重置通道位置映射
167
+ self._reverse_ch_pos = None
168
+
169
+ # 信号数据转换(默认不处理)
170
+ def _signal_wrapper(self, data: RscPacket):
171
+ if data is None:
172
+ return
173
+ # 根据映射关系做通道转换-(注意数据和通道的一致性)
174
+ # data.channels = [self.channel_display_mapping.get(i, i) for i in data.channels]
175
+
176
+ # 升级为类变量,减少计算
177
+ if self._reverse_ch_pos is None:
178
+ self._reverse_ch_pos = map_indices(self._acq_param["channels"], data.channels)
179
+
180
+ # 更新通道(数据)顺序和输入一致
181
+ data.channels = self._acq_param["original_channels"]
182
+ data.eeg = [data.eeg[i] for i in self._reverse_ch_pos]
183
+
184
+
185
+ def map_indices(A, B):
186
+ """
187
+
188
+ 参数:
189
+ A: 源数组(无重复值)
190
+ B: 目标数组(无重复值)
191
+
192
+ 返回:
193
+ C: 与A长度相同的数组,元素为A中对应值在B中的索引(不存在则为-1)
194
+ """
195
+ # 创建B的值到索引的映射字典(O(n)操作)
196
+ b_map = {value: idx for idx, value in enumerate(B)}
197
+
198
+ # 遍历A,获取每个元素在B中的位置(O(m)操作)
199
+ return [b_map.get(a, -1) for a in A]
200
+
201
+
202
+ # Register the C16RS device with the DeviceFactory
203
+ DeviceFactory.register(C16RS.device_type, C16RS)
204
+
205
+
@@ -16,6 +16,7 @@ class C256RS(QLBaseDevice):
16
16
  device_type = 0x40 # C64RS设备类型标识符
17
17
 
18
18
  def __init__(self, socket):
19
+ super().__init__(socket)
19
20
  self.socket = socket
20
21
 
21
22
  self._id = None
@@ -16,6 +16,7 @@ class C64RS(QLBaseDevice):
16
16
  device_type = 0x39 # C64RS设备类型标识符
17
17
 
18
18
  def __init__(self, socket):
19
+ super().__init__(socket)
19
20
  self.socket = socket
20
21
 
21
22
  self._id = None
@@ -145,9 +146,9 @@ class C64RS(QLBaseDevice):
145
146
  def init_edf_handler(self):
146
147
  self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
147
148
  self._edf_handler.set_device_type(self.device_type)
148
- self._edf_handler.set_device_no(self.device_name)
149
+ self._edf_handler.set_device_no(self.device_no)
149
150
  self._edf_handler.set_storage_path(self._storage_path)
150
- self._edf_handler.set_file_prefix(self._file_prefix)
151
+ self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C64RS')
151
152
 
152
153
  @property
153
154
  def edf_handler(self):
qlsdk/rsc/device/c64s1.py CHANGED
@@ -16,6 +16,7 @@ class C64S1(QLBaseDevice):
16
16
  device_type = 0x40 # C64RS设备类型标识符
17
17
 
18
18
  def __init__(self, socket):
19
+ super().__init__(socket)
19
20
  self.socket = socket
20
21
 
21
22
  self._id = None
@@ -151,9 +152,9 @@ class C64S1(QLBaseDevice):
151
152
  def init_edf_handler(self):
152
153
  self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
153
154
  self._edf_handler.set_device_type(self.device_type)
154
- self._edf_handler.set_device_no(self.device_name)
155
+ self._edf_handler.set_device_no(self.device_no)
155
156
  self._edf_handler.set_storage_path(self._storage_path)
156
- self._edf_handler.set_file_prefix(self._file_prefix)
157
+ self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C64S1')
157
158
 
158
159
  @property
159
160
  def edf_handler(self):
@@ -26,6 +26,7 @@ class DeviceFactory(object):
26
26
 
27
27
  # Register the C64RS device with the DeviceFactory
28
28
  DeviceFactory.register(C64RS.device_type, C64RS)
29
+ # Register the ARSKindling device with the DeviceFactory
29
30
  DeviceFactory.register(ARSKindling.device_type, ARSKindling)
30
31
 
31
32
 
@@ -15,6 +15,9 @@ class IDevice(ABC):
15
15
  def device_no(self) -> str:
16
16
  pass
17
17
 
18
+ def produce(self, data) -> None:
19
+ pass
20
+
18
21
  def start_listening(self):
19
22
  pass
20
23
 
@@ -33,15 +33,15 @@ class DeviceContainer(object):
33
33
  等待设备连接
34
34
  '''
35
35
  def connect(self, device_id: str, timeout:int=30) -> Optional[IDevice]:
36
- logger.info(f"Searching for device: {device_id}")
36
+ logger.info(f"开始搜索设备: {device_id}")
37
37
  self.add_search(device_id)
38
38
  for s in range(timeout):
39
39
  device = self.get_device(device_id)
40
40
  if device:
41
- logger.success(f"Device {device_id} connected")
41
+ logger.success(f"设备[{device_id}]已连接成功。")
42
42
  return device
43
43
  time.sleep(1)
44
- logger.error(f"Device {device_id} not found")
44
+ logger.warning(f"{timeout}内未搜索到设备:{device_id}")
45
45
  return None
46
46
 
47
47
  def _listening(self):
@@ -55,15 +55,16 @@ class DeviceContainer(object):
55
55
 
56
56
  while True:
57
57
  client_socket, addr = tcp_socket.accept()
58
- logger.info(f"接收到来自 {addr[0]}:{addr[1]} 的连接")
58
+ logger.info(f"接收到来自[{addr[0]}:{addr[1]}]的连接,待确认设备类型...")
59
59
 
60
60
 
61
61
  # 为每个新连接创建线程处理
62
62
  client_handler = Thread(
63
63
  target=self.client_handler,
64
- args=(client_socket,)
64
+ args=(client_socket,),
65
+ daemon=True
65
66
  )
66
- client_handler.daemon = True
67
+
67
68
  client_handler.start()
68
69
 
69
70
  except KeyboardInterrupt:
@@ -84,18 +85,16 @@ class DeviceContainer(object):
84
85
  device.start_listening()
85
86
  # GET_DEVICE_INFO
86
87
  msg = GetDeviceInfoCommand.build(device).pack()
87
- logger.info(f"发送获取设备信息命令: {msg.hex()}")
88
+ logger.trace(f"发送获取设备信息命令: {msg.hex()}")
88
89
  device.send(msg)
89
- logger.info(f"base device {dir(device)}")
90
90
  # 添加设备
91
91
  while True:
92
92
  if device.device_name:
93
- logger.info(f"设备 {device.device_name} 已连接")
94
93
  real_device = DeviceFactory.create_device(device)
95
94
  device.stop_listening()
96
95
  real_device.start_listening()
97
96
  self.add_device(real_device)
98
- logger.info(f"real_device consumers {len(real_device.signal_consumers)}")
97
+ logger.info(f"设备[{device.device_name}]已连接,设备类型为:{hex(real_device.device_type)}")
99
98
  break
100
99
 
101
100
 
@@ -107,20 +106,20 @@ class DeviceContainer(object):
107
106
  if device is None or device.device_no is None:
108
107
  logger.warning("无效的设备")
109
108
 
110
- self._devices.update({device.device_no: device})
111
- logger.debug(f"add_device {device.device_no} then has devices {self._devices}")
109
+ self._devices[device.device_no] = device
110
+ logger.info(f"添加设备[{device.device_no}]到设备列表,已连接设备数量:{len(self._devices)}")
112
111
 
113
- self._broadcaster.remove_device(device.device_no)
114
- logger.debug(f"add_device {device.device_no} then has broadcaster {self._broadcaster}")
112
+ # 标记设备为已连接
113
+ self._broadcaster.mark_device_as_connected(device.device_no)
115
114
 
116
- def get_device(self, device_id=None)->IDevice:
117
- logger.trace(f"已连接设备数量:{len(self._devices)}")
115
+ def get_device(self, device_no:str=None)->IDevice:
116
+ logger.info(f"已连接设备数量:{len(self._devices)}")
118
117
  if len(self._devices) == 0:
119
118
  return None
120
119
 
121
120
  # 未指定device_id,返回第一个设备
122
- if device_id is None:
121
+ if device_no is None:
123
122
  return list(self._devices.values())[0]
124
123
 
125
- return self._devices.get(device_id)
124
+ return self._devices.get(device_no, None)
126
125
 
@@ -5,6 +5,11 @@ from loguru import logger
5
5
 
6
6
  from qlsdk.core.message import UDPMessage
7
7
 
8
+ '''
9
+ 广播器类,用于发送和接收设备广播消息
10
+ 主要功能:发送设备搜索消息,接收设备连接消息
11
+ 注意:广播端口需要和ar4sdk做区分,使用54366时不能和x8同时使用
12
+ '''
8
13
  class UdpBroadcaster:
9
14
  # 广播端口需要和ar4sdk做区分, 使用54366时不能和x8同时使用
10
15
  def __init__(self, port=54366):
@@ -18,70 +23,54 @@ class UdpBroadcaster:
18
23
  self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
19
24
  self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
20
25
 
26
+ # 添加设备序列号到待广播列表
21
27
  def add_device(self, device_id):
22
- """添加设备序列号到待广播列表"""
28
+
23
29
  with self.lock:
24
30
  if device_id not in self.devices_to_broadcast:
25
31
  self.devices_to_broadcast.append(device_id)
26
- logger.info(f"Added device {device_id} to broadcast list.")
32
+ logger.info(f"添加设备[{device_id}]到搜索列表。")
27
33
 
34
+ # 从待广播列表移除设备序列号
28
35
  def remove_device(self, device_id):
29
- """从待广播列表中移除设备序列号"""
36
+
30
37
  with self.lock:
31
38
  if device_id in self.devices_to_broadcast:
32
39
  self.devices_to_broadcast.remove(device_id)
33
- logger.info(f"Removed device {device_id} from broadcast list.")
40
+ logger.info(f"把设备[{device_id}]从搜索列表中移除。")
34
41
 
42
+ # 把设备标记为已连接
35
43
  def mark_device_as_connected(self, device_id):
36
- """将设备标记为已连接,并从未广播列表中移除"""
37
44
  with self.lock:
45
+ # 如果设备已连接,则从搜索列表中移除
38
46
  if device_id in self.devices_to_broadcast:
39
47
  self.devices_to_broadcast.remove(device_id)
48
+
49
+ # 添加到已连接设备集合
40
50
  self.connected_devices.add(device_id)
41
- logger.info(f"Device {device_id} is now connected.")
51
+
52
+ logger.info(f"设备[{device_id}]已连接,从搜索列表中移除。")
42
53
 
54
+ # 广播设备信息,寻求配对
43
55
  def broadcast_devices(self):
44
- """轮询发送广播"""
56
+
45
57
  while self.running:
46
58
  with self.lock:
47
59
  for device_id in self.devices_to_broadcast:
48
60
  message = UDPMessage.search(device_id)
49
61
  self.sock.sendto(message, ('<broadcast>', self.broadcast_port))
50
- logger.info(f"Broadcasting device ID: {device_id}")
51
- time.sleep(1) # 每隔1秒发送一次广播
62
+ logger.debug(f"设备[{device_id}]广播消息已发送。")
63
+
64
+ # 每隔1秒发送一次广播
65
+ time.sleep(1)
52
66
 
53
67
  def start(self):
54
68
  """启动广播线程"""
55
- self.broadcast_thread = Thread(target=self.broadcast_devices)
56
- self.broadcast_thread.setDaemon(True)
69
+ self.broadcast_thread = Thread(target=self.broadcast_devices, daemon=True)
57
70
  self.broadcast_thread.start()
58
71
 
59
72
  def stop(self):
60
73
  """停止广播"""
61
74
  self.running = False
62
75
  self.broadcast_thread.join()
63
- self.sock.close()
64
-
65
- # 示例使用
66
- if __name__ == "__main__":
67
- broadcaster = UdpBroadcaster()
68
-
69
- # 添加设备序列号到待广播列表
70
- broadcaster.add_device_to_broadcast("390024130032")
71
-
72
- # 启动广播
73
- broadcaster.start()
74
-
75
- try:
76
- # 模拟运行一段时间
77
- time.sleep(10)
78
-
79
- # 标记设备为已连接
80
- broadcaster.mark_device_as_connected("390024130032")
81
-
82
- # 继续运行一段时间
83
- time.sleep(10)
84
-
85
- finally:
86
- # 停止广播
87
- broadcaster.stop()
76
+ self.sock.close()
qlsdk/rsc/parser/base.py CHANGED
@@ -1,3 +1,6 @@
1
+ from io import BytesIO
2
+ from multiprocessing import Lock
3
+ import time
1
4
  from qlsdk.rsc.interface import IDevice, IParser
2
5
 
3
6
  from loguru import logger
@@ -11,7 +14,13 @@ class TcpMessageParser(IParser):
11
14
  self.device = device
12
15
  self.running = False
13
16
 
14
- self.cache = b''
17
+ # 网络实时数据缓存,选用BytesIO
18
+ # 临时缓冲区-用于接收数据
19
+ self.cache = BytesIO()
20
+ # 缓冲区-用于处理数据
21
+ self.buffer = BytesIO()
22
+ # 读写锁-用于临时缓冲区(避免读写冲突)
23
+ self._lock = Lock()
15
24
 
16
25
  @property
17
26
  def header(self):
@@ -28,42 +37,114 @@ class TcpMessageParser(IParser):
28
37
  def set_device(self, device):
29
38
  self.device = device
30
39
 
31
- def append(self, buffer):
32
- self.cache += buffer
33
- logger.trace(f"已缓存的数据长度: {len(self.cache)}")
40
+ def append(self, value):
41
+ # self.cache.write(buffer)
42
+ with self._lock:
43
+ self.cache.write(value)
34
44
 
35
45
  def __parser__(self):
36
- logger.info("数据解析开始")
46
+ logger.trace("数据解析开始")
47
+
48
+ # 告警阈值(10M)
49
+ warn_len = 10 * 1024 * 1024
50
+
37
51
  while self.running:
38
- if len(self.cache) < 14:
39
- continue
40
- if self.cache[0] != 0x5A or self.cache[1] != 0xA5:
41
- self.cache = self.cache[1:]
42
- continue
43
- pkg_len = int.from_bytes(self.cache[8:12], 'little')
44
- logger.trace(f" cache len: {len(self.cache)}, pkg_len len: {len(self.cache)}")
45
- # 一次取整包数据
46
- if len(self.cache) < pkg_len:
52
+ buf_len = get_len(self.buffer)
53
+
54
+ # logger.info(f"当前操作区缓存长度: {buf_len}, 缓存内容: {self.buffer.getvalue().hex()}")
55
+ if buf_len < self.header_len:
56
+ # logger.trace(f"操作区缓存数据不足: {len}, 等待数据...")
57
+ if not self.__fill_from_cache():
58
+ time.sleep(0.05)
59
+ continue
60
+
61
+ if buf_len > warn_len:
62
+ logger.warning(f"操作区缓存数据过大: {buf_len} bytes, 可能存在数据丢失风险")
63
+
64
+ start_pos = self.buffer.tell()
65
+ # logger.info(f"当前缓存位置: {start_pos}")
66
+ head = self.buffer.read(2)
67
+ # logger.info(f'当前缓存头部: {head.hex()}')
68
+ if head != self.header:
69
+ logger.debug(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
70
+ self.buffer.seek(start_pos + 1) # 移动到下一个字节
47
71
  continue
48
- pkg = self.cache[:pkg_len]
49
- self.cache = self.cache[pkg_len:]
72
+
73
+ # 移动下标(指向包长度的位置)
74
+ self.buffer.seek(start_pos + 8)
75
+ # 包总长度
76
+ pkg_len = int.from_bytes(self.buffer.read(4), 'little')
77
+ # logger.trace(f" cache len: {len(self.cache)}, pkg_len len: {len(self.cache)}")
78
+
79
+ buf_len = get_len(self.buffer)
80
+ # 直接等待长度足够(如果从头开始判断,因为逻辑相同,所以会执行一样的操作)
81
+ while buf_len < pkg_len:
82
+ if self.__fill_from_cache():
83
+ continue
84
+ else:
85
+ time.sleep(0.05)
86
+
87
+ # 读取剩余数据
88
+ self.buffer.seek(pkg_len)
89
+ tmp = self.buffer.read()
90
+
91
+ # 读取当前数据包
92
+ self.buffer.seek(start_pos)
93
+ pkg = self.buffer.read(pkg_len)
94
+
95
+ # 清空操作区缓存(truncate会保留内存,重新初始化)
96
+ self.buffer = BytesIO()
97
+ if len(tmp) > 0:
98
+ self.buffer.write(tmp)
99
+ self.buffer.seek(0)
100
+
50
101
  self.unpack(pkg)
51
102
 
103
+ # 填充操作区缓存
104
+ def __fill_from_cache(self) -> bool:
105
+ result = False
106
+
107
+ cur_pos = self.buffer.tell()
108
+ # 移动到操作区缓存末尾,内容追加到缓冲区尾部
109
+ self.buffer.seek(0,2)
110
+ # 操作缓冲区
111
+ with self._lock:
112
+ self.cache.seek(0, 2)
113
+
114
+ # 临时缓冲区只要有数据,就写入操作缓冲区(避免分片传输导致数据不完整)
115
+ if self.cache.tell() > 0:
116
+ self.buffer.write(self.cache.getvalue())
117
+ self.cache = BytesIO() # 清空缓冲区
118
+ result = True
119
+
120
+ self.buffer.seek(cur_pos) # 恢复到原位置
121
+
122
+ return result
123
+
52
124
  def unpack(self, packet):
53
125
  # 提取指令码
54
126
  cmd_code = int.from_bytes(packet[self.cmd_pos : self.cmd_pos + 2], 'little')
55
127
  cmd_class = CommandFactory.create_command(cmd_code)
56
- logger.info(f"收到指令:{cmd_class.cmd_desc}[{hex(cmd_code)}]")
128
+ # logger.trace(f"收到指令:{cmd_class.cmd_desc}[{hex(cmd_code)}]")
57
129
  instance = cmd_class(self.device)
58
130
  start = time_ns()
59
- logger.info(f"开始解析: {start}")
131
+ # logger.trace(f"开始解析: {start}")
60
132
  instance.parse_body(packet[self.header_len:-2])
61
- logger.info(f"解析完成:{time_ns()}, 解析耗时:{time_ns() - start}ns")
133
+ # logger.trace(f"解析完成:{time_ns()}, 解析耗时:{time_ns() - start}ns")
62
134
  return instance
63
135
 
64
136
  def start(self):
65
137
  self.running = True
66
- parser = Thread(target=self.__parser__,)
67
- parser.daemon = True
138
+ parser = Thread(target=self.__parser__, daemon=True)
68
139
  parser.start()
69
-
140
+
141
+ # 工具方法
142
+ def get_len(buf: BytesIO) -> int:
143
+ if buf is None:
144
+ return 0
145
+ cur_pos = buf.tell()
146
+ buf.seek(0, 2) # 移动到操作区缓存末尾
147
+ len = buf.tell()
148
+ buf.seek(cur_pos) # 恢复到原位置
149
+ return len
150
+
@@ -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、日志级别及文案优化
@@ -20,7 +20,7 @@ qlsdk/core/network/monitor.py,sha256=QqjjPwSr1kgqDTTySp5bpalZmsBQTaAWSxrfPLdROZo
20
20
  qlsdk/persist/__init__.py,sha256=b8qk1aOU6snEMCQNYDl1ijV3-2gwBmMt76fiAzNk1E8,107
21
21
  qlsdk/persist/ars_edf.py,sha256=_pYtHqucB-utMw-xUXZc9IB8_8ThbLFpTl_-WBQR-Sc,10555
22
22
  qlsdk/persist/edf.py,sha256=ETngb86CfkIUJYWmw86QR445MvTFC7Edk_CH9nyNgtY,7857
23
- qlsdk/persist/rsc_edf.py,sha256=fY1eRMflCvOshN_YTxmzT3MYzEHyHtNMiczCgXWgJ7w,11878
23
+ qlsdk/persist/rsc_edf.py,sha256=wV3akdwzEihDAyR9DtmO0jNdLH1jEvx34ghJQtrfk2k,12578
24
24
  qlsdk/persist/stream.py,sha256=TCVF1sqDrHiYBsJC27At66AaCs-_blXeXA_WXdJiIVA,5828
25
25
  qlsdk/rsc/__init__.py,sha256=hOMiN0eYn4jYo7O4_0IPlQT0hD15SqqCQUihOVlTZvs,269
26
26
  qlsdk/rsc/device_manager.py,sha256=1ucd-lzHkNeQPKPzXV6OBkAMqPp_vOcsLyS-9TJ7wRc,4448
@@ -29,27 +29,28 @@ qlsdk/rsc/eegion.py,sha256=lxrktO-3Z_MYdFIwc4NxvgLM5AL5kU3UItjH6tsKmHY,11670
29
29
  qlsdk/rsc/entity.py,sha256=-fRWFkVWp9d8Y1uh6GiacXC5scdeEKNiNFf3aziGdCE,17751
30
30
  qlsdk/rsc/paradigm.py,sha256=DGfwY36sMdPIMRjbGo661GvUTEwsRRi3jrmG405mSTk,12840
31
31
  qlsdk/rsc/proxy.py,sha256=9CPdGNGWremwBUh4GvlXAykYB-x_BEPPLqsNvwuwIDE,2736
32
- qlsdk/rsc/command/__init__.py,sha256=3rONCv9eFjIS1Hk2T0uoCwSo_wt89QSi1PYfdFNLv3k,11896
32
+ qlsdk/rsc/command/__init__.py,sha256=FpumdCRV3aSnCvLT7MceMb7lF8WgGbdi_w0L8wX7ryg,12137
33
33
  qlsdk/rsc/command/message.py,sha256=nTdG-Vp4MBnltyrgedAWiKD6kzOaPrg58Z_hq6yjhys,12220
34
- qlsdk/rsc/device/__init__.py,sha256=5tNSYmpl_jfaHZcOMyB2IcO-tzL-Pj5f3V8CRC8uLHc,73
35
- qlsdk/rsc/device/arskindling.py,sha256=HEy0yCloDWiveRT5lY3qVDAfqPfr_FiztbiZeqQPT3k,14895
36
- qlsdk/rsc/device/base.py,sha256=lrdLi9OXeVgLsUn5Bif9sJ9PZo5cDUQ-moOGur5Gsp8,15685
37
- qlsdk/rsc/device/c256_rs.py,sha256=ns4osmACbStDf7oz8aBnfZhJCe1R2g3M--_Oum6eN50,13921
38
- qlsdk/rsc/device/c64_rs.py,sha256=hzOQxtZf_X-sdTR32-WsO7MNGn-tyPImuXRpwpdSMTo,13578
39
- qlsdk/rsc/device/c64s1.py,sha256=2TmJJ9Ncr0jy8HtdI66C-kSFyCSqAILco3Eijth_oIo,13920
40
- qlsdk/rsc/device/device_factory.py,sha256=B_JZOAGGaLafSdly2i6PeIV2rVoz7S3MJyFfUCICu94,1257
34
+ qlsdk/rsc/device/__init__.py,sha256=xtTXLT9QFKtb-qS-A8-ewSxJ3zXgImFCX0OoAPw6hHE,185
35
+ qlsdk/rsc/device/arskindling.py,sha256=owci6MEGjyWqohEXzPdKj_ESeVIZKgO53StVj6Tmi18,15002
36
+ qlsdk/rsc/device/base.py,sha256=6cMaACVVgZ7oKdqbyVRl3B32M9Em6_jjr-FUuxRW6Ys,17404
37
+ qlsdk/rsc/device/c16_rs.py,sha256=IpJn4hBFHg67lvWkl8kAAztdZYsgi2-njFuxMi2SsHQ,6390
38
+ qlsdk/rsc/device/c256_rs.py,sha256=K1XmLqZpvHTAfCm_dr2VsGxHc67aJQVDV1cI41a1WTI,13955
39
+ qlsdk/rsc/device/c64_rs.py,sha256=cZIioIRGgd4Ub0ieho4_XujBNo8AQgJEjXcqgcEkyFQ,13644
40
+ qlsdk/rsc/device/c64s1.py,sha256=L7nKmsoMCGj6GMjHYfYkKgkBtrGfP516kQHQ5I1FAUE,13986
41
+ qlsdk/rsc/device/device_factory.py,sha256=P8nNDB2qk0kbu4OMYtEZMKSdXWp-7fLDzuNyR1Thf8Q,1315
41
42
  qlsdk/rsc/interface/__init__.py,sha256=xeRzIlQSB7ZSf4r5kLfH5cDQLzCyWeJAReG8Xq5nOE0,70
42
43
  qlsdk/rsc/interface/command.py,sha256=1s5Lxb_ejsd-JNvKMqU2aFSnOoW-_cx01VSD3czxmQI,199
43
- qlsdk/rsc/interface/device.py,sha256=0wCKyI19fbDHVuIMoOhLcJTUSbp4D8ou9SfBVxYA8Qs,3034
44
+ qlsdk/rsc/interface/device.py,sha256=apBQAeu1g0Qmw73qQqr6uG1re9qCep6oKfjWKlGJdp4,3092
44
45
  qlsdk/rsc/interface/handler.py,sha256=ADDe_a2RAxGMuooLyivH0JBPTGBcFP2JaTVX41R1A4w,198
45
46
  qlsdk/rsc/interface/parser.py,sha256=DxuFZiprJJbG4pfFbbZPaG8MlBiBRe0S0lJrvc2Iees,251
46
47
  qlsdk/rsc/manager/__init__.py,sha256=4ljT3mR8YPBDQur46B5xPqK5tjLKlsWfgCJVuA0gs-8,40
47
- qlsdk/rsc/manager/container.py,sha256=4yQH8P_pEk7vaO_UYkEe9UPga7pFdcKgo1ZpOApAFqM,4866
48
+ qlsdk/rsc/manager/container.py,sha256=N9QB85FOA_7Oa_8M1y1M2UODA_tpS9JQONhGj8ObBG8,4811
48
49
  qlsdk/rsc/manager/search.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  qlsdk/rsc/network/__init__.py,sha256=PfYiqXS2pZV__uegQ1TjaeYhY1pefZ_shwE_X5HNVbs,23
50
- qlsdk/rsc/network/discover.py,sha256=ONXN6YWY-OMU0sBoLqqKUyb-8drtAp1g_MvnpzaFvHQ,3124
51
+ qlsdk/rsc/network/discover.py,sha256=4aojzRFInTC3d8K2TYGbnP1Ji5fOFEi31ekghj7ce5k,2977
51
52
  qlsdk/rsc/parser/__init__.py,sha256=8RgwbKCINu3eTsxVLF9cMoBXJnVrDocOEFP6NGP_atk,34
52
- qlsdk/rsc/parser/base.py,sha256=mR41XedSptTwbMdo-KZ_ejo2b2pi5pc-_TGWfW2AF34,2277
53
+ qlsdk/rsc/parser/base.py,sha256=s6tkWQXoMq8ZA4Nns_aG1JS5PV3UXLMFarTZiCoPagM,5504
53
54
  qlsdk/sdk/__init__.py,sha256=v9LKP-5qXCqnAsCkiRE9LDb5Tagvl_Qd_fqrw7y9yd4,68
54
55
  qlsdk/sdk/ar4sdk.py,sha256=tugH3UUeNebdka78AzLyrtAXbYQQE3iFJ227zUit6tY,27261
55
56
  qlsdk/sdk/hub.py,sha256=uEOGZBZtMDCWlV8G2TZe6FAo6eTPcwHAW8zdqr1eq_0,1571
@@ -57,7 +58,7 @@ qlsdk/sdk/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,
57
58
  qlsdk/sdk/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
58
59
  qlsdk/x8/__init__.py,sha256=FDpDK7GAYL-g3vzfU9U_V03QzoYoxH9YLm93PjMlANg,4870
59
60
  qlsdk/x8m/__init__.py,sha256=cLeUqEEj65qXw4Qa4REyxoLh6T24anSqPaKe9_lR340,634
60
- qlsdk2-0.4.2.dist-info/METADATA,sha256=g6FdFdj4E8B5kRk-MVTVnaUli_S0Z6ckyNKTADf6Dd4,7063
61
- qlsdk2-0.4.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
62
- qlsdk2-0.4.2.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
63
- qlsdk2-0.4.2.dist-info/RECORD,,
61
+ qlsdk2-0.5.0.dist-info/METADATA,sha256=Lp7nByzF1dCM1LGXQxyKv8sc-vkTDuLjZtw9nu_JFBk,1134
62
+ qlsdk2-0.5.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
63
+ qlsdk2-0.5.0.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
64
+ qlsdk2-0.5.0.dist-info/RECORD,,
@@ -1,121 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: qlsdk2
3
- Version: 0.4.2
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
- # qlsdk project
30
-
31
-
32
-
33
- ## Getting started
34
-
35
- To make it easy for you to get started with GitLab, here's a list of recommended next steps.
36
-
37
- Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
38
-
39
- ## Add your files
40
-
41
- - [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
42
- - [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
43
-
44
- ```
45
- cd existing_repo
46
- git remote add origin http://10.60.170.104/sw/qlsdk-project.git
47
- git branch -M main
48
- git push -uf origin main
49
- ```
50
-
51
- ## Integrate with your tools
52
-
53
- - [ ] [Set up project integrations](http://10.60.170.104/sw/qlsdk-project/-/settings/integrations)
54
-
55
- ## Collaborate with your team
56
-
57
- - [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
58
- - [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
59
- - [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
60
- - [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
61
- - [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
62
-
63
- ## Test and Deploy
64
-
65
- Use the built-in continuous integration in GitLab.
66
-
67
- - [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
68
- - [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
69
- - [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
70
- - [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
71
- - [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
72
-
73
- ***
74
-
75
- # Editing this README
76
-
77
- When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
78
-
79
- ## Suggestions for a good README
80
-
81
- Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
82
-
83
- ## Name
84
- Choose a self-explaining name for your project.
85
-
86
- ## Description
87
- Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
88
-
89
- ## Badges
90
- On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
91
-
92
- ## Visuals
93
- Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
94
-
95
- ## Installation
96
- Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
97
-
98
- ## Usage
99
- Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
100
-
101
- ## Support
102
- Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
103
-
104
- ## Roadmap
105
- If you have ideas for releases in the future, it is a good idea to list them in the README.
106
-
107
- ## Contributing
108
- State if you are open to contributions and what your requirements are for accepting them.
109
-
110
- For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
111
-
112
- You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
113
-
114
- ## Authors and acknowledgment
115
- Show your appreciation to those who have contributed to the project.
116
-
117
- ## License
118
- For open source projects, say how it is licensed.
119
-
120
- ## Project status
121
- If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
File without changes