diff --git a/cold.py b/cold.py index 3005456..0f6bc41 100755 --- a/cold.py +++ b/cold.py @@ -19,13 +19,14 @@ check_door_status, check_light_safe_to_turn_on, check_marta_safe, + soft_interlock_loop, ) from caen.caenGUIall import caenGUIall from Inner_tracker_GUI.caenGUIall_v2 import caenGUI8LV from db.module_db import ModuleDB +from power_supply.power_supply_ctrl import PowerSupplyController from db.utils import * - # Configure logging logger = logging.getLogger("integration") @@ -49,6 +50,11 @@ def __init__(self): self.update_timer.timeout.connect(self.update_ui) self.update_timer.start(1000) # Update every second + # Setup soft interlock timer (5-second interval, independent of UI updates) + self.soft_interlock_timer = QTimer() + self.soft_interlock_timer.timeout.connect(self.soft_interlock_check) + self.soft_interlock_timer.start(5000) + # Connect to MQTT broker at startup self.connect_mqtt() @@ -62,6 +68,11 @@ def closeEvent(self, event): self.update_timer.stop() logger.debug("Stopped update timer") + # Stop the soft interlock timer + if hasattr(self, "soft_interlock_timer"): + self.soft_interlock_timer.stop() + logger.debug("Stopped soft interlock timer") + # Cleanup Thermal Camera tab if it exists if hasattr(self, "thermal_camera_tab"): self.thermal_camera_tab.cleanup() @@ -125,7 +136,9 @@ def setup_ui(self): # Load the MARTA Cold Room tab from UI file # Create a temporary QMainWindow to load the UI temp_window = QtWidgets.QMainWindow() - marta_ui_file = os.path.join(os.path.dirname(__file__), "coldroom", "marta_coldroom.ui") + marta_ui_file = os.path.join( + os.path.dirname(__file__), "coldroom", "marta_coldroom.ui" + ) uic.loadUi(marta_ui_file, temp_window) # Create a QWidget for our tab and get the central widget from temp_window @@ -160,14 +173,18 @@ def setup_ui(self): self.tab_widget.addTab(self.module_db.ui.tab_2, "Module Inventory") self.tab_widget.addTab(self.module_db.ui.moduleDetailsTab, "Module Details") self.module_db.ui.viewDetailsPB.clicked.connect( - lambda: self.tab_widget.setCurrentIndex(self.tab_widget.indexOf(self.module_db.ui.moduleDetailsTab)) + lambda: self.tab_widget.setCurrentIndex( + self.tab_widget.indexOf(self.module_db.ui.moduleDetailsTab) + ) ) self.module_db.ui.selectModulePB.setEnabled(False) self.modules_list_tab.db_url = self.module_db.db_url # Load settings tab from UI file self.settings_tab = QtWidgets.QWidget() - settings_ui_file = os.path.join(os.path.dirname(__file__), "coldroom", "settings_coldroom.ui") + settings_ui_file = os.path.join( + os.path.dirname(__file__), "coldroom", "settings_coldroom.ui" + ) uic.loadUi(settings_ui_file, self.settings_tab) self.tab_widget.addTab(self.settings_tab, "Settings") @@ -175,6 +192,10 @@ def setup_ui(self): self.IT_caen_tab = caenGUI8LV() self.tab_widget.addTab(self.IT_caen_tab, "IT CAEN") + # Add Power Supply tab + self.power_supply_tab = PowerSupplyController() + self.tab_widget.addTab(self.power_supply_tab, "Power Supply") + # Pre-fill settings with values from system self.load_settings_to_ui() self.get_ring_id() @@ -190,10 +211,15 @@ def setup_ui(self): def get_ring_id(self): self.number_of_modules = 0 # Add default at the start - self.ring_id=get_ring_from_cable("I1") #hardcode the single harting cable I1 + # self.ring_id=get_ring_from_cable("I1") #hardcode the single harting cable I1 + self.ring_id = get_ring_from_cable( + "I1", db_url=self.module_db.db_url + ) # hardcode the single harting cable I1 if self.ring_id == None: - ring_history_file = os.path.join(os.path.dirname(__file__), "ring_history.txt") - + ring_history_file = os.path.join( + os.path.dirname(__file__), "ring_history.txt" + ) + if os.path.exists(ring_history_file): with open(ring_history_file, "r") as f: lines = f.readlines() @@ -201,8 +227,8 @@ def get_ring_id(self): self.ring_id = lines[-1].strip() else: self.ring_id = None - - #TODO: move this to the right place so it works also when ring ID LineEdit is updated + + # TODO: move this to the right place so it works also when ring ID LineEdit is updated if self.ring_id != None: self.modules_list_tab.ring_id_LE.setText(self.ring_id) logger.info(f"Loaded ring ID from history: {self.ring_id}") @@ -214,7 +240,10 @@ def get_ring_id(self): elif self.ring_id.startswith("L3_"): self.number_of_modules = 36 self.modules_list_tab.populate_from_config( - self.caen_tab, self.mounted_modules, self.number_of_modules, self.thermal_camera_tab + self.caen_tab, + self.mounted_modules, + self.number_of_modules, + self.thermal_camera_tab, ) return self.ring_id @@ -232,9 +261,13 @@ def setup_ring_id(self): elif self.ring_id.startswith("L3_"): self.number_of_modules = 36 else: - self.message_box.setText("Invalid ring ID format. Must start with L1_, L2_, or L3_.") + self.message_box.setText( + "Invalid ring ID format. Must start with L1_, L2_, or L3_." + ) self.message_box.exec_() - self.modules_list_tab.populate_from_config(self.caen_tab, self.mounted_modules, self.number_of_modules) + self.modules_list_tab.populate_from_config( + self.caen_tab, self.mounted_modules, self.number_of_modules + ) def save_ring_id(self): ring_history_file = os.path.join(os.path.dirname(__file__), "ring_history.txt") @@ -243,22 +276,30 @@ def save_ring_id(self): logger.info(f"Ring ID {self.ring_id} saved successfully.") def get_mounted_modules(self): - self.mounted_modules = get_modules_on_ring(self.ring_id, db_url=self.module_db.db_url) + self.mounted_modules = get_modules_on_ring( + self.ring_id, db_url=self.module_db.db_url + ) for module_name in self.mounted_modules: - self.mounted_modules[module_name].update(get_module_endpoints(module_name, db_url=self.module_db.db_url)) self.mounted_modules[module_name].update( - {"speed": get_module_speed(module_name, db_url=self.module_db.db_url)} + get_module_endpoints(module_name, db_url=self.module_db.db_url) ) self.mounted_modules[module_name].update( - {"fuseId": get_module_fuse_id(module_name, db_url=self.module_db.db_url)} + {"speed": get_module_speed(module_name, db_url=self.module_db.db_url)} ) self.mounted_modules[module_name].update( { - "temperature_offsets": get_module(module_name, db_url=self.module_db.db_url).get( - "temperature_offsets", {} + "fuseId": get_module_fuse_id( + module_name, db_url=self.module_db.db_url ) } ) + self.mounted_modules[module_name].update( + { + "temperature_offsets": get_module( + module_name, db_url=self.module_db.db_url + ).get("temperature_offsets", {}) + } + ) logger.debug(f"Mounted modules for ring {self.ring_id}: {self.mounted_modules}") return self.mounted_modules @@ -266,11 +307,21 @@ def load_settings_to_ui(self): # Fill settings UI with current values self.settings_tab.brokerLineEdit.setText(self.system.settings["mqtt"]["broker"]) self.settings_tab.portSpinBox.setValue(self.system.settings["mqtt"]["port"]) - self.settings_tab.martaTopicLineEdit.setText(self.system.settings["MARTA"]["mqtt_topic"]) - self.settings_tab.coldroomTopicLineEdit.setText(self.system.settings["Coldroom"]["mqtt_topic"]) - self.settings_tab.co2SensorTopicLineEdit.setText(self.system.settings["Coldroom"]["co2_sensor_topic"]) - self.settings_tab.thermalCameraTopicLineEdit.setText(self.system.settings["ThermalCamera"]["mqtt_topic"]) - self.settings_tab.cleanroomTopicLineEdit.setText(self.system.settings["Cleanroom"]["mqtt_topic"]) + self.settings_tab.martaTopicLineEdit.setText( + self.system.settings["MARTA"]["mqtt_topic"] + ) + self.settings_tab.coldroomTopicLineEdit.setText( + self.system.settings["Coldroom"]["mqtt_topic"] + ) + self.settings_tab.co2SensorTopicLineEdit.setText( + self.system.settings["Coldroom"]["co2_sensor_topic"] + ) + self.settings_tab.thermalCameraTopicLineEdit.setText( + self.system.settings["ThermalCamera"]["mqtt_topic"] + ) + self.settings_tab.cleanroomTopicLineEdit.setText( + self.system.settings["Cleanroom"]["mqtt_topic"] + ) def connect_signals(self): # Module list @@ -287,66 +338,88 @@ def configure_line_edit(field_name, placeholder): le.setStyleSheet("QLineEdit { color: grey; }") # Connect signals - le.textChanged.connect(lambda: le.setStyleSheet("QLineEdit { color: black; }")) - le.editingFinished.connect(lambda: le.setPlaceholderText(placeholder) if le.text() == "" else None) + le.textChanged.connect( + lambda: le.setStyleSheet("QLineEdit { color: black; }") + ) + le.editingFinished.connect( + lambda: ( + le.setPlaceholderText(placeholder) if le.text() == "" else None + ) + ) # Light controls - self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_light_on_PB").clicked.connect( - self.coldroom_light_on - ) - self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_light_off_PB").clicked.connect( - self.coldroom_light_off - ) + self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_light_on_PB" + ).clicked.connect(self.coldroom_light_on) + self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_light_off_PB" + ).clicked.connect(self.coldroom_light_off) # Dry air controls - self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_dry_air_on_PB").clicked.connect( - self.coldroom_dry_air_on - ) - self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_dry_air_off_PB").clicked.connect( - self.coldroom_dry_air_off - ) + self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_dry_air_on_PB" + ).clicked.connect(self.coldroom_dry_air_on) + self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_dry_air_off_PB" + ).clicked.connect(self.coldroom_dry_air_off) # Dry air bypass controls - self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_dry_air_bypass_on_PB").clicked.connect( - self.coldroom_dry_air_bypass_on - ) - self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_dry_air_bypass_off_PB").clicked.connect( - self.coldroom_dry_air_bypass_off - ) + self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_dry_air_bypass_on_PB" + ).clicked.connect(self.coldroom_dry_air_bypass_on) + self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_dry_air_bypass_off_PB" + ).clicked.connect(self.coldroom_dry_air_bypass_off) # Door controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_door_toggle_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_door_toggle_PB" + ) if button: button.clicked.connect(self.toggle_coldroom_door) # Temperature controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_temp_ctrl_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_temp_ctrl_PB" + ) if button: button.clicked.connect(self.toggle_coldroom_temp_control) - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_run_start") - if button: + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_run_start" + ) + if button and self.system._martacoldroom: button.clicked.connect(self.system._martacoldroom.run) - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_run_stop") - if button: + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_run_stop" + ) + if button and self.system._martacoldroom: button.clicked.connect(self.system._martacoldroom.stop) configure_line_edit("coldroom_temp_LE", "-30°C to 30°C") configure_line_edit("coldroom_humidity_LE", "0% to 50%") # Temperature setpoint label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "coldroom_temp_set_point_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "coldroom_temp_set_point_label" + ) if label: logger.debug("Connected temperature set point label") # Humidity setpoint label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "coldroom_humidity_set_point_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "coldroom_humidity_set_point_label" + ) if label: logger.debug("Connected humidity set point label") # Temperature control LED - ctrl_temp_led = self.marta_coldroom_tab.findChild(QtWidgets.QFrame, "ctrl_temp_LED") + ctrl_temp_led = self.marta_coldroom_tab.findChild( + QtWidgets.QFrame, "ctrl_temp_LED" + ) if ctrl_temp_led: logger.debug("Connected temperature control LED") # Humidity control LED - ctrl_humidity_led = self.marta_coldroom_tab.findChild(QtWidgets.QFrame, "ctrl_humidity_LED") + ctrl_humidity_led = self.marta_coldroom_tab.findChild( + QtWidgets.QFrame, "ctrl_humidity_LED" + ) if ctrl_humidity_led: logger.debug("Connected humidity control LED") # Light control LED @@ -358,136 +431,206 @@ def configure_line_edit(field_name, placeholder): if dry_air_led: logger.debug("Connected dry air control LED") # Safe to open LED - safe_to_open_led = self.marta_coldroom_tab.findChild(QtWidgets.QFrame, "safe_to_open_LED") + safe_to_open_led = self.marta_coldroom_tab.findChild( + QtWidgets.QFrame, "safe_to_open_LED" + ) if safe_to_open_led: logger.debug("Connected safe to open LED") # Door state label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "coldroom_door_state_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "coldroom_door_state_label" + ) if label: logger.debug("Connected door state label") + # Soft interlock LED + soft_interlock_led = self.marta_coldroom_tab.findChild( + QtWidgets.QFrame, "soft_interlock_LED" + ) + if soft_interlock_led: + logger.debug("Connected soft interlock LED") + # Soft interlock message label + soft_interlock_msg = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "soft_interlock_msg" + ) + if soft_interlock_msg: + logger.debug("Connected soft interlock message label") # Temperature setpoint controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_temp_set_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_temp_set_PB" + ) if button: button.clicked.connect(self.set_coldroom_temperature) # Humidity controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_humidity_ctrl_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_humidity_ctrl_PB" + ) if button: button.clicked.connect(self.toggle_coldroom_humidity_control) # Humidity setpoint controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_humidity_set_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_humidity_set_PB" + ) if button: button.clicked.connect(self.set_coldroom_humidity) # Run controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_run_toggle_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_run_toggle_PB" + ) if button: button.clicked.connect(self.toggle_coldroom_run) # Reset alarms - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "coldroom_reset_alarms_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "coldroom_reset_alarms_PB" + ) if button: button.clicked.connect(self.reset_coldroom_alarms) # MARTA CO2 Plant controls # Temperature controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_temp_set_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_temp_set_PB" + ) if button: button.clicked.connect(self.set_marta_temperature) - configure_line_edit("marta_temp_LE", "-30°C to 18°C") # Placeholder for temperature input + configure_line_edit( + "marta_temp_LE", "-30°C to 18°C" + ) # Placeholder for temperature input # Speed controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_speed_set_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_speed_set_PB" + ) if button: button.clicked.connect(self.set_marta_speed) - configure_line_edit("marta_speed_LE", "5000 to 6000 RPM") # Placeholder for speed input + configure_line_edit( + "marta_speed_LE", "5000 to 6000 RPM" + ) # Placeholder for speed input # Flow controls - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_flow_set_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_flow_set_PB" + ) if button: button.clicked.connect(self.set_marta_flow) - configure_line_edit("marta_flow_LE", "0 to 5 L/min") # Placeholder for flow input + configure_line_edit( + "marta_flow_LE", "0 to 5 L/min" + ) # Placeholder for flow input # Supply temperature label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_temp_supply_value_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_temp_supply_value_label" + ) if label: logger.debug("Connected supply temperature label") # Return temperature label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_temp_return_value_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_temp_return_value_label" + ) if label: logger.debug("Connected return temperature label") # Supply pressure label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_pressure_supply_value_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_pressure_supply_value_label" + ) if label: logger.debug("Connected supply pressure label") # Return pressure label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_pressure_return_value_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_pressure_return_value_label" + ) if label: logger.debug("Connected return pressure label") # Speed label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_speed_value_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_speed_value_label" + ) if label: logger.debug("Connected speed label") # Temperature Set Point Label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_temp_set_point_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_temp_set_point_label" + ) if label: logger.debug("Connected temperature set point label") # Speed Set Point Label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_speed_set_point_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_speed_set_point_label" + ) if label: logger.debug("Connected speed set point label") # Flow Set Point Label - label = self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "marta_flow_set_point_label") + label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "marta_flow_set_point_label" + ) if label: logger.debug("Connected flow set point label") # Other MARTA controls # Start chiller button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_chiller_start_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_chiller_start_PB" + ) if button: button.clicked.connect(self.start_marta_chiller) # Start CO2 button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_co2_start_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_co2_start_PB" + ) if button: button.clicked.connect(self.start_marta_co2) # Stop CO2 button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_co2_stop_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_co2_stop_PB" + ) if button: button.clicked.connect(self.stop_marta_co2) # Stop chiller button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_chiller_stop_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_chiller_stop_PB" + ) if button: button.clicked.connect(self.stop_marta_chiller) # Clear alarms button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_alarms_clear_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_alarms_clear_PB" + ) if button: button.clicked.connect(self.clear_marta_alarms) # Reconnect button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_reconnect_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_reconnect_PB" + ) if button: button.clicked.connect(self.reconnect_marta) # Refresh button - button = self.marta_coldroom_tab.findChild(QtWidgets.QPushButton, "marta_refresh_PB") + button = self.marta_coldroom_tab.findChild( + QtWidgets.QPushButton, "marta_refresh_PB" + ) if button: button.clicked.connect(self.refresh_marta) # Flow active checkbox - checkbox = self.marta_coldroom_tab.findChild(QtWidgets.QCheckBox, "marta_flow_active_CB") + checkbox = self.marta_coldroom_tab.findChild( + QtWidgets.QCheckBox, "marta_flow_active_CB" + ) if checkbox: checkbox.clicked.connect(self.toggle_marta_flow_active) @@ -501,28 +644,40 @@ def configure_line_edit(field_name, placeholder): logger.debug(f"Found checkbox: {checkbox.objectName()}") # Update validators to match placeholder ranges - temp_lineedit_coldroom = self.marta_coldroom_tab.findChild(QtWidgets.QLineEdit, "coldroom_temp_LE") + temp_lineedit_coldroom = self.marta_coldroom_tab.findChild( + QtWidgets.QLineEdit, "coldroom_temp_LE" + ) if temp_lineedit_coldroom: - temp_lineedit_coldroom.setValidator(QtGui.QDoubleValidator(-30, 30, 2)) # Matches placeholder + temp_lineedit_coldroom.setValidator( + QtGui.QDoubleValidator(-30, 30, 2) + ) # Matches placeholder - humid_lineedit_coldroom = self.marta_coldroom_tab.findChild(QtWidgets.QLineEdit, "coldroom_humidity_LE") + humid_lineedit_coldroom = self.marta_coldroom_tab.findChild( + QtWidgets.QLineEdit, "coldroom_humidity_LE" + ) if humid_lineedit_coldroom: humid_lineedit_coldroom.setValidator(QtGui.QDoubleValidator(0, 50, 2)) # Add input validation for numeric fields - temp_lineedit = self.marta_coldroom_tab.findChild(QtWidgets.QLineEdit, "marta_temp_LE") + temp_lineedit = self.marta_coldroom_tab.findChild( + QtWidgets.QLineEdit, "marta_temp_LE" + ) if temp_lineedit: validator = QtGui.QDoubleValidator(-30, 18, 2) # min, max, decimals temp_lineedit.setValidator(validator) # Add input validation for numeric fields - speed_lineedit = self.marta_coldroom_tab.findChild(QtWidgets.QLineEdit, "marta_speed_LE") + speed_lineedit = self.marta_coldroom_tab.findChild( + QtWidgets.QLineEdit, "marta_speed_LE" + ) if speed_lineedit: validator = QtGui.QDoubleValidator(5000, 6000, 0) # min, max, decimals speed_lineedit.setValidator(validator) # Add input validation for numeric fields - flow_lineedit = self.marta_coldroom_tab.findChild(QtWidgets.QLineEdit, "marta_flow_LE") + flow_lineedit = self.marta_coldroom_tab.findChild( + QtWidgets.QLineEdit, "marta_flow_LE" + ) if flow_lineedit: validator = QtGui.QDoubleValidator(0, 5, 2) # min, max, decimals flow_lineedit.setValidator(validator) @@ -551,6 +706,48 @@ def connect_mqtt(self): self.statusBar().showMessage(error_msg) logger.error(error_msg) + def soft_interlock_check(self): + logger.info("Called soft Interlock") + used_caen_channels = self.modules_list_tab.get_used_channels() + logger.info(f"Used CAEN channels for safety checks: {used_caen_channels}") + alarm_publish = ( + (lambda msg: self.system._martacoldroom._client.publish("/alarm", msg)) + if self.system._martacoldroom + else None + ) + logger.info(f"Alarm publish function: {alarm_publish}") + is_safe, msg = soft_interlock_loop( + self.system.status, + self.caen_tab.last_response, + used_caen_channels, + self.caen_tab, + publish_alarm=alarm_publish, + ) + logger.info(f"Soft interlock result: is_safe={is_safe}, msg={msg}") + + soft_interlock_led = self.marta_coldroom_tab.findChild( + QtWidgets.QFrame, "soft_interlock_LED" + ) + if soft_interlock_led: + status_color = "green" if is_safe else "red" + soft_interlock_led.setStyleSheet("background-color: white;") + QTimer.singleShot( + 500, + lambda led=soft_interlock_led, color=status_color: led.setStyleSheet( + f"background-color: {color};" + ), + ) + logger.info(f"Updated soft interlock LED: {status_color} with pulse") + + soft_interlock_msg_label = self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "soft_interlock_msg" + ) + if soft_interlock_msg_label: + soft_interlock_msg_label.setText(msg) + logger.info(f"Updated soft interlock message: {msg}") + + logger.info("Completed soft interlock loop") + def update_ui(self): """Update UI with current system status""" try: @@ -573,43 +770,79 @@ def update_ui(self): logger.debug(f"Updating Cleanroom UI with status: {cleanroom}") # Temperature - label = central.findChild(QtWidgets.QLabel, "cleanroom_temp_value_label") - if label and "temperature" in cleanroom and cleanroom["temperature"] is not None: + label = central.findChild( + QtWidgets.QLabel, "cleanroom_temp_value_label" + ) + if ( + label + and "temperature" in cleanroom + and cleanroom["temperature"] is not None + ): temp_value = cleanroom["temperature"] label.setText(f"{temp_value:.1f}") logger.debug(f"Updated Cleanroom temperature: {temp_value}") # Humidity - label = central.findChild(QtWidgets.QLabel, "cleanroom_humidity_value_label") - if label and "humidity" in cleanroom and cleanroom["humidity"] is not None: + label = central.findChild( + QtWidgets.QLabel, "cleanroom_humidity_value_label" + ) + if ( + label + and "humidity" in cleanroom + and cleanroom["humidity"] is not None + ): humid_value = cleanroom["humidity"] label.setText(f"{humid_value:.1f}") logger.debug(f"Updated Cleanroom humidity: {humid_value}") # Dewpoint - label = central.findChild(QtWidgets.QLabel, "cleanroom_dewpoint_value_label") - if label and "dewpoint" in cleanroom and cleanroom["dewpoint"] is not None: + label = central.findChild( + QtWidgets.QLabel, "cleanroom_dewpoint_value_label" + ) + if ( + label + and "dewpoint" in cleanroom + and cleanroom["dewpoint"] is not None + ): dewpoint = cleanroom["dewpoint"] label.setText(f"{dewpoint:.1f}") logger.debug(f"Updated Cleanroom dewpoint: {dewpoint}") # Pressure - label = central.findChild(QtWidgets.QLabel, "cleanroom_pressure_value_label") - if label and "pressure" in cleanroom and cleanroom["pressure"] is not None: + label = central.findChild( + QtWidgets.QLabel, "cleanroom_pressure_value_label" + ) + if ( + label + and "pressure" in cleanroom + and cleanroom["pressure"] is not None + ): pressure = cleanroom["pressure"] label.setText(f"{pressure:.1f}") logger.debug(f"Updated Cleanroom pressure: {pressure}") - label = central.findChild(QtWidgets.QLabel, "cleanroom_last_update_value_label") - if label and "last_update" in cleanroom and cleanroom["last_update"] is not None: + label = central.findChild( + QtWidgets.QLabel, "cleanroom_last_update_value_label" + ) + if ( + label + and "last_update" in cleanroom + and cleanroom["last_update"] is not None + ): last_update = cleanroom["last_update"] label.setText(f"{last_update}") logger.debug(f"Updated Cleanroom last update: {last_update}") - label = central.findChild(QtWidgets.QLabel, "cleanroom_last_update_value_label_2") + label = central.findChild( + QtWidgets.QLabel, "cleanroom_last_update_value_label_2" + ) if label: - delta_t_update = self.system._martacoldroom._cleanroom_last_update_elapsed_time - delta_t_update = time.strftime("%H:%M:%S", time.gmtime(delta_t_update)) + delta_t_update = ( + self.system._martacoldroom._cleanroom_last_update_elapsed_time + ) + delta_t_update = time.strftime( + "%H:%M:%S", time.gmtime(delta_t_update) + ) label.setText(delta_t_update) logger.debug(f"Updated Cleanroom delta t update: {delta_t_update}") @@ -621,15 +854,21 @@ def update_ui(self): coldroom = self.system.status["coldroom"] logger.debug(f"Updating Coldroom UI with status: {coldroom}") - temp_control_active = coldroom.get("ch_temperature", {}).get("status", False) - hum_control_active = coldroom.get("ch_humidity", {}).get("status", False) + temp_control_active = coldroom.get("ch_temperature", {}).get( + "status", False + ) + hum_control_active = coldroom.get("ch_humidity", {}).get( + "status", False + ) # =========================================================================================== COLDROOM TEMPERATURE PROCESS =========================================================================================== # Temperature Current Value if "ch_temperature" in coldroom: # Current temperature - label = central.findChild(QtWidgets.QLabel, "coldroom_temp_value_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_temp_value_label" + ) if label: temp_value = coldroom["ch_temperature"].get("value", "?") label.setText(f"{temp_value:.1f}") @@ -639,7 +878,9 @@ def update_ui(self): if "ch_temperature" in coldroom: if "status" in coldroom["ch_temperature"]: # Update Temperature control LED - ctrl_temp_led = central.findChild(QtWidgets.QFrame, "ctrl_temp_LED") + ctrl_temp_led = central.findChild( + QtWidgets.QFrame, "ctrl_temp_LED" + ) if ctrl_temp_led: ctrl_temp_led.setStyleSheet( "background-color: green;" @@ -671,7 +912,9 @@ def update_ui(self): # Temperature setpoint label if "ch_temperature" in coldroom: if "setpoint" in coldroom["ch_temperature"]: - label = central.findChild(QtWidgets.QLabel, "coldroom_temp_set_point_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_temp_set_point_label" + ) if label: temp_setpoint = coldroom["ch_temperature"]["setpoint"] label.setText(f"{temp_setpoint:.1f}") @@ -682,7 +925,9 @@ def update_ui(self): # Humidity Current Value if "ch_humidity" in coldroom: # Current humidity - label = central.findChild(QtWidgets.QLabel, "coldroom_humidity_value_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_humidity_value_label" + ) if label: humid_value = coldroom["ch_humidity"].get("value", "?") label.setText(f"{humid_value:.1f}") @@ -692,7 +937,9 @@ def update_ui(self): if "ch_humidity" in coldroom: if "status" in coldroom["ch_humidity"]: # Update Humidity control LED - ctrl_humidity_led = central.findChild(QtWidgets.QFrame, "ctrl_humidity_LED") + ctrl_humidity_led = central.findChild( + QtWidgets.QFrame, "ctrl_humidity_LED" + ) if ctrl_humidity_led: ctrl_humidity_led.setStyleSheet( "background-color: green;" @@ -704,7 +951,9 @@ def update_ui(self): ) # Humidity setpoint lineedit - lineedit = central.findChild(QtWidgets.QLineEdit, "coldroom_humidity_LE") + lineedit = central.findChild( + QtWidgets.QLineEdit, "coldroom_humidity_LE" + ) if lineedit: user_has_entered_value = bool(lineedit.text().strip()) # Case 1: User is actively typing - preserve their input @@ -724,7 +973,9 @@ def update_ui(self): # Humidity setpoint label if "ch_humidity" in coldroom: if "setpoint" in coldroom["ch_humidity"]: - label = central.findChild(QtWidgets.QLabel, "coldroom_humidity_set_point_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_humidity_set_point_label" + ) if label: humid_setpoint = coldroom["ch_humidity"]["setpoint"] label.setText(f"{humid_setpoint:.1f}") @@ -732,7 +983,9 @@ def update_ui(self): # =========================================================================================== COLDROOM DEWPOINT PROCESS =========================================================================================== # Dewpoint (from coldroom data) - label = central.findChild(QtWidgets.QLabel, "coldroom_dewpoint_value_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_dewpoint_value_label" + ) if label and "dew_point_c" in coldroom: dewpoint = coldroom["dew_point_c"] label.setText(f"{dewpoint:.1f}") @@ -745,32 +998,50 @@ def update_ui(self): light_led = central.findChild(QtWidgets.QFrame, "light_LED") if light_led: light_led.setStyleSheet( - "background-color: yellow;" if coldroom["light"] else "background-color: black;" + "background-color: yellow;" + if coldroom["light"] + else "background-color: black;" ) # LED is yellow when ON - logger.debug(f"Updated light LED: {'yellow' if coldroom['light'] else 'black'}") + logger.debug( + f"Updated light LED: {'yellow' if coldroom['light'] else 'black'}" + ) if bool(coldroom["light"]) is False: logger.debug("Light off, check if it is safe") - used_caen_channels = self.modules_list_tab.get_used_channels() + used_caen_channels = ( + self.modules_list_tab.get_used_channels() + ) is_safe = check_light_safe_to_turn_on( - self.system.status, self.caen_tab.last_response, used_caen_channels + self.system.status, + self.caen_tab.last_response, + used_caen_channels, + ) + button = central.findChild( + QtWidgets.QPushButton, "coldroom_light_on_PB" ) - button = central.findChild(QtWidgets.QPushButton, "coldroom_light_on_PB") if not is_safe: logger.debug("not safe") if button: button.setEnabled(False) - logger.debug("Disabled light button due to safety check") + logger.debug( + "Disabled light button due to safety check" + ) else: if button: button.setEnabled(True) - logger.debug("Enabled light button after safety check") + logger.debug( + "Enabled light button after safety check" + ) # Dry air bypass - dryair_bypass_led = central.findChild(QtWidgets.QFrame, "dryair_bypass_LED") + dryair_bypass_led = central.findChild( + QtWidgets.QFrame, "dryair_bypass_LED" + ) coldroom_air = self.system.status.get("coldroomair", {}) if dryair_bypass_led: if "air_bypass_status" in coldroom_air: dryair_bypass_led.setStyleSheet( - "background-color: green;" if coldroom_air["air_bypass_status"] else "background-color: red;" + "background-color: green;" + if coldroom_air["air_bypass_status"] + else "background-color: red;" ) logger.debug( f"Updated dry air bypass LED: {'green' if coldroom_air['air_bypass_status'] else 'red'}" @@ -783,18 +1054,24 @@ def update_ui(self): # Door status and LED if "CmdDoorUnlock_Reff" in coldroom: door_status = "OPEN" if coldroom["CmdDoorUnlock_Reff"] else "CLOSED" - label = central.findChild(QtWidgets.QLabel, "coldroom_door_state_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_door_state_label" + ) if label: label.setText(door_status) logger.debug(f"Updated door status: {door_status}") # =========================================================================================== COLDROOM RUN PROCESS =========================================================================================== # Update safe to open LED based on door safety check - safe_to_open_led = central.findChild(QtWidgets.QFrame, "safe_to_open_LED") + safe_to_open_led = central.findChild( + QtWidgets.QFrame, "safe_to_open_LED" + ) if safe_to_open_led: used_caen_channels = self.modules_list_tab.get_used_channels() is_safe, door_msg = check_door_safe_to_open( - self.system.status, self.caen_tab.last_response, used_caen_channels + self.system.status, + self.caen_tab.last_response, + used_caen_channels, ) # Check co2 values if "co2_sensor" in self.system.status: @@ -802,18 +1079,31 @@ def update_ui(self): if "CO2" in co2_data: if co2_data["CO2"] > 800: is_safe = False - door_msg += "CO2 levels safe: False" + # door_msg += "CO2 levels safe: False" + door_msg += "CO2 levels safe: NO" + door_msg += f" (Current CO2: {co2_data['CO2']:.1f} ppm > 800 ppm)" else: - door_msg += "CO2 levels safe: True" - safe_to_open_led.setStyleSheet("background-color: green;" if is_safe else "background-color: red;") - logger.debug(f"Updated safe to open LED: {'green' if is_safe else 'red'} (is_safe={is_safe})") + # door_msg += "CO2 levels safe: True" + door_msg += "CO2 levels safe: Yes" + safe_to_open_led.setStyleSheet( + "background-color: green;" + if is_safe + else "background-color: red;" + ) + logger.debug( + f"Updated safe to open LED: {'green' if is_safe else 'red'} (is_safe={is_safe})" + ) self.system._martacoldroom.publish_door_safety_status(is_safe) - self.marta_coldroom_tab.findChild(QtWidgets.QLabel, "door_safety_msg").setText(door_msg) + self.marta_coldroom_tab.findChild( + QtWidgets.QLabel, "door_safety_msg" + ).setText(door_msg) # =========================================================================================== COLDROOM RUN PROCESS =========================================================================================== # Run status if "running" in coldroom: - label = central.findChild(QtWidgets.QLabel, "coldroom_run_state_label") + label = central.findChild( + QtWidgets.QLabel, "coldroom_run_state_label" + ) if label: run_text = "Running" if coldroom["running"] else "Stopped" label.setText(run_text) @@ -826,9 +1116,13 @@ def update_ui(self): dry_air_led = central.findChild(QtWidgets.QFrame, "dryair_LED") if dry_air_led: dry_air_led.setStyleSheet( - "background-color: green;" if coldroom["dry_air_status"] else "background-color: red;" + "background-color: green;" + if coldroom["dry_air_status"] + else "background-color: red;" + ) + logger.debug( + f"Updated dry air LED: {'green' if coldroom['dry_air_status'] else 'red'}" ) - logger.debug(f"Updated dry air LED: {'green' if coldroom['dry_air_status'] else 'red'}") # Update CO2 sensor data if "co2_sensor" in self.system.status: @@ -905,7 +1199,18 @@ def update_ui(self): # =========================================================================================== MARTA =========================================================================================== # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - self.modules_list_tab.marta_safe, self.modules_list_tab.marta_log_msg = check_marta_safe(self.system.status) + self.modules_list_tab.marta_safe, self.modules_list_tab.marta_log_msg = ( + check_marta_safe(self.system.status) + ) + + # logger.info("Called soft Interlock") + # used_caen_channels = self.modules_list_tab.get_used_channels() + # logger.info(f"Used CAEN channels for safety checks: {used_caen_channels}") + # alarm_publish = (lambda msg: self.system._martacoldroom._client.publish("/alarm", msg)) if self.system._martacoldroom else None + # logger.info(f"Alarm publish function: {alarm_publish}") + # soft_interlock_loop(self.system.status, self.caen_tab.last_response, used_caen_channels, self.caen_tab, publish_alarm=alarm_publish) + # logger.info("Completed soft interlock loop") + # Update MARTA CO2 Plant values if "marta" in self.system.status: marta = self.system.status["marta"] @@ -914,38 +1219,50 @@ def update_ui(self): # =========================================================================================== MARTA FSM STATE PROCESS =========================================================================================== # Update FSM state if "fsm_state" in marta: - state_label = central.findChild(QtWidgets.QLabel, "marta_state_label") + state_label = central.findChild( + QtWidgets.QLabel, "marta_state_label" + ) if state_label: state_label.setText(marta["fsm_state"]) logger.debug(f"Updated MARTA state: {marta['fsm_state']}") # Update state-specific UI elements if marta["fsm_state"] == "ALARM": # Highlight alarm state - alarm_frame = central.findChild(QtWidgets.QFrame, "marta_alarm_frame") + alarm_frame = central.findChild( + QtWidgets.QFrame, "marta_alarm_frame" + ) if alarm_frame: alarm_frame.setStyleSheet("background-color: red;") # Show alarm message - alarm_msg = central.findChild(QtWidgets.QLabel, "marta_alarm_msg_label") + alarm_msg = central.findChild( + QtWidgets.QLabel, "marta_alarm_msg_label" + ) if alarm_msg and "alarm_message" in marta: alarm_msg.setText(marta["alarm_message"]) else: # Clear alarm highlighting - alarm_frame = central.findChild(QtWidgets.QFrame, "marta_alarm_frame") + alarm_frame = central.findChild( + QtWidgets.QFrame, "marta_alarm_frame" + ) if alarm_frame: alarm_frame.setStyleSheet("") # =========================================================================================== MARTA TEMPERATURE PROCESS =========================================================================================== # Temperature from TT05_CO2 (Supply) if "TT05_CO2" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_temp_supply_value_label") + label = central.findChild( + QtWidgets.QLabel, "marta_temp_supply_value_label" + ) if label: temp_value = marta["TT05_CO2"] label.setText(f"{temp_value:.1f}") logger.debug(f"Updated MARTA supply temperature: {temp_value}") # Temperature from TT06_CO2 (Return) if "TT06_CO2" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_temp_return_value_label") + label = central.findChild( + QtWidgets.QLabel, "marta_temp_return_value_label" + ) if label: temp_value = marta["TT06_CO2"] label.setText(f"{temp_value:.1f}") @@ -963,7 +1280,9 @@ def update_ui(self): marta_temp_lE_active_flag = True temp_setpoint = lineedit.text() lineedit.setText(str(temp_setpoint)) - logger.debug(f"Updated MARTA temperature setpoint: {temp_setpoint}") + logger.debug( + f"Updated MARTA temperature setpoint: {temp_setpoint}" + ) else: marta_temp_lE_active_flag = False lineedit.clear() @@ -971,16 +1290,22 @@ def update_ui(self): # Temperature setpoint label if "temperature_setpoint" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_temp_set_point_label") + label = central.findChild( + QtWidgets.QLabel, "marta_temp_set_point_label" + ) if label: temp_setpoint = marta["temperature_setpoint"] label.setText(f"{temp_setpoint:.1f}") - logger.debug(f"Updated MARTA temperature setpoint: {temp_setpoint}") + logger.debug( + f"Updated MARTA temperature setpoint: {temp_setpoint}" + ) # =========================================================================================== MARTA PRESSURE PROCESS =========================================================================================== # Pressure from PT05_CO2 (Supply) if "PT05_CO2" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_pressure_supply_value_label") + label = central.findChild( + QtWidgets.QLabel, "marta_pressure_supply_value_label" + ) if label: pressure_value = marta["PT05_CO2"] label.setText(f"{pressure_value:.3f}") @@ -988,7 +1313,9 @@ def update_ui(self): # Pressure from PT06_CO2 (Return) if "PT06_CO2" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_pressure_return_value_label") + label = central.findChild( + QtWidgets.QLabel, "marta_pressure_return_value_label" + ) if label: pressure_value = marta["PT06_CO2"] label.setText(f"{pressure_value:.3f}") @@ -997,7 +1324,9 @@ def update_ui(self): # =========================================================================================== MARTA SPEED PROCESS =========================================================================================== # Speed if "LP_speed" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_speed_value_label") + label = central.findChild( + QtWidgets.QLabel, "marta_speed_value_label" + ) if label: speed_value = marta["LP_speed"] label.setText(f"{speed_value:.1f}") @@ -1024,7 +1353,9 @@ def update_ui(self): # Speed setpoint label if "speed_setpoint" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_speed_set_point_label") + label = central.findChild( + QtWidgets.QLabel, "marta_speed_set_point_label" + ) if label: speed_setpoint = marta["speed_setpoint"] label.setText(f"{speed_setpoint:.1f}") @@ -1035,12 +1366,18 @@ def update_ui(self): # Flow control LED if "set_flow_active" in marta: # Update Flow control LED - ctrl_flow_led = central.findChild(QtWidgets.QFrame, "marta_flow_flag_LED") + ctrl_flow_led = central.findChild( + QtWidgets.QFrame, "marta_flow_flag_LED" + ) if ctrl_flow_led: ctrl_flow_led.setStyleSheet( - "background-color: green;" if marta["set_flow_active"] else "background-color: black;" + "background-color: green;" + if marta["set_flow_active"] + else "background-color: black;" ) # LED is green when ON - logger.debug(f"Updated flow control LED: {'green' if marta['set_flow_active'] else 'black'}") + logger.debug( + f"Updated flow control LED: {'green' if marta['set_flow_active'] else 'black'}" + ) # Flow setpoint lineedit if "set_flow_setpoint" in marta: @@ -1061,7 +1398,9 @@ def update_ui(self): # Flow setpoint (affects speed) if "flow_setpoint" in marta: - label = central.findChild(QtWidgets.QLabel, "marta_flow_set_point_label") + label = central.findChild( + QtWidgets.QLabel, "marta_flow_set_point_label" + ) if label: flow_setpoint = marta["flow_setpoint"] label.setText(f"{flow_setpoint:.1f}") @@ -1083,7 +1422,9 @@ def set_coldroom_temperature(self): central = self.marta_coldroom_tab # Check if temperature control is enabled coldroom = self.system.status.get("coldroom", {}) - if "ch_temperature" not in coldroom or not coldroom.get("ch_temperature", {}).get("status", False): + if "ch_temperature" not in coldroom or not coldroom.get( + "ch_temperature", {} + ).get("status", False): msg = "Temperature control is not enabled. Please enable temperature control first." self.statusBar().showMessage(msg) logger.warning(msg) @@ -1123,8 +1464,12 @@ def set_coldroom_humidity(self): central = self.marta_coldroom_tab # Check if humidity control is enabled coldroom = self.system.status.get("coldroom", {}) - if "ch_humidity" not in coldroom or not coldroom.get("ch_humidity", {}).get("status", False): - msg = "Humidity control is not enabled. Please enable humidity control first." + if "ch_humidity" not in coldroom or not coldroom.get("ch_humidity", {}).get( + "status", False + ): + msg = ( + "Humidity control is not enabled. Please enable humidity control first." + ) self.statusBar().showMessage(msg) logger.warning(msg) return @@ -1142,11 +1487,15 @@ def set_coldroom_humidity(self): if 0 <= value <= 50: # Humidity range from UI validator if self.system._martacoldroom: self.system._martacoldroom.set_humidity(value) - msg = f"Set coldroom humidity to {value}%" - self.statusBar().showMessage(msg) - logger.info(msg) + msg = f"Set coldroom humidity to {value}%" + self.statusBar().showMessage(msg) + logger.info(msg) + else: + msg = "MARTA Cold Room client not initialized" + self.statusBar().showMessage(msg) + logger.error(msg) else: - msg = "MARTA Cold Room client not initialized" + msg = "Humidity must be between 0% and 50%" self.statusBar().showMessage(msg) logger.error(msg) except ValueError: @@ -1257,7 +1606,9 @@ def toggle_coldroom_door(self): # This would normally require safety checks if self.system._martacoldroom: # For demo purposes, we're just toggling the state - self.system._martacoldroom.publish_cmd("door", self.system._martacoldroom._coldroom_client, str(new_state)) + self.system._martacoldroom.publish_cmd( + "door", self.system._martacoldroom._coldroom_client, str(new_state) + ) msg = f"Set door to {'OPEN' if new_state else 'CLOSED'}" self.statusBar().showMessage(msg) logger.info(msg) @@ -1312,8 +1663,12 @@ def update_marta_button_states(self, state): central = self.marta_coldroom_tab # Get all MARTA control buttons - start_chiller_btn = central.findChild(QtWidgets.QPushButton, "marta_chiller_start_PB") - stop_chiller_btn = central.findChild(QtWidgets.QPushButton, "marta_stop_chiller_PB") + start_chiller_btn = central.findChild( + QtWidgets.QPushButton, "marta_chiller_start_PB" + ) + stop_chiller_btn = central.findChild( + QtWidgets.QPushButton, "marta_stop_chiller_PB" + ) start_co2_btn = central.findChild(QtWidgets.QPushButton, "marta_co2_start_PB") stop_co2_btn = central.findChild(QtWidgets.QPushButton, "marta_co2_stop_PB") @@ -1582,13 +1937,25 @@ def toggle_marta_flow_active(self): def save_settings(self): try: # Update settings object - self.system.settings["mqtt"]["broker"] = self.settings_tab.brokerLineEdit.text() + self.system.settings["mqtt"][ + "broker" + ] = self.settings_tab.brokerLineEdit.text() self.system.settings["mqtt"]["port"] = self.settings_tab.portSpinBox.value() - self.system.settings["MARTA"]["mqtt_topic"] = self.settings_tab.martaTopicLineEdit.text() - self.system.settings["Coldroom"]["mqtt_topic"] = self.settings_tab.coldroomTopicLineEdit.text() - self.system.settings["Coldroom"]["co2_sensor_topic"] = self.settings_tab.co2SensorTopicLineEdit.text() - self.system.settings["ThermalCamera"]["mqtt_topic"] = self.settings_tab.thermalCameraTopicLineEdit.text() - self.system.settings["Cleanroom"]["mqtt_topic"] = self.settings_tab.cleanroomTopicLineEdit.text() + self.system.settings["MARTA"][ + "mqtt_topic" + ] = self.settings_tab.martaTopicLineEdit.text() + self.system.settings["Coldroom"][ + "mqtt_topic" + ] = self.settings_tab.coldroomTopicLineEdit.text() + self.system.settings["Coldroom"][ + "co2_sensor_topic" + ] = self.settings_tab.co2SensorTopicLineEdit.text() + self.system.settings["ThermalCamera"][ + "mqtt_topic" + ] = self.settings_tab.thermalCameraTopicLineEdit.text() + self.system.settings["Cleanroom"][ + "mqtt_topic" + ] = self.settings_tab.cleanroomTopicLineEdit.text() # Write to file with open("settings.yaml", "w") as f: @@ -1611,12 +1978,17 @@ def save_settings(self): if __name__ == "__main__": args = argparse.ArgumentParser(description="Cold Room Control Application") args.add_argument( - "--loglevel", "-log", default="WARNING", help="Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)" + "--loglevel", + "-log", + default="WARNING", + help="Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)", ) parsed_args = args.parse_args() logger = logging.getLogger("integration") log_level = getattr(logging, parsed_args.loglevel.upper(), logging.WARNING) - logging.basicConfig(level=log_level, format="%(asctime)s - %(levelname)s - %(message)s") + logging.basicConfig( + level=log_level, format="%(asctime)s - %(levelname)s - %(message)s" + ) app = QtWidgets.QApplication(sys.argv) window = MainApp() diff --git a/coldroom/marta_coldroom.py b/coldroom/marta_coldroom.py index 006372b..6071678 100644 --- a/coldroom/marta_coldroom.py +++ b/coldroom/marta_coldroom.py @@ -157,7 +157,9 @@ def on_message(self, client, userdata, msg): if self._system.has_valid_status(): self._system.safety_flags["door_locked"] = not check_dew_point(self._system.status) self._system.safety_flags["sleep"] = check_door_status(self._system.status) - self._system.safety_flags["door_safe"] = check_door_safe_to_open(self._system.status) + self._system.safety_flags["door_safe"] = check_door_safe_to_open( + self._system.status, self._system.status.get("caen", {}), {"HV": []} + ) logger.debug(f"Safety flags updated: {self._system.safety_flags}") def publish_cmd(self, command, target, payload): @@ -169,7 +171,7 @@ def publish_cmd(self, command, target, payload): target (str): Either 'marta' or 'coldroom' or 'cleanroom' payload: The command payload """ - logger.debug("Publishing", command, target, payload) + logger.debug(f"Publishing {command} {target} {payload}") if target == "marta": # Add MARTA topic topic = f"{self.TOPIC_BASE_MARTA}cmd/{command}" elif target == "cleanroom": # Add cleanroom topic diff --git a/coldroom/marta_coldroom.ui b/coldroom/marta_coldroom.ui index 35c8ea8..4eacd73 100644 --- a/coldroom/marta_coldroom.ui +++ b/coldroom/marta_coldroom.ui @@ -94,6 +94,79 @@ + + + + + + + + + 60 + 60 + + + + + 300 + 300 + + + + background-color: red; + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + 160 + 60 + + + + + 16 + + + + Soft Interlock + + + Qt::AlignCenter + + + + + + + + + Qt::Vertical + + + + + + + + 16 + + + + - + + + + + diff --git a/coldroom/safety.py b/coldroom/safety.py index 3a3f2e9..a97bcf5 100644 --- a/coldroom/safety.py +++ b/coldroom/safety.py @@ -1,4 +1,5 @@ import os +from tkinter import NO, YES, YES import yaml import logging @@ -25,7 +26,9 @@ def check_dew_point(system_status): # Get the three required temperature values marta_supply_temp = system_status.get("marta", {}).get("TT05_CO2") marta_return_temp = system_status.get("marta", {}).get("TT06_CO2") - coldroom_temp = system_status.get("coldroom", {}).get("ch_temperature", {}).get("value") + coldroom_temp = ( + system_status.get("coldroom", {}).get("ch_temperature", {}).get("value") + ) # coldroom = system_status.get("coldroom", {}) # logger.debug(f"Coldroom: {coldroom.get('CmdDoorUnlock_Reff')}") # door_status = system_status.get("coldroom", {}).get("CmdDoorUnlock_Reff") @@ -52,28 +55,38 @@ def check_dew_point(system_status): if "coldroom" not in system_status: logger.debug("Coldroom data not available") - return False # Conservative approach - if we can't check, assume it's unsafe + return ( + False # Conservative approach - if we can't check, assume it's unsafe + ) # Check if environment data exists - if "cleanroom" not in system_status or "dewpoint" not in system_status["cleanroom"]: + if ( + "cleanroom" not in system_status + or "dewpoint" not in system_status["cleanroom"] + ): return False # Conservative approach - reference_dew_point = system_status["cleanroom"]["dewpoint"] # External dewpoint + reference_dew_point = system_status["cleanroom"][ + "dewpoint" + ] # External dewpoint logger.debug(f"Reference dew point: {reference_dew_point}") delta = 1 # Allowable delta between dew point and temperature return min_temperature > reference_dew_point + delta except Exception as e: logger.debug(f"Error in check_dew_point: {str(e)}") - return False # Conservative approach + return False, "Error checking dew point safety" # Conservative approach def check_door_status(system_status): try: - if "coldroom" not in system_status or "door_status" not in system_status["coldroom"]: + if ( + "coldroom" not in system_status + or "door_status" not in system_status["coldroom"] + ): return False # Conservative approach return system_status["coldroom"]["CmdDoorUnlock_Reff"] == 1 # Door is open except Exception as e: logger.debug(f"Error in check_door_status: {str(e)}") - return False # Conservative approach + return False, "Error checking door status" # Conservative approach def check_light_status(system_status): @@ -83,7 +96,7 @@ def check_light_status(system_status): return system_status["coldroom"]["light"] == 1 # Light is on except Exception as e: logger.debug(f"Error in check_light_status: {str(e)}") - return False # Conservative approach + return False, "Error checking light status" # Conservative approach def check_any_hv_on(caen_ch_status, used_channels): @@ -98,7 +111,10 @@ def check_any_hv_on(caen_ch_status, used_channels): return hv_on except Exception as e: logger.debug(f"Error in check_any_hv_on: {str(e)}") - return True + return ( + True, + "Error checking high voltage status", + ) # Conservative approach - if we can't check, assume it's unsafe def check_cleanroom_expired(elapsed_time, threshold=600): @@ -114,21 +130,33 @@ def check_door_safe_to_open(system_status, caen_ch_status, used_channels): try: # Check if we have all necessary data if "coldroom" not in system_status: - return False # Conservative approach - if we can't check, assume it's unsafe + return ( + False # Conservative approach - if we can't check, assume it's unsafe + ) # 1. Check if dew point conditions are safe - clean_room_expired = check_cleanroom_expired(system_status["cleanroom"]["elapsed_time"]) + clean_room_expired = check_cleanroom_expired( + system_status["cleanroom"]["elapsed_time"] + ) if clean_room_expired: dew_point_safe = False log_msg += f"!!! Warning: Cleanroom data expired, not able to check dew point !!!\n" else: dew_point_safe = check_dew_point(system_status) - log_msg += f"Dew point safe: {dew_point_safe}\n" + if not dew_point_safe: + log_msg += f"!!! Warning: Dew point conditions are not safe for opening door !!!\n" + log_msg += f"Dew point safe: NO\n" + else: + log_msg += f"Dew point safe: YES\n" # 2. Check if high voltage is off hv_on = check_any_hv_on(caen_ch_status, used_channels) hv_safe = not hv_on - log_msg += f"High voltage safe: {hv_safe}\n" + if hv_safe == False: + log_msg += f"!!! Warning: High voltage is ON, not safe to open door !!!\n" + log_msg += f"High voltage safe: NO\n" + else: + log_msg += f"High voltage safe: YES\n" # 3. Check if light is off (light should be off when opening door) # light_off = not check_light_status(system_status) @@ -148,7 +176,7 @@ def check_door_safe_to_open(system_status, caen_ch_status, used_channels): except Exception as e: logger.debug(f"Error in check_door_safe_to_open: {str(e)}") - return False # Conservative approach - if we can't check, assume it's unsafe + return False, "Error checking door safety" def check_light_safe_to_turn_on(system_status, caen_ch_status, used_channels): @@ -160,7 +188,9 @@ def check_light_safe_to_turn_on(system_status, caen_ch_status, used_channels): try: # Check if we have all necessary data if "coldroom" not in system_status: - return False # Conservative approach - if we can't check, assume it's unsafe + return ( + False # Conservative approach - if we can't check, assume it's unsafe + ) # Check if high voltage is off hv_on = check_any_hv_on(caen_ch_status, used_channels) @@ -191,11 +221,238 @@ def check_marta_safe(system_status): # def check_marta_on_for_ot # def check_marta_on_for_it -#def switch_all_lv_off +# def switch_all_lv_off # def soft_interlock_loop: ### describe only highlevel # if condition then : action # if ! check_lv_safe_on and something_on : switch_all_lv_off() -#when performing an active safety action here we send a msg to the alarm topic "/alarm" \ No newline at end of file +# when performing an active safety action here we send a msg to the alarm topic "/alarm" + + +def Is_any_lv_on(caen_ch_status, used_channels): + """ + Check if any LV channel is on. + Returns True if any LV channel is on, False if all are off. + """ + try: + for channel in used_channels["LV"]: + if channel is None: + continue + ch_str = f"caen_{channel}_IsOn" + if bool(caen_ch_status.get(ch_str, False)): + logger.info(f"LV channel {channel} is ON") + return True + return False + except Exception as e: + logger.debug(f"Error in Is_any_lv_on: {str(e)}") + return True # Conservative - assume LV is on if we can't check + + +def Is_it_safe_to_on_lv(system_status, caen_ch_status, used_channels): + """ + Check if it's safe to turn on LV channels based on multiple safety conditions. + Returns True if it's safe to turn on LV channels, False otherwise. + """ + log_msg = "" + try: + # Check if we have all necessary data + if "coldroom" not in system_status: + return ( + False # Conservative approach - if we can't check, assume it's unsafe + ) + + # Check if MARTA is running + marta_safe, marta_msg = check_marta_safe(system_status) + log_msg += f"MARTA safe: {marta_safe} ({marta_msg})\n" + + # It's safe to turn on LV channels if MARTA is safe + is_safe = marta_safe + return is_safe, log_msg + + except Exception as e: + logger.debug(f"Error in Is_it_safe_to_on_lv: {str(e)}") + return False, "Error checking LV safety" + + +# def check_lv_safe_on(caen_ch_status, used_channels): +# """ +# Check if any LV channel is on. +# Returns True if any LV channel is on, False if all are off. +# """ +# try: +# for channel in used_channels["LV"]: +# if channel is None: +# continue +# ch_str = f"caen_{channel}_IsOn" +# if bool(caen_ch_status.get(ch_str, False)): +# return True +# return False +# except Exception as e: +# logger.debug(f"Error in check_lv_safe_on: {str(e)}") +# return True # Conservative - assume LV is on if we can't check + + +def check_marta_on_for_OT(system_status): + """ + Check if MARTA is running for OT (Outer Tracker). + Returns True if MARTA is connected and operational. + """ + try: + if "marta" not in system_status or "serviceroom" not in system_status: + return False + + logger.info( + f"Checking MARTA OT status: {system_status['marta'].get('fsm_state', 'N/A')}, OT valve: {system_status['serviceroom'].get('outer_valve', 'N/A')}" + ) + fsm_state = system_status["marta"].get("fsm_state", "") + OT_valve = system_status["serviceroom"].get("outer_valve", 0) + + if fsm_state not in ("DISCONNECTED", "NONE", ""): + if OT_valve == 1: # Outer valve is open, so OT is likely running + logger.info( + "MARTA FSM state is good and OT valve is open, treating as running" + ) + return True + else: + logger.debug( + "MARTA FSM state is good but OT valve is closed, treating as not running" + ) + return False + else: + return False + except Exception as e: + logger.debug(f"Error in check_marta_on_for_OT: {str(e)}") + return False + + +def check_marta_on_for_IT(system_status): + """ + Check if MARTA is running for IT (Inner Tracker). + Returns True if MARTA is connected and operational. + """ + try: + if "marta" not in system_status or "serviceroom" not in system_status: + return False + logger.info( + f"Checking MARTA IT status: {system_status['marta'].get('fsm_state', 'N/A')}, IT valve: {system_status['serviceroom'].get('inner_valve', 'N/A')}" + ) + fsm_state = system_status["marta"].get("fsm_state", "") + IT_valve = system_status["serviceroom"].get("inner_valve", 0) + if fsm_state not in ("DISCONNECTED", "NONE", ""): + if IT_valve == 1: # Inner valve is open, so IT is likely running + logger.info( + "MARTA FSM state is good and IT valve is open, treating as running" + ) + return True + else: + logger.debug( + "MARTA FSM state is good but IT valve is closed, treating as not running" + ) + return False + else: + return False + except Exception as e: + logger.debug(f"Error in check_marta_on_for_IT: {str(e)}") + return False + + +def switch_all_lv_off(caen, used_channels): + """ + Turn off all LV channels. + Returns True if all commands were sent successfully. + """ + try: + for channel in used_channels["LV"]: + logger.info(f"Attempting to turn off LV channel {channel}") + if channel is None: + continue + logger.warning(f"Safety interlock: turning off LV channel {channel}") + caen.off(channel) + return True + except Exception as e: + logger.error(f"Error in switch_all_lv_off: {str(e)}") + return False + + +def soft_interlock_loop( + system_status, caen_ch_status, used_channels, caen, publish_alarm=None +): + """ + Soft interlock loop - monitors safety conditions and takes protective action. + + High-level logic: + if LV is on AND MARTA is not running: + → switch all LV off + → publish alarm message to /alarm topic + + When performing an active safety action, a message is sent to the alarm topic "/alarm" + via the publish_alarm callback. + + Args: + system_status (dict): Full system status including MARTA, coldroom, etc. + caen_ch_status (dict): CAEN channel status with caen_{channel}_IsOn keys. + used_channels (dict): Active channel list {"LV": [...], "HV": [...]}. + caen: CAEN control object with on()/off() methods. + publish_alarm (callable, optional): Function to publish alarm messages. + Signature: publish_alarm(message_string) + + Returns: + tuple: (is_safe: bool, message: str) + """ + try: + lv_on = Is_any_lv_on(caen_ch_status, used_channels) + lv_safe_to_on = Is_it_safe_to_on_lv( + system_status, caen_ch_status, used_channels + ) + marta_ot = check_marta_on_for_OT(system_status) + marta_it = check_marta_on_for_IT(system_status) + + if lv_on: + lv_status = "ON" + else: + lv_status = "OFF" + + if marta_ot: + marta_ot_status = "RUNNING" + else: + marta_ot_status = "NOT RUNNING" + + if marta_it: + marta_it_status = "RUNNING" + else: + marta_it_status = "NOT RUNNING" + + log_msg = f"\nSoft interlock: LV_on={lv_status}, MARTA_OT={marta_ot_status}, MARTA_IT={marta_it_status}\n" + logger.info(log_msg) + + if lv_safe_to_on == False: + log_msg += ( + "\n!!! Warning: Conditions are not safe to turn on LV channels !!!\n" + ) + log_msg += f"\nLV safe to on: NO\n" + # switch_all_lv_off(caen, used_channels) + else: + log_msg += f"\nLV safe to on: YES\n" + + if lv_on and not marta_ot: + alarm_msg = ( + "SAFETY INTERLOCK: LV channels are on but MARTA OT is not running. " + "Turning off all LV channels to prevent module damage." + ) + logger.warning(alarm_msg) + # switch_all_lv_off(caen, used_channels) + if publish_alarm: + publish_alarm(alarm_msg) + return False, alarm_msg + else: + log_msg += "\nAll safety conditions met.\n" + logger.info(log_msg) + + return True, log_msg + + except Exception as e: + err_msg = f"Error in soft_interlock_loop: {str(e)}" + logger.error(err_msg) + return False, err_msg diff --git a/power_supply/power_supply.ui b/power_supply/power_supply.ui index 19fab89..82cb2b2 100644 --- a/power_supply/power_supply.ui +++ b/power_supply/power_supply.ui @@ -1,7 +1,7 @@ - MainWindow - + PowerSupply + 0 @@ -11,184 +11,171 @@ - MainWindow + Power Supply - - - - - - Power Supply Control - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Set Voltage Value - - - Qt::AlignCenter - - - - - - - ? - - - Qt::AlignCenter - - - - - - - Set Current - - - - - - - ON - - - - - - - ? - - - Qt::AlignCenter - - - - - - - ? - - - Qt::AlignCenter - - - - - - - Set Voltage - - - - - - - - - - Set Curent Value - - - Qt::AlignCenter - - - - - - - ? - - - Qt::AlignCenter - - - - - - - - 30 - 30 - - - - - 30 - 30 - - - - background-color: red; - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - - Voltage ( v ) - - - Qt::AlignCenter - - - - - - - OFF - - - - - - - Current Status ( i ) - - - Qt::AlignCenter - - - - - - - - - - - - 0 - 0 - 696 - 22 - - - - + + + + + Power Supply Control + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Set Voltage Value + + + Qt::AlignCenter + + + + + + + ? + + + Qt::AlignCenter + + + + + + + Set Current + + + + + + + ON + + + + + + + ? + + + Qt::AlignCenter + + + + + + + ? + + + Qt::AlignCenter + + + + + + + Set Voltage + + + + + + + + + + Set Curent Value + + + Qt::AlignCenter + + + + + + + ? + + + Qt::AlignCenter + + + + + + + + 30 + 30 + + + + + 30 + 30 + + + + background-color: red; + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + Voltage ( v ) + + + Qt::AlignCenter + + + + + + + OFF + + + + + + + Current Status ( i ) + + + Qt::AlignCenter + + + + + + + diff --git a/power_supply/power_supply_ctrl.py b/power_supply/power_supply_ctrl.py index 39dbab8..162b571 100644 --- a/power_supply/power_supply_ctrl.py +++ b/power_supply/power_supply_ctrl.py @@ -1,7 +1,8 @@ +import os import sys import logging import pyvisa as visa -from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox +from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox from PyQt5.uic import loadUi from PyQt5.QtCore import QTimer @@ -16,11 +17,12 @@ ) logger = logging.getLogger(__name__) -class PowerSupplyController(QMainWindow): - def __init__(self): - super(PowerSupplyController, self).__init__() +class PowerSupplyController(QWidget): + def __init__(self, parent=None): + super(PowerSupplyController, self).__init__(parent) # Load the UI file - loadUi('power_supply.ui', self) + ui_path = os.path.join(os.path.dirname(__file__), 'power_supply.ui') + loadUi(ui_path, self) # Initialize status dictionary self.status = { @@ -30,15 +32,21 @@ def __init__(self): 'set_voltage': 0.0, 'set_current': 0.0 } + self.connected = False + + # Set up timer early so closeEvent doesn't crash on connection failure + self.timer = QTimer(self) # Connect to the power supply self.connect_to_power_supply() - + if not self.connected: + self.timer.stop() + return + # Connect UI signals to slots self.setup_ui_connections() # Set up a timer to periodically update measurements - self.timer = QTimer(self) self.timer.timeout.connect(self.update_measurements) self.timer.start(1000) # Update every 1000ms (1 second) @@ -57,11 +65,11 @@ def connect_to_power_supply(self): ip_address = '192.168.0.16' self.rm = visa.ResourceManager() self.power_supply = self.rm.open_resource(f'TCPIP0::{ip_address}::INSTR') + self.connected = True print("Successfully connected to power supply") except Exception as e: logger.error(f"Failed connection: {e}") QMessageBox.critical(self, "Connection Error", f"Failed to connect to power supply: {e}") - self.close() def setup_ui_connections(self): """Connect UI signals to slots""" @@ -84,7 +92,8 @@ def update_ui(self): def update_measurements(self): """Periodically update the voltage and current measurements""" - # if self.status['power'] == 'ON': + if not self.connected: + return try: # Read actual voltage and current voltage = self.power_supply.query('MEAS:VOLT?\x00').strip() @@ -99,11 +108,11 @@ def update_measurements(self): self.log_status("Updated measurements") except Exception as e: logger.error(f"Error reading measurements: {e}") - QMessageBox.critical(self, "Measurement Error", f"Failed to read measurements: {e}") - print(f"Error reading measurements: {e}") def set_voltage(self): """Set the voltage value from the line edit""" + if not self.connected: + return try: voltage = float(self.powsup_voltage_LE.text()) self.power_supply.write(f'APPLY {voltage}\x00') @@ -119,6 +128,8 @@ def set_voltage(self): def set_current(self): """Set the current value from the line edit""" + if not self.connected: + return try: current = float(self.powsup_current_LE.text()) self.power_supply.write(f'SOUR:CURR {current}\x00') @@ -134,6 +145,8 @@ def set_current(self): def power_on(self): """Turn the power supply ON""" + if not self.connected: + return try: self.power_supply.write('OUTP:STAT ON\x00') self.status['power'] = 'ON' @@ -147,6 +160,8 @@ def power_on(self): def power_off(self): """Turn the power supply OFF""" + if not self.connected: + return try: self.power_supply.write('OUTP:STAT OFF\x00') self.status['power'] = 'OFF' @@ -163,7 +178,8 @@ def power_off(self): def closeEvent(self, event): """Ensure proper cleanup when closing the application""" try: - self.timer.stop() + if hasattr(self, 'timer'): + self.timer.stop() if hasattr(self, 'power_supply'): self.power_off() # Turn off power supply when closing self.power_supply.close() @@ -177,9 +193,14 @@ def closeEvent(self, event): def main(): app = QApplication(sys.argv) - window = PowerSupplyController() + window = QMainWindow() + controller = PowerSupplyController() + window.setCentralWidget(controller) + window.setWindowTitle("Power Supply") + window.resize(controller.width(), controller.height()) window.show() sys.exit(app.exec_()) if __name__ == '__main__': + from PyQt5.QtWidgets import QMainWindow main()