Add resources and exception handler

This commit is contained in:
CPD 2025-03-05 12:41:10 +01:00
parent 11833761bd
commit f99ecf4822
3 changed files with 105 additions and 31 deletions

View File

@ -1,9 +1,14 @@
''' app/init.py ''' ''' app/init.py '''
import sys import sys
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from .ui.main_window import MainWindow from .ui.main_window import MainWindow
from .utility.config import AppConfig from .utility.config import AppConfig
from . import resources
# This is necessary to set the taskbar icon
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(u'n203.cpdctrl-gui.1')
def run() -> int: def run() -> int:
""" """
@ -13,6 +18,7 @@ def run() -> int:
int: The exit status code. int: The exit status code.
""" """
app: QApplication = QApplication(sys.argv) app: QApplication = QApplication(sys.argv)
app.setWindowIcon(QIcon(":/icons/cpdctrl-gui-logo"))
AppConfig.initialize() AppConfig.initialize()
window: MainWindow = MainWindow() window: MainWindow = MainWindow()
@ -21,3 +27,58 @@ def run() -> int:
print("Saving configuration") print("Saving configuration")
AppConfig.finalize() AppConfig.finalize()
return sys.exit(exitcode) return sys.exit(exitcode)
import sys
import traceback
import logging
from PyQt6 import QtCore, QtWidgets
# This is taken from:
# https://timlehr.com/2018/01/python-exception-hooks-with-qt-message-box/index.html
# basic logger functionality
log = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
log.addHandler(handler)
def show_exception_box(log_msg):
"""Checks if a QApplication instance is available and shows a messagebox with the exception message.
If unavailable (non-console application), log an additional notice.
"""
if QtWidgets.QApplication.instance() is not None:
errorbox = QtWidgets.QMessageBox()
errorbox.setText("Oops. An unexpected error occured:\n{0}".format(log_msg))
errorbox.exec()
else:
log.debug("No QApplication instance available.")
class UncaughtHook(QtCore.QObject):
_exception_caught = QtCore.pyqtSignal(object)
def __init__(self, *args, **kwargs):
super(UncaughtHook, self).__init__(*args, **kwargs)
# this registers the exception_hook() function as hook with the Python interpreter
sys.excepthook = self.exception_hook
# connect signal to execute the message box function always on main thread
self._exception_caught.connect(show_exception_box)
def exception_hook(self, exc_type, exc_value, exc_traceback):
"""Function handling uncaught exceptions.
It is triggered each time an uncaught exception occurs.
"""
if issubclass(exc_type, KeyboardInterrupt):
# ignore keyboard interrupt to support console applications
sys.__excepthook__(exc_type, exc_value, exc_traceback)
else:
exc_info = (exc_type, exc_value, exc_traceback)
log_msg = '\n'.join([''.join(traceback.format_tb(exc_traceback)),
'{0}: {1}'.format(exc_type.__name__, exc_value)])
log.critical("Uncaught exception:\n {0}".format(log_msg), exc_info=exc_info)
# trigger message box show
self._exception_caught.emit(log_msg)
# create a global instance of our class to register the hook
qt_exception_hook = UncaughtHook()

View File

