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.
- qlsdk/ar4/__init__.py +9 -5
- qlsdk/core/device.py +10 -1
- qlsdk/core/entity/__init__.py +1 -0
- qlsdk/core/message/command.py +56 -35
- qlsdk/core/utils.py +20 -15
- qlsdk/persist/__init__.py +2 -1
- qlsdk/persist/ars_edf.py +278 -0
- qlsdk/persist/edf.py +21 -2
- qlsdk/persist/rsc_edf.py +187 -124
- qlsdk/persist/stream.py +161 -0
- qlsdk/rsc/__init__.py +4 -2
- qlsdk/rsc/command/__init__.py +177 -61
- qlsdk/rsc/command/message.py +171 -74
- qlsdk/rsc/device/__init__.py +2 -0
- qlsdk/rsc/device/arskindling.py +384 -0
- qlsdk/rsc/device/base.py +413 -0
- qlsdk/rsc/device/c256_rs.py +364 -0
- qlsdk/rsc/device/c64_rs.py +358 -0
- qlsdk/rsc/device/c64s1.py +364 -0
- qlsdk/rsc/device/device_factory.py +33 -0
- qlsdk/rsc/entity.py +63 -24
- qlsdk/rsc/interface/__init__.py +3 -0
- qlsdk/rsc/interface/command.py +10 -0
- qlsdk/rsc/interface/device.py +114 -0
- qlsdk/rsc/interface/handler.py +9 -0
- qlsdk/rsc/interface/parser.py +12 -0
- qlsdk/rsc/manager/__init__.py +2 -0
- qlsdk/rsc/manager/container.py +126 -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 +1 -1
- qlsdk/rsc/parser/__init__.py +1 -0
- qlsdk/rsc/parser/base.py +69 -0
- qlsdk/sdk/ar4sdk.py +13 -4
- qlsdk/x8/__init__.py +4 -0
- {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/METADATA +1 -1
- qlsdk2-0.4.2.dist-info/RECORD +63 -0
- qlsdk2-0.4.0a3.dist-info/RECORD +0 -42
- {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/WHEEL +0 -0
- {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.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,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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
|
150
|
-
|
|
185
|
+
def pack_body(self):
|
|
186
|
+
return b''
|
|
187
|
+
|
|
188
|
+
|
|
151
189
|
# 设置阻抗采集参数
|
|
152
190
|
class SetImpedanceParamCommand(DeviceCommand):
|
|
153
191
|
cmd_code = 0x411
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
#
|
|
192
|
+
cmd_desc = "设置阻抗测量参数"
|
|
193
|
+
|
|
194
|
+
# 启动阻抗测量
|
|
157
195
|
class StartImpedanceCommand(DeviceCommand):
|
|
158
196
|
cmd_code = 0x412
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
#
|
|
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
|
+
|