class RoboEpicsChannel:
     def __init__(self,pvName,verbose=0):
         self.MAXPUTS=10
         self.pvName=pvName
         self.status=0 # no error
         self.severity=0
         self.statusReady=0 # has a command been executed
         self.waiting=0 # is the variable overbooked
         self.putAttempts=0 # the number of times it has tried to put a variable
         self.putSuccess=0 # if the last put command was successful
         self.verbose=verbose # if epics should print everything out for alarms and all
         self.CurrentText=""
         if ((self.pvName.upper().find("ROBO")<0) & DebugMode):
             self.rPrint("Variable "+pvName+" affects beamline and will not be altered")
         self.reconnect()
     def rPrint(self,cString):
         self.CurrentText=self.pvName+": "+cString
         print self.CurrentText
     def binaryClick(self): #specific method for dealing with the binary variables that return T/F based on success in robot
 		putOK=self.rPutVal(5)
 		robotOK=self.getVal()
 		if (robotOK==5):
 			time.sleep(.12)
 			robotOK=self.getVal()
 		return (putOK & robotOK)
     def getVal(self):
         try:
             val=self.chan.getw()
         except:
             try: #try one time to reconnect
                 self.rPrint("Variable "+self.pvName+" appears to be disconnected, attempting reconnect")
                 self.reconnect()
                 val=self.chan.getw()
                 self.statusReady=0
             except:
                 self.connected=0
                 val=""
                 self.rPrint("Variable "+self.pvName+" cannot be read after 1 reconnect attempt")
         return val
 
     def putVal(self,val): #direct putval without any retry features
         try:
             if (self.waiting):
                 time.sleep(2) # give 2 seconds rest while the system is queued
             if ((self.pvName.upper().find("ROBO")<0) & DebugMode):
                 self.rPrint("Debug Mode Enabled, thus not altered")
             else:
                 self.chan.putw(val)
 		
             self.statusReady=0
             time.sleep(0.12)
             # sleep 120 ms to wait for alarm callback
             if (self.statusReady==0):
                 self.alarmUpdate()
             return self.putSuccess
         except:
             self.connected=0
 	    return 0
 
 
     def rPutVal(self,val): # the safe put function that ensures the robot accepts the command, the CAChannel package makes this trickier than needed
         self.putSuccess=0
         self.putAttempts=0
         while ((self.putSuccess<>1) & (self.putAttempts<self.MAXPUTS)):
             self.putVal(val)	
             if (self.putSuccess==0):
                 self.rPrint("Ensure Robot is on and in correct Position! Attempt "+self.putAttempts.__str__()+" of "+self.MAXPUTS.__str__())
                 tempPA=self.putAttempts
                 self.reconnect()
                 time.sleep(5)
                 self.putAttempts=tempPA
 
         if (self.putSuccess):
             if (self.verbose):
     			print self.pvName+" was successfully put"
             return 1
         else:
             self.rPrint("Put was not successful")
             return 0
     
     def statusText(self):
     	return self.pvName+" is currently "+ca.alarmStatusString(self.status)
 	
     def alarmHandler(self,epics_args,user_args):
     	# a function to handle the alarm feedback from epics telling the user what has happened
         # the primary purpose is to not allow channels to get overbooked CALC and to know if a variable has been set or nto
         self.status=epics_args["pv_status"]
         self.severity=epics_args["pv_severity"]
         self.alarmUpdate()
 	
     def alarmUpdate(self):
         self.statusReady=1
         if ((self.status<>ca.AlarmStatus.NO_ALARM) & (self.status<>ca.AlarmStatus.CALC_ALARM)):
 		
             self.putSuccess=0
             self.putAttempts+=1
             if (self.verbose):
                 print self.statusText()
             print "Variable "+self.pvName+" not successfully written after "+str(self.putAttempts)+" try"
             
             print "Alarm Severity : "+ca.alarmSeverityString(self.severity)
         else:
             self.putSuccess=1
             self.putAttempts=0
 			
         if (self.status==ca.AlarmStatus.CALC_ALARM):
             self.waiting=1
             if (self.verbose):
                 print "Variable "+self.pvName+" is currently queued!"
         if ((self.waiting==1) & (self.status==ca.AlarmStatus.NO_ALARM)):
             self.waiting=0
             if (self.verbose):
                 print "Queue Cleared!"
     def reconnect(self):
         try:
             self.chan=CaChannel()
             self.chan.search(self.pvName)
             self.chan.pend_io()
             if (self.chan.element_count()<1):
                 self.rPrint("No channels named "+self.pvName+" found, is the SoftIOC running?")
                 self.connected=(self.chan.state()==ca.ch_state.cs_conn)
             self.chan.add_masked_array_event(ca.DBR_STRING,1,ca.DBE_ALARM | ca.DBE_VALUE,self.alarmHandler)
 	    
         except CaChannelException, status:
             print ca.message(status)
             self.connected=0
