biosignal-device-interface 0.2.4__py3-none-any.whl → 0.2.6__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.
@@ -21,6 +21,7 @@ class DeviceType(Enum):
21
21
  OTB_MUOVI = auto(), "OT Bioelettronica Muovi"
22
22
  OTB_MUOVI_PLUS = auto(), "OT Bioelettronica Muovi Plus"
23
23
  OTB_SYNCSTATION = auto(), "OT Bioelettronica SyncStation"
24
+ OTB_SESSANTAQUATTRO = auto(), "OT Bioelettronica Sessantaquattro"
24
25
 
25
26
 
26
27
  class OTBDeviceType(Enum):
@@ -36,6 +37,7 @@ class OTBDeviceType(Enum):
36
37
  MUOVI = auto(), "Muovi"
37
38
  MUOVI_PLUS = auto(), "Muovi Plus"
38
39
  SYNCSTATION = auto(), "SyncStation"
40
+ SESSANTAQUATTRO = auto(), "Sessantaquattro"
39
41
 
40
42
 
41
43
  class DeviceChannelTypes(Enum):
@@ -58,4 +60,6 @@ DEVICE_NAME_DICT: dict[DeviceType | OTBDeviceType, str] = {
58
60
  OTBDeviceType.MUOVI_PLUS: "Muovi Plus",
59
61
  DeviceType.OTB_SYNCSTATION: "SyncStation",
60
62
  OTBDeviceType.SYNCSTATION: "SyncStation",
63
+ DeviceType.OTB_SESSANTAQUATTRO: "Sessantaquattro",
64
+ OTBDeviceType.SESSANTAQUATTRO: "Sessantaquattro",
61
65
  }
