Fix and refactor led script updates. They now work from the file and the LedScriptViewer

This commit is contained in:
CPD 2025-03-17 13:24:53 +01:00
parent 605310dbe5
commit bfaae16ac4
3 changed files with 60 additions and 34 deletions

View File

@ -112,10 +112,10 @@ class MainWindow(QMainWindow):
self.w_right_tab.addTab(self.w_plot, "Plot") self.w_right_tab.addTab(self.w_plot, "Plot")
# LED SCRIPT # LED SCRIPT
self.w_led_script = LedScriptViewer(LedScript(0)) self.w_led_script_viewer = LedScriptViewer(LedScript(0))
self.w_led_script.dataChanged.connect(self._led_script_updated) self.w_led_script_viewer.scriptUpdated.connect(self._led_script_updated)
self.w_lefttab.addTab(self.w_led_script, "LED Script") self.w_lefttab.addTab(self.w_led_script_viewer, "LED Script")
self.w_measurement_settings.w_led_script.script_changed.connect(self.led_script_load) self.w_measurement_settings.w_led_script_load.loadScript.connect(self.led_script_load)
self.verbose = True self.verbose = True
@ -256,16 +256,14 @@ class MainWindow(QMainWindow):
if self.leddev is None: if self.leddev is None:
raise ValueError("No led control device selected") raise ValueError("No led control device selected")
name = self.w_measurement_settings.get_value("name") name = self.w_measurement_settings.get_value("name")
script = self.w_measurement_settings.get_value("led_script") script = self.w_measurement_settings.get_value("led_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")
# check if device supports buffer mode # check if device supports buffer mode
if use_buffer and not callable(getattr(self.vmdev, 'buffer_measure', None)): if use_buffer and not callable(getattr(self.vmdev, 'buffer_measure', None)):
# show warning dialog # show warning dialog
ret = QMessageBox.warning(self, "Buffer mode not supported", "The selected voltage measurement device does not support the buffer measurement mode.\nClick Ok to continue without buffer measurement mode.", buttons=QMessageBox.StandardButton.Ok|QMessageBox.StandardButton.Cancel, defaultButton=QMessageBox.StandardButton.Ok) ret = QMessageBox.warning(self, "Buffer mode not supported", "The selected voltage measurement device does not support the buffer measurement mode.\nClick OK to continue without buffer measurement mode.", buttons=QMessageBox.StandardButton.Ok|QMessageBox.StandardButton.Cancel, defaultButton=QMessageBox.StandardButton.Ok)
if ret == QMessageBox.StandardButton.Ok: if ret == QMessageBox.StandardButton.Ok:
log.warning("Device does not support buffer mode, disabling") log.warning("Device does not support buffer mode, disabling")
use_buffer = False use_buffer = False
@ -290,6 +288,8 @@ class MainWindow(QMainWindow):
self.topbar.disable_button("meas_save") self.topbar.disable_button("meas_save")
self.topbar.disable_button("meas_load") self.topbar.disable_button("meas_load")
self.topbar.enable_button("meas_stop") self.topbar.enable_button("meas_stop")
self.w_measurement_settings.setEnabled(False)
self.w_metadata.setEnabled(False)
self.w_plot.clear_data() self.w_plot.clear_data()
# have the led script member be the only auto-updating script, # have the led script member be the only auto-updating script,
@ -326,7 +326,7 @@ class MainWindow(QMainWindow):
# the time left estimation might be a little to short, since the actual measurement is started a few lines of # the time left estimation might be a little to short, since the actual measurement is started a few lines of
# code later # code later
self.led_script.start() self.led_script.start()
self.w_led_script.update_time_predictions() self.w_led_script_viewer.update_time_predictions()
def measure_stop(self): def measure_stop(self):
log.info("Stopping measurement") log.info("Stopping measurement")
@ -339,7 +339,7 @@ class MainWindow(QMainWindow):
self.set_status("Saving data...") self.set_status("Saving data...")
self.data_collector.save_csv_in_dir() self.data_collector.save_csv_in_dir()
self.proc_measure = None self.proc_measure = None
# dont update w_led_script, keep displaying the last values # don't update w_led_script, keep displaying the last values
self.led_script.reset() self.led_script.reset()
self.topbar.enable_button("meas_start") self.topbar.enable_button("meas_start")
self.topbar.enable_button("connect_vmdev") self.topbar.enable_button("connect_vmdev")
@ -347,10 +347,12 @@ class MainWindow(QMainWindow):
self.topbar.enable_button("meas_save") self.topbar.enable_button("meas_save")
self.topbar.enable_button("meas_load") self.topbar.enable_button("meas_load")
self.topbar.disable_button("meas_stop") self.topbar.disable_button("meas_stop")
self.w_measurement_settings.setEnabled(True)
self.w_metadata.setEnabled(True)
self.set_status("Ready") self.set_status("Ready")
def measure_update(self): def measure_update(self):
self.w_led_script.update_time(time.time()) self.w_led_script_viewer.update_time(time.time())
if self.proc_measure.is_alive(): if self.proc_measure.is_alive():
while not self.data_queue.empty(): while not self.data_queue.empty():
# print(data_queue.qsize(), "\n\n") # print(data_queue.qsize(), "\n\n")
@ -402,6 +404,14 @@ class MainWindow(QMainWindow):
self.set_status(f"Aborted saving data, no file selected") self.set_status(f"Aborted saving data, no file selected")
def measurement_load(self, filepath): def measurement_load(self, filepath):
"""
Load measurement data from the filepath. Only updates the plot.
Parameters
----------
filepath
The path to the file or directory to load the data from.
File may be csv or pickle containing (data, metadata)
"""
log.info(f"Loading measurement data from '{filepath}'") log.info(f"Loading measurement data from '{filepath}'")
if self.measurement_is_running(): if self.measurement_is_running():
QMessageBox.critical(self, "Measurement running", "Can not load data while measurement is running") QMessageBox.critical(self, "Measurement running", "Can not load data while measurement is running")
@ -421,6 +431,9 @@ class MainWindow(QMainWindow):
self.w_plot.set_data(data[:,1], data[:,2], data[:,3]) self.w_plot.set_data(data[:,1], data[:,2], data[:,3])
def measurement_load_dialog(self): def measurement_load_dialog(self):
"""
Open a file dialog to load measurement data. This only updates the plot
"""
if self.measurement_is_running(): if self.measurement_is_running():
QMessageBox.critical(self, "Measurement running", "Can not load data while measurement is running") QMessageBox.critical(self, "Measurement running", "Can not load data while measurement is running")
return return
@ -436,6 +449,9 @@ class MainWindow(QMainWindow):
self.measurement_load(file_path) self.measurement_load(file_path)
def led_script_load(self): def led_script_load(self):
"""
Load a new led script from the value in the measurement settings widget
"""
script = self.w_measurement_settings.get_value("led_script") script = self.w_measurement_settings.get_value("led_script")
script_type = self.w_measurement_settings.get_value("led_script_type") script_type = self.w_measurement_settings.get_value("led_script_type")
auto_update = AppConfig.MAIN_CFG.get_or("led_script_watch_file", False) auto_update = AppConfig.MAIN_CFG.get_or("led_script_watch_file", False)
@ -454,7 +470,7 @@ class MainWindow(QMainWindow):
self.led_script_watcher.fileChanged.connect(self._led_script_update_from_file) self.led_script_watcher.fileChanged.connect(self._led_script_update_from_file)
else: else:
self.led_script_watcher = None self.led_script_watcher = None
self.w_led_script.set_script(self.led_script) self.w_led_script_viewer.set_script(self.led_script)
self._led_script_updated() self._led_script_updated()
def _led_script_update_from_file(self): def _led_script_update_from_file(self):
@ -468,9 +484,8 @@ class MainWindow(QMainWindow):
""" """
Send the new led script to the measurement thread and update the gui widget. Send the new led script to the measurement thread and update the gui widget.
""" """
print("Update script")
# update gui # update gui
self.w_led_script.update_time_predictions() self.w_led_script_viewer.update_time_predictions()
# update the measurement led script # update the measurement led script
if self.measurement_is_running(): if self.measurement_is_running():
self.command_queue.put(("led_script", self.led_script.copy())) self.command_queue.put(("led_script", self.led_script.copy()))

