This commit is contained in:
mohacsi_i 2022-11-14 17:37:03 +01:00
parent 19f5f728cc
commit c7867a910f
8 changed files with 174 additions and 373 deletions

View File

@ -29,7 +29,7 @@ lut_db = yaml.load(fp, Loader=yaml.Loader)
# lut_db = yaml.load(fp, Loader=yaml.Loader) # lut_db = yaml.load(fp, Loader=yaml.Loader)
# Load beamline specific database # Load beamline specific database
bl = os.getenv('BEAMLINE_XNAME', "X12SA") bl = os.getenv("BEAMLINE_XNAME", "X12SA")
fp = open(f"{path}/db/{bl.lower()}_database.yml", "r") fp = open(f"{path}/db/{bl.lower()}_database.yml", "r")
lut_db.update(yaml.load(fp, Loader=yaml.Loader)) lut_db.update(yaml.load(fp, Loader=yaml.Loader))
@ -45,27 +45,16 @@ def createProxy(name: str, connect=True) -> OphydObject:
cls_candidate = globals()[entry["type"]] cls_candidate = globals()[entry["type"]]
print(f"Device candidate: {cls_candidate}") print(f"Device candidate: {cls_candidate}")
try:
if issubclass(cls_candidate, OphydObject): if issubclass(cls_candidate, OphydObject):
ret = cls_candidate(**entry["config"]) ret = cls_candidate(**entry["config"])
if connect: if connect:
ret.wait_for_connection(timeout=5) ret.wait_for_connection(timeout=5)
return ret return ret
else: else:
raise RuntimeError(f"Unsupported return class: {schema}") raise RuntimeError(f"Unsupported return class: {entry["type"]}")
except TypeError:
# Simulated devices
if issubclass(type(cls_candidate), OphydObject):
return cls_candidate
else:
raise RuntimeError(f"Unsupported return class: {schema}")
if __name__ == "__main__": if __name__ == "__main__":
for key in lut_db: for key in lut_db:
print(key) print(key)
dut = createProxy(str(key)) dut = createProxy(str(key))

View File

@ -52,12 +52,12 @@ class DelayPair(PseudoPositioner):
@pseudo_position_argument @pseudo_position_argument
def forward(self, pseudo_pos): def forward(self, pseudo_pos):
'''Run a forward (pseudo -> real) calculation''' """Run a forward (pseudo -> real) calculation"""
return self.RealPosition(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay+pseudo_pos.width) return self.RealPosition(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay+pseudo_pos.width)
@real_position_argument @real_position_argument
def inverse(self, real_pos): def inverse(self, real_pos):
'''Run an inverse (real -> pseudo) calculation''' """Run an inverse (real -> pseudo) calculation"""
return self.PseudoPosition(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1) return self.PseudoPosition(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1)
@ -71,7 +71,6 @@ class DelayGeneratorDG645(Device):
Front panel outputs T0, AB, CD, EF and GH are a combination of these signals. Front panel outputs T0, AB, CD, EF and GH are a combination of these signals.
Back panel outputs are directly routed signals. So signals are NOT INDEPENDENT. Back panel outputs are directly routed signals. So signals are NOT INDEPENDENT.
Front panel signals: Front panel signals:
All signals go high after their defined delays and go low after the trigger All signals go high after their defined delays and go low after the trigger
holdoff period, i.e. this is the trigger window. Front panel outputs provide holdoff period, i.e. this is the trigger window. Front panel outputs provide
@ -112,7 +111,6 @@ class DelayGeneratorDG645(Device):
burstDelay = Component(EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name='burstdelay', kind=Kind.config) burstDelay = Component(EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name='burstdelay', kind=Kind.config)
burstPeriod = Component(EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name='burstperiod', kind=Kind.config) burstPeriod = Component(EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name='burstperiod', kind=Kind.config)
def stage(self): def stage(self):
"""Trigger the generator by arming to accept triggers""" """Trigger the generator by arming to accept triggers"""
self.arm.write(1).wait() self.arm.write(1).wait()
@ -145,10 +143,6 @@ class DelayGeneratorDG645(Device):
self.burstMode.set(0).wait() self.burstMode.set(0).wait()
# Automatically connect to test environmenr if directly invoked
# pair = DelayPair("DGEN01:", name="delayer", channel="CD") if __name__ == "__main__":
dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer") dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer")

