From 2395d89206aa51ad61fa22284f77cdd8f7d52176 Mon Sep 17 00:00:00 2001
From: CPD <CPD@TUZEWSI-2LN203M.ads.mwn.de>
Date: Wed, 26 Mar 2025 19:00:49 +0100
Subject: [PATCH] Add autoreconnect after power on

---
 cpdctrl_gui/ui/main_window.py                 | 111 +++++++++++++-----
 .../ui/widgets/settings/app_settings.py       |   5 +
 2 files changed, 88 insertions(+), 28 deletions(-)

diff --git a/cpdctrl_gui/ui/main_window.py b/cpdctrl_gui/ui/main_window.py
index 41977d8..5e4c349 100644
--- a/cpdctrl_gui/ui/main_window.py
+++ b/cpdctrl_gui/ui/main_window.py
@@ -136,8 +136,10 @@ class MainWindow(QMainWindow):
             self.power_switch = ClewareSwitch(exe)
         self.vmdev = None
         self.leddev = None
-        self.vmdev_autoconnect()
-        self.leddev_autoconnect()
+        if AppConfig.MAIN_CFG.get_or("voltage_measurement_device_auto_reconnect", False):
+            self.vmdev_autoconnect()
+        if AppConfig.MAIN_CFG.get_or("led_device_auto_reconnect", False):
+            self.leddev_autoconnect()
 
         # Measurement
         self.idle_timer = None
@@ -206,8 +208,11 @@ class MainWindow(QMainWindow):
         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}")
+            if fail_silently:
+                raise e
+            else:
+                QMessageBox.critical(self, "Connection failed", f"Failed to connect to '{leddev_name}', the following error occurred: \n{e}")
+                log.error(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)
@@ -224,15 +229,14 @@ class MainWindow(QMainWindow):
         self.w_measurement_settings.set_value("device_led", led_name)
 
     def leddev_autoconnect(self):
-        if AppConfig.MAIN_CFG.get_or("led_device_auto_reconnect", False):
-            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, fail_silently=True)
-            except KeyError:
-                pass
-            except Exception as e:
-                log.error(f"Failed to auto-connect to LED device: {e}")
+        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, fail_silently=True)
+        except KeyError:
+            pass
+        except Exception as e:
+            log.error(f"Failed to auto-connect to LED device: {e}")
 
     def leddev_connect_from_dialog(self):
         """
@@ -245,13 +249,13 @@ class MainWindow(QMainWindow):
             leddev_type, leddev_name = device_dialog.get_selected()
             self.leddev_connect(leddev_type, leddev_name)
 
-    def leddev_test_connection(self) -> bool:
+    def leddev_test_connection(self, silent=False) -> 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}'")
+                if not silent: QMessageBox.warning(self, "LED Controller Disconnected", f"Lost connection to the LED controller '{self.leddev}'")
                 self.leddev = None
                 self.leddev_connected()
         return False
@@ -262,8 +266,11 @@ class MainWindow(QMainWindow):
         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}")
+            if fail_silently:
+                raise e
+            else:
+                QMessageBox.critical(self, "Connection failed", f"Failed to connect to '{vmdev_name}', the following error occured: \n{e}")
+                log.error(f"Failed to connect to '{vmdev_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)
@@ -274,15 +281,14 @@ class MainWindow(QMainWindow):
         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, fail_silently=True)
-            except KeyError:
-                pass
-            except Exception as e:
-                log.error(f"Failed to auto-connect to voltage measurement device: {e}")
+        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, fail_silently=True)
+        except KeyError:
+            pass
+        except Exception as e:
+            log.error(f"Failed to auto-connect to voltage measurement device: {e}")
     def vmdev_connect_from_dialog(self):
         """
         Open a dialog that lets the user choose a voltage measurement device,
@@ -294,13 +300,13 @@ class MainWindow(QMainWindow):
             vmdev_type, vmdev_name = device_dialog.get_selected()
             self.vmdev_connect(vmdev_type, vmdev_name)
 
