From 0e61cd37c35dc8154ff63d2ee3b6073fc8f44cc7 Mon Sep 17 00:00:00 2001 From: CPD Date: Wed, 19 Mar 2025 17:01:27 +0100 Subject: [PATCH] Add connection checks --- cpdctrl_gui/ui/main_window.py | 89 ++++++++++++++++--- .../ui/widgets/settings/app_settings.py | 5 ++ .../widgets/settings/measurement_settings.py | 8 +- 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/cpdctrl_gui/ui/main_window.py b/cpdctrl_gui/ui/main_window.py index 32c0155..4bd3002 100644 --- a/cpdctrl_gui/ui/main_window.py +++ b/cpdctrl_gui/ui/main_window.py @@ -138,6 +138,7 @@ class MainWindow(QMainWindow): self.leddev_autoconnect() # Measurement + self.idle_timer = None self.measurement_timer = None self.led_script = None self.led_script_watcher = None @@ -150,6 +151,8 @@ class MainWindow(QMainWindow): self.menuBar().m_file.addAction(self.a_open_about) self.menuBar().m_file.addAction(self.a_open_help) + self.idle_start() + def set_status(self, msg): self.statusBar().showMessage(msg) @@ -196,15 +199,26 @@ class MainWindow(QMainWindow): # return TreeView(self) # LED DEVICE MANAGEMENT - def leddev_connect(self, leddev_type, leddev_name): + def leddev_connect(self, leddev_type, leddev_name, fail_silently=False): log.info(f"Connecting to LED device {leddev_name} ({leddev_type})") - self.leddev = ledd.connect_device(leddev_type, leddev_name) + try: + self.leddev = ledd.connect_device(leddev_type, leddev_name) + except Exception as e: + if not fail_silently: + QMessageBox.critical(self, "Connection failed", f"Failed to connect to '{leddev_name}', the following error occured: \n{e}") + return AppConfig.MAIN_CFG.set("led_device_last.type", leddev_type) AppConfig.MAIN_CFG.set("led_device_last.name", leddev_name) + self.leddev_connected() + + def leddev_connected(self): # Update the settings widget value - self.w_measurement_settings.set_value("device_led_controller", str(self.leddev)) - led_name = self.leddev.get_led_name() - if not led_name: led_name = "Unknown" + self.w_measurement_settings.set_value("device_led_controller", self.leddev) + led_name = None + if self.leddev is not None: + led_name = self.leddev.get_led_name() + if not led_name: + led_name = "Unknown" self.w_measurement_settings.set_value("device_led", led_name) def leddev_autoconnect(self): @@ -212,7 +226,7 @@ class MainWindow(QMainWindow): try: leddev_type = AppConfig.MAIN_CFG.get("led_device_last.type") leddev_name = AppConfig.MAIN_CFG.get("led_device_last.name") - self.leddev_connect(leddev_type, leddev_name) + self.leddev_connect(leddev_type, leddev_name, fail_silently=True) except KeyError: pass except Exception as e: @@ -229,21 +243,40 @@ class MainWindow(QMainWindow): leddev_type, leddev_name = device_dialog.get_selected() self.leddev_connect(leddev_type, leddev_name) + def leddev_test_connection(self) -> bool: + if self.leddev is not None: + try: + self.leddev.test_connection() + return True + except ConnectionError as e: + QMessageBox.warning(self, "LED Controller Disconnected", f"Lost connection to the LED controller '{self.leddev}'") + self.leddev = None + self.leddev_connected() + return False + # VOLTAGE DEVICE MANAGEMENT - def vmdev_connect(self, vmdev_type, vmdev_name): + def vmdev_connect(self, vmdev_type, vmdev_name, fail_silently=False): log.info(f"Connecting to voltage measurement device {vmdev_name} ({vmdev_type})") - self.vmdev = vmd.connect_device(vmdev_type, vmdev_name) + try: + self.vmdev = vmd.connect_device(vmdev_type, vmdev_name) + except Exception as e: + if not fail_silently: + QMessageBox.critical(self, "Connection failed", f"Failed to connect to '{leddev_name}', the following error occured: \n{e}") + return AppConfig.MAIN_CFG.set("voltage_measurement_device_last.type", vmdev_type) AppConfig.MAIN_CFG.set("voltage_measurement_device_last.name", vmdev_name) + self.vmdev_connected() + + def vmdev_connected(self): # Update the settings widget value - self.w_measurement_settings.set_value("device_voltage_measurement", str(self.vmdev)) + self.w_measurement_settings.set_value("device_voltage_measurement", self.vmdev) def vmdev_autoconnect(self): if AppConfig.MAIN_CFG.get_or("voltage_measurement_device_auto_reconnect", False): try: vmdev_type = AppConfig.MAIN_CFG.get("voltage_measurement_device_last.type") vmdev_name = AppConfig.MAIN_CFG.get("voltage_measurement_device_last.name") - self.vmdev_connect(vmdev_type, vmdev_name) + self.vmdev_connect(vmdev_type, vmdev_name, fail_silently=True) except KeyError: pass except Exception as e: @@ -259,17 +292,47 @@ class MainWindow(QMainWindow): vmdev_type, vmdev_name = device_dialog.get_selected() self.vmdev_connect(vmdev_type, vmdev_name) + def vmdev_test_connection(self) -> bool: + if self.vmdev is not None: + try: + self.vmdev.test_connection() + return True + except ConnectionError as e: + QMessageBox.warning(self, "Voltage Measurement Device Disconnected", f"Lost connection to the voltage measurement device '{self.vmdev}'") + self.vmdev = None + self.vmdev_connected() + return False + + # IDLE - NOT IN MEASUREMENT + # check if devices stayed connected + def idle_start(self): + self.idle_timer = QTimer(self) + self.idle_timer.timeout.connect(self.idle_update) + self.idle_timer.start(AppConfig.MAIN_CFG.get_or("idle_update_interval_s", 10)*1000) + + def idle_update(self): + self.vmdev_test_connection() + self.leddev_test_connection() + + def idle_stop(self): + self.idle_timer.stop() + self.idle_timer = None + + # MEASUREMENT def measure_start(self): if self.vmdev is None: self.vmdev_connect_from_dialog() if self.vmdev is None: - raise ValueError("No measurement device selected") + raise RuntimeError("No measurement device selected") if self.leddev is None: self.leddev_connect_from_dialog() if self.leddev is None: - raise ValueError("No led control device selected") + raise RuntimeError("No led control device selected") + self.idle_stop() + if not (self.vmdev_connected() and self.leddev_connected()): + raise RuntimeError(f"Can not start measurement, a device lost connection.") name = self.w_measurement_settings.get_value("name") script = self.w_measurement_settings.get_value("led_script") @@ -334,6 +397,7 @@ class MainWindow(QMainWindow): self.data_queue, auto_add_metadata, )) + # todo: error handling self.proc_measure.start() self.measurement_timer = QTimer(self) self.measurement_timer.timeout.connect(self.measure_update) @@ -381,6 +445,7 @@ class MainWindow(QMainWindow): self.w_measurement_settings.setEnabled(True) self.w_metadata.setEnabled(True) self.set_status("Ready") + self.idle_start() def measure_update(self): self.w_led_script_viewer.update_time(time.time()) diff --git a/cpdctrl_gui/ui/widgets/settings/app_settings.py b/cpdctrl_gui/ui/widgets/settings/app_settings.py index ebab1b1..6c459b2 100644 --- a/cpdctrl_gui/ui/widgets/settings/app_settings.py +++ b/cpdctrl_gui/ui/widgets/settings/app_settings.py @@ -30,4 +30,9 @@ class AppSettings(QWidget): self.w_form.add_form_row("metadata_auto_add", "Auto-Add Metadata", True, QCheckBox(), "Automatically add measurement metadata to the data file.\nThis includes: device names, measurement mode, measurement interval, start and stop times, led script") w_usb_switch_exe = FileSelection(filemode=QFileDialog.FileMode.ExistingFile) self.w_form.add_form_row("power_switch_exe", "Power Switch Executable", "", w_usb_switch_exe, "Path to the USBSwitchCmd executable for the Cleware USB switch\nRequires a relaunch to take effect") + w_idle_dt = QSpinBox() + w_idle_dt.setMinimum(10) + w_idle_dt.setMaximum(200000) + w_idle_dt.setSingleStep(10) + self.w_form.add_form_row("idle_update_interval_s", "Device Connection Check Interval (s)", 30, w_plot_dt, "How often to check whether the devices are still connected.\nApplies only when not in a measurement.") diff --git a/cpdctrl_gui/ui/widgets/settings/measurement_settings.py b/cpdctrl_gui/ui/widgets/settings/measurement_settings.py index 432a857..4bc6abb 100644 --- a/cpdctrl_gui/ui/widgets/settings/measurement_settings.py +++ b/cpdctrl_gui/ui/widgets/settings/measurement_settings.py @@ -28,7 +28,13 @@ class DeviceSelection(QGroupBox): def set_value(self, key, value): key = key.replace("device_", "") if key in self.devices_widgets: - self.devices_widgets[key].setText(value) + 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}'")