qlsdk2 0.6.0a6__py3-none-any.whl → 0.6.0a7__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/ars_edf.py CHANGED
@@ -195,39 +195,14 @@ class ARSKindlingEDFHandler(object):
195
195
  if packet is None:
196
196
  # self._edf_writer_thread.stop_recording()
197
197
  for k in self._edf_handler.keys():
198
- self._edf_handler[k].append(None)
198
+ self._edf_handler[k].write(None)
199
199
  return
200
200
 
201
- #按分区写入数据
202
-
201
+ #按分区写入数据
203
202
  for k in self._channel_spilt.keys():
204
- logger.info(f'分区{k}, {self._channel_spilt[k]}')
203
+ logger.trace(f'分区{k}, {self._channel_spilt[k]}')
205
204
  p = packet
206
205
  self.writeA(p, self._channel_spilt[k], k)
207
-
208
- # with self._lock:
209
- # if self.channels is None:
210
- # logger.info(f"开始记录数据到文件...")
211
- # self.channels = packet.channels
212
- # self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
213
- # self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
214
- # self._start_time = datetime.now()
215
- # logger.info(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
216
-
217
- # if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
218
- # self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
219
- # logger.warning(f"数据包丢失: {self._last_pkg_id} -> {packet.pkg_id}, 丢包数: {packet.pkg_id - self._last_pkg_id - 1}")
220
-
221
- # self._last_pkg_id = packet.pkg_id
222
- # self._total_packets += 1
223
-
224
- # if self._edf_writer_thread is None:
225
- # self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name)
226
- # self._edf_writer_thread.set_start_time(self._start_time)
227
- # self._edf_writer_thread.start()
228
- # logger.info(f"开始写入数据: {self.file_name}")
229
-
230
- # self._edf_writer_thread.append(packet.eeg)
231
206
 
232
207
  def writeA(self, packet: RscPacket, channel_filter, name='A'):
233
208
  # 参数检查
qlsdk/persist/rsc_edf.py CHANGED
@@ -62,7 +62,7 @@ class EDFStreamWriter(Thread):
62
62
  self._recording = False
63
63
 
64
64
  def append(self, data):
65
- if data:
65
+ if data is not None:
66
66
  # 数据
67
67
  self.data_queue.put(data)
68
68
 
@@ -243,8 +243,9 @@ class StartStimulationCommand(DeviceCommand):
243
243
  # error_channel - 8B
244
244
  # error_channel= int.from_bytes(body[9:17], 'big')
245
245
  channels = to_channels(body[9:17])
246
- logger.success(f"通道 {channels} 刺激开始")
247
- self.device.trigger(f"通道 {channels} 刺激开始")
246
+ logger.info(f"通道 {channels} 刺激开始")
247
+ if self.device:
248
+ self.device.trigger(f"通道 {channels} 刺激开始")
248
249
  # error_type - 1B
249
250
  error_type = body[17]
250
251
 
@@ -268,8 +269,9 @@ class StopStimulationNotifyCommand(DeviceCommand):
268
269
  # error_channel - 8B
269
270
  # error_channel= int.from_bytes(body[9:17], 'big')
270
271
  channels = to_channels(body[9:17])
271
- logger.success(f"通道 {channels} 刺激结束")
272
- self.device.trigger(f"通道 {channels} 刺激结束", time)
272
+ logger.info(f"通道 {channels} 刺激结束")
273
+ if self.device:
274
+ self.device.trigger(f"通道 {channels} 刺激结束", time)
273
275
  # error_type - 1B
274
276
  error_type = body[17]
275
277
  # 刺激信息
@@ -1,14 +1,7 @@
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
1
  from loguru import logger
8
2
  from qlsdk.persist import ARSKindlingEDFHandler
9
- from qlsdk.rsc.interface import IDevice, IParser
3
+ from qlsdk.rsc.interface import IDevice
10
4
  from qlsdk.rsc.command import *
11
- from qlsdk.rsc.parser.base import TcpMessageParser
12
5
  from qlsdk.rsc.device.base import QLBaseDevice
13
6
 
14
7
  class ARSKindling(QLBaseDevice):
@@ -17,117 +10,6 @@ class ARSKindling(QLBaseDevice):
17
10
 
18
11
  def __init__(self, socket):
19
12
  super().__init__(socket)
