qlsdk2 0.4.0a2__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,16 @@
1
1
  import abc
2
+ import numpy as np
3
+ from time import time_ns
2
4
  from typing import Dict, Type
3
- from enum import Enum
4
5
  from loguru import logger
5
- from rsc.core.crctools import crc16
6
+
7
+
8
+ from qlsdk.core.crc import crc16
9
+ from qlsdk.core.device import BaseDevice,
10
+ from qlsdk.core.entity import RscPacket, ImpedancePacket
11
+ from qlsdk.core.utils import to_channels, to_bytes
12
+ from qlsdk.rsc.interface import IDevice, IParser
13
+ from qlsdk.rsc.device import DeviceFactory
6
14
 
7
15
  class DeviceCommand(abc.ABC):
8
16
  # 消息头
@@ -12,7 +20,7 @@ class DeviceCommand(abc.ABC):
12
20
  # 消息指令码位置
13
21
  CMD_POS = 12
14
22
 
15
- def __init__(self, device):
23
+ def __init__(self, device: BaseDevice):
16
24
  self.device = device
17
25
 
18
26
  @classmethod
@@ -23,6 +31,10 @@ class DeviceCommand(abc.ABC):
23
31
  @abc.abstractmethod
24
32
  def cmd_code(self) -> int:
25
33
  pass
34
+ @property
35
+ @abc.abstractmethod
36
+ def cmd_desc(self) -> str:
37
+ pass
26
38
 
27
39
  @staticmethod
28
40
  def checksum(data: bytes) -> bytes:
@@ -30,17 +42,23 @@ class DeviceCommand(abc.ABC):
30
42
 
31
43
  def pack(self, body=b'') -> bytes:
32
44
  # header+body+checksum
33
- header = self.header_pack(len(body))
45
+ body = self.pack_body()
46
+ header = self.pack_header(len(body))
34
47
  payload = header + body
35
48
  return payload + DeviceCommand.checksum(payload)
36
-
37
- def header_pack(self, body_len: int) -> bytes:
49
+ def pack_body(self) -> bytes:
50
+ """构建消息体"""
51
+ return b''
52
+ def pack_header(self, body_len: int) -> bytes:
53
+ device_id = int(self.device.device_id) if self.device and self.device.device_id else 0
54
+ device_type = int(self.device.device_type) if self.device and self.device.device_type else 0
55
+
38
56
  """构建消息头"""
39
57
  return (
40
58
  DeviceCommand.HEADER_PREFIX
41
- + self.device.pkg_type.to_bytes(1, 'little')
42
- + self.device.device_type.to_bytes(1, 'little')
43
- + self.device.device_id.to_bytes(4, 'little')
59
+ + int(2).to_bytes(1, 'little') # pkgType
60
+ + device_type.to_bytes(1, 'little')
61
+ + device_id.to_bytes(4, 'little')
44
62
  + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
45
63
  + self.cmd_code.to_bytes(2, 'little')
46
64
  )
@@ -48,33 +66,14 @@ class DeviceCommand(abc.ABC):
48
66
  def unpack(self, payload: bytes) -> bytes:
49
67
  """解析消息体"""
50
68
  # 解析消息体
51
- body = payload[self.HEADER_LEN:-2]
52
-
53
-
54
- class DeviceInfoCommand(DeviceCommand):
55
- """设备信息指令"""
56
- cmd_code = 0x01
57
-
58
- def parse_body(self, body: bytes):
59
- # 解析设备信息
60
- pass
61
-
62
- class SignalDataCommand(DeviceCommand):
63
- """设备信息指令"""
64
- cmd_code = 0x02
69
+ body = payload[self.HEADER_LEN:-2]
65
70
 
66
71
  def parse_body(self, body: bytes):
67
- # 解析设备信息
68
- pass
69
-
70
- if __name__ == "__main__":
71
- # 测试代码
72
- device = None # 这里应该是设备实例
73
- command1 = DeviceInfoCommand.build(device)
74
- logger.debug(command1.cmd_code)
75
- command2 = SignalDataCommand.build(device)
76
- logger.debug(command2.cmd_code)
77
-
72
+ time = int.from_bytes(body[0:8], 'little')
73
+ # result - 1B
74
+ result = body[8]
75
+ logger.info(f"[{time}]{self.cmd_desc}{'成功' if result == 0 else '失败'}")
76
+
78
77
 
79
78
 
80
79
  class CommandFactory:
@@ -87,15 +86,16 @@ class CommandFactory:
87
86
 
88
87
  @classmethod
89
88
  def create_command(cls, code: int) -> Type[DeviceCommand]:
90
- logger.debug(f"Creating command for code: {hex(code)}")
89
+ logger.trace(f"Creating command for code: {hex(code)}")
91
90
  if code not in cls._commands:
92
- logger.warning(f"Unsupported command code: {hex(code)}")
91
+ logger.warning(f"不支持的设备指令: {hex(code)}")
93
92
  return cls._commands[DefaultCommand.cmd_code]
94
93
  return cls._commands[code]
95
94
 
96
95
  # =============================================================================
97
96
  class DefaultCommand(DeviceCommand):
98
97
  cmd_code = 0x00
98
+ cmd_desc = "未定义"
99
99
 
100
100
  def parse_body(self, body: bytes):
101
101
  # Response parsing example: 2 bytes version + 4 bytes serial
@@ -103,32 +103,36 @@ class DefaultCommand(DeviceCommand):
103
103
 
104
104
  class GetDeviceInfoCommand(DeviceCommand):
105
105
  cmd_code = 0x17
106
+ cmd_desc = "设备信息"
106
107
 
107
108
  def parse_body(self, body: bytes):
108
- logger.info(f"Received GetDeviceInfoCommand body len: {len(body)}")
109
- # time - 8b
109
+ # time - 8B
110
110
  self.device.connect_time = int.from_bytes(body[0:8], 'little')
111
111
  self.device.current_time = self.device.connect_time
112
- # result - 1b
112
+ # result - 1B
113
113
  result = body[8]
114
- # deviceId - 4b
115
- self.device.device_id = body[9:13].hex()
116
- # deviceType - 4b
117
- self.device.device_type = body[13:17].hex()
118
- # softVersion - 4b
114
+ # deviceId - 4B
115
+ self.device.device_id = int.from_bytes(body[9:13], 'big')
116
+ # deviceType - 4B
117
+ self.device.device_type = int.from_bytes(body[13:17], 'little')
118
+ # softVersion - 4B
119
119
  self.device.software_version = body[17:21].hex()
120
- # hardVersion - 4b
120
+ # hardVersion - 4B
121
121
  self.device.hardware_version = body[21:25].hex()
122
- # deviceName - 16b
122
+ # deviceName - 16B
123
123
  self.device.device_name = body[25:41].decode('utf-8').rstrip('\x00')
124
- # flag - 4b
124
+ # flag - 4B
125
125
  flag = int.from_bytes(body[41:45], 'little')
126
126
  logger.debug(f"Received device info: {result}, {flag}, {self.device}")
127
127
 
128
+ # 创建设备对象
129
+ device = Dev
130
+
128
131
 
129
132
  # 握手
130
133
  class HandshakeCommand(DeviceCommand):
131
134
  cmd_code = 0x01
135
+ cmd_desc = "握手"
132
136
 
133
137
  def parse_body(self, body: bytes):
134
138
  logger.info(f"Received handshake response: {body.hex()}")
@@ -136,8 +140,8 @@ class HandshakeCommand(DeviceCommand):
136
140
  # 查询电量
137
141
  class QueryBatteryCommand(DeviceCommand):
138
142
  cmd_code = 0x16
143
+ cmd_desc = "电量信息"
139
144
  def parse_body(self, body: bytes):
140
- logger.info(f"Received QueryBatteryCommand body len: {len(body)}")
141
145
  # time - 8b
142
146
  self.device.current_time = int.from_bytes(body[0:8], 'little')
143
147
  # result - 1b
@@ -152,78 +156,168 @@ class QueryBatteryCommand(DeviceCommand):
152
156
  self.device.battery_total = body[12]
153
157
  # state - 1b
154
158
  # state = body[13]
159
+ logger.debug(f"电量更新: {self.device}")
155
160
  else:
156
161
  logger.warning(f"QueryBatteryCommand message received but result is failed.")
157
162
 
158
163
  # 设置采集参数
159
164
  class SetAcquisitionParamCommand(DeviceCommand):
160
165
  cmd_code = 0x451