@@ -0,0 +1,227 @@
1
+ """
2
+ Constants for the Sessantaquattro device.
3
+
4
+ Developer: Dominik I. Braun
5
+ Contact: dome.braun@fau.de
6
+ Last Update: 2025-09-16
7
+ """
8
+
9
+ from typing import Dict
10
+ from aenum import Enum, auto
11
+
12
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
13
+ DeviceChannelTypes,
14
+ )
15
+
16
+
17
+ class SessantaquattroSamplingFrequencyMode(Enum):
18
+ """Enum class for the sampling frequency of the Sessantaquattro device."""
19
+
20
+ _init_ = "value __doc__"
21
+
22
+ NONE = 0, "No sampling frequency set."
23
+ LOW = auto(), "500 Hz (2000 Hz - Accelerometer)"
24
+ MEDIUM = auto(), "1000 Hz (4000 Hz - Accelerometer)"
25
+ HIGH = auto(), "2000 Hz (8000 Hz - Accelerometer)"
26
+ ULTRA = auto(), "4000 Hz (16000 Hz - Accelerometer)"
27
+
28
+
29
+ class SessantaquattroDetectionMode(Enum):
30
+ """
31
+ Enum class for the working mode of the Sessantaquattro device.
32
+ """
33
+
34
+ _init_ = "value __doc__"
35
+
36
+ NONE = 0, "No working mode set."
37
+ MONOPOLAR = auto(), ("Monopolar Mode")
38
+ BIPOLAR = auto(), ("Bipolar Mode")
39
+ DIFFERENTIAL = auto(), ("Differential Mode")
40
+ ACCELEROMETER = auto(), ("Accelerometer Mode")
41
+ UNDEFINED = auto(), ("Undefined Mode")
42
+ IMPEDANCE_ADVANCED = auto(), ("Impedance Check Advanced")
43
+ IMPEDANCE = auto(), ("Impedance Check")
44
+ TEST = auto(), ("Test Mode")
45
+
46
+
47
+ class SessantaquattroChannelMode(Enum):
48
+ """
49
+ Enum class for the channel mode of the Sessantaquattro device.
50
+ """
51
+
52
+ _init_ = "value __doc__"
53
+
54
+ NONE = 0, "No channel mode set."
55
+ LOW = auto(), (
56
+ "8 bioelec. + 2 AUX + 2 accessory (if MODE=001: 4 bio + 2 AUX + 2 acc)"
57
+ )
58
+ MEDIUM = auto(), (
59
+ "16 bioelec. + 2 AUX + 2 accessory (if MODE=001: 8 bio + 2 AUX + 2 acc)"
60
+ )
61
+ HIGH = auto(), (
62
+ "32 bioelec. + 2 AUX + 2 accessory (if MODE=001: 16 bio + 2 AUX + 2 acc)"
63
+ )
64
+ ULTRA = auto(), (
65
+ "64 bioelec. + 2 AUX + 2 accessory (if MODE=001: 32 bio + 2 AUX + 2 acc)"
66
+ )
67
+
68
+
69
+ class SessantaquattroResolutionMode(Enum):
70
+ """
71
+ Enum class for the resolution mode of the Sessantaquattro device.
72
+ """
73
+
74
+ _init_ = "value __doc__"
75
+
76
+ NONE = 0, "No resolution mode set."
77
+ LOW = auto(), ("16 Bits Resolution")
78
+ HIGH = auto(), ("24 Bits Resolution")
79
+
80
+
81
+ class SessantaquattroGainMode(Enum):
82
+ """
83
+ Enum class for the gain mode of the Sessantaquattro device.
84
+ """
85
+
86
+ _init_ = "value __doc__"
87
+
88
+ NONE = 0, "No gain mode set."
89
+ DEFAULT = auto(), (
90
+ "Gain x2 (for 24 bits resolution) / Gain x8 (for 16 bits resolution)"
91
+ )
92
+ LOW = auto(), ("Gain x4")
93
+ MEDIUM = auto(), ("Gain x6")
94
+ HIGH = auto(), ("Gain x8")
95
+
96
+
97
+ class SessantaquattroTriggerMode(Enum):
98
+ """
99
+ Enum class for the trigger mode of the Sessantaquattro device.
100
+ """
101
+
102
+ _init_ = "value __doc__"
103
+
104
+ NONE = 0, "No trigger mode set."
105
+ DEFAULT = auto(), (
106
+ "The data transfer is controlled from GO/STOP bit, REC has no effect."
107
+ )
108
+ INTERNAL = auto(), (
109
+ "The data transfer ist triggered by the internal signal (phototransistor)"
110
+ )
111
+ EXTERNAL = auto(), (
112
+ "The data transfer is triggered by the external signal (from the adapter)"
113
+ )
114
+ SDCARD = auto(), (
115
+ "SDCARD: SD card acquisition starts/stops with the hardware button or with the REC bit."
116
+ )
117
+
118
+
119
+ class SessantaquattroRecordingMode(Enum):
120
+ """
121
+ Enum class for the recording mode of the Sessantaquattro device.
122
+ """
123
+
124
+ _init_ = "value __doc__"
125
+
126
+ NONE = 0, "No recording mode set."
127
+ STOP = auto(), ("Stop the recording. Works only if TRIG = 3 (SDCARD).")
128
+ START = auto(), ("Start the recording. Works only if TRIG = 3 (SDCARD).")
129
+
130
+
131
+ def _get_sampling_frequency(
132
+ detection_mode: SessantaquattroDetectionMode,
133
+ sampling_freq_mode: SessantaquattroSamplingFrequencyMode,
134
+ ) -> int:
135
+ """Get sampling frequency for given detection and sampling frequency modes."""
136
+ if detection_mode == SessantaquattroDetectionMode.ACCELEROMETER:
137
+ base_freq = 2000
138
+ else:
139
+ base_freq = 500
140
+
141
+ multiplier = 2 ** (sampling_freq_mode.value - 1)
142
+ return base_freq * multiplier
143
+
144
+
145
+ def _get_biosignal_channel_count(
146
+ sampling_freq_mode: SessantaquattroSamplingFrequencyMode,
147
+ detection_mode: SessantaquattroDetectionMode,
148
+ ) -> int:
149
+ """Get biosignal channel count for given modes."""
150
+ base_channels = 8 * (2 ** (sampling_freq_mode.value - 1))
151
+
152
+ # Bipolar mode has half the channels
153
+ if detection_mode == SessantaquattroDetectionMode.BIPOLAR:
154
+ return base_channels // 2
155
+
156
+ return base_channels
157
+
158
+
159
+ # Generate dictionaries using functions
160
+ SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT: Dict[
161
+ SessantaquattroDetectionMode, Dict[SessantaquattroSamplingFrequencyMode, int]
162
+ ] = {
163
+ detection_mode: {
164
+ sampling_freq: _get_sampling_frequency(detection_mode, sampling_freq)
165
+ for sampling_freq in SessantaquattroSamplingFrequencyMode
166
+ if sampling_freq != SessantaquattroSamplingFrequencyMode.NONE
167
+ }
168
+ for detection_mode in SessantaquattroDetectionMode
169
+ if detection_mode != SessantaquattroDetectionMode.NONE
170
+ }
171
+
172
+ SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT: Dict[
173
+ SessantaquattroSamplingFrequencyMode,
174
+ Dict[SessantaquattroDetectionMode, Dict[DeviceChannelTypes, int]],
175
+ ] = {
176
+ sampling_freq: {
177
+ detection_mode: {
178
+ DeviceChannelTypes.BIOSIGNAL: _get_biosignal_channel_count(
179
+ sampling_freq, detection_mode
180
+ ),
181
+ DeviceChannelTypes.AUXILIARY: 4,
182
+ }
183
+ for detection_mode in SessantaquattroDetectionMode
184
+ if detection_mode != SessantaquattroDetectionMode.NONE
185
+ }
186
+ for sampling_freq in SessantaquattroSamplingFrequencyMode
187
+ if sampling_freq != SessantaquattroSamplingFrequencyMode.NONE
188
+ }
189
+
190
+ SESSANTAQUATTRO_GAIN_MODE_CHARACTERISTICS_DICT: Dict[
191
+ SessantaquattroResolutionMode, Dict[SessantaquattroGainMode, float]
192
+ ] = {
193
+ SessantaquattroResolutionMode.LOW: {
194
+ SessantaquattroGainMode.DEFAULT: 286.1e-6, # Gain8, 16-bit (in mV, originally 286.1 nV)
195
+ SessantaquattroGainMode.LOW: 572.2e-6, # Gain4, 16-bit (in mV, originally 572.2 nV)
196
+ SessantaquattroGainMode.MEDIUM: 381.5e-6, # Gain6, 16-bit (in mV, originally 381.5 nV)
197
+ SessantaquattroGainMode.HIGH: 286.1e-6, # Gain8, 16-bit (in mV, originally 286.1 nV)
198
+ },
199
+ SessantaquattroResolutionMode.HIGH: {
200
+ SessantaquattroGainMode.DEFAULT: 71.5e-6, # Gain8, 24-bit (in mV, originally 71.5 nV)
201
+ SessantaquattroGainMode.LOW: 143.0e-6, # Gain4, 24-bit (in mV, originally 143.0 nV)
202
+ SessantaquattroGainMode.MEDIUM: 95.4e-6, # Gain6, 24-bit (in mV, originally 95.4 nV)
203
+ SessantaquattroGainMode.HIGH: 71.5e-6, # Gain8, 24-bit (in mV, originally 71.5 nV)
204
+ },
205
+ }
206
+
207
+ SESSANTAQUATTRO_AUXILIARY_LSB_DICT: Dict[SessantaquattroResolutionMode, float] = {
208
+ SessantaquattroResolutionMode.LOW: 146.48e-6, # in mV (originally 146.48 nV)
209
+ SessantaquattroResolutionMode.HIGH: 572.2e-6, # in mV (originally 572.2 nV)
210
+ }
211
+
212
+ SESSANTAQUATTRO_SAMPLES_PER_FRAME_DICT: Dict[SessantaquattroChannelMode, int] = {
213
+ SessantaquattroChannelMode.LOW: 48,
214
+ SessantaquattroChannelMode.MEDIUM: 28,
215
+ SessantaquattroChannelMode.HIGH: 16,
216
+ SessantaquattroChannelMode.ULTRA: 8,
217
+ }
218
+
219
+ if __name__ == "__main__":
220
+ # Print the generated dictionaries for verification
221
+ import pprint
222
+
223
+ print("Sessantaquattro Detection Mode Characteristics Dictionary:")
224
+ pprint.pprint(SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT)
225
+
226
+ print("\nSessantaquattro Channel Mode Characteristics Dictionary:")
227
+ pprint.pprint(SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT)
@@ -5,9 +5,11 @@ from biosignal_device_interface.devices.otb import (
5
5
  OTBMuoviWidget,
6
6
  OTBMuoviPlusWidget,
7
7
  OTBQuattrocentoLightWidget,
8
+ OTBSessantaquattroWidget,
8
9
  OTBMuovi,
9
10
  OTBQuattrocento,
10
11
  OTBQuattrocentoLight,
12
+ OTBSessantaquattro,
11
13
  OTBSyncStationWidget,
12
14
  OTBDevicesWidget,
13
15
  )
