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.
- qlsdk/ar4/__init__.py +9 -5
- qlsdk/core/__init__.py +2 -1
- qlsdk/core/device.py +10 -1
- qlsdk/core/entity/__init__.py +1 -1
- qlsdk/core/message/command.py +93 -53
- qlsdk/core/network/__init__.py +34 -0
- qlsdk/core/network/monitor.py +55 -0
- qlsdk/core/utils.py +2 -0
- qlsdk/persist/edf.py +21 -2
- qlsdk/persist/rsc_edf.py +190 -126
- qlsdk/rsc/__init__.py +4 -2
- qlsdk/rsc/command/__init__.py +183 -61
- qlsdk/rsc/command/message.py +171 -74
- qlsdk/rsc/device/__init__.py +2 -0
- qlsdk/rsc/device/base.py +388 -0
- qlsdk/rsc/device/c64_rs.py +364 -0
- qlsdk/rsc/device/device_factory.py +29 -0
- qlsdk/rsc/device_manager.py +2 -0
- qlsdk/rsc/entity.py +95 -179
- qlsdk/rsc/interface/__init__.py +3 -0
- qlsdk/rsc/interface/command.py +10 -0
- qlsdk/rsc/interface/device.py +107 -0
- qlsdk/rsc/interface/handler.py +9 -0
- qlsdk/rsc/interface/parser.py +9 -0
- qlsdk/rsc/manager/__init__.py +2 -0
- qlsdk/rsc/manager/container.py +121 -0
- qlsdk/rsc/manager/search.py +0 -0
- qlsdk/rsc/network/__init__.py +1 -0
- qlsdk/rsc/network/discover.py +87 -0
- qlsdk/rsc/paradigm.py +4 -3
- qlsdk/rsc/parser/__init__.py +1 -0
- qlsdk/rsc/parser/base.py +66 -0
- qlsdk/sdk/ar4sdk.py +13 -4
- qlsdk/x8/__init__.py +4 -0
- {qlsdk2-0.4.0a2.dist-info → qlsdk2-0.4.1.dist-info}/METADATA +2 -2
- qlsdk2-0.4.1.dist-info/RECORD +58 -0
- qlsdk2-0.4.0a2.dist-info/RECORD +0 -40
- {qlsdk2-0.4.0a2.dist-info → qlsdk2-0.4.1.dist-info}/WHEEL +0 -0
- {qlsdk2-0.4.0a2.dist-info → qlsdk2-0.4.1.dist-info}/top_level.txt +0 -0
qlsdk/rsc/command/message.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
+
|
|
42
|
-
+
|
|
43
|
-
+
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
if
|
|
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.
|
|
89
|
+
logger.trace(f"Creating command for code: {hex(code)}")
|
|
91
90
|
if code not in cls._commands:
|
|
92
|
-
logger.warning(f"
|
|
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
|
-
|
|
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 -
|
|
112
|
+
# result - 1B
|
|
113
113
|
result = body[8]
|
|
114
|
-
# deviceId -
|
|
115
|
-
self.device.device_id = body[9:13]
|
|
116
|
-
# deviceType -
|
|
117
|
-
self.device.device_type = body[13:17]
|
|
118
|
-
# softVersion -
|
|
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 -
|
|
120
|
+
# hardVersion - 4B
|
|
121
121
|
self.device.hardware_version = body[21:25].hex()
|
|
122
|
-
# deviceName -
|
|
122
|
+
# deviceName - 16B
|
|
123
123
|
self.device.device_name = body[25:41].decode('utf-8').rstrip('\x00')
|
|
124
|
-
# flag -
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
|
175
|
-
|
|
191
|
+
def pack_body(self):
|
|
192
|
+
return b''
|
|
193
|
+
|
|
194
|
+
|
|
176
195
|
# 设置阻抗采集参数
|
|
177
196
|
class SetImpedanceParamCommand(DeviceCommand):
|
|
178
197
|
cmd_code = 0x411
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
#
|
|
198
|
+
cmd_desc = "设置阻抗测量参数"
|
|
199
|
+
|
|
200
|
+
# 启动阻抗测量
|
|
182
201
|
class StartImpedanceCommand(DeviceCommand):
|
|
183
202
|
cmd_code = 0x412
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
#
|
|
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
|
+
|