import os.path from PyQt6.QtCore import pyqtSignal from PyQt6.QtWidgets import QWidget, QRadioButton, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox, QFileDialog, QLabel from PyQt6.QtWidgets import QFormLayout, QDoubleSpinBox, QCheckBox, QLineEdit, QGroupBox from os import path from cpdctrl.led_script import LedScript, InvalidScript from cpdctrl_gui.utility.config import AppConfig from .base import SettingsForm class DeviceSelection(QGroupBox): def __init__(self, parent=None): super().__init__(parent=parent, title="Devices") self.layout = QFormLayout() self.setLayout(self.layout) self.devices_widgets = {} self._add_device("voltage_measurement", "Voltage Measurement", "N.C.") self._add_device("led_controller", "Led Controller", "N.C.") self._add_device("led", "Led Lamp", "Unknown") # self.layout.addStretch(1) def _add_device(self, key, label, name): self.devices_widgets[key] = QLabel(name) self.layout.addRow(QLabel(label), self.devices_widgets[key]) def set_value(self, key, value): key = key.replace("device_", "") if key in self.devices_widgets: if value is None: text = "N.C." elif type(value) != str: text = str(value) else: text = value self.devices_widgets[key].setText(text) else: raise KeyError(f"Unknown device '{key}'") class ScriptSelection(QGroupBox): loadScript = pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent, title="LED Script") self.layout = QVBoxLayout() # Radio buttons self.radio_script_file = QRadioButton("Script file") self.radio_constant_value = QRadioButton("Constant brightness (%)") self.radio_script_file.toggled.connect(self.on_radio_button_toggled) self.radio_constant_value.toggled.connect(self.on_radio_button_toggled) # Load from file button self.btn_load_script = QPushButton("Load from file") self.btn_load_script.clicked.connect(self.load_file) self.w_script_file = QLineEdit() self.w_script_file.setEnabled(False) last_file = AppConfig.MAIN_CFG.get_or("tmp_last_script_file", "") # only use last file if exists and is valid if os.path.isfile(last_file): try: LedScript.parse_script(last_file) except InvalidScript: last_file = "" self.w_script_file.setText(last_file) self.file_path = last_file # QSpinBox for constant value self.w_constant_value = QSpinBox() self.w_constant_value.setRange(0, 100) # signal when changed self.w_constant_value.valueChanged.connect(lambda: self.loadScript.emit()) # Layouts l_constant_value = QVBoxLayout() l_constant_value.addWidget(self.radio_constant_value) l_constant_value.addWidget(self.w_constant_value) l_file = QVBoxLayout() l_file.addWidget(self.radio_script_file) l_file.addWidget(self.btn_load_script) l_file.addWidget(self.w_script_file) # Add layouts to main layout self.layout.addLayout(l_constant_value) self.layout.addLayout(l_file) self.setLayout(self.layout) # Initial state self.radio_constant_value.setChecked(True) self.on_radio_button_toggled() self.layout.addStretch(1) def on_radio_button_toggled(self): if self.radio_script_file.isChecked(): self.btn_load_script.setEnabled(True) self.w_constant_value.setEnabled(False) # if a file is set, load it when the button is toggled if self.file_path: self.loadScript.emit() else: self.btn_load_script.setEnabled(False) self.w_constant_value.setEnabled(True) def load_file(self): # options = QFileDialog.Options() last_dir = AppConfig.MAIN_CFG.get_or("tmp_last_script_dir", "") file_path, _ = QFileDialog.getOpenFileName(self, "Open Script File", last_dir, "All Files (*);;Text files (*.led)") if file_path: dir_name = path.dirname(file_path) AppConfig.MAIN_CFG.set("tmp_last_script_dir", dir_name) AppConfig.MAIN_CFG.set("tmp_last_script_file", file_path) self.file_path = file_path self.w_script_file.setText(self.file_path) # signal the change self.loadScript.emit() def get_script(self): if self.radio_script_file.isChecked(): return self.file_path else: return int(self.w_constant_value.value()) def get_script_type(self): if self.radio_script_file.isChecked(): return "file" else: return "constant" class MeasurementSettings(QWidget): """ Widget allowing the user to set the measurement settings. It loads the values from AppConfig.MEAS_CFG and automatically updates the values in AppConfig.MEAS_CFG when the user changes them. """ def __init__(self, parent=None): super().__init__(parent) self.l_vbox = QVBoxLayout() # self.label = QLabel("Measurement Settings") # self.layout.addWidget(self.label) self.setLayout(self.l_vbox) # devices self.w_device_selection = DeviceSelection() self.l_vbox.addWidget(self.w_device_selection) # - script self.w_led_script_load = ScriptSelection() self.l_vbox.addWidget(self.w_led_script_load) # key-value stuff in a form self.w_form = SettingsForm(AppConfig.MEAS_CFG) self.l_vbox.addWidget(self.w_form) self.w_form.add_form_row("name", "Name", "", QLineEdit(self), "Name of the measurement") w_box_interval = QDoubleSpinBox(self) w_box_interval.setDecimals(2) w_box_interval.setMinimum(0.01) w_box_interval.setSingleStep(0.1) self.w_form.add_form_row("interval", "Interval [s]", 1.0, w_box_interval, "Amount of seconds to wait between voltage measurements and LED device updates") w_box_max_measurements = QSpinBox(self) w_box_max_measurements.setMaximum(2147483647) # max int32 w_box_max_measurements.setMinimum(0) # 0 for infinite measurements self.w_form.add_form_row("max_measurements", "Max Measurement Points", 0, w_box_max_measurements, "Number of measurements to take. Set to 0 for infinite measurements") w_stop_script_end = QCheckBox(self) self.w_form.add_form_row("stop_on_script_end", "Stop on Script End", False, w_stop_script_end, "Stop measurement when LED script ends") self.w_poweroff_script_end = QCheckBox(self) # do after adding to form, since the actual value will have been loaded w_stop_script_end.stateChanged.connect(self._update_poweroff_checkbox) w_box_max_measurements.valueChanged.connect(self._update_poweroff_checkbox) self._update_poweroff_checkbox() self.w_form.add_form_row("power_off_script_end", "Power Off on Script End", False, self.w_poweroff_script_end, "Turn off the USB power switch when the measurement ends") self.w_form.add_form_row("use_buffer", "Use Buffer", False, QCheckBox(self), "If available, use the voltage device buffer for more accurate measurement timings.\nLeads to a lower accuracy of LED update timings, up to 1*interval") w_box_flush_after = QSpinBox(self) w_box_flush_after.setMaximum(2147483647) # max int32 w_box_flush_after.setSingleStep(500) self.w_form.add_form_row("flush_after", "Flush-Data Interval", 0, w_box_flush_after, "Number of measurements to take before writing the data to an intermediate file") self.l_vbox.addStretch(1) def _update_poweroff_checkbox(self): """ Enable the power-off-on-end checkbox only if the measurement will stopped automatically """ max_meas = self.w_form.get_value("max_measurements") stop_script_end = self.w_form.get_value("stop_on_script_end") if max_meas > 0 or stop_script_end == True: self.w_poweroff_script_end.setEnabled(True) else: self.w_poweroff_script_end.setEnabled(False) def set_value(self, key, value): if key in self.w_form: self.w_form.set_value(key, value) elif key.startswith("device_"): self.w_device_selection.set_value(key, value) else: raise ValueError(f"Unknown key: {key}") def get_value(self, key): if key in self.w_form: return self.w_form.get_value(key) elif key == "led_script": return self.w_led_script_load.get_script() elif key == "led_script_type": return self.w_led_script_load.get_script_type() else: raise ValueError(f"Unknown key: {key}")