298 lines
12 KiB
Python
298 lines
12 KiB
Python
''' app/ui/main_window.py '''
|
|
from PyQt6.QtCore import Qt, QTimer
|
|
from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QTextEdit, QLabel, QDialog, QToolButton, QStatusBar, QFileDialog
|
|
from PyQt6.QtWidgets import QToolBox
|
|
from PyQt6.QtGui import QIcon
|
|
from .widgets.menubar import MenuBar
|
|
from .widgets.toolbar import ToolBar
|
|
from .widgets.metadata_input import MetadataInput
|
|
from .widgets.measurement_settings import ScriptSelection, MeasurementSettings
|
|
from .widgets.plot import Plot
|
|
from .widgets.device_select import ListChoice
|
|
# from .widgets.treeview import TreeView
|
|
|
|
import multiprocessing as mp
|
|
import threading as mt
|
|
|
|
from ..utility.config import AppConfig
|
|
|
|
import cpdctrl.voltage_measurement_device as vmd
|
|
import cpdctrl.led_control_device as ledd
|
|
from cpdctrl.led_script import LedScript
|
|
from cpdctrl.utility.data import DataCollector
|
|
from cpdctrl.measurement import measure
|
|
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
"""
|
|
MainWindow
|
|
|
|
Args:
|
|
QMainWindow (QMainWindow): Inheritance
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""
|
|
Initialize the Main-Window.
|
|
"""
|
|
super().__init__()
|
|
# Window-Settings
|
|
self.setWindowTitle(AppConfig.APP_NAME)
|
|
self.setGeometry(100, 100, 800, 600)
|
|
central_widget = QWidget(self)
|
|
self.setCentralWidget(central_widget)
|
|
|
|
layout = QHBoxLayout(central_widget)
|
|
central_widget.setLayout(layout)
|
|
|
|
self.create_toolbars()
|
|
|
|
# Add Widgets to Window
|
|
self.setMenuBar(MenuBar(self))
|
|
self.setStatusBar(QStatusBar(self))
|
|
|
|
self.w_leftbox = QToolBox(self)
|
|
layout.addWidget(self.w_leftbox)
|
|
|
|
init_elements = [("name1", "val1"), ("name2", "val2"), ("interval", 0.5)]
|
|
self.w_metadata = MetadataInput(init_elements)
|
|
self.w_leftbox.addItem(self.w_metadata, "Measurement metadata")
|
|
# Measurement settings
|
|
self.w_measurement_settings = MeasurementSettings()
|
|
self.w_leftbox.addItem(self.w_measurement_settings, "Measurement settings")
|
|
self.w_measurement_settings.set_value("interval", AppConfig.MAIN_CFG.get_or("interval", 0.5))
|
|
|
|
self.w_plot = Plot()
|
|
layout.addWidget(self.w_plot)
|
|
|
|
self.verbose = True
|
|
|
|
# devices
|
|
self.vmdev = None
|
|
self.leddev = None
|
|
self.vmdev_autoconnect()
|
|
self.leddev_autoconnect()
|
|
|
|
# Measurement
|
|
self.measurement_timer = None
|
|
self.led_script = None
|
|
self.data_collector = None
|
|
self.data_queue = None
|
|
self.proc_measure = None
|
|
|
|
self.set_status("Ready")
|
|
|
|
def set_status(self, msg):
|
|
self.statusBar().showMessage(msg)
|
|
|
|
def create_toolbars(self) -> None:
|
|
"""
|
|
Creates and adds the top and right toolbars to the main window.
|
|
"""
|
|
# Top Toolbar [PyQt6.QtWidgets.QToolBar]
|
|
self.topbar = ToolBar(self, orientation=Qt.Orientation.Horizontal,
|
|
style=Qt.ToolButtonStyle.ToolButtonTextUnderIcon, icon_size=(24, 24))
|
|
|
|
# Top Toolbar Buttons
|
|
self.topbar.add_button("meas_devices", "Devices", QIcon.fromTheme(QIcon.ThemeIcon.Printer), self.vmdev_connect_from_dialog)
|
|
self.topbar.add_button("meas_start", "Start", QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart), self.measure_start)
|
|
self.topbar.add_button("meas_stop", "Stop", QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStop), self.measure_stop)
|
|
self.topbar.add_button("meas_save", "Save", QIcon.fromTheme(QIcon.ThemeIcon.DocumentSaveAs), self.measurement_save)
|
|
self.topbar.add_separator()
|
|
self.topbar.add_button("app_exit", "Exit", QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit), self.app_exit)
|
|
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.topbar)
|
|
|
|
# disable the Stop and Save buttons
|
|
self.topbar.disable_button("meas_stop")
|
|
self.topbar.disable_button("meas_save")
|
|
|
|
# Right Toolbar [PyQt6.QtWidgets.QToolBar]
|
|
# self.rightbar = ToolBar(self, orientation=Qt.Orientation.Vertical, style=Qt.ToolButtonStyle.ToolButtonIconOnly, icon_size=(24, 24))
|
|
# self.rightbar.add_separator()
|
|
# self.rightbar.add_button("Settings", "resources/assets/icons/windows/shell32-315.ico", self.settings_window)
|
|
# self.addToolBar(Qt.ToolBarArea.RightToolBarArea, self.rightbar)
|
|
|
|
# def create_treeview(self) -> TreeView:
|
|
"""
|
|
Creates and adds the tree view widget to the main window.
|
|
"""
|
|
# return TreeView(self)
|
|
|
|
def leddev_connect(self, 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.name", leddev_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)
|
|
except KeyError:
|
|
pass
|
|
def leddev_connect_from_dialog(self):
|
|
"""
|
|
Open a dialog that lets the user choose a LED control device,
|
|
and then connects to the device.
|
|
"""
|
|
devices = ledd.list_devices()
|
|
device_dialog = ListChoice(devices)
|
|
if device_dialog.exec() == QDialog.DialogCode.Accepted:
|
|
leddev_type, leddev_name = device_dialog.get_selected()
|
|
if self.verbose: print(f"Connecting to {leddev_type}:{leddev_name}")
|
|
self.leddev_connect(leddev_type, leddev_name)
|
|
|
|
def vmdev_connect(self, 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.name", vmdev_name)
|
|
|
|
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)
|
|
except KeyError:
|
|
pass
|
|
except Exception as e:
|
|
print(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,
|
|
and then connects to the device.
|
|
"""
|
|
devices = vmd.list_devices()
|
|
device_dialog = ListChoice(devices)
|
|
if device_dialog.exec() == QDialog.DialogCode.Accepted:
|
|
vmdev_type, vmdev_name = device_dialog.get_selected()
|
|
if self.verbose: print(f"Connecting to {vmdev_type}:{vmdev_name}")
|
|
self.vmdev_connect(vmdev_type, vmdev_name)
|
|
|
|
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")
|
|
|
|
if self.leddev is None:
|
|
self.leddev_connect_from_dialog()
|
|
if self.leddev is None:
|
|
raise ValueError("No led control device selected")
|
|
|
|
self.topbar.disable_button("meas_start")
|
|
self.topbar.disable_button("meas_devices")
|
|
self.topbar.disable_button("meas_save")
|
|
self.topbar.enable_button("meas_stop")
|
|
self.w_plot.clear_data()
|
|
|
|
script = 100
|
|
measurement_name = "guitest"
|
|
led_script = LedScript(script=script)
|
|
flush_after = self.w_measurement_settings.get_value("flush_after")
|
|
use_buffer = self.w_measurement_settings.get_value("use_buffer")
|
|
max_measurements = self.w_measurement_settings.get_value("max_measurements")
|
|
stop_on_script_end = self.w_measurement_settings.get_value("stop_on_script_end")
|
|
interval = self.w_measurement_settings.get_value("interval")
|
|
|
|
metadata = self.w_metadata.get_dict()
|
|
metadata["interval"] = str(interval)
|
|
metadata["name"] = measurement_name
|
|
metadata["led"] = "led"
|
|
metadata["led_script"] = str(script)
|
|
self.w_metadata.update_from_dict({
|
|
"interval": str(interval),
|
|
"led_script": str(script)
|
|
})
|
|
|
|
if self.verbose:
|
|
print(f"Starting measurement with:\n\tinterval = {interval}\n\tflush_after = {flush_after}\n\tuse_buffer = {use_buffer}\n\tmax_measurements = {max_measurements}\n\tstop_on_script_end = {stop_on_script_end}")
|
|
|
|
self.led_script = LedScript(script=script, auto_update=True, verbose=True)
|
|
self.data_collector = DataCollector(metadata=metadata, data_path=AppConfig.MAIN_CFG.get("datadir"), data_name=measurement_name)
|
|
# data_collector.clear()
|
|
self.data_queue = mp.Queue()
|
|
self.command_queue = mp.Queue()
|
|
# Argument order must match the definition
|
|
self.proc_measure = mt.Thread(target=measure, args=(
|
|
self.vmdev,
|
|
self.leddev,
|
|
self.led_script,
|
|
self.data_collector,
|
|
interval,
|
|
flush_after,
|
|
use_buffer,
|
|
max_measurements,
|
|
stop_on_script_end,
|
|
self.verbose, # verbose
|
|
self.command_queue,
|
|
self.data_queue
|
|
))
|
|
self.proc_measure.start()
|
|
self.measurement_timer = QTimer(self)
|
|
self.measurement_timer.timeout.connect(self.measure_update)
|
|
self.measurement_timer.start(300) # TODO: set interval
|
|
|
|
def measure_stop(self):
|
|
if not self.measurement_is_running():
|
|
raise RuntimeError("measure_stop: Measurement is not running")
|
|
self.command_queue.put("stop")
|
|
self.measurement_timer.stop()
|
|
self.proc_measure.join()
|
|
self.set_status("Ready")
|
|
self.led_script.stop_updating() # stop watching for file updates (if enabled)
|
|
self.data_collector.save_csv(verbose=True)
|
|
data, metadata = self.data_collector.get_data()
|
|
self.proc_measure = None
|
|
self.led_script = None
|
|
self.topbar.enable_button("meas_start")
|
|
self.topbar.enable_button("meas_devices")
|
|
self.topbar.enable_button("meas_save")
|
|
self.topbar.disable_button("meas_stop")
|
|
|
|
def measure_update(self):
|
|
if self.proc_measure.is_alive():
|
|
while not self.data_queue.empty():
|
|
# print(data_queue.qsize(), "\n\n")
|
|
current_data = self.data_queue.get(block=False)
|
|
i, tval, vval, led_val = current_data
|
|
print(f"Data {i:03}: {tval}s, {vval}V, {led_val}%")
|
|
self.set_status(f"Data {i:03}: {tval}s, {vval}V, {led_val}%")
|
|
# update the plot
|
|
self.w_plot.update_plot(tval, vval, led_val)
|
|
|
|
def measurement_is_running(self):
|
|
return self.proc_measure is not None
|
|
|
|
def measurement_save(self) -> None:
|
|
"""
|
|
Save the last measurement data to a file.
|
|
"""
|
|
if self.data_collector is None:
|
|
raise RuntimeError("Can not save, not data collector initialized")
|
|
csv = self.data_collector.to_csv()
|
|
# open file dialog
|
|
filename = self.data_collector.dirname + ".csv"
|
|
file_name, _ = QFileDialog.getSaveFileName(self, "Save File", filename, "CSV Files (*.csv)")
|
|
if file_name:
|
|
with open(file_name, "w") as f:
|
|
f.write(csv)
|
|
self.set_status(f"Saved data to {file_name}")
|
|
else:
|
|
self.set_status(f"Aborted saving data, no file selected")
|
|
|
|
def app_exit(self) -> None:
|
|
"""
|
|
Closes the application.
|
|
"""
|
|
self.close()
|
|
|
|
def settings_window(self) -> None:
|
|
"""
|
|
Event handler for the "Settings" button. Displays the "Settings" window.
|
|
"""
|
|
|
|
def __del__(self):
|
|
self.measure_stop()
|