@@ -4,6 +4,9 @@ from biosignal_device_interface.devices.otb.otb_quattrocento_light import (
4
4
  )
5
5
  from biosignal_device_interface.devices.otb.otb_muovi import OTBMuovi
6
6
  from biosignal_device_interface.devices.otb.otb_syncstation import OTBSyncStation
7
+ from biosignal_device_interface.devices.otb.otb_sessantaquattro import (
8
+ OTBSessantaquattro,
9
+ )
7
10
 
8
11
  # Widgets
9
12
  # All OTB Devices Widget
@@ -27,3 +30,6 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento
27
30
  from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
28
31
  OTBSyncStationWidget,
29
32
  )
33
+ from biosignal_device_interface.gui.device_template_widgets.otb.otb_sessantaquattro_widget import (
34
+ OTBSessantaquattroWidget,
35
+ )
@@ -212,6 +212,7 @@ class OTBMuovi(BaseDevice):
212
212
  def clear_socket(self) -> None:
213
213
  if self._client_socket is not None:
214
214
  self._client_socket.readAll()
215
+ self._received_bytes = bytearray()
215
216
 
216
217
  def _read_data(self) -> None:
217
218
  super()._read_data()
@@ -57,7 +57,7 @@ class OTBQuattrocento(BaseDevice):
57
57
 
