""" Class library for the Pearl PGM OTF scans of monochromator angles Author: Juraj Krempasky Matthias Muntwiler Created: Feb 19, 2013 """ import time, os, os.path, sys, string, re ARCH = os.environ['EPICS_HOST_ARCH'] sys.path.insert(0, '/afs/psi.ch/user/w/wang_x1/public/'+ARCH) sys.path.append('/afs/psi.ch/user/k/krempasky/public') from jkpkg.app import epicsMotor from jkpkg.app import epv import threading import blstatus MAXARRAY = 6000 #!OTF class OTFscan_th(threading.Thread): def __init__(self, worker): threading.Thread.__init__(self) self.worker = worker self.running = 1 # assign 0 to end the thread self.start() def run(self): try: #self.worker.eopt = 0 # do not drive PGM+ID parm = {'FOLDER':self.worker.folder, 'FILE':self.worker.file} (isok,rstring) = self.worker.makefile(parm, set=1, xmcd=self.worker.xmcd) if isok: self.worker.msg = "%s"%(os.path.basename(rstring)) self.worker.path = rstring else: self.worker.msg = "%s"%(rstring) raise Exception, self.worker.msg print self.worker.msg self.worker.outparam['done']=0 uprate = self.worker.uparam['uprate'] #(E1,E2,tr_time)=self.worker.uparam['otf'] #(beta1, beta2, theta1, theta2, ttime) = self.uparam['angles'] print "update interval is %g seconds" % (uprate * 0.01) cnt = 0 while self.running: time.sleep(0.01) if cnt >= uprate: cnt = 0 else: cnt = cnt + 1 continue try: self.worker.getdata() except: self.worker.msg = "EPICS channels missing" break if not self.running: break if not self.worker.isbusy(): break E_act = self.worker.E_rbk #self.worker.outparam['pb_set']=int(100*(E_act-E1)/(E2-E1)) self.worker.outparam['Erbk']=("%.1f eV"%(E_act)) # do not overshoot if self.worker.check_endpoint(): self.worker.stop() break if self.worker.pntcnt > MAXARRAY: self.worker.msg = "OTF points exceed %d, abort..."%(MAXARRAY) raise Exception, self.worker.msg self.worker.msg = self.worker.msg + ' done' print self.worker.msg self.worker.savedata(self.worker.path, self.worker.file) finally: print "scan thread exit" self.worker.stop() self.worker.outparam['done']=1 self.worker.running = 0 ##!1################################################################### class epicsPGM(object): """ detector = "kei6517", "ah401", or "cadc" (default) """ def __init__(self, detector = "cadc"): # keep the identifiers consistent with the region.py module self.err = None self.pvs = {} #to monitor self.xpvs = {} #to monitor self.pvsdata = {} self.setpvs = {} self.mm = epicsMotor.motor("X03DA-PGM:MI") self.gm = epicsMotor.motor("X03DA-PGM:GR") self.detector = detector #uparam['cadc_otf']= sclock, sbuffer, srate #sclock: 0:1kHz 1:5kHz 2:10kHz 3:50kHz 4:100kHz #srate: Idle 1 10 100 Hz self.uparam={'uprate':10,\ 'ptpsp':[(560,600,10),(600,615,5)],\ 'otf':[840.0, 880.0, 1.0],\ 'angles':[-87.0, -87.0, 86.0, 86.0, 1.0],\ 'cadc_otf':[4,600,3],\ 'cadc_ptp':[2,10000,0]} #'cadc_otf':[3,1000,3],'cadc_ptp':[1,10000,0]} self.outparam={'pb_len':0,'pb_set':0,'Erbk':0,'done':0} self.running = 0 self.xmcd = None self.xmcdrunning = 0 self.pntcnt = 0 self.cmd = '???' self.msg = 'X03DA OTF (%s)' % (self.detector) self.folder = 'TEST' self.file = 'test' self.format = 'itx' self.path = "/tmp/otfdata" + '.' + self.format self.connect() def connect(self): self.err = None if 1: # PVs read via getdata() self.pvs['E'] = epv.epv('X03DA-PGM:energy') self.pvs['E_rbk'] = epv.epv('X03DA-PGM:rbkenergy') self.pvs['E_crbk'] = epv.epv('X03DA-PGM:CERBK') self.pvs['cff_rbk'] = epv.epv('X03DA-PGM:rbkcff') self.pvs['theta'] = epv.epv('X03DA-PGM:theta') self.pvs['theta_rbk'] = epv.epv('X03DA-PGM:rbktheta') self.pvs['beta'] = epv.epv('X03DA-PGM:beta') self.pvs['beta_rbk'] = epv.epv('X03DA-PGM:rbkbeta') self.pvs['grating'] = epv.epv('X03DA-PGM:grating') self.pvs['dmov'] = epv.epv('X03DA-PGM:dmov') if self.detector == 'kei6517': self.pvs['K1'] = epv.epv('X03DA-KEI11:READOUT') self.pvs['K2'] = epv.epv('X03DA-KEI12:READOUT') elif self.detector == 'ah401': self.pvs['K1'] = epv.epv('X03DA-AH401B:CH1') self.pvs['K2'] = epv.epv('X03DA-AH401B:CH2') elif self.detector == 'cadc3': self.pvs['K1'] = epv.epv('X03DA-OP:CURRENT3') self.pvs['K2'] = epv.epv('X03DA-OP:CURRENT2') else: # cadc self.pvs['K1'] = epv.epv('X03DA-OP:CURRENT1') self.pvs['K2'] = epv.epv('X03DA-OP:CURRENT2') print "selected detector: " + self.detector self.pvs['machine current (mA)'] = epv.epv('ARIDI-PCT:CURRENT') self.xpvs['HALT'] = epv.epv('X03DA-PGM:stop') self.xpvs['alldone'] = epv.epv('X03DA-PGM:dmov') # set PVs self.setpvs['set_E'] = epv.epv('X03DA-PGM:setE.PROC') self.setpvs['set_cff'] = epv.epv('X03DA-PGM:dftcff.C') self.setpvs['set_beta'] = epv.epv('X03DA-PGM:ronbeta.A ') self.setpvs['set_theta'] = epv.epv('X03DA-PGM:rontheta.A ') #self.pvs[E].pend_io() self.ResetPvsData() # Wait for all PVs to connect try: self.pvs['E'].pend_io() except: self.err = 'EPICS channels not available' print ("connect() ERROR:%s"%(self.err)) return 0 return 1 def addIgorWave(self, id, wave, figor, unit='', phystype='', axiswave=''): """ adds a one-dimensional dataset to the open igor file. id = key of the data source in the pvsdata dictionary wave = target name of the igor wave figor = file object with the open igor file unit = physical unit. is overridden by EPICS EGU field if available phystype = physical data type (see pearl-optics-import-data.itx) axiswave = wave name of independent axis """ if not id in self.pvsdata.keys(): print "id. %s unknown\n"%(id) return vec = self.pvsdata[id] # wave header and data if id in self.pvs.keys(): pvname = self.pvs[id].pvname try: unit = self.pvs[id].callBack.pv_units except: pass else: pvname = '' if pvname: line = 'X // %s\n' % (pvname) figor.write(line) line = 'WAVES/D/O/N=(%d) %s\nBEGIN\n' % (len(vec),wave) figor.write(line) svec = string.replace(str(vec),',',' ') figor.write(svec[1:len(svec)-1]+'\n') figor.write('END\n') # scaling and meta-data if unit: line = 'X setscale d 0,0,"%s",%s\n' % (unit, wave) figor.write(line) if pvname: line = 'X note %s, "PV=%s"\n' % (wave, pvname) figor.write(line) if phystype: line = 'X note %s, "PhysicalType=%s"\n' % (wave, phystype) figor.write(line) if axiswave: line = 'X note %s, "Axis1=%s"\n' % (wave, axiswave) figor.write(line) figor.write('\n') def savedata_itx(self, path='/tmp/otfdata.itx', comment=None): """ saves measured data in an igor text file. path = file name including path. the file name will be used as igor data folder name. it should not be longer than 32 characters and not contain too many special characters. non-alphanumeric characters are changed to underscores for folder names. """ try: fo = open(path,'w') except: print 'savedata_itx(): cannot open %s\n' % (path,) return line = 'IGOR\n' fo.write(line) line = 'X // X03DA OTF scan version 2013-04-02\n' fo.write(line) (datafolder, ext) = os.path.splitext(os.path.basename(path)) datafolder = re.sub('[^a-zA-Z0-9_]', '_', datafolder, count=0) if datafolder: line = '\nX newdatafolder /s/o root:raw' #fo.write(line) line = '\nX newdatafolder /s/o %s\n\n' % (datafolder) #fo.write(line) if comment: line = 'X string comment = "%s"\n\n' % (comment) # wave names should conform to the list in pearl-optics-import-data.itx self.addIgorWave('elapsed_time_secs', 'elapsed_time', fo, \ phystype='time', unit='s') self.addIgorWave('machine current (mA)', 'ringcurrent', fo, \ phystype='ring current', unit='mA', axiswave='photonenergy') self.addIgorWave('E_crbk', 'photonenergy', fo, \ phystype='photon energy', unit='eV', axiswave='elapsed_time') self.addIgorWave('cff_rbk', 'cff', fo, \ phystype='focus constant', unit='', axiswave='photonenergy') if self.scanType == "angle": self.addIgorWave('beta_rbk', 'beta_rbk', fo, \ phystype='grating angle', unit='deg', axiswave='photonenergy') self.addIgorWave('theta_rbk', 'theta_rbk', fo, \ phystype='mirror angle', unit='deg', axiswave='photonenergy') # write data if channel is available if self.pvs['K1'].getw() <> 0.0: self.addIgorWave('K1', 'current_ch1', fo, \ phystype='current', unit='A', axiswave='photonenergy') line = 'X current_ch1 *= 400/ringcurrent\n' fo.write(line) line = 'X note current_ch1, "normalized to ringcurrent"\n\n' fo.write(line) if self.pvs['K2'].getw() <> 0.0: self.addIgorWave('K2', 'current_ch2', fo, \ phystype='current', unit='A', axiswave='photonenergy') line = 'X current_ch2 *= 400/ringcurrent\n' fo.write(line) line = 'X note current_ch2, "normalized to ringcurrent"\n\n' fo.write(line) bls = blstatus.BLStatus() bls.save_itx_waves(fo) fo.close() def makefile(self, parm, set = 0, xmcd=None): ''' parm: string dictionary to be evaluated set: create the file ''' set = set keys=['FOLDER','FILE'] try: parm = eval(str(parm)) except: return (0,"ERROR:%s format"%(str(parm))) if type(parm)<>type({}): return (0,"ERROR:%s not dict"%(str(parm))) if len(parm.keys())<>2: return (0,"ERROR:%s 2 keys needed"%(str(parm))) if 'FOLDER' not in parm.keys(): return (0,"ERROR: FOLDER key mising") if 'FILE' not in parm.keys(): return (0,"ERROR: FILE key mising") upath = os.getenv('HOME')+'/Data1/' folder = parm['FOLDER'] folder = os.path.join(upath, folder) if not os.path.exists(folder): try: os.makedirs(folder) except: return (0,'ERROR mkdir %s'%(folder)) tstamp = time.strftime("%y%m%d-%H%M%S", time.localtime()) fname = "otf-%s.%s" % (tstamp, self.format) path = os.path.join(folder, fname) if set: try: fd = open(path,'w') fd.close() except: msg = '%s %s'%(sys.exc_info()[0],sys.exc_info()[1]) return (0,msg) return (1,path) def savedata(self, path='/tmp/otfdata.txt', comment=None): if self.format == 'itx': self.savedata_itx(path, comment) return try: fd=open(path,'w') except: print 'savedata(): cannot open %s\n'%(path,) return #Frithjof wants this order: keys=['E_crbk','K1','K2','K3','K4','machine current (mA)','cff_rbk','elapsed_time_secs'] #keys = self.pvs.keys() header=str(keys)[1:-1]; header = string.replace(header,"elapsed_time_secs",'time') header = string.replace(header,',',' ') #header = string.replace(header,' ','') header = string.replace(header,"'",'') header = string.replace(header,",",' ') header = string.replace(header,"_",'') header = string.replace(header,"machine current (mA)",'MCurr') fd.write(header+'\n') vec = self.pvsdata[keys[0]] cnt = len(vec)-1 print '%s data length %d\n'%(keys[0],cnt,) for i in range(cnt): txt = '' for key in keys: #print ('%s %s\n')%(i,key) vec = self.pvsdata[key] #print vec s = str(vec[i]) #s = str(dili[i].values())[1:-1];string.replace(s,',',' ') txt=txt+s+ ' ' txt=txt+'\n' fd.write(txt) fd.close() def ResetPvsData(self): self.start = time.time() self.pvsdata['timestamp'] = [0] self.pvsdata['elapsed_time_secs'] = [0] self.pvsdata['machine current (mA)'] = [0] self.pvsdata['E'] = [0] self.pvsdata['E_rbk'] = [0] self.pvsdata['E_crbk'] = [0] self.pvsdata['cff'] = [0] self.pvsdata['cff_rbk'] = [0] self.pvsdata['theta'] = [0] self.pvsdata['theta_rbk'] = [0] self.pvsdata['theta_set'] = [0] self.pvsdata['beta'] = [0] self.pvsdata['beta_rbk'] = [0] self.pvsdata['beta_set'] = [0] self.pvsdata['grating'] = [0] self.pvsdata['dmov'] = [0] self.pvsdata['K1'] = [0] self.pvsdata['K2'] = [0] #!setE def setE(self,E,fast=0, mode=None, id=None): ''' mode: 0:LINEAR 1:CIRC+, 2:CIRC-''' if fast: self.velo() #if self.E == E: # return self.E = E self.set_E = 1 def velo(self, maxspeed = 0.03): ''' set velocities to custom or maximum values ''' if maxspeed > 0.03: maxspeed = 0.03 (self.mm.VELO, self.gm.VELO) = (maxspeed, maxspeed) time.sleep(0.05) #print "mirror: vbas=%f velo=%f acc=%f"%(self.mm.VELO, self.mm.VBAS, self.mm.ACCL) #print "grating: vbas=%f velo=%f acc=%f"%(self.gm.VELO, self.gm.VBAS, self.gm.ACCL) def setDft(self): ''' reset velocities to default values ''' (self.mm.VELO, self.gm.VELO) = (0.03, 0.03) (self.mm.VBAS, self.gm.VBAS) = (0.003, 0.003) def set_EnergyScan(self, E1, E2, tr_time = 2): ''' sets up an energy scan E1 = start energy in eV E2 = end energy in eV tr_time = travel time in minutes call start_scan() to actuall start the scan ''' self.travel_time = tr_time self.E = E1 time.sleep(0.05) (self.E1, self.theta1, self.beta1) = (self.E, self.theta, self.beta) self.E = E2 time.sleep(0.05) (self.E2, self.theta2, self.beta2) = (self.E, self.theta, self.beta) self.scanType = 'energy' print "Start: E = %f, theta = %f, beta = %f" % (self.E1, self.theta1, self.beta1) print "End: E = %f, theta = %f, beta = %f\n" % (self.E2, self.theta2, self.beta2) def set_AngleScan(self, beta1, beta2, theta1, theta2, tr_time = 2): ''' sets up a linear angle scan beta1 = start angle in degrees beta2 = end angle in degrees theta1 = start angle in degrees theta2 = end angle in degrees tr_time is travel time in minutes call start_scan() to actuall start the scan ''' self.travel_time = tr_time self.theta1 = theta1 self.theta2 = theta2 self.beta1 = beta1 self.beta2 = beta2 self.scanType = 'angle' print "Start: theta = %f, beta = %f" % (self.theta1, self.beta1) print "End: theta = %f, beta = %f\n" % (self.theta2, self.beta2) def start_scan(self): ''' starts an on-the-fly scan which was set up by one of the set_XxxxScan() functions ''' self.msg = "moving to start..." print self.msg mbeta = abs(self.beta1 - self.beta_rbk) > 0.0001 mtheta = abs(self.theta1 - self.theta_rbk) > 0.0001 # note: do not read back from self.beta or self.theta! if mbeta: self.set_beta = self.beta1 if mtheta: self.set_theta = self.theta1 nretries = 5 while (mbeta or mtheta) and (nretries > 0): nretries = nretries - 1 if self.waitdone(timeout = 30 / 0.1): time.sleep(2) print "Readback: theta = %f, beta = %f" % (self.theta_rbk, self.beta_rbk) mbeta = abs(self.beta1 - self.beta_rbk) > 0.0001 mtheta = abs(self.theta1 - self.theta_rbk) > 0.0001 if mbeta: self.set_beta = self.beta1 if mtheta: self.set_theta = self.theta1 if not mbeta and not mtheta: self.msg = "start point reached" else: self.msg = "timeout while moving to start" return False if mbeta or mtheta: self.msg = "failed to reach start point" print self.msg return False print self.msg maxspeed = 0.03 tr_time = self.travel_time * 60 dbeta = abs(self.beta2 - self.beta1) dtheta = abs(self.theta2 - self.theta1) mbeta = dbeta > 0.0001 mtheta = dtheta > 0.0001 print "dtheta = %f, dbeta = %f\n" % (dtheta, dbeta) vtheta = dtheta / tr_time vbeta = dbeta / tr_time print "vtheta = %f, vbeta = %f\n"%(vtheta,vbeta) if vtheta > maxspeed or not mtheta: vtheta = maxspeed if vbeta > maxspeed or not mbeta: vbeta = maxspeed (self.mm.VBAS, self.gm.VBAS) = (vtheta/10, vbeta/10) time.sleep (0.1) (self.mm.VELO, self.gm.VELO) = (vtheta, vbeta) time.sleep (0.1) print "mirror: velo=%f vbas=%f acc=%f" % (self.mm.VELO, self.mm.VBAS, self.mm.ACCL) print "grating: velo=%f vbas=%f acc=%f\n" % (self.gm.VELO, self.gm.VBAS, self.gm.ACCL) #self.outparam['pb_len']=int(E2-E1) self.ResetPvsData() if mbeta: self.set_beta = self.beta2 if mtheta: self.set_theta = self.theta2 result = self.waitbusy() if result: self.msg = "flying to destination..." else: self.msg = "timeout while starting scan" print self.msg return result def check_endpoint(self): """ returns true if the scan has passed the end point, False otherwise. """ result = ((self.beta_rbk - self.beta2) * (self.beta2 - self.beta1) > 0) \ or ((self.theta_rbk - self.theta2) * (self.theta2 - self.theta1) > 0) return result def otfp(self, E1, E2, tr_time = 2): ''' starts an energy scan obsolete - use set_EnergyScan() and start_scan() instead ''' self.set_EnergyScan(E1, E2, tr_time) self.start_scan() ######################################################################## def __getattr__(self, attrname): try: if (attrname == 'E'): return self.pvs['E'].getw() elif (attrname == 'E_rbk'): return self.pvs['E_rbk'].getw() elif (attrname == 'E_crbk'): return self.pvs['E_crbk'].getw() elif (attrname == 'cff'): return self.pvs['cff'].getw() elif (attrname == 'cff_rbk'): return self.pvs['cff_rbk'].getw() elif (attrname == 'theta'): return self.pvs['theta'].getw() elif (attrname == 'theta_rbk'): return self.pvs['theta_rbk'].getw() elif (attrname == 'beta'): return self.pvs['beta'].getw() elif (attrname == 'beta_rbk'): return self.pvs['beta_rbk'].getw() elif (attrname == 'alldone'): return self.xpvs['alldone'].getw() elif (attrname == 'dmov'): return self.pvs['dmov'].getw() elif (attrname == 'grating'): return self.pvs['grating'].getw() elif (attrname == 'K1'): return self.pvs['K1'].getw() elif (attrname == 'K2'): return self.pvs['K2'].getw() #else: raise AttributeError, attrname else: return self.__dict__[attrname] except: msg = 'epicsPGM() __getattr__ %s %s'%(sys.exc_info()[0],sys.exc_info()[1]) print msg def __setattr__(self, attrname, value): try: if (attrname == 'E'): self.pvs['E'].putw(value) elif (attrname == 'set_E'): self.setpvs['set_E'].putw(value) elif (attrname == 'HALT'): self.xpvs['HALT'].putw(1) elif (attrname == 'set_cff'): self.pvs['set_cff'].putw(value) elif (attrname == 'set_theta'): self.setpvs['set_theta'].putw(value) elif (attrname == 'set_beta'): self.setpvs['set_beta'].putw(value) #else: raise AttributeError, attrname else: self.__dict__[attrname] = value except: msg = 'epicsPGM() __setattr__ %s %s'%(sys.exc_info()[0],sys.exc_info()[1]) print msg def xmcdgo(self): raise Exception, "xmcdgo method not implemented for PEARL" #!go############################## def otfgo(self): ''' handles energy scan command ''' if self.running: return print "otfgo..." self.pntcnt = 0 (E1,E2,ttime)=self.uparam['otf'] self.running = 1 self.set_EnergyScan(E1, E2, ttime) self.start_scan() self.thread = OTFscan_th(self) def go_angle(self): ''' handles angle scan command ''' if self.running: return print "go_angle..." self.pntcnt = 0 (beta1, beta2, theta1, theta2, ttime) = self.uparam['angles'] self.running = 1 self.set_AngleScan(beta1, beta2, theta1, theta2, ttime) self.start_scan() self.thread = OTFscan_th(self) def stop(self): try: self.thread.running = 0 self.thread.join() except: pass self.HALT=1 self.setDft() def getdata(self): #if not self.pvs['K1'].checkMonitor(): # return if self.pntcnt == 0: self.pvsdata['elapsed_time_secs'] = [time.time() - self.start] self.pvsdata['timestamp'] = [time.ctime()] for pv in self.pvs.keys(): self.pvsdata[pv] = [self.pvs[pv].getw()] else: self.pvsdata['elapsed_time_secs'].append(time.time() - self.start) self.pvsdata['timestamp'].append(time.ctime()) for pv in self.pvs.keys(): self.pvsdata[pv].append(self.pvs[pv].getw() ) self.pntcnt +=1 #tstamp =time.strftime("%d/%b/%y %H:%M:%S",time.localtime()) def isbusy(self): if self.alldone: return 0 else: return 1 def waitbusy(self): """ wait until motors have started """ tick = 0 #print 'waitbusy...' while not self.isbusy(): time.sleep(0.1) tick = tick+1 if tick >= 5: return 0 #print '.' return 1 def waitdone(self, timeout = 100): self.waitbusy() tick = 0 while self.isbusy(): if not self.running: break time.sleep(0.1) tick = tick+1 self.msg = "current energy %.1f eV" % (self.E_crbk) if tick >= timeout: self.msg = "TIMEOUT" return 0 return 1 def test(): gds = epicsPGM() gds.ResetPvsData() gds.getdata() print str(gds.pvsdata) gds.ResetPvsData() gds.getdata() print str(gds.pvsdata) if __name__ == '__main__': test()