diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 46cda62..55c14dd 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -1,14 +1,26 @@ ''' app/ui/main_window.py ''' from PyQt6.QtCore import Qt -from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QTextEdit, QLabel -from ..utils.config import AppConfig +from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QTextEdit, QLabel, QDialog +from PyQt6.QtGui import QIcon from .widgets.menubar import MenuBar from .widgets.toolbar import ToolBar from .widgets.statusbar import StatusBar from .widgets.metadata_input import MetadataInput 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): """ @@ -50,6 +62,14 @@ class MainWindow(QMainWindow): layout.addWidget(MetadataInput(init_elements)) layout.addWidget(Plot()) + self.verbose = True + + # devices + self.vmdev = None + self.leddev = None + self.vmdev_autoconnect() + self.leddev_autoconnect() + def create_toolbars(self) -> None: """ Creates and adds the top and right toolbars to the main window. @@ -59,13 +79,11 @@ class MainWindow(QMainWindow): style=Qt.ToolButtonStyle.ToolButtonTextUnderIcon, icon_size=(24, 24)) # Top Toolbar Buttons - self.topbar.add_button( - "Open", "resources/assets/icons/windows/imageres-10.ico", self.open_file) - self.topbar.add_button( - "Save", "resources/assets/icons/windows/shell32-259.ico", self.save_file) + self.topbar.add_button("Devices", QIcon.fromTheme(QIcon.ThemeIcon.Printer), self.vmdev_connect_from_dialog) + self.topbar.add_button("Start", QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart), self.measure_start) + self.topbar.add_button("Save", "resources/assets/icons/windows/shell32-259.ico", self.save_file) self.topbar.add_separator() - self.topbar.add_button( - "Exit", "resources/assets/icons/windows/shell32-220.ico", self.exit_app) + self.topbar.add_button("Exit", "resources/assets/icons/windows/shell32-220.ico", self.exit_app) # Right Toolbar [PyQt6.QtWidgets.QToolBar] self.rightbar = ToolBar(self, orientation=Qt.Orientation.Vertical, @@ -74,10 +92,7 @@ class MainWindow(QMainWindow): # Right Toolbar Buttons self.rightbar.add_separator() - self.rightbar.add_button( - "Privacy", "resources/assets/icons/windows/shell32-167.ico", self.privacy_window) - self.rightbar.add_button( - "Settings", "resources/assets/icons/windows/shell32-315.ico", self.settings_window) + self.rightbar.add_button("Settings", "resources/assets/icons/windows/shell32-315.ico", self.settings_window) self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.topbar) self.addToolBar(Qt.ToolBarArea.RightToolBarArea, self.rightbar) @@ -88,6 +103,122 @@ class MainWindow(QMainWindow): """ # 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 + 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") + script = 100 + measurement_name = "guitest" + led_script = LedScript(script=script) + flush_after = AppConfig.MAIN_CFG.get_or("flush_after", None) + use_buffer = AppConfig.MAIN_CFG.get_or("use_buffer", False) + max_measurements = AppConfig.MAIN_CFG.get_or("max_measurements", None) + stop_on_script_end = AppConfig.MAIN_CFG.get_or("stop_on_script_end", False) + interval = AppConfig.MAIN_CFG.get_or("measurement_interval_s", 1.0) + + metadata = {} + metadata["interval"] = str(interval) + metadata["name"] = measurement_name + metadata["led"] = "led" + metadata["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}") + + led_script = LedScript(script=script, auto_update=True, verbose=True) + data_collector = DataCollector(metadata=metadata, data_path=AppConfig.MAIN_CFG.get("datadir"), data_name=measurement_name) + # data_collector.clear() + data_queue = mp.Queue() + command_queue = mp.Queue() + # Argument order must match the definition + + proc_measure = mt.Thread(target=measure, args=( + self.vmdev, + self.leddev, + led_script, + data_collector, + interval, + flush_after, + use_buffer, + max_measurements, + stop_on_script_end, + self.verbose, # verbose + command_queue, + data_queue + )) + proc_measure.start() + try: + while proc_measure.is_alive(): + while not data_queue.empty(): + # print(data_queue.qsize(), "\n\n") + current_data = data_queue.get(block=False) + i, tval, vval, led_val = current_data + print(f"Data {i:03}: {tval}s, {vval}V, {led_val}%") + + except KeyboardInterrupt: + pass + command_queue.put("stop") + proc_measure.join() + print("Measurement stopped" + " " * 50) + led_script.stop_updating() # stop watching for file updates (if enabled) + data_collector.save_csv(verbose=True) + data, metadata = data_collector.get_data() + + def create_edit(self) -> QTextEdit: """ Creates and adds the QTextEdit widget to the main window. @@ -116,9 +247,3 @@ class MainWindow(QMainWindow): """ Event handler for the "Settings" button. Displays the "Settings" window. """ - - def privacy_window(self) -> None: - """ - Event handler for the "Privacy" button. Displays the "Privacy" window. - """ - print("privacy_window") diff --git a/app/ui/widgets/device_select.py b/app/ui/widgets/device_select.py new file mode 100644 index 0000000..ef5c93d --- /dev/null +++ b/app/ui/widgets/device_select.py @@ -0,0 +1,27 @@ +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QListWidget, QListWidgetItem, QLabel, QDialogButtonBox, QDialog +from typing import Callable + +# QT6 non-window that presents a list of devices of which one can be connected +class ListChoice(QDialog): + def __init__(self, items: dict[str, list[str]], parent=None): + super().__init__(parent) + self.setWindowTitle("Select Device") + self.setLayout(QVBoxLayout()) + self.layout().addWidget(QLabel("Select a device to connect to:")) + self.device_list = QListWidget() + self.layout().addWidget(self.device_list) + for key, items in items.items(): + for item in items: + w = QListWidgetItem(f"{key}: {item}", parent=self.device_list) + w.key = key + w.item = item + # self.device_list.addItem(f"{key}: {item}") + + self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + self.layout().addWidget(self.buttons) + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + + def get_selected(self): + selected_item = self.device_list.currentItem() + return selected_item.key, selected_item.item \ No newline at end of file