20
- # self.socket = socket
21
-
22
- self._id = None
23
-
24
- # 设备信息
25
- # self.device_id = None
26
- # self.device_name = None
27
- self._device_no = None
28
- # self.software_version = None
29
- # self.hardware_version = None
30
- # self.connect_time = None
31
- # self.current_time = None
32
- # # mV
33
- # self.voltage = None
34
- # # %
35
- # self.battery_remain = None
36
- # # %
37
- # self.battery_total = None
38
- # persist
39
- self._recording = False
40
- self._storage_path = None
41
- self._file_prefix = None
42
-
43
- # 可设置参数
44
- # 采集:采样量程、采样率、采样通道
45
- # 刺激:刺激电流、刺激频率、刺激时间、刺激通道
46
- # 采样量程(mV):188、375、563、750、1125、2250、4500
47
- self._sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188
48
- # 采样率(Hz):250、500、1000、2000、4000、8000、16000、32000
49
- self._sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500
50
- self._physical_max = self._sample_range * 1000 # 物理最大值(uV)
51
- self._physical_min = -self._sample_range * 1000 # 物理最小值(uV)
52
- self._digital_max = 8388607
53
- self._digital_min = -8388608
54
- self._physical_range = self._physical_max - self._physical_min
55
- self._digital_range = 16777215
56
- self._acq_channels = None
57
- self._acq_param = {
58
- "sample_range": 188,
59
- "sample_rate": 500,
60
- "channels": [],
61
- }
62
-
63
- self._stim_param = {
64
- "stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
65
- "channels": [],
66
- "param": [{
67
- "channel_id": 0, #通道号 从0开始 -- 必填
68
- "waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
69
- "current": 1, #电流强度(mA) -- 必填
70
- "duration": 30, #平稳阶段持续时间(s) -- 必填
71
- "ramp_up": 5, #上升时间(s) 默认0
72
- "ramp_down": 5, #下降时间(s) 默认0
73
- "frequency": 500, #频率(Hz) -- 非直流必填
74
- "phase_position": 0, #相位 -- 默认0
75
- "duration_delay": "0", #延迟启动时间(s) -- 默认0
76
- "pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
77
- },
78
- {
79
- "channel_id": 1, #通道号 从0开始 -- 必填
80
- "waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
81
- "current": 1, #电流强度(mA) -- 必填
82
- "duration": 30, #平稳阶段持续时间(s) -- 必填
83
- "ramp_up": 5, #上升时间(s) 默认0
84
- "ramp_down": 5, #下降时间(s) 默认0
85
- "frequency": 500, #频率(Hz) -- 非直流必填
86
- "phase_position": 0, #相位 -- 默认0
87
- "duration_delay": "0", #延迟启动时间(s) -- 默认0
88
- "pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
89
- }
90
- ]
91
- }
92
-
93
- self.stim_paradigm = None
94
-
95
- signal_info = {
96
- "param" : None,
97
- "start_time" : None,
98
- "finished_time" : None,
99
- "packet_total" : None,
100
- "last_packet_time" : None,
101
- "state" : 0
102
- }
103
- stim_info = {
104
-
105
- }
106
- Impedance_info = {
107
-
108
- }
109
- # 信号采集状态
110
- # 信号数据包总数(一个信号采集周期内)
111
- # 信号采集参数
112
- # 电刺激状态
113
- # 电刺激开始时间(最近一次)
114
- # 电刺激结束时间(最近一次)
115
- # 电刺激参数
116
- # 启动数据解析线程
117
- # 数据存储状态
118
- # 存储目录
119
-
120
- #
121
- self.__signal_consumer: Dict[str, Queue[Any]]={}
122
- self.__impedance_consumer: Dict[str, Queue[Any]]={}
123
-
124
- # EDF文件处理器
125
- self._edf_handler = None
126
- self.storage_enable = True
127
-
128
- # self.start_message_listening()
129
- # self.start()
130
- # self.parser.set_device(self)
131
13
 
132
14
  self.channel_mapping = {
133
15
  "A1": 55, "A2": 56, "A3": 53, "A4": 54, "A5": 51, "A6": 52, "A7": 49, "A8": 50,
@@ -143,18 +25,6 @@ class ARSKindling(QLBaseDevice):
143
25
  "D9": 9, "D10": 10, "D11": 11, "D12": 12, "D13": 13, "D14": 14,
144
26
  }
145
27
 
146
- @property
147
- def device_no(self):
148
- return self._device_no
149
-
150
- @device_no.setter
151
- def device_no(self, value: str):
152
- self._device_no = value
153
-
154
- @property
155
- def parser(self) -> IParser:
156
- return self._parser
157
-
158
28
  @classmethod
159
29
  def from_parent(cls, parent:IDevice) -> IDevice:
160
30
  rlt = cls(parent.socket)
@@ -168,68 +38,8 @@ class ARSKindling(QLBaseDevice):
168
38
  self._edf_handler.set_device_no(self.device_no)
169
39
  self._edf_handler.set_storage_path(self._storage_path)
170
40
  self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'ARS')
41
+ logger.debug(f"EDF Handler initialized")
171
42
 
172
- @property
173
- def edf_handler(self):
174
- if not self.storage_enable:
175
- return None
176
-
177
- if self._edf_handler is None:
178
- self.init_edf_handler()
179
-
180
- return self._edf_handler
181
-
182
- @property
183
- def acq_channels(self):
184
- if self._acq_channels is None:
185
- self._acq_channels = [i for i in range(1, 63)]
186
- return self._acq_channels
187
- @property
188
- def sample_range(self):
189
- return self._sample_range if self._sample_range else 188
190
- @property
191
- def sample_rate(self):
192
- return self._sample_rate if self._sample_rate else 500
193
- @property
194
- def resolution(self):
195
- return 24
196
-
197
- @property
198
- def signal_consumers(self):
199
- return self.__signal_consumer
200
-
201
- @property
202
- def impedance_consumers(self):
203
- return self.__impedance_consumer
204
-
205
- # 设置记录文件路径
206
- def set_storage_path(self, path):
207
- self._storage_path = path
208
-
209
- # 设置记录文件名称前缀
210
- def set_file_prefix(self, prefix):
211
- self._file_prefix = prefix
212
-
213
- # 接收socket消息
214
- def accept(self):
215
- while True:
216
- # 缓冲去4M
217
- data = self.socket.recv(4096*1024)
218
- if not data:
219
- logger.warning(f"设备{self.device_no}连接结束")
220
- break
221
-
222
- self._parser.append(data)
223
-
224
-
225
- # socket发送数据
226
- def send(self, data):
227
- self.socket.sendall(data)
228
-
229
- # 设置刺激参数
230
- def set_stim_param(self, param):
231
- self.stim_paradigm = param
232
-
233
43
  # 设置采集参数
234
44
  def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
235
45
  self._acq_param["original_channels"] = channels
@@ -249,108 +59,7 @@ class ARSKindling(QLBaseDevice):
249
59
  self._acq_param["sample_range"] = sample_range
250
60
  self._acq_channels = channels
251
61
  self._sample_rate = sample_rate