161
- def parse_body(self, body: bytes):
162
- logger.info(f"Received SetAcquisitionParam response: {body.hex()}")
166
+ cmd_desc = "设置信号采集参数"
167
+
168
+ def pack_body(self):
169
+ body = to_bytes(self.device.acq_channels)
170
+ body += self.device.sample_range.to_bytes(4, byteorder='little')
171
+ body += self.device.sample_rate.to_bytes(4, byteorder='little')
172
+ body += self.device.sample_num.to_bytes(4, byteorder='little')
173
+ body += self.device.resolution.to_bytes(1, byteorder='little')
174
+ body += bytes.fromhex('00')
175
+
176
+ return body
163
177
 
164
178
  # 启动采集
165
179
  class StartAcquisitionCommand(DeviceCommand):
166
180
  cmd_code = 0x452
167
- def parse_body(self, body: bytes):
168
- logger.info(f"Received acquisition start response: {body.hex()}")
181
+ cmd_desc = "启动信号采集"
182
+
183
+ def pack_body(self):
184
+ return bytes.fromhex('0000')
169
185
 
170
186
  # 停止采集
171
187
  class StopAcquisitionCommand(DeviceCommand):
172
188
  cmd_code = 0x453
189
+ cmd_desc = "停止信号采集"
173
190
 
174
- def parse_body(self, body: bytes):
175
- logger.info(f"Received acquisition stop response: {body.hex()}")
191
+ def pack_body(self):
192
+ return b''
193
+
194
+
176
195
  # 设置阻抗采集参数
177
196
  class SetImpedanceParamCommand(DeviceCommand):
178
197
  cmd_code = 0x411
179
- def parse_body(self, body: bytes):
180
- logger.info(f"Received SetImpedanceParamCommand response: {body.hex()}")
181
- # 启动采集
198
+ cmd_desc = "设置阻抗测量参数"
199
+
200
+ # 启动阻抗测量
182
201
  class StartImpedanceCommand(DeviceCommand):
183
202
  cmd_code = 0x412
184
- def parse_body(self, body: bytes):
185
- logger.info(f"Received StartImpedanceCommand response: {body.hex()}")
203
+ cmd_desc = "启动阻抗测量"
204
+ def pack_body(self):
205
+ body = bytes.fromhex('0000')
206
+ body += to_bytes(self.device.acq_channels)
207
+ body += bytes.fromhex('0000000000000000') # 8字节占位符
208
+ return body
209
+
186
210
 
187
- # 停止采集
211
+ # 停止阻抗测量
188
212
  class StopImpedanceCommand(DeviceCommand):
189
213
  cmd_code = 0x413
214
+ cmd_desc = "停止阻抗测量"
190
215
 
191
- def parse_body(self, body: bytes):
192
- logger.info(f"Received StopImpedanceCommand response: {body.hex()}")
193
-
194
- # 启动采集
216
+ def pack_body(self):
217
+ return b''
218
+
219
+ # 启动刺激
195
220
  class StartStimulationCommand(DeviceCommand):
196
221
  cmd_code = 0x48C
222
+ cmd_desc = "启动刺激"
223
+ def pack_body(self):
224
+ return self.device.stim_paradigm.to_bytes()
225
+ # return bytes.fromhex('01000000000000008813000000000000010000000000000000000140420f00640064000000803f0000010000000000000000000000000000000000000000008813000000000000')
197
226
  def parse_body(self, body: bytes):
198
- logger.info(f"Received acquisition start response: {body.hex()}")
227
+ # time - 8B
228
+ time = int.from_bytes(body[0:8], 'little')
229
+ # result - 1B
230
+ result = body[8]
231
+ # error_channel - 8B
232
+ # error_channel= int.from_bytes(body[9:17], 'big')
233
+ channels = to_channels(body[9:17])
234
+ logger.success(f"通道 {channels} 刺激开始")
235
+ self.device.trigger(f"通道 {channels} 刺激开始")
236
+ # error_type - 1B
237
+ error_type = body[17]
199
238
 
200
- # 停止采集
239
+ # 停止刺激
201
240
  class StopStimulationCommand(DeviceCommand):
202
241
  cmd_code = 0x488
