diff --git a/pyqt-apps/scripts/sirius-hla-si-id-control.py b/pyqt-apps/scripts/sirius-hla-si-id-control.py index 26e48336c..1066922b8 100644 --- a/pyqt-apps/scripts/sirius-hla-si-id-control.py +++ b/pyqt-apps/scripts/sirius-hla-si-id-control.py @@ -7,7 +7,7 @@ from siriushla.sirius_application import SiriusApplication from siriuspy.envars import VACA_PREFIX from siriushla.si_id_control import IDControl, APUControlWindow, \ - PAPUControlWindow, DELTAControlWindow + DELTAControlWindow, IVUControlWindow parser = _argparse.ArgumentParser( @@ -25,15 +25,15 @@ isall = args.isall app = SiriusApplication() -if 'PAPU' in args.device: - app.open_window( - PAPUControlWindow, parent=None, prefix=prefix, device=device) -elif 'APU' in args.device: +if 'APU' in args.device: app.open_window( APUControlWindow, parent=None, prefix=prefix, device=device) elif 'DELTA' in args.device: app.open_window( DELTAControlWindow, parent=None, prefix=prefix, device=device) +elif 'IVU' in args.device: + app.open_window( + IVUControlWindow, parent=None, prefix=prefix, device=device) elif not device or isall: app.open_window(IDControl, parent=None, prefix=prefix) sys.exit(app.exec_()) diff --git a/pyqt-apps/siriushla/as_ap_launcher/menu.py b/pyqt-apps/siriushla/as_ap_launcher/menu.py index d5e2c45a2..251bfe438 100755 --- a/pyqt-apps/siriushla/as_ap_launcher/menu.py +++ b/pyqt-apps/siriushla/as_ap_launcher/menu.py @@ -461,8 +461,9 @@ def _create_id_menu(self): self.add_object_to_level1(menu, All) idlist = ['SI-06SB:ID-APU22', 'SI-07SP:ID-APU22', - 'SI-09SA:ID-APU22', 'SI-11SP:ID-APU58', - 'SI-10SB:ID-DELTA52', 'SI-17SA:ID-APU22'] + 'SI-08SB:ID-IVU18', 'SI-09SA:ID-APU22', + 'SI-10SB:ID-DELTA52', 'SI-11SP:ID-APU58', + 'SI-14SB:ID-IVU18', 'SI-17SA:ID-APU22'] for idname in idlist: idname = SiriusPVName(idname) beamline = IDSearch.conv_idname_2_beamline(idname) diff --git a/pyqt-apps/siriushla/si_id_control/__init__.py b/pyqt-apps/siriushla/si_id_control/__init__.py index 701d6b264..397eb687c 100644 --- a/pyqt-apps/siriushla/si_id_control/__init__.py +++ b/pyqt-apps/siriushla/si_id_control/__init__.py @@ -3,5 +3,6 @@ from .apu import APUControlWindow # from .epu import EPUControlWindow from .delta import DELTAControlWindow -from .papu import PAPUControlWindow +# from .papu import PAPUControlWindow +from .ivu import IVUControlWindow from .id_control import IDControl diff --git a/pyqt-apps/siriushla/si_id_control/id_control.py b/pyqt-apps/siriushla/si_id_control/id_control.py index c340a3cbc..5d0ede5e0 100644 --- a/pyqt-apps/siriushla/si_id_control/id_control.py +++ b/pyqt-apps/siriushla/si_id_control/id_control.py @@ -14,7 +14,7 @@ from .apu import APUSummaryHeader, APUSummaryWidget from .delta import DELTASummaryHeader, DELTASummaryWidget -from .papu import PAPUSummaryHeader, PAPUSummaryWidget +from .ivu import IVUSummaryHeader, IVUSummaryWidget from .util import get_id_icon @@ -60,12 +60,16 @@ def _setupUi(self): self._gbox_epu = QGroupBox('DELTA', self) self._gbox_epu.setLayout(self._setupDELTALayout()) + self._gbox_ivu = QGroupBox('IVU', self) + self._gbox_ivu.setLayout(self._setupIVULayout()) + lay = QGridLayout(cwid) lay.addWidget(self.label_mov1, 0, 0) lay.addWidget(label, 0, 1) lay.addWidget(self.label_mov2, 0, 2) lay.addWidget(self._gbox_apu, 1, 0, 1, 3) lay.addWidget(self._gbox_epu, 2, 0, 1, 3) + lay.addWidget(self._gbox_ivu, 3, 0, 1, 3) lay.setColumnStretch(0, 1) lay.setColumnStretch(1, 15) lay.setColumnStretch(2, 1) @@ -128,6 +132,25 @@ def _setupDELTALayout(self): self._channels_mov.append(ch_mov) return lay + + def _setupIVULayout(self): + lay = QVBoxLayout() + lay.setAlignment(Qt.AlignTop) + + self._delta_header = IVUSummaryHeader(self) + lay.addWidget(self._delta_header) + + idlist = ['SI-08SB:ID-IVU18', 'SI-14SB:ID-IVU18'] + for idname in idlist: + delta_wid = IVUSummaryWidget(self, self._prefix, idname) + lay.addWidget(delta_wid) + self._id_widgets.append(delta_wid) + ch_mov = SiriusConnectionSignal(_PVName(idname).substitute( + prefix=self._prefix, propty='Moving-Mon')) + ch_mov.new_value_signal[int].connect(self._handle_moving_vis) + self._channels_mov.append(ch_mov) + + return lay # def _setupPAPULayout(self): # lay = QVBoxLayout() diff --git a/pyqt-apps/siriushla/si_id_control/ivu.py b/pyqt-apps/siriushla/si_id_control/ivu.py new file mode 100644 index 000000000..787a28f9e --- /dev/null +++ b/pyqt-apps/siriushla/si_id_control/ivu.py @@ -0,0 +1,419 @@ +"""IVU Control Module.""" + +from qtpy.QtCore import Qt, QSize +from qtpy.QtWidgets import QGroupBox, QLabel, \ + QHBoxLayout, QVBoxLayout, QWidget, QPushButton, \ + QGridLayout, QSizePolicy as QSzPlcy +import qtawesome as qta +from pydm.widgets import PyDMPushButton + +from siriuspy.epics import PV + +from ..util import connect_newprocess, connect_window +from ..widgets import SiriusLedAlert, SiriusLabel, SiriusSpinbox, \ + PyDMLogLabel, PyDMLed, SiriusEnumComboBox, SiriusLineEdit +from ..widgets.dialog import StatusDetailDialog + +from .base import IDCommonControlWindow, \ + IDCommonSummaryBase, IDCommonSummaryHeader, IDCommonSummaryWidget + + +class IVUControlWindowUtils(): + """.""" + + STATUS_PVS = { + "Alarms": ( + "Alarm-Mon", "AlarmBits-Mon", "AlarmLabels-Cte" + ), + "Interlock": ( + "Intlk-Mon", "IntlkBits-Mon", "IntlkLabels-Cte" + ), + "Is Operational": "IsOperational-Mon", + "PLC State": "PLCState-Mon", + "Sw": { + "Killed": "KillSw-Mon", + "Limit": "LimSw-Mon" + }, + "Logs": { + "IOC Log": "Log-Mon", + "Sequencer State Machine Log": "StateMachineLog-Mon" + } + } + + MAIN_CONTROL_PVS = { + "KParam": { + "SP": "KParam-SP", + "RB": "KParam-RB", + "Mon": "KParam-Mon" + }, + "KParam Speed": { + "SP": "KParamVelo-SP", + "RB": "KParamVelo-RB", + }, + "Change KParam": { + "pvname": "KParamChange-Cmd", + "icon": "fa5s.play" + }, + "PParam": { + "SP": "PParam-SP", + "RB": "PParam-RB", + "Mon": "PParam-Mon" + }, + "PParam Speed": { + "SP": "PParamVelo-SP", + "RB": "PParamVelo-RB", + }, + "Change PParam": { + "pvname": "PParamChange-Cmd", + "icon": "fa5s.play" + }, + "Polarization": { + "SP": "Pol-Sel", + "RB": "Pol-Sts", + "Mon": "Pol-Mon" + }, + "Change Polarization": { + "pvname": "PolChange-Cmd", + "icon": "fa5s.play" + }, + "Moving": "Moving-Mon", + "Motors Enabled": "MotorsEnbld-Mon", + "Abort": { + "pvname": "Abort-Cmd", + "icon": "fa5s.stop" + } + } + + AUX_CONTROL_PVS = { + "Max. Speed": { + "SP": "MaxVelo-SP", + "RB": "MaxVelo-RB", + }, + "Max. Acc.": { + "SP": "MaxAcc-SP", + "RB": "MaxAcc-RB", + }, + "Header": ( + "KParam", "PParam" + ), + "Acceleration": ( + "KParamAcc-SP", "KParamAcc-RB", + "PParamAcc-SP", "PParamAcc-RB" + ), + "Tolerance": ( + "PosTol-SP", "PosTol-RB", + "PolTol-SP", "PolTol-RB" + ), + "Start Parking": { + "pvname": "StartParking-Cmd", + "icon": "fa5s.parking" + } + } + + +class IVUControlWindow(IDCommonControlWindow, IVUControlWindowUtils): + """IVU Control Window.""" + + def _mainControlsWidget(self): + group = QGroupBox('Main Controls') + lay = QGridLayout() + group.setLayout(lay) + + row = 0 + for title, pv_info in self.MAIN_CONTROL_PVS.items(): + label = QLabel( + title, self, alignment=Qt.AlignRight | Qt.AlignVCenter) + label.setFixedWidth(150) + lay.addWidget(label, row, 0) + + if title in ("Moving", "Motors Enabled"): + self._createMotion(pv_info, lay, row) + elif title in ["KParam", "PParam", "KParam Speed", "PParam Speed"]: + self._createParam(pv_info, lay, row) + elif title == "Polarization": + self._createPolarization(pv_info, lay, row) + elif isinstance(pv_info, str): + pvname = self.dev_pref.substitute(propty=pv_info) + lbl = SiriusLabel(self, init_channel=pvname) + lbl.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + lbl.setMinimumWidth(125) + lbl.showUnits = True + lbl.setMaximumHeight(40) + lay.addWidget(lbl, row, 1, 1, 2) + else: + self._createIconBtns(pv_info, lay, row) + row += 1 + + return group + + def _statusWidget(self): + return self._createStatusGroup("Status", self.STATUS_PVS) + + def _auxCommandsWidget(self): + group = QGroupBox('Auxiliary Controls') + lay = QGridLayout() + group.setLayout(lay) + + row = 0 + for title, pv_info in self.AUX_CONTROL_PVS.items(): + if "Header" not in title: + label = QLabel( + title, self, alignment=Qt.AlignRight | Qt.AlignVCenter) + lay.addWidget(label, row, 0) + + if title in ["Max. Speed", "Max. Acc."]: + self._createParam(pv_info, lay, row) + elif title in ["Abort", "Start Parking"]: + self._createIconBtns(pv_info, lay, row) + elif "Header" in title: + self._createHeaders(pv_info, lay, row) + else: + self._createAccTol(pv_info, lay, row) + row += 1 + + return group + + def _ctrlModeWidget(self): + gbox_ctrlmode = QGroupBox('Control Mode') + lay_ctrlmode = QHBoxLayout(gbox_ctrlmode) + lay_ctrlmode.setAlignment(Qt.AlignCenter) + + label = QLabel("Is Remote") + lay_ctrlmode.addWidget(label, alignment=Qt.AlignRight) + + self._led_ctrlmode = PyDMLed( + self, self.dev_pref.substitute(propty='IsRemote-Mon')) + self._led_ctrlmode.offColor = PyDMLed.Red + self._led_ctrlmode.onColor = PyDMLed.LightGreen + lay_ctrlmode.addWidget(self._led_ctrlmode, alignment=Qt.AlignLeft) + + return gbox_ctrlmode + + def _ffSettingsWidget(self): + but = QPushButton('Feedforward Settings', self) + connect_newprocess( + but, ['sirius-hla-si-ap-idff.py', self._device]) + return but + + # --- auxiliary methods --- + + def _createParam(self, pv_info, lay, row): + pvname = self.dev_pref.substitute(propty=pv_info["SP"]) + cb = SiriusLineEdit(self, init_channel=pvname) + lay.addWidget(cb, row, 1, 1, 1) + lay.setContentsMargins(3, 3, 3, 3) + + col = 2 + for key in ["RB", "Mon"]: + if key not in pv_info: + continue + pvname = self.dev_pref.substitute(propty=pv_info[key]) + lbl = SiriusLabel(self, init_channel=pvname, keep_unit=True) + lbl.setMinimumWidth(125) + lbl.showUnits = True + lbl.setAlignment(Qt.AlignCenter) + lay.addWidget(lbl, row, col, 1, 1) + col += 1 + + def _createMotion(self, pv_info, lay, row): + pvname = self.dev_pref.substitute(propty=pv_info) + led = PyDMLed(self, init_channel=pvname) + led.setMaximumWidth(50) + lay.addWidget(led, row, 1, 1, 1) + + def _createPolarization(self, pv_info, lay, row): + + pvname = self.dev_pref.substitute(propty=pv_info["SP"]) + cb = SiriusEnumComboBox(self, init_channel=pvname) + cb.setMinimumWidth(50) + lay.addWidget(cb, row, 1, 1, 1) + lay.setContentsMargins(0, 0, 0, 0) + + col = 2 + for key in ["RB", "Mon"]: + pvname = self.dev_pref.substitute(propty=pv_info[key]) + lbl = SiriusLabel(self, init_channel=pvname) + lbl.setMinimumWidth(125) + lbl.showUnits = True + lbl.setAlignment(Qt.AlignCenter) + lay.addWidget(lbl, row, col, 1, 1) + col += 1 + + def _createDetailedLedBtn(self, pv_tuple): + + btn = QPushButton('', self) + btn.setIcon(qta.icon('fa5s.list-ul')) + btn.setObjectName('sts') + btn.setStyleSheet( + '#sts{min-width:25px; max-width:25px; icon-size:20px;}') + pvname = self.dev_pref.substitute(propty=pv_tuple[0]) + pvname_labels = self.dev_pref.substitute(propty=pv_tuple[1]) + + try: + pv = PV(pvname_labels) + pv.wait_for_connection(timeout=5) + labels = pv.get() + connect_window( + btn, StatusDetailDialog, self, pvname=pvname, + labels=list(labels), section="ID") + except: + btn.setEnabled(False) + return btn + + def _createStatusWidget(self, title, pv_suffix): + pv_tuple = tuple() + wid = QWidget() + lay = QHBoxLayout() + lay.setContentsMargins(0, 0, 0, 0) + wid.setLayout(lay) + + label = QLabel(title, self, alignment=Qt.AlignRight | Qt.AlignVCenter) + lay.addWidget(label) + + if isinstance(pv_suffix, tuple): + pv_tuple = (pv_suffix[1], pv_suffix[2]) + pv_suffix = pv_suffix[0] + + pvname = self.dev_pref.substitute(propty=pv_suffix) + if 'PLC State' in title: + led = SiriusLabel(self, pvname) + else: + led = SiriusLedAlert(init_channel=pvname) + lay.addWidget(led, alignment=Qt.AlignLeft) + + if pv_tuple: + detailed_btn = self._createDetailedLedBtn(pv_tuple) + lay.addWidget(detailed_btn, alignment=Qt.AlignLeft) + + return wid + + def _createLogWidget(self, title, pv_suffix): + wid = QWidget() + lay = QVBoxLayout() + lay.setContentsMargins(0, 0, 0, 0) + wid.setLayout(lay) + + label = QLabel(title, self) + lay.addWidget(label) + + pvname = self.dev_pref.substitute(propty=pv_suffix) + log = PyDMLogLabel(init_channel=pvname) + lay.addWidget(log) + + return wid + + def _createStatusGroup(self, title, pvs): + group = QGroupBox() + lay = QVBoxLayout() + group.setLayout(lay) + group.setTitle(title) + + pos = [0, 0] + for title, pv_info in pvs.items(): + if isinstance(pv_info, dict): + widget = self._createStatusGroup(title, pv_info) + else: + if "Log" in title: + widget = self._createLogWidget(title, pv_info) + else: + widget = self._createStatusWidget(title, pv_info) + lay.addWidget(widget) + pos[1] += 1 + + return group + + def _createIconBtns(self, pv_info, lay, row): + btn = PyDMPushButton(self, label='', icon=qta.icon(pv_info["icon"])) + btn.channel = self.dev_pref.substitute(propty=pv_info["pvname"]) + btn.pressValue = 1 + btn.setIconSize(QSize(20, 20)) + btn.setMaximumWidth(25) + btn.setStyleSheet( + '#Start{min-width:30px; max-width:30px; icon-size:25px;}') + lay.addWidget(btn, row, 1, 1, 4) + + def _createHeaders(self, pv_info, lay, row): + col = 1 + for header_lbl in pv_info: + lbl = QLabel(header_lbl, self, alignment=Qt.AlignCenter) + lbl.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Maximum) + lay.addWidget(lbl, row, col, 1, 2) + col += 2 + + def _createAccTol(self, pv_info, lay, row): + col = 1 + for enum in range(0, 2): + pvname = self.dev_pref.substitute(propty=pv_info[enum*2]) + edit = SiriusLineEdit(self, init_channel=pvname) + lay.addWidget(edit, row, col, 1, 1) + + pvname = self.dev_pref.substitute(propty=pv_info[(enum*2)+1]) + lbl = SiriusLabel(self, init_channel=pvname, keep_unit=True) + lbl.showUnits = True + lbl.setAlignment(Qt.AlignCenter) + lay.addWidget(lbl, row, col+1, 1, 1) + col += 2 + + +class IVUSummaryBase(IDCommonSummaryBase): + """IVU Summary Base Widget.""" + + MODEL_WIDTHS = ( + ('Alarms', 4), + ('Interlock', 4), + ('KParam', 6), + ('KParam Speed', 6), + ('Start', 4), + ('Stop', 4), + ) + + +class IVUSummaryHeader(IDCommonSummaryHeader, IVUSummaryBase): + """IVU Summary Header.""" + + +class IVUSummaryWidget(IDCommonSummaryWidget, IVUSummaryBase): + """IVU Summary Widget.""" + + def _get_widgets(self, prop): + wids, orientation = super()._get_widgets(prop) + if prop == 'Alarms': + led = SiriusLedAlert( + self, self.dev_pref.substitute(propty='Alarm-Mon')) + wids.append(led) + if prop == 'Interlock': + led = SiriusLedAlert( + self, self.dev_pref.substitute(propty='Intlk-Mon')) + wids.append(led) + elif prop == 'KParam': + spb = SiriusSpinbox( + self, self.dev_pref.substitute(propty='KParam-SP')) + wids.append(spb) + lbl = SiriusLabel( + self, self.dev_pref.substitute(propty='KParam-Mon')) + wids.append(lbl) + elif prop == 'KParam Speed': + spb = SiriusSpinbox( + self, self.dev_pref.substitute(propty='KParamVelo-SP')) + wids.append(spb) + lbl = SiriusLabel( + self, self.dev_pref.substitute(propty='KParamVelo-RB')) + wids.append(lbl) + elif prop == 'Start': + btn = PyDMPushButton(self, label='', icon=qta.icon('fa5s.play')) + btn.channel = self.dev_pref.substitute(propty='KParamChange-Cmd') + btn.pressValue = 1 + btn.setObjectName('Start') + btn.setStyleSheet( + '#Start{min-width:30px; max-width:30px; icon-size:25px;}') + wids.append(btn) + elif prop == 'Stop': + btn = PyDMPushButton(self, label='', icon=qta.icon('fa5s.stop')) + btn.setToolTip('Stop all motion, lock all brakes.') + btn.channel = self.dev_pref.substitute(propty='Abort-Cmd') + btn.pressValue = 1 + btn.setObjectName('Stop') + btn.setStyleSheet( + '#Stop{min-width:30px; max-width:30px; icon-size:25px;}') + wids.append(btn) + return wids, orientation