diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..770c04b Binary files /dev/null and b/.DS_Store differ diff --git a/aliases/alvra.py b/aliases/alvra.py index 9f6ad15..fd857a8 100755 --- a/aliases/alvra.py +++ b/aliases/alvra.py @@ -211,7 +211,7 @@ aliases = { 'alias' : 'PhaseShifter', 'z_und' : 127, 'desc' : 'Experiment laser phase shifter', - 'eco_type' : 'devices_general.timing.PhaseShifterAramis'}, + 'eco_type' : 'devices_general.alvratiming.PhaseShifterAramis'}, # 'http://sf-daq-2:10000' : { # 'alias' : 'DetJF', # 'z_und' : 127, @@ -219,7 +219,7 @@ aliases = { # 'eco_type' : 'devices_general.detectors.JF'}, # 'SLAAR21-LTIM01-EVR0' : { # 'alias' : 'LaserShutter', -# 'z_und' : 142, +# 'z_und' : 127, # 'desc' : 'Laser Shutter', # 'eco_type' : 'loptics.laser_shutter.laser_shutter'}, # = dict( diff --git a/devices_general/alvratiming.py b/devices_general/alvratiming.py new file mode 100644 index 0000000..60fb564 --- /dev/null +++ b/devices_general/alvratiming.py @@ -0,0 +1,212 @@ +from epics import PV +import os +import numpy as np +import time +from threading import Thread + +_basefolder = "/sf/alvra/config/lasertiming" +_posTypes = ['user','dial','raw'] + +def timeToStr(value,n=12): + fmt = "%%+.%df" % n + value = fmt%value + #print(value) + idx_point = value.find(".") + ret_str = value[:idx_point] + " ." + ngroups = (len(value)-idx_point)//3 + for n in range(ngroups): + ret_str += " %s" % value[idx_point+1+3*n:idx_point+1+3*(n+1)] + #print(idx_point+1+3*n,idx_point+1*3*(n-1),ret_str) + return ret_str + + +class Storage(object): + def __init__(self,pvname): + self._filename = os.path.join(_basefolder,pvname) + self.pvname = pvname + self.last_read_time = -1 + + @property + def last_modified_time(self): + if os.path.isfile(self._filename): + return os.stat(self._filename).st_mtime + else: + return -1 + + @property + def value(self): + lmod = self.last_modified_time + if os.path.isfile(self._filename): + # need to read again ? + if self.last_read_time == -1 or lmod > self.last_read_time : + #print("actually reading") + value = float(np.loadtxt(self._filename)) + self.last_read_time = lmod + self.last_read = value + else: + value = self.last_read + else: + print("could not read",self._filename) + value = 0 + return value + + def store(self,value): + with open(self._filename,"w") as f: + f.write("# %s\n"%time.asctime()) + f.write("%.15f"%value) + + +class Pockels_trigger(PV): + """ this class is needed to store the offset in files and read in s """ + def __init__(self,pv_basename): + pvname = pv_basename + "-RB" + PV.__init__(self,pvname) + self._pv_setvalue = PV(pv_basename + "-SP") + self._filename = os.path.join(_basefolder,pvname) + self._storage = Storage(pvname) + + @property + def offset(self): return self._storage.value + + def get_dial(self): + return super().get()*1e-6 + + def get(self): + """ convert time to sec """ + return self.get_dial()-self.offset + + def store(self,value=None): + if value == None: value = self.get_dial() + self._storage.store( value ) + + def move(self,value): + dial = value + self.offset + self._pv_setvalue.put(dial*1e6) + + def set(self,value): + newoffset = self.get_dial()-value + self.store(newoffset) + + + + def __repr__(self): + dial = timeToStr( self.get_dial(),n=12 ) + user = timeToStr( self.get(),n=12 ) + return "Pockel Trigger PV: %s user , dial = %s, %s"%(self.pvname,user,dial) + +class Phase_shifter(PV): + """ this class is needed to store the offset in files and read in ps """ + def __init__(self,pv_basename="SLAAR01-TSPL-EPL"): + pvname = pv_basename+":CURR_DELTA_T" + PV.__init__(self,pvname) + self._filename = os.path.join(_basefolder,pvname) + self._pv_setvalue = PV(pv_basename + ":NEW_DELTA_T") + self._pv_execute = PV(pv_basename + ":SET_NEW_PHASE.PROC") + self._storage = Storage(pvname) + + @property + def offset(self): return self._storage.value + + + def get_dial(self): + return super().get()*1e-12 + + def get(self): + """ convert time to sec """ + return self.get_dial()-self.offset + + def store(self,value=None): + if value == None: value = self.get_dial() + self._storage.store( value ) + + def move(self,value): + dial = value + self.offset + dial_ps = dial*1e12 + self._pv_setvalue.put(dial_ps) + time.sleep(0.1) + self._pv_execute.put(1) + while( np.abs(self.get()-value) > 100e-15 ): time.sleep(0.2) + + def set(self,value): + newoffset = self.get_dial()-value + self.store(newoffset) + + + def __repr__(self): + dial = timeToStr( self.get_dial(),n=15 ) + user = timeToStr( self.get(),n=15 ) + return "Phase Shifter: user,dial = %s , %s"%(user,dial) + + +#_pockels_in = Pockels_trigger("SLAAR-LTIM01-EVR0:Pul2-Delay") +#_pockels_out = Pockels_trigger("SLAAR-LTIM01-EVR0:Pul3-Delay") +#_phase_shifter = Phase_shifter("SLAAR01-TSPL-EPL") + + +class PhaseShifterAramis: + def __init__(self,Id,name=None,elog=None): + self.Id = Id + self._pshifter = Phase_shifter(Id) + self._elog = elog + self.name = name + + def changeTo(self, value, hold=False, check=True): + """ Adjustable convention""" + + mover = lambda value: self._pshifter.move(\ + value) + return Changer( + target=value, + parent=self, + mover=mover, + hold=hold, + stopper=None) + + def stop(self): + """ Adjustable convention""" + pass + + + def get_current_value(self,posType='user',readback=True): + """ Adjustable convention""" + _keywordChecker([('posType',posType,_posTypes)]) + if posType == 'user': + return self._pshifter.get() + if posType == 'dial': + return self._pshifter.get_dial() + + def set_current_value(self,value,posType='user'): + """ Adjustable convention""" + _keywordChecker([('posType',posType,_posTypes)]) + if posType == 'user': + return self._motor.set(value) + +class Changer: + def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None): + self.target = target + self._mover = mover + self._stopper = stopper + self._thread = Thread(target=self._mover,args=(target,)) + if not hold: + self._thread.start() + + def wait(self): + self._thread.join() + + def start(self): + self._thread.start() + + def status(self): + if self._thread.ident is None: + return 'waiting' + else: + if self._isAlive: + return 'changing' + else: + return 'done' + def stop(self): + self._stopper() + +def _keywordChecker(kw_key_list_tups): + for tkw,tkey,tlist in kw_key_list_tups: + assert tkey in tlist, "Keyword %s should be one of %s"%(tkw,tlist) diff --git a/endstations/alvra_prime.py b/endstations/alvra_prime.py index 4d43c5a..4433e7a 100755 --- a/endstations/alvra_prime.py +++ b/endstations/alvra_prime.py @@ -38,4 +38,5 @@ class microscope: ### Microscope focus and zoom motors ### self.focus = MotorRecord(Id+':FOCUS') - self.zoom = MotorRecord(Id+':ZOOM') \ No newline at end of file + self.zoom = MotorRecord(Id+':ZOOM') + diff --git a/timing/alvralasertiming.py b/timing/alvralasertiming.py new file mode 100644 index 0000000..658762b --- /dev/null +++ b/timing/alvralasertiming.py @@ -0,0 +1,235 @@ +from epics import PV +import os +import numpy as np +import time +from ..devices_general.utilities import Changer + + +_basefolder = "/sf/alvra/config/lasertiming" + +def timeToStr(value,n=12): + fmt = "%%+.%df" % n + value = fmt%value + #print(value) + idx_point = value.find(".") + ret_str = value[:idx_point] + " ." + ngroups = (len(value)-idx_point)//3 + for n in range(ngroups): + ret_str += " %s" % value[idx_point+1+3*n:idx_point+1+3*(n+1)] + #print(idx_point+1+3*n,idx_point+1*3*(n-1),ret_str) + return ret_str + +def niceTimeToStr(delay,fmt="%+.0f"): + a_delay = abs(delay) + if a_delay >= 1: + ret = fmt % delay + "s" + elif 1e-3 <= a_delay < 1: + ret = fmt % (delay*1e3) + "ms" + elif 1e-6 <= a_delay < 1e-3: + ret = fmt % (delay*1e6) + "us" + elif 1e-9 <= a_delay < 1e-6: + ret = fmt % (delay*1e9) + "ns" + elif 1e-12 <= a_delay < 1e-9: + ret = fmt % (delay*1e12) + "ps" + elif 1e-15 <= a_delay < 1e-12: + ret = fmt % (delay*1e12) + "fs" + elif 1e-18 <= a_delay < 1e-15: + ret = fmt % (delay*1e12) + "as" + elif a_delay < 1e-18: + ret = "0s" + else: + ret = str(delay) +"s" + return ret + + +class Storage(object): + def __init__(self,pvname): + self._filename = os.path.join(_basefolder,pvname) + self.pvname = pvname + self.last_read_time = -1 + + @property + def last_modified_time(self): + if os.path.isfile(self._filename): + return os.stat(self._filename).st_mtime + else: + return -1 + + @property + def value(self): + lmod = self.last_modified_time + if os.path.isfile(self._filename): + # need to read again ? + if self.last_read_time == -1 or lmod > self.last_read_time : + #print("actually reading") + value = float(np.loadtxt(self._filename)) + self.last_read_time = lmod + self.last_read = value + else: + value = self.last_read + else: + print("could not read",self._filename) + value = 0 + return value + + def store(self,value): + with open(self._filename,"w") as f: + f.write("# %s\n"%time.asctime()) + f.write("%.15f"%value) + + +class Pockels_trigger(PV): + """ this class is needed to store the offset in files and read in s """ + def __init__(self,pv_basename): + pvname = pv_basename + "-RB" + PV.__init__(self,pvname) + self._pv_setvalue = PV(pv_basename + "-SP") + self._filename = os.path.join(_basefolder,pvname) + self._storage = Storage(pvname) + + @property + def offset(self): return self._storage.value + + def get_dial(self): + return np.round(super().get()*1e-6,9) + + def get(self): + """ convert time to sec """ + return self.get_dial()-self.offset + + def store(self,value=None): + if value == None: value = self.get_dial() + self._storage.store( value ) + + def move(self,value): + dial = value + self.offset + self._pv_setvalue.put(dial*1e6) + + def set(self,value): + newoffset = self.get_dial()-value + self.store(newoffset) + + + + def __repr__(self): + dial = timeToStr( self.get_dial(),n=12 ) + user = timeToStr( self.get(),n=12 ) + return "Pockel Trigger PV: %s user , dial = %s, %s"%(self.pvname,user,dial) + +_OSCILLATOR_PERIOD = 1/71.368704e6 + +class Phase_shifter(PV): + """ this class is needed to store the offset in files and read in ps """ + def __init__(self,pv_basename="SLAAR01-TSPL-EPL",dial_max=14.0056e-9,precision=100e-15): + pvname = pv_basename+":CURR_DELTA_T" + PV.__init__(self,pvname) + self._filename = os.path.join(_basefolder,pvname) + self._pv_setvalue = PV(pv_basename + ":NEW_DELTA_T") + self._pv_execute = PV(pv_basename + ":SET_NEW_PHASE.PROC") + self._storage = Storage(pvname) + self.dial_max = dial_max + self.retry = precision + + @property + def offset(self): return self._storage.value + + + def get_dial(self): + return super().get()*1e-12 + + def get(self): + """ convert time to sec """ + return self.get_dial()-self.offset + + def store(self,value=None): + if value == None: value = self.get_dial() + self._storage.store( value ) + + def move(self,value,accuracy=None): + if accuracy is None: accuracy = self.retry + dial = value + self.offset + dial = np.mod(dial,_OSCILLATOR_PERIOD) + if dial > self.dial_max: dial = self.dial_max + dial_ps = dial*1e12 + self._pv_setvalue.put(dial_ps) + time.sleep(0.1) + self._pv_execute.put(1) + #print(accuracy) + while( np.abs(self.get_dial()-dial) > accuracy ): + #print(np.abs(self.get_dial()-dial)) + time.sleep(0.2) + + def set(self,value): + newoffset = self.get_dial()-value + newoffset = np.mod(newoffset,_OSCILLATOR_PERIOD) + self.store(newoffset) + + + def __repr__(self): + dial = timeToStr( self.get_dial(),n=15 ) + user = timeToStr( self.get(),n=15 ) + return "Phase Shifter: user,dial = %s , %s"%(user,dial) + + +_slicer_gate = Pockels_trigger("SLAAR-LTIM01-EVR0:Pul2-Delay") +_sdg1 = Pockels_trigger("SLAAR-LTIM01-EVR0:Pul3-Delay") +_phase_shifter = Phase_shifter("SLAAR01-TSPL-EPL") + + +_POCKELS_CELL_RESOLUTION = 7e-9 +class Lxt(object): + def __init__(self): + self.sdg1 = _sdg1 + self.slicer_gate = _slicer_gate + self.phase_shifter = _phase_shifter + self.Id = 'SLAAR01-TSPL-EPL' + self.name = 'lxt' + self.elog = None + + def move_sdg(self,value): + self.sdg1.move(value) + + def move(self,value,accuracy=None): + self.sdg1.move(-value) + self.slicer_gate.move(-value) + self.phase_shifter.move(value,accuracy=accuracy) + + def set(self,value): + self.phase_shifter.set(value) + self.sdg1.set(-value) + + def get(self): + # pulses are at SOME_IDX*OSCILLATOR_PERIOD-PHASESHITER + # the -PHASESHITER is due to the inverted sign + phase_shifter = self.phase_shifter.get() + sdg1_delay = self.sdg1.get() + + idx_pulse = (sdg1_delay+phase_shifter)/_OSCILLATOR_PERIOD + + delay = int(idx_pulse)*_OSCILLATOR_PERIOD - phase_shifter + return -delay + + def changeTo(self, value, hold=False): + """ Adjustable convention""" + + changer = lambda value: self.move(\ + value) + return Changer( + target=value, + parent=self, + changer=changer, + hold=hold, + stopper=None) + + + def get_current_value(self): + return self.get() + + def set_current_value(self,value): + self.set(value) + + def __repr__(self): + delay = niceTimeToStr(lxt.get()) + return "delay = %s"%(delay) + +lxt = Lxt()