From 2fc609bdc167a88de00b1f21183de3cd7fd491d8 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 24 Jul 2023 11:02:02 -0400 Subject: [PATCH 1/3] 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/3] 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/3] 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")