252
- self._sample_range = sample_range
253
-
254
- # 通用配置-TODO
255
- def set_config(self, key:str, val: str):
256
- pass
257
-
258
- def start_impedance(self):
259
- logger.info("启动阻抗测量")
260
- msg = StartImpedanceCommand.build(self).pack()
261
- logger.debug(f"start_impedance message is {msg.hex()}")
262
- self.socket.sendall(msg)
263
-
264
- def stop_impedance(self):
265
- logger.info("停止阻抗测量")
266
- msg = StopImpedanceCommand.build(self).pack()
267
- logger.debug(f"stop_impedance message is {msg.hex()}")
268
- self.socket.sendall(msg)
269
-
270
- def start_stimulation(self):
271
- if self.stim_paradigm is None:
272
- logger.warning("刺激参数未设置,请先设置刺激参数")
273
- return
274
- logger.info("启动电刺激")
275
- msg = StartStimulationCommand.build(self).pack()
276
- logger.debug(f"start_stimulation message is {msg.hex()}")
277
- self.socket.sendall(msg)
278
- t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
279
- t.start()
280
-
281
- def _stop_stimulation_trigger(self, duration):
282
- delay = duration
283
- while delay > 0:
284
- sleep(1)
285
- delay -= 1
286
- logger.info(f"_stop_stimulation_trigger duration: {duration}")
287
- if self._edf_handler:
288
- self._edf_handler.trigger("stimulation should be stopped")
289
- else:
290
- logger.warning("stop stim trigger fail. no edf writer alive")
291
-
292
- def stop_stimulation(self):
293
- logger.info("停止电刺激")
294
- msg = StopStimulationCommand.pack()
295
- logger.debug(f"stop_stimulation message is {msg.hex()}")
296
- self.socket.sendall(msg)
297
-
298
- # 启动采集
299
- def start_acquisition(self, recording = True):
300
- logger.info("启动信号采集")
301
- self._recording = recording
302
- # 设置数据采集参数
303
- param_bytes = SetAcquisitionParamCommand.build(self).pack()
304
- # 启动数据采集
305
- start_bytes = StartAcquisitionCommand.build(self).pack()
306
- msg = param_bytes + start_bytes
307
- logger.debug(f"start_acquisition message is {msg.hex()}")
308
- self.socket.sendall(msg)
309
-
310
- # 停止采集
311
- def stop_acquisition(self):
312
- logger.info("停止信号采集")
313
- msg = StopAcquisitionCommand.build(self).pack()
314
- logger.debug(f"stop_acquisition message is {msg.hex()}")
315
- self.socket.sendall(msg)
316
- if self._edf_handler:
317
- # 发送结束标识
318
- self.edf_handler.write(None)
319
-
320
- # 订阅实时数据
321
- def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
322
- logger.info(f"订阅{self.device_no}的数据流")
323
- # 数据队列
324
- if q is None:
325
- q = Queue(maxsize=1000)
326
-
327
- # 队列名称
328
- if topic is None:
329
- topic = f"{type}_{time_ns()}"
330
-
331
- # 订阅生理电信号数据
332
- if type == "signal":
333
- # topic唯一,用来区分不同的订阅队列(下同)
334
- if topic in list(self.__signal_consumer.keys()):
335
- logger.warning(f"exists {type} subscribe of {topic}")
336
- else:
337
- self.__signal_consumer[topic] = q
338
-
339
- # 订阅阻抗数据
340
- if type == "impedance":
341
- if topic in list(self.__signal_consumer.keys()):
342
- logger.warning(f"exists {type} subscribe of {topic}")
343
- else:
344
- self.__impedance_consumer[topic] = q
345
-
346
- return topic, q
347
-
348
- def trigger(self, desc):
349
- if self._edf_handler:
350
- self.edf_handler.trigger(desc)
351
- else:
352
- logger.warning("no edf handler, no place to recording trigger")
353
-
62
+ self._sample_range = sample_range
354
63
 
355
64
  def gen_set_acquirement_param(self) -> bytes:
356
65
 
qlsdk/rsc/device/base.py CHANGED
@@ -93,21 +93,6 @@ class QLBaseDevice(IDevice):
93
93
  }
94
94
 
95
95
  self.stim_paradigm: StimulationParadigm = None
96
-
97
- signal_info = {
98
- "param" : None,
99
- "start_time" : None,
100
- "finished_time" : None,
101
- "packet_total" : None,
102
- "last_packet_time" : None,
103
- "state" : 0
104
- }
105
- stim_info = {
106
-
107
- }
108
- Impedance_info = {
109
-
110
- }
111
96
  # 信号采集状态
112
97
  # 信号数据包总数(一个信号采集周期内)
113
98
  # 信号采集参数
@@ -128,6 +113,8 @@ class QLBaseDevice(IDevice):
128
113
  self.storage_enable = True
129
114
  self._listening = False
130
115
  # self.ready()
116
+ self._signal_cache: Queue = None
117
+ self._recording = False
131
118
 
132
119
  def parser(self) -> IParser:
133
120
  return self._parser
@@ -166,9 +153,16 @@ class QLBaseDevice(IDevice):
166
153
  for pos in trigger_positions:
167
154
  self.trigger(data.trigger[pos])
168
155
  # 存储
169
- if self.storage_enable:
170
- self._write_signal(data)
171
-
156
+ if self.storage_enable:
157
+ # 确保记录线程启动
158
+ if self._recording is False:
159
+ self._start_recording()
160
+
161
+ # 写入文件的缓存队列
162
+ if self._signal_cache is None:
163
+ self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
164
+ self._signal_cache.put(data)
165
+
172
166
  if len(self.signal_consumers) > 0 :
173
167
  logger.trace(f"dg eeg: {data.eeg}")
174
168
  # 信号数字值转物理值
@@ -187,14 +181,35 @@ class QLBaseDevice(IDevice):
187
181
  def _signal_wrapper(self, body: bytes):
188
182
  return RscPacket().transfer(body)
189
183
 
190
- def _write_signal(self, data: RscPacket):
184
+ def _write_signal(self):
191
185
  # 文件写入到edf
192
186
  if self._edf_handler is None:
193
187
  logger.debug("Initializing EDF handler for data storage")
194
188
  self.init_edf_handler()
195
189
 
196
- if self._edf_handler:
190
+ while self._recording:
191
+ data = self._signal_cache.get()
197
192
  self._edf_handler.write(data)
