biosignal-device-interface 0.1.12b0__py3-none-any.whl → 0.1.32a1__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.
Files changed (29) hide show
  1. biosignal_device_interface/constants/devices/core/base_device_constants.py +10 -0
  2. biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +313 -0
  3. biosignal_device_interface/constants/devices/otb/otb_syncstation_constants.py +233 -0
  4. biosignal_device_interface/devices/__init__.py +2 -0
  5. biosignal_device_interface/devices/core/base_device.py +8 -9
  6. biosignal_device_interface/devices/otb/__init__.py +8 -0
  7. biosignal_device_interface/devices/otb/otb_muovi.py +4 -5
  8. biosignal_device_interface/devices/otb/otb_quattrocento.py +332 -0
  9. biosignal_device_interface/devices/otb/otb_quattrocento_light.py +14 -15
  10. biosignal_device_interface/devices/otb/otb_syncstation.py +407 -0
  11. biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +8 -0
  12. biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +17 -8
  13. biosignal_device_interface/gui/device_template_widgets/core/base_multiple_devices_widget.py +7 -4
  14. biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +8 -0
  15. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +9 -9
  16. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +9 -9
  17. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +59 -55
  18. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_widget.py +260 -0
  19. biosignal_device_interface/gui/device_template_widgets/otb/otb_syncstation_widget.py +262 -0
  20. biosignal_device_interface/gui/plot_widgets/biosignal_plot_widget.py +9 -4
  21. biosignal_device_interface/gui/ui/otb_quattrocento_template_widget.ui +415 -0
  22. biosignal_device_interface/gui/ui/otb_syncstation_template_widget.ui +732 -0
  23. biosignal_device_interface/gui/ui_compiled/otb_quattrocento_template_widget.py +318 -0
  24. biosignal_device_interface/gui/ui_compiled/otb_syncstation_template_widget.py +495 -0
  25. biosignal_device_interface-0.1.32a1.dist-info/LICENSE +675 -0
  26. {biosignal_device_interface-0.1.12b0.dist-info → biosignal_device_interface-0.1.32a1.dist-info}/METADATA +7 -17
  27. {biosignal_device_interface-0.1.12b0.dist-info → biosignal_device_interface-0.1.32a1.dist-info}/RECORD +28 -18
  28. biosignal_device_interface-0.1.12b0.dist-info/LICENSE +0 -395
  29. {biosignal_device_interface-0.1.12b0.dist-info → biosignal_device_interface-0.1.32a1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,407 @@
1
+ """
2
+ Device class for real-time interfacing the OTB Syncstation device.
3
+ Developer: Dominik I. Braun
4
+ Contact: dome.braun@fau.de
5
+ Last Update: 2025-01-09
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import TYPE_CHECKING, Union, Dict
10
+ from PySide6.QtNetwork import QTcpSocket, QHostAddress
11
+ from PySide6.QtCore import QIODevice
12
+ import numpy as np
13
+
14
+ # Local Libraries
15
+ from biosignal_device_interface.constants.devices.otb.otb_syncstation_constants import (
16
+ PROBE_CHARACTERISTICS_DICT,
17
+ SYNCSTATION_CHARACTERISTICS_DICT,
18
+ SYNCSTATION_CONVERSION_FACTOR_DICT,
19
+ SyncStationDetectionMode,
20
+ SyncStationProbeConfigMode,
21
+ SyncStationRecOnMode,
22
+ SyncStationWorkingMode,
23
+ )
24
+ from biosignal_device_interface.devices.core.base_device import BaseDevice
25
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
26
+ DeviceType,
27
+ DeviceChannelTypes,
28
+ )
29
+
30
+
31
+ if TYPE_CHECKING:
32
+ from PySide6.QtWidgets import QMainWindow, QWidget
33
+ from aenum import Enum
34
+
35
+
36
+ class OTBSyncStation(BaseDevice):
37
+ """
38
+ Device class for real-time interfacing the OTB Syncstation device.
39
+ The SyncStation class is using a TCP/IP protocol to communicate with the device.
40
+ """
41
+
42
+ def __init__(self, parent: Union[QMainWindow, QWidget] = None) -> None:
43
+ """
44
+ Constructor of the OTBSyncStation class.
45
+
46
+ Args:
47
+ parent (Union[QMainWindow, QWidget]): The parent object of the device. Defaults to None.
48
+ """
49
+ super().__init__(parent)
50
+
51
+ # Device Parameters
52
+ self._device_type = DeviceType.OTB_SYNCSTATION
53
+
54
+ # Connection Parameters
55
+ self._interface: QTcpSocket = QTcpSocket()
56
+
57
+ # Configuration Parameters
58
+ self._configuration_command_A: bytearray = None
59
+ self._configuration_command_B: bytearray = None
60
+
61
+ # Configuration Parameters A
62
+ self._rec_on_mode: SyncStationRecOnMode = None
63
+ self._working_mode: SyncStationWorkingMode = None
64
+ self._number_of_probes: int = None
65
+ self._bytes_configuration_A: Dict[
66
+ SyncStationProbeConfigMode, Dict[str, SyncStationDetectionMode | bool]
67
+ ] = None
68
+
69
+ # Configuration Parameters B
70
+ self._bytes_configuration_B: Dict[str, int] = None
71
+
72
+ def _connect_to_device(self) -> bool:
73
+ super()._connect_to_device()
74
+
75
+ self._received_bytes: bytearray = bytearray()
76
+ self._make_request()
77
+
78
+ def _make_request(self) -> bool:
79
+ super()._make_request()
80
+
81
+ self._interface.connectToHost(
82
+ QHostAddress(self._connection_settings[0]),
83
+ self._connection_settings[1],
84
+ QIODevice.ReadWrite,
85
+ )
86
+
87
+ if not self._interface.waitForConnected(1000):
88
+ self._disconnect_from_device()
89
+ return False
90
+
91
+ self.is_connected = True
92
+ self.connect_toggled.emit(True)
93
+
94
+ self._interface.readyRead.connect(self._read_data)
95
+ return True
96
+
97
+ def _disconnect_from_device(self) -> None:
98
+ super()._disconnect_from_device()
99
+
100
+ self._interface.disconnectFromHost()
101
+ self._interface.readyRead.disconnect(self._read_data)
102
+ self._interface.close()
103
+
104
+ self.is_connected = False
105
+ self.connect_toggled.emit(False)
106
+
107
+ def configure_device(self, params) -> None:
108
+ super().configure_device(params)
109
+
110
+ success = self._configure_byte_sequence_A()
111
+
112
+ if not success:
113
+ print("Unable to configure device.")
114
+ return
115
+
116
+ self._send_configuration_to_device()
117
+
118
+ self._is_configured = True
119
+ self.configure_toggled.emit(True)
120
+
121
+ def _configure_byte_sequence_A(self) -> None:
122
+ start_byte = 0
123
+ start_byte += (self._rec_on_mode.value - 1) << 6
124
+
125
+ self._sampling_frequency = SYNCSTATION_CHARACTERISTICS_DICT[
126
+ "channel_information"
127
+ ][self._working_mode]["sampling_frequency"]
128
+ self._bytes_per_sample = SYNCSTATION_CHARACTERISTICS_DICT[
129
+ "channel_information"
130
+ ][self._working_mode]["bytes_per_sample"]
131
+
132
+ self._configuration_command_A = bytearray()
133
+ self._number_of_channels = 0
134
+ self._number_of_bytes = 0
135
+
136
+ self._number_of_biosignal_channels = 0
137
+ self._number_of_auxiliary_channels = 0
138
+ self._biosignal_channel_indices = []
139
+ self._auxiliary_channel_indices = []
140
+
141
+ for key, value in self._bytes_configuration_A.items():
142
+ probe_command = 0
143
+ probe_command += (key.value - 1) << 4
144
+ probe_command += (self._working_mode.value - 1) << 3
145
+ probe_command += (value["detection_mode"].value - 1) << 1
146
+ probe_command += int(value["probe_status"])
147
+
148
+ if value["probe_status"]:
149
+ self._configuration_command_A.append(probe_command)
150
+ channels = PROBE_CHARACTERISTICS_DICT[key][DeviceChannelTypes.ALL]
151
+ biosignal_channels = PROBE_CHARACTERISTICS_DICT[key][
152
+ DeviceChannelTypes.BIOSIGNAL
153
+ ]
154
+ auxiliary_channels = PROBE_CHARACTERISTICS_DICT[key][
155
+ DeviceChannelTypes.AUXILIARY
156
+ ]
157
+
158
+ self._biosignal_channel_indices.append(
159
+ np.arange(
160
+ self._number_of_channels,
161
+ self._number_of_channels + biosignal_channels,
162
+ )
163
+ )
164
+
165
+ self._auxiliary_channel_indices.append(
166
+ np.arange(
167
+ self._number_of_channels + biosignal_channels,
168
+ self._number_of_channels + channels,
169
+ )
170
+ )
171
+
172
+ self._number_of_channels += channels
173
+ self._number_of_biosignal_channels += biosignal_channels
174
+ self._number_of_auxiliary_channels += auxiliary_channels
175
+
176
+ self._conversion_factor_biosignal = SYNCSTATION_CONVERSION_FACTOR_DICT[
177
+ value["detection_mode"]
178
+ ]
179
+ self._conversion_factor_auxiliary = self._conversion_factor_biosignal
180
+
181
+ self._biosignal_channel_indices = np.hstack(self._biosignal_channel_indices)
182
+ self._auxiliary_channel_indices = np.hstack(self._auxiliary_channel_indices)
183
+ self._number_of_bytes = self._number_of_channels * self._bytes_per_sample
184
+
185
+ # Add SyncStation Channels
186
+ self._number_of_channels += SYNCSTATION_CHARACTERISTICS_DICT[
187
+ DeviceChannelTypes.ALL
188
+ ]
189
+ self._number_of_auxiliary_channels += SYNCSTATION_CHARACTERISTICS_DICT[
190
+ DeviceChannelTypes.ALL
191
+ ]
192
+
193
+ self._number_of_bytes += (
194
+ SYNCSTATION_CHARACTERISTICS_DICT[DeviceChannelTypes.ALL]
195
+ * SYNCSTATION_CHARACTERISTICS_DICT["bytes_per_sample"]
196
+ )
197
+
198
+ self._samples_per_frame = int(
199
+ (1 / SYNCSTATION_CHARACTERISTICS_DICT["FPS"]) * self._sampling_frequency
200
+ )
201
+
202
+ self._buffer_size = int(self._number_of_bytes * self._samples_per_frame)
203
+
204
+ num_probes = len(self._configuration_command_A)
205
+ start_byte += num_probes << 1
206
+ self._configuration_command_A.insert(0, start_byte)
207
+ start_byte_ckc8 = self._crc_check(
208
+ self._configuration_command_A, len(self._configuration_command_A)
209
+ )
210
+ self._configuration_command_A.append(start_byte_ckc8)
211
+
212
+ return True
213
+
214
+ def _crc_check(self, command_bytes: bytearray, command_length: int) -> bytes:
215
+ """
216
+ Performs the Cyclic Redundancy Check (CRC) of the transmitted bytes.
217
+
218
+ Translated function from example code provided by OT Bioelettronica.
219
+
220
+ Args:
221
+ command_bytes (bytearray):
222
+ Bytearray of the transmitted bytes.
223
+
224
+ command_length (int):
225
+ Length of the transmitted bytes.
226
+
227
+ Returns:
228
+ bytes:
229
+ CRC of the transmitted bytes.
230
+ """
231
+
232
+ crc = 0
233
+ j = 0
234
+
235
+ while command_length > 0:
236
+ extracted_byte = command_bytes[j]
237
+ for i in range(8, 0, -1):
238
+ sum = crc % 2 ^ extracted_byte % 2
239
+ crc = crc // 2
240
+
241
+ if sum > 0:
242
+ crc_bin = format(crc, "08b")
243
+ a_bin = format(140, "08b")
244
+
245
+ str_list = []
246
+
247
+ for k in range(8):
248
+ str_list.append("0" if crc_bin[k] == a_bin[k] else "1")
249
+
250
+ crc = int("".join(str_list), 2)
251
+
252
+ extracted_byte = extracted_byte // 2
253
+
254
+ command_length -= 1
255
+ j += 1
256
+
257
+ return crc
258
+
259
+ def _configure_byte_sequence_B(self) -> None:
260
+ # TODO: Implement this method
261
+ ...
262
+
263
+ def _send_configuration_to_device(self) -> None:
264
+ print(
265
+ f"Device configuration sent: {[int.from_bytes(self._configuration_command_A[i : i + 1], 'big') for i in range(len(self._configuration_command_A))]}"
266
+ )
267
+ self._interface.write(self._configuration_command_A)
268
+
269
+ def _stop_streaming(self):
270
+ self._configuration_command_A[0] -= 1
271
+ self._configuration_command_A[-1] = self._crc_check(
272
+ self._configuration_command_A, len(self._configuration_command_A) - 1
273
+ )
274
+
275
+ self._send_configuration_to_device()
276
+
277
+ self._is_streaming = False
278
+ self.stream_toggled.emit(False)
279
+
280
+ def _start_streaming(self):
281
+ self._configuration_command_A[0] += 1
282
+ self._configuration_command_A[-1] = self._crc_check(
283
+ self._configuration_command_A, len(self._configuration_command_A) - 1
284
+ )
285
+
286
+ self._send_configuration_to_device()
287
+
288
+ self._is_streaming = True
289
+ self.stream_toggled.emit(True)
290
+
291
+ def _clear_socket(self) -> None:
292
+ """
293
+ Clears the socket from any remaining data.
294
+ """
295
+ self._interface.readAll()
296
+ self._received_bytes = bytearray()
297
+
298
+ def _read_data(self) -> None:
299
+ if not self._is_streaming:
300
+ packet = self._interface.readAll()
301
+
302
+ else:
303
+ if self._interface.bytesAvailable() > 0:
304
+
305
+ packet = self._interface.readAll()
306
+ packet_bytearray = bytearray(packet.data())
307
+
308
+ if not packet_bytearray:
309
+ return
310
+
311
+ self._received_bytes.extend(packet_bytearray)
312
+
313
+ while len(self._received_bytes) >= self._buffer_size:
314
+ self._process_data(
315
+ bytearray(self._received_bytes)[: self._buffer_size]
316
+ )
317
+ self._received_bytes = bytearray(self._received_bytes)[
318
+ self._buffer_size :
319
+ ]
320
+
321
+ def _process_data(self, input: bytearray) -> None:
322
+ data: np.ndarray = np.frombuffer(input, dtype=np.uint8).astype(np.float32)
323
+
324
+ samples = self._samples_per_frame
325
+ data = np.reshape(data, (samples, self._number_of_bytes)).T
326
+ processed_data = self._bytes_to_integers(data)
327
+
328
+ # Emit the data
329
+ self.data_available.emit(processed_data)
330
+ self.biosignal_data_available.emit(self._extract_biosignal_data(processed_data))
331
+ self.auxiliary_data_available.emit(self._extract_auxiliary_data(processed_data))
332
+
333
+ def _integer_to_bytes(self, command: int) -> bytes:
334
+ return int(command).to_bytes(1, byteorder="big")
335
+
336
+ # Convert channels from bytes to integers
337
+ def _bytes_to_integers(
338
+ self,
339
+ data: np.ndarray,
340
+ ) -> np.ndarray:
341
+ samples = self._samples_per_frame
342
+ frame_data = np.zeros((self._number_of_channels, samples), dtype=np.float32)
343
+ channels_to_read = 0
344
+ for device in list(SyncStationProbeConfigMode)[1:]:
345
+ if self._bytes_configuration_A[device]["probe_status"]:
346
+ channel_number = PROBE_CHARACTERISTICS_DICT[device][
347
+ DeviceChannelTypes.ALL
348
+ ]
349
+ # Convert channel's byte value to integer
350
+ if self._working_mode == SyncStationWorkingMode.EMG:
351
+ channel_indices = (
352
+ np.arange(0, channel_number * 2, 2) + channels_to_read * 2
353
+ )
354
+ data_sub_matrix = self._decode_int16(data, channel_indices)
355
+ frame_data[
356
+ channels_to_read : channels_to_read + channel_number, :
357
+ ] = data_sub_matrix
358
+
359
+ elif self._working_mode == SyncStationWorkingMode.EEG:
360
+ channel_indices = (
361
+ np.arange(0, channel_number * 3, 3) + channels_to_read * 2
362
+ )
363
+ data_sub_matrix = self._decode_int24(data, channel_indices)
364
+ frame_data[
365
+ channels_to_read : channels_to_read + channel_number, :
366
+ ] = data_sub_matrix
367
+
368
+ channels_to_read += channel_number
369
+ del data_sub_matrix
370
+ del channel_indices
371
+
372
+ syncstation_aux_bytes_number = (
373
+ SYNCSTATION_CHARACTERISTICS_DICT[DeviceChannelTypes.ALL]
374
+ * SYNCSTATION_CHARACTERISTICS_DICT["bytes_per_sample"]
375
+ )
376
+ syncstation_aux_starting_byte = (
377
+ self._number_of_bytes - syncstation_aux_bytes_number
378
+ )
379
+ channel_indices = np.arange(
380
+ syncstation_aux_starting_byte,
381
+ syncstation_aux_starting_byte + syncstation_aux_bytes_number,
382
+ 2,
383
+ )
384
+ data_sub_matrix = self._decode_int16(data, channel_indices)
385
+ frame_data[channels_to_read : channels_to_read + 6, :] = data_sub_matrix
386
+ return np.array(frame_data)
387
+
388
+ def _decode_int24(
389
+ self, data: np.ndarray, channel_indices: np.ndarray
390
+ ) -> np.ndarray:
391
+ data_sub_matrix = (
392
+ data[channel_indices, :] * 2**16
393
+ + data[channel_indices + 1, :] * 2**8
394
+ + data[channel_indices + 2, :]
395
+ )
396
+ negative_indices = np.where(data_sub_matrix >= 2**23)
397
+ data_sub_matrix[negative_indices] -= 2**24
398
+
399
+ return data_sub_matrix
400
+
401
+ def _decode_int16(
402
+ self, data: np.ndarray, channel_indices: np.ndarray
403
+ ) -> np.ndarray:
404
+ data_sub_matrix = data[channel_indices, :] * 2**8 + data[channel_indices + 1, :]
405
+ negative_indices = np.where(data_sub_matrix >= 2**15)
406
+ data_sub_matrix[negative_indices] -= 2**16
407
+ return data_sub_matrix
@@ -23,6 +23,12 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget
23
23
  from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
24
24
  OTBQuattrocentoLightWidget,
25
25
  )
26
+ from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_widget import (
27
+ OTBQuattrocentoWidget,
28
+ )
29
+ from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
30
+ OTBSyncStationWidget,
31
+ )
26
32
 
27
33
  if TYPE_CHECKING:
28
34
  from PySide6.QtWidgets import QWidget, QMainWindow
@@ -36,8 +42,10 @@ class AllDevicesWidget(BaseMultipleDevicesWidget):
36
42
  super().__init__(parent)
37
43
 
38
44
  self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
45
+ DeviceType.OTB_QUATTROCENTO: OTBQuattrocentoWidget(self),
39
46
  DeviceType.OTB_QUATTROCENTO_LIGHT: OTBQuattrocentoLightWidget(self),
40
47
  DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
41
48
  DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
49
+ DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
42
50
  }
43
51
  self._set_devices(self._device_selection)
@@ -19,6 +19,7 @@ from biosignal_device_interface.devices.core.base_device import BaseDevice
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from enum import Enum
22
+ from PySide6.QtWidgets import QLineEdit
22
23
 
23
24
 
24
25
  class BaseDeviceWidget(QWidget):
@@ -36,7 +37,7 @@ class BaseDeviceWidget(QWidget):
36
37
  self.parent_widget: QWidget | QMainWindow | None = parent
37
38
 
38
39
  # Device Setup
39
- self.device: BaseDevice | None = None
40
+ self._device: BaseDevice | None = None
40
41
  self._device_params: Dict[str, Union[str, int, float]] = {}
41
42
 
42
43
  # GUI setup
@@ -90,16 +91,16 @@ class BaseDeviceWidget(QWidget):
90
91
  def _set_device(self, device: BaseDevice) -> None:
91
92
  """ """
92
93
  # Device Setup
93
- self.device: BaseDevice = device
94
+ self._device: BaseDevice = device
94
95
  self._initialize_device_params()
95
96
  self._set_signals()
96
97
  self._initialize_ui()
97
98
 
98
99
  def _set_signals(self) -> None:
99
100
  """ """
100
- self.device.data_available.connect(self.data_arrived.emit)
101
- self.device.biosignal_data_available.connect(self.biosignal_data_arrived.emit)
102
- self.device.auxiliary_data_available.connect(self.auxiliary_data_arrived.emit)
101
+ self._device.data_available.connect(self.data_arrived.emit)
102
+ self._device.biosignal_data_available.connect(self.biosignal_data_arrived.emit)
103
+ self._device.auxiliary_data_available.connect(self.auxiliary_data_arrived.emit)
103
104
 
104
105
  def get_device_information(self) -> Dict[str, Enum | int | float | str]:
105
106
  """
@@ -110,12 +111,20 @@ class BaseDeviceWidget(QWidget):
110
111
  Dictionary that holds information about the
111
112
  current device configuration and status.
112
113
  """
113
- return self.device.get_device_information()
114
+ return self._device.get_device_information()
114
115
 
115
116
  def disconnect_device(self) -> None:
116
117
  """ """
117
- if self.device._is_connected or self.device._is_streaming:
118
- self.device.toggle_connection()
118
+ if self._device.is_connected or self._device._is_streaming:
119
+ self._device.toggle_connection()
120
+
121
+ def _check_ip_input(self, line_edit: QLineEdit, default: str) -> None:
122
+ if not self._device.check_valid_ip(line_edit.text()):
123
+ line_edit.setText(default)
124
+
125
+ def _check_port_input(self, line_edit: QLineEdit, default: str) -> None:
126
+ if not self._device.check_valid_port(line_edit.text()):
127
+ line_edit.setText(default)
119
128
 
120
129
  def closeEvent(self, event: QCloseEvent) -> None:
121
130
  self.disconnect_device()
@@ -49,9 +49,6 @@ class BaseMultipleDevicesWidget(QWidget):
49
49
 
50
50
  self.device_stacked_widget = self.ui.deviceStackedWidget
51
51
  self.device_selection_combo_box = self.ui.deviceSelectionComboBox
52
- self.device_selection_combo_box.currentIndexChanged.connect(
53
- self._update_stacked_widget
54
- )
55
52
 
56
53
  def get_device_information(self) -> Dict[str, Union[str, int]]:
57
54
  return self._get_current_widget().get_device_information()
@@ -76,13 +73,16 @@ class BaseMultipleDevicesWidget(QWidget):
76
73
  current_widget.stream_toggled.disconnect(self.stream_toggled)
77
74
 
78
75
  except (TypeError, RuntimeError):
79
- pass
76
+ ...
80
77
 
81
78
  self.device_stacked_widget.setCurrentIndex(index)
82
79
  current_widget = self._get_current_widget()
83
80
 
81
+ # Data arrived
84
82
  current_widget.data_arrived.connect(self.data_arrived.emit)
83
+ # Biosignal data arrived
85
84
  current_widget.biosignal_data_arrived.connect(self.biosignal_data_arrived.emit)
85
+ # Auxiliary data arrived
86
86
  current_widget.auxiliary_data_arrived.connect(self.auxiliary_data_arrived.emit)
87
87
 
88
88
  current_widget.connect_toggled.connect(self.connect_toggled)
@@ -97,6 +97,9 @@ class BaseMultipleDevicesWidget(QWidget):
97
97
  self.device_selection_combo_box.addItem(DEVICE_NAME_DICT[device_type])
98
98
 
99
99
  self._update_stacked_widget(0)
100
+ self.device_selection_combo_box.currentIndexChanged.connect(
101
+ self._update_stacked_widget
102
+ )
100
103
 
101
104
  def _get_current_widget(self) -> BaseDeviceWidget:
102
105
  return self.device_stacked_widget.currentWidget()
@@ -16,6 +16,12 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget
16
16
  from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
17
17
  OTBQuattrocentoLightWidget,
18
18
  )
19
+ from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_widget import (
20
+ OTBQuattrocentoWidget,
21
+ )
22
+ from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
23
+ OTBSyncStationWidget,
24
+ )
19
25
 
20
26
  if TYPE_CHECKING:
21
27
  from PySide6.QtWidgets import QWidget, QMainWindow
@@ -29,8 +35,10 @@ class OTBDevicesWidget(BaseMultipleDevicesWidget):
29
35
  super().__init__(parent)
30
36
 
31
37
  self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
38
+ DeviceType.OTB_QUATTROCENTO: OTBQuattrocentoWidget(self),
32
39
  DeviceType.OTB_QUATTROCENTO_LIGHT: OTBQuattrocentoLightWidget(self),
33
40
  DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
34
41
  DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
42
+ DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
35
43
  }
36
44
  self._set_devices(self._device_selection)
@@ -34,10 +34,10 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
34
34
  self._set_device(OTBMuovi(parent=self, is_muovi_plus=True))
35
35
 
36
36
  def _toggle_connection(self) -> None:
37
- if not self.device._is_connected:
37
+ if not self._device.is_connected:
38
38
  self.connect_push_button.setEnabled(False)
39
39
 
40
- self.device.toggle_connection(
40
+ self._device.toggle_connection(
41
41
  (
42
42
  self.connection_ip_combo_box.currentText(),
43
43
  int(self.connection_port_label.text()),
@@ -68,7 +68,7 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
68
68
  self.input_detection_mode_combo_box.currentIndex() + 1
69
69
  )
70
70
 
71
- self.device.configure_device(self._device_params)
71
+ self._device.configure_device(self._device_params)
72
72
 
73
73
  def _configuration_toggled(self, is_configured: bool) -> None:
74
74
  if is_configured:
@@ -82,7 +82,7 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
82
82
 
83
83
  def _toggle_stream(self) -> None:
84
84
  self.stream_push_button.setEnabled(False)
85
- self.device.toggle_streaming()
85
+ self._device.toggle_streaming()
86
86
 
87
87
  def _stream_toggled(self, is_streaming: bool) -> None:
88
88
  self.stream_push_button.setEnabled(True)
@@ -112,17 +112,17 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
112
112
  # Command Push Buttons
113
113
  self.connect_push_button: QPushButton = self.ui.commandConnectionPushButton
114
114
  self.connect_push_button.clicked.connect(self._toggle_connection)
115
- self.device.connect_toggled.connect(self._connection_toggled)
115
+ self._device.connect_toggled.connect(self._connection_toggled)
116
116
 
117
117
  self.configure_push_button: QPushButton = self.ui.commandConfigurationPushButton
118
118
  self.configure_push_button.clicked.connect(self._toggle_configuration)
119
119
  self.configure_push_button.setEnabled(False)
120
- self.device.configure_toggled.connect(self._configuration_toggled)
120
+ self._device.configure_toggled.connect(self._configuration_toggled)
121
121
 
122
122
  self.stream_push_button: QPushButton = self.ui.commandStreamPushButton
123
123
  self.stream_push_button.clicked.connect(self._toggle_stream)
124
124
  self.stream_push_button.setEnabled(False)
125
- self.device.stream_toggled.connect(self._stream_toggled)
125
+ self._device.stream_toggled.connect(self._stream_toggled)
126
126
 
127
127
  # Connection parameters
128
128
  self.connection_group_box: QGroupBox = self.ui.connectionGroupBox
@@ -135,13 +135,13 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
135
135
  lambda: (
136
136
  self.connection_ip_combo_box.clear(),
137
137
  self.connection_ip_combo_box.addItems(
138
- self.device.get_server_wifi_ip_address()
138
+ self._device.get_server_wifi_ip_address()
139
139
  ),
140
140
  )
141
141
  )
142
142
 
143
143
  self.connection_ip_combo_box.clear()
144
- self.connection_ip_combo_box.addItems(self.device.get_server_wifi_ip_address())
144
+ self.connection_ip_combo_box.addItems(self._device.get_server_wifi_ip_address())
145
145
 
146
146
  self.connection_port_label.setText(str(MUOVI_NETWORK_PORT))
147
147
 
@@ -34,10 +34,10 @@ class OTBMuoviWidget(BaseDeviceWidget):
34
34
  self._set_device(OTBMuovi(parent=self))
35
35
 
36
36
  def _toggle_connection(self) -> None:
37
- if not self.device._is_connected:
37
+ if not self._device.is_connected:
38
38
  self.connect_push_button.setEnabled(False)
39
39
 
40
- self.device.toggle_connection(
40
+ self._device.toggle_connection(
41
41
  (
42
42
  self.connection_ip_combo_box.currentText(),
43
43
  int(self.connection_port_label.text()),
@@ -68,7 +68,7 @@ class OTBMuoviWidget(BaseDeviceWidget):
68
68
  self.input_detection_mode_combo_box.currentIndex() + 1
69
69
  )
70
70
 
71
- self.device.configure_device(self._device_params)
71
+ self._device.configure_device(self._device_params)
72
72
 
73
73
  def _configuration_toggled(self, is_configured: bool) -> None:
74
74
  if is_configured:
@@ -82,7 +82,7 @@ class OTBMuoviWidget(BaseDeviceWidget):
82
82
 
83
83
  def _toggle_stream(self) -> None:
84
84
  self.stream_push_button.setEnabled(False)
85
- self.device.toggle_streaming()
85
+ self._device.toggle_streaming()
86
86
 
87
87
  def _stream_toggled(self, is_streaming: bool) -> None:
88
88
  self.stream_push_button.setEnabled(True)
@@ -112,17 +112,17 @@ class OTBMuoviWidget(BaseDeviceWidget):
112
112
  # Command Push Buttons
113
113
  self.connect_push_button: QPushButton = self.ui.commandConnectionPushButton
114
114
  self.connect_push_button.clicked.connect(self._toggle_connection)
115
- self.device.connect_toggled.connect(self._connection_toggled)
115
+ self._device.connect_toggled.connect(self._connection_toggled)
116
116
 
117
117
  self.configure_push_button: QPushButton = self.ui.commandConfigurationPushButton
118
118
  self.configure_push_button.clicked.connect(self._toggle_configuration)
119
119
  self.configure_push_button.setEnabled(False)
120
- self.device.configure_toggled.connect(self._configuration_toggled)
120
+ self._device.configure_toggled.connect(self._configuration_toggled)
121
121
 
122
122
  self.stream_push_button: QPushButton = self.ui.commandStreamPushButton
123
123
  self.stream_push_button.clicked.connect(self._toggle_stream)
124
124
  self.stream_push_button.setEnabled(False)
125
- self.device.stream_toggled.connect(self._stream_toggled)
125
+ self._device.stream_toggled.connect(self._stream_toggled)
126
126
 
127
127
  # Connection parameters
128
128
  self.connection_group_box: QGroupBox = self.ui.connectionGroupBox
@@ -135,13 +135,13 @@ class OTBMuoviWidget(BaseDeviceWidget):
135
135
  lambda: (
136
136
  self.connection_ip_combo_box.clear(),
137
137
  self.connection_ip_combo_box.addItems(
138
- self.device.get_server_wifi_ip_address()
138
+ self._device.get_server_wifi_ip_address()
139
139
  ),
140
140
  )
141
141
  )
142
142
 
143
143
  self.connection_ip_combo_box.clear()
144
- self.connection_ip_combo_box.addItems(self.device.get_server_wifi_ip_address())
144
+ self.connection_ip_combo_box.addItems(self._device.get_server_wifi_ip_address())
145
145
 
146
146
  self.connection_port_label.setText(str(MUOVI_NETWORK_PORT))
147
147