View File

@ -1,12 +1,14 @@
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea, QTableView, QFormLayout from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea, QTableView, QFormLayout
from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSignal
import numpy as np
from cpdctrl.led_script import LedScript
import time import time
import datetime import datetime
import logging
log = logging.getLogger(__name__)
import numpy as np
from cpdctrl.led_script import LedScript
timedelta = [("d", 24*3600), ("h", 3600), ("m", 60), ("s", 1)] timedelta = [("d", 24*3600), ("h", 3600), ("m", 60), ("s", 1)]
def duration_to_string(duration: float) -> str: def duration_to_string(duration: float) -> str:
@ -35,6 +37,12 @@ def duration_to_string(duration: float) -> str:
return s.strip() return s.strip()
class TimeLeft(QWidget): class TimeLeft(QWidget):
"""
Widget that shows:
- Time passed
- Time left
- End time and date
"""
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.t_end = None self.t_end = None
@ -88,6 +96,7 @@ class LedScriptTableModel(QAbstractTableModel):
It only allows editing values that are in the future It only allows editing values that are in the future
""" """
scriptUpdated = pyqtSignal()
def __init__(self, led_script: LedScript, parent=None): def __init__(self, led_script: LedScript, parent=None):
super().__init__(parent) super().__init__(parent)
self.led_script: LedScript = led_script self.led_script: LedScript = led_script
@ -117,8 +126,16 @@ class LedScriptTableModel(QAbstractTableModel):
def setData(self, index: QModelIndex, value, role: int): def setData(self, index: QModelIndex, value, role: int):
if role == Qt.ItemDataRole.EditRole: if role == Qt.ItemDataRole.EditRole:
self.led_script.script[self.indices[index.column()]][index.row()] = value newscript = self.led_script.script.copy()
self.dataChanged.emit(index, index, role) newscript[self.indices[index.column()]][index.row()] = value
try:
log.info(f"Updating script from {self.led_script.script} to {newscript}")
self.led_script.update_from_script(newscript)
except ValueError as e:
raise e
# return False
self.dataChanged.emit(index, index) # this is for updating the view
self.scriptUpdated.emit() # this for notifying the main window
return True return True
return False return False
@ -149,6 +166,7 @@ class LedScriptTableModel(QAbstractTableModel):
class LedScriptViewer(QWidget): class LedScriptViewer(QWidget):
scriptUpdated = pyqtSignal()
def __init__(self, led_script: LedScript, parent=None): def __init__(self, led_script: LedScript, parent=None):
super().__init__(parent) super().__init__(parent)
self.led_script = None self.led_script = None
@ -173,14 +191,10 @@ class LedScriptViewer(QWidget):
self.w_time_left = TimeLeft(self) self.w_time_left = TimeLeft(self)
self.l_vbox.addWidget(self.w_time_left) self.l_vbox.addWidget(self.w_time_left)
self.l_vbox.addStretch(1) self.l_vbox.addStretch(1)
# TODO: this should emit when the script is changed
# TODO: apparently not working, also cant use on_update_callback since the file watcher watchdog thing
# TODO: when updating, the update notice comes twice so I suspect the model copies the other one?
# is not in a QThread, thus the callback cant interact with qt objects and signals
self.model.dataChanged.connect(self.update_time_predictions)
def set_script(self, led_script: LedScript): def set_script(self, led_script: LedScript):
self.model = LedScriptTableModel(led_script, self) self.model = LedScriptTableModel(led_script, self)
self.model.scriptUpdated.connect(self.scriptUpdated)
self.w_table.setModel(self.model) self.w_table.setModel(self.model)
self.w_table.show() self.w_table.show()
@ -199,8 +213,6 @@ class LedScriptViewer(QWidget):
t_now t_now
Current time since epoch. Current time since epoch.
""" """
if self.model.led_script.has_updated():
self.update_time_predictions()
self.model.update_time(t_now) self.model.update_time(t_now)
self.w_time_left.update_time(t_now) self.w_time_left.update_time(t_now)
self.w_table.update() self.w_table.update()