-    def vmdev_test_connection(self) -> bool:
+    def vmdev_test_connection(self, silent=False) -> 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}'")
+                if not silent: 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
@@ -460,6 +466,10 @@ class MainWindow(QMainWindow):
                 self.w_plot.update_plot(tval, vval, led_val)
         else:  # measurement might have stopped after max N or script end
             self.measure_stop()
+            # this should only be run when the measurement is stopped automatically
+            # and is therefore not part of measure_stop()
+            if AppConfig.MAIN_CFG.get_or("power_switch_turn_off_when_measurement_ends", False):
+                self.power_off()
 
     def measurement_is_running(self):
         return self.proc_measure is not None
@@ -595,14 +605,59 @@ class MainWindow(QMainWindow):
             self.command_queue.put(("metadata", metadata))
 
     def power_on(self):
+        """
+        Send power on command to the power switch.
+        If configured, wait for a while and then try to reconnect to unconnected devices.
+        """
         if self.power_switch is None:
             raise RuntimeError("No power switch configured")
+        log.info("Powering on")
         self.power_switch.on()
+        # TODO: change to zero
+        timeout = AppConfig.MAIN_CFG.get_or("power_switch_autoconnect_devices_timeout_s", 20)
+        if not timeout > 0: return
+        if self.vmdev and self.leddev: return
+        dialog = QDialog()
+        dialog.setWindowTitle("Autoconnect devices")
+        buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Cancel)
+        buttons.accepted.connect(dialog.accept)
+        buttons.rejected.connect(dialog.reject)
+        dialog.setLayout(QVBoxLayout())
+        dialog.l_time = QLabel(f"{timeout} s")
+        dialog.time_left = timeout
+        dialog.layout().addWidget(QLabel(f"Trying to reconnect to devices in"))
+        dialog.layout().addWidget(dialog.l_time)
+        dialog.layout().addWidget(buttons)
+        dialog.time_left = timeout
+        def update_time_left(dialog):
+            dialog.time_left -= 1
+            dialog.l_time.setText(f"{dialog.time_left} s")
+            if dialog.time_left <= 0:
+                dialog.accept()
+        dialog.update_time_left = update_time_left
+        dialog.timer = QTimer()
+        dialog.timer.timeout.connect(lambda: dialog.update_time_left(dialog))
+        dialog.timer.start(1000)
+        ret = dialog.exec()
+        dialog.timer.stop()
+        if ret == QDialog.DialogCode.Accepted:
+            log.info("Trying to autoconnect last devices")
+            if self.vmdev is None:
+                self.vmdev_autoconnect()
+            if self.leddev is None:
+                self.leddev_autoconnect()
+        else:
+            pass
 
     def power_off(self):
         if self.power_switch is None:
             raise RuntimeError("No power switch configured")
+        log.info("Powering off")
         self.power_switch.off()
+        silent = True
+        self.leddev_test_connection(silent=silent)
+        self.vmdev_test_connection(silent=silent)
+
 
     def app_exit(self) -> None:
         """
diff --git a/cpdctrl_gui/ui/widgets/settings/app_settings.py b/cpdctrl_gui/ui/widgets/settings/app_settings.py
index 24b6db5..a197f41 100644
--- a/cpdctrl_gui/ui/widgets/settings/app_settings.py
+++ b/cpdctrl_gui/ui/widgets/settings/app_settings.py
@@ -49,6 +49,11 @@ class AppSettings(QWidget):
         # power_switch
         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", group="power_switch")
+        w_power_on_reconnect_dt = QSpinBox()
+        w_power_on_reconnect_dt.setMinimum(0)
+        w_power_on_reconnect_dt.setMaximum(60)
+        w_power_on_reconnect_dt.setSingleStep(1)
+        self.w_form.add_form_row("power_switch_autoconnect_devices_timeout_s", "Autoconnect devices after (s)", 15, w_power_on_reconnect_dt, "After switching on, wait x seconds before trying to reconnect to the last devices\nSet to 0 to disable the auto-connection attempt.", group="power_switch")
 
         self.w_form.update_alignment()