''' Created on Dec 3, 2015 A class to handle the EPICS PVs in a slightly more convenient way than whatever CSS provides. First PV is connected, then it can be used to read and write. And at the end, it should be disconnected . @author: Hovanes Egiyan ''' import time import sys import math import threading # from threading import threading from java.lang import Thread, Runnable from org.csstudio.opibuilder.scriptUtil import PVUtil from org.csstudio.opibuilder.scriptUtil import ConsoleUtil from org.csstudio.utility.pv import PVFactory class EpicsPV(object): ''' Class to represent an EPICS PV and it's values ''' sleepTime = 0.01 # Sleep time maxAttempts = 1000 # Number of attempts to connect this PV readbackTolreance = 0.1 # Tolerance between setpoint and readback def __init__(self, name, varType="double", connectType = "SYNC"): ''' Constructor. Connects the PV ''' # ConsoleUtil.writeInfo( "Creating EpicsPV object for PV " + name ) self.pvName = name # EPICS PV names (string for CA address) self.pvType = varType # Type of the variable (double, string, int) self.conType = connectType self.rbValue = None # Readback value self.spValue = None # Setpoint value self.objLock = threading.RLock() # Object lock for object of this class # Acquire the lock to start with self.objLock.acquire() try : self.pvObject = PVFactory.createPV(self.pvName) # Create the PV finally: pass # ConsoleUtil.writeInfo( "__init__ released " + self.pvName ) self.objLock.release() return def connect(self): if( self.conType == "ASYNC" ): self.objLock.acquire() try: # Connect PV asynchronously thread = Thread( EpicsPVConnection( self ) ) thread.start() finally: pass # ConsoleUtil.writeInfo( "connect released " + self.pvName ) self.objLock.release() else: try: self.connectSync() # Connect the PV object synchronously except Exception, e: ConsoleUtil.writeInfo( "There was a problem in EpicsPV.connect: " + str(e) ) raise e return def __repr__(self): return "PV: ".__repr__() + self.pvName.__repr__() + " of type ".__repr__() + \ self.pvType.__repr__() + " has SP value of ".__repr__() + self.spValue.__repr__() + \ " and RB value of ".__repr__() + self.rbValue.__repr__() def __str__(self): return "PV: ".__str__() + self.pvName.__str__() + " of type ".__str__() + \ self.pvType.__str__() + " has SP value of ".__str__() + self.spValue.__str__() + \ " and RB value of ".__str__() + self.rbValue.__str__() def __del__(self): ''' Destructor. Disconnects the PV ''' self.disconnect() return def connectSync(self): ''' connect this PV. Return value is the number of seconds that it took to connect this PV. Throws an exception if it takes too long to connect. ''' # ConsoleUtil.writeInfo( "Connecting EpicsPV object for PV " + self.pvName ) self.objLock.acquire() try: self.pvObject.start() # Connect the PV # Loop for a while until the PV is connected attemptNumber = 0 while( not self.isConnected() and (attemptNumber < EpicsPV.maxAttempts) ): time.sleep( EpicsPV.sleepTime ) attemptNumber += 1 # If the number of maximum attempts is exceeded raise an exception if( attemptNumber >= EpicsPV.maxAttempts ): raise Exception( "PV %s did not connect after %d attempts" % ( self.pvName, attemptNumber ) ) else : self.rbValue = self.readValue() self.spValue = self.rbValue finally: pass # ConsoleUtil.writeInfo( "connectSync released " + self.pvName ) self.objLock.release() # ConsoleUtil.writeInfo( "Connected in " + str(attemptNumber*EpicsPV.sleepTime) + " seconds" ) # return the number of seconds it took to connect return (attemptNumber*EpicsPV.sleepTime ) def disconnect(self): ''' Stop and delete the PV object. PV object delettion happens here rather than in the destructor. Destructor will call this method instead. ''' self.objLock.acquire() # ConsoleUtil.writeInfo( "Disconnecting EpicsPV object for PV " + self.pvName ) try: self.pvObject.stop() # Disconnect the PV # self.pvObject.__del__() # Delete the object self.pvObject = None finally: pass # ConsoleUtil.writeInfo( "disconnect released " + self.pvName ) self.objLock.release() return def isConnected(self): ''' return true if PV is connected, otherwise return false ''' return self.pvObject.isConnected() def ensureConnected(self): ''' ensure that this pv is connected ''' if( self.isConnected() ) : # If already connected just return true return True # Keep trying to connect attemptNumber = 0 while( not self.isConnected() and (attemptNumber < EpicsPV.maxAttempts) ): time.sleep( EpicsPV.sleepTime ) attemptNumber += 1 # If the number of maximum attempts is exceeded raise an exception if( attemptNumber >= EpicsPV.maxAttempts ): return False return True def readValue(self): ''' Read the PV value into the rbValue and return the latest readback value. ''' self.objLock.acquire() # ConsoleUtil.writeInfo( "Reading value for " + self.pvName ) try: if( self.ensureConnected() ): if( self.pvType == "double" ): self.rbValue = PVUtil.getDouble( self.pvObject ) elif( self.pvType == "string" ): self.rbValue = PVUtil.getString( self.pvObject ) elif( self.pvType == "int"): self.rbValue = PVUtil.getInt( self.pvObject ) else : raise Exception( "Unknown type for PV %s" % ( self.pvName ) ) else: raise Exception( "PV %s got disconnected and could not connect" % ( self.pvName ) ) except Exception, e: ConsoleUtil.writeInfo( "There was a problem in EpicsPV.readValue: " + str(e) ) raise e finally: pass # ConsoleUtil.writeInfo( "readValue released " + self.pvName ) self.objLock.release() return self.rbValue def setValue(self, value, tolerance = None ): ''' Set the PV to desired value. The method tries to verify that the value has been set to within the specified tolerance. This method throws and exception if the readback value never goes into the range defined by the tolerance. ''' # ConsoleUtil.writeInfo( "Acquiring lock for " + self.pvName ) self.objLock.acquire() # ConsoleUtil.writeInfo( "Setting value for " + self.pvName + " to " + str(value) ) try: self.spValue = value # Set the setpoint values if( self.spValue == None ): return if( tolerance == None ): tolerance = EpicsPV.readbackTolreance if( self.ensureConnected() ): attemptNumber = 0 # Set the attempt number to zero while( ( abs( self.readValue() - self.spValue ) > tolerance ) and (attemptNumber < EpicsPV.maxAttempts) ): self.pvObject.setValue( value ) # Try to set the value time.sleep( EpicsPV.sleepTime ) # If the number of allowed attempts is exhausted, throw an exception if( attemptNumber > EpicsPV.maxAttempts ): errMsg = "Could not set PV %s to %s within %s tolerance" \ % ( self.pvName, str(self.spValue), str(tolerance) ) raise Exception( errMsg ) else: raise Exception( "PV %s got disconnected and could not connect" % ( self.pvName ) ) return except Exception, e: ConsoleUtil.writeInfo( "There was a problem in EpicsPV.setValue: " + str(e) ) raise e finally: pass # ConsoleUtil.writeInfo( "setValue released " + self.pvName ) self.objLock.release() return def setValueAsync(self, value, tolerance = None ): # Set PV value asynchronously self.objLock.acquire() # ConsoleUtil.writeInfo( "Setting value for " + self.pvName + " to " + str(value) + " by launching a new thread") try: thread = Thread( EpicsPVSetValue( self, value, tolerance ) ) # self.objLock.release() thread.start() # ConsoleUtil.writeInfo( "Launched thread to set " + self.pvName ) finally: pass # ConsoleUtil.writeInfo( "setValueAsync released " + self.pvName ) self.objLock.release() return def getName(self): return self.pvName def getReadbackValue(self): return self.rbValue def getSetpointValue(self): return self.spValue def getType(self): return self.pvType def getPV(self): return self.pvObject class EpicsPVConnection(Runnable): ''' An auxiliary class to be used for asynchronously establishing connection to the PVs of EpicsPVSet ''' def __init__(self, pvObject): self.pv = pvObject return def run(self): self.pv.connectSync() return class EpicsPVSetValue(Runnable): ''' An auxiliary class to be used for asynchronously set values of PVs of the EpicsPVSet to a single value. ''' def __init__(self, pvObject, val, tol = None): self.pv = pvObject self.value = val self.tolerance = tol return def run(self): # ConsoleUtil.writeInfo( "Running set thread for " + self.pv.pvName ) self.pv.setValue( self.value, self.tolerance ) return class testEpicsPV(Runnable): def run(self): # if __name__ == '__main__': NumberOfPVs = 20 pvList = [] for iPV in range(0, NumberOfPVs) : pvName = "testEpicsPV:" + str(iPV) ConsoleUtil.writeInfo("PV name is : " + pvName) try: newPV = EpicsPV(pvName, "double") newPV.connect() pvList.append( newPV ) except Exception, e : ConsoleUtil.writeInfo( "There was a problem in main: " + str(e) ) time.sleep(3.0) iPV = 0 for pvObject in pvList: ConsoleUtil.writeInfo("Setting " + pvObject.getName() + " to " + str(float(iPV)) ) pvObject.setValue( 10*float( iPV ) ) iPV += 1 time.sleep(3.0) for pvObject in pvList: ConsoleUtil.writeInfo("The value for " + pvObject.getName() + " is " + str(pvObject.readValue() ) ) time.sleep(3.0) for pvObject in pvList: pvObject.disconnect() if __name__ == '__main__': thread = Thread( testEpicsPV() ) thread.start()