class RealRoboEpicsChannel:
    def __init__(self,pvName,verbose=0,asString=0,vcCallback=None):
        self.MAXPUTS=10
        self.pvName=pvName
        self.asString=asString
        self.curValue=0
        self.lastPoll=0
        self.status=0 # no error
        self.severity=0
        self.statusReady=0 # has a command been executed
        self.waiting=0 # is the variable overbooked
        self.putAttempts=0 # the number of times it has tried to put a variable
        self.putSuccess=0 # if the last put command was successful
        self.verbose=verbose # if epics should print everything out for alarms and all
        self.CurrentText=""
        self.vcCallback=vcCallback # the callback should the value change feed with (chname,newVal,oldVal)
        
        if ((self.pvName.upper().find("ROBO")<0) & DebugMode):
            self.rPrint("Variable "+pvName+" affects beamline and will not be altered")
        self.reconnect()
    def rPrint(self,cString):
        self.CurrentText=self.pvName+": "+cString
        print self.CurrentText
    def binaryClick(self): #specific method for dealing with the binary variables that return T/F based on success in robot
		putOK=self.rPutVal(5)
		robotOK=self.getVal()
		if (robotOK==5):
			time.sleep(.12)
			robotOK=self.getVal()
		if (ReadOnly):
			putOK=1
			robotOK=1
		return (putOK & robotOK)
    def getVal(self):
        
        try:
            cTime=time.time()
            if (cTime-self.lastPoll)>0.2: # prevent double polling
                
                val=self.chgetw()
                self.curValue=val
                self.lastPoll=cTime
            else:
                val=self.curValue
        except:
            try: #try one time to reconnect
                self.rPrint("Variable "+self.pvName+" appears to be disconnected, attempting reconnect")
                self.reconnect()
                val=self.chgetw()
                self.statusReady=0
            except:
                self.connected=0
                val=""
                self.rPrint("Variable "+self.pvName+" cannot be read after 1 reconnect attempt")
        return val
    def chgetw(self):
        if self.asString==1:
            return str(self.chan.getw(ca.DBF_STRING))
        else:
            return self.chan.getw()
    def putVal(self,iVal): #direct putval without any retry features
        self.lastPoll=0
        
        # crop string when it is too long
        val=iVal
        if type(iVal)==type(''):
            if len(iVal)>40:
                val=iVal[0:39]
                print 'String was cropped :'+str(val)
                
        try:
            if (self.waiting):
                time.sleep(2) # give 2 seconds rest while the system is queued
            if ((self.pvName.upper().find("ROBO")<0) & DebugMode):
                self.rPrint("Debug Mode Enabled, thus not altered")
            else:
                if not ReadOnly:
                    self.curValue=val # default assumption is the value will be loaded
                    self.chan.putw(val)
		
            self.statusReady=0
            time.sleep(0.12)
            # sleep 120 ms to wait for alarm callback
            if (self.statusReady==0):
                self.alarmUpdate()
            return self.putSuccess
        except:
            self.connected=0
	    return 0


    def rPutVal(self,val): # the safe put function that ensures the robot accepts the command, the CAChannel package makes this trickier than needed
        self.putSuccess=0
        self.putAttempts=0
        self.lastPoll=0
        while ((self.putSuccess<>1) & (self.putAttempts<self.MAXPUTS)):
            self.putVal(val)	
            if (self.putSuccess==0):
                self.rPrint("Ensure Robot is on and in correct Position! Attempt "+self.putAttempts.__str__()+" of "+self.MAXPUTS.__str__())
                tempPA=self.putAttempts
                self.reconnect()
                time.sleep(5)
                self.putAttempts=tempPA

        if (self.putSuccess):
            if (self.verbose):
    			print self.pvName+" was successfully put"
            return 1
        else:
            self.rPrint("Put was not successful")
            return 0
    
    def statusText(self):
    	return self.pvName+" is currently "+ca.alarmStatusString(self.status)
	
    def vcHandler(self,epics_args,user_args):
        # A function to handle value change callbacks
        newValue=epics_args['pv_value']
        self.lastPoll=time.time() 
        if newValue!=self.curValue:
            # It is different from what we set it to, ick
            self.curValue=newValue
            self.vcCallback.__call__(self.pvName,newValue,self.curValue)
            
        
           
    def alarmHandler(self,epics_args,user_args):
    	# a function to handle the alarm feedback from epics telling the user what has happened
        # the primary purpose is to not allow channels to get overbooked CALC and to know if a variable has been set or nto
        self.status=epics_args["pv_status"]
        self.severity=epics_args["pv_severity"]
        self.alarmUpdate()
	
    def alarmUpdate(self):
        self.statusReady=1
        if ((self.status<>ca.AlarmStatus.NO_ALARM) & (self.status<>ca.AlarmStatus.CALC_ALARM)):
		
            self.putSuccess=0
            self.putAttempts+=1
            if (self.verbose):
                print self.statusText()
            if not (self.pvName.upper()=='ARIDI-PCT:CURRENT'): 
                print "Variable "+self.pvName+" not successfully written after "+str(self.putAttempts)+" try"
                print "Alarm Severity : "+ca.alarmSeverityString(self.severity)
        else:
            self.putSuccess=1
            self.putAttempts=0
			
        if (self.status==ca.AlarmStatus.CALC_ALARM):
            self.waiting=1
            if (self.verbose):
                print "Variable "+self.pvName+" is currently queued!"
        if ((self.waiting==1) & (self.status==ca.AlarmStatus.NO_ALARM)):
            self.waiting=0
            if (self.verbose):
                print "Queue Cleared!"
    def reconnect(self):
        try:
            self.chan=CaChannel()
            self.chan.search(self.pvName)
            self.chan.pend_io()
            if (self.chan.element_count()<1):
                self.rPrint("No channels named "+self.pvName+" found, is the SoftIOC running?")
                self.connected=(self.chan.state()==ca.ch_state.cs_conn)
            self.chan.add_masked_array_event(ca.DBR_STRING,None,ca.DBE_ALARM,self.alarmHandler)
            self.rebind(self.vcCallback)
        except CaChannelException, status:
            print ca.message(status)
            self.connected=0