Merge branch 'feature-first-epics-devices' into 'master'

First EPICS devices from controls repo

See merge request bec/ophyd_devices!1
This commit is contained in:
wakonig_k 2022-11-24 15:52:27 +00:00
commit e119f122de
15 changed files with 2027 additions and 1 deletions

4
.gitignore vendored
View File

@ -5,4 +5,6 @@
.DS_Store
**/out
**/.vscode
**/.pytest_cache
**/.pytest_cache
*.egg-info

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 13 17:06:51 2021
@author: mohacsi_i
"""
import os
import yaml
from ophyd.ophydobj import OphydObject
from devices import *
# ####################################################
# Test connection to beamline devices
# ####################################################
# Current file path
path = os.path.dirname(os.path.abspath(__file__))
# Load simulated device database
fp = open(f"{path}/db/test_database.yml", "r")
lut_db = yaml.load(fp, Loader=yaml.Loader)
# Load beamline specific database
bl = os.getenv("BEAMLINE_XNAME", "TESTENV")
if bl != "TESTENV":
fp = open(f"{path}/db/{bl.lower()}_database.yml", "r")
lut_db.update(yaml.load(fp, Loader=yaml.Loader))
def createProxy(name: str, connect=True) -> OphydObject:
"""Create an ophyd device from its alias
Factory routine that uses the beamline database to instantiate
ophyd devices from their alias and pre-defined configuration.
Does nothing if the device is already an OphydObject!
"""
if issubclass(type(name), OphydObject):
return name
entry = lut_db[name]
# Yeah, using global namespace
cls_candidate = globals()[entry["type"]]
# print(f"Class candidate: {cls_candidate}")
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: {entry['type']}")
if __name__ == "__main__":
num_errors = 0
for key in lut_db:
try:
dut = createProxy(str(key))
print(f"{key}\t: {type(dut)}\t{dut.read()}")
# print(f"{key}\t: {type(dut)}")
except Exception as ex:
num_errors += 1
print(key)
print(ex)
print(f"\nTotal number of errors: {num_errors}")

View File

@ -0,0 +1,10 @@
from .devices.DelayGeneratorDG645 import DelayGeneratorDG645
from .devices.slits import SlitH, SlitV
from .devices.XbpmBase import XbpmBase, XbpmCsaxsOp
from .devices.SpmBase import SpmBase
from .devices.InsertionDevice import InsertionDevice
# Standard ophyd classes
from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor
from ophyd.sim import SynAxis, SynSignal, SynPeriodicSignal
from ophyd.quadem import QuadEM

View File

@ -0,0 +1,21 @@
ring:
desc: 'SLS storage ring status'
acquisition: {schedule: sync}
config: {name: ring, prefix: ''}
deviceGroup: epicsDevice
status: {enabled: true}
type: SlsStatus
frontendstatus:
desc: 'Minimal beamline frontend status'
acquisition: {schedule: sync}
config: {name: frontendstatus, prefix: X06DA}
deviceGroup: epicsDevice
status: {enabled: true}
type: FrontEndStatus
opticshutchstatus:
desc: 'Minimal beamline optics hutch status'
acquisition: {schedule: sync}
config: {name: opticshutchstatus, prefix: X06DA}
deviceGroup: epicsDevice
status: {enabled: true}
type: OpticsHutchStatus

View File

@ -0,0 +1,35 @@
motor1:
desc: 'Simulated axis 1'
acquisition: {schedule: sync}
config: {name: motor1}
deviceGroup: epicsDevice
status: {enabled: true}
type: SynAxis
motor2:
desc: 'Simulated axis 2'
acquisition: {schedule: sync}
config: {name: motor2}
deviceGroup: epicsDevice
status: {enabled: true}
type: SynAxis
det1:
desc: 'Simulated signal 1'
acquisition: {schedule: sync}
config: {name: det1}
deviceGroup: epicsDevice
status: {enabled: true}
type: SynSignal
det2:
desc: 'Simulated signal 2'
acquisition: {schedule: sync}
config: {name: det2}
deviceGroup: epicsDevice
status: {enabled: true}
type: SynSignal
per1:
desc: 'Simulated periodic signal 1'
acquisition: {schedule: sync}
config: {name: per1}
deviceGroup: epicsDevice
status: {enabled: true}
type: SynPeriodicSignal

View File

@ -0,0 +1,944 @@
idgap:
desc: 'Undulator gap size [mm]'
acquisition: {schedule: sync}
config: {name: idgap, prefix: 'X12SA-ID'}
deviceGroup: monitor
status: {enabled: true}
type: InsertionDevice
bm1trx:
desc: 'FrontEnd XBPM 1 horizontal movement'
acquisition: {schedule: sync}
config: {name: bm1trx, prefix: 'X12SA-FE-BM1:TRH'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm1try:
desc: 'FrontEnd XBPM 1 vertical movement'
acquisition: {schedule: sync}
config: {name: bm1try, prefix: 'X12SA-FE-BM1:TRV'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm2trx:
desc: 'FrontEnd XBPM 2 horizontal movement'
acquisition: {schedule: sync}
config: {name: bm2trx, prefix: 'X12SA-FE-BM2:TRH'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm2try:
desc: 'FrontEnd XBPM 2 vertical movement'
acquisition: {schedule: sync}
config: {name: bm2try, prefix: 'X12SA-FE-BM2:TRV'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
di2trx:
desc: 'FrontEnd diaphragm 2 horizontal movement'
acquisition: {schedule: sync}
config: {name: di2trx, prefix: 'X12SA-FE-DI2:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
di2try:
desc: 'FrontEnd diaphragm 2 vertical movement'
acquisition: {schedule: sync}
config: {name: di2try, prefix: 'X12SA-FE-DI2:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl0trxo:
desc: 'FrontEnd slit outer blade movement'
acquisition: {schedule: sync}
config: {name: sl0trxo, prefix: 'X12SA-FE-SH1:TRX2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl0trxi:
desc: 'FrontEnd slit inner blade movement'
acquisition: {schedule: sync}
config: {name: sl0trxi, prefix: 'X12SA-FE-SH1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl0h:
desc: 'FrontEnd slit virtual movement'
acquisition: {schedule: sync}
config: {name: sl0h, prefix: 'X12SA-FE-SH1:'}
deviceGroup: epicsDevice
status: {enabled: true}
type: SlitH
bm3trx:
desc: 'OpticsHutch XBPM 1 horizontal movement'
acquisition: {schedule: sync}
config: {name: bm3trx, prefix: 'X12SA-OP-BM1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm3try:
desc: 'OpticsHutch XBPM 1 vertical movement'
acquisition: {schedule: sync}
config: {name: bm3try, prefix: 'X12SA-OP-BM1:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl1trxo:
desc: 'OpticsHutch slit outer blade movement'
acquisition: {schedule: sync}
config: {name: sl1trxo, prefix: 'X12SA-OP-SH1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl1trxi:
desc: 'OpticsHutch slit inner blade movement'
acquisition: {schedule: sync}
config: {name: sl1trxi, prefix: 'X12SA-OP-SH1:TRX2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl1tryt:
desc: 'OpticsHutch slit top blade movement'
acquisition: {schedule: sync}
config: {name: sl1tryt, prefix: 'X12SA-OP-SV1:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl1tryb:
desc: 'OpticsHutch slit bottom blade movement'
acquisition: {schedule: sync}
config: {name: sl1tryb, prefix: 'X12SA-OP-SV1:TRY2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl1h:
desc: 'OpticsHutch slit virtual movement'
acquisition: {schedule: sync}
config: {name: sl1h, prefix: 'X12SA-OP-SH1:'}
deviceGroup: epicsDevice
status: {enabled: true}
type: SlitH
sl1v:
desc: 'OpticsHutch slit virtual movement'
acquisition: {schedule: sync}
config: {name: sl1v, prefix: 'X12SA-OP-SV1:'}
deviceGroup: epicsDevice
status: {enabled: true}
type: SlitV
fi1try:
desc: 'OpticsHutch filter 1 movement'
acquisition: {schedule: sync}
config: {name: fi1try, prefix: 'X12SA-OP-FI1:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fi2try:
desc: 'OpticsHutch filter 2 movement'
acquisition: {schedule: sync}
config: {name: fi2try, prefix: 'X12SA-OP-FI2:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fi3try:
desc: 'OpticsHutch filter 3 movement'
acquisition: {schedule: sync}
config: {name: fi3try, prefix: 'X12SA-OP-FI3:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mobdai:
desc: 'Monochromator bender inner motor'
acquisition: {schedule: sync}
config: {name: mobdai, prefix: 'X12SA-OP-MO:TRYA'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mobdbo:
desc: 'Monochromator bender outer motor'
acquisition: {schedule: sync}
config: {name: mobdbo, prefix: 'X12SA-OP-MO:TRYB'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mobdco:
desc: 'Monochromator bender outer motor'
acquisition: {schedule: sync}
config: {name: mobdco, prefix: 'X12SA-OP-MO:TRYC'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mobddi:
desc: 'Monochromator bender inner motor'
acquisition: {schedule: sync}
config: {name: mobddi, prefix: 'X12SA-OP-MO:TRYD'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mobd:
desc: 'Monochromator bender virtual motor'
acquisition: {schedule: sync}
config: {name: mobd, prefix: 'X12SA-OP-MO:'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: PmMonoBender
bm4trx:
desc: 'OpticsHutch XBPM 2 horizontal movement'
acquisition: {schedule: sync}
config: {name: bm4trx, prefix: 'X12SA-OP-BM2:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm4try:
desc: 'OpticsHutch XBPM 2 vertical movement'
acquisition: {schedule: sync}
config: {name: bm4try, prefix: 'X12SA-OP-BM2:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mitrx:
desc: 'Mirror horizontal movement'
acquisition: {schedule: sync}
config: {name: mitrx, prefix: 'X12SA-OP-MI:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mitry1:
desc: 'Mirror vertical movement 1'
acquisition: {schedule: sync}
config: {name: mitry1, prefix: 'X12SA-OP-MI:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mitry2:
desc: 'Mirror vertical movement 2'
acquisition: {schedule: sync}
config: {name: mitry2, prefix: 'X12SA-OP-MI:TRY2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mitry3:
desc: 'Mirror vertical movement 3'
acquisition: {schedule: sync}
config: {name: mitry3, prefix: 'X12SA-OP-MI:TRY3'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mibd1:
desc: 'Mirror bender 1'
acquisition: {schedule: sync}
config: {name: mibd1, prefix: 'X12SA-OP-MI:TRZ1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mibd2:
desc: 'Mirror bender 2'
acquisition: {schedule: sync}
config: {name: mibd2, prefix: 'X12SA-OP-MI:TRZ2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm5trx:
desc: 'OpticsHutch XBPM 3 horizontal movement'
acquisition: {schedule: sync}
config: {name: bm5trx, prefix: 'X12SA-OP-BM3:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bm5try:
desc: 'OpticsHutch XBPM 3 vertical movement'
acquisition: {schedule: sync}
config: {name: bm5try, prefix: 'X12SA-OP-BM3:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl2trxo:
desc: 'OpticsHutch slit 2 outer blade movement'
acquisition: {schedule: sync}
config: {name: sl2trxo, prefix: 'X12SA-OP-SH2:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl2trxi:
desc: 'OpticsHutch slit 2 inner blade movement'
acquisition: {schedule: sync}
config: {name: sl2trxi, prefix: 'X12SA-OP-SH2:TRX2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl2tryt:
desc: 'OpticsHutch slit 2 top blade movement'
acquisition: {schedule: sync}
config: {name: sl2tryt, prefix: 'X12SA-OP-SV2:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl2tryb:
desc: 'OpticsHutch slit 2 bottom blade movement'
acquisition: {schedule: sync}
config: {name: sl2tryb, prefix: 'X12SA-OP-SV2:TRY2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
sl2h:
desc: 'OpticsHutch slit 2 virtual movement'
acquisition: {schedule: sync}
config: {name: sl2h, prefix: 'X12SA-OP-SH2:'}
deviceGroup: epicsDevice
status: {enabled: true}
type: SlitH
sl2v:
desc: 'OpticsHutch slit 2 virtual movement'
acquisition: {schedule: sync}
config: {name: sl2v, prefix: 'X12SA-OP-SV2:'}
deviceGroup: epicsDevice
status: {enabled: true}
type: SlitV
aptrx:
desc: 'ES aperture horizontal movement'
acquisition: {schedule: sync}
config: {name: aptrx, prefix: 'X12SA-ES1-PIN1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
aptry:
desc: 'ES aperture vertical movement'
acquisition: {schedule: sync}
config: {name: aptry, prefix: 'X12SA-ES1-PIN1:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
ebtrx:
desc: 'Exposure box 2 horizontal movement'
acquisition: {schedule: sync}
config: {name: ebtrx, prefix: 'X12SA-ES1-EB:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
ebtry:
desc: 'Exposure box 2 vertical movement'
acquisition: {schedule: sync}
config: {name: ebtry, prefix: 'X12SA-ES1-EB:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
ebtrz:
desc: 'Exposure box 2 axial movement'
acquisition: {schedule: sync}
config: {name: ebtrz, prefix: 'X12SA-ES1-EB:TRZ1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fttrx1:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: fttrx1, prefix: 'X12SA-ES1-FTS1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fttry1:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: fttry1, prefix: 'X12SA-ES1-FTS1:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fttrz:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: fttrz, prefix: 'X12SA-ES1-FTS1:TRZ1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fttrx2:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: fttrx2, prefix: 'X12SA-ES1-FTS2:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
fttry2:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: fttry2, prefix: 'X12SA-ES1-FTS2:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bs1x:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: bs1x, prefix: 'X12SA-ES1-BS1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bs1y:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: bs1y, prefix: 'X12SA-ES1-BS1:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bs2x:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: bs2x, prefix: 'X12SA-ES1-BS2:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
bs2y:
desc: 'Dunno these motors???'
acquisition: {schedule: sync}
config: {name: bs2y, prefix: 'X12SA-ES1-BS2:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
dttrx:
desc: 'Detector tower motion'
acquisition: {schedule: sync}
config: {name: dttrx, prefix: 'X12SA-ES1-DETT:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
dttry:
desc: 'Detector tower motion, no encoder'
acquisition: {schedule: sync}
config: {name: dttry, prefix: 'X12SA-ES1-DETT:TRY1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
dttrz:
desc: 'Detector tower motion'
acquisition: {schedule: sync}
config: {name: dttrz, prefix: 'X12SA-ES1-DETT:TRZ1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
dtpush:
desc: 'Detector tower tilt pusher'
acquisition: {schedule: sync}
config: {name: dtpush, prefix: 'X12SA-ES1-DETT:ROX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
dtth:
desc: 'Detector tower tilt rotation'
acquisition: {schedule: sync}
config: {name: dtth, prefix: 'X12SA-ES1-DETT:ROX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: PmDetectorRotation
dettrx:
desc: 'Detector tower motion'
acquisition: {schedule: sync}
config: {name: dettrx, prefix: 'X12SA-ES1-DET1:TRX1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
eyex:
desc: 'X-ray eye motion'
acquisition: {schedule: sync}
config: {name: eyex, prefix: X12SA-ES2-ES01}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
eyey:
desc: 'X-ray eye motion'
acquisition: {schedule: sync}
config: {name: eyey, prefix: X12SA-ES2-ES02}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
eyefoc:
desc: 'X-ray eye focusing motor'
acquisition: {schedule: sync}
config: {name: eyefoc, prefix: X12SA-ES2-ES25}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
micfoc:
desc: 'Microscope focusing motor'
acquisition: {schedule: sync}
config: {name: micfoc, prefix: X12SA-ES2-ES03}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
samx:
desc: 'Sample motion'
acquisition: {schedule: sync}
config: {name: samx, prefix: X12SA-ES2-ES04}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
samy:
desc: 'Sample motion'
acquisition: {schedule: sync}
config: {name: samy, prefix: X12SA-ES2-ES05}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
curr:
desc: 'SLS ring current'
acquisition: {schedule: sync}
config: {name: curr, read_pv: 'ARIDI-PCT:CURRENT'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
sec:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: sec, read_pv: X12SA-ES1-SCALER.S1}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
cyb:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: cyb, read_pv: X12SA-ES1-SCALER.S2}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
diode:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: diode, read_pv: X12SA-ES1-SCALER.S3}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
led:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: led, read_pv: X12SA-ES1-SCALER.S4}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
fal0:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: fal0, read_pv: X12SA-ES1-SCALER.S4}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
fal1:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: fal1, read_pv: X12SA-ES1-SCALER.S5}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
fal2:
desc: 'Some scaler...'
acquisition: {schedule: sync}
config: {name: fal2, read_pv: X12SA-ES1-SCALER.S6}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm1i:
desc: 'Some VME XBPM...'
acquisition: {schedule: sync}
config: {name: bpm1i, read_pv: X12SA-OP-BPM1:SUM}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm1:
desc: 'XBPM 1: Somewhere around mono (VME)'
acquisition: {schedule: sync}
config: {name: bpm1, prefix: 'X12SA-OP-BPM2:'}
deviceGroup: monitor
status: {enabled: true}
type: XbpmCsaxsOp
bpm2i:
desc: 'Some VME XBPM...'
acquisition: {schedule: sync}
config: {name: bpm2i, read_pv: X12SA-OP-BPM2:SUM}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm2:
desc: 'XBPM 2: Somewhere around mono (VME)'
acquisition: {schedule: sync}
config: {name: bpm2, prefix: 'X12SA-OP-BPM2:'}
deviceGroup: monitor
status: {enabled: true}
type: XbpmCsaxsOp
bpm3:
desc: 'XBPM 3: White beam AH501 before mono'
acquisition: {schedule: sync}
config: {name: bpm3, prefix: 'X12SA-OP-BPM3:'}
deviceGroup: monitor
status: {enabled: true}
type: QuadEM
bpm3a:
desc: 'XBPM 3: White beam AH501 before mono'
acquisition: {schedule: sync}
config: {name: bpm3a, read_pv: 'X12SA-OP-BPM3:Current1:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm3b:
desc: 'XBPM 3: White beam AH501 before mono'
acquisition: {schedule: sync}
config: {name: bpm3b, read_pv: 'X12SA-OP-BPM3:Current2:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm3c:
desc: 'XBPM 3: White beam AH501 before mono'
acquisition: {schedule: sync}
config: {name: bpm3c, read_pv: 'X12SA-OP-BPM3:Current3:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm3d:
desc: 'XBPM 3: White beam AH501 before mono'
acquisition: {schedule: sync}
config: {name: bpm3d, read_pv: 'X12SA-OP-BPM3:Current4:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm4a:
desc: 'XBPM 4: VME between mono and mirror'
acquisition: {schedule: sync}
config: {name: bpm4a, read_pv: 'X12SA-OP1-SCALER.S2'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm4b:
desc: 'XBPM 4: VME between mono and mirror'
acquisition: {schedule: sync}
config: {name: bpm4b, read_pv: 'X12SA-OP1-SCALER.S3'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm4c:
desc: 'XBPM 4: VME between mono and mirror'
acquisition: {schedule: sync}
config: {name: bpm4c, read_pv: 'X12SA-OP1-SCALER.S4'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm4d:
desc: 'XBPM 4: VME between mono and mirror'
acquisition: {schedule: sync}
config: {name: bpm4d, read_pv: 'X12SA-OP1-SCALER.S5'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm5:
desc: 'XBPM 5: AH501 past the mirror'
acquisition: {schedule: sync}
config: {name: bpm5, prefix: 'X12SA-OP-BPM5:'}
deviceGroup: monitor
status: {enabled: true}
type: QuadEM
bpm5a:
desc: 'XBPM 5: AH501 past the mirror'
acquisition: {schedule: sync}
config: {name: bpm5a, read_pv: 'X12SA-OP-BPM5:Current1:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm5b:
desc: 'XBPM 5: AH501 past the mirror'
acquisition: {schedule: sync}
config: {name: bpm5b, read_pv: 'X12SA-OP-BPM5:Current2:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm5c:
desc: 'XBPM 5: AH501 past the mirror'
acquisition: {schedule: sync}
config: {name: bpm5c, read_pv: 'X12SA-OP-BPM5:Current3:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm5d:
desc: 'XBPM 5: AH501 past the mirror'
acquisition: {schedule: sync}
config: {name: bpm5d, read_pv: 'X12SA-OP-BPM5:Current4:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm6:
desc: 'XBPM 6: Xbox, not commissioned'
acquisition: {schedule: sync}
config: {name: bpm6, prefix: 'X12SA-OP-BPM6:'}
deviceGroup: monitor
status: {enabled: true}
type: QuadEM
bpm6a:
desc: 'XBPM 6: Xbox, not commissioned'
acquisition: {schedule: sync}
config: {name: bpm6a, read_pv: 'X12SA-OP-BPM6:Current1:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm6b:
desc: 'XBPM 6: Xbox, not commissioned'
acquisition: {schedule: sync}
config: {name: bpm6b, read_pv: 'X12SA-OP-BPM6:Current2:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm6c:
desc: 'XBPM 6: Xbox, not commissioned'
acquisition: {schedule: sync}
config: {name: bpm6c, read_pv: 'X12SA-OP-BPM6:Current3:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
bpm6d:
desc: 'XBPM 6: Xbox, not commissioned'
acquisition: {schedule: sync}
config: {name: bpm6d, read_pv: 'X12SA-OP-BPM6:Current4:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
ftp:
desc: 'Flight tube pressure'
acquisition: {schedule: sync}
config: {name: ftp, read_pv: 'X12SA-ES1-FT1MT1:PRESSURE'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
eyecenx:
desc: 'X-ray eye intensit math'
acquisition: {schedule: sync}
config: {name: eyecenx, read_pv: 'XOMNYI-XEYE-XCEN:0'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
eyeceny:
desc: 'X-ray eye intensit math'
acquisition: {schedule: sync}
config: {name: eyeceny, read_pv: 'XOMNYI-XEYE-YCEN:0'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
eyeint:
desc: 'X-ray eye intensit math'
acquisition: {schedule: sync}
config: {name: eyeint, read_pv: 'XOMNYI-XEYE-INT_MEAN:0'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
transd:
desc: 'Transmission diode'
acquisition: {schedule: sync}
config: {name: transd, read_pv: 'X12SA-OP-BPM1:Current1:MeanValue_RBV'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
YASYM:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: YASYM, read_pv: 'X12SA-LBB:Y-ASYM'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
YSYM:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: YSYM, read_pv: 'X12SA-LBB:Y-SYM'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
XASYM:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: XASYM, read_pv: 'X12SA-LBB:X-ASYM'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
XSYM:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: XSYM, read_pv: 'X12SA-LBB:X-SYM'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
FBPMDX:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: FBPMDX, read_pv: 'X12SA-ID-FBPMD:X'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
FBPMDY:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: FBPMDY, read_pv: 'X12SA-ID-FBPMD:Y'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
FBPMUX:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: FBPMUX, read_pv: 'X12SA-ID-FBPMU:X'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
FBPMUY:
desc: 'FOFB reference'
acquisition: {schedule: sync}
config: {name: FBPMUY, read_pv: 'X12SA-ID-FBPMU:Y'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
sttrx:
desc: 'Girder X translation'
acquisition: {schedule: sync}
config: {name: sttrx, prefix: 'X12SA-HG'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: GirderMotorX1
sttry:
desc: 'Girder Y translation'
acquisition: {schedule: sync}
config: {name: sttry, prefix: 'X12SA-HG'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: GirderMotorY1
strox:
desc: 'Girder virtual pitch'
acquisition: {schedule: sync}
config: {name: strox, prefix: 'X12SA-HG'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: GirderMotorPITCH
stroy:
desc: 'Girder virtual yaw'
acquisition: {schedule: sync}
config: {name: stroy, prefix: 'X12SA-HG'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: GirderMotorYAW
stroz:
desc: 'Girder virtual roll'
acquisition: {schedule: sync}
config: {name: stroz, prefix: 'X12SA-HG'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: GirderMotorROLL
motry:
desc: 'OpticsHutch optical table vertical movement'
acquisition: {schedule: sync}
config: {name: motry, prefix: 'X12SA-OP-OT:TRY'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
motrz1:
desc: 'Monochromator crystal 1 axial movement'
acquisition: {schedule: sync}
config: {name: motrz1, prefix: 'X12SA-OP-MO:TRZ1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
motrz1e:
desc: 'Monochromator crystal 1 axial movement encoder'
acquisition: {schedule: sync}
config: {name: motrz1e, read_pv: 'X12SA-OP-MO:ECZ1'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
moroll1:
desc: 'Monochromator crystal 1 roll'
acquisition: {schedule: sync}
config: {name: moroll1, prefix: 'X12SA-OP-MO:ROZ1'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
motrx2:
desc: 'Monochromator crystal 2 horizontal movement'
acquisition: {schedule: sync}
config: {name: motrx2, prefix: 'X12SA-OP-MO:TRX2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
motry2:
desc: 'Monochromator crystal 2 vertical movement'
acquisition: {schedule: sync}
config: {name: motry2, prefix: 'X12SA-OP-MO:TRY2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
#monot:
# desc: 'Monochromator temperature'
# acquisition: {schedule: sync}
# config: {name: monot, read_pv: 'X12SA-OP-MO:TC3'}
# deviceGroup: monitor
# status: {enabled: true}
# type: EpicsSignalRO
moyaw2:
desc: 'Monochromator crystal 2 yaw movement'
acquisition: {schedule: sync}
config: {name: moyaw2, prefix: 'X12SA-OP-MO:ROY2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
moroll2:
desc: 'Monochromator crystal 2 roll movement'
acquisition: {schedule: sync}
config: {name: moroll2, prefix: 'X12SA-OP-MO:ROZ2'}
deviceGroup: beamlineMotor
status: {enabled: true}
type: EpicsMotor
mopush1:
desc: 'Monochromator crystal 1 angle'
acquisition: {schedule: sync}
config: {name: mopush1, read_pv: 'X12SA-OP-MO:ROX1'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
moth1:
desc: 'Monochromator Theta 1'
acquisition: {schedule: sync}
config: {name: moth1, read_pv: 'X12SA-OP-MO:ROX1'}
deviceGroup: monitor
status: {enabled: true}
type: MonoTheta1
moth1e:
desc: 'Monochromator crystal 1 theta encoder'
acquisition: {schedule: sync}
config: {name: moth1e, read_pv: 'X12SA-OP-MO:ECX1'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
mopush2:
desc: 'Monochromator crystal 2 angle'
acquisition: {schedule: sync}
config: {name: mopush2, read_pv: 'X12SA-OP-MO:ROX2'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
moth2:
desc: 'Monochromator Theta 2'
acquisition: {schedule: sync}
config: {name: moth1, read_pv: 'X12SA-OP-MO:ROX2'}
deviceGroup: monitor
status: {enabled: true}
type: MonoTheta2
moth2e:
desc: 'Monochromator crystal 2 theta encoder'
acquisition: {schedule: sync}
config: {name: moth2e, read_pv: 'X12SA-OP-MO:ECX2'}
deviceGroup: monitor
status: {enabled: true}
type: EpicsSignalRO
mokev:
desc: 'Monochromator energy in keV'
acquisition: {schedule: sync}
config: {name: mokev, read_pv: 'X12SA-OP-MO:ROX2'}
deviceGroup: monitor
status: {enabled: true}
type: EnergyKev

View File

@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 9 16:12:47 2021
@author: mohacsi_i
"""
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO, Kind
from ophyd import PositionerBase
from ophyd.pseudopos import (
pseudo_position_argument,
real_position_argument,
PseudoSingle,
PseudoPositioner,
)
class DelayStatic(Device):
"""Static axis for the T0 output channel
It allows setting the logic levels, but the timing is fixed.
The signal is high after receiving the trigger until the end
of the holdoff period.
"""
# Other channel stuff
ttl_mode = Component(EpicsSignal, "OutputModeTtlSS.PROC", kind=Kind.config)
nim_mode = Component(EpicsSignal, "OutputModeNimSS.PROC", kind=Kind.config)
polarity = Component(
EpicsSignal,
"OutputPolarityBI",
write_pv="OutputPolarityBO",
name="polarity",
kind=Kind.config,
)
amplitude = Component(
EpicsSignal, "OutputAmpAI", write_pv="OutputAmpAO", name="amplitude", kind=Kind.config
)
polarity = Component(
EpicsSignal, "OutputOffsetAI", write_pv="OutputOffsetAO", name="offset", kind=Kind.config
)
class DummyPositioner(Device, PositionerBase):
setpoint = Component(EpicsSignal, "DelayAO", kind=Kind.config)
readback = Component(EpicsSignalRO, "DelayAI", kind=Kind.config)
class DelayPair(PseudoPositioner):
"""Delay pair interface for DG645
Virtual motor interface to a pair of signals (on the frontpanel).
It offers a simple delay and pulse width interface for scanning.
"""
# The pseudo positioner axes
delay = Component(PseudoSingle, limits=(0, 2000.0), name="delay")
width = Component(PseudoSingle, limits=(0, 2000.0), name="pulsewidth")
# The real delay axes
ch1 = Component(DummyPositioner, name="ch1")
ch2 = Component(DummyPositioner, name="ch2")
def __init__(self, *args, **kwargs):
# Change suffix names before connecting (a bit of dynamic connections)
self.__class__.__dict__["ch1"].suffix = kwargs["channel"][0]
self.__class__.__dict__["ch2"].suffix = kwargs["channel"][1]
del kwargs["channel"]
# Call parent to start the connections
super().__init__(*args, **kwargs)
@pseudo_position_argument
def forward(self, pseudo_pos):
"""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"""
return self.PseudoPosition(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1)
class DelayGeneratorDG645(Device):
"""DG645 delay generator
This class implements a thin Ophyd wrapper around the Stanford Research DG645
digital delay generator.
Internally, the DG645 generates 8+1 signals: A, B, C, D, E, F, G, H and T0
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
a combination of these events.
Option 1 back panel 5V signals:
All signals go high after their defined delays and go low after the trigger
holdoff period, i.e. this is the trigger window. The signals will stay high
until the end of the window.
Option 2 back panel 30V signals:
All signals go high after their defined delays for ~100ns. This is fixed by
electronics (30V needs quite some power). This is not implemented in the
current device
"""
state = Component(EpicsSignalRO, "EventStatusLI", name="status_register")
status = Component(EpicsSignalRO, "StatusSI", name="status")
# Front Panel
channelT0 = Component(DelayStatic, "T0", name="T0")
channelAB = Component(DelayPair, "", name="AB", channel="AB")
channelCD = Component(DelayPair, "", name="CD", channel="CD")
channelEF = Component(DelayPair, "", name="EF", channel="EF")
channelGH = Component(DelayPair, "", name="GH", channel="GH")
# Minimum time between triggers
holdoff = Component(
EpicsSignal,
"TriggerHoldoffAI",
write_pv="TriggerHoldoffAO",
name="trigger_holdoff",
kind=Kind.config,
)
inhibit = Component(
EpicsSignal,
"TriggerInhibitMI",
write_pv="TriggerInhibitMO",
name="trigger_inhibit",
kind=Kind.config,
)
source = Component(
EpicsSignal,
"TriggerSourceMI",
write_pv="TriggerSourceMO",
name="trigger_source",
kind=Kind.config,
)
level = Component(
EpicsSignal,
"TriggerLevelAI",
write_pv="TriggerLevelAO",
name="trigger_level",
kind=Kind.config,
)
rate = Component(
EpicsSignal,
"TriggerRateAI",
write_pv="TriggerRateAO",
name="trigger_rate",
kind=Kind.config,
)
# Command PVs
arm = Component(
EpicsSignal, "TriggerDelayBI", write_pv="TriggerDelayBO", name="arm", kind=Kind.omitted
)
# Burst mode
burstMode = Component(
EpicsSignal, "BurstModeBI", write_pv="BurstModeBO", name="burstmode", kind=Kind.config
)
burstConfig = Component(
EpicsSignal, "BurstConfigBI", write_pv="BurstConfigBO", name="burstconfig", kind=Kind.config
)
burstCount = Component(
EpicsSignal, "BurstCountLI", write_pv="BurstCountLO", name="burstcount", 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
)
def stage(self):
"""Trigger the generator by arming to accept triggers"""
self.arm.write(1).wait()
def unstage(self):
"""Stop the trigger generator from accepting triggers"""
self.arm.write(0).wait()
def burstEnable(self, count, delay, period, config="all"):
"""Enable the burst mode"""
# Validate inputs
count = int(count)
assert count > 0, "Number of bursts must be positive"
assert delay > 0, "Burst delay must be positive"
assert period > 0, "Burst period must be positive"
assert config in ["all", "first"], "Supported bust configs are 'all' and 'first'"
self.burstMode.set(1).wait()
self.burstCount.set(count).wait()
self.burstDelay.set(delay).wait()
self.burstPeriod.set(period).wait()
if config == "all":
self.burstConfig.set(0).wait()
elif config == "first":
self.burstConfig.set(1).wait()
def busrtDisable(self):
"""Disable the burst mode"""
self.burstMode.set(0).wait()
# Automatically connect to test environmenr if directly invoked
if __name__ == "__main__":
dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer")

