biosignal-device-interface 0.2.1a1__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- biosignal_device_interface/constants/devices/__init__.py +3 -3
- biosignal_device_interface/constants/devices/core/base_device_constants.py +61 -61
- biosignal_device_interface/constants/devices/otb/otb_muovi_constants.py +129 -129
- biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +313 -313
- biosignal_device_interface/constants/devices/otb/otb_quattrocento_light_constants.py +59 -59
- biosignal_device_interface/constants/devices/otb/otb_syncstation_constants.py +233 -233
- biosignal_device_interface/constants/plots/color_palette.py +59 -59
- biosignal_device_interface/devices/__init__.py +17 -17
- biosignal_device_interface/devices/core/base_device.py +424 -412
- biosignal_device_interface/devices/otb/__init__.py +29 -29
- biosignal_device_interface/devices/otb/otb_muovi.py +290 -290
- biosignal_device_interface/devices/otb/otb_quattrocento.py +332 -332
- biosignal_device_interface/devices/otb/otb_quattrocento_light.py +210 -210
- biosignal_device_interface/devices/otb/otb_syncstation.py +407 -407
- biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +51 -51
- biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +130 -130
- biosignal_device_interface/gui/device_template_widgets/core/base_multiple_devices_widget.py +108 -108
- biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +44 -44
- biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +158 -158
- biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +158 -158
- biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +174 -174
- biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_widget.py +260 -260
- biosignal_device_interface/gui/device_template_widgets/otb/otb_syncstation_widget.py +262 -262
- biosignal_device_interface/gui/plot_widgets/biosignal_plot_widget.py +500 -501
- biosignal_device_interface/gui/ui/devices_template_widget.ui +38 -38
- biosignal_device_interface/gui/ui/otb_muovi_plus_template_widget.ui +171 -171
- biosignal_device_interface/gui/ui/otb_muovi_template_widget.ui +171 -171
- biosignal_device_interface/gui/ui/otb_quattrocento_light_template_widget.ui +266 -266
- biosignal_device_interface/gui/ui/otb_quattrocento_template_widget.ui +415 -415
- biosignal_device_interface/gui/ui/otb_syncstation_template_widget.ui +732 -732
- biosignal_device_interface/gui/ui_compiled/devices_template_widget.py +56 -56
- biosignal_device_interface/gui/ui_compiled/otb_muovi_plus_template_widget.py +153 -153
- biosignal_device_interface/gui/ui_compiled/otb_muovi_template_widget.py +153 -153
- biosignal_device_interface/gui/ui_compiled/otb_quattrocento_light_template_widget.py +217 -217
- biosignal_device_interface/gui/ui_compiled/otb_quattrocento_template_widget.py +318 -318
- biosignal_device_interface/gui/ui_compiled/otb_syncstation_template_widget.py +495 -495
- {biosignal_device_interface-0.2.1a1.dist-info → biosignal_device_interface-0.2.2.dist-info}/METADATA +3 -2
- biosignal_device_interface-0.2.2.dist-info/RECORD +46 -0
- {biosignal_device_interface-0.2.1a1.dist-info → biosignal_device_interface-0.2.2.dist-info}/WHEEL +1 -1
- {biosignal_device_interface-0.2.1a1.dist-info → biosignal_device_interface-0.2.2.dist-info/licenses}/LICENSE +675 -675
- biosignal_device_interface-0.2.1a1.dist-info/RECORD +0 -46
|
@@ -1,412 +1,424 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Base Device class for real-time interfaces to hardware devices.
|
|
3
|
-
Developer: Dominik I. Braun
|
|
4
|
-
Contact: dome.braun@fau.de
|
|
5
|
-
Last Update: 2024-06-05
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
# Python Libraries
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
from typing import TYPE_CHECKING, Union, Dict, Tuple
|
|
11
|
-
|
|
12
|
-
from abc import abstractmethod
|
|
13
|
-
import socket
|
|
14
|
-
import psutil
|
|
15
|
-
from PySide6.QtCore import QObject, Signal, QTimer
|
|
16
|
-
import numpy as np
|
|
17
|
-
import re
|
|
18
|
-
|
|
19
|
-
# Local Libraries
|
|
20
|
-
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
21
|
-
DeviceType,
|
|
22
|
-
DEVICE_NAME_DICT,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# Type Checking
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
# Python Libraries
|
|
28
|
-
from PySide6.QtWidgets import QMainWindow, QWidget
|
|
29
|
-
from PySide6.QtNetwork import QTcpSocket, QUdpSocket, QTcpServer
|
|
30
|
-
from PySide6.QtSerialPort import QSerialPort
|
|
31
|
-
from aenum import Enum
|
|
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
|
-
self.
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
self.
|
|
71
|
-
self.
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
self.
|
|
77
|
-
self.
|
|
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
|
-
@abstractmethod
|
|
173
|
-
def
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
"""
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
"""
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
1
|
+
"""
|
|
2
|
+
Base Device class for real-time interfaces to hardware devices.
|
|
3
|
+
Developer: Dominik I. Braun
|
|
4
|
+
Contact: dome.braun@fau.de
|
|
5
|
+
Last Update: 2024-06-05
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Python Libraries
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from typing import TYPE_CHECKING, Union, Dict, Tuple
|
|
11
|
+
|
|
12
|
+
from abc import abstractmethod
|
|
13
|
+
import socket
|
|
14
|
+
import psutil
|
|
15
|
+
from PySide6.QtCore import QObject, Signal, QTimer
|
|
16
|
+
import numpy as np
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
# Local Libraries
|
|
20
|
+
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
21
|
+
DeviceType,
|
|
22
|
+
DEVICE_NAME_DICT,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Type Checking
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
# Python Libraries
|
|
28
|
+
from PySide6.QtWidgets import QMainWindow, QWidget
|
|
29
|
+
from PySide6.QtNetwork import QTcpSocket, QUdpSocket, QTcpServer
|
|
30
|
+
from PySide6.QtSerialPort import QSerialPort
|
|
31
|
+
from aenum import Enum
|
|
32
|
+
|
|
33
|
+
import warnings
|
|
34
|
+
|
|
35
|
+
# Set to keep track of seen error messages
|
|
36
|
+
_seen_error_messages = set()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def warn_once(e):
|
|
40
|
+
"""
|
|
41
|
+
Issue a warning for the given error, but only once per unique message.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
e: Exception instance whose message will be used to track uniqueness.
|
|
45
|
+
"""
|
|
46
|
+
error_message = str(e)
|
|
47
|
+
if error_message not in _seen_error_messages:
|
|
48
|
+
warnings.warn(f"An error occurred: {error_message}", UserWarning)
|
|
49
|
+
_seen_error_messages.add(error_message)
|
|
50
|
+
|
|
51
|
+
class BaseDevice(QObject):
|
|
52
|
+
# Signals
|
|
53
|
+
connect_toggled: Signal = Signal(bool)
|
|
54
|
+
configure_toggled: Signal = Signal(bool)
|
|
55
|
+
stream_toggled: Signal = Signal(bool)
|
|
56
|
+
data_available: Signal = Signal(np.ndarray)
|
|
57
|
+
biosignal_data_available: Signal = Signal(np.ndarray)
|
|
58
|
+
auxiliary_data_available: Signal = Signal(np.ndarray)
|
|
59
|
+
|
|
60
|
+
def __init__(self, parent: Union[QMainWindow, QWidget] = None, **kwargs) -> None:
|
|
61
|
+
super().__init__(parent)
|
|
62
|
+
|
|
63
|
+
# Device Parameters
|
|
64
|
+
self._device_type: DeviceType = None
|
|
65
|
+
|
|
66
|
+
# Device Information
|
|
67
|
+
self._sampling_frequency: int | None = None
|
|
68
|
+
self._number_of_channels: int | None = None
|
|
69
|
+
self._number_of_biosignal_channels: int | None = None
|
|
70
|
+
self._biosignal_channel_indices: list[int] | None = None
|
|
71
|
+
self._number_of_auxiliary_channels: int | None = None
|
|
72
|
+
self._auxiliary_channel_indices: list[int] | None = None
|
|
73
|
+
self._samples_per_frame: int | None = None
|
|
74
|
+
self._bytes_per_sample: int | None = None
|
|
75
|
+
|
|
76
|
+
self._conversion_factor_biosignal: float = None # Returns mV
|
|
77
|
+
self._conversion_factor_auxiliary: float = None # Returns mV
|
|
78
|
+
|
|
79
|
+
# Connection Parameters
|
|
80
|
+
self._interface: QTcpServer | QTcpSocket | QUdpSocket | QSerialPort | None = (
|
|
81
|
+
None
|
|
82
|
+
)
|
|
83
|
+
self._connection_settings: Tuple[str, int] | None = None
|
|
84
|
+
self._buffer_size: int | None = None
|
|
85
|
+
self._received_bytes: bytearray | None = None
|
|
86
|
+
|
|
87
|
+
self._connection_timeout_timer: QTimer = QTimer()
|
|
88
|
+
self._connection_timeout_timer.setSingleShot(True)
|
|
89
|
+
self._connection_timeout_timer.timeout.connect(self._disconnect_from_device)
|
|
90
|
+
self._connection_timeout_timer.setInterval(1000)
|
|
91
|
+
|
|
92
|
+
# Device Status
|
|
93
|
+
self.is_connected: bool = False
|
|
94
|
+
self._is_configured: bool = False
|
|
95
|
+
self._is_streaming: bool = False
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def _connect_to_device(self) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Function to attempt a connection to the devices.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
bool:
|
|
104
|
+
Success of the connection attempt.
|
|
105
|
+
"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def _make_request(self) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Requests a connection or checks if someone connected to the server.
|
|
112
|
+
After connection is successful, the Signal connected_signal emits True
|
|
113
|
+
and sets the current state is_connected to True.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
bool:
|
|
117
|
+
Returns True if request was successfully. False if not.
|
|
118
|
+
"""
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def _disconnect_from_device(self) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Closes the connection to the device.
|
|
125
|
+
|
|
126
|
+
self.interface closes and is set to None.
|
|
127
|
+
Device state is_connected is set to False.
|
|
128
|
+
Signal connected_signal emits False.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
bool:
|
|
132
|
+
Success of the disconnection attempt.
|
|
133
|
+
"""
|
|
134
|
+
self.is_connected = False
|
|
135
|
+
self.connect_toggled.emit(False)
|
|
136
|
+
self._is_configured = False
|
|
137
|
+
self.configure_toggled.emit(False)
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def configure_device(self, params: Dict[str, Union[Enum, Dict[str, Enum]]]) -> None: # type: ignore
|
|
141
|
+
"""
|
|
142
|
+
Sends a configuration byte sequence based on selected params to the device.
|
|
143
|
+
An overview of possible configurations can be seen in
|
|
144
|
+
biosignal_device_interface/constants/{device}.py.
|
|
145
|
+
|
|
146
|
+
E.g., enums/sessantaquattro.py
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
params (Dict[str, Union[Enum, Dict[str, Enum]]]):
|
|
151
|
+
Dictionary that holds the configuration settings
|
|
152
|
+
to which the device should be configured to.
|
|
153
|
+
|
|
154
|
+
The first one should be the attributes (configuration mode) name,
|
|
155
|
+
and the second its respective value. Orient yourself on the
|
|
156
|
+
enums of the device to choose the correct configuration settings.
|
|
157
|
+
"""
|
|
158
|
+
self._update_configuration_parameters(params)
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
def _start_streaming(self) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Sends the command to start the streaming to the device.
|
|
164
|
+
|
|
165
|
+
if successful:
|
|
166
|
+
Device state is_streaming is set to True.
|
|
167
|
+
Signal streaming_signal emits True.
|
|
168
|
+
"""
|
|
169
|
+
self._is_streaming = True
|
|
170
|
+
self.stream_toggled.emit(True)
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def _stop_streaming(self) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Sends the command to stop the streaing to the device
|
|
176
|
+
|
|
177
|
+
if successful:
|
|
178
|
+
Device state is_streaming is set to False.
|
|
179
|
+
Signal streaming_signal emits False.
|
|
180
|
+
"""
|
|
181
|
+
self._is_streaming = False
|
|
182
|
+
self.stream_toggled.emit(False)
|
|
183
|
+
|
|
184
|
+
@abstractmethod
|
|
185
|
+
def clear_socket(self) -> None:
|
|
186
|
+
"""Reads all the bytes from the buffer."""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def _read_data(self) -> None:
|
|
191
|
+
"""
|
|
192
|
+
This function is called when bytes are ready to be read in the buffer.
|
|
193
|
+
After reading the bytes from the buffer, _process_data is called to
|
|
194
|
+
decode and process the raw data.
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
@abstractmethod
|
|
199
|
+
def _process_data(self, input: bytearray) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Decodes the transmitted bytes and convert them to respective
|
|
202
|
+
output format (e.g., mV).
|
|
203
|
+
|
|
204
|
+
Emits the processed data through the Signal data_available_signal
|
|
205
|
+
which can be connected to a function using:
|
|
206
|
+
{self.device}.data_available_signal.connect(your_custom_function).
|
|
207
|
+
|
|
208
|
+
This works perfectly fine outside of this class.
|
|
209
|
+
|
|
210
|
+
Your custom function your_custom_function needs to have a parameter
|
|
211
|
+
"data" which is of type np.ndarray.
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
In case that the current configuration of the device was requested,
|
|
215
|
+
the configuration is provided through the Signal
|
|
216
|
+
configuration_available_signal that emits the current parameters
|
|
217
|
+
in a dictionary.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
input (bytearray):
|
|
221
|
+
Bytearray of the transmitted raw data.
|
|
222
|
+
"""
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
def _update_configuration_parameters(
|
|
226
|
+
self, params: Dict[str, Union[Enum, Dict[str, Enum]]]
|
|
227
|
+
) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Updates the device attributes with the new configuration parameters.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
params (Dict[str, Union[Enum, Dict[str, Enum]]]):
|
|
233
|
+
Dictionary that holds the configuration settings
|
|
234
|
+
to which the device should be configured to.
|
|
235
|
+
|
|
236
|
+
The first one should be the attributes (configuration mode) name,
|
|
237
|
+
and the second its respective value. Orient yourself on the
|
|
238
|
+
enums of the device to choose the correct configuration settings.
|
|
239
|
+
"""
|
|
240
|
+
for key, value in params.items():
|
|
241
|
+
key = "_" + key
|
|
242
|
+
if hasattr(self, key):
|
|
243
|
+
setattr(self, key, value)
|
|
244
|
+
else:
|
|
245
|
+
print(
|
|
246
|
+
f"Attribute '{key}' not found in the class of {self._device_type.name}",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def _extract_biosignal_data(
|
|
250
|
+
self, data: np.ndarray, milli_volts: bool = True
|
|
251
|
+
) -> np.ndarray:
|
|
252
|
+
"""
|
|
253
|
+
Extracts the biosignals from the transmitted data.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
data (np.ndarray):
|
|
257
|
+
Raw data that got transmitted.
|
|
258
|
+
|
|
259
|
+
milli_volts (bool, optional):
|
|
260
|
+
If True, the biosignal data is converted to milli volts.
|
|
261
|
+
Defaults to True.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
np.ndarray:
|
|
265
|
+
Extracted biosignal channels.
|
|
266
|
+
"""
|
|
267
|
+
biosignal_data = data[self._biosignal_channel_indices]
|
|
268
|
+
if milli_volts:
|
|
269
|
+
return biosignal_data * self._conversion_factor_biosignal
|
|
270
|
+
return biosignal_data
|
|
271
|
+
|
|
272
|
+
def _extract_auxiliary_data(
|
|
273
|
+
self, data: np.ndarray, milli_volts: bool = True
|
|
274
|
+
) -> np.ndarray:
|
|
275
|
+
"""
|
|
276
|
+
Extract auxiliary channels from the transmitted data.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
data (np.ndarray):
|
|
280
|
+
Raw data that got transmitted.
|
|
281
|
+
milli_volts (bool, optional):
|
|
282
|
+
If True, the auxiliary data is converted to milli volts.
|
|
283
|
+
Defaults to True.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
np.ndarray:
|
|
287
|
+
Extracted auxiliary channel data.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
if milli_volts:
|
|
291
|
+
return (
|
|
292
|
+
data[self._auxiliary_channel_indices]
|
|
293
|
+
* self._conversion_factor_auxiliary
|
|
294
|
+
)
|
|
295
|
+
return data[self._auxiliary_channel_indices]
|
|
296
|
+
|
|
297
|
+
def toggle_connection(self, settings: Tuple[str, int] = None) -> bool:
|
|
298
|
+
"""
|
|
299
|
+
Toggles the connection to the device.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
settings (Tuple[str, int], optional):
|
|
303
|
+
If CommunicationProtocol.TCPIP:
|
|
304
|
+
Tuple[0] = IP -> string
|
|
305
|
+
Tuple[1] = Port -> int
|
|
306
|
+
|
|
307
|
+
If CommunicationProtocol.SERIAL pr CommunicationProtocol.USB:
|
|
308
|
+
Tuple[0] = COM Port -> string
|
|
309
|
+
Tuple[1] = Baudrate -> int
|
|
310
|
+
|
|
311
|
+
Defaults to None.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
bool:
|
|
315
|
+
True if connection attempt was successfully. False if not.
|
|
316
|
+
"""
|
|
317
|
+
self._connection_settings = settings
|
|
318
|
+
|
|
319
|
+
if self.is_connected:
|
|
320
|
+
if self._is_streaming:
|
|
321
|
+
self.toggle_streaming()
|
|
322
|
+
|
|
323
|
+
success: bool = self._disconnect_from_device()
|
|
324
|
+
else:
|
|
325
|
+
success: bool = self._connect_to_device()
|
|
326
|
+
|
|
327
|
+
return success
|
|
328
|
+
|
|
329
|
+
def toggle_streaming(self) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Toggles the current state of the streaming.
|
|
332
|
+
If device is streaming, the streaming is stopped and vice versa.
|
|
333
|
+
"""
|
|
334
|
+
if self._is_streaming:
|
|
335
|
+
self._stop_streaming()
|
|
336
|
+
self.clear_socket()
|
|
337
|
+
else:
|
|
338
|
+
self._start_streaming()
|
|
339
|
+
|
|
340
|
+
self.clear_socket()
|
|
341
|
+
|
|
342
|
+
def get_device_information(self) -> Dict[str, Enum | int | float | str]:
|
|
343
|
+
"""
|
|
344
|
+
Gets the current configuration of the device.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Dict[str, Enum | int | float | str]:
|
|
348
|
+
Dictionary that holds information about the
|
|
349
|
+
current device configuration and status.
|
|
350
|
+
"""
|
|
351
|
+
return {
|
|
352
|
+
"name": DEVICE_NAME_DICT[self._device_type],
|
|
353
|
+
"sampling_frequency": self._sampling_frequency,
|
|
354
|
+
"number_of_channels": self._number_of_channels,
|
|
355
|
+
"number_of_biosignal_channels": self._number_of_biosignal_channels,
|
|
356
|
+
"number_of_auxiliary_channels": self._number_of_auxiliary_channels,
|
|
357
|
+
"samples_per_frame": self._samples_per_frame,
|
|
358
|
+
"conversion_factor_biosignal": self._conversion_factor_biosignal,
|
|
359
|
+
"conversion_factor_auxiliary": self._conversion_factor_auxiliary,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def check_valid_ip(self, ip_address: str) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
Checks if the provided IP is valid.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
ip (str): IP to be checked.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
bool: True if IP is valid. False if not.
|
|
371
|
+
"""
|
|
372
|
+
ip_pattern = re.compile(
|
|
373
|
+
r"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\."
|
|
374
|
+
r"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\."
|
|
375
|
+
r"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\."
|
|
376
|
+
r"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return bool(ip_pattern.match(ip_address))
|
|
380
|
+
|
|
381
|
+
def check_valid_port(self, port: str) -> bool:
|
|
382
|
+
"""
|
|
383
|
+
Checks if the provided port is valid.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
port (str): Port to be checked.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
bool: True if port is valid. False if not.
|
|
390
|
+
"""
|
|
391
|
+
try:
|
|
392
|
+
port_num = int(port)
|
|
393
|
+
return 0 <= port_num <= 65535
|
|
394
|
+
except ValueError:
|
|
395
|
+
return False
|
|
396
|
+
|
|
397
|
+
def get_server_wifi_ip_address(self) -> list[str]:
|
|
398
|
+
"""
|
|
399
|
+
Returns the IP address of the host server.
|
|
400
|
+
"""
|
|
401
|
+
try:
|
|
402
|
+
# Get all network interfaces
|
|
403
|
+
interfaces = psutil.net_if_addrs()
|
|
404
|
+
|
|
405
|
+
addresses_return = []
|
|
406
|
+
|
|
407
|
+
# Iterate through interfaces to find the one associated with WiFi
|
|
408
|
+
for interface, addresses in interfaces.items():
|
|
409
|
+
if (
|
|
410
|
+
any(keyword in interface.lower() for keyword in ["wlan", "wi-fi", "wifi", "wireless", "en0", "wlp", "wln", "wl"])
|
|
411
|
+
):
|
|
412
|
+
for address in addresses:
|
|
413
|
+
# Check if the address is an IPv4 address and not a loopback or virtual address
|
|
414
|
+
if (
|
|
415
|
+
address.family == socket.AF_INET
|
|
416
|
+
and not address.address.startswith("127.")
|
|
417
|
+
):
|
|
418
|
+
addresses_return.append(address.address)
|
|
419
|
+
|
|
420
|
+
return addresses_return[::-1] if addresses_return else [""]
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
warn_once("Error occurred while getting server IP address: " + str(e))
|
|
424
|
+
return [""]
|