193
+ if data is None:
194
+ break
195
+
196
+ self._recording = False
197
+ def _start_recording(self):
198
+ if self.storage_enable is False:
199
+ logger.trace("Storage is disabled, will not start recording")
200
+ return
201
+
202
+ if self._signal_cache is None:
203
+ self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
204
+
205
+ try:
206
+ self._recording = True
207
+ t = Thread(target=self._write_signal, daemon=True)
208
+ t.start()
209
+ logger.info(f"开启记录")
210
+ except Exception as e:
211
+ logger.error(f"开启记录失败: {str(e)}")
212
+ return
198
213
 
199
214
  def start_listening(self):
200
215
 
@@ -305,7 +320,7 @@ class QLBaseDevice(IDevice):
305
320
  @property
306
321
  def edf_handler(self):
307
322
  if not self.storage_enable:
308
- logger.warning("EDF storage is disabled, no edf handler available")
323
+ logger.info("已关闭文件记录,不会生成edf/bdf文件")
309
324
  return None
310
325
 
311
326
  if self._edf_handler is None:
@@ -339,8 +354,12 @@ class QLBaseDevice(IDevice):
339
354
  return self._impedance_consumer
340
355
 
341
356
  # 设置记录文件路径
342
- def set_storage_path(self, path):
343
- self._storage_path = path
357
+ def set_storage_path(self, dir: str):
358
+ import os
359
+
360
+ abs_path = os.path.abspath(dir)
361
+ os.makedirs(abs_path, exist_ok=True)
362
+ self._storage_path = abs_path
344
363
 
345
364
  # 设置记录文件名称前缀
346
365
  def set_file_prefix(self, prefix):
@@ -362,10 +381,6 @@ class QLBaseDevice(IDevice):
362
381
  self._acq_channels = channels
363
382
  self._sample_rate = sample_rate
364
383
  self._sample_range = sample_range
365
-
366
- # 通用配置-TODO
367
- def set_config(self, key:str, val: str):
368
- pass
369
384
 
370
385
  def start_impedance(self):
371
386
  logger.info(f"[设备-{self.device_no}]启动阻抗测量")
@@ -399,8 +414,8 @@ class QLBaseDevice(IDevice):
399
414
  sleep(1)
400
415
  delay -= 1
401
416
  logger.debug(f"_stop_stimulation_trigger duration: {duration}")
402
- if self._edf_handler:
403
- self._edf_handler.trigger("stimulation should be stopped")
417
+ if self.edf_handler:
418
+ self.edf_handler.trigger("stimulation should be stopped")
404
419
  else:
405
420
  logger.warning("stop stim trigger fail. no edf writer alive")
406
421
 
@@ -411,11 +426,10 @@ class QLBaseDevice(IDevice):
411
426
  self.socket.sendall(msg)
412
427
 
413
428
  # 启动采集
414
- def start_acquisition(self, recording = True):
429
+ def start_acquisition(self):
415
430
  logger.info(f"[设备-{self.device_no}]启动信号采集")
416
- self._recording = recording
417
- # 初始化EDF处理器
418
- self.init_edf_handler()
431
+ # 记录准备
432
+ self._start_recording()
419
433
  # 设置数据采集参数
420
434
  param_bytes = SetAcquisitionParamCommand.build(self).pack()
421
435
  # 启动数据采集
@@ -430,9 +444,10 @@ class QLBaseDevice(IDevice):
430
444
  msg = StopAcquisitionCommand.build(self).pack()
431
445
  logger.trace(f"stop_acquisition message is {msg.hex()}")
432
446
  self.socket.sendall(msg)
433
- if self._edf_handler:
447
+ # 结束标识
448
+ if self._signal_cache:
434
449
  # 发送结束标识
435
- self.edf_handler.write(None)
450
+ self._signal_cache.put(None)
436
451
 
437
452
  '''
438
453
  订阅数据
@@ -450,7 +465,7 @@ class QLBaseDevice(IDevice):
450
465
 
451
466
  # 数据队列
452
467
  if q is None:
453
- q = Queue(maxsize=1024 * 1024)
468
+ q = Queue(maxsize=100 * 1024 * 1024)
454
469
 
455
470
  # 订阅生理电信号数据
456
471
  if type == "signal":
@@ -470,10 +485,10 @@ class QLBaseDevice(IDevice):
470
485
  return topic, q
471
486
 
472
487
  def trigger(self, desc):
473
- if self._edf_handler:
488
+ if self.edf_handler:
474
489
  self.edf_handler.trigger(desc)
475
490
  else:
476
- logger.warning("没有开启文件记录时,无法记录trigger信息")
491
+ logger.info("已关闭文件记录,不会记录trigger信息")
477
492
 
478
493
  def gen_set_acquirement_param(self) -> bytes:
479
494
 
@@ -499,6 +514,9 @@ class QLBaseDevice(IDevice):
499
514
 
500
515
  # 关闭解析器
501
516
  self._parser.stop()
517
+
518
+ def enable_storage(self, enable: bool = True):
519
+ self.storage_enable = enable
502
520
 
503
521
  def __str__(self):
504
522
  return f'''
@@ -115,11 +115,7 @@ class C16RS(QLBaseDevice):
115
115
  self._edf_handler.set_device_no(self.device_no)
116
116
  self._edf_handler.set_storage_path(self._storage_path)
117
117
  self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C16R')
118
-
119
- # 设置刺激参数
120
- def set_stim_param(self, param):
121
- self.stim_paradigm = param
122
-
118
+
123
119
  # 设置采集参数
124
120
  def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
125
121
  # 保存原始通道参数
@@ -13,6 +13,13 @@ class C256RS(QLBaseDevice):
13
13
  def __init__(self, socket):
14
14
  super().__init__(socket)
15
15
 
16
+ @classmethod
17
+ def from_parent(cls, parent:IDevice) -> IDevice:
18
+ rlt = cls(parent.socket)
19
+ rlt.device_id = parent.device_id
20
+ rlt._device_no = parent.device_no
21
+ return rlt
22
+
16
23
  def init_edf_handler(self):
17
24
  self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
18
25
  self._edf_handler.set_device_type(self.device_type)