View File

@ -0,0 +1,28 @@
from ophyd import PVPositioner, Component, EpicsSignal, EpicsSignalRO, Kind
class InsertionDevice(PVPositioner):
"""Python wrapper for the CSAXS insertion device control
This wrapper provides a positioner interface for the ID control.
is completely custom XBPM with templates directly in the
VME repo. Thus it needs a custom ophyd template as well...
WARN: The x and y are not updated by the IOC
"""
status = Component(EpicsSignalRO, "-USER:STATUS", auto_monitor=True)
errorSource = Component(EpicsSignalRO, "-USER:ERROR-SOURCE", auto_monitor=True)
isOpen = Component(EpicsSignalRO, "-GAP:ISOPEN", auto_monitor=True)
# PVPositioner interface
setpoint = Component(EpicsSignal, "-GAP:SET", auto_monitor=True)
readback = Component(EpicsSignalRO, "-GAP:READ", auto_monitor=True, kind=Kind.hinted)
done = Component(EpicsSignalRO, ":DONE", auto_monitor=True)
stop_signal = Component(EpicsSignal, "-GAP:STOP", kind=Kind.omitted)
# Automatically start simulation if directly invoked
# (NA for important devices)
if __name__ == "__main__":
pass

View File

@ -0,0 +1,108 @@
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
SPM's consist of a set of four horizontal tungsten blades and are
used to monitor the beam height (only Y) for the bending magnet
beamlines of SLS.
Note: EPICS provided signals are read only, but the users can
change the beam position offset.
"""
# Motor interface
s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True)
s2 = Component(EpicsSignalRO, "Current2", auto_monitor=True)
s3 = Component(EpicsSignalRO, "Current3", auto_monitor=True)
s4 = Component(EpicsSignalRO, "Current4", auto_monitor=True)
sum = Component(EpicsSignalRO, "SumAll", auto_monitor=True)
y = Component(EpicsSignalRO, "Y", auto_monitor=True)
scale = Component(EpicsSignal, "PositionScaleY", auto_monitor=True)
offset = Component(EpicsSignal, "PositionOffsetY", auto_monitor=True)
class SpmSim(SpmBase):
"""Python wrapper for simulated Staggered Blade Pair Monitors
SPM's consist of a set of four horizontal tungsten blades and are
used to monitor the beam height (only Y) for the bending magnet
beamlines of SLS.
This simulation device extends the basic proxy with a script that
fills signals with quasi-randomized values.
"""
# Motor interface
s1w = Component(EpicsSignal, "Current1:RAW.VAL", auto_monitor=False)
s2w = Component(EpicsSignal, "Current2:RAW.VAL", auto_monitor=False)
s3w = Component(EpicsSignal, "Current3:RAW.VAL", auto_monitor=False)
s4w = Component(EpicsSignal, "Current4:RAW.VAL", auto_monitor=False)
rangew = Component(EpicsSignal, "RANGEraw", auto_monitor=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._MX = 0
self._MY = 0
self._I0 = 255.0
self._x = np.linspace(-5, 5, 64)
self._y = np.linspace(-5, 5, 64)
self._x, self._y = np.meshgrid(self._x, self._y)
def _simFrame(self):
"""Generator to simulate a jumping gaussian"""
# Define normalized 2D gaussian
def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1):
return np.exp(
-((x - mx) ** 2.0 / (2.0 * sx**2.0) + (y - my) ** 2.0 / (2.0 * sy**2.0))
)
# Generator for dynamic values
self._MX = 0.75 * self._MX + 0.25 * (10.0 * np.random.random() - 5.0)
self._MY = 0.75 * self._MY + 0.25 * (10.0 * np.random.random() - 5.0)
self._I0 = 0.75 * self._I0 + 0.25 * (255.0 * np.random.random())
arr = self._I0 * gaus2d(self._x, self._y, self._MX, self._MY)
return arr
def sim(self):
# Get next frame
beam = self._simFrame()
total = np.sum(beam) - np.sum(beam[24:48, :])
rnge = np.floor(np.log10(total) - 0.0)
s1 = np.sum(beam[0:16, :]) / 10**rnge
s2 = np.sum(beam[16:24, :]) / 10**rnge
s3 = np.sum(beam[40:48, :]) / 10**rnge
s4 = np.sum(beam[48:64, :]) / 10**rnge
self.s1w.set(s1).wait()
self.s2w.set(s2).wait()
self.s3w.set(s3).wait()
self.s4w.set(s4).wait()
self.rangew.set(rnge).wait()
# Print debug info
print(f"Raw signals: R={rnge}\t{s1}\t{s2}\t{s3}\t{s4}")
# plt.imshow(beam)
# plt.show(block=False)
plt.pause(0.5)
# Automatically start simulation if directly invoked
if __name__ == "__main__":
spm1 = SpmSim("X06D-FE-BM1:", name="spm1")
spm2 = SpmSim("X06D-FE-BM2:", name="spm2")
spm1.wait_for_connection(timeout=5)
spm2.wait_for_connection(timeout=5)
spm1.rangew.set(1).wait()
spm2.rangew.set(1).wait()
while True:
print("---")
spm1.sim()
spm2.sim()

View File

@ -0,0 +1,138 @@
import numpy as np
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO
import matplotlib.pyplot as plt
class XbpmCsaxsOp(Device):
"""Python wrapper for custom XBPMs in the cSAXS optics hutch
This is completely custom XBPM with templates directly in the
VME repo. Thus it needs a custom ophyd template as well...
WARN: The x and y are not updated by the IOC
"""
sum = Component(EpicsSignalRO, "SUM", auto_monitor=True)
x = Component(EpicsSignalRO, "POSH", auto_monitor=True)
y = Component(EpicsSignalRO, "POSV", auto_monitor=True)
s1 = Component(EpicsSignalRO, "CHAN1", auto_monitor=True)
s2 = Component(EpicsSignalRO, "CHAN2", auto_monitor=True)
s3 = Component(EpicsSignalRO, "CHAN3", auto_monitor=True)
s4 = Component(EpicsSignalRO, "CHAN4", auto_monitor=True)
class XbpmBase(Device):
"""Python wrapper for X-ray Beam Position Monitors
XBPM's consist of a metal-coated diamond window that ejects
photoelectrons from the incoming X-ray beam. These electons
are collected and their current is measured. Effectively
they act as four quadrant photodiodes and are used as BPMs
at the undulator beamlines of SLS.
Note: EPICS provided signals are read only, but the user can
change the beam position offset.
"""
# Motor interface
s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True)
s2 = Component(EpicsSignalRO, "Current2", auto_monitor=True)
s3 = Component(EpicsSignalRO, "Current3", auto_monitor=True)
s4 = Component(EpicsSignalRO, "Current4", auto_monitor=True)
sum = Component(EpicsSignalRO, "SumAll", auto_monitor=True)
asymH = Component(EpicsSignalRO, "asymH", auto_monitor=True)
asymV = Component(EpicsSignalRO, "asymV", auto_monitor=True)
x = Component(EpicsSignalRO, "X", auto_monitor=True)
y = Component(EpicsSignalRO, "Y", auto_monitor=True)
scaleH = Component(EpicsSignal, "PositionScaleX", auto_monitor=False)
offsetH = Component(EpicsSignal, "PositionOffsetX", auto_monitor=False)
scaleV = Component(EpicsSignal, "PositionScaleY", auto_monitor=False)
offsetV = Component(EpicsSignal, "PositionOffsetY", auto_monitor=False)
class XbpmSim(XbpmBase):
"""Python wrapper for simulated X-ray Beam Position Monitors
XBPM's consist of a metal-coated diamond window that ejects
photoelectrons from the incoming X-ray beam. These electons
are collected and their current is measured. Effectively
they act as four quadrant photodiodes and are used as BPMs
at the undulator beamlines of SLS.
Note: EPICS provided signals are read only, but the user can
change the beam position offset.
This simulation device extends the basic proxy with a script that
fills signals with quasi-randomized values.
"""
# Motor interface
s1w = Component(EpicsSignal, "Current1:RAW.VAL", auto_monitor=False)
s2w = Component(EpicsSignal, "Current2:RAW.VAL", auto_monitor=False)
s3w = Component(EpicsSignal, "Current3:RAW.VAL", auto_monitor=False)
s4w = Component(EpicsSignal, "Current4:RAW.VAL", auto_monitor=False)
rangew = Component(EpicsSignal, "RANGEraw", auto_monitor=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._MX = 0
self._MY = 0
self._I0 = 255.0
self._x = np.linspace(-5, 5, 64)
self._y = np.linspace(-5, 5, 64)
self._x, self._y = np.meshgrid(self._x, self._y)
def _simFrame(self):
"""Generator to simulate a jumping gaussian"""
# define normalized 2D gaussian
def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1):
return np.exp(
-((x - mx) ** 2.0 / (2.0 * sx**2.0) + (y - my) ** 2.0 / (2.0 * sy**2.0))
)
# Generator for dynamic values
self._MX = 0.75 * self._MX + 0.25 * (10.0 * np.random.random() - 5.0)
self._MY = 0.75 * self._MY + 0.25 * (10.0 * np.random.random() - 5.0)
self._I0 = 0.75 * self._I0 + 0.25 * (255.0 * np.random.random())
arr = self._I0 * gaus2d(self._x, self._y, self._MX, self._MY)
return arr
def sim(self):
# Get next frame
beam = self._simFrame()
total = np.sum(beam)
rnge = np.floor(np.log10(total) - 0.0)
s1 = np.sum(beam[32:64, 32:64]) / 10**rnge
s2 = np.sum(beam[0:32, 32:64]) / 10**rnge
s3 = np.sum(beam[32:64, 0:32]) / 10**rnge
s4 = np.sum(beam[0:32, 0:32]) / 10**rnge
self.s1w.set(s1).wait()
self.s2w.set(s2).wait()
self.s3w.set(s3).wait()
self.s4w.set(s4).wait()
self.rangew.set(rnge).wait()
# Print debug info
print(f"Raw signals: R={rnge}\t{s1}\t{s2}\t{s3}\t{s4}")
# plt.imshow(beam)
# plt.show(block=False)
plt.pause(0.5)
# Automatically start simulation if directly invoked
if __name__ == "__main__":
xbpm1 = XbpmSim("X01DA-FE-XBPM1:", name="xbpm1")
xbpm2 = XbpmSim("X01DA-FE-XBPM2:", name="xbpm2")
xbpm1.wait_for_connection(timeout=5)
xbpm2.wait_for_connection(timeout=5)
xbpm1.rangew.set(1).wait()
xbpm2.rangew.set(1).wait()
while True:
print("---")
xbpm1.sim()
xbpm2.sim()

View File

@ -0,0 +1,23 @@
from .DelayGeneratorDG645 import DelayGeneratorDG645
from .slits import SlitH, SlitV
from .XbpmBase import XbpmBase, XbpmCsaxsOp
from .SpmBase import SpmBase
from .InsertionDevice import InsertionDevice
from .specMotors import (
PmMonoBender,
PmDetectorRotation,
GirderMotorX1,
GirderMotorY1,
GirderMotorROLL,
GirderMotorYAW,
GirderMotorPITCH,
MonoTheta1,
MonoTheta2,
EnergyKev,
)
# Standard ophyd classes
from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor
from ophyd.sim import SynAxis, SynSignal, SynPeriodicSignal
from ophyd.quadem import QuadEM

View File

@ -0,0 +1,30 @@
TABLES_DT_PUSH_DIST_MM = 890
class DetectorTableTheta(PseudoPositioner):
"""Detector table tilt motor
Small wrapper to adjust the detector table tilt as angle.
The table is pushed from one side by a single vertical motor.
Note: Rarely used!
"""
# Real axis (in degrees)
pusher = Component(EpicsMotor, "", name="pusher")
# Virtual axis
theta = Component(PseudoSingle, name="theta")
_real = ["pusher"]
@pseudo_position_argument
def forward(self, pseudo_pos):
return self.RealPosition(
pusher=tan(pseudo_pos.theta * 3.141592 / 180.0) * TABLES_DT_PUSH_DIST_MM
)
@real_position_argument
def inverse(self, real_pos):
return self.PseudoPosition(
theta=-180 * atan(real_pos.pusher / TABLES_DT_PUSH_DIST_MM) / 3.141592
)

View File

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
"""
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,
)
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

