qlsdk2 0.4.0a3__py3-none-any.whl → 0.4.2__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.
Files changed (41) hide show
  1. qlsdk/ar4/__init__.py +9 -5
  2. qlsdk/core/device.py +10 -1
  3. qlsdk/core/entity/__init__.py +1 -0
  4. qlsdk/core/message/command.py +56 -35
  5. qlsdk/core/utils.py +20 -15
  6. qlsdk/persist/__init__.py +2 -1
  7. qlsdk/persist/ars_edf.py +278 -0
  8. qlsdk/persist/edf.py +21 -2
  9. qlsdk/persist/rsc_edf.py +187 -124
  10. qlsdk/persist/stream.py +161 -0
  11. qlsdk/rsc/__init__.py +4 -2
  12. qlsdk/rsc/command/__init__.py +177 -61
  13. qlsdk/rsc/command/message.py +171 -74
  14. qlsdk/rsc/device/__init__.py +2 -0
  15. qlsdk/rsc/device/arskindling.py +384 -0
  16. qlsdk/rsc/device/base.py +413 -0
  17. qlsdk/rsc/device/c256_rs.py +364 -0
  18. qlsdk/rsc/device/c64_rs.py +358 -0
  19. qlsdk/rsc/device/c64s1.py +364 -0
  20. qlsdk/rsc/device/device_factory.py +33 -0
  21. qlsdk/rsc/entity.py +63 -24
  22. qlsdk/rsc/interface/__init__.py +3 -0
  23. qlsdk/rsc/interface/command.py +10 -0
  24. qlsdk/rsc/interface/device.py +114 -0
  25. qlsdk/rsc/interface/handler.py +9 -0
  26. qlsdk/rsc/interface/parser.py +12 -0
  27. qlsdk/rsc/manager/__init__.py +2 -0
  28. qlsdk/rsc/manager/container.py +126 -0
  29. qlsdk/rsc/manager/search.py +0 -0
  30. qlsdk/rsc/network/__init__.py +1 -0
  31. qlsdk/rsc/network/discover.py +87 -0
  32. qlsdk/rsc/paradigm.py +1 -1
  33. qlsdk/rsc/parser/__init__.py +1 -0
  34. qlsdk/rsc/parser/base.py +69 -0
  35. qlsdk/sdk/ar4sdk.py +13 -4
  36. qlsdk/x8/__init__.py +4 -0
  37. {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/METADATA +1 -1
  38. qlsdk2-0.4.2.dist-info/RECORD +63 -0
  39. qlsdk2-0.4.0a3.dist-info/RECORD +0 -42
  40. {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/WHEEL +0 -0
  41. {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,14 @@
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 qlsdk.core import crc16
6
6
 
7
- class UDPCommand(Enum):
8
- CONNECT = 0x10
9
- NOTIFY = 0x09
10
-
7
+
8
+ from qlsdk.core.crc import crc16
9
+ from qlsdk.core.entity import RscPacket, ImpedancePacket
10
+ from qlsdk.core.utils import to_channels, to_bytes
11
+ from qlsdk.rsc.interface import IDevice
11
12
 
12
13
  class DeviceCommand(abc.ABC):
13
14
  # 消息头
@@ -17,40 +18,61 @@ class DeviceCommand(abc.ABC):
17
18
  # 消息指令码位置
18
19
  CMD_POS = 12
19
20
 
20
- def __init__(self, device):
21
+ def __init__(self, device: IDevice):
21
22
  self.device = device
23
+
24
+ @classmethod
25
+ def build(cls, device) :
26
+ return cls(device)
27
+
22
28
  @property
23
29
  @abc.abstractmethod
24
30
  def cmd_code(self) -> int:
25
31
  pass
32
+ @property
33
+ @abc.abstractmethod
34
+ def cmd_desc(self) -> str:
35
+ pass
36
+
37
+ @staticmethod
38
+ def checksum(data: bytes) -> bytes:
39
+ return crc16(data).to_bytes(2, 'little')
26
40
 
27
- @classmethod
28
- def pack(cls, body=b'') -> bytes:
41
+ def pack(self, body=b'') -> bytes:
29
42
  # header+body+checksum
30
- header = DeviceCommand.build_header(cls.cmd_code, len(body))
43
+ body = self.pack_body()
44
+ header = self.pack_header(len(body))
31
45
  payload = header + body
32
- return payload + DeviceCommand.calculate_checksum(payload)
33
-
34
- @staticmethod
35
- def build_header(cmd_code, body_len: int = 0, pkg_type=2, device_type=0, device_id=0) -> bytes:
36
- """Construct protocol header"""
46
+ return payload + DeviceCommand.checksum(payload)
47
+ def pack_body(self) -> bytes:
48
+ """构建消息体"""
49
+ return b''
50
+ def pack_header(self, body_len: int) -> bytes:
51
+ device_id = int(self.device.device_id) if self.device and self.device.device_id else 0
52
+ device_type = int(self.device.device_type) if self.device and self.device.device_type else 0
53
+
54
+ """构建消息头"""
37
55
  return (
38
56
  DeviceCommand.HEADER_PREFIX
39
- + pkg_type.to_bytes(1, 'little')
57
+ + int(2).to_bytes(1, 'little') # pkgType
40
58
  + device_type.to_bytes(1, 'little')
41
59
  + device_id.to_bytes(4, 'little')
42
60
  + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
43
- + cmd_code.to_bytes(2, 'little')
61
+ + self.cmd_code.to_bytes(2, 'little')
44
62
  )
45
-
46
- @staticmethod
47
- def calculate_checksum(data: bytes) -> bytes:
48
- return crc16(data).to_bytes(2, 'little')
49
-
50
- @abc.abstractmethod
63
+
64
+ def unpack(self, payload: bytes) -> bytes:
65
+ """解析消息体"""
66
+ # 解析消息体
67
+ body = payload[self.HEADER_LEN:-2]
68
+
51
69
  def parse_body(self, body: bytes):
52
- """Implement in subclasses for command-specific parsing"""
53
- pass
70
+ time = int.from_bytes(body[0:8], 'little')
71
+ # result - 1B
72
+ result = body[8]
73
+ logger.info(f"[{time}]{self.cmd_desc}{'成功' if result == 0 else '失败'}")
74
+
75
+
54
76
 
55
77
  class CommandFactory:
56
78
  """Registry for command implementations"""
@@ -62,15 +84,16 @@ class CommandFactory:
62
84
 
63
85
  @classmethod
64
86
  def create_command(cls, code: int) -> Type[DeviceCommand]:
65
- logger.debug(f"Creating command for code: {hex(code)}")
87
+ logger.trace(f"Creating command for code: {hex(code)}")
66
88
  if code not in cls._commands:
67
- logger.warning(f"Unsupported command code: {hex(code)}")
89
+ logger.warning(f"不支持的设备指令: {hex(code)}")
68
90
  return cls._commands[DefaultCommand.cmd_code]
69
91
  return cls._commands[code]
70
92
 
71
93
  # =============================================================================
72
94
  class DefaultCommand(DeviceCommand):
73
95
  cmd_code = 0x00
96
+ cmd_desc = "未定义"
74
97
 
75
98
  def parse_body(self, body: bytes):
76
99
  # Response parsing example: 2 bytes version + 4 bytes serial
@@ -78,32 +101,38 @@ class DefaultCommand(DeviceCommand):
78
101
 
79
102
  class GetDeviceInfoCommand(DeviceCommand):
80
103
  cmd_code = 0x17
104
+ cmd_desc = "设备信息"
81
105
 
82
106
  def parse_body(self, body: bytes):
83
- logger.info(f"Received GetDeviceInfoCommand body len: {len(body)}")
84
- # time - 8b
107
+ # time - 8B
85
108
  self.device.connect_time = int.from_bytes(body[0:8], 'little')
86
109
  self.device.current_time = self.device.connect_time
87
- # result - 1b
110
+ # result - 1B
88
111
  result = body[8]
89
- # deviceId - 4b
112
+ # deviceId - 4B
113
+ # self.device.device_id = int.from_bytes(body[9:13], 'big')
90
114
  self.device.device_id = body[9:13].hex()
91
- # deviceType - 4b
92
- self.device.device_type = body[13:17].hex()
93
- # softVersion - 4b
115
+ # deviceType - 4B
116
+ device_type = int.from_bytes(body[13:17], 'little')
117
+ self.device.set_device_type(device_type)
118
+ # softVersion - 4B
94
119
  self.device.software_version = body[17:21].hex()
95
- # hardVersion - 4b
120
+ # hardVersion - 4B
96
121
  self.device.hardware_version = body[21:25].hex()
97
- # deviceName - 16b
122
+ # deviceName - 16B
98
123
  self.device.device_name = body[25:41].decode('utf-8').rstrip('\x00')
99
- # flag - 4b
124
+ self.device.set_device_no(self.device.device_name)
125
+ # flag - 4B
100
126
  flag = int.from_bytes(body[41:45], 'little')
101
127
  logger.debug(f"Received device info: {result}, {flag}, {self.device}")
102
128
 
129
+ # 创建设备对象
130
+
103
131
 
104
132
  # 握手
105
133
  class HandshakeCommand(DeviceCommand):
106
134
  cmd_code = 0x01
135
+ cmd_desc = "握手"
107
136
 
108
137
  def parse_body(self, body: bytes):
109
138
  logger.info(f"Received handshake response: {body.hex()}")
@@ -111,8 +140,8 @@ class HandshakeCommand(DeviceCommand):
111
140
  # 查询电量
112
141
  class QueryBatteryCommand(DeviceCommand):
113
142
  cmd_code = 0x16
143
+ cmd_desc = "电量信息"
114
144
  def parse_body(self, body: bytes):
115
- logger.info(f"Received QueryBatteryCommand body len: {len(body)}")
116
145
  # time - 8b
117
146
  self.device.current_time = int.from_bytes(body[0:8], 'little')
118
147
  # result - 1b
@@ -127,78 +156,162 @@ class QueryBatteryCommand(DeviceCommand):
127
156
  self.device.battery_total = body[12]
128
157
  # state - 1b
129
158
  # state = body[13]
159
+ logger.debug(f"电量更新: {self.device}")
130
160
  else:
131
161
  logger.warning(f"QueryBatteryCommand message received but result is failed.")
132
162
 
133
163
  # 设置采集参数
134
164
  class SetAcquisitionParamCommand(DeviceCommand):
135
165
  cmd_code = 0x451
136
- def parse_body(self, body: bytes):
137
- logger.info(f"Received SetAcquisitionParam response: {body.hex()}")
166
+ cmd_desc = "设置信号采集参数"
167
+
168
+ def pack_body(self):
169
+
170
+ return self.device.gen_set_acquirement_param()
138
171
 
139
172
  # 启动采集
140
173
  class StartAcquisitionCommand(DeviceCommand):
141
174
  cmd_code = 0x452
142
- def parse_body(self, body: bytes):
143
- logger.info(f"Received acquisition start response: {body.hex()}")
175
+ cmd_desc = "启动信号采集"
176
+
177
+ def pack_body(self):
178
+ return bytes.fromhex('0000')
144
179
 
145
180
  # 停止采集
146
181
  class StopAcquisitionCommand(DeviceCommand):
147
182
  cmd_code = 0x453
183
+ cmd_desc = "停止信号采集"
148
184
 
149
- def parse_body(self, body: bytes):
150
- logger.info(f"Received acquisition stop response: {body.hex()}")
185
+ def pack_body(self):
186
+ return b''
187
+
188
+
151
189
  # 设置阻抗采集参数
152
190
  class SetImpedanceParamCommand(DeviceCommand):
153
191
  cmd_code = 0x411
154
- def parse_body(self, body: bytes):
155
- logger.info(f"Received SetImpedanceParamCommand response: {body.hex()}")
156
- # 启动采集
192
+ cmd_desc = "设置阻抗测量参数"
193
+
194
+ # 启动阻抗测量
157
195
  class StartImpedanceCommand(DeviceCommand):
158
196
  cmd_code = 0x412
159
- def parse_body(self, body: bytes):
160
- logger.info(f"Received StartImpedanceCommand response: {body.hex()}")
197
+ cmd_desc = "启动阻抗测量"
198
+ def pack_body(self):
199
+ body = bytes.fromhex('0000')
200
+ body += to_bytes(self.device.acq_channels)
201
+ body += bytes.fromhex('0000000000000000') # 8字节占位符
202
+ return body
203
+
161
204
 
162
- # 停止采集
205
+ # 停止阻抗测量
163
206
  class StopImpedanceCommand(DeviceCommand):
164
207
  cmd_code = 0x413
208
+ cmd_desc = "停止阻抗测量"
165
209
 
166
- def parse_body(self, body: bytes):
167
- logger.info(f"Received StopImpedanceCommand response: {body.hex()}")
168
-
210
+ def pack_body(self):
211
+ return b''
212
+
169
213
  # 启动刺激
170
214
  class StartStimulationCommand(DeviceCommand):
171
215
  cmd_code = 0x48C
216
+ cmd_desc = "启动刺激"
217
+ def pack_body(self):
218
+ return self.device.stim_paradigm.to_bytes()
219
+ # return bytes.fromhex('01000000000000008813000000000000010000000000000000000140420f00640064000000803f0000010000000000000000000000000000000000000000008813000000000000')
172
220
  def parse_body(self, body: bytes):
173
- logger.info(f"Received acquisition start response: {body.hex()}")
221
+ # time - 8B
222
+ time = int.from_bytes(body[0:8], 'little')
223
+ # result - 1B
224
+ result = body[8]
225
+ # error_channel - 8B
226
+ # error_channel= int.from_bytes(body[9:17], 'big')
227
+ channels = to_channels(body[9:17])
228
+ logger.success(f"通道 {channels} 刺激开始")
229
+ self.device.trigger(f"通道 {channels} 刺激开始")
230
+ # error_type - 1B
231
+ error_type = body[17]
174
232
 
175
233
  # 停止刺激
176
234
  class StopStimulationCommand(DeviceCommand):
177
235
  cmd_code = 0x488
236
+ cmd_desc = "停止刺激"
237
+
238
+ # 启动刺激
239
+ class StopStimulationNotifyCommand(DeviceCommand):
240
+ cmd_code = 0x48D
241
+ cmd_desc = "停止刺激通知"
242
+ def pack_body(self):
243
+ return self.device.stim_paradigm.to_bytes()
244
+ # return bytes.fromhex('01000000000000008813000000000000010000000000000000000140420f00640064000000803f0000010000000000000000000000000000000000000000008813000000000000')
245
+ def parse_body(self, body: bytes):
246
+ # time - 8B
247
+ time = int.from_bytes(body[0:8], 'little')
248
+ # result - 1B
249
+ result = body[8]
250
+ # error_channel - 8B
251
+ # error_channel= int.from_bytes(body[9:17], 'big')
252
+ channels = to_channels(body[9:17])
253
+ logger.success(f"通道 {channels} 刺激结束")
254
+ self.device.trigger(f"通道 {channels} 刺激结束", time)
255
+ # error_type - 1B
256
+ error_type = body[17]
257
+ # 刺激信息
258
+ class StimulationInfoCommand(DeviceCommand):
259
+ cmd_code = 0x48e
260
+ cmd_desc = "刺激告警信息"
178
261
 
179
262
  def parse_body(self, body: bytes):
180
- logger.info(f"Received acquisition stop response: {body.hex()}")
263
+ time = int.from_bytes(body[0:8], 'little')
264
+ # result - 1B
265
+ result = body[8]
266
+ # error_channel - 8B
267
+ channels = to_channels(body[9:17])
268
+ # 保留位-8B
269
+ # error_type - 1B
270
+ err_type = body[17]
271
+ # 特征位-4B
272
+ # errType = int.from_bytes(body[25:29], 'little')
273
+ logger.warning(f"刺激告警信息[{err_type}],通道 {channels} 刺激驱动不足")
274
+
181
275
 
182
276
  # 阻抗数据
183
277
  class ImpedanceDataCommand(DeviceCommand):
184
278
  cmd_code = 0x415
279
+ cmd_desc = "阻抗数据"
185
280
 
186
281
  def parse_body(self, body: bytes):
187
282
  logger.info(f"Received impedance data: {body.hex()}")
283
+ packet = ImpedancePacket().transfer(body)
188
284
 
189
285
  # 信号数据
190
286
  class SignalDataCommand(DeviceCommand):
191
287
  cmd_code = 0x455
288
+ cmd_desc = "信号数据"
192
289
 
193
- def parse_body(self, body: bytes):
194
- logger.info(f"Received signal data: {len(body)}字节, the subscribe is {self.device.signal_consumers}")
195
- for q in list(self.device.signal_consumers.values()):
196
- q.put(body)
290
+ def unpack(self, payload):
291
+ return super().unpack(payload)
292
+
293
+ def parse_body(self, body: bytes):
294
+ # 解析数据包
295
+ packet = RscPacket()
296
+ 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)
309
+
310
+
197
311
 
198
312
  # =============================================================================
199
- # Command Registration
313
+ # 指令实现类注册到指令工厂
200
314
  # =============================================================================
201
-
202
315
  CommandFactory.register_command(DefaultCommand.cmd_code, DefaultCommand)
203
316
  CommandFactory.register_command(GetDeviceInfoCommand.cmd_code, GetDeviceInfoCommand)
204
317
  CommandFactory.register_command(HandshakeCommand.cmd_code, HandshakeCommand)
@@ -210,5 +323,8 @@ CommandFactory.register_command(SetImpedanceParamCommand.cmd_code, SetImpedanceP
210
323
  CommandFactory.register_command(StartImpedanceCommand.cmd_code, StartImpedanceCommand)
211
324
  CommandFactory.register_command(StopImpedanceCommand.cmd_code, StopImpedanceCommand)
212
325
  CommandFactory.register_command(StartStimulationCommand.cmd_code, StartStimulationCommand)
326
+ CommandFactory.register_command(StimulationInfoCommand.cmd_code, StimulationInfoCommand)
213
327
  CommandFactory.register_command(ImpedanceDataCommand.cmd_code, ImpedanceDataCommand)
214
328
  CommandFactory.register_command(SignalDataCommand.cmd_code, SignalDataCommand)
329
+
330
+