58
58
  # Device Information
59
59
  self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
60
- self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in mV
60
+ self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in V
61
61
  self._number_of_streamed_channels: int = None
62
62
 
63
63
  # Connection Parameters
@@ -60,7 +60,7 @@ class OTBQuattrocentoLight(BaseDevice):
60
60
  self._auxiliary_channel_start_index: int = 384 # Fix value
61
61
  self._number_of_auxiliary_channels: int = 16 # Fix value
62
62
  self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
63
- self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in mV
63
+ self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in V
64
64
  self._bytes_per_sample: int = 2 # Fix value
65
65
  # Quattrocento unique parameters
66
66
  self._streaming_frequency: int | None = None
@@ -0,0 +1,363 @@
1
+ """
2
+ Device class for real-time interfacing the Sessantaquattro device.
3
+ Developer: Dominik I. Braun
4
+ Contact: dome.braun@fau.de
5
+ Last Update: 2025-09-16
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import TYPE_CHECKING, Union, Dict
10
+ from PySide6.QtNetwork import QTcpSocket, QTcpServer, QHostAddress
11
+ import numpy as np
12
+
13
+ # Local Libraries
14
+ from biosignal_device_interface.devices.core.base_device import BaseDevice
15
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
16
+ DeviceType,
17
+ DeviceChannelTypes,
18
+ )
19
+
20
+ # Constants
21
+ from biosignal_device_interface.constants.devices.otb.otb_sessantaquattro_constants import (
22
+ SESSANTAQUATTRO_AUXILIARY_LSB_DICT,
23
+ SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT,
24
+ SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT,
25
+ SESSANTAQUATTRO_GAIN_MODE_CHARACTERISTICS_DICT,
26
+ SESSANTAQUATTRO_SAMPLES_PER_FRAME_DICT,
27
+ SessantaquattroChannelMode,
28
+ SessantaquattroDetectionMode,
29
+ SessantaquattroGainMode,
30
+ SessantaquattroRecordingMode,
31
+ SessantaquattroResolutionMode,
32
+ SessantaquattroSamplingFrequencyMode,
33
+ SessantaquattroTriggerMode,
34
+ )
35
+
36
+ if TYPE_CHECKING:
37
+ from PySide6.QtWidgets import QMainWindow, QWidget
38
+ from aenum import Enum
39
+
40
+
41
+ class OTBSessantaquattro(BaseDevice):
42
+ """
43
+ Device class for real-time interfacing the Sessantaquattro device.
44
+
45
+ parent (Union[QMainWindow, QWidget], optional):
46
+ Parent widget to which the device is assigned to.
47
+ Defaults to None.
48
+
49
+ The Sessantaquattro class is using a TCP/IP protocol to communicate with the device.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ parent: Union[QMainWindow, QWidget],
55
+ ) -> None:
56
+ """Initialize the OTBSessantaquattro device.
57
+
58
+ Args:
59
+ parent (Union[QMainWindow, QWidget]): Parent widget to which the device is assigned to.
60
+ """
61
+ super().__init__(parent=parent)
62
+
63
+ # Device Parameters
64
+ self._device_type: DeviceType = DeviceType.OTB_SESSANTAQUATTRO
65
+
66
+ # Connection Parameters
67
+ self._interface: QTcpServer = None
68
+ self._client_socket: QTcpSocket = None
69
+
70
+ # Configuration Parameters
71
+ ## Control Byte 0
72
+ self._sampling_frequency_mode: SessantaquattroSamplingFrequencyMode = (
73
+ SessantaquattroSamplingFrequencyMode.NONE
74
+ )
75
+ self._channel_mode: SessantaquattroChannelMode = SessantaquattroChannelMode.NONE
76
+ self._detection_mode: SessantaquattroDetectionMode = (
77
+ SessantaquattroDetectionMode.NONE
78
+ )
79
+
80
+ ## Control Byte 1
81
+ self._resolution_mode: SessantaquattroResolutionMode = (
82
+ SessantaquattroResolutionMode.NONE
83
+ )
84
+ self._gain_mode: SessantaquattroGainMode = SessantaquattroGainMode.NONE
85
+ self._trigger_mode: SessantaquattroTriggerMode = SessantaquattroTriggerMode.NONE
86
+ self._recording_mode: SessantaquattroRecordingMode = (
87
+ SessantaquattroRecordingMode.NONE
88
+ )
89
+
90
+ def _connect_to_device(self):
91
+ super()._connect_to_device()
92
+
93
+ self._interface = QTcpServer(self)
94
+ self._received_bytes: bytearray = bytearray()
95
+
96
+ if not self._interface.listen(
97
+ QHostAddress(self._connection_settings[0]), self._connection_settings[1]
98
+ ):
99
+ return False
100
+
101
+ self._interface.newConnection.connect(self._make_request)
102
+
103
+ self._connection_timeout_timer.start()
104
+
105
+ return True
106
+
107
+ def _make_request(self) -> bool:
108
+ super()._make_request()
109
+ self._client_socket = self._interface.nextPendingConnection()
110
+
111
+ if self._client_socket:
112
+
113
+ self._client_socket.readyRead.connect(self._read_data)
114
+
115
+ if not self.is_connected:
116
+ self.is_connected = True
117
+ self.connect_toggled.emit(self.is_connected)
118
+ self._connection_timeout_timer.stop()
119
+ return True
120
+
121
+ elif not self._is_configured:
122
+ self._is_configured = True
123
+ self.configure_toggled.emit(self._is_configured)
124
+ return True
125
+
126
+ def _disconnect_from_device(self) -> bool:
127
+ super()._disconnect_from_device()
128
+
129
+ if self._client_socket is not None:
130
+ self._client_socket.readyRead.disconnect(self._read_data)
131
+ self._client_socket.disconnectFromHost()
132
+ self._client_socket.close()
133
+
134
+ if self._interface is not None:
135
+ self._interface.close()
136
+
137
+ return True
138
+
139
+ def configure_device(
140
+ self, params: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
141
+ ) -> None:
142
+ super().configure_device(params)
143
+
144
+ if not self.is_connected or self._client_socket is None:
145
+ return
146
+
147
+ # Set Configuration Parameters
148
+ self._sampling_frequency = SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT[
149
+ self._detection_mode
150
+ ][self._sampling_frequency_mode]
151
+
152
+ channels = SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT[
153
+ self._sampling_frequency_mode
154
+ ][self._detection_mode]
155
+
156
+ self._number_of_biosignal_channels = channels[DeviceChannelTypes.BIOSIGNAL]
157
+ self._biosignal_channel_indices = np.arange(
158
+ 0, self._number_of_biosignal_channels
159
+ )
160
+ self._number_of_auxiliary_channels = channels[DeviceChannelTypes.AUXILIARY]
161
+ self._auxiliary_channel_indices = np.arange(
162
+ self._number_of_biosignal_channels,
163
+ self._number_of_biosignal_channels + self._number_of_auxiliary_channels,
164
+ )
165
+
166
+ self._number_of_channels = (
167
+ self._number_of_biosignal_channels + self._number_of_auxiliary_channels
168
+ )
169
+
170
+ self._bytes_per_sample = (
171
+ 2 if self._resolution_mode == SessantaquattroResolutionMode.LOW else 3
172
+ )
173
+
174
+ self._conversion_factor_biosignal = SESSANTAQUATTRO_GAIN_MODE_CHARACTERISTICS_DICT[
175
+ self._resolution_mode
176
+ ][self._gain_mode]
177
+
178
+ self._conversion_factor_auxiliary = SESSANTAQUATTRO_AUXILIARY_LSB_DICT[
179
+ self._resolution_mode
180
+ ]
181
+
182
+ self._samples_per_frame = SESSANTAQUATTRO_SAMPLES_PER_FRAME_DICT[
183
+ self._channel_mode
184
+ ]
185
+
186
+ # Prepare streaming
187
+ self._buffer_size = (
188
+ self._number_of_channels * self._samples_per_frame * self._bytes_per_sample
189
+ )
190
+
191
+ self._received_bytes = bytearray()
192
+
193
+ self._configure_command()
194
+ self._send_configuration_to_device()
195
+
196
+ def _send_configuration_to_device(self) -> None:
197
+ configuration_bytes = int(self._configuration_command).to_bytes(
198
+ 2, byteorder="big"
199
+ )
200
+
201
+ success = self._client_socket.write(configuration_bytes)
202
+
203
+ if success == -1:
204
+ self._disconnect_from_device()
205
+
206
+ def _configure_command(self) -> None:
207
+ self._configuration_command = 0
208
+
209
+ # Control Byte 0
210
+ # Bit 0 - Transmission Mode | Handled by Start / Stop Streaming
211
+ # Bit 1
212
+ if self._recording_mode is None or self._recording_mode == SessantaquattroRecordingMode.NONE:
213
+ self._recording_mode = SessantaquattroRecordingMode.STOP
214
+ self._configuration_command += (self._recording_mode.value - 1) << 1
215
+
216
+ # Bit 3-2
217
+ if self._trigger_mode is None or self._trigger_mode == SessantaquattroTriggerMode.NONE:
218
+ self._trigger_mode = SessantaquattroTriggerMode.DEFAULT
219
+ self._configuration_command += (self._trigger_mode.value - 1) << 2
220
+
221
+ # Bit 5-4
222
+ if self._gain_mode is None or self._gain_mode == SessantaquattroGainMode.NONE:
223
+ self._gain_mode = SessantaquattroGainMode.DEFAULT
224
+ self._configuration_command += (self._gain_mode.value - 1) << 4
225
+
226
+ # Bit 6 - Filter mode: enable high pass filter
227
+ self._configuration_command += 1 << 6
228
+
229
+ # Bit 7 - Resolution mode
230
+ if self._resolution_mode is None or self._resolution_mode == SessantaquattroResolutionMode.NONE:
231
+ self._resolution_mode = SessantaquattroResolutionMode.LOW
232
+ self._configuration_command += (self._resolution_mode.value - 1) << 7
233
+
234
+ # Control Byte 1
235
+ # Bit 2-0 - Detection Mode
236
+ if self._detection_mode is None or self._detection_mode == SessantaquattroDetectionMode.NONE:
237
+ self._detection_mode = SessantaquattroDetectionMode.MONOPOLAR
238
+ self._configuration_command += (self._detection_mode.value - 1) << 8
239
+
240
+ # Bit 4-3 - Channel Mode
241
+ if self._channel_mode is None or self._channel_mode == SessantaquattroChannelMode.NONE:
242
+ self._channel_mode = SessantaquattroChannelMode.LOW
243
+ self._configuration_command += (self._channel_mode.value - 1) << 11
244
+
245
+ # Bit 6-5 - Sampling Frequency Mode
246
+ if self._sampling_frequency_mode is None or self._sampling_frequency_mode == SessantaquattroSamplingFrequencyMode.NONE:
247
+ self._sampling_frequency_mode = SessantaquattroSamplingFrequencyMode.HIGH
248
+ self._configuration_command += (self._sampling_frequency_mode.value - 1) << 13
249
+
250
+ # Bit 7 - GETSET - always 0 for setting the configuration
251
+ self._configuration_command += 0 << 15
252
+
253
+ def _start_streaming(self) -> None:
254
+ super()._start_streaming()
255
+
256
+ if self._configuration_command is None:
257
+ return
258
+
259
+ self._configuration_command += 1
260
+ self._send_configuration_to_device()
261
+
262
+ def _stop_streaming(self) -> None:
263
+ super()._stop_streaming()
264
+
265
+ if self._configuration_command is None:
266
+ return
267
+
268
+ self._configuration_command -= 1
269
+ self._send_configuration_to_device()
270
+
271
+ def clear_socket(self) -> None:
272
+ if self._client_socket is not None:
273
+ self._client_socket.readAll()
274
+ self._received_bytes = bytearray()
275
+
276
+ def _read_data(self) -> None:
277
+ super()._read_data()
278
+
279
+ if self._client_socket is None:
280
+ return
281
+
282
+ if not self._is_streaming:
283
+ buffer_size: int = 13
284
+ config_bytes = self._client_socket.read(
285
+ buffer_size
286
+ ) # TODO: Catches configuration settings, has to be checked
287
+
288
+ self._process_configuration_data(config_bytes)
289
+ return
290
+
291
+ while self._client_socket.bytesAvailable() > self._buffer_size:
292
+ packet = self._client_socket.read(self._buffer_size)
293
+ if not packet:
294
+ continue
295
+
296
+ self._received_bytes.extend(packet)
297
+
298
+ while len(self._received_bytes) >= self._buffer_size:
299
+ data_to_process = self._received_bytes[: self._buffer_size]
300
+ self._process_data(data_to_process)
301
+ self._received_bytes = self._received_bytes[self._buffer_size :]
302
+
303
+ def _process_configuration_data(self, input: bytearray) -> None:
304
+ # TODO: Implement configuration data processing
305
+ ...
306
+
307
+ def _process_data(self, input: bytearray) -> None:
308
+ super()._process_data(input)
309
+
310
+ decoded_data = self._bytes_to_integers(input)
311
+
312
+ processed_data = decoded_data.reshape(
313
+ self._number_of_channels, -1, order="F"
314
+ ).astype(np.float32)
315
+
316
+ self.data_available.emit(processed_data)
317
+ self.biosignal_data_available.emit(self._extract_biosignal_data(processed_data))
318
+ self.auxiliary_data_available.emit(self._extract_auxiliary_data(processed_data))
319
+
320
+ # Convert integer to bytes
321
+ def _integer_to_bytes(self, command: int) -> bytes:
322
+ return command.to_bytes(2, byteorder="big")
323
+
324
+ # Convert channels from bytes to integers
325
+ def _bytes_to_integers(
326
+ self,
327
+ data: bytearray,
328
+ ) -> np.ndarray:
329
+ channel_values = []
330
+ # Separate channels from byte-string. One channel has
331
+ # "bytes_in_sample" many bytes in it.
332
+ for channel_index in range(len(data) // 2):
333
+ channel_start = channel_index * self._bytes_per_sample
334
+ channel_end = (channel_index + 1) * self._bytes_per_sample
335
+ channel = data[channel_start:channel_end]
336
+
337
+ # Convert channel's byte value to integer
338
+ if self._resolution_mode == SessantaquattroResolutionMode.LOW:
339
+ value = self._decode_int16(channel)
340
+ else:
341
+ value = self._decode_int24(channel)
342
+ channel_values.append(value)
343
+
344
+ return np.array(channel_values)
345
+
346
+ def _decode_int16(self, bytes_value: bytearray) -> int:
347
+ value = None
348
+ # Combine 2 bytes to a 16 bit integer value
349
+ value = bytes_value[0] * 256 + bytes_value[1]
350
+ # See if the value is negative and make the two's complement
351
+ if value >= 32768:
352
+ value -= 65536
353
+ return value
354
+
355
+ # Convert byte-array value to an integer value and apply two's complement
356
+ def _decode_int24(self, bytes_value: bytearray) -> int:
357
+ value = None
358
+ # Combine 3 bytes to a 24 bit integer value
359
+ value = bytes_value[0] * 65536 + bytes_value[1] * 256 + bytes_value[2]
360
+ # See if the value is negative and make the two's complement
361
+ if value >= 8388608:
362
+ value -= 16777216
363
+ return value
@@ -29,6 +29,9 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento
29
29
  from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
30
30
  OTBSyncStationWidget,
31
31
  )
32
+ from biosignal_device_interface.gui.device_template_widgets.otb.otb_sessantaquattro_widget import (
33
+ OTBSessantaquattroWidget,
34
+ )
32
35
 
33
36
  if TYPE_CHECKING:
34
37
  from PySide6.QtWidgets import QWidget, QMainWindow
@@ -47,5 +50,6 @@ class AllDevicesWidget(BaseMultipleDevicesWidget):
47
50
  DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
48
51
  DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
49
52
  DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
53
+ DeviceType.OTB_SESSANTAQUATTRO: OTBSessantaquattroWidget(self),
50
54
  }
51
55
  self._set_devices(self._device_selection)