qlsdk2 0.3.0a2__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.
qlsdk/rsc/paradigm.py ADDED
@@ -0,0 +1,310 @@
1
+ from enum import Enum
2
+ from abc import ABC, abstractmethod
3
+ from loguru import logger
4
+ import struct
5
+
6
+ from qlsdk.core import to_bytes, to_channels
7
+
8
+ class C64Channel(Enum):
9
+ CH0 = 0
10
+ CH1 = 1
11
+ CH2 = 2
12
+ CH3 = 3
13
+ CH4 = 4
14
+ CH5 = 5
15
+ CH6 = 6
16
+ CH7 = 7
17
+ CH8 = 8
18
+ CH9 = 9
19
+ CH10 = 10
20
+ CH11 = 11
21
+ CH12 = 12
22
+ CH13 = 13
23
+ CH14 = 14
24
+ CH15 = 15
25
+
26
+ class WaveForm(Enum):
27
+ DC = 0
28
+ PULSE = 1
29
+ AC = 2
30
+ SQUARE = 3
31
+
32
+ class StimulationChannel(ABC):
33
+ def __init__(self, channel_id: int, wave_form: int, current: float, duration: float, ramp_up: float = None, ramp_down: float = None,):
34
+ self.channel_id = channel_id
35
+ self.wave_form = wave_form
36
+ self.current_max = current
37
+ self.current_min = 0
38
+ self.duration = duration
39
+ self.ramp_up = ramp_up
40
+ self.ramp_down = ramp_down
41
+ self.delay_time = 0
42
+ self.frequency = 0
43
+ self.phase_position = 0
44
+ # 预留值,用作占位
45
+ self.reserved = 0
46
+
47
+ def to_bytes(self):
48
+ result = self.channel_id.to_bytes(1, 'little')
49
+ result += self.wave_form.to_bytes(1, 'little')
50
+ result += int(self.current_max * 1000 ).to_bytes(2, 'little', signed = True)
51
+ result += int(self.current_min * 1000 ).to_bytes(2, 'little', signed = True)
52
+ result += struct.pack('<f', self.frequency)
53
+ result += struct.pack('<f', self.phase_position)
54
+
55
+ result += self._ext_bytes()
56
+
57
+ result += int(self.delay_time).to_bytes(4, 'little')
58
+ result += int(self.ramp_up * 1000).to_bytes(4, 'little')
59
+ result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
60
+ result += int(self.ramp_down * 1000).to_bytes(4, 'little')
61
+
62
+ return result
63
+
64
+ def _ext_bytes(self):
65
+ return bytes.fromhex("00000000000000000000000000000000")
66
+
67
+ def to_json(self):
68
+ pass
69
+
70
+ def from_json(self, param):
71
+ pass
72
+
73
+ def __str__(self):
74
+ return f"ACStimulation(channel_id={self.channel_id}, waveform={self.waveform}, current={self.current_max}, duration={self.duration}, ramp_up={self.ramp_up}, ramp_down={self.ramp_down}, frequency={self.frequency}, phase_position={self.phase_position}, duration_delay={self.duration_delay})"
75
+
76
+
77
+
78
+ # 刺激模式-直流
79
+ class DCStimulation(StimulationChannel):
80
+ '''
81
+ channel_id: int, 通道编号,从0开始
82
+ current: float, 电流值,单位为mA
83
+ duration: float, 刺激时间,单位为秒
84
+ ramp_up: float, 上升时间,单位为秒
85
+ ramp_down: float, 下降时间,单位为秒
86
+ '''
87
+ def __init__(self, channel_id: int, current: float, duration: float, ramp_up: float = 0, ramp_down: float = 0):
88
+ super().__init__(channel_id, WaveForm.DC.value, current, duration, ramp_up, ramp_down)
89
+ def to_json(self):
90
+ return {
91
+ "channel_id": self.channel_id,
92
+ "waveform": self.wave_form,
93
+ "current_max": self.current_max,
94
+ "duration": self.duration,
95
+ "ramp_up": self.ramp_up,
96
+ "ramp_down": self.ramp_down,
97
+ "frequency": self.frequency,
98
+ "phase_position": self.phase_position,
99
+ "delay_time": self.delay_time
100
+ }
101
+
102
+ def from_json(self, param):
103
+ pass
104
+ def __str__(self):
105
+ return f"ACStimulation(channel_id={self.channel_id}, waveform={self.waveform}, current={self.current_max}, duration={self.duration}, ramp_up={self.ramp_up}, ramp_down={self.ramp_down}, frequency={self.frequency}, phase_position={self.phase_position}, duration_delay={self.duration_delay})"
106
+
107
+
108
+ # 刺激模式-方波
109
+ class SquareWaveStimulation(StimulationChannel):
110
+ '''
111
+ channel_id: int, 通道编号,从0开始
112
+ current: float, 电流值,单位为mA
113
+ duration: float, 刺激时间,单位为秒
114
+ ramp_up: float, 上升时间,单位为秒
115
+ ramp_down: float, 下降时间,单位为秒
116
+ frequency: float, 频率,单位为Hz
117
+ duty_cycle: float, 占空比,高电平时间/周期,范围(0, 100)
118
+ '''
119
+ def __init__(self, channel_id: int, current: float, duration: float, ramp_up: float = 0, ramp_down: float = 0,
120
+ frequency: float = None, duty_cycle: float = 0.5):
121
+ super().__init__(channel_id, WaveForm.SQUARE.value, current, duration, ramp_up, ramp_down)
122
+ self.frequency = frequency
123
+ self.duty_cycle = duty_cycle
124
+
125
+
126
+ def to_bytes(self):
127
+ # Convert the object to bytes for transmission
128
+ result = self.channel_id.to_bytes(1, 'little')
129
+ result += self.wave_form.to_bytes(1, 'little')
130
+ result += int(self.current_max * 1000 * 1000).to_bytes(4, 'little', signed = True)
131
+ # result += int(self.current_min * 1000).to_bytes(2, 'little')
132
+ result += int(self.frequency).to_bytes(2, 'little')
133
+ result += int(self.reserved).to_bytes(2, 'little')
134
+ result += struct.pack('<f', self.duty_cycle)
135
+
136
+ result += self._ext_bytes()
137
+
138
+ result += int(self.delay_time).to_bytes(4, 'little')
139
+ result += int(self.ramp_up * 1000).to_bytes(4, 'little')
140
+ result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
141
+ result += int(self.ramp_down * 1000).to_bytes(4, 'little')
142
+
143
+ return result
144
+
145
+ # 刺激模式-交流
146
+ class ACStimulation(StimulationChannel):
147
+ '''
148
+ channel_id: int, 通道编号,从0开始
149
+ current: float, 电流值,单位为mA
150
+ duration: float, 刺激时间,单位为秒
151
+ ramp_up: float, 上升时间,单位为秒
152
+ ramp_down: float, 下降时间,单位为秒
153
+ frequency: float, 频率,单位为Hz
154
+ phase_position: int, 相位位置,单位为度
155
+ '''
156
+
157
+ def __init__(self, channel_id: int, current: float, duration: float, ramp_up: float = 0, ramp_down: float = 0,
158
+ frequency: float = None, phase_position: int = 0):
159
+ super().__init__(channel_id, WaveForm.AC.value, current, duration, ramp_up, ramp_down)
160
+ self.current_max = abs(current)
161
+ self.current_min = -abs(current)
162
+ self.frequency = frequency
163
+ # self.frequency = frequency
164
+ self.phase_position = phase_position
165
+
166
+ def to_json(self):
167
+ return {
168
+ "channel_id": self.channel_id,
169
+ "waveform": self.wave_form,
170
+ "current_max": self.current_max,
171
+ "duration": self.duration,
172
+ "ramp_up": self.ramp_up,
173
+ "ramp_down": self.ramp_down,
174
+ "frequency": self.frequency,
175
+ "phase_position": self.phase_position
176
+ }
177
+
178
+ def from_json(self, param):
179
+ pass
180
+ def __str__(self):
181
+ return f"ACStimulation(channel_id={self.channel_id}, waveform={self.waveform}, current={self.current_max}, duration={self.duration}, ramp_up={self.ramp_up}, ramp_down={self.ramp_down}, frequency={self.frequency}, phase_position={self.phase_position}, duration_delay={self.duration_delay})"
182
+
183
+ # 刺激模式-脉冲
184
+ class PulseStimulation(StimulationChannel):
185
+ '''
186
+ channel_id: int, 通道编号,从0开始
187
+ current: float, 电流值,单位为mA
188
+ duration: float, 刺激时间,单位为秒
189
+ ramp_up: float, 上升时间,单位为秒
190
+ ramp_down: float, 下降时间,单位为秒
191
+ frequency: float, 频率,单位为Hz
192
+ phase_position: int, 相位位置,单位为度
193
+ '''
194
+ def __init__(self, channel_id: int, current: float, duration: float, frequency: float, pulse_width: int,
195
+ pulse_width_ratio: float = 1, ramp_up: float = 0, ramp_down: float = 0, delay_time = 0):
196
+ super().__init__(channel_id, WaveForm.PULSE.value, current, duration, ramp_up, ramp_down)
197
+ self.frequency = frequency
198
+ self.duration_delay = delay_time
199
+ self.pulse_width = pulse_width
200
+ self.pulse_width_ratio = pulse_width_ratio
201
+ self.pulse_interval = 0
202
+ self.with_group_repeats = 1
203
+ self.pulse_time_f = 0
204
+ self.pulse_time_out = 0
205
+ self.pulse_time_idle = 0
206
+
207
+ def to_bytes(self):
208
+ # Convert the object to bytes for transmission
209
+ result = self.channel_id.to_bytes(1, 'little')
210
+ result += self.wave_form.to_bytes(1, 'little')
211
+ result += int(self.current_max * 1000 * 1000).to_bytes(4, 'little', signed = True)
212
+ # result += int(self.current_min * 1000).to_bytes(2, 'little')
213
+ result += int(self.frequency).to_bytes(2, 'little')
214
+ result += int(self.pulse_width).to_bytes(2, 'little')
215
+ result += struct.pack('<f', self.pulse_width_ratio)
216
+
217
+ result += int(self.pulse_interval).to_bytes(2, 'little')
218
+ result += int(self.with_group_repeats).to_bytes(2, 'little')
219
+ result += int(self.pulse_time_f).to_bytes(4, 'little')
220
+ result += int(self.pulse_time_out).to_bytes(4, 'little')
221
+ result += int(self.pulse_time_idle).to_bytes(4, 'little')
222
+
223
+ result += int(self.delay_time).to_bytes(4, 'little')
224
+ result += int(self.ramp_up * 1000).to_bytes(4, 'little')
225
+ result += int((self.duration + self.ramp_up) * 1000).to_bytes(4, 'little')
226
+ result += int(self.ramp_down * 1000).to_bytes(4, 'little')
227
+
228
+ return result
229
+
230
+ def to_json(self):
231
+ return {
232
+ "channel_id": self.channel_id,
233
+ "wave_form": self.wave_form,
234
+ "current_max": self.current_max,
235
+ "current_min": self.current_min,
236
+ "duration": self.duration,
237
+ "ramp_up": self.ramp_up,
238
+ "ramp_down": self.ramp_down,
239
+ "frequency": self.frequency,
240
+ "phase_position": self.phase_position,
241
+ "duration_delay": self.duration_delay,
242
+ "pulse_width": self.pulse_width,
243
+ "delay_time": self.delay_time,
244
+ "pulse_interval": self.pulse_interval,
245
+ "with_group_repeats": self.with_group_repeats
246
+ }
247
+
248
+
249
+ # 刺激范式
250
+ class StimulationParadigm(object):
251
+ def __init__(self):
252
+ self.channels = None
253
+ self.duration = None
254
+ self.interval_time = 0
255
+ self.characteristic = 1
256
+ self.mode = 0
257
+ self.repeats = 0
258
+
259
+ def add_channel(self, channel: StimulationChannel, update=False):
260
+ if self.channels is None:
261
+ self.channels = {}
262
+ channel_id = channel.channel_id + 1
263
+ if channel_id in self.channels.keys():
264
+ logger.warning(f"Channel {channel_id} already exists")
265
+ if update:
266
+ self.channels[channel_id] = channel
267
+ else:
268
+ self.channels[channel_id] = channel
269
+
270
+ # 计算刺激时间
271
+ duration = channel.duration + channel.ramp_up + channel.ramp_down
272
+ if self.duration is None or duration > self.duration:
273
+ self.duration = duration
274
+
275
+
276
+ def to_bytes(self):
277
+ result = to_bytes(list(self.channels.keys()), 64)
278
+ result += int(self.duration * 1000).to_bytes(4, 'little')
279
+ result += int(self.interval_time).to_bytes(4, 'little')
280
+ result += int(self.characteristic).to_bytes(4, 'little')
281
+ result += int(self.mode).to_bytes(1, 'little')
282
+ result += int(self.repeats).to_bytes(4, 'little')
283
+ for channel in self.channels.values():
284
+ result += channel.to_bytes()
285
+ return result
286
+
287
+ def to_json(self):
288
+ # Convert the object to JSON for transmission
289
+ return {
290
+ "channels": list(self.channels.keys()),
291
+ "duration": self.duration,
292
+ "interval_time": self.interval_time,
293
+ "characteristic": self.characteristic,
294
+ "mode": self.mode,
295
+ "repeats": self.repeats,
296
+ "stim": [channel.to_json() for channel in self.channels.values()]
297
+ }
298
+
299
+ # @staticmethod
300
+ # def from_json(param: Dict[str, Any]):
301
+ # pass
302
+
303
+ def clear(self):
304
+ self.channels = None
305
+ self.duration = None
306
+ self.interval_time = 0
307
+ self.characteristic = 1
308
+ self.mode = 0
309
+ self.repeats = 0
310
+
qlsdk/rsc/proxy.py ADDED
@@ -0,0 +1,76 @@
1
+ from loguru import logger
2
+ from threading import Thread, Lock
3
+ import socket
4
+
5
+ class DeviceProxy(object):
6
+ def __init__(self, client_socket):
7
+ self.client_socket = client_socket
8
+ self.server_socket = None
9
+ # 客户端设备
10
+ # self.device = QLDevice(client_socket)
11
+ # self.parser = DeviceParser(self.device)
12
+
13
+ self._init_server()
14
+
15
+ def _init_server(self):
16
+ # 连接目标服务(桌面端运行在本地的18125端口)
17
+ self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
18
+ self.server_socket.connect(('localhost', 18125))
19
+
20
+ def start(self):
21
+ logger.info("DeviceProxy连接已建立")
22
+
23
+ try:
24
+ # 创建双向转发线程
25
+ client_thread = Thread(
26
+ target=self.data_forward,
27
+ args=(self.client_socket, self.server_socket, "设备->软件")
28
+ )
29
+ server_thread = Thread(
30
+ target=self.data_forward,
31
+ args=(self.server_socket, self.client_socket, "软件->设备")
32
+ )
33
+
34
+ # 启动线程
35
+ client_thread.start()
36
+ server_thread.start()
37
+
38
+ # 等待线程结束
39
+ client_thread.join()
40
+ server_thread.join()
41
+
42
+ except Exception as e:
43
+ logger.error(f"连接异常: {str(e)}")
44
+ self.client_socket.close()
45
+ finally:
46
+ logger.error(f"连接已结束")
47
+ self.client_socket.close()
48
+
49
+ def data_forward(self, src, dest, direction, callback=None):
50
+ """
51
+ 数据转发函数
52
+ :param src: 源socket连接
53
+ :param dest: 目标socket连接
54
+ :param direction: 转发方向描述
55
+ """
56
+ try:
57
+ logger.debug(f"[{direction}] 转发开始")
58
+ while True:
59
+ data = src.recv(4096*1024) # 接收数据缓冲区设为4KB
60
+ if not data:
61
+ break
62
+ logger.info(f"[{direction}] 转发指令: {hex(int.from_bytes(data[12:14], 'little'))} ")
63
+ logger.debug(f"[{direction}] 转发 {data.hex()} ")
64
+ if callback:
65
+ callback(data)
66
+ dest.sendall(data)
67
+ logger.debug(f"[{direction}] 转发 {len(data)} 字节")
68
+ except ConnectionResetError:
69
+ logger.error(f"[{direction}] 连接已关闭")
70
+ finally:
71
+ src.close()
72
+ dest.close()
73
+
74
+ # def device_message(self, data):
75
+ # # 解析数据包
76
+ # self.parser.append(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: qlsdk2
3
- Version: 0.3.0a2
3
+ Version: 0.4.0a1
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -0,0 +1,40 @@
1
+ qlsdk/__init__.py,sha256=mjjFQ8LEHgwMmud5ie2Tgl2aOafnz53gkcAju0q7T0c,446
2
+ qlsdk/ar4/__init__.py,sha256=iR0bFanDwqSxSEM1nCcge1l-GSIWjwnMeRQa47wGrds,4752
3
+ qlsdk/ar4m/__init__.py,sha256=Z13X53WKmKp6MW_fA1FUPdQgJxhxxuyzemJWPS_CEHA,639
4
+ qlsdk/core/__init__.py,sha256=nFXRJIMxV8nnHIn0doWey0pDZvfmhMu8J4ECMfzAZgc,126
5
+ qlsdk/core/device.py,sha256=dPoQUwd78-Ieef6ZcBI7isVm0W8aYarqBy6UHP7Yjoc,587
6
+ qlsdk/core/exception.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ qlsdk/core/local.py,sha256=vbison4XZtS4SNYLJ9CqBhetEcukdviTWmvtdA1efkQ,811
8
+ qlsdk/core/utils.py,sha256=eAg6oa8lGoVdaPcUAR128J1-Nwf_ghDPehUVtbw_WCc,4036
9
+ qlsdk/core/crc/__init__.py,sha256=kaYSr6KN5g4U49xlxAvT2lnEeGtwX4Dz1ArwKDvUIIY,143
10
+ qlsdk/core/crc/crctools.py,sha256=sDeE6CMccQX2cRAyMQK0SZUk1fa50XMuwqXau5UX5C8,4242
11
+ qlsdk/core/entity/__init__.py,sha256=xEROyxn-KX1aa1XomOmYgwfeYOx52ZXDAs0SftAEOD8,3289
12
+ qlsdk/core/filter/__init__.py,sha256=YIWIzDUKN30mq2JTr53ZGblggZfC_rLUp2FSRrsQFgU,36
13
+ qlsdk/core/filter/norch.py,sha256=5RdIBX5eqs5w5nmVAnCB3ESSuAT_vVBZ2g-dg6HMZdY,1858
14
+ qlsdk/core/message/__init__.py,sha256=sHuavOyHf4bhH6VdDpTA1EsCh7Q-XsPHcFiItpVz3Rs,51
15
+ qlsdk/core/message/command.py,sha256=wk5DAsvlAtUikGZ4a8honRZf-iQQaIypjzud8GpnHtU,11311
16
+ qlsdk/core/message/tcp.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ qlsdk/core/message/udp.py,sha256=KS_pKZcCEFhNGABCOZTOHiVFkfPNZl_0K5RXppwMIvA,3085
18
+ qlsdk/persist/__init__.py,sha256=OR8mnPTsktxw67G-z0VsPWJXInkKircMTHQdibmmz3M,63
19
+ qlsdk/persist/edf.py,sha256=usgFjZJVcfAUwSy6cJtRdgjdqMgOS4viLCDGcwAGMZE,7106
20
+ qlsdk/persist/rsc_edf.py,sha256=DM871w2-2vXNSEIzNnMLzW_ftCZSM8NhPSmu56h5jz4,8692
21
+ qlsdk/rsc/__init__.py,sha256=bR678DilIIK7tFP65tscPUI7Bp4VN0ndVQWt6bnQKGg,197
22
+ qlsdk/rsc/device_manager.py,sha256=ZUuhwamKky_Z9jI9_ww5S8ZcGZpSTkjWwP1kRZEOK2U,3597
23
+ qlsdk/rsc/discover.py,sha256=cFDOADWD6Y0oE4x37f8j14a18Udwi6F0uSTbiC_1N1E,3039
24
+ qlsdk/rsc/eegion.py,sha256=lxrktO-3Z_MYdFIwc4NxvgLM5AL5kU3UItjH6tsKmHY,11670
25
+ qlsdk/rsc/entity.py,sha256=qmLvJ6gCR9r7y60m91L20FJYOaAHwH6k6aXecqHurjU,21062
26
+ qlsdk/rsc/paradigm.py,sha256=Iy_eU_yZLHiUZlLNPvqke5GDVJFnMEQ_PQ6cRxTm2BM,12606
27
+ qlsdk/rsc/proxy.py,sha256=9CPdGNGWremwBUh4GvlXAykYB-x_BEPPLqsNvwuwIDE,2736
28
+ qlsdk/rsc/command/__init__.py,sha256=D5pBE2leqFG-IPU2pDAGuLsqHmc0wRPrBOXR0-K8d6k,8117
29
+ qlsdk/rsc/command/message.py,sha256=bcuUDJj0CMekcZMNhKrzIpkHtDC-QBCs3dJG6Bx_PHc,8723
30
+ qlsdk/sdk/__init__.py,sha256=v9LKP-5qXCqnAsCkiRE9LDb5Tagvl_Qd_fqrw7y9yd4,68
31
+ qlsdk/sdk/ar4sdk.py,sha256=ZILuUVgUZN7-LNQm4xdItxmZWGwvFt4oWUKGez1BCLI,27101
32
+ qlsdk/sdk/hub.py,sha256=uEOGZBZtMDCWlV8G2TZe6FAo6eTPcwHAW8zdqr1eq_0,1571
33
+ qlsdk/sdk/libs/libAr4SDK.dll,sha256=kZp9_DRwPdAJ5OgTFQSqS8tEETxUs7YmmETuBP2g60U,15402132
34
+ qlsdk/sdk/libs/libwinpthread-1.dll,sha256=W77ySaDQDi0yxpnQu-ifcU6-uHKzmQpcvsyx2J9j5eg,52224
35
+ qlsdk/x8/__init__.py,sha256=XlQn3Af_HA7bGrxhv3RZKWMob_SnmissXiso7OH1UgI,4750
36
+ qlsdk/x8m/__init__.py,sha256=cLeUqEEj65qXw4Qa4REyxoLh6T24anSqPaKe9_lR340,634
37
+ qlsdk2-0.4.0a1.dist-info/METADATA,sha256=-yHCdrfe8EJRciRLY0Pl2bjjrFYtPkHuOyiYLPb6ey8,7066
38
+ qlsdk2-0.4.0a1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
39
+ qlsdk2-0.4.0a1.dist-info/top_level.txt,sha256=2CHzn0SY-NIBVyBl07Suh-Eo8oBAQfyNPtqQ_aDatBg,6
40
+ qlsdk2-0.4.0a1.dist-info/RECORD,,