From f39ca1fbcdf6520935d2dae83f8be03f95e6c2da Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 11 Aug 2020 23:55:12 +0200 Subject: [PATCH] merging all laser timing files --- slic/devices/general/timing.py | 196 +++++++++++++++++++++++++++-- slic/devices/timing/lasertiming.py | 40 ++++++ 2 files changed, 223 insertions(+), 13 deletions(-) diff --git a/slic/devices/general/timing.py b/slic/devices/general/timing.py index 444a96a1..1521eea0 100644 --- a/slic/devices/general/timing.py +++ b/slic/devices/general/timing.py @@ -4,8 +4,15 @@ import numpy as np import time from slic.core.task import Task -_basefolder = "/sf/bernina/config/com/data/src/lasertiming" + +#TODO +#bernina +PS = "SLAAR02-TSPL-EPL" +_basefolder = "/sf/bernina/config/eco/offsets" +#alvra: +PS = "SLAAR01-TSPL-EPL" _basefolder = "/sf/alvra/config/lasertiming" + _posTypes = ["user", "dial", "raw"] @@ -22,7 +29,31 @@ def timeToStr(value, n=12): 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): + """ this class is needed to store the offset in files and read in s """ def __init__(self, pvname): self._filename = os.path.join(_basefolder, pvname) @@ -61,10 +92,11 @@ class Storage(object): class Pockels_trigger(PV): - def __init__(self, pv_basename): - pvname = pv_basename + "-RB" + def __init__(self, pv_get, pv_set, pv_offset_get): #TODO make offset optional + pvname = pv_get PV.__init__(self, pvname) - self._pv_setvalue = PV(pv_basename + "-SP") + self._pv_offset_get = PV(pv_offset_get) + self._pv_setvalue = PV(pv_set) self._filename = os.path.join(_basefolder, pvname) self._storage = Storage(pvname) @@ -73,7 +105,7 @@ class Pockels_trigger(PV): return self._storage.value def get_dial(self): - return super().get() * 1e-6 + return np.round(super().get() * 1e-6, 9) + self._pv_offset_get.get() * 1e-9 - 7.41e-9 #TODO what is the constant? def get(self): """ convert time to sec """ @@ -98,16 +130,20 @@ class Pockels_trigger(PV): 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 """ +_OSCILLATOR_PERIOD = 1 / 71.368704e6 - def __init__(self, pv_basename="SLAAR01-TSPL-EPL"): + +class Phase_shifter(PV): + + def __init__(self, pv_basename=PS, 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): @@ -125,17 +161,25 @@ class Phase_shifter(PV): value = self.get_dial() self._storage.store(value) - def move(self, 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) - while np.abs(self.get() - value) > 100e-15: + #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): @@ -144,9 +188,135 @@ class Phase_shifter(PV): 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") + +#TODO + +#bernina +sg_get = "SLAAR-LTIM02-EVR0:Pul3-Delay-RB" +sg_set = "SLAAR-LTIM02-EVR0:Pul3_NEW_DELAY" +sg_off = "SLAAR-LTIM02-EVR0:UnivDlyModule1-Delay1-RB" + +sdg_get = "SLAAR-LTIM02-EVR0:Pul2-Delay-RB" +sdg_set = "SLAAR-LTIM02-EVR0:Pul2_NEW_DELAY" +sdg_off = "SLAAR-LTIM02-EVR0:UnivDlyModule1-Delay0-RB" + +#alvra: +sg = "SLAAR-LTIM01-EVR0:Pul2-Delay" +sg_get = sg + "-RB" +sg_set = sg + "-SP" +sg_offset = "SLAAR-LTIM01-EVR0:UnivDlyModule1-Delay1-RB" + +sdg = "SLAAR-LTIM01-EVR0:Pul3-Delay" +sdg_get = sdg + "-RB" +sdg_set = sdg + "-SP" + + + +_slicer_gate = Pockels_trigger(sg_get, sg_set, sg_off) +_sdg1 = Pockels_trigger(sdg_get, sdg_set, sdg_off) +_phase_shifter = Phase_shifter(PS) + + +_POCKELS_CELL_RESOLUTION = 7e-9 + + +class Lxt(object): + + def __init__(self, accuracy_poly=[100e-15, 1e-7]): + self.sdg1 = _sdg1 + self.slicer_gate = _slicer_gate + self.phase_shifter = _phase_shifter + self.Id = PS + self.name = "lxt" + self.elog = None + self.accuracy_poly = accuracy_poly + + def move_sdg(self, value): + self.sdg1.move(value) + + def move(self, value, accuracy=None): + self.sdg1.move(-value) + self.slicer_gate.move(-value) + if not accuracy: + accuracy = np.abs(value) * self.accuracy_poly[1] + self.accuracy_poly[0] + self.phase_shifter.move(value, accuracy=accuracy) + + def set(self, value): + self.phase_shifter.set(value) + self.slicer_gate.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 set_target_value(self, value, hold=False): + """ Adjustable convention""" + changer = lambda: self.move(value) + return Task(changer, hold=hold) + + 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) + + + + +class eTiming: + + def __init__(self, Id): + self.Id = Id + self.name = "Globi laser electronic timing (us)" + self._eTimeSet = PV("SLAAR01-LTIM-PDLY:DELAY") # current laser timing number from Edwin's timing PV + self._eTimeRBK = PV("SLAAR-LGEN:DLY_OFFS1") # readback on Edwin's timing PV + self._moving = PV("SLAAR01-LTIM-PDLY:WAITING") + + def get_current_value(self): + return self._eTimeRBK.get() * 1e6 # convert from us to ps + + def wait_for_valid_value(self): + tval = np.nan + while not np.isfinite(tval): + tval = self.get_current_value() + return tval + + def move_and_wait(self, value, checktime=0.01): + self._eTimeSet.put(value) + time.sleep(0.2) + while self._moving.get() == 0.0: + time.sleep(checktime) + + def set_current_value(self, value): + self._eTimeSet.put(value) + + def __repr__(self): + return self.__str__() + + def set_target_value(self, value, hold=False): + changer = lambda: self.move_and_wait(value) + return Task(changer, hold=hold) + + def __str__(self): + eTimingRBKStr = str(self.get_current_value()) + eTimingSetStr = self._eTimeSet.get(as_string=True) + s = "Globi laser electronic timing\n" + s += "- electronic timing readback (ps): {}\n".format(eTimingRBKStr) + s += "- electronic timing setpoint (ps): {}".format(eTimingSetStr) + return s + class PhaseShifterAramis: diff --git a/slic/devices/timing/lasertiming.py b/slic/devices/timing/lasertiming.py index ac57b3cd..1521eea0 100644 --- a/slic/devices/timing/lasertiming.py +++ b/slic/devices/timing/lasertiming.py @@ -13,6 +13,8 @@ _basefolder = "/sf/bernina/config/eco/offsets" PS = "SLAAR01-TSPL-EPL" _basefolder = "/sf/alvra/config/lasertiming" +_posTypes = ["user", "dial", "raw"] + def timeToStr(value, n=12): fmt = "%%+.%df" % n @@ -317,3 +319,41 @@ class eTiming: +class PhaseShifterAramis: + + def __init__(self, Id, name=None, elog=None, z_undulator=None, description=None): + self.Id = Id + self._pshifter = Phase_shifter(Id) + self._elog = elog + self.name = name + + def set_target_value(self, value, hold=False, check=True): + """ Adjustable convention""" + mover = lambda: self._pshifter.move(value) + return Task(mover, hold=hold) + + 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) + + +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) + + +