242
+ cmd_desc = "停止刺激"
243
+
244
+ # 启动刺激
245
+ class StopStimulationNotifyCommand(DeviceCommand):
246
+ cmd_code = 0x48D
247
+ cmd_desc = "停止刺激通知"
248
+ def pack_body(self):
249
+ return self.device.stim_paradigm.to_bytes()
250
+ # return bytes.fromhex('01000000000000008813000000000000010000000000000000000140420f00640064000000803f0000010000000000000000000000000000000000000000008813000000000000')
251
+ def parse_body(self, body: bytes):
252
+ # time - 8B
253
+ time = int.from_bytes(body[0:8], 'little')
254
+ # result - 1B
255
+ result = body[8]
256
+ # error_channel - 8B
257
+ # error_channel= int.from_bytes(body[9:17], 'big')
258
+ channels = to_channels(body[9:17])
259
+ logger.success(f"通道 {channels} 刺激结束")
260
+ self.device.trigger(f"通道 {channels} 刺激结束", time)
261
+ # error_type - 1B
262
+ error_type = body[17]
263
+ # 刺激信息
264
+ class StimulationInfoCommand(DeviceCommand):
265
+ cmd_code = 0x48e
266
+ cmd_desc = "刺激告警信息"
203
267
 
204
268
  def parse_body(self, body: bytes):
205
- logger.info(f"Received acquisition stop response: {body.hex()}")
269
+ time = int.from_bytes(body[0:8], 'little')
270
+ # result - 1B
271
+ result = body[8]
272
+ # error_channel - 8B
273
+ channels = to_channels(body[9:17])
274
+ # 保留位-8B
275
+ # error_type - 1B
276
+ err_type = body[17]
277
+ # 特征位-4B
278
+ # errType = int.from_bytes(body[25:29], 'little')
279
+ logger.warning(f"刺激告警信息[{err_type}],通道 {channels} 刺激驱动不足")
280
+
206
281
 
207
282
  # 阻抗数据
208
283
  class ImpedanceDataCommand(DeviceCommand):
209
284
  cmd_code = 0x415
285
+ cmd_desc = "阻抗数据"
210
286
 
211
287
  def parse_body(self, body: bytes):
212
288
  logger.info(f"Received impedance data: {body.hex()}")
289
+ packet = ImpedancePacket().transfer(body)
213
290
 
214
291
  # 信号数据
215
292
  class SignalDataCommand(DeviceCommand):
216
293
  cmd_code = 0x455
294
+ cmd_desc = "信号数据"
295
+
296
+ def unpack(self, payload):
297
+ return super().unpack(payload)
217
298
 
218
- def parse_body(self, body: bytes):
219
- logger.info(f"Received signal data: {len(body)}字节, the subscribe is {self.device.signal_consumers}")
220
- for q in list(self.device.signal_consumers.values()):
221
- q.put(body)
299
+ def parse_body(self, body: bytes):
300
+ # 解析数据包
301
+ packet = RscPacket()
302
+ packet.transfer(body)
303
+
304
+ # 文件写入到edf
305
+ if self.device.edf_handler:
306
+ self.device.edf_handler.write(packet)
307
+
308
+ if len(self.device.signal_consumers) > 0 :
309
+ # 信号数字值转物理值
310
+ packet.eeg = self.device.eeg2phy(np.array(packet.eeg))
311
+
312
+ # 发送数据包到订阅者
313
+ for q in list(self.device.signal_consumers.values()):
314
+ q.put(packet)
315
+
316
+
222
317
 
223
318
  # =============================================================================
224
- # Command Registration
319
+ # 指令实现类注册到指令工厂
225
320
  # =============================================================================
226
-
227
321
  CommandFactory.register_command(DefaultCommand.cmd_code, DefaultCommand)
228
322
  CommandFactory.register_command(GetDeviceInfoCommand.cmd_code, GetDeviceInfoCommand)
229
323
  CommandFactory.register_command(HandshakeCommand.cmd_code, HandshakeCommand)
@@ -235,5 +329,8 @@ CommandFactory.register_command(SetImpedanceParamCommand.cmd_code, SetImpedanceP
235
329
  CommandFactory.register_command(StartImpedanceCommand.cmd_code, StartImpedanceCommand)
236
330
  CommandFactory.register_command(StopImpedanceCommand.cmd_code, StopImpedanceCommand)
237
331
  CommandFactory.register_command(StartStimulationCommand.cmd_code, StartStimulationCommand)
332
+ CommandFactory.register_command(StimulationInfoCommand.cmd_code, StimulationInfoCommand)
238
333
  CommandFactory.register_command(ImpedanceDataCommand.cmd_code, ImpedanceDataCommand)
239
- CommandFactory.register_command(SignalDataCommand.cmd_code, SignalDataCommand)
334
+ CommandFactory.register_command(SignalDataCommand.cmd_code, SignalDataCommand)
335
+
336
+
@@ -0,0 +1,2 @@
1
+ from .base import QLBaseDevice
2
+ from .device_factory import DeviceFactory