qlsdk2 0.6.0__py3-none-any.whl → 0.6.0a2__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/rsc/device/c64s1.py CHANGED
@@ -1,15 +1,145 @@
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
+
1
7
  from loguru import logger
2
8
  from qlsdk.persist import RscEDFHandler
3
- from qlsdk.rsc.interface import IDevice
9
+ from qlsdk.rsc.interface import IDevice, IParser
4
10
  from qlsdk.rsc.command import *
11
+ from qlsdk.rsc.parser.base import TcpMessageParser
5
12
  from qlsdk.rsc.device.base import QLBaseDevice
6
13
 
7
14
  class C64S1(QLBaseDevice):
8
15
 
9
- device_type = 0x40 # C64RS-S1设备类型标识符
16
+ device_type = 0x40 # C64RS设备类型标识符
10
17
 
11
18
  def __init__(self, socket):
12
19
  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
13
143
 
14
144
  @classmethod
15
145
  def from_parent(cls, parent:IDevice) -> IDevice:
@@ -25,5 +155,211 @@ class C64S1(QLBaseDevice):
25
155
  self._edf_handler.set_device_no(self.device_no)
26
156
  self._edf_handler.set_storage_path(self._storage_path)
27
157
  self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C64S1')
28
- logger.debug(f"EDF Handler initialized")
29
-
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
+
@@ -24,10 +24,6 @@ class IDevice(ABC):
24
24
  @property
25
25
  def device_no(self) -> str:
26
26
  pass
27
-
28
- def set_impedance_channels(self):
29
- raise NotImplementedError("Not Supported")
30
-
31
27
 
32
28
  def read_msg(self, size: int) -> bytes:
33
29
  raise NotImplementedError("Not Supported")
@@ -65,21 +61,4 @@ class IDevice(ABC):
65
61
  raise NotImplementedError("Not Supported")
66
62
  def disconnect(self) -> None:
67
63
  raise NotImplementedError("Not Supported")
68
-
69
- def set_stim_param(self, param):
70
- pass
71
-
72
- def trigger(self, desc):
73
- pass
74
-
75
- def enable_storage(self, enable: bool = True):
76
- pass
77
-
78
- def set_impedance_channel(self):
79
- raise NotImplementedError("Not Supported")
80
-
81
- def start_impedance(self):
82
- raise NotImplementedError("Not Supported")
83
-
84
- def stop_impedance(self):
85
- raise NotImplementedError("Not Supported")
64
+
qlsdk/rsc/paradigm.py CHANGED
@@ -61,22 +61,6 @@ class StimulationChannel(ABC):
61
61
 
62
62
  return result
63
63
 
64
- def to_bytes_c256(self):
65
- result = self.channel_id.to_bytes(1, 'little')
66
- result += self.wave_form.to_bytes(1, 'little')
67
-
68
- result += self._to_bytes_c256()
69
-
70
- result += int(self.delay_time).to_bytes(4, 'little')
71
- result += int(self.ramp_up * 1000).to_bytes(4, 'little')
72
- result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
73
- result += int(self.ramp_down * 1000).to_bytes(4, 'little')
74
-
75
- return result
76
-
77
- def _to_bytes_c256(self):
78
- return bytes.fromhex("00000000000000000000000000000000000000000000000000000000")
79
-
80
64
  def _ext_bytes(self):
81
65
  return bytes.fromhex("00000000000000000000000000000000")
82
66
 
@@ -115,9 +99,6 @@ class DCStimulation(StimulationChannel):
115
99
  "delay_time": self.delay_time
116
100
  }
117
101
 
118
- def _to_bytes_c256(self):
119
- return int(self.current_max * 1000 ).to_bytes(2, 'little') + bytes.fromhex("0000000000000000000000000000000000000000000000000000")
120
-
121
102
  def from_json(self, param):
122
103
  pass
123
104
  def __str__(self):
@@ -159,19 +140,9 @@ class SquareWaveStimulation(StimulationChannel):
159
140
  result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