View File

@ -24,20 +24,3 @@ class InsertionDevice(PVPositioner):
# (NA for important devices) # (NA for important devices)
if __name__ == "__main__": if __name__ == "__main__":
pass pass

View File

@ -2,6 +2,7 @@ import numpy as np
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO from ophyd import Device, Component, EpicsSignal, EpicsSignalRO
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
class SpmBase(Device): class SpmBase(Device):
"""Python wrapper for the Staggered Blade Pair Monitors """Python wrapper for the Staggered Blade Pair Monitors
@ -101,4 +102,3 @@ if __name__ == "__main__":
print("---") print("---")
spm1.sim() spm1.sim()
spm2.sim() spm2.sim()

View File

@ -48,9 +48,6 @@ class XbpmBase(Device):
offsetV = Component(EpicsSignal, "PositionOffsetY", auto_monitor=False) offsetV = Component(EpicsSignal, "PositionOffsetY", auto_monitor=False)
class XbpmSim(XbpmBase): class XbpmSim(XbpmBase):
"""Python wrapper for simulated X-ray Beam Position Monitors """Python wrapper for simulated X-ray Beam Position Monitors
@ -134,20 +131,3 @@ if __name__ == "__main__":
print("---") print("---")
xbpm1.sim() xbpm1.sim()
xbpm2.sim() xbpm2.sim()

View File

@ -3,11 +3,10 @@
Created on Wed Oct 13 18:06:15 2021 Created on Wed Oct 13 18:06:15 2021
@author: mohacsi_i @author: mohacsi_i
IMPORTANT: Virtual monochromator axes should be implemented already in EPICS!!!
""" """
import numpy as np import numpy as np
from math import isclose from math import isclose
from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind
@ -16,6 +15,7 @@ from ophyd.sim import SynAxis, Syn2DGauss
LN_CORR = 2e-4 LN_CORR = 2e-4
def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False):
"""Convert between angle and energy for Si monchromators """Convert between angle and energy for Si monchromators
ATTENTION: 'angle' must be in radians, not degrees! ATTENTION: 'angle' must be in radians, not degrees!
@ -61,8 +61,6 @@ def e2a(energy, hkl=[1,1,1], lnc=False, bent=False):
return angle return angle
class MonoMotor(PseudoPositioner): class MonoMotor(PseudoPositioner):
"""Monochromator axis """Monochromator axis
@ -110,10 +108,7 @@ class MonoDccm(PseudoPositioner):
@pseudo_position_argument @pseudo_position_argument
def forward(self, pseudo_pos): def forward(self, pseudo_pos):
""" """WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning"""
WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning
"""
if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001:
# Probably the common energy was changed # Probably the common energy was changed
return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592) return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592)
@ -126,5 +121,3 @@ class MonoDccm(PseudoPositioner):
return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0), return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0),
en2=a2e(3.141592*real_pos.th2/180.0), en2=a2e(3.141592*real_pos.th2/180.0),
energy=-a2e(3.141592*real_pos.th1/180.0)) energy=-a2e(3.141592*real_pos.th1/180.0))

View File