@ -119,6 +119,7 @@ class MainWindow(QMainWindow):
""" """
# return TreeView(self) # return TreeView(self)
# LED DEVICE MANAGEMENT
def leddev_connect(self, leddev_type, leddev_name): def leddev_connect(self, leddev_type, leddev_name):
self.leddev = ledd.connect_device(leddev_type, leddev_name) self.leddev = ledd.connect_device(leddev_type, leddev_name)
AppConfig.MAIN_CFG.set("led_device_last.type", leddev_type) AppConfig.MAIN_CFG.set("led_device_last.type", leddev_type)
@ -137,12 +138,13 @@ class MainWindow(QMainWindow):
and then connects to the device. and then connects to the device.
""" """
devices = ledd.list_devices() devices = ledd.list_devices()
device_dialog = ListChoice(devices) device_dialog = ListChoice(devices, window_title="Select LED device")
if device_dialog.exec() == QDialog.DialogCode.Accepted: if device_dialog.exec() == QDialog.DialogCode.Accepted:
leddev_type, leddev_name = device_dialog.get_selected() leddev_type, leddev_name = device_dialog.get_selected()
if self.verbose: print(f"Connecting to {leddev_type}:{leddev_name}") if self.verbose: print(f"Connecting to {leddev_type}:{leddev_name}")
self.leddev_connect(leddev_type, leddev_name) self.leddev_connect(leddev_type, leddev_name)
# VOLTAGE DEVICE MANAGEMENT
def vmdev_connect(self, vmdev_type, vmdev_name): def vmdev_connect(self, vmdev_type, vmdev_name):
self.vmdev = vmd.connect_device(vmdev_type, vmdev_name) self.vmdev = vmd.connect_device(vmdev_type, vmdev_name)
AppConfig.MAIN_CFG.set("voltage_measurement_device_last.type", vmdev_type) AppConfig.MAIN_CFG.set("voltage_measurement_device_last.type", vmdev_type)
@ -164,12 +166,13 @@ class MainWindow(QMainWindow):
and then connects to the device. and then connects to the device.
""" """
devices = vmd.list_devices() devices = vmd.list_devices()
device_dialog = ListChoice(devices) device_dialog = ListChoice(devices, window_title="Select CPD Measurement Device")
if device_dialog.exec() == QDialog.DialogCode.Accepted: if device_dialog.exec() == QDialog.DialogCode.Accepted:
vmdev_type, vmdev_name = device_dialog.get_selected() vmdev_type, vmdev_name = device_dialog.get_selected()
if self.verbose: print(f"Connecting to {vmdev_type}:{vmdev_name}") if self.verbose: print(f"Connecting to {vmdev_type}:{vmdev_name}")
self.vmdev_connect(vmdev_type, vmdev_name) self.vmdev_connect(vmdev_type, vmdev_name)
# MEASUREMENT
def measure_start(self): def measure_start(self):
if self.vmdev is None: if self.vmdev is None:
self.vmdev_connect_from_dialog() self.vmdev_connect_from_dialog()
@ -187,8 +190,8 @@ class MainWindow(QMainWindow):
self.topbar.enable_button("meas_stop") self.topbar.enable_button("meas_stop")
self.w_plot.clear_data() self.w_plot.clear_data()
script = 100
measurement_name = "guitest" measurement_name = "guitest"
script = self.w_measurement_settings.get_value("led_script")
led_script = LedScript(script=script) led_script = LedScript(script=script)
flush_after = self.w_measurement_settings.get_value("flush_after") flush_after = self.w_measurement_settings.get_value("flush_after")
use_buffer = self.w_measurement_settings.get_value("use_buffer") use_buffer = self.w_measurement_settings.get_value("use_buffer")
@ -294,4 +297,5 @@ class MainWindow(QMainWindow):
""" """
def __del__(self): def __del__(self):
self.measure_stop() if self.measurement_is_running():
self.measure_stop()

View File