@@ -20,7 +27,12 @@ class C256RS(QLBaseDevice):
20
27
  self._edf_handler.set_storage_path(self._storage_path)
21
28
  self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C256RS')
22
29
  logger.debug(f"EDF Handler initialized")
23
-
30
+
31
+ @property
32
+ def acq_channels(self):
33
+ if self._acq_channels is None:
34
+ self._acq_channels = [i for i in range(1, 256)]
35
+ return self._acq_channels
24
36
 
25
37
  def _signal_wrapper(self, body: bytes):
26
38
  return C256RSPacket().transfer(body)
qlsdk/rsc/device/c64s1.py CHANGED
@@ -1,145 +1,15 @@
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
1
  from loguru import logger
8
2
  from qlsdk.persist import RscEDFHandler
9
- from qlsdk.rsc.interface import IDevice, IParser
3
+ from qlsdk.rsc.interface import IDevice
10
4
  from qlsdk.rsc.command import *
11
- from qlsdk.rsc.parser.base import TcpMessageParser
12
5
  from qlsdk.rsc.device.base import QLBaseDevice
13
6
 
14
7
  class C64S1(QLBaseDevice):
15
8
 
16
- device_type = 0x40 # C64RS设备类型标识符
9
+ device_type = 0x40 # C64RS-S1设备类型标识符
17
10
 
18
11
  def __init__(self, socket):
19
12
  super().__init__(socket)
20
- self.socket = socket
21
-
22
- self._id = None
23
-
24
- # 设备信息
25
- self.device_id = None
26
- self.device_name = None
27
- self._device_no = None
28
- self.software_version = None
29
- self.hardware_version = None
30
- self.connect_time = None
31
- self.current_time = None
32
- # mV
33
- self.voltage = None
34
- # %
35
- self.battery_remain = None
36
- # %
37
- self.battery_total = None
38
- # persist
39
- self._recording = False
40
- self._storage_path = None
41
- self._file_prefix = None
42
-
43
- # 可设置参数
44
- # 采集:采样量程、采样率、采样通道
45
- # 刺激:刺激电流、刺激频率、刺激时间、刺激通道
46
- # 采样量程(mV):188、375、563、750、1125、2250、4500
47
- self._sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188
48
- # 采样率(Hz):250、500、1000、2000、4000、8000、16000、32000
49
- self._sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500
50
- self._physical_max = self._sample_range * 1000 # 物理最大值(uV)
51
- self._physical_min = -self._sample_range * 1000 # 物理最小值(uV)
52
- self._digital_max = 8388607
53
- self._digital_min = -8388608
54
- self._physical_range = self._physical_max - self._physical_min
55
- self._digital_range = 16777215
56
- self._acq_channels = None
57
- self._acq_param = {
58
- "sample_range": 188,
59
- "sample_rate": 500,
60
- "channels": [],
61
- }
62
-
63
- self._stim_param = {
64
- "stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
65
- "channels": [],
66
- "param": [{
67
- "channel_id": 0, #通道号 从0开始 -- 必填
68
- "waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
69
- "current": 1, #电流强度(mA) -- 必填
70
- "duration": 30, #平稳阶段持续时间(s) -- 必填
71
- "ramp_up": 5, #上升时间(s) 默认0
72
- "ramp_down": 5, #下降时间(s) 默认0
73
- "frequency": 500, #频率(Hz) -- 非直流必填
74
- "phase_position": 0, #相位 -- 默认0
75
- "duration_delay": "0", #延迟启动时间(s) -- 默认0
76
- "pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
77
- },
78
- {
79
- "channel_id": 1, #通道号 从0开始 -- 必填
80
- "waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
81
- "current": 1, #电流强度(mA) -- 必填
82
- "duration": 30, #平稳阶段持续时间(s) -- 必填
83
- "ramp_up": 5, #上升时间(s) 默认0
84
- "ramp_down": 5, #下降时间(s) 默认0
85
- "frequency": 500, #频率(Hz) -- 非直流必填
86
- "phase_position": 0, #相位 -- 默认0
87
- "duration_delay": "0", #延迟启动时间(s) -- 默认0
88
- "pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
89
- }
90
- ]
91
- }
92
-
93
- self.stim_paradigm = None
94
-
95
- signal_info = {
96
- "param" : None,
97
- "start_time" : None,
98
- "finished_time" : None,
99
- "packet_total" : None,
100
- "last_packet_time" : None,
101
- "state" : 0
102
- }
103
- stim_info = {
104
-
105
- }
106
- Impedance_info = {
107
-
108
- }
109
- # 信号采集状态
110
- # 信号数据包总数(一个信号采集周期内)
111
- # 信号采集参数
112
- # 电刺激状态
113
- # 电刺激开始时间(最近一次)
114
- # 电刺激结束时间(最近一次)
115
- # 电刺激参数
116
- # 启动数据解析线程
117
- # 数据存储状态
118
- # 存储目录
119
-
120
- #
121
- self.__signal_consumer: Dict[str, Queue[Any]]={}
122
- self.__impedance_consumer: Dict[str, Queue[Any]]={}
123
-
124
- # EDF文件处理器
125
- self._edf_handler = None
126
- self.storage_enable = True
127
-
128
- self._parser: IParser = TcpMessageParser(self)
129
- self._parser.start()
130
-
131
- # 启动数据接收线程
132
- self._accept = Thread(target=self.accept)
133
- self._accept.daemon = True
134
- self._accept.start()
135
-
136
- @property
137
- def device_no(self):
138
- return self._device_no
139
-
140
- @device_no.setter
141
- def device_no(self, value: str):
142
- self._device_no = value
143
13
 
144
14
  @classmethod
145
15
  def from_parent(cls, parent:IDevice) -> IDevice:
@@ -155,211 +25,5 @@ class C64S1(QLBaseDevice):
155
25
  self._edf_handler.set_device_no(self.device_no)
156
26
  self._edf_handler.set_storage_path(self._storage_path)
157
27
  self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C64S1')