@ -1,130 +0,0 @@
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 13 18:06:15 2021
@author: mohacsi_i
"""
import numpy as np
from math import isclose
from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind
from ophyd.pseudopos import pseudo_position_argument, real_position_argument
from ophyd.sim import SynAxis, Syn2DGauss
LN_CORR = 2e-4
def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False):
""" Convert between angle and energy for Si monchromators
ATTENTION: 'angle' must be in radians, not degrees!
"""
lncorr = LN_CORR if lnc else 0.0
angle = angle*np.pi/180 if deg else angle
# Lattice constant along direction
d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl)
energy = 12.39842 / (2.0 * d0 * np.sin(angle))
return energy
def e2w(energy):
""" Convert between energy and wavelength
"""
return 0.1 * 12398.42 / energy
def w2e(wwl):
""" Convert between wavelength and energy
"""
return 12398.42 * 0.1 / wwl
def e2a(energy, hkl=[1,1,1], lnc=False, bent=False):
""" Convert between energy and angle for Si monchromators
ATTENTION: 'angle' must be in radians, not degrees!
"""
lncorr = LN_CORR if lnc else 0.0
# Lattice constant along direction
d0 = 2*5.43102 * (1.0-lncorr) / np.linalg.norm(hkl)
angle = np.arcsin(12.39842/d0/energy)
# Rfine for bent mirror
if bent:
rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle)
dt = 0.2e-3 / rho * 0.279
d0 = 2 * 5.43102 * (1.0+dt) / np.linalg.norm(hkl)
angle = np.arcsin(12.39842/d0/energy)
return angle
class MonoMotor(PseudoPositioner):
""" Monochromator axis
Small wrapper to combine a real angular axis with the corresponding energy.
ATTENTION: 'angle' is in degrees, at least for PXIII
"""
# Real axis (in degrees)
angle = Component(EpicsMotor, "", name='angle')
# Virtual axis
energy = Component(PseudoSingle, name='energy')
_real = ['angle']
@pseudo_position_argument
def forward(self, pseudo_pos):
return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592)
@real_position_argument
def inverse(self, real_pos):
return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0))
class MonoDccm(PseudoPositioner):
""" Combined DCCM monochromator
The first crystal selects the energy, the second one is only following.
DCCMs are quite simple in terms that they can't crash and we don't
have a beam offset.
ATTENTION: 'angle' is in degrees, at least for PXIII
"""
# Real axis (in degrees)
th1 = Component(EpicsMotor, "ROX1", name='theta1')
th2 = Component(EpicsMotor, "ROX2", name='theta2')
# Virtual axes
en1 = Component(PseudoSingle, name='en1')
en2 = Component(PseudoSingle, name='en2')
energy = Component(PseudoSingle, name='energy', kind=Kind.hinted)
# Other parameters
#feedback = Component(EpicsSignal, "MONOBEAM", name="feedback")
#enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1")
#enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2")
@pseudo_position_argument
def forward(self, pseudo_pos):
"""
WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning
"""
if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001:
# Probably the common energy was changed
return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592)
else:
# Probably the individual axes was changes
return self.RealPosition(th1=-180.0*e2a(pseudo_pos.en1)/3.141592, th2=180.0*e2a(pseudo_pos.en2)/3.141592)
@real_position_argument
def inverse(self, real_pos):
return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0),
en2=a2e(3.141592*real_pos.th2/180.0),
energy=-a2e(3.141592*real_pos.th1/180.0))

View File

@ -2,12 +2,6 @@ from ophyd import Device, Component, EpicsMotor, PseudoPositioner, PseudoSingle
from ophyd.pseudopos import pseudo_position_argument,real_position_argument from ophyd.pseudopos import pseudo_position_argument,real_position_argument
class SlitH(PseudoPositioner): class SlitH(PseudoPositioner):
"""Python wrapper for virtual slits """Python wrapper for virtual slits
@ -37,7 +31,6 @@ class SlitH(PseudoPositioner):
gapx=real_pos.x2-real_pos.x1) gapx=real_pos.x2-real_pos.x1)
class SlitV(PseudoPositioner): class SlitV(PseudoPositioner):
"""Python wrapper for virtual slits """Python wrapper for virtual slits
@ -56,13 +49,12 @@ class SlitV(PseudoPositioner):
@pseudo_position_argument @pseudo_position_argument
def forward(self, pseudo_pos): def forward(self, pseudo_pos):
'''Run a forward (pseudo -> real) calculation''' """Run a forward (pseudo -> real) calculation"""
return self.RealPosition(y1=pseudo_pos.ceny-pseudo_pos.gapy/2, return self.RealPosition(y1=pseudo_pos.ceny-pseudo_pos.gapy/2,
y2=pseudo_pos.ceny+pseudo_pos.gapy/2) y2=pseudo_pos.ceny+pseudo_pos.gapy/2)
@real_position_argument @real_position_argument
def inverse(self, real_pos): def inverse(self, real_pos):
'''Run an inverse (real -> pseudo) calculation''' """Run an inverse (real -> pseudo) calculation"""
return self.PseudoPosition(ceny=(real_pos.y1+real_pos.y2)/2, return self.PseudoPosition(ceny=(real_pos.y1+real_pos.y2)/2,
gapy=real_pos.y2-real_pos.y1) gapy=real_pos.y2-real_pos.y1)