160
141
  result += int(self.ramp_down * 1000).to_bytes(4, 'little')
161
142
 
162
- return result
163
-
164
- def _to_bytes_c256(self):
165
- result = int(self.current_max * 1000000 ).to_bytes(4, 'little')
166
- result += int(self.frequency).to_bytes(2, 'little')
167
- result += bytes.fromhex("0000")
168
- result += struct.pack('<f', self.duty_cycle)
169
- result = int(self.current_min * 1000000 ).to_bytes(4, 'little')
170
- result += self._ext_bytes()
171
- return result
172
-
173
-
174
- # 刺激模式-正弦
143
+ return result
144
+
145
+ # 刺激模式-交流
175
146
  class ACStimulation(StimulationChannel):
176
147
  '''
177
148
  channel_id: int, 通道编号,从0开始
@@ -191,8 +162,7 @@ class ACStimulation(StimulationChannel):
191
162
  self.frequency = frequency
192
163
  # self.frequency = frequency
193
164
  self.phase_position = phase_position
194
- if current < 0:
195
- self.phase_position = (phase_position + 180) % 360
165
+
196
166
  def to_json(self):
197
167
  return {
198
168
  "channel_id": self.channel_id,
@@ -207,15 +177,6 @@ class ACStimulation(StimulationChannel):
207
177
 
208
178
  def from_json(self, param):
209
179
  pass
210
-
211
- def _to_bytes_c256(self):
212
- result = int(self.current_max * 1000 ).to_bytes(2, 'little')
213
- result += bytes.fromhex("0000")
214
- result += struct.pack('<f', self.frequency)
215
- result += struct.pack('<f', self.phase_position)
216
- result += self._ext_bytes()
217
- return result
218
-
219
180
  def __str__(self):
220
181
  return f"ACStimulation(channel_id={self.channel_id}, waveform={self.waveform}, current={self.current_max}, duration={self.duration}, ramp_up={self.ramp_up}, ramp_down={self.ramp_down}, frequency={self.frequency}, phase_position={self.phase_position}, duration_delay={self.duration_delay})"
221
182
 
@@ -269,15 +230,6 @@ class PulseStimulation(StimulationChannel):
269
230
 
270
231
  return result
271
232
 
272
- def _to_bytes_c256(self):
273
- result = int(self.current_max * 1000000 ).to_bytes(4, 'little')
274
- result += int(self.frequency).to_bytes(2, 'little')
275
- result += bytes.fromhex("0000")
276
- result += struct.pack('<f', self.duty_cycle)
277
- result = int(self.current_min * 1000000 ).to_bytes(4, 'little')
278
- result += self._ext_bytes()
279
- return result
280
-
281
233
  def to_json(self):
282
234
  return {
283
235
  "channel_id": self.channel_id,
@@ -299,80 +251,6 @@ class PulseStimulation(StimulationChannel):
299
251
 
300
252
  # 刺激范式
301
253
  class StimulationParadigm(object):
302
- def __init__(self):
303
- self.channels = None
304
- self.duration = None
305
- self.interval_time = 25000
306
- self.characteristic = 1
307
- self.mode = 0
308
- self.repeats = 0
309
-
310
- def add_channel(self, channel: StimulationChannel, update=False):
311
- if self.channels is None:
312
- self.channels = {}
313
- channel_id = channel.channel_id + 1
314
- if channel_id in self.channels.keys():
315
- logger.warning(f"Channel {channel_id} already exists")
316
- if update:
317
- self.channels[channel_id] = channel
318
- else:
319
- self.channels[channel_id] = channel
320
-
321
- # 计算刺激时间
322
- duration = channel.duration + channel.ramp_up + channel.ramp_down
323
- if self.duration is None or duration > self.duration:
324
- self.duration = duration
325
-
326
-
327
- def to_bytes(self):
328
- result = to_bytes(list(self.channels.keys()), 64)
329
- result += int(self.duration * 1000).to_bytes(4, 'little')
330
- result += int(self.interval_time).to_bytes(4, 'little')
331
- result += int(self.characteristic).to_bytes(4, 'little')
332
- result += int(self.mode).to_bytes(1, 'little')
333
- result += int(self.repeats).to_bytes(4, 'little')
334
- for channel in self.channels.values():
335
- result += channel.to_bytes()
336
- return result
337
-
338
- def to_bytes_c256(self):
339
- result = to_bytes(list(self.channels.keys()), 256)
340
- result += int(self.duration * 1000).to_bytes(4, 'little')
341
- result += int(self.interval_time).to_bytes(4, 'little')
342
- result += int(self.characteristic).to_bytes(4, 'little')
343
- result += int(self.mode).to_bytes(1, 'little')
344
- result += int(self.repeats).to_bytes(4, 'little')
345
- for channel in self.channels.values():
346
- result += channel.to_bytes()
347
- return result
348
-
349
- def to_json(self):
350
- # Convert the object to JSON for transmission
351
- return {
352
- "channels": list(self.channels.keys()),
353
- "duration": self.duration,
354
- "interval_time": self.interval_time,
355
- "characteristic": self.characteristic,
356
- "mode": self.mode,
357
- "repeats": self.repeats,
358
- "stim": [channel.to_json() for channel in self.channels.values()]
359
- }
360
-
361
- # @staticmethod
362
- # def from_json(param: Dict[str, Any]):
363
- # pass
364
-
365
- def clear(self):
366
- self.channels = None
367
- self.duration = None
368
- self.interval_time = 25000
369
- self.characteristic = 1
370
- self.mode = 0
371
- self.repeats = 0
372
-
373
-
374
- # 刺激范式
375
- class C256StimulationParadigm(object):
376
254
  def __init__(self):
377
255
  self.channels = None
378
256
  self.duration = None
@@ -431,4 +309,5 @@ class C256StimulationParadigm(object):
431
309
  self.interval_time = 0
432
310
  self.characteristic = 1
433
311
  self.mode = 0
434
- self.repeats = 0
312
+ self.repeats = 0
313
+
qlsdk/rsc/parser/base.py CHANGED
@@ -53,7 +53,7 @@ class TcpMessageParser(IParser):
53
53
  buf_len = get_len(self.buffer)
54
54
 
55
55
  if buf_len < self.header_len:
56
- # logger.trace(f"等待数据中...: expect: {self.header_len}, actual: {buf_len}")
56
+ logger.trace(f"等待数据中...: expect: {self.header_len}, actual: {buf_len}")
57
57
  if not self.__fill_from_cache():
58
58
  time.sleep(0.1)
59
59
  continue
@@ -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.trace(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
67
+ logger.debug(f"数据包头部不匹配: {head.hex()}, 期望: {self.header.hex()},继续查找...")
68
68
  self.buffer.seek(start_pos + 1) # 移动到下一个字节
69
69
  continue
70
70
 
@@ -131,15 +131,10 @@ class TcpMessageParser(IParser):
131
131
 
132
132
  def unpack(self, packet):
133
133
  # 提取指令码
134
- cmd_code = None
135
- try:
136
- cmd_code = int.from_bytes(packet[self.cmd_pos : self.cmd_pos + 2], 'little')
137
- cmd_class = CommandFactory.create_command(cmd_code)
138
- instance = cmd_class(self.device)
139
- instance.parse_body(packet[self.header_len:-2])
140
- except Exception as e:
141
- logger.error(f"指令[{cmd_code}]数据包[len={len(packet)}]解析异常: {e}")
142
-
134
+ cmd_code = int.from_bytes(packet[self.cmd_pos : self.cmd_pos + 2], 'little')
135
+ cmd_class = CommandFactory.create_command(cmd_code)
136
+ instance = cmd_class(self.device)
137
+ instance.parse_body(packet[self.header_len:-2])
143
138
  return instance
144
139
 
145
140
  def start(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qlsdk2
3
- Version: 0.6.0
3
+ Version: 0.6.0a2
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun