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/__init__.py
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
28
|
-
def pack(cls, body=b'') -> bytes:
|
|
41
|
+
def pack(self, body=b'') -> bytes:
|
|
29
42
|
# header+body+checksum
|
|
30
|
-
|
|
43
|
+
body = self.pack_body()
|
|
44
|
+
header = self.pack_header(len(body))
|
|
31
45
|
payload = header + body
|
|
32
|
-
return payload + DeviceCommand.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
+
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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.
|
|
87
|
+
logger.trace(f"Creating command for code: {hex(code)}")
|
|
66
88
|
if code not in cls._commands:
|
|
67
|
-
logger.warning(f"
|
|
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
|
-
|
|
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 -
|
|
110
|
+
# result - 1B
|
|
88
111
|
result = body[8]
|
|
89
|
-
# deviceId -
|
|
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 -
|
|
92
|
-
|
|
93
|
-
|
|
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 -
|
|
120
|
+
# hardVersion - 4B
|
|
96
121
|
self.device.hardware_version = body[21:25].hex()
|
|
97
|
-
# deviceName -
|
|
122
|
+
# deviceName - 16B
|
|
98
123
|
self.device.device_name = body[25:41].decode('utf-8').rstrip('\x00')
|
|
99
|
-
|
|
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,168 @@ 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
|
-
|
|
137
|
-
|
|
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
|
|
138
177
|
|
|
139
178
|
# 启动采集
|
|
140
179
|
class StartAcquisitionCommand(DeviceCommand):
|
|
141
180
|
cmd_code = 0x452
|
|
142
|
-
|
|
143
|
-
|
|
181
|
+
cmd_desc = "启动信号采集"
|
|
182
|
+
|
|
183
|
+
def pack_body(self):
|
|
184
|
+
return bytes.fromhex('0000')
|
|
144
185
|
|
|
145
186
|
# 停止采集
|
|
146
187
|
class StopAcquisitionCommand(DeviceCommand):
|
|
147
188
|
cmd_code = 0x453
|
|
189
|
+
cmd_desc = "停止信号采集"
|
|
148
190
|
|
|
149
|
-
def
|
|
150
|
-
|
|
191
|
+
def pack_body(self):
|
|
192
|
+
return b''
|
|
193
|
+
|
|
194
|
+
|
|
151
195
|
# 设置阻抗采集参数
|
|
152
196
|
class SetImpedanceParamCommand(DeviceCommand):
|
|
153
197
|
cmd_code = 0x411
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
#
|
|
198
|
+
cmd_desc = "设置阻抗测量参数"
|
|
199
|
+
|
|
200
|
+
# 启动阻抗测量
|
|
157
201
|
class StartImpedanceCommand(DeviceCommand):
|
|
158
202
|
cmd_code = 0x412
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
|
|
161
210
|
|
|
162
|
-
#
|
|
211
|
+
# 停止阻抗测量
|
|
163
212
|
class StopImpedanceCommand(DeviceCommand):
|
|
164
213
|
cmd_code = 0x413
|
|
214
|
+
cmd_desc = "停止阻抗测量"
|
|
165
215
|
|
|
166
|
-
def
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
def pack_body(self):
|
|
217
|
+
return b''
|
|
218
|
+
|
|
169
219
|
# 启动刺激
|
|
170
220
|
class StartStimulationCommand(DeviceCommand):
|
|
171
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')
|
|
172
226
|
def parse_body(self, body: bytes):
|
|
173
|
-
|
|
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]
|
|
174
238
|
|
|
175
239
|
# 停止刺激
|
|
176
240
|
class StopStimulationCommand(DeviceCommand):
|
|
177
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 = "刺激告警信息"
|
|
178
267
|
|
|
179
268
|
def parse_body(self, body: bytes):
|
|
180
|
-
|
|
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
|
+
|
|
181
281
|
|
|
182
282
|
# 阻抗数据
|
|
183
283
|
class ImpedanceDataCommand(DeviceCommand):
|
|
184
284
|
cmd_code = 0x415
|
|
285
|
+
cmd_desc = "阻抗数据"
|
|
185
286
|
|
|
186
287
|
def parse_body(self, body: bytes):
|
|
187
288
|
logger.info(f"Received impedance data: {body.hex()}")
|
|
289
|
+
packet = ImpedancePacket().transfer(body)
|
|
188
290
|
|
|
189
291
|
# 信号数据
|
|
190
292
|
class SignalDataCommand(DeviceCommand):
|
|
191
293
|
cmd_code = 0x455
|
|
294
|
+
cmd_desc = "信号数据"
|
|
192
295
|
|
|
193
|
-
def
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
296
|
+
def unpack(self, payload):
|
|
297
|
+
return super().unpack(payload)
|
|
298
|
+
|
|
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
|
+
|
|
197
317
|
|
|
198
318
|
# =============================================================================
|
|
199
|
-
#
|
|
319
|
+
# 指令实现类注册到指令工厂
|
|
200
320
|
# =============================================================================
|
|
201
|
-
|
|
202
321
|
CommandFactory.register_command(DefaultCommand.cmd_code, DefaultCommand)
|
|
203
322
|
CommandFactory.register_command(GetDeviceInfoCommand.cmd_code, GetDeviceInfoCommand)
|
|
204
323
|
CommandFactory.register_command(HandshakeCommand.cmd_code, HandshakeCommand)
|
|
@@ -210,5 +329,8 @@ CommandFactory.register_command(SetImpedanceParamCommand.cmd_code, SetImpedanceP
|
|
|
210
329
|
CommandFactory.register_command(StartImpedanceCommand.cmd_code, StartImpedanceCommand)
|
|
211
330
|
CommandFactory.register_command(StopImpedanceCommand.cmd_code, StopImpedanceCommand)
|
|
212
331
|
CommandFactory.register_command(StartStimulationCommand.cmd_code, StartStimulationCommand)
|
|
332
|
+
CommandFactory.register_command(StimulationInfoCommand.cmd_code, StimulationInfoCommand)
|
|
213
333
|
CommandFactory.register_command(ImpedanceDataCommand.cmd_code, ImpedanceDataCommand)
|
|
214
334
|
CommandFactory.register_command(SignalDataCommand.cmd_code, SignalDataCommand)
|
|
335
|
+
|
|
336
|
+
|