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)
# 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")
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"]]
print(f"Device candidate: {cls_candidate}")
try:
if issubclass(cls_candidate, OphydObject):
ret = cls_candidate(**entry["config"])
if connect:
ret.wait_for_connection(timeout=5)
return ret
else:
raise RuntimeError(f"Unsupported return class: {schema}")
except TypeError:
# Simulated devices
if issubclass(type(cls_candidate), OphydObject):
return cls_candidate
else:
raise RuntimeError(f"Unsupported return class: {schema}")
raise RuntimeError(f"Unsupported return class: {entry["type"]}")
if __name__ == "__main__":
for key in lut_db:
print(key)
dut = createProxy(str(key))

View File

@ -52,12 +52,12 @@ class DelayPair(PseudoPositioner):
@pseudo_position_argument
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)
@real_position_argument
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)
@ -71,7 +71,6 @@ class DelayGeneratorDG645(Device):
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.
Front panel signals:
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
@ -112,7 +111,6 @@ class DelayGeneratorDG645(Device):
burstDelay = Component(EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name='burstdelay', kind=Kind.config)
burstPeriod = Component(EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name='burstperiod', kind=Kind.config)
def stage(self):
"""Trigger the generator by arming to accept triggers"""
self.arm.write(1).wait()
@ -145,10 +143,6 @@ class DelayGeneratorDG645(Device):
self.burstMode.set(0).wait()
# pair = DelayPair("DGEN01:", name="delayer", channel="CD")
# Automatically connect to test environmenr if directly invoked
if __name__ == "__main__":
dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer")

View File

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

View File

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

View File

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

View File

@ -3,11 +3,10 @@
Created on Wed Oct 13 18:06:15 2021
@author: mohacsi_i
IMPORTANT: Virtual monochromator axes should be implemented already in EPICS!!!
"""
import numpy as np
from math import isclose
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
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!
@ -61,8 +61,6 @@ def e2a(energy, hkl=[1,1,1], lnc=False, bent=False):
return angle
class MonoMotor(PseudoPositioner):
"""Monochromator axis
@ -110,10 +108,7 @@ class MonoDccm(PseudoPositioner):
@pseudo_position_argument
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:
# 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)
@ -126,5 +121,3 @@ class MonoDccm(PseudoPositioner):
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

@ -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
class SlitH(PseudoPositioner):
"""Python wrapper for virtual slits
@ -37,7 +31,6 @@ class SlitH(PseudoPositioner):
gapx=real_pos.x2-real_pos.x1)
class SlitV(PseudoPositioner):
"""Python wrapper for virtual slits
@ -56,13 +49,12 @@ class SlitV(PseudoPositioner):
@pseudo_position_argument
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,
y2=pseudo_pos.ceny+pseudo_pos.gapy/2)
@real_position_argument
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,
gapy=real_pos.y2-real_pos.y1)