From 07bffba0f98d1f3eb94de2e0fb78d4337db714bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor=20da=20Silva?= Date: Tue, 31 Mar 2026 08:14:16 -0300 Subject: [PATCH 1/7] Add UE44 PVs --- pyqt-apps/siriushla/si_id_control/ue.py | 177 ++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 pyqt-apps/siriushla/si_id_control/ue.py diff --git a/pyqt-apps/siriushla/si_id_control/ue.py b/pyqt-apps/siriushla/si_id_control/ue.py new file mode 100644 index 000000000..849fbacdd --- /dev/null +++ b/pyqt-apps/siriushla/si_id_control/ue.py @@ -0,0 +1,177 @@ +"""UE Control Module.""" + +from qtpy.QtCore import Qt, QSize +from qtpy.QtWidgets import QGroupBox, QLabel, QWidget, \ + QPushButton, QHBoxLayout, QGridLayout, QSizePolicy +import qtawesome as qta +from pydm.widgets import PyDMPushButton + +from ..util import connect_newprocess, connect_window +from ..widgets import SiriusLedAlert, SiriusLabel, SiriusSpinbox, \ + SiriusLedState, SiriusLineEdit, SiriusEnumComboBox +from ..widgets.dialog import StatusDetailDialog + +from .base import IDCommonControlWindow, IDCommonDialog, \ + IDCommonSummaryBase, IDCommonSummaryHeader, IDCommonSummaryWidget + + +class UEControlWindow(IDCommonControlWindow): + """UE Control Window.""" + + OPERATION_PVS = { + "Pol": { + "Sel": "Pol-Sel", + "Mon": "Pol-Mon" + }, + "Power Off": "PowerOff-Mon", + "Kill Override": "KillOverride-Mon", + "Is Remote": "IsRemote-Mon", + "Device Status": "DeviceStatus-Mon", + "Start Parking": { + "Cmd": "StartParking-Cmd", + "icon": "fa5s.parking" + }, + "Sw": { + "Kill Override": "KillOverride-Mon" + }, + "Abort": { + "Cmd": "Abort-Cmd", + "icon": "fa5s.stop" + } + } + + MAIN_CONTROL_PVS = { + "KParam": { + "SP": "KParam-SP", + "Mon": "KParam-Mon" + }, + "KParam Speed": { + "SP": "KParamVelo-SP", + "RB": "KParamVelo-RB", + "Mon": "KParamVelo-Mon" + }, + "KParam Min Pos": { + "Cte": "KParamMinPos-Cte" + }, + "KParam Max Pos": { + "Cte": "KParamMaxPos-Cte" + }, + "KParam Parked": { + "Cte": "KParamParked-Cte" + }, + "Change KParam": { + "Cmd": "KParamChange-Cmd", + "icon": "fa5s.play" + }, + "PParam": { + "SP": "PParam-SP", + "Mon": "PParam-Mon" + }, + "PParam Speed": { + "SP": "PParamVelo-SP", + "RB": "PParamVelo-RB", + "Mon": "PParamVelo-Mon" + }, + "PParam Min Pos": { + "Cte": "PParamMinPos-Cte" + }, + "PParam Max Pos": { + "Cte": "PParamMaxPos-Cte" + }, + "PParam Parked": { + "Cte": "PParamParked-Cte" + }, + "Change PParam": { + "Cmd": "PParamChange-Cmd", + "icon": "fa5s.play" + }, + "CParam": { + "SP": "CParam-SP", + "Mon": "CParam-Mon" + }, + "CParam Speed": { + "SP": "CParamVelo-SP", + "RB": "CParamVelo-RB", + "Mon": "CParamVelo-Mon" + }, + "CParam Min Pos": { + "Cte": "CParamMinPos-Cte" + }, + "CParam Max Pos": { + "Cte": "CParamMaxPos-Cte" + }, + "CParam State NC": { + "Mon": "CParamStateNC-Mon" + }, + "CParam Error NC": { + "Mon": "CParamErrorNC-Mon" + }, + "CParam Parked": { + "Cte": "CParamParked-Cte" + }, + "Change CParam": { + "Cmd": "CParamChange-Cmd", + "icon": "fa5s.play" + }, + "Offset": { + "SP": "Offset-SP", + "Mon": "Offset-Mon" + }, + "Offset Speed": "OffsetVelo-Mon", + "Offset Min Pos": "OffsetMinPos-Cte", + "Offset Max Pos": "OffsetMaxPos-Cte", + "Offset NC State": "OffsetStateNC-Mon", + "Offset NC Error": "OffsetErrorNC-Mon", + "Servos Positions": { + "Top Right": "TIPos-Mon", + "Top Left": "TOPos-Mon", + "Bottom Right": "BIPos-Mon", + "Bottom Left": "BOPos-Mon" + }, + "Servos IO Status": { + "Top Outside": "TOStatusIO-Mon", + "Top Inside": "TIStatusIO-Mon", + "Bot Outside": "BOStatusIO-Mon", + "Bot Inside": "BIStatusIO-Mon" + }, + "Servos NC Status": { + "Top Outside": "TOStateNC-Mon", + "Top Inside": "TIStateNC-Mon", + "Bot Outside": "BOStateNC-Mon", + "Bot Inside": "BIStateNC-Mon" + }, + "Servos Error NC": { + "Top Outside": "TOErrorNC-Mon", + "Top Inside": "TIErrorNC-Mon", + "Bot Outside": "BOErrorNC-Mon", + "Bot Inside": "BIErrorNC-Mon" + }, + "KShift NC State": "KShiftStateNC-Mon", + "KShift NC Error": "KShiftErrorNC-Mon", + "PShift NC State": "PShiftStateNC-Mon", + "PShift NC Error": "PShiftErrorNC-Mon", + "CParam NC State": "CParamStateNC-Mon", + "CParam NC Error": "CParamErrorNC-Mon", + "Max. Speed": { + "SP": "MaxVelo-SP", + "RB": "MaxVelo-RB" + }, + "Speed Setpoint": "Velo-SP", + "Acc. Setpoint": "Acc-SP", + "Period Length": "PeriodLength-Cte", + "Moving": "Moving-Mon" + } + + SCANS_PVS = { + "Read Traj": { + "Cmd": "ReadTraj-Cmd", + "icon": "fa5s.play" + }, + "Write Traj": { + "Cmd": "WriteTraj-Cmd", + "icon": "mdi.keyboard-return" + }, + "Fly First Pos": "FlyFirstPos-Mon", + "Scan Done": "ScanDone-Mon", + "Scan Mode": "ScanMode-Sel" + } From 67c5b1cf265948a1b6075cdd402be0d8dea23692 Mon Sep 17 00:00:00 2001 From: Ana Clara Oliveira Date: Thu, 2 Apr 2026 17:24:35 -0300 Subject: [PATCH 2/7] ID.WIP: include UE44 in launcher and IDControl GUI --- pyqt-apps/scripts/sirius-hla-si-id-control.py | 6 +- pyqt-apps/siriushla/as_ap_launcher/menu.py | 8 +-- pyqt-apps/siriushla/si_id_control/__init__.py | 1 + .../siriushla/si_id_control/id_control.py | 60 +++++++++++++++---- pyqt-apps/siriushla/si_id_control/ue.py | 19 ++++++ 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/pyqt-apps/scripts/sirius-hla-si-id-control.py b/pyqt-apps/scripts/sirius-hla-si-id-control.py index ca9c68368..831a85b26 100644 --- a/pyqt-apps/scripts/sirius-hla-si-id-control.py +++ b/pyqt-apps/scripts/sirius-hla-si-id-control.py @@ -7,7 +7,8 @@ from siriushla.sirius_application import SiriusApplication from siriuspy.envars import VACA_PREFIX from siriushla.si_id_control import IDControl, APUControlWindow, \ - DELTAControlWindow, IVUControlWindow, VPUControlWindow + DELTAControlWindow, IVUControlWindow, VPUControlWindow, \ + UEControlWindow parser = _argparse.ArgumentParser( @@ -37,6 +38,9 @@ elif 'VPU' in args.device: app.open_window( VPUControlWindow, parent=None, prefix=prefix, device=device) +elif 'UE44' in args.device: + app.open_window( + UEControlWindow, 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 f064db237..13bb6e1a0 100755 --- a/pyqt-apps/siriushla/as_ap_launcher/menu.py +++ b/pyqt-apps/siriushla/as_ap_launcher/menu.py @@ -468,7 +468,7 @@ def _create_id_menu(self): 'SI-08SB:ID-IVU18', 'SI-09SA:ID-APU22', 'SI-10SB:ID-DELTA52', - 'SI-11SP:ID-APU58', + 'SI-11SP:ID-UE44', 'SI-14SB:ID-IVU18', 'SI-17SA:ID-APU22', 'SI-20SB:ID-APU22', @@ -479,10 +479,10 @@ def _create_id_menu(self): text = '{0} - {1} ({2})'.format( idname.dev, idname.sub, beamline) \ if LEVEL2A == QAction else beamline - APU = LEVEL2A(text, menu) + ID_DEV = LEVEL2A(text, menu) self.connect_newprocess( - APU, ['sirius-hla-si-id-control.py', '-dev', idname]) - self.add_object_to_level1(menu, APU) + ID_DEV, ['sirius-hla-si-id-control.py', '-dev', idname]) + self.add_object_to_level1(menu, ID_DEV) return menu diff --git a/pyqt-apps/siriushla/si_id_control/__init__.py b/pyqt-apps/siriushla/si_id_control/__init__.py index e130af5bf..d025ba540 100644 --- a/pyqt-apps/siriushla/si_id_control/__init__.py +++ b/pyqt-apps/siriushla/si_id_control/__init__.py @@ -6,4 +6,5 @@ # from .papu import PAPUControlWindow from .ivu import IVUControlWindow from .vpu import VPUControlWindow +from .ue import UEControlWindow 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 78d2f3855..3f1edd056 100644 --- a/pyqt-apps/siriushla/si_id_control/id_control.py +++ b/pyqt-apps/siriushla/si_id_control/id_control.py @@ -4,7 +4,7 @@ from qtpy.QtGui import QMovie from qtpy.QtCore import Qt, Slot, QSize from qtpy.QtWidgets import QVBoxLayout, QWidget, QGroupBox, QGridLayout, \ - QLabel, QAction, QMenu + QLabel, QAction, QMenu, QScrollArea from pydm.connection_inspector import ConnectionInspector from siriuspy.envars import VACA_PREFIX as _vaca_prefix @@ -16,6 +16,7 @@ from .delta import DELTASummaryHeader, DELTASummaryWidget from .ivu import IVUSummaryHeader, IVUSummaryWidget from .vpu import VPUSummaryHeader, VPUSummaryWidget +from .ue import UESummaryHeader, UESummaryWidget from .util import get_id_icon @@ -39,7 +40,8 @@ def _setupUi(self): label = QLabel('

ID Control Window

', self, alignment=Qt.AlignCenter) - label.setStyleSheet('QLabel{min-height: 3em; max-height: 3em;}') + label.setStyleSheet( + 'QLabel{min-height: 3em; max-height: 3em; min-width: 75em;}') self.label_mov1 = QLabel(self) self.label_mov1.setVisible(False) @@ -67,14 +69,32 @@ def _setupUi(self): self._gbox_vpu = QGroupBox('VPU', self) self._gbox_vpu.setLayout(self._setupVPULayout()) + self._gbox_ue = QGroupBox('UE', self) + self._gbox_ue.setLayout(self._setupUELayout()) + + scarea = QScrollArea(self) + scarea.setStyleSheet( + '.QScrollArea{min-width: 30em;}') + scarea.setSizeAdjustPolicy(scarea.AdjustToContents) + scarea.setWidgetResizable(True) + scr_ar_wid = QWidget() + scr_ar_wid.setObjectName('scrollarea') + scr_ar_wid.setStyleSheet( + '#scrollarea {background-color: transparent;}') + laysa = QVBoxLayout(scr_ar_wid) + laysa.setContentsMargins(0, 0, 0, 0) + laysa.addWidget(self._gbox_apu) + laysa.addWidget(self._gbox_delta) + laysa.addWidget(self._gbox_ivu) + laysa.addWidget(self._gbox_vpu) + laysa.addWidget(self._gbox_ue) + scarea.setWidget(scr_ar_wid) + 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_delta, 2, 0, 1, 3) - lay.addWidget(self._gbox_ivu, 3, 0, 1, 3) - lay.addWidget(self._gbox_vpu, 4, 0, 1, 3) + lay.addWidget(scarea, 1, 0, 1, 3) lay.setColumnStretch(0, 1) lay.setColumnStretch(1, 15) lay.setColumnStretch(2, 1) @@ -88,7 +108,6 @@ def _setupAPULayout(self): idlist = [ 'SI-09SA:ID-APU22', - 'SI-11SP:ID-APU58', 'SI-17SA:ID-APU22', 'SI-20SB:ID-APU22', ] @@ -126,8 +145,8 @@ def _setupIVULayout(self): lay = QVBoxLayout() lay.setAlignment(Qt.AlignTop) - self._delta_header = IVUSummaryHeader(self) - lay.addWidget(self._delta_header) + self._ivu_header = IVUSummaryHeader(self) + lay.addWidget(self._ivu_header) idlist = ['SI-08SB:ID-IVU18', 'SI-14SB:ID-IVU18'] for idname in idlist: @@ -145,8 +164,8 @@ def _setupVPULayout(self): lay = QVBoxLayout() lay.setAlignment(Qt.AlignTop) - self._delta_header = VPUSummaryHeader(self) - lay.addWidget(self._delta_header) + self._vpu_header = VPUSummaryHeader(self) + lay.addWidget(self._vpu_header) idlist = ['SI-06SB:ID-VPU29', 'SI-07SP:ID-VPU29'] for idname in idlist: @@ -160,6 +179,25 @@ def _setupVPULayout(self): return lay + def _setupUELayout(self): + lay = QVBoxLayout() + lay.setAlignment(Qt.AlignTop) + + self._ue_header = UESummaryHeader(self) + lay.addWidget(self._ue_header) + + idlist = ['SI-11SP:ID-UE44', ] + for idname in idlist: + ue_wid = UESummaryWidget(self, self._prefix, idname) + lay.addWidget(ue_wid) + self._id_widgets.append(ue_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 _create_actions(self): self.blctrl_enbl_act = QAction("Enable Beamline Control", self) self.blctrl_enbl_act.triggered.connect( diff --git a/pyqt-apps/siriushla/si_id_control/ue.py b/pyqt-apps/siriushla/si_id_control/ue.py index 849fbacdd..028be7b56 100644 --- a/pyqt-apps/siriushla/si_id_control/ue.py +++ b/pyqt-apps/siriushla/si_id_control/ue.py @@ -175,3 +175,22 @@ class UEControlWindow(IDCommonControlWindow): "Scan Done": "ScanDone-Mon", "Scan Mode": "ScanMode-Sel" } + + +class UESummaryBase(IDCommonSummaryBase): + """UE Summary Base Widget.""" + + MODEL_WIDTHS = ( + ('KParam', 6), + ('KParam Speed', 6), + ('Start', 4), + ('Stop', 4), + ) + + +class UESummaryHeader(IDCommonSummaryHeader, UESummaryBase): + """UE Summary Header.""" + + +class UESummaryWidget(IDCommonSummaryWidget, UESummaryBase): + """UE Summary Widget.""" From d1649a55baf2eeba93a8e2a90529de59cfe118c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor=20da=20Silva?= Date: Tue, 31 Mar 2026 08:14:16 -0300 Subject: [PATCH 3/7] Add UE44 PVs --- pyqt-apps/siriushla/si_id_control/ue.py | 740 ++++++++++++++++++++---- 1 file changed, 621 insertions(+), 119 deletions(-) diff --git a/pyqt-apps/siriushla/si_id_control/ue.py b/pyqt-apps/siriushla/si_id_control/ue.py index 028be7b56..885987fe6 100644 --- a/pyqt-apps/siriushla/si_id_control/ue.py +++ b/pyqt-apps/siriushla/si_id_control/ue.py @@ -6,7 +6,7 @@ import qtawesome as qta from pydm.widgets import PyDMPushButton -from ..util import connect_newprocess, connect_window +from siriushla.util import connect_newprocess, connect_window from ..widgets import SiriusLedAlert, SiriusLabel, SiriusSpinbox, \ SiriusLedState, SiriusLineEdit, SiriusEnumComboBox from ..widgets.dialog import StatusDetailDialog @@ -19,150 +19,156 @@ class UEControlWindow(IDCommonControlWindow): """UE Control Window.""" OPERATION_PVS = { - "Pol": { - "Sel": "Pol-Sel", - "Mon": "Pol-Mon" - }, - "Power Off": "PowerOff-Mon", - "Kill Override": "KillOverride-Mon", - "Is Remote": "IsRemote-Mon", - "Device Status": "DeviceStatus-Mon", - "Start Parking": { - "Cmd": "StartParking-Cmd", - "icon": "fa5s.parking" - }, - "Sw": { - "Kill Override": "KillOverride-Mon" - }, - "Abort": { - "Cmd": "Abort-Cmd", - "icon": "fa5s.stop" - } + "Power Off": { + "StateMon": "PowerOff-Mon" + }, + "Kill Override": { + "StateMon": "KillOverride-Mon" + }, + "Is Remote": { + "StateMon": "IsRemote-Mon" + }, + "Device Status": { + "StateMon": "DeviceStatus-Mon" + }, } MAIN_CONTROL_PVS = { "KParam": { "SP": "KParam-SP", - "Mon": "KParam-Mon" - }, - "KParam Speed": { - "SP": "KParamVelo-SP", - "RB": "KParamVelo-RB", - "Mon": "KParamVelo-Mon" - }, - "KParam Min Pos": { - "Cte": "KParamMinPos-Cte" - }, - "KParam Max Pos": { - "Cte": "KParamMaxPos-Cte" - }, - "KParam Parked": { - "Cte": "KParamParked-Cte" + "Mon": "KParam-Mon", + "RB": "KParam-RB" }, + # "KParam Speed": { + # "SP": "KParamVelo-SP", + # "RB": "KParamVelo-RB", + # "Mon": "KParamVelo-Mon" + # }, + # "KParam Min Pos": { + # "Cte": "KParamMinPos-Cte" + # }, + # "KParam Max Pos": { + # "Cte": "KParamMaxPos-Cte" + # }, "Change KParam": { "Cmd": "KParamChange-Cmd", "icon": "fa5s.play" }, "PParam": { "SP": "PParam-SP", - "Mon": "PParam-Mon" - }, - "PParam Speed": { - "SP": "PParamVelo-SP", - "RB": "PParamVelo-RB", - "Mon": "PParamVelo-Mon" - }, - "PParam Min Pos": { - "Cte": "PParamMinPos-Cte" - }, - "PParam Max Pos": { - "Cte": "PParamMaxPos-Cte" - }, - "PParam Parked": { - "Cte": "PParamParked-Cte" + "Mon": "PParam-Mon", + "RB": "PParam-RB" }, + # "PParam Speed": { + # "SP": "PParamVelo-SP", + # "RB": "PParamVelo-RB", + # "Mon": "PParamVelo-Mon" + # }, + # "PParam Min Pos": { + # "Cte": "PParamMinPos-Cte" + # }, + # "PParam Max Pos": { + # "Cte": "PParamMaxPos-Cte" + # }, "Change PParam": { "Cmd": "PParamChange-Cmd", "icon": "fa5s.play" }, "CParam": { "SP": "CParam-SP", - "Mon": "CParam-Mon" + "Mon": "CParam-Mon", + "RB": "CParam-RB" }, - "CParam Speed": { - "SP": "CParamVelo-SP", - "RB": "CParamVelo-RB", - "Mon": "CParamVelo-Mon" + # "CParam Speed": { + # "SP": "CParamVelo-SP", + # "RB": "CParamVelo-RB", + # "Mon": "CParamVelo-Mon" + # }, + # "CParam Min Pos": { + # "Cte": "CParamMinPos-Cte" + # }, + # "CParam Max Pos": { + # "Cte": "CParamMaxPos-Cte" + # }, + # "CParam State NC": { + # "Mon": "CParamStateNC-Mon" + # }, + # "CParam Error NC": { + # "Mon": "CParamErrorNC-Mon" + # }, + "Change CParam": { + "Cmd": "CParamChange-Cmd", + "icon": "fa5s.play" }, - "CParam Min Pos": { - "Cte": "CParamMinPos-Cte" + "Offset": { + "SP": "Offset-SP", + "Mon": "Offset-Mon", + "RB": "Offset-RB" }, - "CParam Max Pos": { - "Cte": "CParamMaxPos-Cte" + "Offset Speed": { + "Mon": "OffsetVelo-Mon" }, - "CParam State NC": { - "Mon": "CParamStateNC-Mon" + # "Offset Min Pos": "OffsetMinPos-Cte", + # "Offset Max Pos": "OffsetMaxPos-Cte", + # "Offset NC State": "OffsetStateNC-Mon", + # "Offset NC Error": "OffsetErrorNC-Mon", + # "Servos Positions": { + # "Top Right": "TIPos-Mon", + # "Top Left": "TOPos-Mon", + # "Bottom Right": "BIPos-Mon", + # "Bottom Left": "BOPos-Mon" + # }, + # "Servos IO Status": { + # "Top Outside": "TOStatusIO-Mon", + # "Top Inside": "TIStatusIO-Mon", + # "Bot Outside": "BOStatusIO-Mon", + # "Bot Inside": "BIStatusIO-Mon" + # }, + # "Servos NC Status": { + # "Top Outside": "TOStateNC-Mon", + # "Top Inside": "TIStateNC-Mon", + # "Bot Outside": "BOStateNC-Mon", + # "Bot Inside": "BIStateNC-Mon" + # }, + # "Servos Error NC": { + # "Top Outside": "TOErrorNC-Mon", + # "Top Inside": "TIErrorNC-Mon", + # "Bot Outside": "BOErrorNC-Mon", + # "Bot Inside": "BIErrorNC-Mon" + # }, + # "KShift NC State": "KShiftStateNC-Mon", + # "KShift NC Error": "KShiftErrorNC-Mon", + # "PShift NC State": "PShiftStateNC-Mon", + # "PShift NC Error": "PShiftErrorNC-Mon", + # "CParam NC State": "CParamStateNC-Mon", + # "CParam NC Error": "CParamErrorNC-Mon", + "Speed Setpoint": { + "SP": "Velo-SP", + "Mon": "Velo-Mon", + "RB": "Velo-RB" }, - "CParam Error NC": { - "Mon": "CParamErrorNC-Mon" + "Acc. Setpoint": { + "SP": "Acc-SP", + "Mon": "Acc-Mon", + "RB": "Acc-RB" }, - "CParam Parked": { - "Cte": "CParamParked-Cte" + "Moving": { + "StateMon": "Moving-Mon" }, - "Change CParam": { - "Cmd": "CParamChange-Cmd", - "icon": "fa5s.play" + "Pol": { + "SP": "Pol-Sel", + "Mon": "Pol-Mon", + "RB": "Pol-RB" }, - "Offset": { - "SP": "Offset-SP", - "Mon": "Offset-Mon" - }, - "Offset Speed": "OffsetVelo-Mon", - "Offset Min Pos": "OffsetMinPos-Cte", - "Offset Max Pos": "OffsetMaxPos-Cte", - "Offset NC State": "OffsetStateNC-Mon", - "Offset NC Error": "OffsetErrorNC-Mon", - "Servos Positions": { - "Top Right": "TIPos-Mon", - "Top Left": "TOPos-Mon", - "Bottom Right": "BIPos-Mon", - "Bottom Left": "BOPos-Mon" - }, - "Servos IO Status": { - "Top Outside": "TOStatusIO-Mon", - "Top Inside": "TIStatusIO-Mon", - "Bot Outside": "BOStatusIO-Mon", - "Bot Inside": "BIStatusIO-Mon" - }, - "Servos NC Status": { - "Top Outside": "TOStateNC-Mon", - "Top Inside": "TIStateNC-Mon", - "Bot Outside": "BOStateNC-Mon", - "Bot Inside": "BIStateNC-Mon" - }, - "Servos Error NC": { - "Top Outside": "TOErrorNC-Mon", - "Top Inside": "TIErrorNC-Mon", - "Bot Outside": "BOErrorNC-Mon", - "Bot Inside": "BIErrorNC-Mon" - }, - "KShift NC State": "KShiftStateNC-Mon", - "KShift NC Error": "KShiftErrorNC-Mon", - "PShift NC State": "PShiftStateNC-Mon", - "PShift NC Error": "PShiftErrorNC-Mon", - "CParam NC State": "CParamStateNC-Mon", - "CParam NC Error": "CParamErrorNC-Mon", - "Max. Speed": { - "SP": "MaxVelo-SP", - "RB": "MaxVelo-RB" - }, - "Speed Setpoint": "Velo-SP", - "Acc. Setpoint": "Acc-SP", - "Period Length": "PeriodLength-Cte", - "Moving": "Moving-Mon" } SCANS_PVS = { + "Scan Mode": { + "SP": "ScanMode-Sel" + }, + "Scan Done": { + "StateMon": "ScanDone-Mon" + }, "Read Traj": { "Cmd": "ReadTraj-Cmd", "icon": "fa5s.play" @@ -171,18 +177,374 @@ class UEControlWindow(IDCommonControlWindow): "Cmd": "WriteTraj-Cmd", "icon": "mdi.keyboard-return" }, - "Fly First Pos": "FlyFirstPos-Mon", - "Scan Done": "ScanDone-Mon", - "Scan Mode": "ScanMode-Sel" + "Fly First Pos": { + "StateMon": "FlyFirstPos-Mon" + }, } + def _mainControlsWidget(self): + group = QGroupBox('Main Controls') + lay = QGridLayout() + lay.setContentsMargins(3, 3, 3, 3) + group.setLayout(lay) + + lay.addWidget( + QLabel('

SP

', self, alignment=Qt.AlignCenter), 0, 1) + lay.addWidget( + QLabel('

RB

', self, alignment=Qt.AlignCenter), 0, 2) + lay.addWidget( + QLabel('

Mon

', self, alignment=Qt.AlignCenter), 0, 3) + + row = 1 + 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 isinstance(pv_info, dict): + if "Cmd" in pv_info: + self._createCmdBtns(pv_info, lay, row) + elif "StateMon" in pv_info: + self._createLedState(pv_info, lay, row) + else: + self._createParam(pv_info, lay, row) + else: + raise NotImplementedError + row += 1 + + self._lb_start = QLabel( + 'Start Movement', self, alignment=Qt.AlignRight | Qt.AlignVCenter) + self._pb_start = PyDMPushButton( + self, label='', icon=qta.icon("fa5s.play")) + self._pb_start.channel = self.dev_pref.substitute(propty='DevCtrl-Cmd') + self._pb_start.pressValue = 120 + self._pb_start.setObjectName("Start") + self._pb_start.setStyleSheet( + '#Start{min-width:30px; max-width:30px; icon-size:25px;}') + + self._lb_abort = QLabel( + 'Abort', self, alignment=Qt.AlignRight | Qt.AlignVCenter) + self._pb_abort = PyDMPushButton( + self, label='', icon=qta.icon("fa5s.stop")) + self._pb_abort.channel = self.dev_pref.substitute(propty='Abort-Cmd') + self._pb_abort.pressValue = 1 + self._pb_abort.setObjectName("Stop") + self._pb_abort.setStyleSheet( + '#Stop{min-width:30px; max-width:30px; icon-size:25px;}') + + self._lb_reset = QLabel( + 'Reset', self, alignment=Qt.AlignRight | Qt.AlignVCenter) + self._pb_reset = PyDMPushButton( + self, label='', icon=qta.icon('fa5s.sync')) + self._pb_reset.channel = self.dev_pref.substitute(propty='DevCtrl-Cmd') + self._pb_reset.pressValue = 100 # Reset + self._pb_reset.setObjectName('Reset') + self._pb_reset.setStyleSheet( + '#Reset{min-width:30px; max-width:30px; icon-size:25px;}') + + lay.addWidget(self._lb_start, row, 0) + lay.addWidget(self._pb_start, row, 1) + lay.addWidget(self._lb_abort, row+1, 0) + lay.addWidget(self._pb_abort, row+1, 1) + lay.addWidget(self._lb_reset, row+2, 0) + lay.addWidget(self._pb_reset, row+2, 1) + + return group + + def _statusWidget(self): + gbox = QGroupBox("Status") + gbox.setSizePolicy( + QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + lay = QGridLayout(gbox) + lay.setVerticalSpacing(15) + row = 0 + + self._pb_dtls = QPushButton( + "Servo Motors and General Status Details", self) + self._pb_dtls.setIcon(qta.icon('fa5s.list-ul')) + connect_window( + self._pb_dtls, UEDetails, self, + prefix=self._prefix, device=self._device) + lay.addWidget(self._pb_dtls, row, 0, 1, 2) + row += 1 + + axis_status_labels = { + 'TOStatusIO-Mon': [ + 'Min SW limit reached', + 'Max SW limit reached', + 'Min limit switch activated', + 'Max limit switch activated', + 'Min kill switch activated', + 'Max kill switch activated', + 'Pneumatic brake released', + 'Pneumatic brake locked', + 'Pneumatic brake Worn', + 'Linear encoder fault', + 'Driver channel fault' + ], + 'TIStatusIO-Mon': [ + 'Min SW limit reached', + 'Max SW limit reached', + 'Min limit switch activated', + 'Max limit switch activated', + 'Min kill switch activated', + 'Max kill switch activated', + 'Pneumatic brake released', + 'Pneumatic brake locked', + 'Pneumatic brake Worn', + 'Linear encoder fault', + 'Driver channel fault' + ], + 'BOStatusIO-Mon': [ + 'Min SW limit reached', + 'Max SW limit reached', + 'Min limit switch activated', + 'Max limit switch activated', + 'Min kill switch activated', + 'Max kill switch activated', + 'Pneumatic brake released', + 'Pneumatic brake locked', + 'Pneumatic brake Worn', + 'Linear encoder fault', + 'Driver channel fault' + ], + 'BIStatusIO-Mon': [ + 'Min SW limit reached', + 'Max SW limit reached', + 'Min limit switch activated', + 'Max limit switch activated', + 'Min kill switch activated', + 'Max kill switch activated', + 'Pneumatic brake released', + 'Pneumatic brake locked', + 'Pneumatic brake Worn', + 'Linear encoder fault', + 'Driver channel fault' + ] + } + + servos_lay = QGridLayout() + servos_row = 0 + + for status, labels in axis_status_labels.items(): + pvname = self.dev_pref.substitute(propty=status) + servo_lbl = status.split('-')[0] + lbl = QLabel( + servo_lbl, self, + alignment=Qt.AlignRight | Qt.AlignVCenter) + read_sts = SiriusLedAlert(self, pvname) + pbt = QPushButton('', self) + pbt.setIcon(qta.icon('fa5s.ellipsis-v')) + pbt.setObjectName('sts') + pbt.setStyleSheet( + '#sts{min-width:18px; max-width:18px; icon-size:20px;}') + connect_window( + pbt, StatusDetailDialog, pvname=pvname, parent=self, + labels=labels, section="ID", title=f'{servo_lbl} Detailed Status') + servos_lay.addWidget(lbl, servos_row, 0) + servos_lay.addWidget(read_sts, servos_row, 1, alignment=Qt.AlignRight) + servos_lay.addWidget(pbt, servos_row, 2, alignment=Qt.AlignLeft) + servos_row += 1 + lay.addLayout(servos_lay, 1, 0, 1, 2, alignment=Qt.AlignHCenter) + row += 1 + + status2labels = { + 'DeviceStatus-Mon': [ + 'Error', + 'Power Off', + 'Software limit enabled', + 'Hardware limit enabled', + 'Kill switches enabled', + 'Control enabled', + 'Moving', + 'Emergency stop button', + 'One or more SW limit reached', + 'One or more HW limit reached', + 'One or more kill SW reached' + ] + } + + for dev_sts, label in status2labels.items(): + pvname = self.dev_pref.substitute(propty=dev_sts) + dev_lay = QGridLayout() + dev_title = QLabel(f'

{dev_sts}

', + self, alignment=Qt.AlignCenter) + dev_lay.addWidget(dev_title, 0, 0, alignment=Qt.AlignCenter) + for idx, lbl in enumerate(label): + sts_lbl = QLabel(lbl) + irow = idx + 1 + read_sts = SiriusLedAlert(self, pvname, bit=idx) + if lbl == "Error": + read_sts.onColor = SiriusLedState.Red + else: + read_sts.onColor = SiriusLedState.Yellow + dev_lay.addWidget(read_sts, irow, 0) + dev_lay.addWidget(sts_lbl, irow, 1) + lay.addLayout(dev_lay, row, 0) + + return gbox + + def _ctrlModeWidget(self): + gbox = QGroupBox("Operation Status") + gbox.setSizePolicy( + QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + lay = QGridLayout(gbox) + lay.setVerticalSpacing(15) + row = 0 + + for title, pv_info in self.OPERATION_PVS.items(): + label = QLabel( + title, self, alignment=Qt.AlignRight | Qt.AlignVCenter) + label.setFixedWidth(150) + lay.addWidget(label, row, 0) + + if isinstance(pv_info, dict): + if "Cmd" in pv_info: + self._createCmdBtns(pv_info, lay, row) + elif "StateMon" in pv_info: + self._createLedState(pv_info, lay, row) + else: + self._createParam(pv_info, lay, row) + else: + raise NotImplementedError + row += 1 + + return gbox + + def _auxCommandsWidget(self): + # scan controls + scangroup = QGroupBox('Scan Controls') + scanlay = QGridLayout() + scanlay.setContentsMargins(3, 3, 3, 3) + scangroup.setLayout(scanlay) + + row = 0 + for title, pv_info in self.SCANS_PVS.items(): + label = QLabel( + title, self, alignment=Qt.AlignRight | Qt.AlignVCenter) + label.setFixedWidth(150) + scanlay.addWidget(label, row, 0) + + if isinstance(pv_info, dict): + if "Cmd" in pv_info: + self._createCmdBtns(pv_info, scanlay, row) + elif "StateMon" in pv_info: + self._createLedState(pv_info, scanlay, row) + else: + self._createParam(pv_info, scanlay, row) + else: + raise NotImplementedError + row += 1 + + # auxiliary parameters + auxgbox = QGroupBox('Auxiliary Parameters', self) + + self._speed_lim = QLabel('Max Speed [mm/s]', self) + self._spin_box_sl = SiriusSpinbox( + self, self.dev_pref.substitute(propty="MaxVelo-SP")) + self._spin_box_sl.setStyleSheet('max-width:4.5em;') + self._lb_spin_box_sl = SiriusLabel( + self, self.dev_pref.substitute(propty="MaxVelo-RB")) + + self._ld_periodlen = QLabel('Period Length', self) + self._lb_periodlen = SiriusLabel( + self, self.dev_pref.substitute(propty='PeriodLength-Cte')) + + self._ld_kpark = QLabel('KParam Park Pos.', self) + self._lb_kpark = SiriusLabel( + self, self.dev_pref.substitute(propty='KParamParked-Cte')) + + self._ld_ppark = QLabel('PParam Park Pos.', self) + self._lb_ppark = SiriusLabel( + self, self.dev_pref.substitute(propty='PParamParked-Cte')) + + self._ld_cpark = QLabel('CParam Park Pos.', self) + self._lb_cpark = SiriusLabel( + self, self.dev_pref.substitute(propty='CParamParked-Cte')) + + self._lb_park = QLabel('Start Parking', self) + self._pb_park = PyDMPushButton( + self, label='', icon=qta.icon("fa5s.parking")) + self._pb_park.channel = self.dev_pref.substitute(propty="StartParking-Cmd") + self._pb_park.pressValue = 1 + self._pb_park.setIconSize(QSize(20, 20)) + self._pb_park.setMaximumWidth(25) + self._pb_park.setStyleSheet( + '#Start{min-width:30px; max-width:30px; icon-size:25px;}') + + auxlay = QGridLayout(auxgbox) + auxlay.addWidget(self._speed_lim, 0, 0) + auxlay.addWidget(self._spin_box_sl, 0, 1) + auxlay.addWidget(self._lb_spin_box_sl, 0, 2) + auxlay.addWidget(self._ld_periodlen, 1, 0) + auxlay.addWidget(self._lb_periodlen, 1, 1) + auxlay.addWidget(self._ld_kpark, 2, 0) + auxlay.addWidget(self._lb_kpark, 2, 1) + auxlay.addWidget(self._ld_ppark, 3, 0) + auxlay.addWidget(self._lb_ppark, 3, 1) + auxlay.addWidget(self._ld_cpark, 4, 0) + auxlay.addWidget(self._lb_cpark, 4, 1) + auxlay.addWidget(self._lb_park, 5, 0) + auxlay.addWidget(self._pb_park, 5, 1) + + auxgbox.setStyleSheet( + '.QLabel{qproperty-alignment: "AlignRight | AlignVCenter";}') + + group = QWidget() + lay = QGridLayout(group) + lay.setContentsMargins(0, 0, 0, 0) + lay.addWidget(scangroup, 0, 0) + lay.addWidget(auxgbox, 1, 0) + return group + + def _ffSettingsWidget(self): + but = QPushButton('Feedforward Settings', self) + connect_newprocess( + but, ['sirius-hla-si-ap-idff.py', self._device]) + return but + + def _createCmdBtns(self, pv_info, lay, row): + btn = PyDMPushButton(self, label='', icon=qta.icon(pv_info["icon"])) + btn.channel = self.dev_pref.substitute(propty=pv_info["Cmd"]) + 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 _createLedState(self, pv_info, lay, row): + pvname = self.dev_pref.substitute(propty=pv_info["StateMon"]) + led = SiriusLedState(self, init_channel=pvname) + lay.addWidget(led, row, 1, alignment=Qt.AlignLeft) + + def _createParam(self, pv_info, lay, row): + if "SP" in pv_info: + pvname = self.dev_pref.substitute(propty=pv_info["SP"]) + widtype = SiriusEnumComboBox if pvname.endswith('Sel') \ + else SiriusLineEdit + cb = widtype(self, init_channel=pvname) + lay.addWidget(cb, row, 1, 1, 1) + + for col, key in {2: "RB", 3: "Mon"}.items(): + 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) + class UESummaryBase(IDCommonSummaryBase): """UE Summary Base Widget.""" MODEL_WIDTHS = ( + ('Device Status', 4), ('KParam', 6), - ('KParam Speed', 6), + ('Speed', 6), ('Start', 4), ('Stop', 4), ) @@ -194,3 +556,143 @@ class UESummaryHeader(IDCommonSummaryHeader, UESummaryBase): class UESummaryWidget(IDCommonSummaryWidget, UESummaryBase): """UE Summary Widget.""" + + def _get_widgets(self, prop): + wids, orientation = super()._get_widgets(prop) + if prop == 'Device Status': + led = SiriusLedAlert( + self, self.dev_pref.substitute(propty='DeviceStatus-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 == 'Speed': + spb = SiriusLineEdit( + self, self.dev_pref.substitute(propty='Velo-SP')) + wids.append(spb) + lbl = SiriusLabel( + self, self.dev_pref.substitute(propty='Velo-RB')) + wids.append(lbl) + elif prop == 'Start': + btn = PyDMPushButton(self, label='', icon=qta.icon('fa5s.play')) + btn.channel = self.dev_pref.substitute(propty='DevCtrl-Cmd') + btn.pressValue = 120 + 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 + + +class UEDetails(IDCommonDialog): + """UE Drive Details""" + + def __init__(self, parent=None, prefix='', device=''): + """Init.""" + super().__init__( + parent, prefix, device, title=device+' Servo Motors and General Status Details') + + def _setupUi(self): + ld_pos = QLabel('

Pos.

', self) + ld_posmin = QLabel('

Min Pos.

', self) + ld_posmax = QLabel('

Max Pos.

', self) + ld_ncstate = QLabel('

NC State

', self) + ld_ncerror = QLabel('

NC Error', self) + ld_status = QLabel('

StatusIO

', self) + ld_temp = QLabel('

Temperature

', self) + + gbox = QGroupBox('Status Details', self) + glay = QGridLayout(gbox) + glay.addWidget(ld_pos, 1, 0) + glay.addWidget(ld_posmin, 2, 0) + glay.addWidget(ld_posmax, 3, 0) + glay.addWidget(ld_ncstate, 4, 0) + glay.addWidget(ld_ncerror, 5, 0) + glay.addWidget(ld_status, 6, 0) + glay.addWidget(ld_temp, 7, 0) + + details = [ + "KParam", + "PParam", + "CParam", + "Offset", + "KShift", + "PShift", + "TI", + "TO", + "BI", + "BO", + ] + + for idx, title in enumerate(details): + col = idx + 1 + posname = 'Pos' if title not in \ + ['KParam', 'PParam', 'CParam', 'Offset'] else '' + ld_dtl = QLabel('

'+title+'

', self) + + if title not in ['KShift', 'PShift']: + pvname = self.dev_pref.substitute( + propty=f'{title}{posname}-Mon') + lb_pos = SiriusLabel(self, pvname) + else: + lb_pos = QLabel("-", self) + + if title in ['KParam', 'PParam', 'CParam', 'Offset']: + pvname = self.dev_pref.substitute( + propty=f'{title}MinPos-Cte') + lb_posmin = SiriusLabel(self, pvname) + pvname = self.dev_pref.substitute( + propty=f'{title}MaxPos-Cte') + lb_posmax = SiriusLabel(self, pvname) + else: + lb_posmin = QLabel("-", self) + lb_posmax = QLabel("-", self) + + if title not in ['KParam', 'PParam']: + pvname = self.dev_pref.substitute( + propty=f'{title}StateNC-Mon') + lb_ncstate = SiriusLabel(self, pvname) + pvname = self.dev_pref.substitute( + propty=f'{title}ErrorNC-Mon') + lb_ncerror = SiriusLabel(self, pvname) + else: + lb_ncstate = QLabel("-", self) + lb_ncerror = QLabel("-", self) + + if title in ["TI", "TO", "BI", "BO"]: + pvname = self.dev_pref.substitute( + propty=f'{title}StatusIO-Mon') + lb_status = SiriusLabel(self, pvname) + pvname = self.dev_pref.substitute( + propty=f'{title}Temp-Mon') + lb_temp = SiriusLabel(self, pvname) + else: + lb_status = QLabel("-", self) + lb_temp = QLabel("-", self) + + glay.addWidget(ld_dtl, 0, col) + glay.addWidget(lb_pos, 1, col) + glay.addWidget(lb_posmin, 2, col) + glay.addWidget(lb_posmax, 3, col) + glay.addWidget(lb_ncstate, 4, col) + glay.addWidget(lb_ncerror, 5, col) + glay.addWidget(lb_status, 6, col) + glay.addWidget(lb_temp, 7, col) + + gbox.setStyleSheet( + 'QLabel{qproperty-alignment: AlignCenter; max-width: 12em;}') + lay = QHBoxLayout(self) + lay.addWidget(gbox) From 5ae677ace1c485b5bad639bf9b7db56581b02052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor=20da=20Silva?= Date: Thu, 9 Apr 2026 08:17:14 -0300 Subject: [PATCH 4/7] ID.ENH: add IPE beamline UE44 --- pyqt-apps/siriushla/si_id_control/ue.py | 80 ++----------------------- 1 file changed, 4 insertions(+), 76 deletions(-) diff --git a/pyqt-apps/siriushla/si_id_control/ue.py b/pyqt-apps/siriushla/si_id_control/ue.py index 885987fe6..a471df4a3 100644 --- a/pyqt-apps/siriushla/si_id_control/ue.py +++ b/pyqt-apps/siriushla/si_id_control/ue.py @@ -39,17 +39,6 @@ class UEControlWindow(IDCommonControlWindow): "Mon": "KParam-Mon", "RB": "KParam-RB" }, - # "KParam Speed": { - # "SP": "KParamVelo-SP", - # "RB": "KParamVelo-RB", - # "Mon": "KParamVelo-Mon" - # }, - # "KParam Min Pos": { - # "Cte": "KParamMinPos-Cte" - # }, - # "KParam Max Pos": { - # "Cte": "KParamMaxPos-Cte" - # }, "Change KParam": { "Cmd": "KParamChange-Cmd", "icon": "fa5s.play" @@ -59,17 +48,6 @@ class UEControlWindow(IDCommonControlWindow): "Mon": "PParam-Mon", "RB": "PParam-RB" }, - # "PParam Speed": { - # "SP": "PParamVelo-SP", - # "RB": "PParamVelo-RB", - # "Mon": "PParamVelo-Mon" - # }, - # "PParam Min Pos": { - # "Cte": "PParamMinPos-Cte" - # }, - # "PParam Max Pos": { - # "Cte": "PParamMaxPos-Cte" - # }, "Change PParam": { "Cmd": "PParamChange-Cmd", "icon": "fa5s.play" @@ -79,23 +57,6 @@ class UEControlWindow(IDCommonControlWindow): "Mon": "CParam-Mon", "RB": "CParam-RB" }, - # "CParam Speed": { - # "SP": "CParamVelo-SP", - # "RB": "CParamVelo-RB", - # "Mon": "CParamVelo-Mon" - # }, - # "CParam Min Pos": { - # "Cte": "CParamMinPos-Cte" - # }, - # "CParam Max Pos": { - # "Cte": "CParamMaxPos-Cte" - # }, - # "CParam State NC": { - # "Mon": "CParamStateNC-Mon" - # }, - # "CParam Error NC": { - # "Mon": "CParamErrorNC-Mon" - # }, "Change CParam": { "Cmd": "CParamChange-Cmd", "icon": "fa5s.play" @@ -108,40 +69,6 @@ class UEControlWindow(IDCommonControlWindow): "Offset Speed": { "Mon": "OffsetVelo-Mon" }, - # "Offset Min Pos": "OffsetMinPos-Cte", - # "Offset Max Pos": "OffsetMaxPos-Cte", - # "Offset NC State": "OffsetStateNC-Mon", - # "Offset NC Error": "OffsetErrorNC-Mon", - # "Servos Positions": { - # "Top Right": "TIPos-Mon", - # "Top Left": "TOPos-Mon", - # "Bottom Right": "BIPos-Mon", - # "Bottom Left": "BOPos-Mon" - # }, - # "Servos IO Status": { - # "Top Outside": "TOStatusIO-Mon", - # "Top Inside": "TIStatusIO-Mon", - # "Bot Outside": "BOStatusIO-Mon", - # "Bot Inside": "BIStatusIO-Mon" - # }, - # "Servos NC Status": { - # "Top Outside": "TOStateNC-Mon", - # "Top Inside": "TIStateNC-Mon", - # "Bot Outside": "BOStateNC-Mon", - # "Bot Inside": "BIStateNC-Mon" - # }, - # "Servos Error NC": { - # "Top Outside": "TOErrorNC-Mon", - # "Top Inside": "TIErrorNC-Mon", - # "Bot Outside": "BOErrorNC-Mon", - # "Bot Inside": "BIErrorNC-Mon" - # }, - # "KShift NC State": "KShiftStateNC-Mon", - # "KShift NC Error": "KShiftErrorNC-Mon", - # "PShift NC State": "PShiftStateNC-Mon", - # "PShift NC Error": "PShiftErrorNC-Mon", - # "CParam NC State": "CParamStateNC-Mon", - # "CParam NC Error": "CParamErrorNC-Mon", "Speed Setpoint": { "SP": "Velo-SP", "Mon": "Velo-Mon", @@ -152,14 +79,14 @@ class UEControlWindow(IDCommonControlWindow): "Mon": "Acc-Mon", "RB": "Acc-RB" }, - "Moving": { - "StateMon": "Moving-Mon" - }, "Pol": { "SP": "Pol-Sel", "Mon": "Pol-Mon", "RB": "Pol-RB" }, + "Moving": { + "StateMon": "Moving-Mon" + } } SCANS_PVS = { @@ -496,6 +423,7 @@ def _auxCommandsWidget(self): lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(scangroup, 0, 0) lay.addWidget(auxgbox, 1, 0) + return group def _ffSettingsWidget(self): From c27327c8c9e944d27dc906e484873800b902eec1 Mon Sep 17 00:00:00 2001 From: Ana Clara Oliveira Date: Fri, 10 Apr 2026 18:53:55 -0300 Subject: [PATCH 5/7] PS.ENH: update Control, Monitor and Commands windows to include LCV power supplies --- pyqt-apps/siriushla/as_ap_monitor/util.py | 2 +- pyqt-apps/siriushla/as_ps_commands/main.py | 2 +- .../siriushla/as_ps_control/SummaryWidgets.py | 2 +- .../control_widget/BasePSControlWidget.py | 14 +++++++++-- .../control_widget/ControlWidgetFactory.py | 7 ++++-- .../IDFFCorrectorControlWidget.py | 24 +++++++++++-------- .../SlowCorrectorControlWidget.py | 2 +- pyqt-apps/siriushla/as_ps_diag/monitor.py | 2 +- pyqt-apps/siriushla/as_ps_diag/util.py | 20 ++++++++-------- 9 files changed, 46 insertions(+), 29 deletions(-) diff --git a/pyqt-apps/siriushla/as_ap_monitor/util.py b/pyqt-apps/siriushla/as_ap_monitor/util.py index 4d75de4d8..a9e7a666f 100644 --- a/pyqt-apps/siriushla/as_ap_monitor/util.py +++ b/pyqt-apps/siriushla/as_ap_monitor/util.py @@ -28,5 +28,5 @@ def get_sec2dev_laypos(sec, label): SEC2LABEL2SECPOS['BO'].update({ 'RF': (2, 1, 1, 1)}) SEC2LABEL2SECPOS['SI'].update({ - 'RF': (0, 6, 1, 1)}) + 'RF': (0, 8, 1, 1)}) return SEC2LABEL2SECPOS[sec][label] diff --git a/pyqt-apps/siriushla/as_ps_commands/main.py b/pyqt-apps/siriushla/as_ps_commands/main.py index cd4134975..c6d9666e9 100644 --- a/pyqt-apps/siriushla/as_ps_commands/main.py +++ b/pyqt-apps/siriushla/as_ps_commands/main.py @@ -1178,7 +1178,7 @@ def _get_ps_tree_names(self): # add SI Corrs psnames.extend(PSSearch.get_psnames( {'sec': 'SI', 'sub': '[0-2][0-9].*', 'dis': 'PS', - 'dev': '(LCH|CH|CV|CC)'})) + 'dev': '(LCH|LCV|CH|CV|CC)'})) # add SI QTrims psnames.extend(PSSearch.get_psnames( {'sec': 'SI', 'sub': '[0-2][0-9].*', 'dis': 'PS', diff --git a/pyqt-apps/siriushla/as_ps_control/SummaryWidgets.py b/pyqt-apps/siriushla/as_ps_control/SummaryWidgets.py index a43f92528..0a514f295 100644 --- a/pyqt-apps/siriushla/as_ps_control/SummaryWidgets.py +++ b/pyqt-apps/siriushla/as_ps_control/SummaryWidgets.py @@ -21,7 +21,7 @@ Quadrupole = re.compile("^.*:PS-Q.*$") QuadrupoleSkew = re.compile("^.*:PS-QS.*$") Sextupole = re.compile("^.*:PS-S.*$") -Corrector = re.compile("^.*:PS-(LCH|CH|CV|CC|FCH|FCV).*$") +Corrector = re.compile("^.*:PS-(LCH|LCV|CH|CV|CC|FCH|FCV).*$") FastCorrector = re.compile("^SI-.*:PS-(FCH|FCV).*$") IsPulsed = re.compile("^.*:PU-.*$") IsDCLink = re.compile("^.*:PS-DCLink.*$") diff --git a/pyqt-apps/siriushla/as_ps_control/control_widget/BasePSControlWidget.py b/pyqt-apps/siriushla/as_ps_control/control_widget/BasePSControlWidget.py index d32a66507..414a7e7cf 100644 --- a/pyqt-apps/siriushla/as_ps_control/control_widget/BasePSControlWidget.py +++ b/pyqt-apps/siriushla/as_ps_control/control_widget/BasePSControlWidget.py @@ -237,7 +237,10 @@ class BasePSControlWidget(QWidget): HORIZONTAL = 0 VERTICAL = 1 - def __init__(self, subsection=None, orientation=0, parent=None): + def __init__( + self, subsection=None, idffsubgroup=None, orientation=0, + parent=None, + ): """Class constructor. Parameters: @@ -246,11 +249,18 @@ def __init__(self, subsection=None, orientation=0, parent=None): orientation Default to HORIZONTAL. Define how the different groups (defined in subclasses) will be laid out. + idffsubgroup + Default to None. To be used in filters defined in subclass. """ super(BasePSControlWidget, self).__init__(parent) self._orientation = orientation self._subsection = subsection - self._dev_list = PSSearch.get_psnames(self._getFilter(subsection)) + self._idffsubgroup = idffsubgroup + filtargs = [subsection] + if idffsubgroup: + filtargs.append(idffsubgroup) + filt = self._getFilter(*filtargs) + self._dev_list = PSSearch.get_psnames(filt) dev0 = PVName(self._dev_list[0]) if dev0.sec == 'LI': if dev0.dev == 'Slnd': diff --git a/pyqt-apps/siriushla/as_ps_control/control_widget/ControlWidgetFactory.py b/pyqt-apps/siriushla/as_ps_control/control_widget/ControlWidgetFactory.py index 699de1dcc..13bb72906 100644 --- a/pyqt-apps/siriushla/as_ps_control/control_widget/ControlWidgetFactory.py +++ b/pyqt-apps/siriushla/as_ps_control/control_widget/ControlWidgetFactory.py @@ -32,7 +32,10 @@ def _device_not_found(section, device): raise AttributeError("{} not defined for {}".format(device, section)) @staticmethod - def factory(parent, section, device, subsection=None, orientation=0): + def factory( + parent, section, device, subsection=None, orientation=0, + idffsubgroup=None + ): if section == "LI": if device == "spectrometer": return LISpectControlWidget( @@ -122,7 +125,7 @@ def factory(parent, section, device, subsection=None, orientation=0): elif device == "corrector-idff": return IDFFCorrectorControlWidget( subsection=subsection, orientation=orientation, - parent=parent) + parent=parent, idffsubgroup=idffsubgroup) elif device == "skew-quadrupole": return SISkewQuadControlWidget( subsection=subsection, orientation=orientation, diff --git a/pyqt-apps/siriushla/as_ps_control/control_widget/IDFFCorrectorControlWidget.py b/pyqt-apps/siriushla/as_ps_control/control_widget/IDFFCorrectorControlWidget.py index 00b31e13d..8792dbf92 100644 --- a/pyqt-apps/siriushla/as_ps_control/control_widget/IDFFCorrectorControlWidget.py +++ b/pyqt-apps/siriushla/as_ps_control/control_widget/IDFFCorrectorControlWidget.py @@ -5,15 +5,19 @@ class IDFFCorrectorControlWidget(BasePSControlWidget): """IDFF corrector control widget.""" - def _getFilter(self, subsection=None): - subsectype = subsection[-1] # 'A' | 'B' | 'P' - qd = "QD" + subsectype # for A type sections - qf = "QF" + subsectype - qd1 = qd + "1" # for B and P type sections - qd2 = qd + "2" # for B and P type sections - trims = qd + "|" + qd1 + "|" + qf + "|" + qd2 - dev = "(CH|CV|QS|" + trims + "|LCH|CC1|CC2)" - return {"sec": "SI", "sub": subsection, "dev": dev} + def _getFilter(self, subsection=None, idffsubgroup=None): + if idffsubgroup: + filt = {"sec": "SI", "sub": subsection, "dev": idffsubgroup} + else: + subsectype = subsection[-1] # 'A' | 'B' | 'P' + qd = "QD" + subsectype # for A type sections + qf = "QF" + subsectype + qd1 = qd + "1" # for B and P type sections + qd2 = qd + "2" # for B and P type sections + trims = qd + "|" + qd1 + "|" + qf + "|" + qd2 + dev = "(CH|CV|QS|" + trims + "|LCH|LCV|CC1|CC2)" + filt = {"sec": "SI", "sub": subsection, "dev": dev} + return filt def _hasTrimButton(self): return False @@ -27,6 +31,6 @@ def _getGroups(self): ('Vertical Corretors', '-CV'), ('Skew Quadrupole', '-QS'), ('Trim Quadrupoles ', '-(QF[ABP]|QDA|QDB[12]|QDP[12])'), - ('Long Coils', '-LCH'), + ('Long Coils', '-LC'), ('Corrector Coils', '-CC') ] diff --git a/pyqt-apps/siriushla/as_ps_control/control_widget/SlowCorrectorControlWidget.py b/pyqt-apps/siriushla/as_ps_control/control_widget/SlowCorrectorControlWidget.py index 4c533e190..3f7b91f49 100644 --- a/pyqt-apps/siriushla/as_ps_control/control_widget/SlowCorrectorControlWidget.py +++ b/pyqt-apps/siriushla/as_ps_control/control_widget/SlowCorrectorControlWidget.py @@ -6,7 +6,7 @@ class SISlowCorrectorControlWidget(BasePSControlWidget): """Storage ring slow correctors.""" def _getFilter(self, subsection=None): - filt = {"sec": "SI", "sub": "\w{4}", "dev": "(CH|CV|CC|LCH).*"} + filt = {"sec": "SI", "sub": "\w{4}", "dev": "(CH|CV|CC|LCH|LCV).*"} if subsection: filt.update({'sub': subsection}) return filt diff --git a/pyqt-apps/siriushla/as_ps_diag/monitor.py b/pyqt-apps/siriushla/as_ps_diag/monitor.py index ec72a9d8a..22d224b5e 100644 --- a/pyqt-apps/siriushla/as_ps_diag/monitor.py +++ b/pyqt-apps/siriushla/as_ps_diag/monitor.py @@ -111,7 +111,7 @@ def update_gridpos(row, col, col_count, offset=0): aux = devices.pop(-1) devices.insert(0, aux) elif sec == 'SI': - if label not in ['FCH', 'FCV', 'ID-CH/CV/QS/CC']: + if label not in ['FCH', 'FCV', 'ID-FF Correctors']: aux = devices.pop(-1) devices.insert(0, aux) if label == 'Trims': diff --git a/pyqt-apps/siriushla/as_ps_diag/util.py b/pyqt-apps/siriushla/as_ps_diag/util.py index 3f5237d19..55f4b635f 100644 --- a/pyqt-apps/siriushla/as_ps_diag/util.py +++ b/pyqt-apps/siriushla/as_ps_diag/util.py @@ -26,8 +26,8 @@ 'S': {'sec': 'SI', 'sub': '.*', 'dev': 'S.*'}, 'CV': {'sec': 'SI', 'sub': '.*(M|C).*', 'dev': 'CV.*'}, 'CH': {'sec': 'SI', 'sub': '.*(M|C).*', 'dev': 'CH.*'}, - 'ID-CH/CV/QS/CC': {'sec': 'SI', 'sub': '.*S(A|B|P)', - 'dev': '(LCH|CH|CV|CC|QS)'}, + 'ID-FF Correctors': {'sec': 'SI', 'sub': '.*S(A|B|P)', + 'dev': '(LCH|LCV|CH|CV|CC|QS)'}, 'FFCH/FFCV': {'sec': 'SI', 'dev': 'FFC.*'}, 'Trims': {'sec': 'SI', 'sub': '[0-2][0-9].*', 'dev': 'Q(F|D|[1-4]).*'}, @@ -89,10 +89,10 @@ def get_label2devices(sec): 'SI': { 'B': (0, 1, 1, 1), 'PM': (0, 2, 1, 1), - 'FFCH/FFCV': (0, 3, 1, 1), - 'Q': (0, 4, 1, 1), - 'S': (0, 5, 1, 1), - 'ID-CH/CV/QS/CC': (0, 7, 1, 2), + 'Q': (0, 3, 1, 1), + 'S': (0, 4, 1, 1), + 'ID-FF Correctors': (0, 5, 1, 2), + 'FFCH/FFCV': (0, 7, 1, 1), 'QS': (1, 1, 1, 2), 'CH': (1, 3, 1, 1), 'CV': (1, 4, 1, 1), @@ -117,15 +117,15 @@ def get_col2dev_count(sec, label): if 'Trims' in label: return 14 if 'FFC' in label: - return 4 + return 2 if 'ID' in label: - return 8 + return 13 if 'FC' in label: return 4 if label == 'S': - return 11 + return 7 if label == 'Q': - return 10 if sec != 'SI' else 6 + return 10 if sec != 'SI' else 5 if label == 'Slnd': return 21 return 10 From 141536898ed84c135313253cafd0f0b0fa8834df Mon Sep 17 00:00:00 2001 From: Ana Clara Oliveira Date: Fri, 10 Apr 2026 19:14:54 -0300 Subject: [PATCH 6/7] ID.ENH: add access to feedforwad controls --- pyqt-apps/scripts/sirius-hla-si-ap-idff.py | 7 +++- pyqt-apps/siriushla/si_ap_idff/main.py | 43 ++++++++++++++++------ pyqt-apps/siriushla/si_id_control/ue.py | 37 ++++++++++++++++--- 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/pyqt-apps/scripts/sirius-hla-si-ap-idff.py b/pyqt-apps/scripts/sirius-hla-si-ap-idff.py index 00cb5e5d5..8c727452c 100644 --- a/pyqt-apps/scripts/sirius-hla-si-ap-idff.py +++ b/pyqt-apps/scripts/sirius-hla-si-ap-idff.py @@ -15,9 +15,14 @@ '-p', "--prefix", type=str, default=VACA_PREFIX, help="Define the prefix for the PVs in the window.") parser.add_argument("idname", type=str, help="ID name.") +parser.add_argument( + '-g', "--idffgroup", type=str, default=None, + help="Power supplies subgroup controlled by IDFF instance.") args = parser.parse_args() app = SiriusApplication() app.open_window( - IDFFWindow, parent=None, prefix=args.prefix, idname=args.idname) + IDFFWindow, parent=None, prefix=args.prefix, + idname=args.idname, idffgroup=args.idffgroup, +) _sys.exit(app.exec_()) diff --git a/pyqt-apps/siriushla/si_ap_idff/main.py b/pyqt-apps/siriushla/si_ap_idff/main.py index b72e217fd..adb7de82c 100644 --- a/pyqt-apps/siriushla/si_ap_idff/main.py +++ b/pyqt-apps/siriushla/si_ap_idff/main.py @@ -39,25 +39,34 @@ class IDFFWindow(SiriusMainWindow): # so as to provide the same PVs as the low level ioc (particularly # "IDPos-Mon") - def __init__(self, parent=None, prefix='', idname=''): + def __init__(self, parent=None, prefix='', idname='', idffgroup=''): """Initialize.""" super().__init__(parent) self.prefix = prefix or _VACA_PREFIX self.idname = _PVName(idname) - self._is_llidff = self.idname.dev.startswith(("IVU", "SIMUL", "VPU")) - if self._is_llidff and self.idname.dev.startswith(("IVU", "SIMUL")): + self._is_llidff = self.idname.dev.startswith( + ("IVU", "SIMUL", "VPU", "UE44", ) + ) + if self.idname.dev.startswith(("IVU", "SIMUL")): self._idffname = _PVName(f"SI-{self.idname.sub}:BS-IDFF-CHCV") - elif self._is_llidff and self.idname.dev.startswith("VPU"): + elif self.idname.dev.startswith("VPU"): self._idffname = _PVName(f"SI-{self.idname.sub}:BS-IDFF-CC") + elif self.idname.dev.startswith("UE44"): + if not idffgroup: + raise ValueError('idffgroup input need to be defined for UE44') + self._idffname = _PVName(f"SI-{self.idname.sub}:BS-IDFF-{idffgroup}") else: self._idffname = _PVName(f"SI-{self.idname.sub}:AP-IDFF") - self.dev_pref = _PVName(self._idffname + ':') - # self._idffname = IDFFConst(idname).idffname + self.dev_pref = _PVName(self._idffname) self._idffdev = self._create_idffdev() self._idffdata = IDSearch.conv_idname_2_idff(self.idname) - self.device = _PVName(self._idffname) + dis = "BS" if self._is_llidff else "AP" + self._idffnickname = _PVName(f"SI-{self.idname.sub}:{dis}-IDFF") + self._idffgroup = idffgroup + if idffgroup: + self._idffnickname = self._idffnickname.substitute(idx=idffgroup) self.setObjectName('IDApp') - self.setWindowTitle(self.device) + self.setWindowTitle(self._idffnickname) self.setWindowIcon(get_idff_icon()) self._setupUi() self.setFocusPolicy(Qt.StrongFocus) @@ -74,6 +83,8 @@ def _setupUi(self): corrs += self._idffdev.ctrldev.IDFF_CH_LABELS corrs += self._idffdev.ctrldev.IDFF_CV_LABELS corrs += self._idffdev.ctrldev.IDFF_CC_LABELS + corrs += self._idffdev.ctrldev.IDFF_LC_LABELS + corrs += self._idffdev.ctrldev.IDFF_QS_LABELS lay.addWidget(self._llStatusWidget(), 1, 0, 1, 1) lay.addWidget(self._llSettingsWidget(corrs), 2, 0, 3, 1) else: @@ -288,7 +299,7 @@ def _basicSettingsWidget(self): 'Calc. values:', self, alignment=Qt.AlignRight) glay_calccorr = QGridLayout() glay_calccorr.addWidget(ld_calccorr, 0, 0) - corr_fams = ['CH', 'CV', 'QS', 'LCH', 'QD1', 'QF', 'QD2', 'CC'] + corr_fams = ['CH', 'CV', 'QS', 'LCH', 'LCV', 'QD1', 'QF', 'QD2', 'CC'] for ridx, corr in enumerate(corr_fams): if corr == 'CH' and not chnames: continue @@ -296,7 +307,7 @@ def _basicSettingsWidget(self): continue if corr == 'QS' and not qsnames: continue - if corr == 'LCH' and not lcnames: + if corr in ('LCH', 'LCV') and not lcnames: continue if corr in ('QD1', 'QF', 'QD2') and not qnnames: continue @@ -495,9 +506,19 @@ def _logWidget(self): return gbox def _corrsMonitorWidget(self): + idffsubgroup = None + if self._idffgroup: + idffsubgroup = ( + "(CH|CV)" if self._idffgroup == "CHCV" else + "LC(H|V)" if self._idffgroup == "LC" else + "QS" if self._idffgroup == "QS" else + None + ) widget = ControlWidgetFactory.factory( self, section='SI', device='corrector-idff', - subsection=self.device.sub, orientation=Qt.Vertical) + subsection=self._idffnickname.sub, + idffsubgroup=idffsubgroup, + orientation=Qt.Vertical) for wid in widget.get_summary_widgets(): detail_bt = wid.get_detail_button() psname = detail_bt.text() diff --git a/pyqt-apps/siriushla/si_id_control/ue.py b/pyqt-apps/siriushla/si_id_control/ue.py index a471df4a3..1e9caaac6 100644 --- a/pyqt-apps/siriushla/si_id_control/ue.py +++ b/pyqt-apps/siriushla/si_id_control/ue.py @@ -6,9 +6,11 @@ import qtawesome as qta from pydm.widgets import PyDMPushButton +from siriuspy.namesys import SiriusPVName as _PVName from siriushla.util import connect_newprocess, connect_window from ..widgets import SiriusLedAlert, SiriusLabel, SiriusSpinbox, \ - SiriusLedState, SiriusLineEdit, SiriusEnumComboBox + SiriusLedState, SiriusLineEdit, SiriusEnumComboBox, \ + PyDMStateButton from ..widgets.dialog import StatusDetailDialog from .base import IDCommonControlWindow, IDCommonDialog, \ @@ -427,10 +429,35 @@ def _auxCommandsWidget(self): return group def _ffSettingsWidget(self): - but = QPushButton('Feedforward Settings', self) - connect_newprocess( - but, ['sirius-hla-si-ap-idff.py', self._device]) - return but + group = QGroupBox('Feedforward Settings') + lay = QGridLayout(group) + + hlay = QHBoxLayout() + lb_glob = QLabel( + "Global Loop State:", alignment=Qt.AlignRight | Qt.AlignVCenter + ) + idff_pref = _PVName(f"SI-{self.dev_pref.sub}:BS-IDFF") + self.bt_idffglob = PyDMStateButton( + self, init_channel=idff_pref.substitute(propty="LoopState-Sel") + ) + self.led_idffglob = SiriusLedState( + self, init_channel=idff_pref.substitute(propty="LoopState-Sts") + ) + hlay.addStretch() + hlay.addWidget(lb_glob) + hlay.addWidget(self.bt_idffglob) + hlay.addWidget(self.led_idffglob) + hlay.addStretch() + lay.addLayout(hlay, 0, 0, 1, 3) + + for col, idffgroup in enumerate(["CHCV", "QS", "LC"]): + but = QPushButton(f'{idffgroup}', self) + connect_newprocess( + but, ['sirius-hla-si-ap-idff.py', self._device, + '-g', idffgroup] + ) + lay.addWidget(but, 1, col) + return group def _createCmdBtns(self, pv_info, lay, row): btn = PyDMPushButton(self, label='', icon=qta.icon(pv_info["icon"])) From d0bcebfb5af8194d65c56f2f868a1c9ca0793cc1 Mon Sep 17 00:00:00 2001 From: Ana Clara Oliveira Date: Fri, 10 Apr 2026 19:15:05 -0300 Subject: [PATCH 7/7] Update version to 1.16.0 --- pyqt-apps/siriushla/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqt-apps/siriushla/VERSION b/pyqt-apps/siriushla/VERSION index 141f2e805..15b989e39 100644 --- a/pyqt-apps/siriushla/VERSION +++ b/pyqt-apps/siriushla/VERSION @@ -1 +1 @@ -1.15.0 +1.16.0