qlsdk2 0.3.0a3__py3-none-any.whl → 0.4.0a1__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.
@@ -0,0 +1,96 @@
1
+
2
+ from enum import Enum
3
+ from time import timezone
4
+ from qlsdk.core.crc import check_crc, crc16
5
+ from qlsdk.core.local import get_ip
6
+
7
+ from loguru import logger
8
+
9
+
10
+ class UDPCommand(Enum):
11
+ CONNECT = 0x10
12
+ NOTIFY = 0x09
13
+
14
+
15
+ class UDPMessage(object):
16
+ START = "SHQuanLan"
17
+ MAC = 'mac'
18
+ DEV_TYPE = "dev_type"
19
+ VERSION_CODE = "version_code"
20
+ VERSION_NAME = "version_name"
21
+
22
+ def __init__(self) -> None:
23
+ self._base = None
24
+ self._message = None
25
+
26
+ @staticmethod
27
+ def parse(packet, address = None):
28
+ plen = len(packet)
29
+ if plen < 10:
30
+ logger.trace("message length too short.")
31
+ return
32
+
33
+ start = packet[:9].decode("utf-8")
34
+ # quanlan udp message
35
+ if start != UDPMessage.START:
36
+ return
37
+
38
+ if not check_crc(packet):
39
+ logger.warn(f"数据CRC校验失败,丢弃!")
40
+ return
41
+
42
+ # message command
43
+ cmd = int.from_bytes(packet[10:12], 'little')
44
+
45
+ return UDPMessage._parse(cmd, packet[12:])
46
+
47
+ @staticmethod
48
+ def _parse(cmd, data):
49
+ # 只解析0x09
50
+ if cmd == UDPCommand.NOTIFY.value:
51
+ return UDPMessage.parseDeviceInfo(data)
52
+ else:
53
+ logger.trace(f'不支持的消息. cmd: {hex(cmd)} dlen: {len(data)} data: {data}')
54
+ return None
55
+
56
+ @staticmethod
57
+ def parseDeviceInfo(data):
58
+ device_info = {}
59
+ try:
60
+ device_info[UDPMessage.DEV_TYPE] = hex(int.from_bytes(data[:2], 'little'))
61
+ device_info[UDPMessage.MAC] = hex(int.from_bytes(data[2:10], 'little'))
62
+ device_info[UDPMessage.VERSION_CODE] = int.from_bytes(data[42:46], 'little')
63
+ device_info[UDPMessage.VERSION_NAME] = str(data[10:42],'utf-8').split('\x00')[0]
64
+ except Exception as e:
65
+ logger.error(f"parseDeviceInfo异常:{e}")
66
+
67
+ return device_info
68
+
69
+ @staticmethod
70
+ def search(device_id : str, server_ip : str=None, server_port : int=19216):
71
+ # 服务端Ip
72
+ if server_ip is None:
73
+ server_ip = get_ip()
74
+
75
+ logger.debug(f"search device {device_id} on {server_ip}:{server_port}")
76
+
77
+ message = bytearray(28)
78
+ # 消息头
79
+ message[:10] = UDPMessage.START.encode('utf-8')
80
+ # 消息类型
81
+ message[10:12] = UDPCommand.CONNECT.value.to_bytes(2, 'little')
82
+ # 设备序列号格式沿用现有协议
83
+ serial_no = f'F0{device_id[:2]}FFFFFFFF{device_id[4:]}'
84
+ message[12:22] = bytes.fromhex(serial_no)
85
+ # 本机ip
86
+ ip = server_ip.split(".")
87
+ message[22] = (int)(ip[0])
88
+ message[23] = (int)(ip[1])
89
+ message[24] = (int)(ip[2])
90
+ message[25] = (int)(ip[3])
91
+ # 服务端端口(按大端输出)
92
+ message[26:28] = server_port.to_bytes(2, 'big')
93
+ # 校验和(按小端输出)
94
+ checksum = crc16(message).to_bytes(2, 'little')
95
+
96
+ return message + checksum
qlsdk/core/utils.py ADDED
@@ -0,0 +1,68 @@
1
+ from bitarray import bitarray
2
+ from loguru import logger
3
+ # 通道数组[1,2,3,...]转换为bytes
4
+ def to_bytes(channels: list[int], upper=256) -> bytes:
5
+ byte_len = int(upper / 8)
6
+ channel = [0] * byte_len
7
+ result = bitarray(upper)
8
+ result.setall(0)
9
+ for i in range(len(channels)):
10
+ if channels[i] > 0 and channels[i] <= upper:
11
+ # 每个字节从低位开始计数
12
+ m = (channels[i] - 1) % 8
13
+ result[channels[i] + 6 - 2 * m] = 1
14
+ return result.tobytes()
15
+
16
+ # 通道bytes转换为数组[1,2,3,...]
17
+ def to_channels(data: bytes) -> list[int]:
18
+ ba = bitarray()
19
+ ba.frombytes(data)
20
+ channels = []
21
+ for i in range(len(ba)):
22
+ if ba[i] == 1:
23
+ m = i % 8
24
+ channels.append(i + 8 - 2 * m)
25
+ return channels
26
+
27
+ def bytes_to_ints(b):
28
+ """将小端字节序的3字节bytes数组转换为int数组"""
29
+ if len(b) % 3 != 0:
30
+ raise ValueError("输入的bytes长度必须是3的倍数")
31
+ return [
32
+ b[i] | (b[i + 1] << 8) | (b[i + 2] << 16)
33
+ for i in range(0, len(b), 3)
34
+ ]
35
+
36
+ def bytes_to_int(b: bytes) -> int:
37
+ """将小端字节序的3字节bytes数组转换为int数组"""
38
+ if len(b) != 3:
39
+ raise ValueError("输入的bytes长度必须是3的倍数")
40
+ return b[0] | (b[1] << 8) | (b[2] << 16)
41
+ def bytes_to_ints2(b):
42
+ """将小端字节序的3字节bytes数组转换为int数组"""
43
+ if len(b) % 3 != 0:
44
+ raise ValueError("输入的bytes长度必须是3的倍数")
45
+ return [
46
+ b[i] | (b[i + 1] << 8) | (b[i + 2] << 16)
47
+ for i in range(0, len(b), 3)
48
+ ]
49
+
50
+ import numpy as np
51
+ if __name__ == "__main__":
52
+
53
+ channels = [1]
54
+ channels1 = [1, 2, 3, 4]
55
+ channels2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]
56
+ channels3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145,146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176,177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238,239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256]
57
+ channels4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
58
+ logger.info(to_bytes(channels).hex())
59
+ logger.info(to_bytes(channels1).hex())
60
+ logger.info(to_bytes(channels2).hex())
61
+ logger.info(to_bytes(channels3).hex())
62
+ logger.info(to_bytes(channels4).hex())
63
+
64
+ bs = 'ffffffffffffff7f000000000000000000000000000000000000000000000000'
65
+ bs1 = '8000000000000000000000000000000000000000000000000000000000000000'
66
+ bs2 = '0100000000000000000000000000000000000000000000000000000000000000'
67
+ logger.info(to_channels(bytes.fromhex(bs1)))
68
+ logger.info(to_channels(bytes.fromhex(bs2)))
qlsdk/persist/__init__.py CHANGED
@@ -1 +1,2 @@
1
- from .edf import EdfHandler
1
+ from .edf import EdfHandler
2
+ from .rsc_edf import RscEDFHandler
@@ -0,0 +1,236 @@
1
+ from datetime import datetime
2
+ from multiprocessing import Lock, Queue
3
+ from pyedflib import FILETYPE_BDFPLUS, FILETYPE_EDFPLUS, EdfWriter
4
+ from threading import Thread
5
+ from loguru import logger
6
+ import numpy as np
7
+ import os
8
+
9
+ class EDFWriterThread(Thread):
10
+ def __init__(self, edf_writer : EdfWriter):
11
+ super().__init__(self)
12
+ self._edf_writer : EdfWriter = edf_writer
13
+ self.data_queue : Queue = Queue()
14
+ self._stop_event : bool = False
15
+ self._recording = False
16
+ self._chunk = np.array([])
17
+ self._points = 0
18
+ self._duration = 0
19
+ self._sample_frequency = 0
20
+ self._total_packets = 0
21
+ self._channels = []
22
+ self._sample_rate = 0
23
+
24
+ def start(self):
25
+ self._stop_event = False
26
+ super().start()
27
+
28
+ def stop(self):
29
+ self._stop_event = True
30
+
31
+ def append(self, data):
32
+ # 数据
33
+ self.data_queue.put(data)
34
+
35
+ def _consumer(self):
36
+ logger.debug(f"开始消费数据 _consumer: {self.data_queue.qsize()}")
37
+ while True:
38
+ if self._recording or (not self.data_queue.empty()):
39
+ try:
40
+ data = self.data_queue.get(timeout=10)
41
+ if data is None:
42
+ break
43
+ # 处理数据
44
+ self._points += len(data)
45
+ self._write_file(data)
46
+ except Exception as e:
47
+ logger.error("数据队列为空,超时(10s)结束")
48
+ break
49
+ else:
50
+ break
51
+
52
+ self.close()
53
+
54
+ def _write_file(self, eeg_data):
55
+ try:
56
+ if (self._chunk.size == 0):
57
+ self._chunk = np.asarray(eeg_data)
58
+ else:
59
+ self._chunk = np.hstack((self._chunk, eeg_data))
60
+
61
+ if self._chunk.size >= self._sample_rate * self._channels:
62
+ self._write_chunk(self._chunk[:self._sample_rate])
63
+ self._chunk = self._chunk[self._sample_rate:]
64
+
65
+ except Exception as e:
66
+ logger.error(f"写入数据异常: {str(e)}")
67
+
68
+ def close(self):
69
+ self._recording = False
70
+ if self._edf_writer:
71
+ self._end_time = datetime.now().timestamp()
72
+ self._edf_writer.writeAnnotation(0, 1, "start recording ")
73
+ self._edf_writer.writeAnnotation(self._duration, 1, "recording end")
74
+ self._edf_writer.close()
75
+
76
+ logger.info(f"文件: {self.file_name}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
77
+
78
+
79
+
80
+ def _write_chunk(self, chunk):
81
+ logger.debug(f"写入数据: {chunk}")
82
+ # 转换数据类型为float64(pyedflib要求)
83
+ data_float64 = chunk.astype(np.float64)
84
+ # 写入时转置为(样本数, 通道数)格式
85
+ self._edf_writer.writeSamples(data_float64)
86
+ self._duration += 1
87
+
88
+ class RscEDFHandler(object):
89
+ '''
90
+ Rsc EDF Handler
91
+ 处理EDF文件的读写
92
+ RSC设备通道数根据选择变化,不同通道采样频率相同
93
+ sample_frequency: 采样频率
94
+ physical_max: 物理最大值
95
+ physical_min: 物理最小值
96
+ digital_max: 数字最大值
97
+ digital_min: 数字最小值
98
+ resolution: 分辨率
99
+ storage_path: 存储路径
100
+
101
+ @author: qlsdk
102
+ @since: 0.4.0
103
+ '''
104
+ def __init__(self, sample_frequency, physical_max, physical_min, digital_max, digital_min, resolution=32, storage_path = None):
105
+ # edf文件参数
106
+ self.physical_max = physical_max
107
+ self.physical_min = physical_min
108
+ self.digital_max = digital_max
109
+ self.digital_min = digital_min
110
+ # 点分辨率
111
+ self.resolution = resolution
112
+ # eeg通道数
113
+ self.eeg_channels = None
114
+ # eeg采样率
115
+ self.eeg_sample_rate = 500
116
+ self.acc_channels = None
117
+ self.acc_sample_rate = 50
118
+ # 缓存
119
+ self._cache = Queue()
120
+ # 采样频率
121
+ self.sample_frequency = sample_frequency
122
+ # bytes per second
123
+ self.bytes_per_second = 0
124
+ self._edf_writer = None
125
+ self._cache2 = tuple()
126
+ self._recording = False
127
+ self._edf_writer = None
128
+ self.annotations = None
129
+ # 每个数据块大小
130
+ self._chunk = np.array([])
131
+ self._Lock = Lock()
132
+ self._duration = 0
133
+ self._points = 0
134
+ self._first_pkg_id = None
135
+ self._last_pkg_id = None
136
+ self._first_timestamp = None
137
+ self._end_time = None
138
+ self._patient_code = "patient_code"
139
+ self._patient_name = "patient_name"
140
+ self._device_type = None
141
+ self._total_packets = 0
142
+ self._lost_packets = 0
143
+ self._storage_path = storage_path
144
+ self._edf_writer_thread = None
145
+
146
+ @property
147
+ def file_name(self):
148
+ if self._storage_path:
149
+ try:
150
+ os.makedirs(self._storage_path, exist_ok=True) # 自动创建目录,存在则忽略
151
+ return f"{self._storage_path}/{self._device_type}_{self._first_timestamp}.edf"
152
+ except Exception as e:
153
+ logger.error(f"创建目录[{self._storage_path}]失败: {e}")
154
+
155
+ return f"{self._device_type}_{self._first_timestamp}.edf"
156
+
157
+ @property
158
+ def file_type(self):
159
+ return FILETYPE_BDFPLUS if self.resolution == 24 else FILETYPE_EDFPLUS
160
+
161
+ def set_device_type(self, device_type):
162
+ self._device_type = device_type
163
+
164
+ def set_storage_path(self, storage_path):
165
+ self._storage_path = storage_path
166
+
167
+ def set_patient_code(self, patient_code):
168
+ self._patient_code = patient_code
169
+
170
+ def set_patient_name(self, patient_name):
171
+ self._patient_name = patient_name
172
+
173
+ def append(self, data, channels=None):
174
+ if self._edf_writer_thread is None:
175
+ self._edf_writer_thread = EDFWriterThread(self.init_edf_writer())
176
+ self._edf_writer_thread.start()
177
+ self._recording = True
178
+ self._edf_writer_thread._recording = True
179
+ logger.info(f"开始写入数据: {self.file_name}")
180
+
181
+ if data:
182
+ # # 通道数
183
+ # if self._first_pkg_id is None:
184
+ # self.eeg_channels = data.eeg_ch_count
185
+ # self.acc_channels = data.acc_ch_count
186
+ # self._first_pkg_id = data.pkg_id
187
+ # self._first_timestamp = data.time_stamp
188
+
189
+ # if self._last_pkg_id and self._last_pkg_id != data.pkg_id - 1:
190
+ # self._lost_packets += data.pkg_id - self._last_pkg_id - 1
191
+ # logger.warning(f"数据包丢失: {self._last_pkg_id} -> {data.pkg_id}, 丢包数: {data.pkg_id - self._last_pkg_id - 1}")
192
+
193
+ self._last_pkg_id = data.pkg_id
194
+ self._total_packets += 1
195
+
196
+ # 数据
197
+ self._cache.put(data)
198
+ self._edf_writer_thread.append(data)
199
+ # if not self._recording:
200
+ # self.start()
201
+
202
+ def trigger(self, data):
203
+ pass
204
+
205
+ def init_edf_writer(self):
206
+ # 创建EDF+写入器
207
+ edf_writer = EdfWriter(
208
+ self.file_name,
209
+ self.eeg_channels,
210
+ file_type=self.file_type
211
+ )
212
+
213
+ # 设置头信息
214
+ edf_writer.setPatientCode(self._patient_code)
215
+ edf_writer.setPatientName(self._patient_name)
216
+ edf_writer.setEquipment(self._device_type)
217
+ edf_writer.setStartdatetime(datetime.now())
218
+
219
+ # 配置通道参数
220
+ signal_headers = []
221
+ for ch in range(self.eeg_channels):
222
+ signal_headers.append({
223
+ "label": f'channels {ch + 1}',
224
+ "dimension": 'uV',
225
+ "sample_frequency": self.sample_frequency,
226
+ "physical_min": self.physical_min,
227
+ "physical_max": self.physical_max,
228
+ "digital_min": self.digital_min,
229
+ "digital_max": self.digital_max
230
+ })
231
+
232
+ edf_writer.setSignalHeaders(signal_headers)
233
+
234
+ return edf_writer
235
+
236
+
qlsdk/rsc/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .discover import *
2
+
3
+ from .entity import DeviceParser, QLDevice
4
+ from .command import *
5
+ from .device_manager import DeviceContainer
6
+ from .proxy import DeviceProxy
7
+ from .paradigm import *
@@ -0,0 +1,214 @@
1
+ import abc
2
+ from typing import Dict, Type
3
+ from enum import Enum
4
+ from loguru import logger
5
+ from qlsdk.core import crc16
6
+
7
+ class UDPCommand(Enum):
8
+ CONNECT = 0x10
9
+ NOTIFY = 0x09
10
+
11
+
12
+ class DeviceCommand(abc.ABC):
13
+ # 消息头
14
+ HEADER_PREFIX = b'\x5A\xA5'
15
+ # 消息头总长度 2(prefix) +1(pkgType) +1(deviceType) +4(deviceId) +4(len) +2(cmd)
16
+ HEADER_LEN = 14
17
+ # 消息指令码位置
18
+ CMD_POS = 12
19
+
20
+ def __init__(self, device):
21
+ self.device = device
22
+ @property
23
+ @abc.abstractmethod
24
+ def cmd_code(self) -> int:
25
+ pass
26
+
27
+ @classmethod
28
+ def pack(cls, body=b'') -> bytes:
29
+ # header+body+checksum
30
+ header = DeviceCommand.build_header(cls.cmd_code, len(body))
31
+ 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"""
37
+ return (
38
+ DeviceCommand.HEADER_PREFIX
39
+ + pkg_type.to_bytes(1, 'little')
40
+ + device_type.to_bytes(1, 'little')
41
+ + device_id.to_bytes(4, 'little')
42
+ + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
43
+ + cmd_code.to_bytes(2, 'little')
44
+ )
45
+
46
+ @staticmethod
47
+ def calculate_checksum(data: bytes) -> bytes:
48
+ return crc16(data).to_bytes(2, 'little')
49
+
50
+ @abc.abstractmethod
51
+ def parse_body(self, body: bytes):
52
+ """Implement in subclasses for command-specific parsing"""
53
+ pass
54
+
55
+ class CommandFactory:
56
+ """Registry for command implementations"""
57
+ _commands: Dict[int, Type[DeviceCommand]] = {}
58
+
59
+ @classmethod
60
+ def register_command(cls, code: int, command: Type[DeviceCommand]):
61
+ cls._commands[code] = command
62
+
63
+ @classmethod
64
+ def create_command(cls, code: int) -> Type[DeviceCommand]:
65
+ logger.debug(f"Creating command for code: {hex(code)}")
66
+ if code not in cls._commands:
67
+ logger.warning(f"Unsupported command code: {hex(code)}")
68
+ return cls._commands[DefaultCommand.cmd_code]
69
+ return cls._commands[code]
70
+
71
+ # =============================================================================
72
+ class DefaultCommand(DeviceCommand):
73
+ cmd_code = 0x00
74
+
75
+ def parse_body(self, body: bytes):
76
+ # Response parsing example: 2 bytes version + 4 bytes serial
77
+ logger.info(f"Received body len: {len(body)}")
78
+
79
+ class GetDeviceInfoCommand(DeviceCommand):
80
+ cmd_code = 0x17
81
+
82
+ def parse_body(self, body: bytes):
83
+ logger.info(f"Received GetDeviceInfoCommand body len: {len(body)}")
84
+ # time - 8b
85
+ self.device.connect_time = int.from_bytes(body[0:8], 'little')
86
+ self.device.current_time = self.device.connect_time
87
+ # result - 1b
88
+ result = body[8]
89
+ # deviceId - 4b
90
+ self.device.device_id = body[9:13].hex()
91
+ # deviceType - 4b
92
+ self.device.device_type = body[13:17].hex()
93
+ # softVersion - 4b
94
+ self.device.software_version = body[17:21].hex()
95
+ # hardVersion - 4b
96
+ self.device.hardware_version = body[21:25].hex()
97
+ # deviceName - 16b
98
+ self.device.device_name = body[25:41].decode('utf-8').rstrip('\x00')
99
+ # flag - 4b
100
+ flag = int.from_bytes(body[41:45], 'little')
101
+ logger.debug(f"Received device info: {result}, {flag}, {self.device}")
102
+
103
+
104
+ # 握手
105
+ class HandshakeCommand(DeviceCommand):
106
+ cmd_code = 0x01
107
+
108
+ def parse_body(self, body: bytes):
109
+ logger.info(f"Received handshake response: {body.hex()}")
110
+
111
+ # 查询电量
112
+ class QueryBatteryCommand(DeviceCommand):
113
+ cmd_code = 0x16
114
+ def parse_body(self, body: bytes):
115
+ logger.info(f"Received QueryBatteryCommand body len: {len(body)}")
116
+ # time - 8b
117
+ self.device.current_time = int.from_bytes(body[0:8], 'little')
118
+ # result - 1b
119
+ result = body[8]
120
+ # 更新设备信息
121
+ if result == 0:
122
+ # voltage - 2b mV
123
+ self.device.voltage = int.from_bytes(body[9:11], 'little')
124
+ # soc - 1b
125
+ self.device.battery_remain = body[11]
126
+ # soh - 1b
127
+ self.device.battery_total = body[12]
128
+ # state - 1b
129
+ # state = body[13]
130
+ else:
131
+ logger.warning(f"QueryBatteryCommand message received but result is failed.")
132
+
133
+ # 设置采集参数
134
+ class SetAcquisitionParamCommand(DeviceCommand):
135
+ cmd_code = 0x451
136
+ def parse_body(self, body: bytes):
137
+ logger.info(f"Received SetAcquisitionParam response: {body.hex()}")
138
+
139
+ # 启动采集
140
+ class StartAcquisitionCommand(DeviceCommand):
141
+ cmd_code = 0x452
142
+ def parse_body(self, body: bytes):
143
+ logger.info(f"Received acquisition start response: {body.hex()}")
144
+
145
+ # 停止采集
146
+ class StopAcquisitionCommand(DeviceCommand):
147
+ cmd_code = 0x453
148
+
149
+ def parse_body(self, body: bytes):
150
+ logger.info(f"Received acquisition stop response: {body.hex()}")
151
+ # 设置阻抗采集参数
152
+ class SetImpedanceParamCommand(DeviceCommand):
153
+ cmd_code = 0x411
154
+ def parse_body(self, body: bytes):
155
+ logger.info(f"Received SetImpedanceParamCommand response: {body.hex()}")
156
+ # 启动采集
157
+ class StartImpedanceCommand(DeviceCommand):
158
+ cmd_code = 0x412
159
+ def parse_body(self, body: bytes):
160
+ logger.info(f"Received StartImpedanceCommand response: {body.hex()}")
161
+
162
+ # 停止采集
163
+ class StopImpedanceCommand(DeviceCommand):
164
+ cmd_code = 0x413
165
+
166
+ def parse_body(self, body: bytes):
167
+ logger.info(f"Received StopImpedanceCommand response: {body.hex()}")
168
+
169
+ # 启动刺激
170
+ class StartStimulationCommand(DeviceCommand):
171
+ cmd_code = 0x48C
172
+ def parse_body(self, body: bytes):
173
+ logger.info(f"Received acquisition start response: {body.hex()}")
174
+
175
+ # 停止刺激
176
+ class StopStimulationCommand(DeviceCommand):
177
+ cmd_code = 0x488
178
+
179
+ def parse_body(self, body: bytes):
180
+ logger.info(f"Received acquisition stop response: {body.hex()}")
181
+
182
+ # 阻抗数据
183
+ class ImpedanceDataCommand(DeviceCommand):
184
+ cmd_code = 0x415
185
+
186
+ def parse_body(self, body: bytes):
187
+ logger.info(f"Received impedance data: {body.hex()}")
188
+
189
+ # 信号数据
190
+ class SignalDataCommand(DeviceCommand):
191
+ cmd_code = 0x455
192
+
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)
197
+
198
+ # =============================================================================
199
+ # Command Registration
200
+ # =============================================================================
201
+
202
+ CommandFactory.register_command(DefaultCommand.cmd_code, DefaultCommand)
203
+ CommandFactory.register_command(GetDeviceInfoCommand.cmd_code, GetDeviceInfoCommand)
204
+ CommandFactory.register_command(HandshakeCommand.cmd_code, HandshakeCommand)
205
+ CommandFactory.register_command(QueryBatteryCommand.cmd_code, QueryBatteryCommand)
206
+ CommandFactory.register_command(SetAcquisitionParamCommand.cmd_code, SetAcquisitionParamCommand)
207
+ CommandFactory.register_command(StartAcquisitionCommand.cmd_code, StartAcquisitionCommand)
208
+ CommandFactory.register_command(StopAcquisitionCommand.cmd_code, StopAcquisitionCommand)
209
+ CommandFactory.register_command(SetImpedanceParamCommand.cmd_code, SetImpedanceParamCommand)
210
+ CommandFactory.register_command(StartImpedanceCommand.cmd_code, StartImpedanceCommand)
211
+ CommandFactory.register_command(StopImpedanceCommand.cmd_code, StopImpedanceCommand)
212
+ CommandFactory.register_command(StartStimulationCommand.cmd_code, StartStimulationCommand)
213
+ CommandFactory.register_command(ImpedanceDataCommand.cmd_code, ImpedanceDataCommand)
214
+ CommandFactory.register_command(SignalDataCommand.cmd_code, SignalDataCommand)