From 2fc609bdc167a88de00b1f21183de3cd7fd491d8 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 24 Jul 2023 11:02:02 -0400 Subject: [PATCH 1/6] Added EpicsQObject: Previously, any Epics PV that triggers a change in the QT UI would do the following: - Define EPICS PV - Define QtSignal - EPICS callback that triggers the QT signal - QT Signal then runs a callback that does some work in the frontend In practically every case, the EPICS callback simply takes the PV value and passes it to the qt signal. This leads to repeating code and very little variation (not DRY) `EpicsQObject` attempts to address that. There are escape hatches in case there is a need for custom epics callbacks --- gui/control_main.py | 421 ++++++++++++-------------------------------- gui/epics_signal.py | 35 ++++ 2 files changed, 152 insertions(+), 304 deletions(-) create mode 100644 gui/epics_signal.py diff --git a/gui/control_main.py b/gui/control_main.py index 78bebe80..907be40f 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -49,6 +49,7 @@ UserScreenDialog, ) from gui.raster import RasterCell, RasterGroup +from gui.epics_signal import EpicsQObject from QPeriodicTable import QPeriodicTable from threads import RaddoseThread, VideoThread @@ -110,39 +111,11 @@ class ControlMain(QtWidgets.QMainWindow): # 1/13/15 - are these necessary? Signal = QtCore.Signal() refreshTreeSignal = QtCore.Signal() - serverMessageSignal = QtCore.Signal(str) - serverPopupMessageSignal = QtCore.Signal(str) - programStateSignal = QtCore.Signal(str) - pauseButtonStateSignal = QtCore.Signal(str) - - xrecRasterSignal = QtCore.Signal(str) - choochResultSignal = QtCore.Signal(str) - energyChangeSignal = QtCore.Signal(float) - mountedPinSignal = QtCore.Signal(int) - beamSizeSignal = QtCore.Signal(float) - controlMasterSignal = QtCore.Signal(int) - zebraArmStateSignal = QtCore.Signal(int) - govRobotSeReachSignal = QtCore.Signal(int) - govRobotSaReachSignal = QtCore.Signal(int) - govRobotDaReachSignal = QtCore.Signal(int) - govRobotBlReachSignal = QtCore.Signal(int) - detMessageSignal = QtCore.Signal(str) - sampleFluxSignal = QtCore.Signal(float) - zebraPulseStateSignal = QtCore.Signal(int) - stillModeStateSignal = QtCore.Signal(int) - zebraDownloadStateSignal = QtCore.Signal(int) - zebraSentTriggerStateSignal = QtCore.Signal(int) - zebraReturnedTriggerStateSignal = QtCore.Signal(int) - fastShutterSignal = QtCore.Signal(float) - gripTempSignal = QtCore.Signal(float) - ringCurrentSignal = QtCore.Signal(float) - beamAvailableSignal = QtCore.Signal(float) - sampleExposedSignal = QtCore.Signal(float) + sampMoveSignal = QtCore.Signal(int, str) roiChangeSignal = QtCore.Signal(int, str) highMagCursorChangeSignal = QtCore.Signal(int, str) lowMagCursorChangeSignal = QtCore.Signal(int, str) - cryostreamTempSignal = QtCore.Signal(str) sampleZoomChangeSignal = QtCore.Signal(object) def __init__(self): @@ -172,7 +145,9 @@ def __init__(self): self.zoom2FrameRatePV = PV(daq_utils.pvLookupDict["zoom2FrameRate"]) self.zoom3FrameRatePV = PV(daq_utils.pvLookupDict["zoom3FrameRate"]) self.zoom4FrameRatePV = PV(daq_utils.pvLookupDict["zoom4FrameRate"]) - self.sampleFluxPV = PV(daq_utils.pvLookupDict["sampleFlux"]) + self.sampleFluxPV = EpicsQObject( + daq_utils.pvLookupDict["sampleFlux"], self.processSampleFlux + ) self.beamFlux_pv = PV(daq_utils.pvLookupDict["flux"]) self.stillMode_pv = PV(daq_utils.pvLookupDict["stillMode"]) self.standardMode_pv = PV(daq_utils.pvLookupDict["standardMode"]) @@ -181,20 +156,27 @@ def __init__(self): self.highMagCursorX_pv = PV(daq_utils.pvLookupDict["highMagCursorX"]) self.highMagCursorY_pv = PV(daq_utils.pvLookupDict["highMagCursorY"]) self.fastShutterOpenPos_pv = PV(daq_utils.pvLookupDict["fastShutterOpenPos"]) - self.gripTemp_pv = PV(daq_utils.pvLookupDict["gripTemp"]) + self.gripTemp_pv = EpicsQObject(daq_utils.pvLookupDict["gripTemp"], self.processGripTemp) if getBlConfig(CRYOSTREAM_ONLINE): - self.cryostreamTemp_pv = PV(cryostreamTempPV[daq_utils.beamline]) + self.cryostreamTemp_pv = EpicsQObject(cryostreamTempPV[daq_utils.beamline], self.processCryostreamTemp)) if daq_utils.beamline == "fmx": - self.slit1XGapSP_pv = PV(daq_utils.motor_dict["slit1XGap"] + ".VAL") - self.slit1YGapSP_pv = PV(daq_utils.motor_dict["slit1YGap"] + ".VAL") + self.slit1XGapSP_pv = PV(f"{daq_utils.motor_dict['slit1XGap']}.VAL") + self.slit1YGapSP_pv = PV(f"{daq_utils.motor_dict['slit1YGap']}.VAL") ringCurrentPvName = "SR:C03-BI{DCCT:1}I:Real-I" - self.ringCurrent_pv = PV(ringCurrentPvName) + self.ringCurrent_pv = EpicsQObject(ringCurrentPvName, + self.processRingCurrent) - self.beamAvailable_pv = PV(daq_utils.pvLookupDict["beamAvailable"]) - self.sampleExposed_pv = PV(daq_utils.pvLookupDict["exposing"]) + self.beamAvailable_pv = EpicsQObject(daq_utils.pvLookupDict["beamAvailable"], + self.processBeamAvailable) + self.sampleExposed_pv = EpicsQObject(daq_utils.pvLookupDict["exposing"], + self.processSampleExposed) - self.beamSize_pv = PV(daq_utils.beamlineComm + "size_mode") - self.energy_pv = PV(daq_utils.motor_dict["energy"] + ".RBV") + self.beamSize_pv = EpicsQObject( + daq_utils.beamlineComm + "size_mode", self.processBeamSize + ) + self.energy_pv = EpicsQObject( + f"{daq_utils.motor_dict['energy']}.RBV", self.processEnergyChange + ) self.rasterStepDefs = {"Coarse": 20.0, "Fine": 10.0, "VFine": 5.0} # Timer that waits for a second before calling raddose 3d @@ -2028,6 +2010,8 @@ def clearEnScanPlotCB(self): self.choochGraph.removeCurves() def displayXrecRaster(self, xrecRasterFlag): + if xrecRasterFlag == "0": + return self.xrecRasterFlag_pv.put("0") if xrecRasterFlag == "100": for i in range(len(self.rasterList)): @@ -4730,104 +4714,6 @@ def row_clicked( ) self.refreshCollectionParams(self.selectedSampleRequest) - def processXrecRasterCB(self, value=None, char_value=None, **kw): - xrecFlag = value - if xrecFlag != "0": - self.xrecRasterSignal.emit(xrecFlag) - - def processChoochResultsCB(self, value=None, char_value=None, **kw): - choochFlag = value - if choochFlag != "0": - self.choochResultSignal.emit(choochFlag) - - def processEnergyChangeCB(self, value=None, char_value=None, **kw): - energyVal = value - self.energyChangeSignal.emit(energyVal) - - def mountedPinChangedCB(self, value=None, char_value=None, **kw): - mountedPinPos = value - self.mountedPinSignal.emit(mountedPinPos) - - def beamSizeChangedCB(self, value=None, char_value=None, **kw): - beamSizeFlag = value - self.beamSizeSignal.emit(beamSizeFlag) - - def controlMasterChangedCB(self, value=None, char_value=None, **kw): - controlMasterPID = value - self.controlMasterSignal.emit(controlMasterPID) - - def zebraArmStateChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.zebraArmStateSignal.emit(armState) - - def govRobotSeReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotSeReachSignal.emit(armState) - - def govRobotSaReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotSaReachSignal.emit(armState) - - def govRobotDaReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotDaReachSignal.emit(armState) - - def govRobotBlReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotBlReachSignal.emit(armState) - - def detMessageChangedCB(self, value=None, char_value=None, **kw): - state = char_value - self.detMessageSignal.emit(state) - - def sampleFluxChangedCB(self, value=None, char_value=None, **kw): - state = value - self.sampleFluxSignal.emit(state) - - def zebraPulseStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraPulseStateSignal.emit(state) - - def stillModeStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.stillModeStateSignal.emit(state) - - def zebraDownloadStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraDownloadStateSignal.emit(state) - - def zebraSentTriggerStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraSentTriggerStateSignal.emit(state) - - def zebraReturnedTriggerStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraReturnedTriggerStateSignal.emit(state) - - def shutterChangedCB(self, value=None, char_value=None, **kw): - shutterVal = value - self.fastShutterSignal.emit(shutterVal) - - def gripTempChangedCB(self, value=None, char_value=None, **kw): - gripVal = value - self.gripTempSignal.emit(gripVal) - - def cryostreamTempChangedCB(self, value=None, char_value=None, **kw): - cryostreamTemp = value - self.cryostreamTempSignal.emit(cryostreamTemp) - - def ringCurrentChangedCB(self, value=None, char_value=None, **kw): - ringCurrentVal = value - self.ringCurrentSignal.emit(ringCurrentVal) - - def beamAvailableChangedCB(self, value=None, char_value=None, **kw): - beamAvailableVal = value - self.beamAvailableSignal.emit(beamAvailableVal) - - def sampleExposedChangedCB(self, value=None, char_value=None, **kw): - sampleExposedVal = value - self.sampleExposedSignal.emit(sampleExposedVal) - def processSampMoveCB(self, value=None, char_value=None, **kw): posRBV = value motID = kw["motID"] @@ -4852,27 +4738,12 @@ def treeChangedCB(self, value=None, char_value=None, **kw): if self.processID != self.treeChanged_pv.get(): self.refreshTreeSignal.emit() - def serverMessageCB(self, value=None, char_value=None, **kw): - serverMessageVar = char_value - self.serverMessageSignal.emit(serverMessageVar) - - def serverPopupMessageCB(self, value=None, char_value=None, **kw): - serverMessageVar = char_value - self.serverPopupMessageSignal.emit(serverMessageVar) - - def programStateCB(self, value=None, char_value=None, **kw): - programStateVar = value - self.programStateSignal.emit(programStateVar) - - def pauseButtonStateCB(self, value=None, char_value=None, **kw): - pauseButtonStateVar = value - self.pauseButtonStateSignal.emit(pauseButtonStateVar) - def initUI(self): self.tabs = QtWidgets.QTabWidget() self.comm_pv = PV(daq_utils.beamlineComm + "command_s") self.immediate_comm_pv = PV(daq_utils.beamlineComm + "immediate_command_s") - self.stillModeStatePV = PV(daq_utils.pvLookupDict["stillModeStatus"]) + self.stillModeStatePV = EpicsQObject(daq_utils.pvLookupDict["stillModeStatus"], + self.processStillModeState) self.progressDialog = QtWidgets.QProgressDialog() self.progressDialog.setCancelButtonText("Cancel") self.progressDialog.setModal(False) @@ -4911,71 +4782,41 @@ def initUI(self): fileMenu.addAction(self.expertAction) fileMenu.addAction(self.staffAction) # Define all of the available actions for the overlay color group - self.BlueOverlayAction = QtWidgets.QAction("Blue", self, checkable=True) - self.RedOverlayAction = QtWidgets.QAction("Red", self, checkable=True) - self.GreenOverlayAction = QtWidgets.QAction("Green", self, checkable=True) - self.WhiteOverlayAction = QtWidgets.QAction("White", self, checkable=True) - self.BlackOverlayAction = QtWidgets.QAction("Black", self, checkable=True) - # Connect all of the trigger callbacks to their respective actions - self.BlueOverlayAction.triggered.connect(self.blueOverlayTriggeredCB) - self.RedOverlayAction.triggered.connect(self.redOverlayTriggeredCB) - self.GreenOverlayAction.triggered.connect(self.greenOverlayTriggeredCB) - self.WhiteOverlayAction.triggered.connect(self.whiteOverlayTriggeredCB) - self.BlackOverlayAction.triggered.connect(self.blackOverlayTriggeredCB) + color_names = ["Blue", "Red", "Green", "White", "Black"] + self.overlay_actions = {color.upper(): QtWidgets.QAction(color, self, checkable=True) for color in color_names} + qt_colors = [QtCore.Qt.blue, QtCore.Qt.red, QtCore.Qt.green, QtCore.Qt.white, QtCore.Qt.black] + colors = {color_name.upper(): qt_color for color_name, qt_color in zip(color_names, qt_colors)} + # Create the action group and populate it self.overlayColorActionGroup = QtWidgets.QActionGroup(self) self.overlayColorActionGroup.setExclusive(True) - self.overlayColorActionGroup.addAction(self.BlueOverlayAction) - self.overlayColorActionGroup.addAction(self.RedOverlayAction) - self.overlayColorActionGroup.addAction(self.GreenOverlayAction) - self.overlayColorActionGroup.addAction(self.WhiteOverlayAction) - self.overlayColorActionGroup.addAction(self.BlackOverlayAction) + + # Connect all of the trigger callbacks to their respective actions + for color_name, action in self.overlay_actions.keys(): + color = colors[color_name] + action.triggered.connect(lambda: self.colorOverlayTriggeredCB(color)) + self.overlayColorActionGroup.addAction(action) + # Create the menu item with the submenu, add the group self.overlayMenu = settingsMenu.addMenu("Overlay Settings") self.overlayMenu.addActions(self.overlayColorActionGroup.actions()) try: - if getBlConfig("defaultOverlayColor") == "GREEN": - self.GreenOverlayAction.setChecked(True) - else: - self.BlueOverlayAction.setChecked(True) + action = self.overlay_actions[getBlConfig("defaultOverlayColor")] + action.setChecked(True) except KeyError as e: logger.warning("No value for defaultOverlayColor") - self.BlueOverlayAction.setChecked(True) + self.overlay_actions["BLUE"].setChecked(True) fileMenu.addAction(exitAction) self.setGeometry(300, 300, 1550, 1000) # width and height here. self.setWindowTitle("LSDC on %s" % daq_utils.beamline) self.show() - def blueOverlayTriggeredCB(self): - overlayBrush = QtGui.QBrush(QtCore.Qt.blue) - self.centerMarker.setBrush(overlayBrush) - self.imageScale.setPen(QtGui.QPen(overlayBrush, 2.0)) - self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) - - def redOverlayTriggeredCB(self): - overlayBrush = QtGui.QBrush(QtCore.Qt.red) - self.centerMarker.setBrush(overlayBrush) - self.imageScale.setPen(QtGui.QPen(overlayBrush, 2.0)) - self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) - - def greenOverlayTriggeredCB(self): - overlayBrush = QtGui.QBrush(QtCore.Qt.green) + def colorOverlayTriggeredCB(self, color): + overlayBrush = QtGui.QBrush(color) self.centerMarker.setBrush(overlayBrush) self.imageScale.setPen(QtGui.QPen(overlayBrush, 2.0)) - self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) - - def whiteOverlayTriggeredCB(self): - overlayBrush = QtGui.QBrush(QtCore.Qt.white) - self.centerMarker.setBrush(overlayBrush) - self.imageScale.setPen(QtGui.QPen(overlayBrush, 2.0)) - self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) - - def blackOverlayTriggeredCB(self): - overlayBrush = QtGui.QBrush(QtCore.Qt.black) - self.centerMarker.setBrush(overlayBrush) - self.imageScale.setPen(QtGui.QPen(overlayBrush, 2.0)) - self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) + self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) def popStaffDialogCB(self): if self.controlEnabled(): @@ -4987,15 +4828,12 @@ def closeAll(self): QtWidgets.QApplication.closeAllWindows() def initCallbacks(self): - self.beamSizeSignal.connect(self.processBeamSize) - self.beamSize_pv.add_callback(self.beamSizeChangedCB) - self.treeChanged_pv = PV(daq_utils.beamlineComm + "live_q_change_flag") self.refreshTreeSignal.connect(self.dewarTree.refreshTree) self.treeChanged_pv.add_callback(self.treeChangedCB) - self.mountedPin_pv = PV(daq_utils.beamlineComm + "mounted_pin") - self.mountedPinSignal.connect(self.processMountedPin) - self.mountedPin_pv.add_callback(self.mountedPinChangedCB) + self.mountedPin_pv = EpicsQObject( + daq_utils.beamlineComm + "mounted_pin", self.processMountedPin + ) det_stop_pv = daq_utils.pvLookupDict["stopEiger"] logger.info("setting stop Eiger detector PV: %s" % det_stop_pv) self.stopDet_pv = PV(det_stop_pv) @@ -5008,133 +4846,108 @@ def initCallbacks(self): rz_reboot_pv = daq_utils.pvLookupDict["zebraRebootIOC"] logger.info("setting zebra reboot ioc PV: %s" % rz_reboot_pv) self.rebootZebraIOC_pv = PV(rz_reboot_pv) - self.zebraArmedPV = PV(daq_utils.pvLookupDict["zebraArmStatus"]) - self.zebraArmStateSignal.connect(self.processZebraArmState) - self.zebraArmedPV.add_callback(self.zebraArmStateChangedCB) - - self.govRobotSeReachPV = PV(daq_utils.pvLookupDict["govRobotSeReach"]) - self.govRobotSeReachSignal.connect(self.processGovRobotSeReach) - self.govRobotSeReachPV.add_callback(self.govRobotSeReachChangedCB) - - self.govRobotSaReachPV = PV(daq_utils.pvLookupDict["govRobotSaReach"]) - self.govRobotSaReachSignal.connect(self.processGovRobotSaReach) - self.govRobotSaReachPV.add_callback(self.govRobotSaReachChangedCB) - - self.govRobotDaReachPV = PV(daq_utils.pvLookupDict["govRobotDaReach"]) - self.govRobotDaReachSignal.connect(self.processGovRobotDaReach) - self.govRobotDaReachPV.add_callback(self.govRobotDaReachChangedCB) - - self.govRobotBlReachPV = PV(daq_utils.pvLookupDict["govRobotBlReach"]) - self.govRobotBlReachSignal.connect(self.processGovRobotBlReach) - self.govRobotBlReachPV.add_callback(self.govRobotBlReachChangedCB) - - self.detectorMessagePV = PV(daq_utils.pvLookupDict["eigerStatMessage"]) - self.detMessageSignal.connect(self.processDetMessage) - self.detectorMessagePV.add_callback(self.detMessageChangedCB) - - self.sampleFluxSignal.connect(self.processSampleFlux) - self.sampleFluxPV.add_callback(self.sampleFluxChangedCB) + self.zebraArmedPV = EpicsQObject( + daq_utils.pvLookupDict["zebraArmStatus"], self.processZebraArmState + ) + self.govRobotSeReachPV = EpicsQObject( + daq_utils.pvLookupDict["govRobotSeReach"], self.processGovRobotSeReach + ) - self.stillModeStateSignal.connect(self.processStillModeState) - self.stillModeStatePV.add_callback(self.stillModeStateChangedCB) + self.govRobotSaReachPV = EpicsQObject( + daq_utils.pvLookupDict["govRobotSaReach"], self.processGovRobotSaReach + ) - self.zebraPulsePV = PV(daq_utils.pvLookupDict["zebraPulseStatus"]) - self.zebraPulseStateSignal.connect(self.processZebraPulseState) - self.zebraPulsePV.add_callback(self.zebraPulseStateChangedCB) + self.govRobotDaReachPV = EpicsQObject( + daq_utils.pvLookupDict["govRobotDaReach"], self.processGovRobotDaReach + ) - self.zebraDownloadPV = PV(daq_utils.pvLookupDict["zebraDownloading"]) - self.zebraDownloadStateSignal.connect(self.processZebraDownloadState) - self.zebraDownloadPV.add_callback(self.zebraDownloadStateChangedCB) + self.govRobotBlReachPV = EpicsQObject( + daq_utils.pvLookupDict["govRobotBlReach"], self.processGovRobotBlReach + ) - self.zebraSentTriggerPV = PV(daq_utils.pvLookupDict["zebraSentTriggerStatus"]) - self.zebraSentTriggerStateSignal.connect(self.processZebraSentTriggerState) - self.zebraSentTriggerPV.add_callback(self.zebraSentTriggerStateChangedCB) + self.detectorMessagePV = EpicsQObject( + daq_utils.pvLookupDict["eigerStatMessage"], self.processDetMessage + ) - self.zebraReturnedTriggerPV = PV( - daq_utils.pvLookupDict["zebraTriggerReturnStatus"] + self.zebraPulsePV = EpicsQObject( + daq_utils.pvLookupDict["zebraPulseStatus"], self.processZebraPulseState ) - self.zebraReturnedTriggerStateSignal.connect( - self.processZebraReturnedTriggerState + + self.zebraDownloadPV = EpicsQObject( + daq_utils.pvLookupDict["zebraDownloading"], self.processZebraDownloadState ) - self.zebraReturnedTriggerPV.add_callback( - self.zebraReturnedTriggerStateChangedCB + + self.zebraSentTriggerPV = EpicsQObject( + daq_utils.pvLookupDict["zebraSentTriggerStatus"], + self.processZebraSentTriggerState, ) - self.controlMaster_pv = PV(daq_utils.beamlineComm + "zinger_flag") - self.controlMasterSignal.connect(self.processControlMaster) - self.controlMaster_pv.add_callback(self.controlMasterChangedCB) + self.zebraReturnedTriggerPV = EpicsQObject( + daq_utils.pvLookupDict["zebraTriggerReturnStatus"], + self.processZebraReturnedTriggerState + ) + self.controlMaster_pv = EpicsQObject( + f"{daq_utils.beamlineComm}zinger_flag", self.processControlMaster + ) self.beamCenterX_pv = PV(daq_utils.pvLookupDict["beamCenterX"]) self.beamCenterY_pv = PV(daq_utils.pvLookupDict["beamCenterY"]) - self.choochResultFlag_pv = PV(daq_utils.beamlineComm + "choochResultFlag") - self.choochResultSignal.connect(self.processChoochResult) - self.choochResultFlag_pv.add_callback(self.processChoochResultsCB) - self.xrecRasterFlag_pv = PV(daq_utils.beamlineComm + "xrecRasterFlag") - self.xrecRasterFlag_pv.put("0") - self.xrecRasterSignal.connect(self.displayXrecRaster) - self.xrecRasterFlag_pv.add_callback(self.processXrecRasterCB) - self.message_string_pv = PV(daq_utils.beamlineComm + "message_string") - self.serverMessageSignal.connect(self.printServerMessage) - self.message_string_pv.add_callback(self.serverMessageCB) - self.popup_message_string_pv = PV( - daq_utils.beamlineComm + "gui_popup_message_string" + self.choochResultFlag_pv = EpicsQObject( + f"{daq_utils.beamlineComm}choochResultFlag", self.processChoochResult + ) + self.xrecRasterFlag_pv = EpicsQObject( + f"{daq_utils.beamlineComm}xrecRasterFlag", + self.displayXrecRaster, + use_string=True, + ) + self.message_string_pv = EpicsQObject( + f"{daq_utils.beamlineComm}message_string", + self.printServerMessage, + use_string=True, + ) + self.popup_message_string_pv = EpicsQObject( + f"{daq_utils.beamlineComm}gui_popup_message_string", + self.popupServerMessage, + use_string=True, ) - self.serverPopupMessageSignal.connect(self.popupServerMessage) - self.popup_message_string_pv.add_callback(self.serverPopupMessageCB) - self.program_state_pv = PV(daq_utils.beamlineComm + "program_state") - self.programStateSignal.connect(self.colorProgramState) - self.program_state_pv.add_callback(self.programStateCB) - self.pause_button_state_pv = PV(daq_utils.beamlineComm + "pause_button_state") - self.pauseButtonStateSignal.connect(self.changePauseButtonState) - self.pause_button_state_pv.add_callback(self.pauseButtonStateCB) - - self.energyChangeSignal.connect(self.processEnergyChange) - self.energy_pv.add_callback(self.processEnergyChangeCB, motID="x") - - self.sampx_pv = PV(daq_utils.motor_dict["sampleX"] + ".RBV") + self.program_state_pv = EpicsQObject( + f"{daq_utils.beamlineComm}program_state", self.colorProgramState + ) + self.pause_button_state_pv = EpicsQObject( + f"{daq_utils.beamlineComm}pause_button_state", self.changePauseButtonState + ) + + self.sampx_pv = PV(f"{daq_utils.motor_dict['sampleX']}.RBV") self.sampMoveSignal.connect(self.processSampMove) self.sampx_pv.add_callback(self.processSampMoveCB, motID="x") - self.sampy_pv = PV(daq_utils.motor_dict["sampleY"] + ".RBV") + self.sampy_pv = PV( f"{daq_utils.motor_dict['sampleY']}.RBV") self.sampy_pv.add_callback(self.processSampMoveCB, motID="y") - self.sampz_pv = PV(daq_utils.motor_dict["sampleZ"] + ".RBV") + self.sampz_pv = PV(f"{daq_utils.motor_dict['sampleZ']}.RBV") self.sampz_pv.add_callback(self.processSampMoveCB, motID="z") if self.scannerType == "PI": - self.sampFineX_pv = PV(daq_utils.motor_dict["fineX"] + ".RBV") + self.sampFineX_pv = PV(f"{daq_utils.motor_dict['fineX']}.RBV") self.sampFineX_pv.add_callback(self.processSampMoveCB, motID="fineX") - self.sampFineY_pv = PV(daq_utils.motor_dict["fineY"] + ".RBV") + self.sampFineY_pv = PV(f"{daq_utils.motor_dict['fineY']}.RBV") self.sampFineY_pv.add_callback(self.processSampMoveCB, motID="fineY") - self.sampFineZ_pv = PV(daq_utils.motor_dict["fineZ"] + ".RBV") + self.sampFineZ_pv = PV(f"{daq_utils.motor_dict['fineZ']}.RBV") self.sampFineZ_pv.add_callback(self.processSampMoveCB, motID="fineZ") - self.omega_pv = PV(daq_utils.motor_dict["omega"] + ".VAL") - self.omegaTweak_pv = PV(daq_utils.motor_dict["omega"] + ".RLV") - self.sampyTweak_pv = PV(daq_utils.motor_dict["sampleY"] + ".RLV") + self.omega_pv = PV(f"{daq_utils.motor_dict['omega']}.VAL") + self.omegaTweak_pv = PV(f"{daq_utils.motor_dict['omega']}.RLV") + self.sampyTweak_pv = PV(f"{daq_utils.motor_dict['sampleY']}.RLV") if daq_utils.beamline == "nyx": - self.sampzTweak_pv = PV(daq_utils.motor_dict["sampleX"] + ".RLV") + self.sampzTweak_pv = PV(f"{daq_utils.motor_dict['sampleX']}.RLV") else: - self.sampzTweak_pv = PV(daq_utils.motor_dict["sampleZ"] + ".RLV") - self.omegaRBV_pv = PV(daq_utils.motor_dict["omega"] + ".RBV") + self.sampzTweak_pv = PV(f"{daq_utils.motor_dict['sampleZ']}.RLV") + self.omegaRBV_pv = PV(f"{daq_utils.motor_dict['omega']}.RBV") self.omegaRBV_pv.add_callback( self.processSampMoveCB, motID="omega" ) # I think monitoring this allows for the textfield to monitor val and this to deal with the graphics. Else next line has two callbacks on same thing. self.photonShutterOpen_pv = PV(daq_utils.pvLookupDict["photonShutterOpen"]) self.photonShutterClose_pv = PV(daq_utils.pvLookupDict["photonShutterClose"]) - self.fastShutterRBV_pv = PV(daq_utils.motor_dict["fastShutter"] + ".RBV") - self.fastShutterSignal.connect(self.processFastShutter) - self.fastShutterRBV_pv.add_callback(self.shutterChangedCB) - self.gripTempSignal.connect(self.processGripTemp) - self.gripTemp_pv.add_callback(self.gripTempChangedCB) - if getBlConfig(CRYOSTREAM_ONLINE): - self.cryostreamTempSignal.connect(self.processCryostreamTemp) - self.cryostreamTemp_pv.add_callback(self.cryostreamTempChangedCB) - self.ringCurrentSignal.connect(self.processRingCurrent) - self.ringCurrent_pv.add_callback(self.ringCurrentChangedCB) - self.beamAvailableSignal.connect(self.processBeamAvailable) - self.beamAvailable_pv.add_callback(self.beamAvailableChangedCB) - self.sampleExposedSignal.connect(self.processSampleExposed) - self.sampleExposed_pv.add_callback(self.sampleExposedChangedCB) + self.fastShutterRBV_pv = EpicsQObject(f"{daq_utils.motor_dict['fastShutter']}.RBV", self.processFastShutter) self.highMagCursorChangeSignal.connect(self.processHighMagCursorChange) self.highMagCursorX_pv.add_callback(self.processHighMagCursorChangeCB, ID="x") self.highMagCursorY_pv.add_callback(self.processHighMagCursorChangeCB, ID="y") diff --git a/gui/epics_signal.py b/gui/epics_signal.py new file mode 100644 index 00000000..e55e7700 --- /dev/null +++ b/gui/epics_signal.py @@ -0,0 +1,35 @@ +from PyQt5.QtCore import pyqtSignal, QObject +from epics import PV +import threading + + +class EpicsQObject(QObject): + """ + Class to simplify the process of triggering a qt signal based on an epics PV + Previously there were multiple unique signals defined and their corresponding callbacks + But their code is not unique. This class attempts to reduce the noise + """ + + # Define the PyQt signal + epics_pv_changed = pyqtSignal(object) + + def __init__(self, pv_name, qt_callback, use_string=False, pv_callback=None): + super().__init__() + self.use_string = use_string + # Define the pyepics PV and its callback + if not pv_callback: + pv_callback = self.on_pv_changed + + self.pv = PV(pv_name, callback=pv_callback, auto_monitor=True) + self.epics_pv_changed.connect(qt_callback) + + # Define the callback for the pyepics PV + def on_pv_changed(self, value, char_value, **kwargs): + # Emit the PyQt signal + if self.use_string: + self.epics_pv_changed.emit(char_value) + else: + self.epics_pv_changed.emit(value) + + def get(self): + return self.pv.get() From ba39b38bbc029af7e548be333f699db6623cd30c Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Mon, 24 Jul 2023 13:22:00 -0400 Subject: [PATCH 2/6] Fixes to epics signals to run LSDC Gui --- gui/control_main.py | 4 ++-- gui/epics_signal.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 907be40f..1838c02b 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -158,7 +158,7 @@ def __init__(self): self.fastShutterOpenPos_pv = PV(daq_utils.pvLookupDict["fastShutterOpenPos"]) self.gripTemp_pv = EpicsQObject(daq_utils.pvLookupDict["gripTemp"], self.processGripTemp) if getBlConfig(CRYOSTREAM_ONLINE): - self.cryostreamTemp_pv = EpicsQObject(cryostreamTempPV[daq_utils.beamline], self.processCryostreamTemp)) + self.cryostreamTemp_pv = EpicsQObject(cryostreamTempPV[daq_utils.beamline], self.processCryostreamTemp) if daq_utils.beamline == "fmx": self.slit1XGapSP_pv = PV(f"{daq_utils.motor_dict['slit1XGap']}.VAL") self.slit1YGapSP_pv = PV(f"{daq_utils.motor_dict['slit1YGap']}.VAL") @@ -4792,7 +4792,7 @@ def initUI(self): self.overlayColorActionGroup.setExclusive(True) # Connect all of the trigger callbacks to their respective actions - for color_name, action in self.overlay_actions.keys(): + for color_name, action in self.overlay_actions.items(): color = colors[color_name] action.triggered.connect(lambda: self.colorOverlayTriggeredCB(color)) self.overlayColorActionGroup.addAction(action) diff --git a/gui/epics_signal.py b/gui/epics_signal.py index e55e7700..86e51855 100644 --- a/gui/epics_signal.py +++ b/gui/epics_signal.py @@ -1,6 +1,5 @@ -from PyQt5.QtCore import pyqtSignal, QObject +from qtpy.QtCore import Signal, QObject from epics import PV -import threading class EpicsQObject(QObject): @@ -11,7 +10,7 @@ class EpicsQObject(QObject): """ # Define the PyQt signal - epics_pv_changed = pyqtSignal(object) + epics_pv_changed = Signal(object) def __init__(self, pv_name, qt_callback, use_string=False, pv_callback=None): super().__init__() @@ -33,3 +32,6 @@ def on_pv_changed(self, value, char_value, **kwargs): def get(self): return self.pv.get() + + def put(self, *args, **kwargs): + self.pv.put(*args, **kwargs) From 25573bf3a03df3e394adacfb892a03d8be6a4f91 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Fri, 19 Apr 2024 15:48:53 -0400 Subject: [PATCH 3/6] Fixed formatting --- gui/control_main.py | 68 ++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 1838c02b..d7c8ac75 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -48,8 +48,8 @@ StaffScreenDialog, UserScreenDialog, ) -from gui.raster import RasterCell, RasterGroup from gui.epics_signal import EpicsQObject +from gui.raster import RasterCell, RasterGroup from QPeriodicTable import QPeriodicTable from threads import RaddoseThread, VideoThread @@ -156,20 +156,25 @@ def __init__(self): self.highMagCursorX_pv = PV(daq_utils.pvLookupDict["highMagCursorX"]) self.highMagCursorY_pv = PV(daq_utils.pvLookupDict["highMagCursorY"]) self.fastShutterOpenPos_pv = PV(daq_utils.pvLookupDict["fastShutterOpenPos"]) - self.gripTemp_pv = EpicsQObject(daq_utils.pvLookupDict["gripTemp"], self.processGripTemp) + self.gripTemp_pv = EpicsQObject( + daq_utils.pvLookupDict["gripTemp"], self.processGripTemp + ) if getBlConfig(CRYOSTREAM_ONLINE): - self.cryostreamTemp_pv = EpicsQObject(cryostreamTempPV[daq_utils.beamline], self.processCryostreamTemp) + self.cryostreamTemp_pv = EpicsQObject( + cryostreamTempPV[daq_utils.beamline], self.processCryostreamTemp + ) if daq_utils.beamline == "fmx": self.slit1XGapSP_pv = PV(f"{daq_utils.motor_dict['slit1XGap']}.VAL") self.slit1YGapSP_pv = PV(f"{daq_utils.motor_dict['slit1YGap']}.VAL") ringCurrentPvName = "SR:C03-BI{DCCT:1}I:Real-I" - self.ringCurrent_pv = EpicsQObject(ringCurrentPvName, - self.processRingCurrent) + self.ringCurrent_pv = EpicsQObject(ringCurrentPvName, self.processRingCurrent) - self.beamAvailable_pv = EpicsQObject(daq_utils.pvLookupDict["beamAvailable"], - self.processBeamAvailable) - self.sampleExposed_pv = EpicsQObject(daq_utils.pvLookupDict["exposing"], - self.processSampleExposed) + self.beamAvailable_pv = EpicsQObject( + daq_utils.pvLookupDict["beamAvailable"], self.processBeamAvailable + ) + self.sampleExposed_pv = EpicsQObject( + daq_utils.pvLookupDict["exposing"], self.processSampleExposed + ) self.beamSize_pv = EpicsQObject( daq_utils.beamlineComm + "size_mode", self.processBeamSize @@ -4742,8 +4747,9 @@ def initUI(self): self.tabs = QtWidgets.QTabWidget() self.comm_pv = PV(daq_utils.beamlineComm + "command_s") self.immediate_comm_pv = PV(daq_utils.beamlineComm + "immediate_command_s") - self.stillModeStatePV = EpicsQObject(daq_utils.pvLookupDict["stillModeStatus"], - self.processStillModeState) + self.stillModeStatePV = EpicsQObject( + daq_utils.pvLookupDict["stillModeStatus"], self.processStillModeState + ) self.progressDialog = QtWidgets.QProgressDialog() self.progressDialog.setCancelButtonText("Cancel") self.progressDialog.setModal(False) @@ -4783,20 +4789,34 @@ def initUI(self): fileMenu.addAction(self.staffAction) # Define all of the available actions for the overlay color group color_names = ["Blue", "Red", "Green", "White", "Black"] - self.overlay_actions = {color.upper(): QtWidgets.QAction(color, self, checkable=True) for color in color_names} - qt_colors = [QtCore.Qt.blue, QtCore.Qt.red, QtCore.Qt.green, QtCore.Qt.white, QtCore.Qt.black] - colors = {color_name.upper(): qt_color for color_name, qt_color in zip(color_names, qt_colors)} - + qt_colors = [ + QtCore.Qt.GlobalColor.blue, + QtCore.Qt.GlobalColor.red, + QtCore.Qt.GlobalColor.green, + QtCore.Qt.GlobalColor.white, + QtCore.Qt.GlobalColor.black, + ] + self.overlay_actions = { + color.upper(): QtWidgets.QAction(color, self, checkable=True) + for color in color_names + } + colors = { + color_name.upper(): qt_color + for color_name, qt_color in zip(color_names, qt_colors) + } + # Create the action group and populate it self.overlayColorActionGroup = QtWidgets.QActionGroup(self) self.overlayColorActionGroup.setExclusive(True) - + # Connect all of the trigger callbacks to their respective actions for color_name, action in self.overlay_actions.items(): color = colors[color_name] - action.triggered.connect(lambda: self.colorOverlayTriggeredCB(color)) + action.triggered.connect( + lambda _, color=color: self.colorOverlayTriggeredCB(color) + ) self.overlayColorActionGroup.addAction(action) - + # Create the menu item with the submenu, add the group self.overlayMenu = settingsMenu.addMenu("Overlay Settings") self.overlayMenu.addActions(self.overlayColorActionGroup.actions()) @@ -4816,7 +4836,7 @@ def colorOverlayTriggeredCB(self, color): overlayBrush = QtGui.QBrush(color) self.centerMarker.setBrush(overlayBrush) self.imageScale.setPen(QtGui.QPen(overlayBrush, 2.0)) - self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) + self.imageScaleText.setPen(QtGui.QPen(overlayBrush, 1.0)) def popStaffDialogCB(self): if self.controlEnabled(): @@ -4884,11 +4904,11 @@ def initCallbacks(self): self.zebraReturnedTriggerPV = EpicsQObject( daq_utils.pvLookupDict["zebraTriggerReturnStatus"], - self.processZebraReturnedTriggerState + self.processZebraReturnedTriggerState, ) self.controlMaster_pv = EpicsQObject( - f"{daq_utils.beamlineComm}zinger_flag", self.processControlMaster + f"{daq_utils.beamlineComm}zinger_flag", self.processControlMaster ) self.beamCenterX_pv = PV(daq_utils.pvLookupDict["beamCenterX"]) self.beamCenterY_pv = PV(daq_utils.pvLookupDict["beamCenterY"]) @@ -4921,7 +4941,7 @@ def initCallbacks(self): self.sampx_pv = PV(f"{daq_utils.motor_dict['sampleX']}.RBV") self.sampMoveSignal.connect(self.processSampMove) self.sampx_pv.add_callback(self.processSampMoveCB, motID="x") - self.sampy_pv = PV( f"{daq_utils.motor_dict['sampleY']}.RBV") + self.sampy_pv = PV(f"{daq_utils.motor_dict['sampleY']}.RBV") self.sampy_pv.add_callback(self.processSampMoveCB, motID="y") self.sampz_pv = PV(f"{daq_utils.motor_dict['sampleZ']}.RBV") self.sampz_pv.add_callback(self.processSampMoveCB, motID="z") @@ -4947,7 +4967,9 @@ def initCallbacks(self): ) # I think monitoring this allows for the textfield to monitor val and this to deal with the graphics. Else next line has two callbacks on same thing. self.photonShutterOpen_pv = PV(daq_utils.pvLookupDict["photonShutterOpen"]) self.photonShutterClose_pv = PV(daq_utils.pvLookupDict["photonShutterClose"]) - self.fastShutterRBV_pv = EpicsQObject(f"{daq_utils.motor_dict['fastShutter']}.RBV", self.processFastShutter) + self.fastShutterRBV_pv = EpicsQObject( + f"{daq_utils.motor_dict['fastShutter']}.RBV", self.processFastShutter + ) self.highMagCursorChangeSignal.connect(self.processHighMagCursorChange) self.highMagCursorX_pv.add_callback(self.processHighMagCursorChangeCB, ID="x") self.highMagCursorY_pv.add_callback(self.processHighMagCursorChangeCB, ID="y") From e1fb579e88f34fbd62bdc4de9b756d65a613299f Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Tue, 14 Oct 2025 19:54:52 -0400 Subject: [PATCH 4/6] Added escape hatch for custom pv class --- gui/epics_signal.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gui/epics_signal.py b/gui/epics_signal.py index 86e51855..563c7d34 100644 --- a/gui/epics_signal.py +++ b/gui/epics_signal.py @@ -12,13 +12,16 @@ class EpicsQObject(QObject): # Define the PyQt signal epics_pv_changed = Signal(object) - def __init__(self, pv_name, qt_callback, use_string=False, pv_callback=None): + def __init__(self, pv_name, qt_callback, use_string=False, pv_callback=None, custom_pv=None): super().__init__() self.use_string = use_string # Define the pyepics PV and its callback if not pv_callback: pv_callback = self.on_pv_changed + if custom_pv: + PV = custom_pv + self.pv = PV(pv_name, callback=pv_callback, auto_monitor=True) self.epics_pv_changed.connect(qt_callback) From e2dd7ceb65f7e050dd8cecb26988bd77b249109e Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 15 Oct 2025 11:56:20 -0400 Subject: [PATCH 5/6] Added custom PV to mountedPin_pv EpicsQObject --- gui/control_main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index c2fe6783..4c4efce9 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -5141,7 +5141,8 @@ def initCallbacks(self): self.refreshTreeSignal.connect(self.dewarTree.refreshTreeThreaded) self.treeChanged_pv.add_callback(self.treeChangedCB) self.mountedPin_pv = EpicsQObject( - daq_utils.beamlineComm + "mounted_pin", self.processMountedPin + daq_utils.beamlineComm + "mounted_pin", self.processMountedPin, + custom_pv=custom_pv.MountedPinPV ) det_stop_pv = daq_utils.pvLookupDict["stopEiger"] logger.info("setting stop Eiger detector PV: %s" % det_stop_pv) From 16f9c33baac1247981c39c5fd9bb4f4b59991e34 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 15 Oct 2025 12:10:22 -0400 Subject: [PATCH 6/6] Clarified that custom_pv should be a subclass of PV --- gui/epics_signal.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gui/epics_signal.py b/gui/epics_signal.py index 563c7d34..10ae60eb 100644 --- a/gui/epics_signal.py +++ b/gui/epics_signal.py @@ -1,6 +1,6 @@ from qtpy.QtCore import Signal, QObject from epics import PV - +from typing import Optional, Type class EpicsQObject(QObject): """ @@ -12,17 +12,20 @@ class EpicsQObject(QObject): # Define the PyQt signal epics_pv_changed = Signal(object) - def __init__(self, pv_name, qt_callback, use_string=False, pv_callback=None, custom_pv=None): + def __init__(self, pv_name, qt_callback, use_string=False, pv_callback=None, + custom_pv: Optional[Type[PV]]=None): super().__init__() self.use_string = use_string # Define the pyepics PV and its callback if not pv_callback: pv_callback = self.on_pv_changed - if custom_pv: - PV = custom_pv + EpicsPV: Type[PV] = PV if custom_pv is None else custom_pv + + if not issubclass(EpicsPV, PV): + raise TypeError("custom_pv must be a PV class or a subclass of PV") - self.pv = PV(pv_name, callback=pv_callback, auto_monitor=True) + self.pv = EpicsPV(pv_name, callback=pv_callback, auto_monitor=True) self.epics_pv_changed.connect(qt_callback) # Define the callback for the pyepics PV