qlsdk2 0.3.0a3__tar.gz → 0.4.0a2__tar.gz
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.
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/PKG-INFO +1 -1
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/setup.py +1 -1
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/__init__.py +1 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/__init__.py +4 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/crc/__init__.py +5 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/crc/crctools.py +95 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/device.py +25 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/entity/__init__.py +92 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/exception.py +0 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/filter/__init__.py +1 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/filter/norch.py +59 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/local.py +34 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/message/__init__.py +2 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/message/command.py +293 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/message/tcp.py +0 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/message/udp.py +96 -0
- qlsdk2-0.4.0a2/src/qlsdk/core/utils.py +68 -0
- qlsdk2-0.4.0a2/src/qlsdk/persist/__init__.py +2 -0
- qlsdk2-0.4.0a2/src/qlsdk/persist/rsc_edf.py +236 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/__init__.py +7 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/command/__init__.py +214 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/command/message.py +239 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/device_manager.py +117 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/discover.py +87 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/eegion.py +360 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/entity.py +551 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/paradigm.py +312 -0
- qlsdk2-0.4.0a2/src/qlsdk/rsc/proxy.py +76 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk2.egg-info/PKG-INFO +1 -1
- qlsdk2-0.4.0a2/src/qlsdk2.egg-info/SOURCES.txt +46 -0
- qlsdk2-0.4.0a2/test/test.py +168 -0
- qlsdk2-0.3.0a3/src/qlsdk/persist/__init__.py +0 -1
- qlsdk2-0.3.0a3/src/qlsdk2.egg-info/SOURCES.txt +0 -21
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/README.md +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/setup.cfg +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/ar4/__init__.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/ar4m/__init__.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/persist/edf.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/sdk/__init__.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/sdk/ar4sdk.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/sdk/hub.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/x8/__init__.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk/x8m/__init__.py +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk2.egg-info/requires.txt +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/src/qlsdk2.egg-info/top_level.txt +0 -0
- {qlsdk2-0.3.0a3 → qlsdk2-0.4.0a2}/test/test_ar4m.py +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# len=256
|
|
2
|
+
CRC_HI = [
|
|
3
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
4
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
5
|
+
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
6
|
+
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
7
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
8
|
+
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
|
9
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
10
|
+
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
11
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
12
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
|
|
13
|
+
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
14
|
+
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
15
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
16
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
|
|
17
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
18
|
+
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
19
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
20
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
21
|
+
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
22
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
23
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
24
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
|
|
25
|
+
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
26
|
+
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
27
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
28
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# len=256
|
|
32
|
+
CRC_LO = [
|
|
33
|
+
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
|
|
34
|
+
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
|
|
35
|
+
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
|
36
|
+
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
|
|
37
|
+
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
|
|
38
|
+
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
|
39
|
+
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
|
|
40
|
+
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
|
|
41
|
+
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
|
42
|
+
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
|
|
43
|
+
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
|
|
44
|
+
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
|
45
|
+
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
|
|
46
|
+
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
|
|
47
|
+
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
|
48
|
+
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
|
|
49
|
+
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
|
|
50
|
+
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
|
51
|
+
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
|
|
52
|
+
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
|
|
53
|
+
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
|
54
|
+
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
|
|
55
|
+
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
|
|
56
|
+
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
|
57
|
+
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
|
|
58
|
+
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def crc16(data):
|
|
62
|
+
crcHi = 0xFF
|
|
63
|
+
crcLo = 0xFF
|
|
64
|
+
for i in range(len(data)):
|
|
65
|
+
index = crcHi ^ data[i]
|
|
66
|
+
crcHi = crcLo ^ CRC_HI[index]
|
|
67
|
+
crcLo = CRC_LO[index]
|
|
68
|
+
|
|
69
|
+
return crcHi << 8 | crcLo
|
|
70
|
+
|
|
71
|
+
class CRCEnum(object):
|
|
72
|
+
CRC8 = 8
|
|
73
|
+
CRC16 = 16
|
|
74
|
+
CRC32 = 32
|
|
75
|
+
|
|
76
|
+
class CRC(object):
|
|
77
|
+
def __init__(self, width=CRCEnum.CRC16) :
|
|
78
|
+
self.width = width
|
|
79
|
+
self.crcHi = 0xFF
|
|
80
|
+
self.crcLo = 0xFF
|
|
81
|
+
|
|
82
|
+
def _init(self):
|
|
83
|
+
if self.width == CRCEnum.CRC16:
|
|
84
|
+
self.crcHi = 0xFF
|
|
85
|
+
self.crcLo = 0xFF
|
|
86
|
+
|
|
87
|
+
def calc(self, data):
|
|
88
|
+
for i in range(len(data)):
|
|
89
|
+
index = self.crcHi ^ data[i]
|
|
90
|
+
self.crcHi = self.crcLo ^ CRC_HI[index]
|
|
91
|
+
self.crcLo = CRC_LO[index]
|
|
92
|
+
|
|
93
|
+
def checksum(self):
|
|
94
|
+
return self.crcHi << 8 | self.crcLo
|
|
95
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
class BaseDevice(object):
|
|
4
|
+
def __init__(self, socket = None):
|
|
5
|
+
self.socket = socket
|
|
6
|
+
self.device_name = None
|
|
7
|
+
self.device_type = None
|
|
8
|
+
self.device_id = None
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def acq_channels(self) :
|
|
12
|
+
return None
|
|
13
|
+
@property
|
|
14
|
+
def sample_range(self) -> int:
|
|
15
|
+
return None
|
|
16
|
+
@property
|
|
17
|
+
def sample_rate(self) -> int:
|
|
18
|
+
return None
|
|
19
|
+
@property
|
|
20
|
+
def sample_num(self) -> int:
|
|
21
|
+
return 10
|
|
22
|
+
@property
|
|
23
|
+
def resolution(self):
|
|
24
|
+
return 24
|
|
25
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from qlsdk.core.utils import to_channels
|
|
2
|
+
from loguru import logger
|
|
3
|
+
|
|
4
|
+
class DataPacket(object):
|
|
5
|
+
def __init__(self, device_type, device_id, channels, data):
|
|
6
|
+
self.data = data
|
|
7
|
+
self.channels = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RscPacket(object):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.time_stamp = None
|
|
13
|
+
self.pkg_id = None
|
|
14
|
+
self.result = None
|
|
15
|
+
self.channels = None
|
|
16
|
+
self.origin_sample_rate = None
|
|
17
|
+
self.sample_rate = None
|
|
18
|
+
self.sample_num = None
|
|
19
|
+
self.resolution = None
|
|
20
|
+
self.filter = None
|
|
21
|
+
self.data_len = None
|
|
22
|
+
self.trigger = None
|
|
23
|
+
self.eeg = None
|
|
24
|
+
|
|
25
|
+
def transfer(self, body: bytes) -> 'RscPacket':
|
|
26
|
+
self.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
27
|
+
self.result = body[8]
|
|
28
|
+
self.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
29
|
+
self.channels = to_channels(body[13: 45])
|
|
30
|
+
self.origin_sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
31
|
+
self.sample_rate = int.from_bytes(body[49: 53], 'little')
|
|
32
|
+
self.sample_num = int.from_bytes(body[53: 57], 'little')
|
|
33
|
+
self.resolution = int(int(body[57]) / 8)
|
|
34
|
+
self.filter = body[58]
|
|
35
|
+
self.data_len = int.from_bytes(body[59: 63], 'little')
|
|
36
|
+
# 步径 相同通道的点间隔
|
|
37
|
+
step = int(len(self.channels) * self.resolution + 4)
|
|
38
|
+
self.trigger = [int.from_bytes(body[i:i+4], 'little') for i in range(63, len(body) - 3, step)]
|
|
39
|
+
b_eeg = body[63:]
|
|
40
|
+
ch_num = len(self.channels)
|
|
41
|
+
self.eeg = [
|
|
42
|
+
[
|
|
43
|
+
int.from_bytes(b_eeg[i * self.resolution + 4 + j * step:i * self.resolution + 4 + j * step + 3], 'big', signed=True)
|
|
44
|
+
for j in range(self.sample_num)
|
|
45
|
+
]
|
|
46
|
+
for i in range(ch_num)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
logger.trace(self)
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
return f"""
|
|
54
|
+
time_stamp: {self.time_stamp}
|
|
55
|
+
pkg_id: {self.pkg_id}
|
|
56
|
+
origin_sample_rate: {self.origin_sample_rate}
|
|
57
|
+
sample_rate: {self.sample_rate}
|
|
58
|
+
sample_num: {self.sample_num}
|
|
59
|
+
resolution: {self.resolution}
|
|
60
|
+
filter: {self.filter}
|
|
61
|
+
channels: {self.channels}
|
|
62
|
+
data len: {self.data_len}
|
|
63
|
+
trigger: {self.trigger}
|
|
64
|
+
eeg: {self.eeg}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
class ImpedancePacket(object):
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.time_stamp = None
|
|
70
|
+
self.pkg_id = None
|
|
71
|
+
self.result = None
|
|
72
|
+
self.channels = None
|
|
73
|
+
self.data_len = None
|
|
74
|
+
self.impedance = None
|
|
75
|
+
|
|
76
|
+
def transfer(self, body:bytes) -> 'ImpedancePacket':
|
|
77
|
+
self.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
78
|
+
self.result = body[8]
|
|
79
|
+
self.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
80
|
+
self.channels = to_channels(body[13: 45])
|
|
81
|
+
|
|
82
|
+
logger.debug(f"impedance: {self}")
|
|
83
|
+
|
|
84
|
+
def __str__(self):
|
|
85
|
+
return f"""
|
|
86
|
+
time_stamp: {self.time_stamp}
|
|
87
|
+
pkg_id: {self.pkg_id}
|
|
88
|
+
result: {self.result}
|
|
89
|
+
channels: {self.channels}
|
|
90
|
+
data len: {self.data_len}
|
|
91
|
+
impedance: {self.impedance}
|
|
92
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .norch import notch_filter_50hz
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def notch_filter_50hz(data: np.ndarray,
|
|
4
|
+
fs: float,
|
|
5
|
+
notch_width: float = 2.0,
|
|
6
|
+
max_harmonics: int = 5) -> np.ndarray:
|
|
7
|
+
"""
|
|
8
|
+
多通道50Hz谐波陷波滤波器
|
|
9
|
+
|
|
10
|
+
参数:
|
|
11
|
+
data : 输入信号,形状为 [通道数, 采样点数] 的二维数组
|
|
12
|
+
fs : 采样频率 (Hz)
|
|
13
|
+
notch_width : 陷波带宽 (Hz),默认2Hz
|
|
14
|
+
max_harmonics : 最大谐波次数,默认处理前10次谐波
|
|
15
|
+
|
|
16
|
+
返回:
|
|
17
|
+
滤波后的信号,形状与输入相同
|
|
18
|
+
"""
|
|
19
|
+
# 输入校验
|
|
20
|
+
if data.ndim != 2:
|
|
21
|
+
raise ValueError("输入必须为二维数组 [channels, samples]")
|
|
22
|
+
if fs <= 0:
|
|
23
|
+
raise ValueError("采样频率必须为正数")
|
|
24
|
+
|
|
25
|
+
n_channels, n_samples = data.shape
|
|
26
|
+
nyquist = fs / 2
|
|
27
|
+
processed = np.empty_like(data)
|
|
28
|
+
|
|
29
|
+
# 生成频率轴
|
|
30
|
+
freqs = np.fft.fftfreq(n_samples, 1/fs)
|
|
31
|
+
|
|
32
|
+
for ch in range(n_channels):
|
|
33
|
+
# FFT变换
|
|
34
|
+
fft_data = np.fft.fft(data[ch])
|
|
35
|
+
|
|
36
|
+
# 生成陷波掩模
|
|
37
|
+
mask = np.ones(n_samples, dtype=bool)
|
|
38
|
+
|
|
39
|
+
# 计算需要消除的谐波
|
|
40
|
+
for k in range(1, max_harmonics+1):
|
|
41
|
+
target_freq = 50 * k
|
|
42
|
+
|
|
43
|
+
# 超过奈奎斯特频率则停止
|
|
44
|
+
if target_freq > nyquist:
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
# 生成陷波范围
|
|
48
|
+
notch_range = (np.abs(freqs - target_freq) <= notch_width/2) | \
|
|
49
|
+
(np.abs(freqs + target_freq) <= notch_width/2)
|
|
50
|
+
|
|
51
|
+
mask &= ~notch_range
|
|
52
|
+
|
|
53
|
+
# 应用频域滤波
|
|
54
|
+
filtered_fft = fft_data * mask
|
|
55
|
+
|
|
56
|
+
# 逆变换并取实数部分
|
|
57
|
+
processed[ch] = np.real(np.fft.ifft(filtered_fft))
|
|
58
|
+
|
|
59
|
+
return processed
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
from time import time
|
|
4
|
+
|
|
5
|
+
# 读取本机全部ip列表
|
|
6
|
+
# return list
|
|
7
|
+
def get_ips():
|
|
8
|
+
return socket.gethostbyname_ex(socket.gethostname())[-1]
|
|
9
|
+
|
|
10
|
+
# 读取本机的ip地址
|
|
11
|
+
# return str
|
|
12
|
+
def get_ip():
|
|
13
|
+
# 优先读取活跃ip地址
|
|
14
|
+
routes = os.popen('route print').readlines()
|
|
15
|
+
for idx, item in enumerate(routes):
|
|
16
|
+
if ' 0.0.0.0 ' in item and len(item.split()) > 2:
|
|
17
|
+
return item.split()[-2]
|
|
18
|
+
|
|
19
|
+
# 取第一个地址
|
|
20
|
+
ips = get_ips()
|
|
21
|
+
if len(ips) > 0 :
|
|
22
|
+
return ips[0]
|
|
23
|
+
|
|
24
|
+
raise ValueError("Ip address not exists.")
|
|
25
|
+
|
|
26
|
+
def get_cache(fname=None):
|
|
27
|
+
if fname is None:
|
|
28
|
+
fname = int(time())
|
|
29
|
+
|
|
30
|
+
cpath = os.path.abspath(os.path.abspath(__file__))
|
|
31
|
+
print(cpath)
|
|
32
|
+
|
|
33
|
+
if __name__ == '__main__':
|
|
34
|
+
get_cache()
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Dict, Type
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from qlsdk.core.crc import crc16
|
|
6
|
+
from qlsdk.core.device import BaseDevice
|
|
7
|
+
from qlsdk.core.entity import RscPacket, ImpedancePacket
|
|
8
|
+
from qlsdk.core.utils import to_channels, to_bytes
|
|
9
|
+
|
|
10
|
+
class DeviceCommand(abc.ABC):
|
|
11
|
+
# 消息头
|
|
12
|
+
HEADER_PREFIX = b'\x5A\xA5'
|
|
13
|
+
# 消息头总长度 2(prefix) +1(pkgType) +1(deviceType) +4(deviceId) +4(len) +2(cmd)
|
|
14
|
+
HEADER_LEN = 14
|
|
15
|
+
# 消息指令码位置
|
|
16
|
+
CMD_POS = 12
|
|
17
|
+
|
|
18
|
+
def __init__(self, device: BaseDevice):
|
|
19
|
+
self.device = device
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def build(cls, device) :
|
|
23
|
+
return cls(device)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def cmd_code(self) -> int:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def checksum(data: bytes) -> bytes:
|
|
32
|
+
return crc16(data).to_bytes(2, 'little')
|
|
33
|
+
|
|
34
|
+
def pack(self, body=b'') -> bytes:
|
|
35
|
+
# header+body+checksum
|
|
36
|
+
body = self.pack_body()
|
|
37
|
+
header = self.pack_header(len(body))
|
|
38
|
+
payload = header + body
|
|
39
|
+
return payload + DeviceCommand.checksum(payload)
|
|
40
|
+
def pack_body(self) -> bytes:
|
|
41
|
+
"""构建消息体"""
|
|
42
|
+
return b''
|
|
43
|
+
def pack_header(self, body_len: int) -> bytes:
|
|
44
|
+
device_id = int(self.device.device_id) if self.device and self.device.device_id else 0
|
|
45
|
+
device_type = int(self.device.device_type) if self.device and self.device.device_type else 0
|
|
46
|
+
|
|
47
|
+
"""构建消息头"""
|
|
48
|
+
return (
|
|
49
|
+
DeviceCommand.HEADER_PREFIX
|
|
50
|
+
+ int(2).to_bytes(1, 'little') # pkgType
|
|
51
|
+
+ device_type.to_bytes(1, 'little')
|
|
52
|
+
+ device_id.to_bytes(4, 'little')
|
|
53
|
+
+ (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
|
|
54
|
+
+ self.cmd_code.to_bytes(2, 'little')
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def unpack(self, payload: bytes) -> bytes:
|
|
58
|
+
"""解析消息体"""
|
|
59
|
+
# 解析消息体
|
|
60
|
+
body = payload[self.HEADER_LEN:-2]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CommandFactory:
|
|
64
|
+
"""Registry for command implementations"""
|
|
65
|
+
_commands: Dict[int, Type[DeviceCommand]] = {}
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def register_command(cls, code: int, command: Type[DeviceCommand]):
|
|
69
|
+
cls._commands[code] = command
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def create_command(cls, code: int) -> Type[DeviceCommand]:
|
|
73
|
+
logger.debug(f"Creating command for code: {hex(code)}")
|
|
74
|
+
if code not in cls._commands:
|
|
75
|
+
logger.warning(f"Unsupported command code: {hex(code)}")
|
|
76
|
+
return cls._commands[DefaultCommand.cmd_code]
|
|
77
|
+
return cls._commands[code]
|
|
78
|
+
|
|
79
|
+
# =============================================================================
|
|
80
|
+
class DefaultCommand(DeviceCommand):
|
|
81
|
+
cmd_code = 0x00
|
|
82
|
+
|
|
83
|
+
def parse_body(self, body: bytes):
|
|
84
|
+
# Response parsing example: 2 bytes version + 4 bytes serial
|
|
85
|
+
logger.info(f"Received body len: {len(body)}")
|
|
86
|
+
|
|
87
|
+
class GetDeviceInfoCommand(DeviceCommand):
|
|
88
|
+
cmd_code = 0x17
|
|
89
|
+
|
|
90
|
+
def parse_body(self, body: bytes):
|
|
91
|
+
logger.info(f"Received GetDeviceInfoCommand body len: {len(body)}")
|
|
92
|
+
# time - 8B
|
|
93
|
+
self.device.connect_time = int.from_bytes(body[0:8], 'little')
|
|
94
|
+
self.device.current_time = self.device.connect_time
|
|
95
|
+
# result - 1B
|
|
96
|
+
result = body[8]
|
|
97
|
+
# deviceId - 4B
|
|
98
|
+
self.device.device_id = int.from_bytes(body[9:13], 'big')
|
|
99
|
+
# deviceType - 4B
|
|
100
|
+
self.device.device_type = int.from_bytes(body[13:17], 'little')
|
|
101
|
+
# softVersion - 4B
|
|
102
|
+
self.device.software_version = body[17:21].hex()
|
|
103
|
+
# hardVersion - 4B
|
|
104
|
+
self.device.hardware_version = body[21:25].hex()
|
|
105
|
+
# deviceName - 16B
|
|
106
|
+
self.device.device_name = body[25:41].decode('utf-8').rstrip('\x00')
|
|
107
|
+
# flag - 4B
|
|
108
|
+
flag = int.from_bytes(body[41:45], 'little')
|
|
109
|
+
logger.debug(f"Received device info: {result}, {flag}, {self.device}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# 握手
|
|
113
|
+
class HandshakeCommand(DeviceCommand):
|
|
114
|
+
cmd_code = 0x01
|
|
115
|
+
|
|
116
|
+
def parse_body(self, body: bytes):
|
|
117
|
+
logger.info(f"Received handshake response: {body.hex()}")
|
|
118
|
+
|
|
119
|
+
# 查询电量
|
|
120
|
+
class QueryBatteryCommand(DeviceCommand):
|
|
121
|
+
cmd_code = 0x16
|
|
122
|
+
def parse_body(self, body: bytes):
|
|
123
|
+
logger.info(f"Received QueryBatteryCommand body len: {len(body)}")
|
|
124
|
+
# time - 8b
|
|
125
|
+
self.device.current_time = int.from_bytes(body[0:8], 'little')
|
|
126
|
+
# result - 1b
|
|
127
|
+
result = body[8]
|
|
128
|
+
# 更新设备信息
|
|
129
|
+
if result == 0:
|
|
130
|
+
# voltage - 2b mV
|
|
131
|
+
self.device.voltage = int.from_bytes(body[9:11], 'little')
|
|
132
|
+
# soc - 1b
|
|
133
|
+
self.device.battery_remain = body[11]
|
|
134
|
+
# soh - 1b
|
|
135
|
+
self.device.battery_total = body[12]
|
|
136
|
+
# state - 1b
|
|
137
|
+
# state = body[13]
|
|
138
|
+
else:
|
|
139
|
+
logger.warning(f"QueryBatteryCommand message received but result is failed.")
|
|
140
|
+
|
|
141
|
+
# 设置采集参数
|
|
142
|
+
class SetAcquisitionParamCommand(DeviceCommand):
|
|
143
|
+
cmd_code = 0x451
|
|
144
|
+
def pack_body(self):
|
|
145
|
+
body = to_bytes(self.device.acq_channels)
|
|
146
|
+
body += self.device.sample_range.to_bytes(4, byteorder='little')
|
|
147
|
+
body += self.device.sample_rate.to_bytes(4, byteorder='little')
|
|
148
|
+
body += self.device.sample_num.to_bytes(4, byteorder='little')
|
|
149
|
+
body += self.device.resolution.to_bytes(1, byteorder='little')
|
|
150
|
+
body += bytes.fromhex('00')
|
|
151
|
+
|
|
152
|
+
return body
|
|
153
|
+
def parse_body(self, body: bytes):
|
|
154
|
+
logger.info(f"Received SetAcquisitionParam response: {body.hex()}")
|
|
155
|
+
|
|
156
|
+
# 启动采集
|
|
157
|
+
class StartAcquisitionCommand(DeviceCommand):
|
|
158
|
+
cmd_code = 0x452
|
|
159
|
+
|
|
160
|
+
def pack_body(self):
|
|
161
|
+
return bytes.fromhex('0000')
|
|
162
|
+
def parse_body(self, body: bytes):
|
|
163
|
+
logger.info(f"Received acquisition start response: {body.hex()}")
|
|
164
|
+
|
|
165
|
+
# 停止采集
|
|
166
|
+
class StopAcquisitionCommand(DeviceCommand):
|
|
167
|
+
cmd_code = 0x453
|
|
168
|
+
|
|
169
|
+
def pack_body(self):
|
|
170
|
+
return b''
|
|
171
|
+
def parse_body(self, body: bytes):
|
|
172
|
+
logger.info(f"Received acquisition stop response: {body.hex()}")
|
|
173
|
+
# 设置阻抗采集参数
|
|
174
|
+
class SetImpedanceParamCommand(DeviceCommand):
|
|
175
|
+
cmd_code = 0x411
|
|
176
|
+
def parse_body(self, body: bytes):
|
|
177
|
+
logger.info(f"Received SetImpedanceParamCommand response: {body.hex()}")
|
|
178
|
+
# 启动采集
|
|
179
|
+
class StartImpedanceCommand(DeviceCommand):
|
|
180
|
+
cmd_code = 0x412
|
|
181
|
+
def pack_body(self):
|
|
182
|
+
body = bytes.fromhex('0000')
|
|
183
|
+
body += to_bytes(self.device.acq_channels)
|
|
184
|
+
body += bytes.fromhex('0000000000000000') # 8字节占位符
|
|
185
|
+
return body
|
|
186
|
+
|
|
187
|
+
def parse_body(self, body: bytes):
|
|
188
|
+
logger.info(f"Received StartImpedanceCommand response: {body.hex()}")
|
|
189
|
+
|
|
190
|
+
# 停止采集
|
|
191
|
+
class StopImpedanceCommand(DeviceCommand):
|
|
192
|
+
cmd_code = 0x413
|
|
193
|
+
|
|
194
|
+
def pack_body(self):
|
|
195
|
+
return b''
|
|
196
|
+
|
|
197
|
+
def parse_body(self, body: bytes):
|
|
198
|
+
logger.info(f"Received StopImpedanceCommand response: {body.hex()}")
|
|
199
|
+
|
|
200
|
+
# 设置采集参数
|
|
201
|
+
class SetStimulationParamCommand(DeviceCommand):
|
|
202
|
+
cmd_code = 0x451
|
|
203
|
+
def pack_body(self):
|
|
204
|
+
body = to_bytes(self.device.acq_channels)
|
|
205
|
+
body += self.device.sample_range.to_bytes(4, byteorder='little')
|
|
206
|
+
body += self.device.sample_rate.to_bytes(4, byteorder='little')
|
|
207
|
+
body += self.device.sample_num.to_bytes(4, byteorder='little')
|
|
208
|
+
body += self.device.resolution.to_bytes(1, byteorder='little')
|
|
209
|
+
body += bytes.fromhex('00')
|
|
210
|
+
|
|
211
|
+
return body
|
|
212
|
+
def parse_body(self, body: bytes):
|
|
213
|
+
logger.info(f"Received SetAcquisitionParam response: {body.hex()}")
|
|
214
|
+
|
|
215
|
+
# 启动采集
|
|
216
|
+
class StartStimulationCommand(DeviceCommand):
|
|
217
|
+
cmd_code = 0x48C
|
|
218
|
+
def pack_body(self):
|
|
219
|
+
return self.device.stim_paradigm.to_bytes()
|
|
220
|
+
# return bytes.fromhex('01000000000000008813000000000000010000000000000000000140420f00640064000000803f0000010000000000000000000000000000000000000000008813000000000000')
|
|
221
|
+
def parse_body(self, body: bytes):
|
|
222
|
+
logger.info(f"Received stimulation start response: {body.hex()}")
|
|
223
|
+
# time - 8B
|
|
224
|
+
time = int.from_bytes(body[0:8], 'little')
|
|
225
|
+
# result - 1B
|
|
226
|
+
result = body[8]
|
|
227
|
+
# error_channel - 8B
|
|
228
|
+
# error_channel= int.from_bytes(body[9:17], 'big')
|
|
229
|
+
channels = to_channels(body[9:17])
|
|
230
|
+
logger.warning(f"通道 {channels} 刺激驱动不足")
|
|
231
|
+
# error_type - 1B
|
|
232
|
+
error_type = body[17]
|
|
233
|
+
|
|
234
|
+
# 停止采集
|
|
235
|
+
class StopStimulationCommand(DeviceCommand):
|
|
236
|
+
cmd_code = 0x488
|
|
237
|
+
|
|
238
|
+
def parse_body(self, body: bytes):
|
|
239
|
+
logger.info(f"Received stimulation stop response: {body.hex()}")
|
|
240
|
+
|
|
241
|
+
# 停止采集
|
|
242
|
+
class StimulationInfoCommand(DeviceCommand):
|
|
243
|
+
cmd_code = 0x48e
|
|
244
|
+
|
|
245
|
+
def parse_body(self, body: bytes):
|
|
246
|
+
logger.info(f"Received stimulation info response: {body.hex()}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# 阻抗数据
|
|
250
|
+
class ImpedanceDataCommand(DeviceCommand):
|
|
251
|
+
cmd_code = 0x415
|
|
252
|
+
|
|
253
|
+
def parse_body(self, body: bytes):
|
|
254
|
+
logger.info(f"Received impedance data: {body.hex()}")
|
|
255
|
+
packet = ImpedancePacket().transfer(body)
|
|
256
|
+
|
|
257
|
+
# 信号数据
|
|
258
|
+
class SignalDataCommand(DeviceCommand):
|
|
259
|
+
cmd_code = 0x455
|
|
260
|
+
|
|
261
|
+
def unpack(self, payload):
|
|
262
|
+
return super().unpack(payload)
|
|
263
|
+
|
|
264
|
+
def parse_body(self, body: bytes):
|
|
265
|
+
# logger.info(f"Received signal data: {len(body)}字节, the subscribe is {self.device.signal_consumers}")
|
|
266
|
+
if len(self.device.signal_consumers) > 0:
|
|
267
|
+
# 解析数据包
|
|
268
|
+
rsc = RscPacket()
|
|
269
|
+
rsc.transfer(body)
|
|
270
|
+
# 发送数据包到订阅者
|
|
271
|
+
for q in list(self.device.signal_consumers.values()):
|
|
272
|
+
q.put(rsc)
|
|
273
|
+
|
|
274
|
+
# =============================================================================
|
|
275
|
+
# Command Registration
|
|
276
|
+
# =============================================================================
|
|
277
|
+
|
|
278
|
+
CommandFactory.register_command(DefaultCommand.cmd_code, DefaultCommand)
|
|
279
|
+
CommandFactory.register_command(GetDeviceInfoCommand.cmd_code, GetDeviceInfoCommand)
|
|
280
|
+
CommandFactory.register_command(HandshakeCommand.cmd_code, HandshakeCommand)
|
|
281
|
+
CommandFactory.register_command(QueryBatteryCommand.cmd_code, QueryBatteryCommand)
|
|
282
|
+
CommandFactory.register_command(SetAcquisitionParamCommand.cmd_code, SetAcquisitionParamCommand)
|
|
283
|
+
CommandFactory.register_command(StartAcquisitionCommand.cmd_code, StartAcquisitionCommand)
|
|
284
|
+
CommandFactory.register_command(StopAcquisitionCommand.cmd_code, StopAcquisitionCommand)
|
|
285
|
+
CommandFactory.register_command(SetImpedanceParamCommand.cmd_code, SetImpedanceParamCommand)
|
|
286
|
+
CommandFactory.register_command(StartImpedanceCommand.cmd_code, StartImpedanceCommand)
|
|
287
|
+
CommandFactory.register_command(StopImpedanceCommand.cmd_code, StopImpedanceCommand)
|
|
288
|
+
CommandFactory.register_command(StartStimulationCommand.cmd_code, StartStimulationCommand)
|
|
289
|
+
CommandFactory.register_command(StimulationInfoCommand.cmd_code, StimulationInfoCommand)
|
|
290
|
+
CommandFactory.register_command(ImpedanceDataCommand.cmd_code, ImpedanceDataCommand)
|
|
291
|
+
CommandFactory.register_command(SignalDataCommand.cmd_code, SignalDataCommand)
|
|
292
|
+
|
|
293
|
+
|
|
File without changes
|