@ -1,5 +1,5 @@
from PyQt6.QtWidgets import QWidget, QRadioButton, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox, QFileDialog, QLabel from PyQt6.QtWidgets import QWidget, QRadioButton, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox, QFileDialog, QLabel
from PyQt6.QtWidgets import QFormLayout, QDoubleSpinBox, QCheckBox from PyQt6.QtWidgets import QFormLayout, QDoubleSpinBox, QCheckBox, QLineEdit
from ...utility.config import AppConfig from ...utility.config import AppConfig
@ -15,55 +15,59 @@ class ScriptSelection(QWidget):
self.radio_constant_value.toggled.connect(self.on_radio_button_toggled) self.radio_constant_value.toggled.connect(self.on_radio_button_toggled)
# Load from file button # Load from file button
self.load_button = QPushButton("Load from file") self.btn_load_script = QPushButton("Load from file")
self.load_button.clicked.connect(self.load_file) self.btn_load_script.clicked.connect(self.load_file)
self.w_script_file = QLineEdit()
self.w_script_file.setEnabled(False)
# QSpinBox for constant value # QSpinBox for constant value
self.spin_box = QSpinBox() self.w_constant_value = QSpinBox()
self.spin_box.setRange(0, 100) self.w_constant_value.setRange(0, 100)
# Layout for radio buttons # Layouts
radio_layout = QHBoxLayout() l_constant_value = QVBoxLayout()
radio_layout.addWidget(self.radio_script_file) l_constant_value.addWidget(self.radio_constant_value)
radio_layout.addWidget(self.radio_constant_value) l_constant_value.addWidget(self.w_constant_value)
# Layout for load button and spin box l_file = QVBoxLayout()
input_layout = QHBoxLayout() l_file.addWidget(self.radio_script_file)
input_layout.addWidget(self.load_button) l_file.addWidget(self.btn_load_script)
input_layout.addWidget(self.spin_box) l_file.addWidget(self.w_script_file)
# Add layouts to main layout # Add layouts to main layout
self.layout.addLayout(radio_layout) self.layout.addLayout(l_constant_value)
self.layout.addLayout(input_layout) self.layout.addLayout(l_file)
self.setLayout(self.layout) self.setLayout(self.layout)
# Initial state # Initial state
self.radio_script_file.setChecked(True) self.radio_constant_value.setChecked(True)
self.on_radio_button_toggled() self.on_radio_button_toggled()
self.layout.addStretch(1) self.layout.addStretch(1)
self.file_path = None
def on_radio_button_toggled(self): def on_radio_button_toggled(self):
if self.radio_script_file.isChecked(): if self.radio_script_file.isChecked():
self.load_button.setEnabled(True) self.btn_load_script.setEnabled(True)
self.spin_box.setEnabled(False) self.w_constant_value.setEnabled(False)
else: else:
self.load_button.setEnabled(False) self.btn_load_script.setEnabled(False)
self.spin_box.setEnabled(True) self.w_constant_value.setEnabled(True)
def load_file(self): def load_file(self):
# options = QFileDialog.Options() # options = QFileDialog.Options()
file_name, _ = QFileDialog.getOpenFileName(self, "Open Script File", "", "All Files (*);;Text files (*.led)") file_path, _ = QFileDialog.getOpenFileName(self, "Open Script File", "", "All Files (*);;Text files (*.led)")
if file_name: if file_path:
with open(file_name, 'r') as file: self.file_path = file_path
self.file_content = file.read() self.w_script_file.setText(self.file_path)
def get_script(self): def get_script(self):
if self.radio_script_file.isChecked(): if self.radio_script_file.isChecked():
return self.file_content return self.file_path
else: else:
return self.spin_box.value() return int(self.w_constant_value.value())
class MeasurementSettings(QWidget): class MeasurementSettings(QWidget):
@ -81,7 +85,9 @@ class MeasurementSettings(QWidget):
self.l_form = QFormLayout() self.l_form = QFormLayout()
# - script # - script
self.l_vbox.addWidget(ScriptSelection()) self.l_vbox.addWidget(QLabel("LED Script"))
self.w_led_script = ScriptSelection()
self.l_vbox.addWidget(self.w_led_script)
# key-value stuff in a form # key-value stuff in a form
self.l_vbox.addLayout(self.l_form) self.l_vbox.addLayout(self.l_form)
self.ws_form = {} self.ws_form = {}
@ -90,6 +96,7 @@ class MeasurementSettings(QWidget):
self._add_form_field("stop_on_script_end", "Stop on Script End", False, QCheckBox(self), "Stop measurement when LED script ends") self._add_form_field("stop_on_script_end", "Stop on Script End", False, QCheckBox(self), "Stop measurement when LED script ends")
self._add_form_field("use_buffer", "Use Buffer", False, QCheckBox(self), "If available, use device buffer for more accurate measurement timings.\nLeads to a lower accuracy of LED update timings, up to 1*interval") self._add_form_field("use_buffer", "Use Buffer", False, QCheckBox(self), "If available, use device buffer for more accurate measurement timings.\nLeads to a lower accuracy of LED update timings, up to 1*interval")
self._add_form_field("flush_after", "Flush after", 0, QSpinBox(self), "Number of measurements to take before writing the data to an intermediate file") self._add_form_field("flush_after", "Flush after", 0, QSpinBox(self), "Number of measurements to take before writing the data to an intermediate file")
self.l_vbox.addStretch(1)
def _add_form_field(self, key: str, label: str, default_value, widget: QWidget, tooltip: str = None): def _add_form_field(self, key: str, label: str, default_value, widget: QWidget, tooltip: str = None):
if tooltip: widget.setToolTip(tooltip) if tooltip: widget.setToolTip(tooltip)
@ -131,6 +138,8 @@ class MeasurementSettings(QWidget):
return self.ws_form[key].isChecked() return self.ws_form[key].isChecked()
else: else:
raise ValueError(f"Unknown widget type: {type(self.ws_form[key])}") raise ValueError(f"Unknown widget type: {type(self.ws_form[key])}")
elif key == "led_script":
return self.w_led_script.get_script()
else: else:
raise ValueError(f"Unknown key: {key}") raise ValueError(f"Unknown key: {key}")