from types import SimpleNamespace from time import sleep from epics import PV from slic.core.adjustable import Adjustable, PVAdjustable from slic.devices.device import Device from slic.devices.simpledevice import SimpleDevice from slic.devices.general.motor import Motor from slic.devices.general.delay_stage import DelayStage from slic.devices.general.smaract import SmarActAxis from slic.devices.general.shutter import Shutter from slic.devices.xoptics.dcm import CoupledDoubleCrystalMonoEnergyWithTimeCorrection from undulator import Undulators ENERGY_OFFSET = 13 # set the mono-undulators energy offset here! laser_pitch = SmarActAxis("SARES11-XICM125:ROX1") laser_trans = SmarActAxis("SARES11-XICM125:TRX1") laser_yaw = SmarActAxis("SARES11-XICM125:ROY1") microscope_pitch = SmarActAxis("SARES11-XMI125:ROY1") microscope_yaw = SmarActAxis("SARES11-XMI125:ROZ1") laser_mod = PVAdjustable("SIN-TIMAST-TMA:Evt-23-Off-SP", process_time=1, name="Laser Mod") CDCMEWTC = CoupledDoubleCrystalMonoEnergyWithTimeCorrection(limit_low=2450, limit_high=2520) PSSS = Motor("SARFE10-PSSS059:MOTOR_Y3", name="PSSS XTAL y") opo_delay = PVAdjustable("SLAAR03-LTIM-PDLY:DELAY", name="OPO Delay") XrayShutter = Shutter("SARFE10-OPSH059") import requests class UndulatorEnergy(PVAdjustable): def __init__(self, process_time=1): self.process_time = process_time super().__init__("SARUN:USER-DELTA", "SARUN03-UIND030:FELPHOTENE", process_time=0.2) # process_time here is only to insert the delta into the panel # assert self.units == "keV" print('units={}'.format(self.units)) self.units = "eV" def set_target_value(self, value): #value /= 1000 current = self.get_current_value() delta = (value - current) print(f"delta: {value} - {current} = {delta}") delta /= 1000 task = super().set_target_value(delta) sleep(0.1) # task.wait() # make sure the delta is set correctly #TODO: the following should be a task? url_go = "http://sf-daq-mgmt.psi.ch:8090/run/Undulators/K_AR_scale" reply = requests.get(url_go)#.json() print('Pressing "GO" produced:', reply) sleep(self.process_time) # wait for undulator change return task def get_current_value(self): value = super().get_current_value() - 0.02037 value *= 1000 return value def stop(self): url_abort = "http://sf-daq-mgmt.psi.ch:8090/abort" reply = requests.get(url_abort)#.json() print('Pressing "Abort" produced:', reply) class UndulatorCoupledDoubleCrystalMonoEnergy(Adjustable): def __init__(self, ID, name=None, process_time=1): # self.und = UndulatorEnergy(process_time=process_time) self.und = Undulators(energy_offset=ENERGY_OFFSET) pvname_setvalue = "SAROP11-ARAMIS:ENERGY_SP" pvname_readback = "SAROP11-ARAMIS:ENERGY" pvname_moving = "SAROP11-ODCM105:MOVING" # This is the WRONG coupling, we need to make sure it is disabled! pvname_ebeam_coupling = "SGE-OP2E-ARAMIS:MODE_SP" pv_setvalue = PV(pvname_setvalue) pv_readback = PV(pvname_readback) pv_moving = PV(pvname_moving) pv_ebeam_coupling = PV(pvname_ebeam_coupling) units = pv_readback.units super().__init__(ID, name=name, units=units) self.pvnames = SimpleNamespace( setvalue = pvname_setvalue, readback = pvname_readback, moving = pvname_moving, ebeam_coupling = pvname_ebeam_coupling ) self.pvs = SimpleNamespace( setvalue = pv_setvalue, readback = pv_readback, moving = pv_moving, ebeam_coupling = pv_ebeam_coupling ) def get_current_value(self): return self.pvs.readback.get() def set_target_value(self, value, hold=False): changer = lambda: self.move_and_wait(value) return self._as_task(changer, hold=hold, stopper=self.stop) def move_and_wait(self, value, wait_time=0.1): # self.disable_ebeam_coupling() self.pvs.setvalue.put(value) self.und.set_target_value(value) sleep(1) #TODO: is this correct? while self.is_moving(): print("mono is moving") sleep(wait_time) def is_moving(self): moving = self.pvs.moving.get() return bool(moving) # def enable_ebeam_coupling(self): # self.pvs.ebeam_coupling.put(1) # def disable_ebeam_coupling(self): # self.pvs.ebeam_coupling.put(0) # @property # def ebeam_coupling(self): # return self.pvs.ebeam_coupling.get(as_string=True) class UndulatorCoupledDoubleCrystalMonoEnergyWithTimeCorrection(Adjustable): def __init__(self, ID="UCDCMEWTC", name="Alvra DCM Undulator-coupled energy with time correction", limit_low=None, limit_high=None, process_time=1): # self.und = UndulatorEnergy(process_time=process_time) self.und = Undulators(energy_offset=ENERGY_OFFSET) self.wait_time = 0.1 self.limit_low = limit_low self.limit_high = limit_high pvname_setvalue = "SAROP11-ARAMIS:ENERGY_SP" pvname_readback = "SAROP11-ARAMIS:ENERGY" pvname_moving = "SAROP11-ODCM105:MOVING" # This is the WRONG coupling, we need to make sure it is disabled! pvname_ebeam_coupling = "SGE-OP2E-ARAMIS:MODE_SP" pv_setvalue = PV(pvname_setvalue) pv_readback = PV(pvname_readback) pv_moving = PV(pvname_moving) pv_ebeam_coupling = PV(pvname_ebeam_coupling) self.timing = Motor("SLAAR11-LMOT-M452:MOTOR_1") # self.electron_energy_rb = PV("SARCL02-MBND100:P-READ") # self.electron_energy_sv = PV("SGE-OP2E-ARAMIS:E_ENERGY_SP") units = pv_readback.units super().__init__(ID, name=name, units=units) self.pvnames = SimpleNamespace( setvalue = pvname_setvalue, readback = pvname_readback, moving = pvname_moving, ebeam_coupling = pvname_ebeam_coupling ) self.pvs = SimpleNamespace( setvalue = pv_setvalue, readback = pv_readback, moving = pv_moving, ebeam_coupling = pv_ebeam_coupling ) def get_current_value(self): return self.pvs.readback.get() def set_target_value(self, value, hold=False): ll = self.limit_low if ll is not None: if value < ll: msg = f"requested value is outside the allowed range: {value} < {ll}" print(msg) raise KeyboardInterrupt(msg) lh = self.limit_high if lh is not None: if value > lh: msg = f"requested value is outside the allowed range: {value} > {lh}" print(msg) raise KeyboardInterrupt(msg) wait_time = self.wait_time self.disable_ebeam_coupling() current_energy = self.get_current_value() delta_energy = value - current_energy timing = self.timing current_delay = timing.get_current_value() delta_delay = convert_E_to_distance(delta_energy) target_delay = current_delay + delta_delay print(f"Energy = {current_energy} -> delta = {delta_energy} -> {value}") print(f"Delay = {current_delay} -> delta = {delta_delay} -> {target_delay}") timing.set_target_value(target_delay).wait() self.move_and_wait(value) sleep(1) # wait so that the set value has changed print("start waiting for DCM") while self.is_moving(): #TODO: moving PV seems broken print("mono is moving") sleep(wait_time) # print("start waiting for electron beam") # while abs(self.electron_energy_rb.get() - self.electron_energy_sv.get()) > 0.25: # sleep(wait_time) sleep(wait_time) def move_and_wait(self, value, wait_time=0.1): self.disable_ebeam_coupling() self.pvs.setvalue.put(value) self.und.set_target_value(value) sleep(1) #TODO: is this correct? while self.is_moving(): print("mono is moving") sleep(wait_time) def is_moving(self): moving = self.pvs.moving.get() return bool(moving) # def enable_ebeam_coupling(self): # self.pvs.ebeam_coupling.put(1) def disable_ebeam_coupling(self): self.pvs.ebeam_coupling.put(0) # @property # def ebeam_coupling(self): # return self.pvs.ebeam_coupling.get(as_string=True) # adjust this function!! # be careful, CDCMEWTC has its own conversion!!! def convert_E_to_distance(E): ### returns a value in mm! #return 0.0061869 * E ### calibration May 2021, S k-edge, 2450 eV, corresponds to 41.2 fs/eV --> this is InSb111 in the DCM! #return 0.00524244 * E ### calibration Nov 2021, Rh L-edge, 3000 eV, corresponds to 35 fs/eV #return 0.0005445 * E ### calibration Mar 2022, Mn K-edge, 6500 eV, corresponds to 3.6 fs/eV return 0.0004692 * E ### calibration Mar 2022, Fe K-edge, 7100 eV, corresponds to 3.13 fs/eV #return 0.0148402 * E ### calibration May 2022, S K-edge, 2470 eV, corresponds to 99 fs/eV #und = UndulatorEnergy() mono_und = UndulatorCoupledDoubleCrystalMonoEnergy("MONO_UND", name="Alvra DCM Undulator-coupled energy") UCDCMEWTC = UndulatorCoupledDoubleCrystalMonoEnergyWithTimeCorrection(limit_low=2450, limit_high=2520) class TXS(Device): def __init__(self, ID="SAROP11-PTXS128", name="TXS"): super().__init__(ID, name=name) self.x = Motor(ID + ":TRX1") self.y = Motor(ID + ":TRY1") self.z = Motor(ID + ":TRZ1") self.crystals = SimpleDevice("TXS Crystals", left = SimpleDevice("Left Crystals", linear = SmarActAxis(ID + ":TRX11"), rotary = SmarActAxis(ID + ":ROTY11"), gonio1 = SmarActAxis(ID + ":ROTX11"), gonio2 = SmarActAxis(ID + ":ROTX12"), gonio3 = SmarActAxis(ID + ":ROTX13") ), right = SimpleDevice("Right Crystals", linear = SmarActAxis(ID + ":TRX21"), rotary = SmarActAxis(ID + ":ROTY21"), gonio1 = SmarActAxis(ID + ":ROTX21"), gonio2 = SmarActAxis(ID + ":ROTX22"), gonio3 = SmarActAxis(ID + ":ROTX23") ) ) from colorama import Fore, Style class Deprecator: def __init__(self, old_name, new_name): self._old_name = old_name self._new_name = new_name def __repr__(self): return "" def __getattr__(self, name): print( Fore.RED + f"\"{self._old_name}\" has been renamed \"{self._new_name}\"" + Style.RESET_ALL ) return lambda *args, **kwargs: self._method(self._new_name, name, *args, **kwargs) def _method(self, new_name, meth_name, *args, **kwargs): args = [repr(v) for v in args] args += [f"{k}={repr(v)}" for k, v in kwargs.items()] args = ", ".join(args) print(f"please use the following command instead:\n {new_name}.{meth_name}({args})")