Files
x03da/script/otf_thread.py
2015-11-02 18:31:58 +01:00

702 lines
23 KiB
Python

"""
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()