View File

@ -34,7 +34,7 @@ class DeviceSelection(QGroupBox):
class ScriptSelection(QGroupBox): class ScriptSelection(QGroupBox):
script_changed = pyqtSignal() loadScript = pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent=parent, title="LED Script") super().__init__(parent=parent, title="LED Script")
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
@ -55,7 +55,7 @@ class ScriptSelection(QGroupBox):
self.w_constant_value = QSpinBox() self.w_constant_value = QSpinBox()
self.w_constant_value.setRange(0, 100) self.w_constant_value.setRange(0, 100)
# signal when changed # signal when changed
self.w_constant_value.valueChanged.connect(lambda: self.script_changed.emit()) self.w_constant_value.valueChanged.connect(lambda: self.loadScript.emit())
# Layouts # Layouts
l_constant_value = QVBoxLayout() l_constant_value = QVBoxLayout()
@ -98,7 +98,7 @@ class ScriptSelection(QGroupBox):
self.file_path = file_path self.file_path = file_path
self.w_script_file.setText(self.file_path) self.w_script_file.setText(self.file_path)
# signal the change # signal the change
self.script_changed.emit() self.loadScript.emit()
def get_script(self): def get_script(self):
@ -130,8 +130,8 @@ class MeasurementSettings(QWidget):
self.w_device_selection = DeviceSelection() self.w_device_selection = DeviceSelection()
self.l_vbox.addWidget(self.w_device_selection) self.l_vbox.addWidget(self.w_device_selection)
# - script # - script
self.w_led_script = ScriptSelection() self.w_led_script_load = ScriptSelection()
self.l_vbox.addWidget(self.w_led_script) self.l_vbox.addWidget(self.w_led_script_load)
# key-value stuff in a form # key-value stuff in a form
self.w_form = SettingsForm(AppConfig.MEAS_CFG) self.w_form = SettingsForm(AppConfig.MEAS_CFG)
self.l_vbox.addWidget(self.w_form) self.l_vbox.addWidget(self.w_form)
@ -155,7 +155,6 @@ class MeasurementSettings(QWidget):
self.l_vbox.addStretch(1) self.l_vbox.addStretch(1)
def set_value(self, key, value): def set_value(self, key, value):
if key in self.w_form: if key in self.w_form:
self.w_form.set_value(key, value) self.w_form.set_value(key, value)
@ -168,8 +167,8 @@ class MeasurementSettings(QWidget):
if key in self.w_form: if key in self.w_form:
return self.w_form.get_value(key) return self.w_form.get_value(key)
elif key == "led_script": elif key == "led_script":
return self.w_led_script.get_script() return self.w_led_script_load.get_script()
elif key == "led_script_type": elif key == "led_script_type":
return self.w_led_script.get_script_type() return self.w_led_script_load.get_script_type()
else: else:
raise ValueError(f"Unknown key: {key}") raise ValueError(f"Unknown key: {key}")