158
-
159
- @property
160
- def edf_handler(self):
161
- if not self.storage_enable:
162
- return None
163
-
164
- if self._edf_handler is None:
165
- self.init_edf_handler()
166
-
167
- return self._edf_handler
168
-
169
- @property
170
- def acq_channels(self):
171
- if self._acq_channels is None:
172
- self._acq_channels = [i for i in range(1, 63)]
173
- return self._acq_channels
174
- @property
175
- def sample_range(self):
176
- return self._sample_range if self._sample_range else 188
177
- @property
178
- def sample_rate(self):
179
- return self._sample_rate if self._sample_rate else 500
180
- @property
181
- def resolution(self):
182
- return 24
183
-
184
- @property
185
- def signal_consumers(self):
186
- return self.__signal_consumer
187
-
188
- @property
189
- def impedance_consumers(self):
190
- return self.__impedance_consumer
191
-
192
- # 设置记录文件路径
193
- def set_storage_path(self, path):
194
- self._storage_path = path
195
-
196
- # 设置记录文件名称前缀
197
- def set_file_prefix(self, prefix):
198
- self._file_prefix = prefix
199
-
200
- # 接收socket消息
201
- def accept(self):
202
- while True:
203
- # 缓冲去4M
204
- data = self.socket.recv(4096*1024)
205
- if not data:
206
- logger.warning(f"设备{self.device_name}连接结束")
207
- break
208
-
209
- self._parser.append(data)
210
-
211
-
212
- # socket发送数据
213
- def send(self, data):
214
- self.socket.sendall(data)
215
-
216
- # 设置刺激参数
217
- def set_stim_param(self, param):
218
- self.stim_paradigm = param
219
-
220
- # 设置采集参数
221
- def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
222
- self._acq_param["channels"] = channels
223
- self._acq_param["sample_rate"] = sample_rate
224
- self._acq_param["sample_range"] = sample_range
225
- self._acq_channels = channels
226
- self._sample_rate = sample_rate
227
- self._sample_range = sample_range
228
-
229
- # 通用配置-TODO
230
- def set_config(self, key:str, val: str):
231
- pass
232
-
233
- def start_impedance(self):
234
- logger.info("启动阻抗测量")
235
- msg = StartImpedanceCommand.build(self).pack()
236
- logger.debug(f"start_impedance message is {msg.hex()}")
237
- self.socket.sendall(msg)
238
-
239
- def stop_impedance(self):
240
- logger.info("停止阻抗测量")
241
- msg = StopImpedanceCommand.build(self).pack()
242
- logger.debug(f"stop_impedance message is {msg.hex()}")
243
- self.socket.sendall(msg)
244
-
245
- def start_stimulation(self):
246
- if self.stim_paradigm is None:
247
- logger.warning("刺激参数未设置,请先设置刺激参数")
248
- return
249
- logger.info("启动电刺激")
250
- msg = StartStimulationCommand.build(self).pack()
251
- logger.debug(f"start_stimulation message is {msg.hex()}")
252
- self.socket.sendall(msg)
253
- t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
254
- t.start()
255
-
256
- def _stop_stimulation_trigger(self, duration):
257
- delay = duration
258
- while delay > 0:
259
- sleep(1)
260
- delay -= 1
261
- logger.info(f"_stop_stimulation_trigger duration: {duration}")
262
- if self._edf_handler:
263
- self._edf_handler.trigger("stimulation should be stopped")
264
- else:
265
- logger.warning("stop stim trigger fail. no edf writer alive")
266
-
267
- def stop_stimulation(self):
268
- logger.info("停止电刺激")
269
- msg = StopStimulationCommand.pack()
270
- logger.debug(f"stop_stimulation message is {msg.hex()}")
271
- self.socket.sendall(msg)
272
-
273
- # 启动采集
274
- def start_acquisition(self, recording = True):
275
- logger.info("启动信号采集")
276
- self._recording = recording
277
- # 设置数据采集参数
278
- param_bytes = SetAcquisitionParamCommand.build(self).pack()
279
- # 启动数据采集
280
- start_bytes = StartAcquisitionCommand.build(self).pack()
281
- msg = param_bytes + start_bytes
282
- logger.debug(f"start_acquisition message is {msg.hex()}")
283
- self.socket.sendall(msg)
284
-
285
- # 停止采集
286
- def stop_acquisition(self):
287
- logger.info("停止信号采集")
288
- msg = StopAcquisitionCommand.build(self).pack()
289
- logger.debug(f"stop_acquisition message is {msg.hex()}")
290
- self.socket.sendall(msg)
291
- if self._edf_handler:
292
- # 发送结束标识
293
- self.edf_handler.write(None)
294
-
295
- # 订阅实时数据
296
- def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
297
-
298
- # 数据队列
299
- if q is None:
300
- q = Queue(maxsize=1000)
301
-
302
- # 队列名称
303
- if topic is None:
304
- topic = f"{type}_{time_ns()}"
305
-
306
- # 订阅生理电信号数据
307
- if type == "signal":
308
- # topic唯一,用来区分不同的订阅队列(下同)
309
- if topic in list(self.__signal_consumer.keys()):
310
- logger.warning(f"exists {type} subscribe of {topic}")
311
- else:
312
- self.__signal_consumer[topic] = q
313
-
314
- # 订阅阻抗数据
315
- if type == "impedance":
316
- if topic in list(self.__signal_consumer.keys()):
317
- logger.warning(f"exists {type} subscribe of {topic}")
318
- else:
319
- self.__impedance_consumer[topic] = q
320
-
321
- return topic, q
322
-
323
- def trigger(self, desc):
324
- if self._edf_handler:
325
- self.edf_handler.trigger(desc)
326
- else:
327
- logger.warning("no edf handler, no place to recording trigger")
328
-
329
- def __str__(self):
330
- return f'''
331
- Device:
332
- Name: {self.device_name},
333
- Type: {hex(self.device_type) if self.device_type else None},
334
- ID: {self.device_id if self.device_id else None},
335
- Software: {self.software_version},
336
- Hardware: {self.hardware_version},
337
- Connect Time: {self.connect_time},
338
- Current Time: {self.current_time},
339
- Voltage: {str(self.voltage) + "mV" if self.voltage else None},
340
- Battery Remain: {str(self.battery_remain)+ "%" if self.battery_remain else None},
341
- Battery Total: {str(self.battery_total) + "%" if self.battery_total else None}
342
- '''
343
-
344
- def __repr__(self):
345
- return f'''
346
- Device:
347
- Name: {self.device_name},
348
- Type: {hex(self.device_type)},
349
- ID: {self.device_id},
350
- Software: {self.software_version},
351
- Hardware: {self.hardware_version},
352
- Connect Time: {self.connect_time},
353
- Current Time: {self.current_time},
354
- Voltage: {self.voltage}mV,
355
- Battery Remain: {self.battery_remain}%,
356
- Battery Total: {self.battery_total}%
357
- '''
358
-
359
- def __eq__(self, other):
360
- return self.device_name == other.device_name and self.device_type == other.device_type and self.device_id == other.device_id
361
-
362
- def __hash__(self):
363
- return hash((self.device_name, self.device_type, self.device_id))
364
-
365
-
28
+ logger.debug(f"EDF Handler initialized")
29
+
@@ -61,4 +61,12 @@ class IDevice(ABC):
61
61
  raise NotImplementedError("Not Supported")
