import inspect import os import platform import random import sys import time from datetime import timedelta from qtpy import QtCore, QtGui from qtpy.QtGui import QColor, QFont, QIcon from qtpy.QtCore import __version__ as QT_VERSION_STR from qtpy.QtCore import ( PYQT_VERSION_STR, QDate, QDateTime, QEventLoop, Qt, Signal, Slot) from qtpy.QtWidgets import ( QApplication, QDockWidget, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QMainWindow, QMessageBox, QProgressBar, QPushButton, QSizePolicy, QSpacerItem, QStackedWidget, QTabBar, QTabWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget) from apps4ops.bdbase.base import BaseWindow from apps4ops.bdbase.enumkind import MsgSeverity, UserMode, Facility from caqtwidgets.pvwidgets import CAQLabel, CAQLineEdit, CAQTableWidget, QHLine _pymodule = os.path.basename(__file__) def _line(): """Macro to return the current line number.688 The current line number within the file is used when reporting messages to the message logging window. Returns: int: Current line number. """ return inspect.currentframe().f_back.f_lineno class AppGui(QWidget): def __init__(self, parent): super(AppGui, self).__init__() self.parent = parent self.cafe = self.parent.cafe self.cyca = self.parent.cyca self.send_to_log_window = self.parent.send_to_log_window self.gui_frame = self.parent.gui_frame self.gui_header = self.parent.gui_header self.font_gui = self.parent.gui_frame.font_gui self.input_parameters = self.parent.input_parameters self.input_labels = self.parent.input_labels self.expert_parameters = self.parent.expert_parameters self.gui_frame.expert_parameters_group.setFixedWidth(260) self.gui_frame.operator_parameters_group.setFixedWidth(260) #self.gui_frame.operator_parameters_group.setFixedHeight(260) self.gui_frame.measurement_tab_wgt.setMinimumWidth(440) self.gui_frame.measurement_tab_wgt.setFixedHeight(280) #self.gui_frame.operator_wgt.setFixedHeight(240) #self.gui_frame.expert_wgt.setFixedHeight(240) self.gui_frame.central_tab_widget.tabBar().setTabText(0, "Control") self.gui_frame.central_tab_widget.tabBar().setTabText(1, "IOC") self.expert_labels = self.parent.expert_labels self.settings = self.parent.settings self.obj_to_upper = True if random.randint(1, 10) > 3 else False self.table_sol_dict = {} self.table_pwr_dict = {} self.offtime_dict = {} self.I_min = 1.0 sec_state_list = ['PG1-HUSH:STATE', 'PSH-HUSH:STATE', 'PG2-HUSH:STATE', 'PO2-HUSH:STATE', 'PPIF-HUSH:STATE', 'PG3-HUSH:STATE'] self.cafe.openPrepare() self.cafe.open(['PRO-HUSH:LASTPWR','PRO-HUSH:TOTPWR', 'PRO:LASTSAVE', 'PPO-HUSH:TOTSAVE']) self.cafe.open(sec_state_list) self.cafe.openNowAndWait(0.1) wgt = self.group_sector_qtabwidget() self.gui_frame.measurement_layout.addWidget( wgt, 0, 1, 6, 3, alignment=Qt.AlignTop) status_wgt = self.group_sector_status() self.gui_frame.measurement_layout.addWidget( status_wgt, 2, 0, 2, 1, alignment=Qt.AlignTop) # | Qt.AlignHCenter) qf = QFrame() qf.setFixedHeight(6) self.gui_frame.measurement_layout.addWidget( qf, 4, 0, 1, 1, alignment=Qt.AlignTop) self.sectorI_dict = {} self.sectorI_dict['G1'] = "Geschlossen" self.sectorI_dict['SH'] = "Geschlossen" self.sectorI_dict['G2'] = "Geschlossen" self.sectorI_dict['O2'] = "Geschlossen" self.sectorI_dict['PIF'] = "Geschlossen" self.sectorI_dict['G3'] = "Geschlossen" g3_line = ["SH", "PIF", "G3"] pif_line = ["SH", "PIF"] o2_line = ["SH", "O2"] g2_line = ["SH", "G2"] def enable_disable_beamline(sect, sector_line, value): self.sectorI_dict[sect] = value #print("sect/value", sect, value) for sector in sector_line: if "Geschlossen" not in self.sectorI_dict[sector]: #print("sector/value", sector, value) if self.table_sol_dict[ sector].standby_value_button.isEnabled(): self.table_sol_dict[ sector].standby_value_button.setEnabled(False) print("DISABLE", sector) else: #print("Geschlossen? sector/value", sector, value) if not self.table_sol_dict[ sector].standby_value_button.isEnabled(): self.table_sol_dict[ sector].standby_value_button.setEnabled(True) def receive_g3_update(value, status, alarm_severity): print("receive_g3_update", flush=True) enable_disable_beamline('G3', g3_line, value) def receive_o2_update(value, status, alarm_severity): enable_disable_beamline('O2', o2_line, value) def receive_g2_update(value, status, alarm_severity): enable_disable_beamline('G2', g2_line, value) def receive_pif_update(value, status, alarm_severity): enable_disable_beamline('PIF', pif_line, value) self.gui_header.beam_current_wgt_dict[ "G3"].trigger_monitor_str.connect(receive_g3_update) self.gui_header.beam_current_wgt_dict[ "O2"].trigger_monitor_str.connect(receive_o2_update) self.gui_header.beam_current_wgt_dict[ "G2"].trigger_monitor_str.connect(receive_g2_update) self.gui_header.beam_current_wgt_dict[ "PIF"].trigger_monitor_str.connect(receive_pif_update) pvd=self.cafe.getPVCache(self.settings.data["G3"]["status"], dt='str') receive_g3_update(pvd.value[0], pvd.alarmStatus, pvd.alarmSeverity) pvd=self.cafe.getPVCache(self.settings.data["PIF"]["status"], dt='str') receive_pif_update(pvd.value[0], pvd.alarmStatus, pvd.alarmSeverity) pvd=self.cafe.getPVCache(self.settings.data["G2"]["status"], dt='str') receive_g2_update(pvd.value[0], pvd.alarmStatus, pvd.alarmSeverity) pvd=self.cafe.getPVCache(self.settings.data["O2"]["status"], dt='str') receive_o2_update(pvd.value[0], pvd.alarmStatus, pvd.alarmSeverity) def receive_sec_state(handle, pv, pvdata): pvsplit = pv.split("-") secsplit = pvsplit[0].split("P") sec = secsplit[1] if pvdata.value[0] == 'ON': #print("sec-ON", secsplit[1], flush=True) self.table_sol_dict[sec].init_value_button.setEnabled(True) self.table_pwr_dict[sec].init_value_button.setEnabled(True) else: #print("sec-OFF", secsplit[1], flush=True) self.table_sol_dict[sec].init_value_button.setEnabled(False) self.table_pwr_dict[sec].init_value_button.setEnabled(False) #print("sec", secsplit[1], flush=True) for state in sec_state_list: self.cafe.monitor(state, receive_sec_state) def group_sector_status(self): qgrid = QGridLayout() #Connect all channels #Heading idx = self.settings.data["header"].index("G1") idx_last = self.settings.data["header"].index("MASTER") #Sector qlp = QLabel("Power \n(kW)") f = qlp.font() f.setPixelSize(13) qlp.setFont(f) qlp.setAlignment(Qt.AlignCenter) qli = QLabel("Initial \n(kW)") f = qli.font() f.setPixelSize(13) qli.setFont(f) qli.setAlignment(Qt.AlignCenter) qgrid.addWidget(qli, 0, 1, 1, 1) qsa = QLabel("Saving \n(MWh)") f = qsa.font() f.setPixelSize(13) qsa.setFont(f) qsa.setAlignment(Qt.AlignCenter) qti = QLabel("Time in \nSaving Mode") f = qti.font() f.setPixelSize(13) qti.setFont(f) qti.setAlignment(Qt.AlignCenter) qtotsav = QLabel("Tot Saved \n(MWh)") f = qtotsav.font() f.setPixelSize(13) qtotsav.setFont(f) qtotsav.setAlignment(Qt.AlignCenter) qgrid.addWidget(qlp, 0, 2, 1, 1) qgrid.addWidget(qsa, 0, 3, 1, 1) qgrid.addWidget(qti, 0, 5, 1, 1) qgrid.addWidget(qtotsav, 0, 6, 1, 1) for i, sector in enumerate(self.settings.data["header"][idx:idx_last]): a, b, c, d, e, f = self.sector_status(sector) a.setContentsMargins(2, 0, 0, 0) qgrid.addWidget(a, i+1, 0, 1, 1) qgrid.addWidget(b, i+1, 1, 1, 1) qgrid.addWidget(c, i+1, 2, 1, 1) qgrid.addWidget(d, i+1, 3, 1, 1) qgrid.addWidget(e, i+1, 5, 1, 1) qgrid.addWidget(f, i+1, 6, 1, 1) _line = QHLine() _line.setFixedHeight(10) _row = qgrid.rowCount() qgrid.addWidget(_line, _row, 1, 1, 6) #qgrid.setRowMinimumHeight(_row, 60) qtot = QLabel("Total:") fnt = qtot.font() fnt.setPixelSize(13) qtot.setFont(fnt) qgrid.addWidget(qtot, _row+1, 0, 1, 1) qgrid.addWidget( CAQLineEdit(self, pv_name="PRO-HUSH:LASTPWR", show_units=True), _row+1, 1, 1, 1) qgrid.addWidget( CAQLineEdit(self, pv_name="PRO-HUSH:TOTPWR", show_units=True), _row+1, 2, 1, 1) qgrid.addWidget( CAQLineEdit(self, pv_name="PRO-HUSH:LASTSAVE", show_units=True), _row+1, 3, 1, 2) qgrid.addWidget( CAQLineEdit(self, pv_name="PRO-HUSH:TOTSAVE", show_units=True), _row+1, 6, 1, 2) qgrid.setContentsMargins(9, 20, 9, 9) qw = QGroupBox("Savings Overview") qw.setContentsMargins(9, 9, 9, 9) qw.setObjectName("OUTER") qw.setLayout(qgrid) qw.setFixedWidth(596) #480 #for column_no in range(0, 5): # qgrid.itemAtPosition(1, column_no).widget().setStyleSheet("QWidget { background: lightblue; }") #addStyleName( #item = qgrid.itemAt(2) #.widget().setStyleSheet("QLabel { background-color: green }") #item.widget().setStyleSheet("background-color: green") #widgets on grid from left to right starting at top rtow return qw def sector_status(self, sector): '''Create each sector line for inclusion into group ''' #device = "SEC-" + sector device = "P" + sector + "-HUSH" #Qlabel qsector = QLabel(sector+":") f = qsector.font() f.setPixelSize(13) qsector.setFont(f) _color = "black" try: _color = self.settings.data[sector]["color"] except KeyError as ex: print(ex, self.settings.data[sector]) _color_str = "color : {0};".format(_color) _incolor = "QLabel {" + _color_str + "}" qsector.setStyleSheet(_incolor) #Savings pv_pwr_tot = device + ":TOTPWR" pv_pwr_last = device + ":LASTPWR" pv_pwr_saved = device + ":LASTSAVE" pv_pwr_timeout = device + ":OFFTIME" pv_pwr_totsave = device + ":TOTSAVE" self.cafe.openPrepare() self.cafe.open([pv_pwr_tot, pv_pwr_last, pv_pwr_saved, pv_pwr_timeout]) self.cafe.openNowAndWait(2.0) time.sleep(0.1) #print("device", device, flush=True) def cb_outtime(handle, pv, pvdata): try: _delta = "{}".format(str(timedelta(seconds=(pvdata.value[0])))) if ", " in _delta: _split = _delta.split(", ") if len(_split[1]) == len("0:00:00"): _delta = _split[0] + ", " + _split[1] if 'days,' in _delta: _delta = _delta.replace("days,", "d") elif 'day,' in _delta: _delta = _delta.replace("day,", "d") self.offtime_dict[pv].setText(_delta) except KeyError: pass now = QDateTime.currentDateTime() xdate = QDate(now.date().year(), 1, 15) xstart = QDateTime(xdate) qpinit = CAQLineEdit(self, pv_name=pv_pwr_last, show_units=False) qpnow = CAQLineEdit(self, pv_name=pv_pwr_tot, show_units=False) qpsave = CAQLineEdit(self, pv_name=pv_pwr_saved, show_units=False) qptotsave = CAQLineEdit(self, pv_name=pv_pwr_totsave, show_units=False) qptime = CAQLineEdit(self, pv_name=pv_pwr_timeout, monitor_callback=cb_outtime, show_units=True) self.offtime_dict[pv_pwr_timeout] = qptime #Time to allow cb_outtime to fire time.sleep(0.5) qptime.setFixedWidth(104) #qtdis = QBasicTimer() #qpnow.setAlignment(Qt.AlignRight) #qpnow.style().polish(qpnow) return qsector, qpinit, qpnow, qpsave, qptime, qptotsave def group_sector_qtabwidget(self): idx_last = self.settings.data["header"].index("MASTER") idx = self.settings.data["header"].index("G1") #open all PVS pv = [] for sector in self.settings.data["header"][idx:idx_last]: device_list = self.settings.data[sector]["device"] attribute_list = self.settings.data[sector]["attribute"] for att in attribute_list: for dev in device_list: pv.append(dev + ":" + att) self.cafe.openPrepare() self.cafe.open(pv) self.cafe.openNowAndWait(1.0) #self.cafe.supplementHandles() sector_wgt_dict = {} for sector in self.settings.data["header"][idx:idx_last]: sector_wgt_dict[sector] = self.ca_table_sector_widget(sector=sector) sector_tab_widget = QTabWidget() sector_tab_widget.setFont(self.font_gui) sector_tab_widget.setStyleSheet("QTabBar {font-size: 12pt;}") sector_tab_widget.tabBar().setShape(QTabBar.TriangularNorth) for i, sector in enumerate(self.settings.data["header"][idx:idx_last]): sector_tab_widget.addTab(sector_wgt_dict[sector], sector) color = self.settings.data[sector]["color"] sector_tab_widget.tabBar().setTabTextColor(i, QColor(color)) #sector_tab_widget.addTab(sector_wgt_dict["INJ2"], "RF") #color = self.settings.data["INJ2"]["color"] #sector_tab_widget.tabBar().setTabTextColor( # len(self.settings.data["header"][idx:]), QColor(color)) return sector_tab_widget def check_status_list(self, pv_list, status_list, line): _brk = ("------------------------------------------------------" + "------------------------------------------------------") self.parent.trigger_log_message.emit( MsgSeverity.INFO.name, _pymodule, line, _brk, {}) _options = {} for pv, stat in zip(pv_list, status_list): if stat != self.cyca.ICAFE_NORMAL: _mess = ("Error in 'set' for " + pv + " ") _options['statusCode'] = ( str(stat) + " " + self.cafe.getStatusCodeAsString(stat)) _options['statusInfo'] = self.cafe.getStatusInfo(stat) self.parent.trigger_log_message.emit( MsgSeverity.WARN.name, _pymodule, line, _mess, _options) self.parent.trigger_log_message.emit( MsgSeverity.INFO.name, _pymodule, line, _brk, {}) _mess = ("The following devices reported an error " + "in 'set' operation:") self.parent.trigger_log_message.emit( MsgSeverity.INFO.name, _pymodule, line, _mess, {}) def check_status(self, pv, stat, line): if stat != self.cyca.ICAFE_NORMAL: _mess = ("Error in 'set' for " + pv + ".") _options = {} _options['statusCode'] = ( str(stat) + " " + self.cafe.getStatusCodeAsString(stat)) _options['statusInfo'] = self.cafe.getStatusInfo( stat) self.parent.trigger_log_message.emit( MsgSeverity.WARN.name, _pymodule, line, _mess, _options) def on_sector_standby(self): target = self.sender() sector = target.sector _table = self.table_sol_dict[sector] def is_update_enabled(): '''Check if update buttons are enabled if NOT, then do not allow inital values values to be updated" ''' if self.table_pwr_dict[sector].init_value_button.isEnabled() \ and _table.init_value_button.isEnabled(): return True else: return False QApplication.processEvents(QEventLoop.AllEvents, 1.0) #Do NOT do updates if in standby mode! #Update button is disabled when in standby #disenable widgets to avoid circular behaviour since #updates of SOL also update PWR, and vice-versa #Do not click if already on standby!! if is_update_enabled(): self.table_pwr_dict[sector].init_value_button.setEnabled(False) _table.init_value_button.click() time.sleep(0.05) self.table_pwr_dict[sector].init_value_button.setEnabled(True) _table.init_value_button.setEnabled(False) self.table_pwr_dict[sector].init_value_button.click() time.sleep(0.05) self.table_sol_dict[sector].init_value_button.setEnabled(True) QApplication.processEvents(QEventLoop.AllEvents, 1.0) if not self.input_parameters['simulation']: status, status_list, pv_list = _table.set_standby_values() if status != self.cyca.ICAFE_NORMAL: self.check_status_list(pv_list, status_list, _line()) pv = 'P' + target.sector + "-HUSH:STATE" stat = self.cafe.set(pv, 0) self.check_status(pv, stat, _line()) def on_sector_restore(self): target = self.sender() sector = target.sector _table = self.table_sol_dict[sector] if not self.input_parameters['simulation']: status, status_list, pv_list = _table.restore_init_values() if status != self.cyca.ICAFE_NORMAL: self.check_status_list(pv_list, status_list, _line()) pv = 'P' + target.sector + "-HUSH:STATE" stat = self.cafe.set(pv, 1) self.check_status(pv, stat, _line()) _table_pwr = self.table_pwr_dict[sector] row_pwr_dict = _table_pwr.get_init_values() sum_pwr = sum(list(row_pwr_dict.values())) pv_last_pwr = 'P' + target.sector + "-HUSH:LASTPWR" stat = self.cafe.set(pv_last_pwr, sum_pwr) self.check_status(pv_last_pwr, stat, _line()) def ca_table_sector_widget(self, sector: str="", color: str="MACHINE"): device_list = self.settings.data[sector]["device"] attribute_list = self.settings.data[sector]["attribute"] try: _standby_values = self.settings.data[sector]["standby"] print("Standby Values", sector, _standby_values, flush=True) except KeyError as ex: print("On Standby values not given for sector {0}".format(sector)) pv_dict = {} for att in attribute_list: pv_dict[att] = [] #[None] * len(device_list) for dev in device_list: pv_dict[att].append(dev + ":" + att) _table_height = 700 try: _delay = self.input_parameters["delayRamp"] except KeyError: _delay = 0.09 pass print(pv_dict['SOL:2'], flush=True) print(_standby_values, flush=True) table_sol = CAQTableWidget( self, pv_list=pv_dict['SOL:2'], show_units=True, notify_freq_hz=0, notify_unison=False, scale_factor=1, show_timestamp=False, init_column=True, pv_list_show=device_list, standby_column=True, standby_values=_standby_values, set_delay = _delay) table_sol.restore_value_button.setToolTip( ("Restore devices to their pre-standby values")) table_sol.init_value_button.setToolTip( ("Shows initial, pre-standby values. Update is also " + "executed automatically before the standby procedure.")) table_sol.standby_value_button.sector = sector table_sol.standby_value_button.clicked.disconnect() table_sol.standby_value_button.clicked.connect(self.on_sector_standby) table_sol.restore_value_button.sector = sector table_sol.restore_value_button.clicked.disconnect() table_sol.restore_value_button.clicked.connect(self.on_sector_restore) self.table_sol_dict[sector] = table_sol table_ist = CAQTableWidget( self, pv_list=pv_dict['IST:2'], show_units=True, notify_freq_hz=2, notify_unison=True, scale_factor=1, show_timestamp=False, init_column=False, pv_list_show=[False]) table_pwr = CAQTableWidget( self, pv_list=pv_dict['PWR'], show_units=False, notify_freq_hz=0, suffix = "kW", notify_unison=False, scale_factor=0.001, show_timestamp=False, init_column=True, init_list=pv_dict['PWR'], pv_list_show=[False]) table_pwr.init_value_button.setToolTip( ("Shows initial, pre-standby values. Update is also " + "executed automatically before the standby procedure.")) self.table_pwr_dict[sector] = table_pwr header_item = QTableWidgetItem() header_init = QTableWidgetItem() header_standby = QTableWidgetItem() header_value = QTableWidgetItem() f = header_value.font() f.setPixelSize(13) header_item.setFont(f) header_item.setText('Device') header_init.setFont(f) header_init.setText('Init. Value') header_standby.setFont(f) header_standby.setText('Standby') header_value.setFont(f) header_value.setText('SOL:2') table_sol.setContentsMargins(15, 0, 15, 10) table_sol.setHorizontalHeaderItem(0, header_item) table_sol.setHorizontalHeaderItem(1, header_init) table_sol.setHorizontalHeaderItem(2, header_standby) table_sol.setHorizontalHeaderItem(3, header_value) table_sol.setColumnWidth(0, 80) table_sol.setColumnWidth(1, 88) table_sol.setColumnWidth(2, 80) table_sol.setColumnWidth(3, 88) table_sol.setFixedWidth(386) ioc_magnets = [] for i, device in enumerate(device_list): if device in self.settings.data[sector]["iocDevice"]: ioc_magnets.append(i) header_value = QTableWidgetItem() f = header_value.font() f.setPixelSize(13) header_value.setFont(f) header_value.setText('IST:2') table_ist.setContentsMargins(15, 0, 15, 10) table_ist.setHorizontalHeaderItem(0, header_value) table_ist.setColumnWidth(0, 90) table_ist.setFixedWidth(140) header_init = QTableWidgetItem() f = header_init.font() header_init.setFont(f) header_init.setText('Init. Value') header_value = QTableWidgetItem() f = header_value.font() f.setPixelSize(13) header_value.setFont(f) header_value.setText('PWR') table_pwr.setContentsMargins(15, 0, 15, 10) table_pwr.setHorizontalHeaderItem(0, header_init) table_pwr.setHorizontalHeaderItem(1, header_value) table_pwr.setColumnWidth(0, 88) table_pwr.setColumnWidth(1, 88) table_pwr.setFixedWidth(226) for i in ioc_magnets: table_sol.paint_rows(row_range=[i, i+1], reset=False, columns=[0, 1]) table_ist.paint_rows(row_range=[i, i+1], reset=False) table_pwr.paint_rows(row_range=[i, i+1], reset=False) pvStatus = self.settings.data[sector]['status'] beamline_status = self.cafe.getCache(pvStatus) if beamline_status is not None: #if I < self.I_min: if "Geschlossen" in beamline_status: try: values = self.settings.data[sector]['ref'] table_sol.set_init_values(values) except KeyError: pass try: pwr_values = self.settings.data[sector]['pwrref'] table_pwr.set_init_values(pwr_values) except KeyError: pass def on_sol_update(): table_sol.init_value_button.setEnabled(False) table_pwr.init_value_button.click() time.sleep(0.1) table_sol.init_value_button.setEnabled(True) pv = "P" + table_pwr.init_value_button.sector + "-HUSH:LASTPWR" _sum = sum(table_pwr.get_init_values().values()) print("SUM up all the values", pv, _sum, flush=True) stat = self.cafe.set(pv, _sum) self.check_status(pv, stat, _line()) def on_init_update(): table_pwr.init_value_button.setEnabled(False) table_sol.init_value_button.click() time.sleep(0.1) table_pwr.init_value_button.setEnabled(True) table_sol.init_value_button.sector = sector table_sol.init_value_button.clicked.connect(on_sol_update) table_pwr.init_value_button.sector = sector table_pwr.init_value_button.clicked.connect(on_init_update) hbox = QHBoxLayout() hbox.addWidget(table_sol) hbox.addWidget(table_ist) hbox.addWidget(table_pwr) hbox.setSpacing(10) hbox.setAlignment(Qt.AlignTop) qw = QWidget() qw.setLayout(hbox) obj_name = self.settings.data[sector]["colorObj"] if self.obj_to_upper: obj_name = obj_name.upper() qw.setObjectName(obj_name) return qw