@ -0,0 +1,66 @@
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
These devices should be implemented as an EPICS SoftMotor IOC,
but thats not the case for all slits. So here is a pure ophyd
implementation. Uses standard naming convention!
NOTE: The real and virtual axes are wrapped together.
"""
# Motor interface
x1 = Component(EpicsMotor, "TRX1")
x2 = Component(EpicsMotor, "TRX2")
cenx = Component(PseudoSingle)
gapx = Component(PseudoSingle)
@pseudo_position_argument
def forward(self, pseudo_pos):
"""Run a forward (pseudo -> real) calculation"""
return self.RealPosition(
x1=pseudo_pos.cenx - pseudo_pos.gapx / 2, x2=pseudo_pos.cenx + pseudo_pos.gapx / 2
)
@real_position_argument
def inverse(self, real_pos):
"""Run an inverse (real -> pseudo) calculation"""
return self.PseudoPosition(
cenx=(real_pos.x1 + real_pos.x2) / 2, gapx=real_pos.x2 - real_pos.x1
)
class SlitV(PseudoPositioner):
"""Python wrapper for virtual slits
These devices should be implemented as an EPICS SoftMotor IOC,
but thats not the case for all slits. So here is a pure ophyd
implementation. Uses standard naming convention!
NOTE: The real and virtual axes are wrapped together.
"""
# Motor interface
y1 = Component(EpicsMotor, "TRY1")
y2 = Component(EpicsMotor, "TRY2")
ceny = Component(PseudoSingle)
gapy = Component(PseudoSingle)
@pseudo_position_argument
def forward(self, pseudo_pos):
"""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"""
return self.PseudoPosition(
ceny=(real_pos.y1 + real_pos.y2) / 2, gapy=real_pos.y2 - real_pos.y1
)

View File

@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
"""
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, tan, atan, sqrt, sin, asin
from ophyd import (
EpicsSignal,
EpicsSignalRO,
EpicsMotor,
PseudoPositioner,
PseudoSingle,
PVPositioner,
Device,
Component,
Kind,
)
from ophyd.pseudopos import pseudo_position_argument, real_position_argument
from ophyd.utils.epics_pvs import data_type
class PmMonoBender(PseudoPositioner):
"""Monochromator bender
Small wrapper to combine the four monochromator bender motors.
"""
# Real axes
ai = Component(EpicsMotor, "TRYA", name="ai")
bo = Component(EpicsMotor, "TRYB", name="bo")
co = Component(EpicsMotor, "TRYC", name="co")
di = Component(EpicsMotor, "TRYD", name="di")
# Virtual axis
bend = Component(PseudoSingle, name="bend")
_real = ["ai", "bo", "co", "di"]
@pseudo_position_argument
def forward(self, pseudo_pos):
delta = pseudo_pos.bend - 0.25 * (
self.ai.position + self.bo.position + self.co.position + self.di.position
)
return self.RealPosition(
ai=self.ai.position + delta,
bo=self.bo.position + delta,
co=self.co.position + delta,
di=self.di.position + delta,
)
@real_position_argument
def inverse(self, real_pos):
return self.PseudoPosition(
bend=0.25 * (real_pos.ai + real_pos.bo + real_pos.co + real_pos.di)
)
def r2d(radians):
return radians * 180 / 3.141592
def d2r(degrees):
return degrees * 3.141592 / 180.0
class PmDetectorRotation(PseudoPositioner):
"""Detector rotation pseudo motor
Small wrapper to convert detector pusher position to rotation angle.
"""
_tables_dt_push_dist_mm = 890
# Real axes
dtpush = Component(EpicsMotor, "", name="dtpush")
# Virtual axis
dtth = Component(PseudoSingle, name="dtth")
_real = ["dtpush"]
@pseudo_position_argument
def forward(self, pseudo_pos):
return self.RealPosition(
dtpush=d2r(tan(-3.14 / 180 * pseudo_pos.dtth)) * self._tables_dt_push_dist_mm
)
@real_position_argument
def inverse(self, real_pos):
return self.PseudoPosition(dtth=r2d(-atan(real_pos.dtpush / self._tables_dt_push_dist_mm)))
class GirderMotorX1(PVPositioner):
"""Girder X translation pseudo motor"""
setpoint = Component(EpicsSignal, ":X_SET", name="sp")
readback = Component(EpicsSignalRO, ":X1", name="rbv")
done = Component(EpicsSignal, ":M-DMOV", name="dmov")
class GirderMotorY1(PVPositioner):
"""Girder Y translation pseudo motor"""
setpoint = Component(EpicsSignal, ":Y_SET", name="sp")
readback = Component(EpicsSignalRO, ":Y1", name="rbv")
done = Component(EpicsSignal, ":M-DMOV", name="dmov")
class GirderMotorYAW(PVPositioner):
"""Girder YAW pseudo motor"""
setpoint = Component(EpicsSignal, ":YAW_SET", name="sp")
readback = Component(EpicsSignalRO, ":YAW1", name="rbv")
done = Component(EpicsSignal, ":M-DMOV", name="dmov")
class GirderMotorROLL(PVPositioner):
"""Girder ROLL pseudo motor"""
setpoint = Component(EpicsSignal, ":ROLL_SET", name="sp")
readback = Component(EpicsSignalRO, ":ROLL1", name="rbv")
done = Component(EpicsSignal, ":M-DMOV", name="dmov")
class GirderMotorPITCH(PVPositioner):
"""Girder YAW pseudo motor"""
setpoint = Component(EpicsSignal, ":PITCH_SET", name="sp")
readback = Component(EpicsSignalRO, ":PITCH1", name="rbv")
done = Component(EpicsSignal, ":M-DMOV", name="dmov")
class VirtualEpicsSignalRO(EpicsSignalRO):
"""This is a test class to create derives signals from one or
multiple original signals...
"""
def calc(self, val):
return val
def get(self, *args, **kwargs):
raw = super().get(*args, **kwargs)
return self.calc(raw)
# def describe(self):
# val = self.get()
# d = super().describe()
# d[self.name]["dtype"] = data_type(val)
# return d
class MonoTheta1(VirtualEpicsSignalRO):
"""Converts the pusher motor position to theta angle"""
_mono_a0_enc_scale1 = -1.0
_mono_a1_lever_length1 = 206.706
_mono_a2_pusher_offs1 = 6.85858
_mono_a3_enc_offs1 = -16.9731
def calc(self, val):
asin_arg = (val - self._mono_a2_pusher_offs1) / self._mono_a1_lever_length1
theta1 = (
self._mono_a0_enc_scale1 * asin(asin_arg) / 3.141592 * 180.0 + self._mono_a3_enc_offs1
)
return theta1
class MonoTheta2(VirtualEpicsSignalRO):
"""Converts the pusher motor position to theta angle"""
_mono_a3_enc_offs2 = -19.7072
_mono_a2_pusher_offs2 = 5.93905
_mono_a1_lever_length2 = 206.572
_mono_a0_enc_scale2 = -1.0
def calc(self, val):
asin_arg = (val - self._mono_a2_pusher_offs2) / self._mono_a1_lever_length2
theta2 = (
self._mono_a0_enc_scale2 * asin(asin_arg) / 3.141592 * 180.0 + self._mono_a3_enc_offs2
)
return theta2
class EnergyKev(VirtualEpicsSignalRO):
"""Converts the pusher motor position to energy in keV"""
_mono_a3_enc_offs2 = -19.7072
_mono_a2_pusher_offs2 = 5.93905
_mono_a1_lever_length2 = 206.572
_mono_a0_enc_scale2 = -1.0
_mono_hce = 12.39852066
_mono_2d2 = 2 * 5.43102 / sqrt(3)
def calc(self, val):
asin_arg = (val - self._mono_a2_pusher_offs2) / self._mono_a1_lever_length2
theta2_deg = (
self._mono_a0_enc_scale2 * asin(asin_arg) / 3.141592 * 180.0 + self._mono_a3_enc_offs2
)
E_keV = -self._mono_hce / self._mono_2d2 / sin(theta2_deg / 180.0 * 3.14152)
return E_keV