62
62
  def disconnect(self) -> None:
63
63
  raise NotImplementedError("Not Supported")
64
-
64
+
65
+ def set_stim_param(self, param):
66
+ pass
67
+
68
+ def trigger(self, desc):
69
+ pass
70
+
71
+ def enable_storage(self, enable: bool = True):
72
+ pass
qlsdk/rsc/parser/base.py CHANGED
@@ -64,7 +64,7 @@ class TcpMessageParser(IParser):
64
64
  start_pos = self.buffer.tell()
65
65
  head = self.buffer.read(2)
66
66
  if head != self.header:
67
- logger.debug(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
67
+ logger.trace(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
68
68
  self.buffer.seek(start_pos + 1) # 移动到下一个字节
69
69
  continue
70
70
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qlsdk2
3
- Version: 0.6.0a6
3
+ Version: 0.6.0a7
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -28,9 +28,9 @@ qlsdk/interface/parser.py,sha256=00Ds01Xp9eUPwx7okL4B1Bl0_fRbgaxfm_2GOeWoZiM,248
28
28
  qlsdk/interface/stimulator.py,sha256=MJbgiI-4qTMjbdhcBAZe8zb10hV9x8yhFCPL3Iq_QyI,28
29
29
  qlsdk/interface/store.py,sha256=694WQnnrtXThP44JkQlBXzghvqA0PwHKK3uA640aiUo,23
30
30
  qlsdk/persist/__init__.py,sha256=b8qk1aOU6snEMCQNYDl1ijV3-2gwBmMt76fiAzNk1E8,107
31
- qlsdk/persist/ars_edf.py,sha256=_pYtHqucB-utMw-xUXZc9IB8_8ThbLFpTl_-WBQR-Sc,10555
31
+ qlsdk/persist/ars_edf.py,sha256=9OSZyEwyTMPMo6Tqnk5wPATCs5Y_Cr80EA3BzhJEJ_0,8928
32
32
  qlsdk/persist/edf.py,sha256=ETngb86CfkIUJYWmw86QR445MvTFC7Edk_CH9nyNgtY,7857
33
- qlsdk/persist/rsc_edf.py,sha256=SKvVXmQIJB5AA_3qvp0uh3PI-VaUk9cP8NXwsrpY4no,13977
33
+ qlsdk/persist/rsc_edf.py,sha256=djR_qXScb_XOOXaoMsvnK_qO2NCPzqEeKxKJCm4xnpk,13989
34
34
  qlsdk/persist/stream.py,sha256=TCVF1sqDrHiYBsJC27At66AaCs-_blXeXA_WXdJiIVA,5828
35
35
  qlsdk/rsc/__init__.py,sha256=hOMiN0eYn4jYo7O4_0IPlQT0hD15SqqCQUihOVlTZvs,269
36
36
  qlsdk/rsc/device_manager.py,sha256=1ucd-lzHkNeQPKPzXV6OBkAMqPp_vOcsLyS-9TJ7wRc,4448
@@ -39,19 +39,19 @@ qlsdk/rsc/eegion.py,sha256=lxrktO-3Z_MYdFIwc4NxvgLM5AL5kU3UItjH6tsKmHY,11670
39
39
  qlsdk/rsc/entity.py,sha256=-fRWFkVWp9d8Y1uh6GiacXC5scdeEKNiNFf3aziGdCE,17751
40
40
  qlsdk/rsc/paradigm.py,sha256=dYkm0BCpx7tkL-1OE3Io48JgKT0j-lP-n3uC1AcFiWs,17561
41
41
  qlsdk/rsc/proxy.py,sha256=9CPdGNGWremwBUh4GvlXAykYB-x_BEPPLqsNvwuwIDE,2736
42
- qlsdk/rsc/command/__init__.py,sha256=bnnPxn5mChrKCpJbnX718YuFEBDZ8KESYfMIf_8K7J0,12225
42
+ qlsdk/rsc/command/__init__.py,sha256=iPrPor7NwBIrkQDxz061gVbnlLYK9KE7VXy-NKJy1XQ,12277
43
43
  qlsdk/rsc/command/message.py,sha256=nTdG-Vp4MBnltyrgedAWiKD6kzOaPrg58Z_hq6yjhys,12220
44
44
  qlsdk/rsc/device/__init__.py,sha256=BzY9lRfssGPUlJ1ys-v3CWNgGihg7mPa2T4X0tl0Vg4,214
45
- qlsdk/rsc/device/arskindling.py,sha256=owci6MEGjyWqohEXzPdKj_ESeVIZKgO53StVj6Tmi18,15002
46
- qlsdk/rsc/device/base.py,sha256=cE4nGsOmw44gU4e1PiJmbv2KuQ0dFIoIIkXXjA4_44A,20239
47
- qlsdk/rsc/device/c16_rs.py,sha256=BHQRHOnsTMAKgqSXaAS2RjPIklZQAl2CVfe6i_iX-i4,5928
48
- qlsdk/rsc/device/c256_rs.py,sha256=EOzNqf4zZwmnzzLyb0KlOXmHhFjPJ3TUVGUDkGgI5Wc,1281
45
+ qlsdk/rsc/device/arskindling.py,sha256=tVih2D3Hhhal3WP0yqjKpzkwFPi6KLenshFStoP-vsw,4123
46
+ qlsdk/rsc/device/base.py,sha256=fCGzjAbagTTGRBfHhbiYOqN3TLOViEd8il8Qp6RiVJs,21106
47
+ qlsdk/rsc/device/c16_rs.py,sha256=qXt8m5vwcKQsN8JBllWnAsda5_Y6qkEhfHQQX101TMQ,5826
48
+ qlsdk/rsc/device/c256_rs.py,sha256=7vAEzf_ggNcwrXKmcZMylnKzLFD5ZqtAIfkkI3lQ1iI,1682
49
49
  qlsdk/rsc/device/c64_rs.py,sha256=x8wHdwATKDU34j9vXNEXsNSJg23RAWmAKL8pgIGamG8,1091
50
- qlsdk/rsc/device/c64s1.py,sha256=L7nKmsoMCGj6GMjHYfYkKgkBtrGfP516kQHQ5I1FAUE,13986
50
+ qlsdk/rsc/device/c64s1.py,sha256=WwiKSjxYpUJVkHDMDzPgp7-klbaiZ2f8EOe3wV6d2WU,1098
51
51
  qlsdk/rsc/device/device_factory.py,sha256=6cPhm3pPGrVXA1s1HePFLjZqmhNI1vOAucFI0VRD_Y0,1317
52
52
  qlsdk/rsc/interface/__init__.py,sha256=xeRzIlQSB7ZSf4r5kLfH5cDQLzCyWeJAReG8Xq5nOE0,70
53
53
  qlsdk/rsc/interface/command.py,sha256=1s5Lxb_ejsd-JNvKMqU2aFSnOoW-_cx01VSD3czxmQI,199
54
- qlsdk/rsc/interface/device.py,sha256=__ap7aJFaaktzMaZ253MMeZy9vgvJmX0Hha1dISGkOk,2064
54
+ qlsdk/rsc/interface/device.py,sha256=LmTnoumG-nLG_H_Fq21XSdTUe_XUvWRWw-8ZNX5_cBU,2238
55
55
  qlsdk/rsc/interface/handler.py,sha256=ADDe_a2RAxGMuooLyivH0JBPTGBcFP2JaTVX41R1A4w,198
56
56
  qlsdk/rsc/interface/parser.py,sha256=Z4PND5LXcJ_8CQ-OIq3KlOEVOceU1hKUuZkoFSIGGLM,334
57
57
  qlsdk/rsc/manager/__init__.py,sha256=4ljT3mR8YPBDQur46B5xPqK5tjLKlsWfgCJVuA0gs-8,40
@@ -61,7 +61,7 @@ qlsdk/rsc/network/__init__.py,sha256=PfYiqXS2pZV__uegQ1TjaeYhY1pefZ_shwE_X5HNVbs
61
61
  qlsdk/rsc/network/discover.py,sha256=GRXP0WxxIorDZWXq1X5CPAV60raSRvNiVwMQE8647XA,3044
62
62
  qlsdk/rsc/parser/__init__.py,sha256=cVKk06bRYOnwE3XMoksGlatKJSaZE1GVrYypQm-9aro,69
63
63
  qlsdk/rsc/parser/base-new.py,sha256=cAOy1V_1fAJyGq7bm7uLxpW41DbkllWOprnfWKpjtsQ,5116
64
- qlsdk/rsc/parser/base.py,sha256=VoVYoy2B583EohRRLPX3GcRiuQxT7RG7-y24WgICP2k,5702
64
+ qlsdk/rsc/parser/base.py,sha256=tH4r_1PVkYfF2alpxKdXiFHmhTDPW_NKbq6DUqU6LVg,5702
65
65
  qlsdk/rsc/parser/rsc.py,sha256=RuBqsg5KZNio7mTyM14svQEeU0_0CLhnRmfbP0NBM2o,4724
66
66
  qlsdk/sdk/__init__.py,sha256=v9LKP-5qXCqnAsCkiRE9LDb5Tagvl_Qd_fqrw7y9yd4,68
67
67
  qlsdk/sdk/ar4sdk.py,sha256=tugH3UUeNebdka78AzLyrtAXbYQQE3iFJ227zUit6tY,27261
@@ -70,7 +70,7 @@ qlsdk/sdk/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,
70
70
  qlsdk/sdk/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
71
71
  qlsdk/x8/__init__.py,sha256=FDpDK7GAYL-g3vzfU9U_V03QzoYoxH9YLm93PjMlANg,4870
72
72
  qlsdk/x8m/__init__.py,sha256=cLeUqEEj65qXw4Qa4REyxoLh6T24anSqPaKe9_lR340,634
73
- qlsdk2-0.6.0a6.dist-info/METADATA,sha256=xrhOFC2qVtEuKyGCkA_rOjFRGbFGrI0XB-hU1vLrHKo,1882
74
- qlsdk2-0.6.0a6.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
75
- qlsdk2-0.6.0a6.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
76
- qlsdk2-0.6.0a6.dist-info/RECORD,,
73
+ qlsdk2-0.6.0a7.dist-info/METADATA,sha256=nNB6PqpoJ4omwY3m3_1ca5yWT4vHRtMIRlloDuD7NGk,1882
74
+ qlsdk2-0.6.0a7.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
75
+ qlsdk2-0.6.0a7.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
76
+ qlsdk2-0.6.0a7.dist-info/RECORD,,