Source code for GUI.MainWindow

from setup import Setup
from GUI.ExperimentPages import *
from GUI.Utils import resource_path

logger = logging.getLogger("root")


[docs]class MainWindow(QMainWindow): """ Defines the main window of the application. :type setup: Setup :param setup: Instance of Setup to allow access to sensors and actuators. """ def __init__(self, setup: Setup, *args, **kwargs) -> None: super(MainWindow, self).__init__(*args, **kwargs) self.setup = setup self.setStyleSheet( """ QMainWindow { background-color: rgb(255, 255, 255); } """ ) self.setWindowTitle("Mass Flow Sensor") self.setup_menu_bar() self.setup_tool_bar() self.setup_status_bar() # Main layout self.stack = QStackedWidget() self.stack.addWidget( PWMSetting( setup=self.setup, start_recording_action=self._start_recording, stop_recording_action=self._stop_recording, enable_output_action=self._toggle_output, set_flow_action=self.setup.set_flow, enable_toggle_setpoint_action=self._enable_toggle_setpoint, disable_toggle_setpoint_action=self._disable_toggle_setpoint, ) ) self.stack.addWidget( PIDSetting( setup=self.setup, start_recording_action=self._start_recording, stop_recording_action=self._stop_recording, enable_output_action=self._toggle_output, set_flow_action=self.setup.set_flow, enable_toggle_setpoint_action=self._enable_toggle_setpoint, disable_toggle_setpoint_action=self._disable_toggle_setpoint, ) ) self.stack.addWidget( MassFlowEstimation( setup=self.setup, enable_competition_mode=self._enable_competition_mode, disable_competition_mode=self._disable_competition_mode, enable_massflow_setting=self._enable_massflow_setting, disable_massflow_setting=self._disable_massflow_setting, ) ) self.setCentralWidget(self.stack) self.stack.currentWidget().enter() self.error_message = QErrorMessage() # Set up timer for the setup error watchdog self.timer = QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self._setup_error_watchdog) self.timer.start() def _setup_error_watchdog(self): if self.setup.error_high_temperature: # reset error self.setup.error_high_temperature = False # issue warning self.error_message.showMessage( "The output was turned off since a high temperature error was triggered when reaching {} °C".format( self.setup.config["safety"]["upper_temperature_limit"] ) ) if self.setup.error_low_flow: # reset error self.setup.error_low_flow = False # issue warning self.error_message.showMessage( "The output was turned off since a low flow error was triggered when going below {} slm".format( self.setup.config["safety"]["lower_flow_limit"] ) ) def setup_menu_bar(self) -> None: bar = self.menuBar() configuration = bar.addMenu("Configuration") self.action_set_temp_calibration = QAction("Set Temperature Calibration", self) self.action_set_temp_calibration.setStatusTip( "Force the current delta T to be zero." ) self.action_set_temp_calibration.triggered.connect(self._calibrate_temperature) configuration.addAction(self.action_set_temp_calibration) self.action_reset_temp_calibration = QAction( "Reset Temperature Calibration", self ) self.action_reset_temp_calibration.setStatusTip( "Reset the temperature calibration." ) self.action_reset_temp_calibration.triggered.connect( self._reset_temperature_calibration ) configuration.addAction(self.action_reset_temp_calibration) configuration.addSeparator() self.action_reverse_temp_sensors = QAction("Reverse Temperature Sensors", self) self.action_reverse_temp_sensors.setStatusTip( "Changes the order of the temperature sensors in case they have been set up wrongly." ) self.action_reverse_temp_sensors.triggered.connect( self._reverse_temperature_sensors ) configuration.addAction(self.action_reverse_temp_sensors)
[docs] def setup_status_bar(self) -> None: """ Sets up a status bar displaying sponsor layouts and tips for hovered over widgets. """ sensirion_img = QPixmap(resource_path("Icons\\sensirion.png")) sensirion_logo = QLabel(self) sensirion_logo.setPixmap(sensirion_img.scaledToHeight(32)) sensirion_logo.setAlignment(Qt.AlignLeft) eth_img = QPixmap(resource_path("Icons\\eth.png")) eth_logo = QLabel(self) eth_logo.setPixmap(eth_img.scaledToHeight(32)) eth_logo.setAlignment(Qt.AlignRight) status_bar = QStatusBar(self) status_bar.addPermanentWidget(sensirion_logo) status_bar.addPermanentWidget(eth_logo) self.setStatusBar(status_bar)
[docs] def setup_tool_bar(self) -> None: """ Adds a toolbar to the main window and defines a set of actions for it. """ toolbar = QToolBar("MyMainToolbar") toolbar.setIconSize(QSize(24, 24)) self.addToolBar(toolbar) # Set up actions self.action_stop_recording = QAction( QIcon(resource_path("Icons\\control-stop-square.png")), "Stop recording", self, ) self.action_stop_recording.setStatusTip("Stop recording measurements") self.action_stop_recording.triggered.connect(self._stop_recording) toolbar.addAction(self.action_stop_recording) self.action_start_recording = QAction( QIcon(resource_path("Icons\\control.png")), "Start recording", self ) self.action_start_recording.setStatusTip("Start recording measurements") self.action_start_recording.triggered.connect(self._start_recording) self.action_start_recording.setDisabled(True) toolbar.addAction(self.action_start_recording) toolbar.addSeparator() self.action_previous_view = QAction( QIcon(resource_path("Icons\\arrow-180.png")), "Previous View", self ) self.action_previous_view.setStatusTip("Go to previous view") self.action_previous_view.triggered.connect(self._go_to_previous_view) self.action_previous_view.setDisabled(True) toolbar.addAction(self.action_previous_view) self.action_next_view = QAction( QIcon(resource_path("Icons\\arrow.png")), "Next View", self ) self.action_next_view.setStatusTip("Go to next view") self.action_next_view.triggered.connect(self._go_to_next_view) toolbar.addAction(self.action_next_view) toolbar.addSeparator() self.action_reset_plots = QAction( QIcon(resource_path("Icons\\application-monitor.png")), "Reset Plots", self ) self.action_reset_plots.setStatusTip("Reset plots to original axis limits") self.action_reset_plots.triggered.connect(self._reset_plots) toolbar.addAction(self.action_reset_plots) toolbar.addSeparator() self.action_competition_mode = QPushButton("Competition Mode", self) self.action_competition_mode.setStatusTip("Win a Sensirion Humi Gadget!") self.action_competition_mode.setCheckable(True) self.action_competition_mode.setChecked(False) self.action_competition_mode.clicked.connect(self._change_competition_mode) toolbar.addWidget(self.action_competition_mode) self.action_toggle_output = QPushButton("Toggle Output", self) self.action_toggle_output.setStatusTip("Turn the heater on or off.") self.action_toggle_output.setCheckable(True) self.action_toggle_output.setStyleSheet( """ QPushButton { background: rgba(0, 102, 255, 50); } """ ) self.action_toggle_output.setChecked(False) self.action_toggle_output.clicked.connect(self._toggle_output) toolbar.addWidget(self.action_toggle_output) self.action_toggle_massflow = QPushButton("Toggle Massflow", self) self.action_toggle_massflow.setStatusTip("Turn the mass flow on or off.") self.action_toggle_massflow.setCheckable(True) self.action_toggle_massflow.setStyleSheet( """ QPushButton { background: rgba(0, 102, 255, 50); } """ ) self.action_toggle_massflow.setChecked(False) self.action_toggle_massflow.clicked.connect(self._toggle_massflow) toolbar.addWidget(self.action_toggle_massflow) toolbar.addSeparator() self.action_toggle_setpoint = QPushButton("Setpoint Change", self) self.action_toggle_setpoint.setStatusTip("Perform a setpoint step.") self.action_toggle_setpoint.clicked.connect(self._toggle_setpoint) toolbar.addWidget(self.action_toggle_setpoint) toolbar.addSeparator() self.action_save_measurement_buffer = QAction( QIcon(resource_path("Icons\\printer.png")), "Save Measurement Buffer", self ) self.action_save_measurement_buffer.setStatusTip("Go to next view") self.action_save_measurement_buffer.triggered.connect(self._save_measurement_buffer) toolbar.addAction(self.action_save_measurement_buffer)
[docs] def _save_measurement_buffer(self): """ Toolbar aciton; Allows to save the measurement buffer as a Matlab .mat file. """ self.setup.save_measurement_buffer(folder='data', name='Measurement_MassflowSensor', type='mat')
[docs] def _toggle_setpoint(self): """ Toolbar action; Allows to change the temperature difference setpoint """ if ( self.setup.temperature_difference_setpoint == self.setup.config["general"]["temperature_difference_set_point_low"] ): self.setup.set_setpoint( self.setup.config["general"]["temperature_difference_set_point_high"] ) else: self.setup.set_setpoint( self.setup.config["general"]["temperature_difference_set_point_low"] )
[docs] def _toggle_massflow(self, state=None) -> None: """ Toolbar action; Allows to turn the massflow output on or off :type state: bool :param state: Set True to turn the output state to on, or False vice versa. """ if state is not None: self.action_toggle_massflow.setChecked(state) if self.action_toggle_massflow.isChecked(): # Enable output self.setup.set_flow(self.setup.nominal_massflow) # Change appearance of button self.action_toggle_massflow.setStyleSheet( """ QPushButton { background: rgba(255, 51, 0, 100); } """ ) else: # Disable output self.setup.set_flow(0) self._toggle_output(state=False) # Change appearance of button self.action_toggle_massflow.setStyleSheet( """ QPushButton { background: rgba(0, 102, 255, 100); } """ )
[docs] def _toggle_output(self, state=None) -> None: """ Toolbar action; Allows to turn the pwm output on or off. :type state: bool :param state: Set True to turn the output state to on, or False vice versa. """ if state is not None: self.action_toggle_output.setChecked(state) if self.action_toggle_output.isChecked(): if not self.action_toggle_massflow.isChecked(): self.error_message.showMessage( "Do not try to heat when there is no airflow!" ) self.action_toggle_output.setChecked(False) else: # Enable output self.setup.enable_output( self.stack.currentWidget().desired_pwm_output() ) # Change appearance of button self.action_toggle_output.setStyleSheet( """ QPushButton { background: rgba(255, 51, 0, 100); } """ ) else: # Disable output self.setup.disable_output() # Change appearance of button self.action_toggle_output.setStyleSheet( """ QPushButton { background: rgba(0, 102, 255, 100); } """ )
[docs] def _change_competition_mode(self) -> None: """ Toolbar action; Allows to set the current view to competition mode. """ if self.action_competition_mode.isChecked(): # enter competition mode self.stack.currentWidget().switch_to_competition_mode() else: # leave competition mode self.stack.currentWidget().switch_to_normal_mode()
[docs] def _stop_recording(self) -> None: """ Toolbar action; Allows to stop recording measurements and thus freeze the plots. :return: """ self.action_stop_recording.setDisabled(True) self.setup.stop_buffering() self.action_start_recording.setEnabled(True)
[docs] def _start_recording(self) -> None: """ Toolbar action; Allows to restart recording measurements. Clears the buffer. """ self.action_start_recording.setDisabled(True) self.setup.start_buffering() self.action_stop_recording.setEnabled(True)
[docs] def _go_to_previous_view(self) -> None: """ Toolbar action; Switches to the previous view in the main layout stack. """ if self.stack.currentIndex() == 0: pass else: self.action_competition_mode.setChecked(False) self.stack.currentWidget().leave() self.stack.setCurrentIndex(self.stack.currentIndex() - 1) self.stack.currentWidget().enter() if self.stack.currentIndex() == 0: self.action_previous_view.setDisabled(True) self.action_next_view.setEnabled(True) else: self.action_previous_view.setEnabled(True) self.action_next_view.setEnabled(True)
[docs] def _go_to_next_view(self) -> None: """ Toolbar action; Switches to the next view in the main layout stack. """ if self.stack.currentIndex() == self.stack.count() - 1: pass else: self.action_competition_mode.setChecked(False) self.stack.currentWidget().leave() self.stack.setCurrentIndex(self.stack.currentIndex() + 1) self.stack.currentWidget().enter() if self.stack.currentIndex() == self.stack.count() - 1: self.action_next_view.setDisabled(True) self.action_previous_view.setEnabled(True) else: self.action_next_view.setEnabled(True) self.action_previous_view.setEnabled(True)
[docs] def _reset_plots(self) -> None: """ Toolbar action; Allows to reset all visible plots to their original view. """ self.stack.currentWidget().reset_plots()
[docs] def _calibrate_temperature(self) -> None: """ Toolbar action; Allows to set the current delta T to zero by saving the current temperature difference and subtracting it from the second temperature measurement. """ self.setup.set_temperature_calibration()
[docs] def _reset_temperature_calibration(self) -> None: """ Toolbar action; Allows to reset the calibration temperature difference to zero. """ self.setup.reset_temperature_calibration()
[docs] def _reverse_temperature_sensors(self) -> None: """ Menu action; Allows to switch the order of the temperature sensors if the hardware setup is the wrong way around. """ self.setup.reverse_temp_sensors()
def _enable_competition_mode(self) -> None: self.action_competition_mode.setEnabled(True) def _disable_competition_mode(self) -> None: self.action_competition_mode.setDisabled(True) def _enable_massflow_setting(self) -> None: self.action_toggle_output.setDisabled(True) self.action_toggle_massflow.setDisabled(True) def _disable_massflow_setting(self) -> None: self.action_toggle_output.setEnabled(True) self.action_toggle_massflow.setEnabled(True) def _enable_toggle_setpoint(self) -> None: self.action_toggle_setpoint.setEnabled(True) def _disable_toggle_setpoint(self) -> None: self.action_toggle_setpoint.setDisabled(True)
class Launcher(object): """ Wrapper for the main window, simply launches the Qt application. :type setup: Setup :param setup: Instance of Setup to allow access to sensors and actuators. """ def __init__(self, setup: Setup): self.setup = setup app = QApplication([]) window = MainWindow(setup=setup) window.showMaximized() app.exec_()