diff --git a/.gitignore b/.gitignore index c097a5a..47011d0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ .DS_Store **/out **/.vscode -**/.pytest_cache \ No newline at end of file +**/.pytest_cache +*.egg-info + diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py new file mode 100644 index 0000000..05d37a8 --- /dev/null +++ b/ophyd_devices/epics/DeviceFactory.py @@ -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}") diff --git a/ophyd_devices/epics/__init__.py b/ophyd_devices/epics/__init__.py new file mode 100644 index 0000000..d57f5ef --- /dev/null +++ b/ophyd_devices/epics/__init__.py @@ -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 diff --git a/ophyd_devices/epics/db/machine_database.yml b/ophyd_devices/epics/db/machine_database.yml new file mode 100644 index 0000000..0146095 --- /dev/null +++ b/ophyd_devices/epics/db/machine_database.yml @@ -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 diff --git a/ophyd_devices/epics/db/test_database.yml b/ophyd_devices/epics/db/test_database.yml new file mode 100644 index 0000000..3686f10 --- /dev/null +++ b/ophyd_devices/epics/db/test_database.yml @@ -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 diff --git a/ophyd_devices/epics/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml new file mode 100644 index 0000000..600ecce --- /dev/null +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -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 + + diff --git a/ophyd_devices/epics/devices/DelayGeneratorDG645.py b/ophyd_devices/epics/devices/DelayGeneratorDG645.py new file mode 100644 index 0000000..6674217 --- /dev/null +++ b/ophyd_devices/epics/devices/DelayGeneratorDG645.py @@ -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") diff --git a/ophyd_devices/epics/devices/InsertionDevice.py b/ophyd_devices/epics/devices/InsertionDevice.py new file mode 100644 index 0000000..4144bc5 --- /dev/null +++ b/ophyd_devices/epics/devices/InsertionDevice.py @@ -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 diff --git a/ophyd_devices/epics/devices/SpmBase.py b/ophyd_devices/epics/devices/SpmBase.py new file mode 100644 index 0000000..502d3c1 --- /dev/null +++ b/ophyd_devices/epics/devices/SpmBase.py @@ -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() diff --git a/ophyd_devices/epics/devices/XbpmBase.py b/ophyd_devices/epics/devices/XbpmBase.py new file mode 100644 index 0000000..1cc540b --- /dev/null +++ b/ophyd_devices/epics/devices/XbpmBase.py @@ -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() diff --git a/ophyd_devices/epics/devices/__init__.py b/ophyd_devices/epics/devices/__init__.py new file mode 100644 index 0000000..f1a0cac --- /dev/null +++ b/ophyd_devices/epics/devices/__init__.py @@ -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 diff --git a/ophyd_devices/epics/devices/cSaxsVirtualMotors.py b/ophyd_devices/epics/devices/cSaxsVirtualMotors.py new file mode 100644 index 0000000..94d3b96 --- /dev/null +++ b/ophyd_devices/epics/devices/cSaxsVirtualMotors.py @@ -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 + ) diff --git a/ophyd_devices/epics/devices/mono_dccm.py b/ophyd_devices/epics/devices/mono_dccm.py new file mode 100644 index 0000000..11f7be7 --- /dev/null +++ b/ophyd_devices/epics/devices/mono_dccm.py @@ -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), + ) diff --git a/ophyd_devices/epics/devices/slits.py b/ophyd_devices/epics/devices/slits.py new file mode 100644 index 0000000..61e29a3 --- /dev/null +++ b/ophyd_devices/epics/devices/slits.py @@ -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 + ) diff --git a/ophyd_devices/epics/devices/specMotors.py b/ophyd_devices/epics/devices/specMotors.py new file mode 100644 index 0000000..b2d54ef --- /dev/null +++ b/ophyd_devices/epics/devices/specMotors.py @@ -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