qlsdk2 0.4.0a2__tar.gz → 0.4.0a3__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.4.0a2 → qlsdk2-0.4.0a3}/PKG-INFO +2 -2
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/setup.py +1 -1
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/__init__.py +2 -1
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/entity/__init__.py +1 -1
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/message/command.py +45 -26
- qlsdk2-0.4.0a3/src/qlsdk/core/network/__init__.py +34 -0
- qlsdk2-0.4.0a3/src/qlsdk/core/network/monitor.py +55 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/utils.py +2 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/persist/rsc_edf.py +34 -29
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/device_manager.py +2 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/entity.py +36 -159
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/paradigm.py +4 -3
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk2.egg-info/PKG-INFO +2 -2
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk2.egg-info/SOURCES.txt +2 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk2.egg-info/requires.txt +1 -1
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/README.md +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/setup.cfg +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/ar4/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/ar4m/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/crc/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/crc/crctools.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/device.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/exception.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/filter/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/filter/norch.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/local.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/message/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/message/tcp.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/core/message/udp.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/persist/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/persist/edf.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/command/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/command/message.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/discover.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/eegion.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/rsc/proxy.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/sdk/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/sdk/ar4sdk.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/sdk/hub.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/x8/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk/x8m/__init__.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/src/qlsdk2.egg-info/top_level.txt +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/test/test.py +0 -0
- {qlsdk2-0.4.0a2 → qlsdk2-0.4.0a3}/test/test_ar4m.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: qlsdk2
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.0a3
|
|
4
4
|
Summary: SDK for quanlan device
|
|
5
5
|
Home-page: https://github.com/hehuajun/qlsdk
|
|
6
6
|
Author: hehuajun
|
|
@@ -12,7 +12,7 @@ Requires-Python: >=3.9
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
Requires-Dist: loguru>=0.6.0
|
|
14
14
|
Requires-Dist: numpy>=1.23.5
|
|
15
|
-
Requires-Dist:
|
|
15
|
+
Requires-Dist: bitarray>=1.5.3
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
18
18
|
Requires-Dist: twine>=3.0; extra == "dev"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
from time import time_ns
|
|
2
3
|
from typing import Dict, Type
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from loguru import logger
|
|
@@ -26,6 +27,10 @@ class DeviceCommand(abc.ABC):
|
|
|
26
27
|
@abc.abstractmethod
|
|
27
28
|
def cmd_code(self) -> int:
|
|
28
29
|
pass
|
|
30
|
+
@property
|
|
31
|
+
@abc.abstractmethod
|
|
32
|
+
def cmd_desc(self) -> str:
|
|
33
|
+
pass
|
|
29
34
|
|
|
30
35
|
@staticmethod
|
|
31
36
|
def checksum(data: bytes) -> bytes:
|
|
@@ -70,15 +75,16 @@ class CommandFactory:
|
|
|
70
75
|
|
|
71
76
|
@classmethod
|
|
72
77
|
def create_command(cls, code: int) -> Type[DeviceCommand]:
|
|
73
|
-
logger.
|
|
78
|
+
logger.trace(f"Creating command for code: {hex(code)}")
|
|
74
79
|
if code not in cls._commands:
|
|
75
|
-
logger.warning(f"
|
|
80
|
+
logger.warning(f"不支持的设备指令: {hex(code)}")
|
|
76
81
|
return cls._commands[DefaultCommand.cmd_code]
|
|
77
82
|
return cls._commands[code]
|
|
78
83
|
|
|
79
84
|
# =============================================================================
|
|
80
85
|
class DefaultCommand(DeviceCommand):
|
|
81
86
|
cmd_code = 0x00
|
|
87
|
+
cmd_desc = "未定义"
|
|
82
88
|
|
|
83
89
|
def parse_body(self, body: bytes):
|
|
84
90
|
# Response parsing example: 2 bytes version + 4 bytes serial
|
|
@@ -86,9 +92,9 @@ class DefaultCommand(DeviceCommand):
|
|
|
86
92
|
|
|
87
93
|
class GetDeviceInfoCommand(DeviceCommand):
|
|
88
94
|
cmd_code = 0x17
|
|
95
|
+
cmd_desc = "设备信息"
|
|
89
96
|
|
|
90
97
|
def parse_body(self, body: bytes):
|
|
91
|
-
logger.info(f"Received GetDeviceInfoCommand body len: {len(body)}")
|
|
92
98
|
# time - 8B
|
|
93
99
|
self.device.connect_time = int.from_bytes(body[0:8], 'little')
|
|
94
100
|
self.device.current_time = self.device.connect_time
|
|
@@ -112,6 +118,7 @@ class GetDeviceInfoCommand(DeviceCommand):
|
|
|
112
118
|
# 握手
|
|
113
119
|
class HandshakeCommand(DeviceCommand):
|
|
114
120
|
cmd_code = 0x01
|
|
121
|
+
cmd_desc = "握手"
|
|
115
122
|
|
|
116
123
|
def parse_body(self, body: bytes):
|
|
117
124
|
logger.info(f"Received handshake response: {body.hex()}")
|
|
@@ -119,8 +126,8 @@ class HandshakeCommand(DeviceCommand):
|
|
|
119
126
|
# 查询电量
|
|
120
127
|
class QueryBatteryCommand(DeviceCommand):
|
|
121
128
|
cmd_code = 0x16
|
|
129
|
+
cmd_desc = "电量信息"
|
|
122
130
|
def parse_body(self, body: bytes):
|
|
123
|
-
logger.info(f"Received QueryBatteryCommand body len: {len(body)}")
|
|
124
131
|
# time - 8b
|
|
125
132
|
self.device.current_time = int.from_bytes(body[0:8], 'little')
|
|
126
133
|
# result - 1b
|
|
@@ -135,12 +142,15 @@ class QueryBatteryCommand(DeviceCommand):
|
|
|
135
142
|
self.device.battery_total = body[12]
|
|
136
143
|
# state - 1b
|
|
137
144
|
# state = body[13]
|
|
145
|
+
logger.debug(f"电量更新: {self.device}")
|
|
138
146
|
else:
|
|
139
147
|
logger.warning(f"QueryBatteryCommand message received but result is failed.")
|
|
140
148
|
|
|
141
149
|
# 设置采集参数
|
|
142
150
|
class SetAcquisitionParamCommand(DeviceCommand):
|
|
143
151
|
cmd_code = 0x451
|
|
152
|
+
cmd_desc = "设置信号采集参数"
|
|
153
|
+
|
|
144
154
|
def pack_body(self):
|
|
145
155
|
body = to_bytes(self.device.acq_channels)
|
|
146
156
|
body += self.device.sample_range.to_bytes(4, byteorder='little')
|
|
@@ -156,6 +166,7 @@ class SetAcquisitionParamCommand(DeviceCommand):
|
|
|
156
166
|
# 启动采集
|
|
157
167
|
class StartAcquisitionCommand(DeviceCommand):
|
|
158
168
|
cmd_code = 0x452
|
|
169
|
+
cmd_desc = "启动信号采集"
|
|
159
170
|
|
|
160
171
|
def pack_body(self):
|
|
161
172
|
return bytes.fromhex('0000')
|
|
@@ -165,19 +176,24 @@ class StartAcquisitionCommand(DeviceCommand):
|
|
|
165
176
|
# 停止采集
|
|
166
177
|
class StopAcquisitionCommand(DeviceCommand):
|
|
167
178
|
cmd_code = 0x453
|
|
179
|
+
cmd_desc = "停止信号采集"
|
|
168
180
|
|
|
169
181
|
def pack_body(self):
|
|
170
182
|
return b''
|
|
171
183
|
def parse_body(self, body: bytes):
|
|
172
184
|
logger.info(f"Received acquisition stop response: {body.hex()}")
|
|
185
|
+
|
|
173
186
|
# 设置阻抗采集参数
|
|
174
187
|
class SetImpedanceParamCommand(DeviceCommand):
|
|
175
188
|
cmd_code = 0x411
|
|
189
|
+
cmd_desc = "设置阻抗测量参数"
|
|
176
190
|
def parse_body(self, body: bytes):
|
|
177
191
|
logger.info(f"Received SetImpedanceParamCommand response: {body.hex()}")
|
|
192
|
+
|
|
178
193
|
# 启动采集
|
|
179
194
|
class StartImpedanceCommand(DeviceCommand):
|
|
180
195
|
cmd_code = 0x412
|
|
196
|
+
cmd_desc = "启动阻抗测量"
|
|
181
197
|
def pack_body(self):
|
|
182
198
|
body = bytes.fromhex('0000')
|
|
183
199
|
body += to_bytes(self.device.acq_channels)
|
|
@@ -190,36 +206,22 @@ class StartImpedanceCommand(DeviceCommand):
|
|
|
190
206
|
# 停止采集
|
|
191
207
|
class StopImpedanceCommand(DeviceCommand):
|
|
192
208
|
cmd_code = 0x413
|
|
209
|
+
cmd_desc = "停止阻抗测量"
|
|
193
210
|
|
|
194
211
|
def pack_body(self):
|
|
195
212
|
return b''
|
|
196
213
|
|
|
197
214
|
def parse_body(self, body: bytes):
|
|
198
215
|
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
216
|
|
|
215
|
-
#
|
|
217
|
+
# 启动刺激
|
|
216
218
|
class StartStimulationCommand(DeviceCommand):
|
|
217
219
|
cmd_code = 0x48C
|
|
220
|
+
cmd_desc = "启动刺激"
|
|
218
221
|
def pack_body(self):
|
|
219
222
|
return self.device.stim_paradigm.to_bytes()
|
|
220
223
|
# return bytes.fromhex('01000000000000008813000000000000010000000000000000000140420f00640064000000803f0000010000000000000000000000000000000000000000008813000000000000')
|
|
221
224
|
def parse_body(self, body: bytes):
|
|
222
|
-
logger.info(f"Received stimulation start response: {body.hex()}")
|
|
223
225
|
# time - 8B
|
|
224
226
|
time = int.from_bytes(body[0:8], 'little')
|
|
225
227
|
# result - 1B
|
|
@@ -227,13 +229,14 @@ class StartStimulationCommand(DeviceCommand):
|
|
|
227
229
|
# error_channel - 8B
|
|
228
230
|
# error_channel= int.from_bytes(body[9:17], 'big')
|
|
229
231
|
channels = to_channels(body[9:17])
|
|
230
|
-
logger.warning(f"通道 {channels}
|
|
232
|
+
logger.warning(f"通道 {channels} 刺激开始")
|
|
231
233
|
# error_type - 1B
|
|
232
234
|
error_type = body[17]
|
|
233
235
|
|
|
234
236
|
# 停止采集
|
|
235
237
|
class StopStimulationCommand(DeviceCommand):
|
|
236
238
|
cmd_code = 0x488
|
|
239
|
+
cmd_desc = "停止刺激"
|
|
237
240
|
|
|
238
241
|
def parse_body(self, body: bytes):
|
|
239
242
|
logger.info(f"Received stimulation stop response: {body.hex()}")
|
|
@@ -241,14 +244,25 @@ class StopStimulationCommand(DeviceCommand):
|
|
|
241
244
|
# 停止采集
|
|
242
245
|
class StimulationInfoCommand(DeviceCommand):
|
|
243
246
|
cmd_code = 0x48e
|
|
247
|
+
cmd_desc = "刺激告警信息"
|
|
244
248
|
|
|
245
249
|
def parse_body(self, body: bytes):
|
|
246
|
-
|
|
250
|
+
time = int.from_bytes(body[0:8], 'little')
|
|
251
|
+
# result - 1B
|
|
252
|
+
result = body[8]
|
|
253
|
+
# error_channel - 8B
|
|
254
|
+
channels = to_channels(body[9:17])
|
|
255
|
+
# 保留位-8B
|
|
256
|
+
# error_type - 1B
|
|
257
|
+
# 特征位-4B
|
|
258
|
+
characteristic = int.from_bytes(body[25:29], 'little')
|
|
259
|
+
logger.warning(f"[{characteristic}]刺激告警,通道 {channels} 刺激驱动不足")
|
|
247
260
|
|
|
248
261
|
|
|
249
262
|
# 阻抗数据
|
|
250
263
|
class ImpedanceDataCommand(DeviceCommand):
|
|
251
264
|
cmd_code = 0x415
|
|
265
|
+
cmd_desc = "阻抗数据"
|
|
252
266
|
|
|
253
267
|
def parse_body(self, body: bytes):
|
|
254
268
|
logger.info(f"Received impedance data: {body.hex()}")
|
|
@@ -257,19 +271,24 @@ class ImpedanceDataCommand(DeviceCommand):
|
|
|
257
271
|
# 信号数据
|
|
258
272
|
class SignalDataCommand(DeviceCommand):
|
|
259
273
|
cmd_code = 0x455
|
|
274
|
+
cmd_desc = "信号数据"
|
|
260
275
|
|
|
261
276
|
def unpack(self, payload):
|
|
262
277
|
return super().unpack(payload)
|
|
263
278
|
|
|
264
|
-
def parse_body(self, body: bytes):
|
|
265
|
-
|
|
266
|
-
if len(self.device.signal_consumers) > 0:
|
|
279
|
+
def parse_body(self, body: bytes):
|
|
280
|
+
if len(self.device.signal_consumers) > 0 or self.device.edf_handler:
|
|
267
281
|
# 解析数据包
|
|
268
282
|
rsc = RscPacket()
|
|
269
283
|
rsc.transfer(body)
|
|
284
|
+
|
|
270
285
|
# 发送数据包到订阅者
|
|
271
286
|
for q in list(self.device.signal_consumers.values()):
|
|
272
287
|
q.put(rsc)
|
|
288
|
+
|
|
289
|
+
# 文件写入到edf
|
|
290
|
+
if self.device.edf_handler:
|
|
291
|
+
self.device.edf_handler.append(rsc)
|
|
273
292
|
|
|
274
293
|
# =============================================================================
|
|
275
294
|
# Command Registration
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
def get_active_interfaces():
|
|
5
|
+
interfaces = {}
|
|
6
|
+
# 获取所有接口地址信息
|
|
7
|
+
all_addrs = psutil.net_if_addrs()
|
|
8
|
+
# 获取接口状态(是否处于UP状态)
|
|
9
|
+
stats = psutil.net_if_stats()
|
|
10
|
+
|
|
11
|
+
for name, addrs in all_addrs.items():
|
|
12
|
+
# 检查接口是否启用
|
|
13
|
+
if stats[name].isup:
|
|
14
|
+
ips = []
|
|
15
|
+
for addr in addrs:
|
|
16
|
+
# 提取IPv4和IPv6地址
|
|
17
|
+
if addr.family == socket.AF_INET:
|
|
18
|
+
ips.append(f"IPv4: {addr.address}")
|
|
19
|
+
elif addr.family == socket.AF_INET6:
|
|
20
|
+
ips.append(f"IPv6: {addr.address}")
|
|
21
|
+
# 过滤无IP的接口(可选)
|
|
22
|
+
if ips:
|
|
23
|
+
interfaces[name] = {
|
|
24
|
+
"status": "UP",
|
|
25
|
+
"IPs": ips
|
|
26
|
+
}
|
|
27
|
+
return interfaces
|
|
28
|
+
|
|
29
|
+
# 调用并打印结果
|
|
30
|
+
active_ifs = get_active_interfaces()
|
|
31
|
+
for iface, info in active_ifs.items():
|
|
32
|
+
print(f"接口: {iface}")
|
|
33
|
+
print(f"状态: {info['status']}")
|
|
34
|
+
print(f"IP地址: {info['IPs']}\n")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
import psutil
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
def get_active_ipv4():
|
|
6
|
+
ips = []
|
|
7
|
+
# 获取所有接口地址信息
|
|
8
|
+
all_addrs = psutil.net_if_addrs()
|
|
9
|
+
# 获取接口状态(是否处于UP状态)
|
|
10
|
+
stats = psutil.net_if_stats()
|
|
11
|
+
|
|
12
|
+
for name, addrs in all_addrs.items():
|
|
13
|
+
# 检查接口是否启用
|
|
14
|
+
if stats[name].isup:
|
|
15
|
+
for addr in addrs:
|
|
16
|
+
# 提取IPv4地址
|
|
17
|
+
if addr.family == socket.AF_INET:
|
|
18
|
+
ips.append(addr.address)
|
|
19
|
+
return ips
|
|
20
|
+
def monitor_up_interfaces(interval=2, callback=None):
|
|
21
|
+
prev_status = {iface: psutil.net_if_stats()[iface].isup
|
|
22
|
+
for iface in psutil.net_if_stats()}
|
|
23
|
+
|
|
24
|
+
while True:
|
|
25
|
+
current_stats = psutil.net_if_stats()
|
|
26
|
+
for iface, stats in current_stats.items():
|
|
27
|
+
current_up = stats.isup
|
|
28
|
+
# 检测状态变化
|
|
29
|
+
if current_up != prev_status.get(iface, None):
|
|
30
|
+
if current_up:
|
|
31
|
+
print(f"[UP] 接口 {iface} 激活")
|
|
32
|
+
if callback: callback(iface, "UP")
|
|
33
|
+
else:
|
|
34
|
+
print(f"[DOWN] 接口 {iface} 断开")
|
|
35
|
+
if callback: callback(iface, "DOWN")
|
|
36
|
+
prev_status[iface] = current_up
|
|
37
|
+
time.sleep(interval)
|
|
38
|
+
|
|
39
|
+
# 自定义回调函数示例
|
|
40
|
+
def notify(iface, status):
|
|
41
|
+
if status == "UP":
|
|
42
|
+
print(f"接口 {iface} 已激活")
|
|
43
|
+
|
|
44
|
+
# 启动监听
|
|
45
|
+
# monitor_up_interfaces(callback=notify)
|
|
46
|
+
|
|
47
|
+
monitor = Thread(target=monitor_up_interfaces, name="Network Status Monitor", args=(2, notify))
|
|
48
|
+
monitor.start()
|
|
49
|
+
|
|
50
|
+
import socket
|
|
51
|
+
def is_port_in_use(ip, port):
|
|
52
|
+
with socket.socket() as s:
|
|
53
|
+
return s.connect_ex((ip, port)) == 0
|
|
54
|
+
|
|
55
|
+
print(get_active_ipv4())
|
|
@@ -5,10 +5,11 @@ from threading import Thread
|
|
|
5
5
|
from loguru import logger
|
|
6
6
|
import numpy as np
|
|
7
7
|
import os
|
|
8
|
+
from qlsdk.core import RscPacket
|
|
8
9
|
|
|
9
10
|
class EDFWriterThread(Thread):
|
|
10
11
|
def __init__(self, edf_writer : EdfWriter):
|
|
11
|
-
super().__init__(
|
|
12
|
+
super().__init__()
|
|
12
13
|
self._edf_writer : EdfWriter = edf_writer
|
|
13
14
|
self.data_queue : Queue = Queue()
|
|
14
15
|
self._stop_event : bool = False
|
|
@@ -21,10 +22,6 @@ class EDFWriterThread(Thread):
|
|
|
21
22
|
self._channels = []
|
|
22
23
|
self._sample_rate = 0
|
|
23
24
|
|
|
24
|
-
def start(self):
|
|
25
|
-
self._stop_event = False
|
|
26
|
-
super().start()
|
|
27
|
-
|
|
28
25
|
def stop(self):
|
|
29
26
|
self._stop_event = True
|
|
30
27
|
|
|
@@ -32,7 +29,7 @@ class EDFWriterThread(Thread):
|
|
|
32
29
|
# 数据
|
|
33
30
|
self.data_queue.put(data)
|
|
34
31
|
|
|
35
|
-
def
|
|
32
|
+
def run(self):
|
|
36
33
|
logger.debug(f"开始消费数据 _consumer: {self.data_queue.qsize()}")
|
|
37
34
|
while True:
|
|
38
35
|
if self._recording or (not self.data_queue.empty()):
|
|
@@ -73,7 +70,8 @@ class EDFWriterThread(Thread):
|
|
|
73
70
|
self._edf_writer.writeAnnotation(self._duration, 1, "recording end")
|
|
74
71
|
self._edf_writer.close()
|
|
75
72
|
|
|
76
|
-
logger.info(f"文件: {self.file_name}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
|
|
73
|
+
# logger.info(f"文件: {self.file_name}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
|
|
74
|
+
# logger.info(f"文件: 完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
|
|
77
75
|
|
|
78
76
|
|
|
79
77
|
|
|
@@ -101,7 +99,7 @@ class RscEDFHandler(object):
|
|
|
101
99
|
@author: qlsdk
|
|
102
100
|
@since: 0.4.0
|
|
103
101
|
'''
|
|
104
|
-
def __init__(self, sample_frequency, physical_max, physical_min, digital_max, digital_min, resolution=
|
|
102
|
+
def __init__(self, sample_frequency, physical_max, physical_min, digital_max, digital_min, resolution=24, storage_path = None):
|
|
105
103
|
# edf文件参数
|
|
106
104
|
self.physical_max = physical_max
|
|
107
105
|
self.physical_min = physical_min
|
|
@@ -137,7 +135,7 @@ class RscEDFHandler(object):
|
|
|
137
135
|
self._end_time = None
|
|
138
136
|
self._patient_code = "patient_code"
|
|
139
137
|
self._patient_name = "patient_name"
|
|
140
|
-
self._device_type =
|
|
138
|
+
self._device_type = "24130032"
|
|
141
139
|
self._total_packets = 0
|
|
142
140
|
self._lost_packets = 0
|
|
143
141
|
self._storage_path = storage_path
|
|
@@ -170,32 +168,39 @@ class RscEDFHandler(object):
|
|
|
170
168
|
def set_patient_name(self, patient_name):
|
|
171
169
|
self._patient_name = patient_name
|
|
172
170
|
|
|
173
|
-
def append(self, data
|
|
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}")
|
|
171
|
+
def append(self, data: RscPacket):
|
|
180
172
|
|
|
181
173
|
if data:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# self._first_timestamp = data.time_stamp
|
|
174
|
+
if self.eeg_channels is None:
|
|
175
|
+
logger.info(f"开始记录数据到文件...")
|
|
176
|
+
self.eeg_channels = data.channels
|
|
177
|
+
self._first_pkg_id = data.pkg_id if self._first_pkg_id is None else self._first_pkg_id
|
|
178
|
+
self._first_timestamp = data.time_stamp if self._first_timestamp is None else self._first_timestamp
|
|
188
179
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
180
|
+
if self._last_pkg_id and self._last_pkg_id != data.pkg_id - 1:
|
|
181
|
+
self._lost_packets += data.pkg_id - self._last_pkg_id - 1
|
|
182
|
+
logger.warning(f"数据包丢失: {self._last_pkg_id} -> {data.pkg_id}, 丢包数: {data.pkg_id - self._last_pkg_id - 1}")
|
|
192
183
|
|
|
193
184
|
self._last_pkg_id = data.pkg_id
|
|
194
185
|
self._total_packets += 1
|
|
195
186
|
|
|
196
|
-
|
|
197
|
-
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# 通道数变化、采样频率、信号放大幅度变化时应生成新的edf文件,handler内部不关注,外部调用方自行控制
|
|
190
|
+
# elif len(self.eeg_channels) != len(data.channels):
|
|
191
|
+
|
|
192
|
+
if self._edf_writer_thread is None:
|
|
193
|
+
self._edf_writer_thread = EDFWriterThread(self.init_edf_writer())
|
|
194
|
+
self._edf_writer_thread.start()
|
|
195
|
+
self._recording = True
|
|
196
|
+
self._edf_writer_thread._recording = True
|
|
197
|
+
logger.info(f"开始写入数据: {self.file_name}")
|
|
198
|
+
|
|
198
199
|
self._edf_writer_thread.append(data)
|
|
200
|
+
|
|
201
|
+
# 数据
|
|
202
|
+
# self._cache.put(data)
|
|
203
|
+
# self._edf_writer_thread.append(data)
|
|
199
204
|
# if not self._recording:
|
|
200
205
|
# self.start()
|
|
201
206
|
|
|
@@ -206,7 +211,7 @@ class RscEDFHandler(object):
|
|
|
206
211
|
# 创建EDF+写入器
|
|
207
212
|
edf_writer = EdfWriter(
|
|
208
213
|
self.file_name,
|
|
209
|
-
self.eeg_channels,
|
|
214
|
+
len(self.eeg_channels),
|
|
210
215
|
file_type=self.file_type
|
|
211
216
|
)
|
|
212
217
|
|
|
@@ -218,7 +223,7 @@ class RscEDFHandler(object):
|
|
|
218
223
|
|
|
219
224
|
# 配置通道参数
|
|
220
225
|
signal_headers = []
|
|
221
|
-
for ch in range(self.eeg_channels):
|
|
226
|
+
for ch in range(len(self.eeg_channels)):
|
|
222
227
|
signal_headers.append({
|
|
223
228
|
"label": f'channels {ch + 1}',
|
|
224
229
|
"dimension": 'uV',
|
|
@@ -74,6 +74,7 @@ class DeviceContainer(object):
|
|
|
74
74
|
def client_handler(self, client_socket):
|
|
75
75
|
|
|
76
76
|
if self._proxy_enabled:
|
|
77
|
+
# 启动代理 TODO: 代理的同时支持接口控制和数据转发
|
|
77
78
|
proxy = DeviceProxy(client_socket)
|
|
78
79
|
proxy.start()
|
|
79
80
|
else:
|
|
@@ -86,6 +87,7 @@ class DeviceContainer(object):
|
|
|
86
87
|
# 添加设备
|
|
87
88
|
while True:
|
|
88
89
|
if device.device_name:
|
|
90
|
+
logger.info(f"设备 {device.device_name} 已连接")
|
|
89
91
|
self.add_device(device)
|
|
90
92
|
break
|
|
91
93
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from multiprocessing import Queue, Process
|
|
2
2
|
from typing import Any, Dict, Literal
|
|
3
|
-
# from rsc.command import *
|
|
4
|
-
from qlsdk.core import *
|
|
5
|
-
from qlsdk.core.device import BaseDevice
|
|
6
3
|
from threading import Thread
|
|
7
4
|
from loguru import logger
|
|
8
5
|
from time import time_ns
|
|
9
6
|
|
|
7
|
+
from qlsdk.core import *
|
|
8
|
+
from qlsdk.core.device import BaseDevice
|
|
9
|
+
from qlsdk.persist import RscEDFHandler
|
|
10
|
+
|
|
10
11
|
class QLDevice(BaseDevice):
|
|
11
12
|
def __init__(self, socket):
|
|
12
13
|
self.socket = socket
|
|
@@ -101,6 +102,9 @@ class QLDevice(BaseDevice):
|
|
|
101
102
|
self.__signal_consumer: Dict[str, Queue[Any]]={}
|
|
102
103
|
self.__impedance_consumer: Dict[str, Queue[Any]]={}
|
|
103
104
|
|
|
105
|
+
# EDF文件处理器
|
|
106
|
+
self._edf_handler = None
|
|
107
|
+
self.storage_enable = True
|
|
104
108
|
|
|
105
109
|
self._parser = DeviceParser(self)
|
|
106
110
|
self._parser.start()
|
|
@@ -109,6 +113,19 @@ class QLDevice(BaseDevice):
|
|
|
109
113
|
self._accept = Thread(target=self.accept)
|
|
110
114
|
self._accept.daemon = True
|
|
111
115
|
self._accept.start()
|
|
116
|
+
|
|
117
|
+
def init_edf_handler(self):
|
|
118
|
+
self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range, - self.sample_range, - 2 >> 24, 2 >> 24 - 1, self.resolution)
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def edf_handler(self):
|
|
122
|
+
if not self.storage_enable:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
if self._edf_handler is None:
|
|
126
|
+
self.init_edf_handler()
|
|
127
|
+
|
|
128
|
+
return self._edf_handler
|
|
112
129
|
|
|
113
130
|
@property
|
|
114
131
|
def acq_channels(self):
|
|
@@ -132,6 +149,9 @@ class QLDevice(BaseDevice):
|
|
|
132
149
|
@property
|
|
133
150
|
def impedance_consumers(self):
|
|
134
151
|
return self.__impedance_consumer
|
|
152
|
+
|
|
153
|
+
def set_storage_path(self, path):
|
|
154
|
+
pass
|
|
135
155
|
|
|
136
156
|
def accept(self):
|
|
137
157
|
while True:
|
|
@@ -220,13 +240,16 @@ class QLDevice(BaseDevice):
|
|
|
220
240
|
msg = StopAcquisitionCommand.build(self).pack()
|
|
221
241
|
logger.debug(f"stop_acquisition message is {msg}")
|
|
222
242
|
self.socket.sendall(msg)
|
|
243
|
+
if self._edf_handler:
|
|
244
|
+
# 发送结束标识
|
|
245
|
+
self.edf_handler.append(None)
|
|
223
246
|
|
|
224
247
|
# 订阅实时数据
|
|
225
248
|
def subscribe(self, topic:str=None, q : Queue=None, type : Literal["signal","impedance"]="signal"):
|
|
226
249
|
|
|
227
250
|
# 数据队列
|
|
228
251
|
if q is None:
|
|
229
|
-
q = Queue()
|
|
252
|
+
q = Queue(maxsize=1000)
|
|
230
253
|
|
|
231
254
|
# 队列名称
|
|
232
255
|
if topic is None:
|
|
@@ -318,7 +341,7 @@ class DeviceParser(object):
|
|
|
318
341
|
|
|
319
342
|
def append(self, buffer):
|
|
320
343
|
self.cache += buffer
|
|
321
|
-
logger.debug(f"
|
|
344
|
+
logger.debug(f"已缓存的数据长度: {len(self.cache)}")
|
|
322
345
|
|
|
323
346
|
# if not self.running:
|
|
324
347
|
# self.start()
|
|
@@ -326,14 +349,13 @@ class DeviceParser(object):
|
|
|
326
349
|
def __parser__(self):
|
|
327
350
|
logger.info("数据解析开始")
|
|
328
351
|
while self.running:
|
|
329
|
-
# logger.debug(f" cache len: {len(self.cache)}")
|
|
330
352
|
if len(self.cache) < 14:
|
|
331
353
|
continue
|
|
332
354
|
if self.cache[0] != 0x5A or self.cache[1] != 0xA5:
|
|
333
355
|
self.cache = self.cache[1:]
|
|
334
356
|
continue
|
|
335
357
|
pkg_len = int.from_bytes(self.cache[8:12], 'little')
|
|
336
|
-
logger.
|
|
358
|
+
logger.trace(f" cache len: {len(self.cache)}, pkg_len len: {len(self.cache)}")
|
|
337
359
|
# 一次取整包数据
|
|
338
360
|
if len(self.cache) < pkg_len:
|
|
339
361
|
continue
|
|
@@ -368,11 +390,13 @@ class TCPMessage(object):
|
|
|
368
390
|
TCPMessage._validate_packet(data)
|
|
369
391
|
# 提取指令码
|
|
370
392
|
cmd_code = int.from_bytes(data[TCPMessage.CMD_POS:TCPMessage.CMD_POS+2], 'little')
|
|
371
|
-
logger.debug(f"收到指令:{hex(cmd_code)}")
|
|
372
393
|
cmd_class = CommandFactory.create_command(cmd_code)
|
|
373
|
-
logger.debug(f"
|
|
394
|
+
logger.debug(f"收到指令:{cmd_class.cmd_desc}[{hex(cmd_code)}]")
|
|
374
395
|
instance = cmd_class(device)
|
|
396
|
+
start = time_ns()
|
|
397
|
+
logger.debug(f"开始解析: {start}")
|
|
375
398
|
instance.parse_body(data[TCPMessage.HEADER_LEN:-2])
|
|
399
|
+
logger.debug(f"解析完成:{time_ns()}, 解析耗时:{time_ns() - start}ns")
|
|
376
400
|
return instance
|
|
377
401
|
|
|
378
402
|
@staticmethod
|
|
@@ -388,9 +412,9 @@ class TCPMessage(object):
|
|
|
388
412
|
if len(data) != expected_len:
|
|
389
413
|
raise ValueError(f"Length mismatch: {len(data)} vs {expected_len}")
|
|
390
414
|
|
|
391
|
-
logger.
|
|
392
|
-
checksum = crc16(data[:-2])
|
|
393
|
-
logger.
|
|
415
|
+
# logger.trace(f"checksum: {int.from_bytes(data[-2:], 'little')}")
|
|
416
|
+
# checksum = crc16(data[:-2])
|
|
417
|
+
# logger.trace(f"checksum recv: {checksum}")
|
|
394
418
|
|
|
395
419
|
|
|
396
420
|
|
|
@@ -402,150 +426,3 @@ class DataPacket(object):
|
|
|
402
426
|
|
|
403
427
|
def parse_body(self, body: bytes):
|
|
404
428
|
raise NotImplementedError("Subclasses should implement this method")
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
class C64Channel(Enum):
|
|
409
|
-
CH0 = 0
|
|
410
|
-
CH1 = 1
|
|
411
|
-
CH2 = 2
|
|
412
|
-
CH3 = 3
|
|
413
|
-
CH4 = 4
|
|
414
|
-
CH5 = 5
|
|
415
|
-
CH6 = 6
|
|
416
|
-
CH7 = 7
|
|
417
|
-
CH8 = 8
|
|
418
|
-
CH9 = 9
|
|
419
|
-
CH10 = 10
|
|
420
|
-
CH11 = 11
|
|
421
|
-
CH12 = 12
|
|
422
|
-
CH13 = 13
|
|
423
|
-
CH14 = 14
|
|
424
|
-
CH15 = 15
|
|
425
|
-
|
|
426
|
-
class WaveForm(Enum):
|
|
427
|
-
DC = 0
|
|
428
|
-
SQUARE = 1
|
|
429
|
-
AC = 2
|
|
430
|
-
CUSTOM = 3
|
|
431
|
-
PULSE = 4
|
|
432
|
-
|
|
433
|
-
# # 刺激通道
|
|
434
|
-
# class StimulationChannel(object):
|
|
435
|
-
# def __init__(self, channel_id: int, waveform: int, current: float, duration: float, ramp_up: float = None, ramp_down: float = None,
|
|
436
|
-
# frequency: float = None, phase_position: int = None, duration_delay: float = None, pulse_width: int = None, pulse_width_rate: int = 1):
|
|
437
|
-
# self.channel_id = channel_id
|
|
438
|
-
# self.waveform = waveform
|
|
439
|
-
# self.current_max = current
|
|
440
|
-
# self.current_min = current
|
|
441
|
-
# self.duration = duration
|
|
442
|
-
# self.ramp_up = ramp_up
|
|
443
|
-
# self.ramp_down = ramp_down
|
|
444
|
-
# self.frequency = frequency
|
|
445
|
-
# self.phase_position = phase_position
|
|
446
|
-
# self.duration_delay = duration_delay
|
|
447
|
-
# self.pulse_width = pulse_width
|
|
448
|
-
# self.delay_time = 0
|
|
449
|
-
# self.pulse_interval = 0
|
|
450
|
-
# self.with_group_repeats = 1
|
|
451
|
-
# self.pulse_width_rate = 1065353216
|
|
452
|
-
# self.pulse_time_f = 0
|
|
453
|
-
# self.pulse_time_out = 0
|
|
454
|
-
# self.pulse_time_idle = 0
|
|
455
|
-
|
|
456
|
-
# def to_bytes(self):
|
|
457
|
-
# # Convert the object to bytes for transmission
|
|
458
|
-
# result = self.channel_id.to_bytes(1, 'little')
|
|
459
|
-
# wave_form = WaveForm.SQUARE.value if self.waveform == WaveForm.PULSE.value else self.waveform
|
|
460
|
-
# result += wave_form.to_bytes(1, 'little')
|
|
461
|
-
# result += int(self.current_max * 1000 * 1000).to_bytes(4, 'little')
|
|
462
|
-
# # result += int(self.current_min * 1000).to_bytes(2, 'little')
|
|
463
|
-
# result += int(self.frequency).to_bytes(2, 'little')
|
|
464
|
-
# result += int(self.pulse_width).to_bytes(2, 'little')
|
|
465
|
-
# result += int(self.pulse_width_rate).to_bytes(4, 'little')
|
|
466
|
-
|
|
467
|
-
# result += int(self.pulse_interval).to_bytes(2, 'little')
|
|
468
|
-
# result += int(self.with_group_repeats).to_bytes(2, 'little')
|
|
469
|
-
# result += int(self.pulse_time_f).to_bytes(4, 'little')
|
|
470
|
-
# result += int(self.pulse_time_out).to_bytes(4, 'little')
|
|
471
|
-
# result += int(self.pulse_time_idle).to_bytes(4, 'little')
|
|
472
|
-
|
|
473
|
-
# result += int(self.delay_time).to_bytes(4, 'little')
|
|
474
|
-
# result += int(self.ramp_up * 1000).to_bytes(4, 'little')
|
|
475
|
-
# result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
|
|
476
|
-
# result += int(self.ramp_down * 1000).to_bytes(4, 'little')
|
|
477
|
-
|
|
478
|
-
# return result
|
|
479
|
-
|
|
480
|
-
# def to_json(self):
|
|
481
|
-
# return {
|
|
482
|
-
# "channel_id": self.channel_id,
|
|
483
|
-
# "waveform": self.waveform,
|
|
484
|
-
# "current_max": self.current_max,
|
|
485
|
-
# "current_min": self.current_min,
|
|
486
|
-
# "duration": self.duration,
|
|
487
|
-
# "ramp_up": self.ramp_up,
|
|
488
|
-
# "ramp_down": self.ramp_down,
|
|
489
|
-
# "frequency": self.frequency,
|
|
490
|
-
# "phase_position": self.phase_position,
|
|
491
|
-
# "duration_delay": self.duration_delay,
|
|
492
|
-
# "pulse_width": self.pulse_width,
|
|
493
|
-
# "delay_time": self.delay_time,
|
|
494
|
-
# "pulse_interval": self.pulse_interval,
|
|
495
|
-
# "with_group_repeats": self.with_group_repeats
|
|
496
|
-
# }
|
|
497
|
-
|
|
498
|
-
# # 刺激范式
|
|
499
|
-
# class StimulationParadigm(object):
|
|
500
|
-
# def __init__(self):
|
|
501
|
-
# self.channels = None
|
|
502
|
-
# self.duration = None
|
|
503
|
-
# self.interval_time = 0
|
|
504
|
-
# self.characteristic = 0
|
|
505
|
-
# self.mode = 0
|
|
506
|
-
# self.repeats = 0
|
|
507
|
-
|
|
508
|
-
# def add_channel(self, channel: StimulationChannel, update=False):
|
|
509
|
-
# if self.channels is None:
|
|
510
|
-
# self.channels = {}
|
|
511
|
-
# channel_id = channel.channel_id + 1
|
|
512
|
-
# if channel_id in self.channels.keys():
|
|
513
|
-
# logger.warning(f"Channel {channel_id} already exists")
|
|
514
|
-
# if update:
|
|
515
|
-
# self.channels[channel_id] = channel
|
|
516
|
-
# else:
|
|
517
|
-
# self.channels[channel_id] = channel
|
|
518
|
-
|
|
519
|
-
# # 计算刺激时间
|
|
520
|
-
# duration = channel.duration + channel.ramp_up + channel.ramp_down
|
|
521
|
-
# if self.duration is None or duration > self.duration:
|
|
522
|
-
# self.duration = duration
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
# def to_bytes(self):
|
|
526
|
-
# result = to_bytes(list(self.channels.keys()), 64)
|
|
527
|
-
# result += int(self.duration * 1000).to_bytes(4, 'little')
|
|
528
|
-
# result += int(self.interval_time).to_bytes(4, 'little')
|
|
529
|
-
# result += int(self.characteristic).to_bytes(4, 'little')
|
|
530
|
-
# result += int(self.mode).to_bytes(1, 'little')
|
|
531
|
-
# result += int(self.repeats).to_bytes(4, 'little')
|
|
532
|
-
# for channel in self.channels.values():
|
|
533
|
-
# result += channel.to_bytes()
|
|
534
|
-
# return result
|
|
535
|
-
|
|
536
|
-
# def to_json(self):
|
|
537
|
-
# # Convert the object to JSON for transmission
|
|
538
|
-
# return {
|
|
539
|
-
# "channels": list(self.channels.keys()),
|
|
540
|
-
# "duration": self.duration,
|
|
541
|
-
# "interval_time": self.interval_time,
|
|
542
|
-
# "characteristic": self.characteristic,
|
|
543
|
-
# "mode": self.mode,
|
|
544
|
-
# "repeats": self.repeats,
|
|
545
|
-
# "stim": [channel.to_json() for channel in self.channels.values()]
|
|
546
|
-
# }
|
|
547
|
-
|
|
548
|
-
# @staticmethod
|
|
549
|
-
# def from_json(param: Dict[str, Any]):
|
|
550
|
-
# pass
|
|
551
|
-
|
|
@@ -187,20 +187,21 @@ class PulseStimulation(StimulationChannel):
|
|
|
187
187
|
current: float, 电流值,单位为mA
|
|
188
188
|
duration: float, 刺激时间,单位为秒
|
|
189
189
|
frequency: float, 频率,单位为Hz
|
|
190
|
-
pulse_width: int, 脉冲宽度,单位为
|
|
190
|
+
pulse_width: int, 脉冲宽度,单位为uS
|
|
191
191
|
pulse_width_ratio: float, 脉冲宽度比,范围(0, 1)
|
|
192
|
+
pulse_interval: int 脉冲间隔,单位为uS
|
|
192
193
|
ramp_up: float, 上升时间,单位为秒
|
|
193
194
|
ramp_down: float, 下降时间,单位为秒
|
|
194
195
|
delay_time: float, 延迟启动时间,单位为秒(暂未启用)
|
|
195
196
|
'''
|
|
196
197
|
def __init__(self, channel_id: int, current: float, duration: float, frequency: float, pulse_width: int,
|
|
197
|
-
pulse_width_ratio: float = 1, ramp_up: float = 0, ramp_down: float = 0, delay_time = 0):
|
|
198
|
+
pulse_width_ratio: float = 1, pulse_interval = 0, ramp_up: float = 0, ramp_down: float = 0, delay_time = 0):
|
|
198
199
|
super().__init__(channel_id, WaveForm.PULSE.value, current, duration, ramp_up, ramp_down)
|
|
199
200
|
self.frequency = frequency
|
|
200
201
|
self.delay_time = delay_time
|
|
201
202
|
self.pulse_width = pulse_width
|
|
202
203
|
self.pulse_width_ratio = pulse_width_ratio
|
|
203
|
-
self.pulse_interval =
|
|
204
|
+
self.pulse_interval = pulse_interval
|
|
204
205
|
self.with_group_repeats = 1
|
|
205
206
|
self.pulse_time_f = 0
|
|
206
207
|
self.pulse_time_out = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: qlsdk2
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.0a3
|
|
4
4
|
Summary: SDK for quanlan device
|
|
5
5
|
Home-page: https://github.com/hehuajun/qlsdk
|
|
6
6
|
Author: hehuajun
|
|
@@ -12,7 +12,7 @@ Requires-Python: >=3.9
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
Requires-Dist: loguru>=0.6.0
|
|
14
14
|
Requires-Dist: numpy>=1.23.5
|
|
15
|
-
Requires-Dist:
|
|
15
|
+
Requires-Dist: bitarray>=1.5.3
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
18
18
|
Requires-Dist: twine>=3.0; extra == "dev"
|
|
@@ -23,6 +23,8 @@ src/qlsdk/core/message/__init__.py
|
|
|
23
23
|
src/qlsdk/core/message/command.py
|
|
24
24
|
src/qlsdk/core/message/tcp.py
|
|
25
25
|
src/qlsdk/core/message/udp.py
|
|
26
|
+
src/qlsdk/core/network/__init__.py
|
|
27
|
+
src/qlsdk/core/network/monitor.py
|
|
26
28
|
src/qlsdk/persist/__init__.py
|
|
27
29
|
src/qlsdk/persist/edf.py
|
|
28
30
|
src/qlsdk/persist/rsc_edf.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|