From d6da41f5e62b6de87718aa5a2faa562b0a558b20 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Wed, 21 Sep 2022 15:02:12 +0200 Subject: [PATCH 01/19] First EPICS devices from controls repo --- ophyd_devices/epics/DelayGeneratorDG645.py | 154 +++++++++++++++++++++ ophyd_devices/epics/mono_dccm.py | 130 +++++++++++++++++ ophyd_devices/epics/slits.py | 68 +++++++++ 3 files changed, 352 insertions(+) create mode 100644 ophyd_devices/epics/DelayGeneratorDG645.py create mode 100644 ophyd_devices/epics/mono_dccm.py create mode 100644 ophyd_devices/epics/slits.py diff --git a/ophyd_devices/epics/DelayGeneratorDG645.py b/ophyd_devices/epics/DelayGeneratorDG645.py new file mode 100644 index 0000000..f3f6ed6 --- /dev/null +++ b/ophyd_devices/epics/DelayGeneratorDG645.py @@ -0,0 +1,154 @@ +# -*- 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() + + + +# pair = DelayPair("DGEN01:", name="delayer", channel="CD") +dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer") + + + + diff --git a/ophyd_devices/epics/mono_dccm.py b/ophyd_devices/epics/mono_dccm.py new file mode 100644 index 0000000..25f2414 --- /dev/null +++ b/ophyd_devices/epics/mono_dccm.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 13 18:06:15 2021 + +@author: mohacsi_i +""" + + + + +import numpy as np +from math import isclose +from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind +from ophyd.pseudopos import pseudo_position_argument, real_position_argument +from ophyd.sim import SynAxis, Syn2DGauss + +LN_CORR = 2e-4 + +def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): + """ Convert between angle and energy for Si monchromators + ATTENTION: 'angle' must be in radians, not degrees! + """ + lncorr = LN_CORR if lnc else 0.0 + angle = angle*np.pi/180 if deg else angle + + # Lattice constant along direction + d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) + energy = 12.39842 / (2.0 * d0 * np.sin(angle)) + return energy + + +def e2w(energy): + """ Convert between energy and wavelength + """ + return 0.1 * 12398.42 / energy + + +def w2e(wwl): + """ Convert between wavelength and energy + """ + return 12398.42 * 0.1 / wwl + + +def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): + """ Convert between energy and angle for Si monchromators + ATTENTION: 'angle' must be in radians, not degrees! + """ + lncorr = LN_CORR if lnc else 0.0 + + # Lattice constant along direction + d0 = 2*5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) + angle = np.arcsin(12.39842/d0/energy) + + # Rfine for bent mirror + if bent: + rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle) + dt = 0.2e-3 / rho * 0.279 + d0 = 2 * 5.43102 * (1.0+dt) / np.linalg.norm(hkl) + angle = np.arcsin(12.39842/d0/energy) + + return angle + + + + +class MonoMotor(PseudoPositioner): + """ Monochromator axis + + Small wrapper to combine a real angular axis with the corresponding energy. + ATTENTION: 'angle' is in degrees, at least for PXIII + """ + # Real axis (in degrees) + angle = Component(EpicsMotor, "", name='angle') + # Virtual axis + energy = Component(PseudoSingle, name='energy') + + _real = ['angle'] + + @pseudo_position_argument + def forward(self, pseudo_pos): + return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592) + + @real_position_argument + def inverse(self, real_pos): + return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0)) + + +class MonoDccm(PseudoPositioner): + """ Combined DCCM monochromator + + The first crystal selects the energy, the second one is only following. + DCCMs are quite simple in terms that they can't crash and we don't + have a beam offset. + ATTENTION: 'angle' is in degrees, at least for PXIII + """ + + # Real axis (in degrees) + th1 = Component(EpicsMotor, "ROX1", name='theta1') + th2 = Component(EpicsMotor, "ROX2", name='theta2') + + # Virtual axes + en1 = Component(PseudoSingle, name='en1') + en2 = Component(PseudoSingle, name='en2') + energy = Component(PseudoSingle, name='energy', kind=Kind.hinted) + + # Other parameters + #feedback = Component(EpicsSignal, "MONOBEAM", name="feedback") + #enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1") + #enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2") + + @pseudo_position_argument + def forward(self, pseudo_pos): + """ + WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning + + """ + if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: + # Probably the common energy was changed + return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592) + else: + # Probably the individual axes was changes + return self.RealPosition(th1=-180.0*e2a(pseudo_pos.en1)/3.141592, th2=180.0*e2a(pseudo_pos.en2)/3.141592) + + @real_position_argument + def inverse(self, real_pos): + return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0), + en2=a2e(3.141592*real_pos.th2/180.0), + energy=-a2e(3.141592*real_pos.th1/180.0)) + + diff --git a/ophyd_devices/epics/slits.py b/ophyd_devices/epics/slits.py new file mode 100644 index 0000000..20337ff --- /dev/null +++ b/ophyd_devices/epics/slits.py @@ -0,0 +1,68 @@ +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) + From 244326937135acec09890544e227fcb85f29e550 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Mon, 14 Nov 2022 14:18:58 +0100 Subject: [PATCH 02/19] Added device factory --- ophyd_devices/epics/DeviceFactory.py | 67 ++ ophyd_devices/epics/db/machine_database.yml | 21 + ophyd_devices/epics/db/test_database.yml | 35 + ophyd_devices/epics/db/x12sa_database.yml | 679 ++++++++++++++++++ .../{ => proxies}/DelayGeneratorDG645.py | 0 ophyd_devices/epics/proxies/__init__.py | 2 + .../epics/{ => proxies}/mono_dccm.py | 0 ophyd_devices/epics/{ => proxies}/slits.py | 0 8 files changed, 804 insertions(+) create mode 100644 ophyd_devices/epics/DeviceFactory.py create mode 100644 ophyd_devices/epics/db/machine_database.yml create mode 100644 ophyd_devices/epics/db/test_database.yml create mode 100644 ophyd_devices/epics/db/x12sa_database.yml rename ophyd_devices/epics/{ => proxies}/DelayGeneratorDG645.py (100%) create mode 100644 ophyd_devices/epics/proxies/__init__.py rename ophyd_devices/epics/{ => proxies}/mono_dccm.py (100%) rename ophyd_devices/epics/{ => proxies}/slits.py (100%) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py new file mode 100644 index 0000000..14dce01 --- /dev/null +++ b/ophyd_devices/epics/DeviceFactory.py @@ -0,0 +1,67 @@ +# -*- 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 ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor +from ophyd.quadem import QuadEM +import pathlib + + +path = os.path.dirname(os.path.abspath(__file__)) + + +from proxies import * + + +# Load SLS common database +fp = open(f"{path}/db/test_database.yml", "r") +lut_db = yaml.load(fp, Loader=yaml.Loader) + +# Load SLS common database +fp = open(f"{path}/db/machine_database.yml", "r") +lut_db = yaml.load(fp, Loader=yaml.Loader) + +# Load beamline specific database +bl = os.getenv('BEAMLINE_XNAME', "X12SA") +fp = open(f"{path}/db/{bl.lower()}_database.yml", "r") +lut_db.update(yaml.load(fp, Loader=yaml.Loader)) + + +def createProxy(name: str, connect=True) -> OphydObject: + """ Factory routine to create an ophyd device with a pre-defined schema. + Does nothing if the device is already an OphydObject! + """ + if issubclass(type(name), OphydObject): + return name + + entry = lut_db[name] + cls_candidate = globals()[entry["type"]] + print(f"Device candidate: {cls_candidate}") + + try: + if issubclass(cls_candidate, OphydObject): + ret = cls_candidate(**entry["config"]) + if connect: + ret.wait_for_connection(timeout=5) + return ret + else: + raise RuntimeError(f"Unsupported return class: {schema}") + except TypeError: + # Simulated devices + if issubclass(type(cls_candidate), OphydObject): + return cls_candidate + else: + raise RuntimeError(f"Unsupported return class: {schema}") + + +if __name__ == "__main__": + pass + + + 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..979e7e9 --- /dev/null +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -0,0 +1,679 @@ +idgap: + desc: 'Undulator gap size [mm]' + acquisition: {schedule: sync} + config: {name: idgap, read_pv: 'X12SA-ID-GAP:READ'} + deviceGroup: monitor + status: {enabled: true} + type: EpicsSignalRO +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-BM2: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 +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 +mopush1: + desc: 'Monochromator crystal 1 angle' + acquisition: {schedule: sync} + config: {name: mopush1, prefix: 'X12SA-OP-MO:ROX1'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +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 +mopush2: + desc: 'Monochromator crystal 2 angle' + acquisition: {schedule: sync} + config: {name: mopush2, prefix: 'X12SA-OP-MO:ROX2'} + 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:ROTY2'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +moroll2: + desc: 'Monochromator crystal 2 roll movement' + acquisition: {schedule: sync} + config: {name: moroll2, prefix: 'X12SA-OP-MO:ROTZ2'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +mobdai: + desc: 'Monochromator ??? inner motor' + acquisition: {schedule: sync} + config: {name: mobdai, prefix: 'X12SA-OP-MO:TRYA'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +mobdbo: + desc: 'Monochromator ??? outer motor' + acquisition: {schedule: sync} + config: {name: mobdbo, prefix: 'X12SA-OP-MO:TRYB'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +mobdco: + desc: 'Monochromator ??? outer motor' + acquisition: {schedule: sync} + config: {name: mobdco, prefix: 'X12SA-OP-MO:TRYC'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +mobddi: + desc: 'Monochromator ??? inner motor' + acquisition: {schedule: sync} + config: {name: mobddi, prefix: 'X12SA-OP-MO:TRYD'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +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 motion' + acquisition: {schedule: sync} + config: {name: dtpush, prefix: 'X12SA-ES1-DETT:ROX1'} + deviceGroup: beamlineMotor + status: {enabled: true} + type: EpicsMotor +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 +bpm3: + desc: 'XBPM 3: White beam before mono' + acquisition: {schedule: sync} + config: {name: bpm3, prefix: 'X12SA-OP-BPM3:'} + deviceGroup: monitor + status: {enabled: true} + type: QuadEM +bpm5: + desc: 'XBPM 5: After mirror' + acquisition: {schedule: sync} + config: {name: bpm5, prefix: 'X12SA-OP-BPM5:'} + deviceGroup: monitor + status: {enabled: true} + type: QuadEM +bpm6: + desc: 'XBPM 6: Not commissioned' + acquisition: {schedule: sync} + config: {name: bpm6, prefix: 'X12SA-OP-BPM6:'} + deviceGroup: monitor + status: {enabled: true} + type: QuadEM +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 diff --git a/ophyd_devices/epics/DelayGeneratorDG645.py b/ophyd_devices/epics/proxies/DelayGeneratorDG645.py similarity index 100% rename from ophyd_devices/epics/DelayGeneratorDG645.py rename to ophyd_devices/epics/proxies/DelayGeneratorDG645.py diff --git a/ophyd_devices/epics/proxies/__init__.py b/ophyd_devices/epics/proxies/__init__.py new file mode 100644 index 0000000..7a737c9 --- /dev/null +++ b/ophyd_devices/epics/proxies/__init__.py @@ -0,0 +1,2 @@ +from .DelayGeneratorDG645 import DelayGeneratorDG645 +from .slits import SlitH, SlitV diff --git a/ophyd_devices/epics/mono_dccm.py b/ophyd_devices/epics/proxies/mono_dccm.py similarity index 100% rename from ophyd_devices/epics/mono_dccm.py rename to ophyd_devices/epics/proxies/mono_dccm.py diff --git a/ophyd_devices/epics/slits.py b/ophyd_devices/epics/proxies/slits.py similarity index 100% rename from ophyd_devices/epics/slits.py rename to ophyd_devices/epics/proxies/slits.py From 8b74d08097705f66b4aa92816cb0c8e87d736080 Mon Sep 17 00:00:00 2001 From: gac-x12sa Date: Mon, 14 Nov 2022 15:19:35 +0100 Subject: [PATCH 03/19] XBPM proxies --- ophyd_devices/epics/db/x12sa_database.yml | 15 ++- ophyd_devices/epics/proxies/SpmBase.py | 104 +++++++++++++++ ophyd_devices/epics/proxies/XbpmBase.py | 153 ++++++++++++++++++++++ ophyd_devices/epics/proxies/__init__.py | 2 + ophyd_devices/epics/proxies/quadem.py | 130 ++++++++++++++++++ 5 files changed, 400 insertions(+), 4 deletions(-) create mode 100644 ophyd_devices/epics/proxies/SpmBase.py create mode 100644 ophyd_devices/epics/proxies/XbpmBase.py create mode 100644 ophyd_devices/epics/proxies/quadem.py diff --git a/ophyd_devices/epics/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index 979e7e9..846844c 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -566,14 +566,21 @@ led: status: {enabled: true} type: EpicsSignalRO bpm3: - desc: 'XBPM 3: White beam before mono' + desc: 'XBPM 3: White beam before mono (VME)' acquisition: {schedule: sync} - config: {name: bpm3, prefix: 'X12SA-OP-BPM3:'} + config: {name: bpm3, prefix: 'X12SA-OP-BPM1:'} deviceGroup: monitor status: {enabled: true} - type: QuadEM + type: XbpmCsaxsOp +bpm4: + desc: 'XBPM 4: Somewhere around mono (VME)' + acquisition: {schedule: sync} + config: {name: bpm4, prefix: 'X12SA-OP-BPM2:'} + deviceGroup: monitor + status: {enabled: true} + type: XbpmCsaxsOp bpm5: - desc: 'XBPM 5: After mirror' + desc: 'XBPM 5: Not commissioned' acquisition: {schedule: sync} config: {name: bpm5, prefix: 'X12SA-OP-BPM5:'} deviceGroup: monitor diff --git a/ophyd_devices/epics/proxies/SpmBase.py b/ophyd_devices/epics/proxies/SpmBase.py new file mode 100644 index 0000000..b080ac2 --- /dev/null +++ b/ophyd_devices/epics/proxies/SpmBase.py @@ -0,0 +1,104 @@ +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 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) + 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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) + + #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/proxies/XbpmBase.py b/ophyd_devices/epics/proxies/XbpmBase.py new file mode 100644 index 0000000..bcd30bd --- /dev/null +++ b/ophyd_devices/epics/proxies/XbpmBase.py @@ -0,0 +1,153 @@ +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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) + + #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/proxies/__init__.py b/ophyd_devices/epics/proxies/__init__.py index 7a737c9..5c7c1e3 100644 --- a/ophyd_devices/epics/proxies/__init__.py +++ b/ophyd_devices/epics/proxies/__init__.py @@ -1,2 +1,4 @@ from .DelayGeneratorDG645 import DelayGeneratorDG645 from .slits import SlitH, SlitV +from .XbpmBase import XbpmBase, XbpmCsaxsOp +from .SpmBase import SpmBase diff --git a/ophyd_devices/epics/proxies/quadem.py b/ophyd_devices/epics/proxies/quadem.py new file mode 100644 index 0000000..25f2414 --- /dev/null +++ b/ophyd_devices/epics/proxies/quadem.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 13 18:06:15 2021 + +@author: mohacsi_i +""" + + + + +import numpy as np +from math import isclose +from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind +from ophyd.pseudopos import pseudo_position_argument, real_position_argument +from ophyd.sim import SynAxis, Syn2DGauss + +LN_CORR = 2e-4 + +def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): + """ Convert between angle and energy for Si monchromators + ATTENTION: 'angle' must be in radians, not degrees! + """ + lncorr = LN_CORR if lnc else 0.0 + angle = angle*np.pi/180 if deg else angle + + # Lattice constant along direction + d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) + energy = 12.39842 / (2.0 * d0 * np.sin(angle)) + return energy + + +def e2w(energy): + """ Convert between energy and wavelength + """ + return 0.1 * 12398.42 / energy + + +def w2e(wwl): + """ Convert between wavelength and energy + """ + return 12398.42 * 0.1 / wwl + + +def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): + """ Convert between energy and angle for Si monchromators + ATTENTION: 'angle' must be in radians, not degrees! + """ + lncorr = LN_CORR if lnc else 0.0 + + # Lattice constant along direction + d0 = 2*5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) + angle = np.arcsin(12.39842/d0/energy) + + # Rfine for bent mirror + if bent: + rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle) + dt = 0.2e-3 / rho * 0.279 + d0 = 2 * 5.43102 * (1.0+dt) / np.linalg.norm(hkl) + angle = np.arcsin(12.39842/d0/energy) + + return angle + + + + +class MonoMotor(PseudoPositioner): + """ Monochromator axis + + Small wrapper to combine a real angular axis with the corresponding energy. + ATTENTION: 'angle' is in degrees, at least for PXIII + """ + # Real axis (in degrees) + angle = Component(EpicsMotor, "", name='angle') + # Virtual axis + energy = Component(PseudoSingle, name='energy') + + _real = ['angle'] + + @pseudo_position_argument + def forward(self, pseudo_pos): + return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592) + + @real_position_argument + def inverse(self, real_pos): + return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0)) + + +class MonoDccm(PseudoPositioner): + """ Combined DCCM monochromator + + The first crystal selects the energy, the second one is only following. + DCCMs are quite simple in terms that they can't crash and we don't + have a beam offset. + ATTENTION: 'angle' is in degrees, at least for PXIII + """ + + # Real axis (in degrees) + th1 = Component(EpicsMotor, "ROX1", name='theta1') + th2 = Component(EpicsMotor, "ROX2", name='theta2') + + # Virtual axes + en1 = Component(PseudoSingle, name='en1') + en2 = Component(PseudoSingle, name='en2') + energy = Component(PseudoSingle, name='energy', kind=Kind.hinted) + + # Other parameters + #feedback = Component(EpicsSignal, "MONOBEAM", name="feedback") + #enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1") + #enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2") + + @pseudo_position_argument + def forward(self, pseudo_pos): + """ + WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning + + """ + if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: + # Probably the common energy was changed + return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592) + else: + # Probably the individual axes was changes + return self.RealPosition(th1=-180.0*e2a(pseudo_pos.en1)/3.141592, th2=180.0*e2a(pseudo_pos.en2)/3.141592) + + @real_position_argument + def inverse(self, real_pos): + return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0), + en2=a2e(3.141592*real_pos.th2/180.0), + energy=-a2e(3.141592*real_pos.th1/180.0)) + + From 82a37ceb3857e5bf4dcb9744cd0bbccacb7685d3 Mon Sep 17 00:00:00 2001 From: gac-x12sa Date: Mon, 14 Nov 2022 15:43:49 +0100 Subject: [PATCH 04/19] A few more fixes --- ophyd_devices/epics/DeviceFactory.py | 12 ++++++++---- ophyd_devices/epics/db/x12sa_database.yml | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index 14dce01..ad25d94 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -9,6 +9,7 @@ import os import yaml from ophyd.ophydobj import OphydObject from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor +from ophyd.sim import SynAxis, SynSignal, SynPeriodicSignal from ophyd.quadem import QuadEM import pathlib @@ -23,9 +24,9 @@ from proxies import * fp = open(f"{path}/db/test_database.yml", "r") lut_db = yaml.load(fp, Loader=yaml.Loader) -# Load SLS common database -fp = open(f"{path}/db/machine_database.yml", "r") -lut_db = yaml.load(fp, Loader=yaml.Loader) +# Load SLS common database (already in DB) +#fp = open(f"{path}/db/machine_database.yml", "r") +#lut_db = yaml.load(fp, Loader=yaml.Loader) # Load beamline specific database bl = os.getenv('BEAMLINE_XNAME', "X12SA") @@ -61,7 +62,10 @@ def createProxy(name: str, connect=True) -> OphydObject: if __name__ == "__main__": - pass + for key in lut_db: + print(key) + dut = createProxy(str(key)) + diff --git a/ophyd_devices/epics/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index 846844c..a80281d 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -43,7 +43,7 @@ di2trx: di2try: desc: 'FrontEnd diaphragm 2 vertical movement' acquisition: {schedule: sync} - config: {name: di2try, prefix: 'X12SA-FE-BM2:TRY1'} + config: {name: di2try, prefix: 'X12SA-FE-DI2:TRY1'} deviceGroup: beamlineMotor status: {enabled: true} type: EpicsMotor @@ -194,24 +194,24 @@ mopush2: 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 +#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:ROTY2'} + 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:ROTZ2'} + config: {name: moroll2, prefix: 'X12SA-OP-MO:ROZ2'} deviceGroup: beamlineMotor status: {enabled: true} type: EpicsMotor From 19f5f728cc5c224f0802bc88a6084d073d6f78ac Mon Sep 17 00:00:00 2001 From: gac-x12sa Date: Mon, 14 Nov 2022 16:33:47 +0100 Subject: [PATCH 05/19] Added insertion device proxy --- ophyd_devices/epics/db/x12sa_database.yml | 4 +- .../epics/proxies/InsertionDevice.py | 43 +++++++++++++++++++ ophyd_devices/epics/proxies/__init__.py | 1 + 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 ophyd_devices/epics/proxies/InsertionDevice.py diff --git a/ophyd_devices/epics/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index a80281d..e6d9cfc 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -1,10 +1,10 @@ idgap: desc: 'Undulator gap size [mm]' acquisition: {schedule: sync} - config: {name: idgap, read_pv: 'X12SA-ID-GAP:READ'} + config: {name: idgap, prefix: 'X12SA-ID'} deviceGroup: monitor status: {enabled: true} - type: EpicsSignalRO + type: InsertionDevice bm1trx: desc: 'FrontEnd XBPM 1 horizontal movement' acquisition: {schedule: sync} diff --git a/ophyd_devices/epics/proxies/InsertionDevice.py b/ophyd_devices/epics/proxies/InsertionDevice.py new file mode 100644 index 0000000..53cc976 --- /dev/null +++ b/ophyd_devices/epics/proxies/InsertionDevice.py @@ -0,0 +1,43 @@ +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/proxies/__init__.py b/ophyd_devices/epics/proxies/__init__.py index 5c7c1e3..6539e1e 100644 --- a/ophyd_devices/epics/proxies/__init__.py +++ b/ophyd_devices/epics/proxies/__init__.py @@ -2,3 +2,4 @@ from .DelayGeneratorDG645 import DelayGeneratorDG645 from .slits import SlitH, SlitV from .XbpmBase import XbpmBase, XbpmCsaxsOp from .SpmBase import SpmBase +from .InsertionDevice import InsertionDevice From c7867a910fb93f9d8bab71f86612712e6736c7c7 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Mon, 14 Nov 2022 17:37:03 +0100 Subject: [PATCH 06/19] Flaking --- ophyd_devices/epics/DeviceFactory.py | 41 ++---- .../epics/proxies/DelayGeneratorDG645.py | 108 +++++++-------- .../epics/proxies/InsertionDevice.py | 29 +--- ophyd_devices/epics/proxies/SpmBase.py | 44 +++--- ophyd_devices/epics/proxies/XbpmBase.py | 88 +++++------- ophyd_devices/epics/proxies/mono_dccm.py | 65 ++++----- ophyd_devices/epics/proxies/quadem.py | 130 ------------------ ophyd_devices/epics/proxies/slits.py | 42 +++--- 8 files changed, 174 insertions(+), 373 deletions(-) delete mode 100644 ophyd_devices/epics/proxies/quadem.py diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index ad25d94..88db585 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -25,47 +25,36 @@ fp = open(f"{path}/db/test_database.yml", "r") lut_db = yaml.load(fp, Loader=yaml.Loader) # Load SLS common database (already in DB) -#fp = open(f"{path}/db/machine_database.yml", "r") -#lut_db = yaml.load(fp, Loader=yaml.Loader) +# fp = open(f"{path}/db/machine_database.yml", "r") +# lut_db = yaml.load(fp, Loader=yaml.Loader) # Load beamline specific database -bl = os.getenv('BEAMLINE_XNAME', "X12SA") +bl = os.getenv("BEAMLINE_XNAME", "X12SA") fp = open(f"{path}/db/{bl.lower()}_database.yml", "r") lut_db.update(yaml.load(fp, Loader=yaml.Loader)) def createProxy(name: str, connect=True) -> OphydObject: - """ Factory routine to create an ophyd device with a pre-defined schema. - Does nothing if the device is already an OphydObject! - """ + """Factory routine to create an ophyd device with a pre-defined schema. + Does nothing if the device is already an OphydObject! + """ if issubclass(type(name), OphydObject): return name - - entry = lut_db[name] + + entry = lut_db[name] cls_candidate = globals()[entry["type"]] print(f"Device candidate: {cls_candidate}") - try: - if issubclass(cls_candidate, OphydObject): - ret = cls_candidate(**entry["config"]) - if connect: - ret.wait_for_connection(timeout=5) - return ret - else: - raise RuntimeError(f"Unsupported return class: {schema}") - except TypeError: - # Simulated devices - if issubclass(type(cls_candidate), OphydObject): - return cls_candidate - else: - raise RuntimeError(f"Unsupported return class: {schema}") + 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__": for key in lut_db: print(key) dut = createProxy(str(key)) - - - - diff --git a/ophyd_devices/epics/proxies/DelayGeneratorDG645.py b/ophyd_devices/epics/proxies/DelayGeneratorDG645.py index f3f6ed6..b4cc615 100644 --- a/ophyd_devices/epics/proxies/DelayGeneratorDG645.py +++ b/ophyd_devices/epics/proxies/DelayGeneratorDG645.py @@ -11,79 +11,78 @@ from ophyd.pseudopos import pseudo_position_argument, real_position_argument, Ps 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. + """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) + 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. + """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') + width = Component(PseudoSingle, limits=(0, 2000.0), name='pulsewidth') # The real delay axes ch1 = Component(DummyPositioner, name='ch1') - ch2 = Component(DummyPositioner, name='ch2') - + 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'] + 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''' + """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) - - + """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 + """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') @@ -94,7 +93,7 @@ class DelayGeneratorDG645(Device): 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) @@ -112,15 +111,14 @@ class DelayGeneratorDG645(Device): burstDelay = Component(EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name='burstdelay', kind=Kind.config) burstPeriod = Component(EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name='burstperiod', kind=Kind.config) - def stage(self): """Trigger the generator by arming to accept triggers""" self.arm.write(1).wait() - + 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 @@ -139,16 +137,12 @@ class DelayGeneratorDG645(Device): 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() - - - -# pair = DelayPair("DGEN01:", name="delayer", channel="CD") -dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer") - - - - + + +# Automatically connect to test environmenr if directly invoked +if __name__ == "__main__": + dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer") diff --git a/ophyd_devices/epics/proxies/InsertionDevice.py b/ophyd_devices/epics/proxies/InsertionDevice.py index 53cc976..ef0bb99 100644 --- a/ophyd_devices/epics/proxies/InsertionDevice.py +++ b/ophyd_devices/epics/proxies/InsertionDevice.py @@ -2,13 +2,13 @@ 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... + """Python wrapper for the CSAXS insertion device control - WARN: The x and y are not updated by the IOC + 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) @@ -24,20 +24,3 @@ class InsertionDevice(PVPositioner): # (NA for important devices) if __name__ == "__main__": pass - - - - - - - - - - - - - - - - - diff --git a/ophyd_devices/epics/proxies/SpmBase.py b/ophyd_devices/epics/proxies/SpmBase.py index b080ac2..88ca98b 100644 --- a/ophyd_devices/epics/proxies/SpmBase.py +++ b/ophyd_devices/epics/proxies/SpmBase.py @@ -2,15 +2,16 @@ 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 user can - change the beam position offset. + """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 user can + change the beam position offset. """ # Motor interface s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True) @@ -21,17 +22,17 @@ class SpmBase(Device): 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. + """Python wrapper for simulated Staggered Blade Pair Monitors - This simulation device extends the basic proxy with a script that - fills signals with quasi-randomized values. + 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) @@ -42,25 +43,25 @@ class SpmSim(SpmBase): 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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) - + #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 @@ -84,7 +85,7 @@ class SpmSim(SpmBase): #plt.imshow(beam) #plt.show(block=False) plt.pause(0.5) - + # Automatically start simulation if directly invoked if __name__ == "__main__": @@ -101,4 +102,3 @@ if __name__ == "__main__": print("---") spm1.sim() spm2.sim() - diff --git a/ophyd_devices/epics/proxies/XbpmBase.py b/ophyd_devices/epics/proxies/XbpmBase.py index bcd30bd..5f6d13b 100644 --- a/ophyd_devices/epics/proxies/XbpmBase.py +++ b/ophyd_devices/epics/proxies/XbpmBase.py @@ -4,12 +4,12 @@ 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... + """Python wrapper for custom XBPMs in the cSAXS optics hutch - WARN: The x and y are not updated by the IOC + 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) @@ -21,16 +21,16 @@ class XbpmCsaxsOp(Device): 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. + """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) @@ -48,23 +48,20 @@ class XbpmBase(Device): 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. + """Python wrapper for simulated X-ray Beam Position Monitors - This simulation device extends the basic proxy with a script that - fills signals with quasi-randomized values. + 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) @@ -75,25 +72,25 @@ class XbpmSim(XbpmBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + self._MX = 0 self._MY = 0 - self._I0 = 255.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) - + 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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) - + #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 @@ -117,7 +114,7 @@ class XbpmSim(XbpmBase): #plt.imshow(beam) #plt.show(block=False) plt.pause(0.5) - + # Automatically start simulation if directly invoked if __name__ == "__main__": @@ -134,20 +131,3 @@ if __name__ == "__main__": print("---") xbpm1.sim() xbpm2.sim() - - - - - - - - - - - - - - - - - diff --git a/ophyd_devices/epics/proxies/mono_dccm.py b/ophyd_devices/epics/proxies/mono_dccm.py index 25f2414..4adeded 100644 --- a/ophyd_devices/epics/proxies/mono_dccm.py +++ b/ophyd_devices/epics/proxies/mono_dccm.py @@ -3,11 +3,10 @@ Created on Wed Oct 13 18:06:15 2021 @author: mohacsi_i + +IMPORTANT: Virtual monochromator axes should be implemented already in EPICS!!! """ - - - import numpy as np from math import isclose from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind @@ -16,9 +15,10 @@ 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! + """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 @@ -30,28 +30,28 @@ def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): def e2w(energy): - """ Convert between energy and wavelength + """Convert between energy and wavelength """ return 0.1 * 12398.42 / energy def w2e(wwl): - """ Convert between wavelength and energy + """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! + """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 + # 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 + # Rfine for bent mirror if bent: rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle) dt = 0.2e-3 / rho * 0.279 @@ -61,37 +61,35 @@ def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): 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 + """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)) - - + 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 + """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) @@ -110,21 +108,16 @@ class MonoDccm(PseudoPositioner): @pseudo_position_argument def forward(self, pseudo_pos): - """ - WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning - - """ + """WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning""" if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: # Probably the common energy was changed return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592) 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)) - - + energy=-a2e(3.141592*real_pos.th1/180.0)) diff --git a/ophyd_devices/epics/proxies/quadem.py b/ophyd_devices/epics/proxies/quadem.py deleted file mode 100644 index 25f2414..0000000 --- a/ophyd_devices/epics/proxies/quadem.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 13 18:06:15 2021 - -@author: mohacsi_i -""" - - - - -import numpy as np -from math import isclose -from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind -from ophyd.pseudopos import pseudo_position_argument, real_position_argument -from ophyd.sim import SynAxis, Syn2DGauss - -LN_CORR = 2e-4 - -def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): - """ Convert between angle and energy for Si monchromators - ATTENTION: 'angle' must be in radians, not degrees! - """ - lncorr = LN_CORR if lnc else 0.0 - angle = angle*np.pi/180 if deg else angle - - # Lattice constant along direction - d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) - energy = 12.39842 / (2.0 * d0 * np.sin(angle)) - return energy - - -def e2w(energy): - """ Convert between energy and wavelength - """ - return 0.1 * 12398.42 / energy - - -def w2e(wwl): - """ Convert between wavelength and energy - """ - return 12398.42 * 0.1 / wwl - - -def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): - """ Convert between energy and angle for Si monchromators - ATTENTION: 'angle' must be in radians, not degrees! - """ - lncorr = LN_CORR if lnc else 0.0 - - # Lattice constant along direction - d0 = 2*5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) - angle = np.arcsin(12.39842/d0/energy) - - # Rfine for bent mirror - if bent: - rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle) - dt = 0.2e-3 / rho * 0.279 - d0 = 2 * 5.43102 * (1.0+dt) / np.linalg.norm(hkl) - angle = np.arcsin(12.39842/d0/energy) - - return angle - - - - -class MonoMotor(PseudoPositioner): - """ Monochromator axis - - Small wrapper to combine a real angular axis with the corresponding energy. - ATTENTION: 'angle' is in degrees, at least for PXIII - """ - # Real axis (in degrees) - angle = Component(EpicsMotor, "", name='angle') - # Virtual axis - energy = Component(PseudoSingle, name='energy') - - _real = ['angle'] - - @pseudo_position_argument - def forward(self, pseudo_pos): - return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592) - - @real_position_argument - def inverse(self, real_pos): - return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0)) - - -class MonoDccm(PseudoPositioner): - """ Combined DCCM monochromator - - The first crystal selects the energy, the second one is only following. - DCCMs are quite simple in terms that they can't crash and we don't - have a beam offset. - ATTENTION: 'angle' is in degrees, at least for PXIII - """ - - # Real axis (in degrees) - th1 = Component(EpicsMotor, "ROX1", name='theta1') - th2 = Component(EpicsMotor, "ROX2", name='theta2') - - # Virtual axes - en1 = Component(PseudoSingle, name='en1') - en2 = Component(PseudoSingle, name='en2') - energy = Component(PseudoSingle, name='energy', kind=Kind.hinted) - - # Other parameters - #feedback = Component(EpicsSignal, "MONOBEAM", name="feedback") - #enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1") - #enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2") - - @pseudo_position_argument - def forward(self, pseudo_pos): - """ - WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning - - """ - if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: - # Probably the common energy was changed - return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592) - else: - # Probably the individual axes was changes - return self.RealPosition(th1=-180.0*e2a(pseudo_pos.en1)/3.141592, th2=180.0*e2a(pseudo_pos.en2)/3.141592) - - @real_position_argument - def inverse(self, real_pos): - return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0), - en2=a2e(3.141592*real_pos.th2/180.0), - energy=-a2e(3.141592*real_pos.th1/180.0)) - - diff --git a/ophyd_devices/epics/proxies/slits.py b/ophyd_devices/epics/proxies/slits.py index 20337ff..0e8a14d 100644 --- a/ophyd_devices/epics/proxies/slits.py +++ b/ophyd_devices/epics/proxies/slits.py @@ -2,20 +2,14 @@ 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. + """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") @@ -37,16 +31,15 @@ class SlitH(PseudoPositioner): 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. - """ + """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") @@ -56,13 +49,12 @@ class SlitV(PseudoPositioner): @pseudo_position_argument def forward(self, pseudo_pos): - '''Run a forward (pseudo -> real) calculation''' + """Run a forward (pseudo -> real) calculation""" return self.RealPosition(y1=pseudo_pos.ceny-pseudo_pos.gapy/2, y2=pseudo_pos.ceny+pseudo_pos.gapy/2) @real_position_argument def inverse(self, real_pos): - '''Run an inverse (real -> pseudo) calculation''' + """Run an inverse (real -> pseudo) calculation""" return self.PseudoPosition(ceny=(real_pos.y1+real_pos.y2)/2, gapy=real_pos.y2-real_pos.y1) - From 3a7dd4e3cfc84517e8be86b29a1f2811fbdbc513 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Mon, 14 Nov 2022 18:00:18 +0100 Subject: [PATCH 07/19] Codestyle --- .../epics/proxies/InsertionDevice.py | 10 +-- ophyd_devices/epics/proxies/SpmBase.py | 64 +++++++++---------- ophyd_devices/epics/proxies/XbpmBase.py | 56 ++++++++-------- ophyd_devices/epics/proxies/mono_dccm.py | 53 ++++++++------- ophyd_devices/epics/proxies/slits.py | 22 +++---- 5 files changed, 107 insertions(+), 98 deletions(-) diff --git a/ophyd_devices/epics/proxies/InsertionDevice.py b/ophyd_devices/epics/proxies/InsertionDevice.py index ef0bb99..dbba07e 100644 --- a/ophyd_devices/epics/proxies/InsertionDevice.py +++ b/ophyd_devices/epics/proxies/InsertionDevice.py @@ -4,11 +4,11 @@ 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... + 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 + 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) @@ -23,4 +23,4 @@ class InsertionDevice(PVPositioner): # Automatically start simulation if directly invoked # (NA for important devices) if __name__ == "__main__": - pass + pass diff --git a/ophyd_devices/epics/proxies/SpmBase.py b/ophyd_devices/epics/proxies/SpmBase.py index 88ca98b..d96985e 100644 --- a/ophyd_devices/epics/proxies/SpmBase.py +++ b/ophyd_devices/epics/proxies/SpmBase.py @@ -6,12 +6,12 @@ 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. + 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 user can - change the beam position offset. + 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) @@ -27,12 +27,12 @@ class SpmBase(Device): 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. + 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. + 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) @@ -53,13 +53,13 @@ class SpmSim(SpmBase): def _simFrame(self): """Generator to simulate a jumping gaussian""" - # define normalized 2D 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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) - #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) + # 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) @@ -68,12 +68,12 @@ class SpmSim(SpmBase): 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 + 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() @@ -82,23 +82,23 @@ class SpmSim(SpmBase): 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.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 = 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.wait_for_connection(timeout=5) + spm2.wait_for_connection(timeout=5) - spm1.rangew.set(1).wait() - spm2.rangew.set(1).wait() + spm1.rangew.set(1).wait() + spm2.rangew.set(1).wait() - while True: - print("---") - spm1.sim() - spm2.sim() + while True: + print("---") + spm1.sim() + spm2.sim() diff --git a/ophyd_devices/epics/proxies/XbpmBase.py b/ophyd_devices/epics/proxies/XbpmBase.py index 5f6d13b..344ea28 100644 --- a/ophyd_devices/epics/proxies/XbpmBase.py +++ b/ophyd_devices/epics/proxies/XbpmBase.py @@ -6,10 +6,10 @@ 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... + 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 + 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) @@ -23,14 +23,14 @@ class XbpmCsaxsOp(Device): 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. + 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. + 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) @@ -51,17 +51,17 @@ class XbpmBase(Device): 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. + 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 + 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. + 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) @@ -118,16 +118,16 @@ class XbpmSim(XbpmBase): # Automatically start simulation if directly invoked if __name__ == "__main__": - xbpm1 = XbpmSim("X01DA-FE-XBPM1:", name="xbpm1") - xbpm2 = XbpmSim("X01DA-FE-XBPM2:", name="xbpm2") + 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.wait_for_connection(timeout=5) + xbpm2.wait_for_connection(timeout=5) - xbpm1.rangew.set(1).wait() - xbpm2.rangew.set(1).wait() + xbpm1.rangew.set(1).wait() + xbpm2.rangew.set(1).wait() - while True: - print("---") - xbpm1.sim() - xbpm2.sim() + while True: + print("---") + xbpm1.sim() + xbpm2.sim() diff --git a/ophyd_devices/epics/proxies/mono_dccm.py b/ophyd_devices/epics/proxies/mono_dccm.py index 4adeded..a53b9a5 100644 --- a/ophyd_devices/epics/proxies/mono_dccm.py +++ b/ophyd_devices/epics/proxies/mono_dccm.py @@ -9,14 +9,15 @@ 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 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): +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! """ @@ -24,7 +25,7 @@ def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): angle = angle*np.pi/180 if deg else angle # Lattice constant along direction - d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) + d0 = 5.43102 * (1.0 - lncorr) / np.linalg.norm(hkl) energy = 12.39842 / (2.0 * d0 * np.sin(angle)) return energy @@ -41,22 +42,22 @@ def w2e(wwl): return 12398.42 * 0.1 / wwl -def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): +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) + 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) + d0 = 2 * 5.43102 * (1.0 + dt) / np.linalg.norm(hkl) + angle = np.arcsin(12.39842 / d0 / energy) return angle @@ -64,8 +65,8 @@ def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): 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 + 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') @@ -86,10 +87,10 @@ class MonoMotor(PseudoPositioner): 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 + 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) @@ -102,22 +103,30 @@ class MonoDccm(PseudoPositioner): 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") + # 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) + 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) + 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)) + 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/proxies/slits.py b/ophyd_devices/epics/proxies/slits.py index 0e8a14d..4c5f5db 100644 --- a/ophyd_devices/epics/proxies/slits.py +++ b/ophyd_devices/epics/proxies/slits.py @@ -1,15 +1,15 @@ from ophyd import Device, Component, EpicsMotor, PseudoPositioner, PseudoSingle -from ophyd.pseudopos import pseudo_position_argument,real_position_argument +from ophyd.pseudopos import pseudo_position_argument, real_position_argument class SlitH(PseudoPositioner): """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! + 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. + NOTE: The real and virtual axes are wrapped together. """ # Motor interface x1 = Component(EpicsMotor, "TRX1") @@ -20,13 +20,13 @@ class SlitH(PseudoPositioner): @pseudo_position_argument def forward(self, pseudo_pos): - '''Run a forward (pseudo -> real) calculation''' + """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''' + """Run an inverse (real -> pseudo) calculation""" return self.PseudoPosition(cenx=(real_pos.x1+real_pos.x2)/2, gapx=real_pos.x2-real_pos.x1) @@ -34,11 +34,11 @@ class SlitH(PseudoPositioner): 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! + 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. + NOTE: The real and virtual axes are wrapped together. """ # Motor interface y1 = Component(EpicsMotor, "TRY1") From 15950e6d05611d5f70e353132602e4980745f368 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Mon, 14 Nov 2022 18:04:36 +0100 Subject: [PATCH 08/19] Small chanege --- ophyd_devices/epics/proxies/SpmBase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ophyd_devices/epics/proxies/SpmBase.py b/ophyd_devices/epics/proxies/SpmBase.py index d96985e..dc01b29 100644 --- a/ophyd_devices/epics/proxies/SpmBase.py +++ b/ophyd_devices/epics/proxies/SpmBase.py @@ -10,7 +10,7 @@ class SpmBase(Device): used to monitor the beam height (only Y) for the bending magnet beamlines of SLS. - Note: EPICS provided signals are read only, but the user can + Note: EPICS provided signals are read only, but the users can change the beam position offset. """ # Motor interface From 5f251db89144b488f4b80c949a2c2e36ec0f83a8 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Tue, 15 Nov 2022 19:03:55 +0100 Subject: [PATCH 09/19] Flaking --- ophyd_devices/epics/DeviceFactory.py | 4 +- .../epics/proxies/DelayGeneratorDG645.py | 170 ++++++++++++------ .../epics/proxies/InsertionDevice.py | 2 + ophyd_devices/epics/proxies/SpmBase.py | 32 ++-- ophyd_devices/epics/proxies/XbpmBase.py | 31 ++-- ophyd_devices/epics/proxies/slits.py | 22 ++- 6 files changed, 169 insertions(+), 92 deletions(-) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index 88db585..cbd6215 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -36,7 +36,7 @@ lut_db.update(yaml.load(fp, Loader=yaml.Loader)) def createProxy(name: str, connect=True) -> OphydObject: """Factory routine to create an ophyd device with a pre-defined schema. - Does nothing if the device is already an OphydObject! + Does nothing if the device is already an OphydObject! """ if issubclass(type(name), OphydObject): return name @@ -51,7 +51,7 @@ def createProxy(name: str, connect=True) -> OphydObject: ret.wait_for_connection(timeout=5) return ret else: - raise RuntimeError(f"Unsupported return class: {entry["type"]}") + raise RuntimeError(f"Unsupported return class: {entry['type']}") if __name__ == "__main__": diff --git a/ophyd_devices/epics/proxies/DelayGeneratorDG645.py b/ophyd_devices/epics/proxies/DelayGeneratorDG645.py index b4cc615..6674217 100644 --- a/ophyd_devices/epics/proxies/DelayGeneratorDG645.py +++ b/ophyd_devices/epics/proxies/DelayGeneratorDG645.py @@ -7,21 +7,37 @@ Created on Tue Nov 9 16:12:47 2021 from ophyd import Device, Component, EpicsSignal, EpicsSignalRO, Kind from ophyd import PositionerBase -from ophyd.pseudopos import pseudo_position_argument, real_position_argument, PseudoSingle, PseudoPositioner +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. + 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) + 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): @@ -32,28 +48,29 @@ class DummyPositioner(Device, PositionerBase): 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. + 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') + 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') + 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'] + 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) + return self.RealPosition(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay + pseudo_pos.width) @real_position_argument def inverse(self, real_pos): @@ -64,52 +81,95 @@ class DelayPair(PseudoPositioner): class DelayGeneratorDG645(Device): """DG645 delay generator - This class implements a thin Ophyd wrapper around the Stanford Research DG645 - digital 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. + 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 + 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') + + 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") + 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) + 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) + 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) + 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""" @@ -126,16 +186,16 @@ class DelayGeneratorDG645(Device): 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'" + 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": + if config == "all": self.burstConfig.set(0).wait() - elif config=="first": + elif config == "first": self.burstConfig.set(1).wait() def busrtDisable(self): diff --git a/ophyd_devices/epics/proxies/InsertionDevice.py b/ophyd_devices/epics/proxies/InsertionDevice.py index dbba07e..4144bc5 100644 --- a/ophyd_devices/epics/proxies/InsertionDevice.py +++ b/ophyd_devices/epics/proxies/InsertionDevice.py @@ -10,6 +10,7 @@ class InsertionDevice(PVPositioner): 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) @@ -20,6 +21,7 @@ class InsertionDevice(PVPositioner): 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__": diff --git a/ophyd_devices/epics/proxies/SpmBase.py b/ophyd_devices/epics/proxies/SpmBase.py index dc01b29..502d3c1 100644 --- a/ophyd_devices/epics/proxies/SpmBase.py +++ b/ophyd_devices/epics/proxies/SpmBase.py @@ -6,13 +6,14 @@ 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. + 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. + 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) @@ -20,20 +21,21 @@ class SpmBase(Device): 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) + 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. + 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. + 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) @@ -46,16 +48,18 @@ class SpmSim(SpmBase): self._MX = 0 self._MY = 0 - self._I0 = 255.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) + 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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) + 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) diff --git a/ophyd_devices/epics/proxies/XbpmBase.py b/ophyd_devices/epics/proxies/XbpmBase.py index 344ea28..1cc540b 100644 --- a/ophyd_devices/epics/proxies/XbpmBase.py +++ b/ophyd_devices/epics/proxies/XbpmBase.py @@ -11,6 +11,7 @@ class XbpmCsaxsOp(Device): 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) @@ -32,6 +33,7 @@ class XbpmBase(Device): 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) @@ -42,9 +44,9 @@ class XbpmBase(Device): 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) + scaleH = Component(EpicsSignal, "PositionScaleX", auto_monitor=False) offsetH = Component(EpicsSignal, "PositionOffsetX", auto_monitor=False) - scaleV = Component(EpicsSignal, "PositionScaleY", auto_monitor=False) + scaleV = Component(EpicsSignal, "PositionScaleY", auto_monitor=False) offsetV = Component(EpicsSignal, "PositionOffsetY", auto_monitor=False) @@ -63,6 +65,7 @@ class XbpmSim(XbpmBase): 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) @@ -84,11 +87,13 @@ class XbpmSim(XbpmBase): """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. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) + 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) + # 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) @@ -98,11 +103,11 @@ class XbpmSim(XbpmBase): # 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 + 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() @@ -111,8 +116,8 @@ class XbpmSim(XbpmBase): 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.imshow(beam) + # plt.show(block=False) plt.pause(0.5) diff --git a/ophyd_devices/epics/proxies/slits.py b/ophyd_devices/epics/proxies/slits.py index 4c5f5db..61e29a3 100644 --- a/ophyd_devices/epics/proxies/slits.py +++ b/ophyd_devices/epics/proxies/slits.py @@ -11,6 +11,7 @@ class SlitH(PseudoPositioner): NOTE: The real and virtual axes are wrapped together. """ + # Motor interface x1 = Component(EpicsMotor, "TRX1") x2 = Component(EpicsMotor, "TRX2") @@ -21,14 +22,16 @@ class SlitH(PseudoPositioner): @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) + 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) + return self.PseudoPosition( + cenx=(real_pos.x1 + real_pos.x2) / 2, gapx=real_pos.x2 - real_pos.x1 + ) class SlitV(PseudoPositioner): @@ -40,6 +43,7 @@ class SlitV(PseudoPositioner): NOTE: The real and virtual axes are wrapped together. """ + # Motor interface y1 = Component(EpicsMotor, "TRY1") y2 = Component(EpicsMotor, "TRY2") @@ -50,11 +54,13 @@ class SlitV(PseudoPositioner): @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) + 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) + return self.PseudoPosition( + ceny=(real_pos.y1 + real_pos.y2) / 2, gapy=real_pos.y2 - real_pos.y1 + ) From cb1549cf2ddf574e84c4321f796d05a1bc95ef8c Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Tue, 15 Nov 2022 19:09:21 +0100 Subject: [PATCH 10/19] Flaking --- ophyd_devices/epics/proxies/mono_dccm.py | 71 ++++++++++++++---------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/ophyd_devices/epics/proxies/mono_dccm.py b/ophyd_devices/epics/proxies/mono_dccm.py index a53b9a5..11f7be7 100644 --- a/ophyd_devices/epics/proxies/mono_dccm.py +++ b/ophyd_devices/epics/proxies/mono_dccm.py @@ -9,8 +9,16 @@ 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 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 @@ -19,10 +27,10 @@ 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! + 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 + angle = angle * np.pi / 180 if deg else angle # Lattice constant along direction d0 = 5.43102 * (1.0 - lncorr) / np.linalg.norm(hkl) @@ -31,20 +39,18 @@ def a2e(angle, hkl=[1, 1, 1], lnc=False, bent=False, deg=False): def e2w(energy): - """Convert between energy and wavelength - """ + """Convert between energy and wavelength""" return 0.1 * 12398.42 / energy def w2e(wwl): - """Convert between wavelength and energy - """ + """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! + ATTENTION: 'angle' must be in radians, not degrees! """ lncorr = LN_CORR if lnc else 0.0 @@ -68,20 +74,21 @@ class MonoMotor(PseudoPositioner): 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'] + # 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) + 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)) + return self.PseudoPosition(energy=a2e(3.141592 * real_pos.angle / 180.0)) class MonoDccm(PseudoPositioner): @@ -94,13 +101,13 @@ class MonoDccm(PseudoPositioner): """ # Real axis (in degrees) - th1 = Component(EpicsMotor, "ROX1", name='theta1') - th2 = Component(EpicsMotor, "ROX2", name='theta2') + 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) + 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") @@ -110,23 +117,27 @@ class MonoDccm(PseudoPositioner): @pseudo_position_argument def forward(self, pseudo_pos): """WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning""" - if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: + if ( + abs(pseudo_pos.energy - self.energy.position) > 0.0001 + and abs(pseudo_pos.en1 - self.en1.position) < 0.0001 + and abs(pseudo_pos.en2 - self.en2.position) < 0.0001 + ): # Probably the common energy was changed return self.RealPosition( - th1=-180.0 * e2a(pseudo_pos.energy) / 3.141592, - th2=180.0 * e2a(pseudo_pos.energy) / 3.141592 - ) + 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 - ) + 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) - ) + energy=-a2e(3.141592 * real_pos.th1 / 180.0), + ) From 5725af830729e5e7d1b5f95284ae4dedaac47396 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Fri, 18 Nov 2022 13:58:39 +0100 Subject: [PATCH 11/19] Output formatting --- ophyd_devices/epics/DeviceFactory.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index cbd6215..bb49122 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -56,5 +56,7 @@ def createProxy(name: str, connect=True) -> OphydObject: if __name__ == "__main__": for key in lut_db: - print(key) + print("") dut = createProxy(str(key)) + print(f"{key}\t:\t{dut.read()}") + From 6c2ae5c1662154399f0659a79e8a105351668ba3 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Fri, 18 Nov 2022 16:19:28 +0100 Subject: [PATCH 12/19] Better config --- ophyd_devices/epics/DeviceFactory.py | 47 ++--- ophyd_devices/epics/__init__.py | 10 + ophyd_devices/epics/db/x12sa_database.yml | 175 +++++++++++++++++- .../DelayGeneratorDG645.py | 0 .../{proxies => devices}/InsertionDevice.py | 0 .../epics/{proxies => devices}/SpmBase.py | 0 .../epics/{proxies => devices}/XbpmBase.py | 0 .../epics/{proxies => devices}/__init__.py | 6 + .../epics/{proxies => devices}/mono_dccm.py | 0 .../epics/{proxies => devices}/slits.py | 0 10 files changed, 208 insertions(+), 30 deletions(-) create mode 100644 ophyd_devices/epics/__init__.py rename ophyd_devices/epics/{proxies => devices}/DelayGeneratorDG645.py (100%) rename ophyd_devices/epics/{proxies => devices}/InsertionDevice.py (100%) rename ophyd_devices/epics/{proxies => devices}/SpmBase.py (100%) rename ophyd_devices/epics/{proxies => devices}/XbpmBase.py (100%) rename ophyd_devices/epics/{proxies => devices}/__init__.py (53%) rename ophyd_devices/epics/{proxies => devices}/mono_dccm.py (100%) rename ophyd_devices/epics/{proxies => devices}/slits.py (100%) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index bb49122..87c999c 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -8,42 +8,41 @@ Created on Wed Oct 13 17:06:51 2021 import os import yaml from ophyd.ophydobj import OphydObject -from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor -from ophyd.sim import SynAxis, SynSignal, SynPeriodicSignal -from ophyd.quadem import QuadEM -import pathlib +from devices import * +# #################################################### +# Test connection to beamline devices +# #################################################### +# Current file path path = os.path.dirname(os.path.abspath(__file__)) - -from proxies import * - - -# Load SLS common database +# Load simulated device database fp = open(f"{path}/db/test_database.yml", "r") lut_db = yaml.load(fp, Loader=yaml.Loader) -# Load SLS common database (already in DB) -# fp = open(f"{path}/db/machine_database.yml", "r") -# lut_db = yaml.load(fp, Loader=yaml.Loader) - # Load beamline specific database -bl = os.getenv("BEAMLINE_XNAME", "X12SA") -fp = open(f"{path}/db/{bl.lower()}_database.yml", "r") -lut_db.update(yaml.load(fp, Loader=yaml.Loader)) +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: - """Factory routine to create an ophyd device with a pre-defined schema. + """ Create an ophyd device from its alias + + Factory routine that uses the beamline database to instantiate + instantiate ophyd devices from their alias and pre-defined + configuration parameters. 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"Device candidate: {cls_candidate}") + #print(f"Class candidate: {cls_candidate}") if issubclass(cls_candidate, OphydObject): ret = cls_candidate(**entry["config"]) @@ -56,7 +55,13 @@ def createProxy(name: str, connect=True) -> OphydObject: if __name__ == "__main__": for key in lut_db: - print("") - dut = createProxy(str(key)) - print(f"{key}\t:\t{dut.read()}") + try: + dut = createProxy(str(key)) + #print(f"{key}\t: {type(dut)}\t{dut.read()}") + print(f"{key}\t: {type(dut)}") + except Exception as ex: + print(ex) + + + 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/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index e6d9cfc..6c351d1 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -565,34 +565,191 @@ led: 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 before mono (VME)' + desc: 'XBPM 3: White beam AH501 before mono' acquisition: {schedule: sync} - config: {name: bpm3, prefix: 'X12SA-OP-BPM1:'} + config: {name: bpm3, prefix: 'X12SA-OP-BPM3:'} deviceGroup: monitor status: {enabled: true} - type: XbpmCsaxsOp -bpm4: - desc: 'XBPM 4: Somewhere around mono (VME)' + type: QuadEM +bpm3a: + desc: 'XBPM 3: White beam AH501 before mono' acquisition: {schedule: sync} - config: {name: bpm4, prefix: 'X12SA-OP-BPM2:'} + config: {name: bpm3a, read_pv: 'X12SA-OP-BPM3:Current1:MeanValue_RBV'} deviceGroup: monitor status: {enabled: true} - type: XbpmCsaxsOp + 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: Not commissioned' + 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: Not commissioned' + 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} diff --git a/ophyd_devices/epics/proxies/DelayGeneratorDG645.py b/ophyd_devices/epics/devices/DelayGeneratorDG645.py similarity index 100% rename from ophyd_devices/epics/proxies/DelayGeneratorDG645.py rename to ophyd_devices/epics/devices/DelayGeneratorDG645.py diff --git a/ophyd_devices/epics/proxies/InsertionDevice.py b/ophyd_devices/epics/devices/InsertionDevice.py similarity index 100% rename from ophyd_devices/epics/proxies/InsertionDevice.py rename to ophyd_devices/epics/devices/InsertionDevice.py diff --git a/ophyd_devices/epics/proxies/SpmBase.py b/ophyd_devices/epics/devices/SpmBase.py similarity index 100% rename from ophyd_devices/epics/proxies/SpmBase.py rename to ophyd_devices/epics/devices/SpmBase.py diff --git a/ophyd_devices/epics/proxies/XbpmBase.py b/ophyd_devices/epics/devices/XbpmBase.py similarity index 100% rename from ophyd_devices/epics/proxies/XbpmBase.py rename to ophyd_devices/epics/devices/XbpmBase.py diff --git a/ophyd_devices/epics/proxies/__init__.py b/ophyd_devices/epics/devices/__init__.py similarity index 53% rename from ophyd_devices/epics/proxies/__init__.py rename to ophyd_devices/epics/devices/__init__.py index 6539e1e..cc273aa 100644 --- a/ophyd_devices/epics/proxies/__init__.py +++ b/ophyd_devices/epics/devices/__init__.py @@ -3,3 +3,9 @@ from .slits import SlitH, SlitV from .XbpmBase import XbpmBase, XbpmCsaxsOp from .SpmBase import SpmBase from .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/proxies/mono_dccm.py b/ophyd_devices/epics/devices/mono_dccm.py similarity index 100% rename from ophyd_devices/epics/proxies/mono_dccm.py rename to ophyd_devices/epics/devices/mono_dccm.py diff --git a/ophyd_devices/epics/proxies/slits.py b/ophyd_devices/epics/devices/slits.py similarity index 100% rename from ophyd_devices/epics/proxies/slits.py rename to ophyd_devices/epics/devices/slits.py From 7bb9c11249fbdd4bfc70ef03fc123b7680e40910 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Fri, 18 Nov 2022 16:24:23 +0100 Subject: [PATCH 13/19] Flaking --- ophyd_devices/epics/DeviceFactory.py | 15 +++++---------- ophyd_devices/epics/devices/__init__.py | 1 - 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index 87c999c..70676e6 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -23,17 +23,16 @@ lut_db = yaml.load(fp, Loader=yaml.Loader) # Load beamline specific database bl = os.getenv("BEAMLINE_XNAME", "TESTENV") -if bl!="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 + """Create an ophyd device from its alias Factory routine that uses the beamline database to instantiate - instantiate ophyd devices from their alias and pre-defined - configuration parameters. + ophyd devices from their alias and pre-defined configuration. Does nothing if the device is already an OphydObject! """ if issubclass(type(name), OphydObject): @@ -42,7 +41,7 @@ def createProxy(name: str, connect=True) -> OphydObject: entry = lut_db[name] # Yeah, using global namespace cls_candidate = globals()[entry["type"]] - #print(f"Class candidate: {cls_candidate}") + # print(f"Class candidate: {cls_candidate}") if issubclass(cls_candidate, OphydObject): ret = cls_candidate(**entry["config"]) @@ -57,11 +56,7 @@ if __name__ == "__main__": 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)}\t{dut.read()}") print(f"{key}\t: {type(dut)}") except Exception as ex: print(ex) - - - - diff --git a/ophyd_devices/epics/devices/__init__.py b/ophyd_devices/epics/devices/__init__.py index cc273aa..5ea635e 100644 --- a/ophyd_devices/epics/devices/__init__.py +++ b/ophyd_devices/epics/devices/__init__.py @@ -8,4 +8,3 @@ from .InsertionDevice import InsertionDevice from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor from ophyd.sim import SynAxis, SynSignal, SynPeriodicSignal from ophyd.quadem import QuadEM - From c483b50043b2ab69d7a9d0061ffbe874c6edefdb Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Tue, 22 Nov 2022 13:29:10 +0100 Subject: [PATCH 14/19] Adding virtual otors for mono bender and detector --- ophyd_devices/epics/db/x12sa_database.yml | 67 ++++++++++++---- ophyd_devices/epics/devices/__init__.py | 2 + ophyd_devices/epics/devices/specMotors.py | 93 +++++++++++++++++++++++ 3 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 ophyd_devices/epics/devices/specMotors.py diff --git a/ophyd_devices/epics/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index 6c351d1..874dd47 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -159,13 +159,27 @@ motrz1: deviceGroup: beamlineMotor status: {enabled: true} type: EpicsMotor -mopush1: - desc: 'Monochromator crystal 1 angle' +motrz1e: + desc: 'Monochromator crystal 1 axial movement encoder' acquisition: {schedule: sync} - config: {name: mopush1, prefix: 'X12SA-OP-MO:ROX1'} - deviceGroup: beamlineMotor + config: {name: motrz1e, prefix: 'X12SA-OP-MO:ECZ1'} + deviceGroup: monitor status: {enabled: true} - type: EpicsMotor + type: EpicsSignalRO +#mopush1: +# desc: 'Monochromator crystal 1 angle' +# acquisition: {schedule: sync} +# config: {name: mopush1, prefix: 'X12SA-OP-MO:ROX1'} +# deviceGroup: beamlineMotor +# status: {enabled: true} +# type: EpicsMotor +moth1e: + desc: 'Monochromator crystal 1 theta encoder' + acquisition: {schedule: sync} + config: {name: moth1e, prefix: 'X12SA-OP-MO:ECX1'} + deviceGroup: monitor + status: {enabled: true} + type: EpicsSignalRO moroll1: desc: 'Monochromator crystal 1 roll' acquisition: {schedule: sync} @@ -187,13 +201,20 @@ motry2: deviceGroup: beamlineMotor status: {enabled: true} type: EpicsMotor -mopush2: - desc: 'Monochromator crystal 2 angle' +#mopush2: +# desc: 'Monochromator crystal 2 angle' +# acquisition: {schedule: sync} +# config: {name: mopush2, prefix: 'X12SA-OP-MO:ROX2'} +# deviceGroup: beamlineMotor +# status: {enabled: true} +# type: EpicsMotor +moth2e: + desc: 'Monochromator crystal 2 theta encoder' acquisition: {schedule: sync} - config: {name: mopush2, prefix: 'X12SA-OP-MO:ROX2'} - deviceGroup: beamlineMotor + config: {name: moth2e, prefix: 'X12SA-OP-MO:ECX2'} + deviceGroup: monitor status: {enabled: true} - type: EpicsMotor + type: EpicsSignalRO #monot: # desc: 'Monochromator temperature' # acquisition: {schedule: sync} @@ -216,33 +237,41 @@ moroll2: status: {enabled: true} type: EpicsMotor mobdai: - desc: 'Monochromator ??? inner motor' + 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 ??? outer motor' + 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 ??? outer motor' + 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 ??? inner motor' + 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} @@ -475,12 +504,19 @@ dttrz: status: {enabled: true} type: EpicsMotor dtpush: - desc: 'Detector tower motion' + 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} @@ -841,3 +877,4 @@ FBPMUY: deviceGroup: monitor status: {enabled: true} type: EpicsSignalRO + diff --git a/ophyd_devices/epics/devices/__init__.py b/ophyd_devices/epics/devices/__init__.py index 5ea635e..b3a5a5a 100644 --- a/ophyd_devices/epics/devices/__init__.py +++ b/ophyd_devices/epics/devices/__init__.py @@ -3,6 +3,8 @@ from .slits import SlitH, SlitV from .XbpmBase import XbpmBase, XbpmCsaxsOp from .SpmBase import SpmBase from .InsertionDevice import InsertionDevice +from .specMotors import PmMonoBender, PmDetectorRotation + # Standard ophyd classes from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor diff --git a/ophyd_devices/epics/devices/specMotors.py b/ophyd_devices/epics/devices/specMotors.py new file mode 100644 index 0000000..f5d4e71 --- /dev/null +++ b/ophyd_devices/epics/devices/specMotors.py @@ -0,0 +1,93 @@ +# -*- 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 + + +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))) From fd7208ed3e7742b8617a88b6eb4470911019cfa8 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Tue, 22 Nov 2022 13:44:28 +0100 Subject: [PATCH 15/19] All devices instantiate --- ophyd_devices/epics/DeviceFactory.py | 4 +++ ophyd_devices/epics/db/x12sa_database.yml | 6 ++-- .../epics/devices/cSaxsVirtualMotors.py | 28 +++++++++++++++++++ ophyd_devices/epics/devices/specMotors.py | 4 +-- 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 ophyd_devices/epics/devices/cSaxsVirtualMotors.py diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index 70676e6..7eafe8a 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -53,10 +53,14 @@ def createProxy(name: str, connect=True) -> OphydObject: 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/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index 874dd47..8bdae37 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -162,7 +162,7 @@ motrz1: motrz1e: desc: 'Monochromator crystal 1 axial movement encoder' acquisition: {schedule: sync} - config: {name: motrz1e, prefix: 'X12SA-OP-MO:ECZ1'} + config: {name: motrz1e, read_pv: 'X12SA-OP-MO:ECZ1'} deviceGroup: monitor status: {enabled: true} type: EpicsSignalRO @@ -176,7 +176,7 @@ motrz1e: moth1e: desc: 'Monochromator crystal 1 theta encoder' acquisition: {schedule: sync} - config: {name: moth1e, prefix: 'X12SA-OP-MO:ECX1'} + config: {name: moth1e, read_pv: 'X12SA-OP-MO:ECX1'} deviceGroup: monitor status: {enabled: true} type: EpicsSignalRO @@ -211,7 +211,7 @@ motry2: moth2e: desc: 'Monochromator crystal 2 theta encoder' acquisition: {schedule: sync} - config: {name: moth2e, prefix: 'X12SA-OP-MO:ECX2'} + config: {name: moth2e, read_pv: 'X12SA-OP-MO:ECX2'} deviceGroup: monitor status: {enabled: true} type: EpicsSignalRO diff --git a/ophyd_devices/epics/devices/cSaxsVirtualMotors.py b/ophyd_devices/epics/devices/cSaxsVirtualMotors.py new file mode 100644 index 0000000..b87825b --- /dev/null +++ b/ophyd_devices/epics/devices/cSaxsVirtualMotors.py @@ -0,0 +1,28 @@ + + +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/specMotors.py b/ophyd_devices/epics/devices/specMotors.py index f5d4e71..7afb824 100644 --- a/ophyd_devices/epics/devices/specMotors.py +++ b/ophyd_devices/epics/devices/specMotors.py @@ -8,7 +8,7 @@ IMPORTANT: Virtual monochromator axes should be implemented already in EPICS!!! """ import numpy as np -from math import isclose +from math import isclose, tan, atan from ophyd import ( EpicsSignal, EpicsSignalRO, @@ -20,7 +20,7 @@ from ophyd import ( Kind, ) from ophyd.pseudopos import pseudo_position_argument, real_position_argument -from ophyd.sim import SynAxis, Syn2DGauss + class PmMonoBender(PseudoPositioner): From bbf53a3e64a770c86d97172e48928c33c7c8a859 Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Tue, 22 Nov 2022 17:52:45 +0100 Subject: [PATCH 16/19] Mono motors in read only mode --- ophyd_devices/epics/DeviceFactory.py | 4 +- ophyd_devices/epics/db/x12sa_database.yml | 88 ++++++++++++++--- ophyd_devices/epics/devices/__init__.py | 2 +- ophyd_devices/epics/devices/specMotors.py | 111 +++++++++++++++++++++- 4 files changed, 186 insertions(+), 19 deletions(-) diff --git a/ophyd_devices/epics/DeviceFactory.py b/ophyd_devices/epics/DeviceFactory.py index 7eafe8a..05d37a8 100644 --- a/ophyd_devices/epics/DeviceFactory.py +++ b/ophyd_devices/epics/DeviceFactory.py @@ -57,8 +57,8 @@ if __name__ == "__main__": 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)}") + print(f"{key}\t: {type(dut)}\t{dut.read()}") + # print(f"{key}\t: {type(dut)}") except Exception as ex: num_errors += 1 print(key) diff --git a/ophyd_devices/epics/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index 8bdae37..987ba9c 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -166,13 +166,13 @@ motrz1e: deviceGroup: monitor status: {enabled: true} type: EpicsSignalRO -#mopush1: -# desc: 'Monochromator crystal 1 angle' -# acquisition: {schedule: sync} -# config: {name: mopush1, prefix: 'X12SA-OP-MO:ROX1'} -# 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 moth1e: desc: 'Monochromator crystal 1 theta encoder' acquisition: {schedule: sync} @@ -201,13 +201,13 @@ motry2: deviceGroup: beamlineMotor status: {enabled: true} type: EpicsMotor -#mopush2: -# desc: 'Monochromator crystal 2 angle' -# acquisition: {schedule: sync} -# config: {name: mopush2, prefix: 'X12SA-OP-MO:ROX2'} -# deviceGroup: beamlineMotor -# status: {enabled: true} -# type: EpicsMotor +mopush2: + desc: 'Monochromator crystal 2 angle' + acquisition: {schedule: sync} + config: {name: mopush2, read_pv: 'X12SA-OP-MO:ROX2.VAL'} + deviceGroup: monitor + status: {enabled: true} + type: EpicsSignalRO moth2e: desc: 'Monochromator crystal 2 theta encoder' acquisition: {schedule: sync} @@ -878,3 +878,63 @@ FBPMUY: 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 +mokev: + desc: 'Monochromator energy in keV' + acquisition: {schedule: sync} + config: {name: mokev, read_pv: 'X12SA-OP-MO:ROX2.VAL'} + deviceGroup: monitor + status: {enabled: true} + type: EnergyKev +moth1: + desc: 'Monochromator Theta 1' + acquisition: {schedule: sync} + config: {name: moth1, read_pv: 'X12SA-OP-MO:ECX1'} + deviceGroup: monitor + status: {enabled: true} + type: MonoTheta1 +moth2: + desc: 'Monochromator Theta 2' + acquisition: {schedule: sync} + config: {name: moth1, read_pv: 'X12SA-OP-MO:ECX2'} + deviceGroup: monitor + status: {enabled: true} + type: MonoTheta2 + + + + diff --git a/ophyd_devices/epics/devices/__init__.py b/ophyd_devices/epics/devices/__init__.py index b3a5a5a..d3c347b 100644 --- a/ophyd_devices/epics/devices/__init__.py +++ b/ophyd_devices/epics/devices/__init__.py @@ -3,7 +3,7 @@ from .slits import SlitH, SlitV from .XbpmBase import XbpmBase, XbpmCsaxsOp from .SpmBase import SpmBase from .InsertionDevice import InsertionDevice -from .specMotors import PmMonoBender, PmDetectorRotation +from .specMotors import PmMonoBender, PmDetectorRotation, GirderMotorX1, GirderMotorY1, GirderMotorROLL, GirderMotorYAW, GirderMotorPITCH, MonoTheta1, MonoTheta2, EnergyKev # Standard ophyd classes diff --git a/ophyd_devices/epics/devices/specMotors.py b/ophyd_devices/epics/devices/specMotors.py index 7afb824..b167dea 100644 --- a/ophyd_devices/epics/devices/specMotors.py +++ b/ophyd_devices/epics/devices/specMotors.py @@ -8,19 +8,20 @@ IMPORTANT: Virtual monochromator axes should be implemented already in EPICS!!! """ import numpy as np -from math import isclose, tan, atan +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): @@ -91,3 +92,109 @@ class PmDetectorRotation(PseudoPositioner): @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 VirtualSignalBase(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(VirtualSignalBase): + """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(VirtualSignalBase): + """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(VirtualSignalBase): + """Converts the pusher motor position to energy in keV + """ + _mono_hce = 12.39852066 + _mono_2d2 = 2*5.43102/sqrt(3) + def calc(self, val): + theta_deg = atan(50 / val) / 2.0 * 180.0 / 3.141592 + E_keV = -self._mono_hce / self._mono_2d2 / sin(theta_deg/180.0*3.14152) + return E_keV + + + + + + + + + + + + + + + + + From a0a0c1398db555319c16a951ac37ff66112b63dd Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Wed, 23 Nov 2022 11:14:33 +0100 Subject: [PATCH 17/19] Tested monochromator virtual axes --- .gitignore | 4 +- ophyd_devices/epics/db/x12sa_database.yml | 202 +++++++++++----------- ophyd_devices/epics/devices/specMotors.py | 27 +-- 3 files changed, 122 insertions(+), 111 deletions(-) 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/db/x12sa_database.yml b/ophyd_devices/epics/db/x12sa_database.yml index 987ba9c..600ecce 100644 --- a/ophyd_devices/epics/db/x12sa_database.yml +++ b/ophyd_devices/epics/db/x12sa_database.yml @@ -145,97 +145,7 @@ fi3try: deviceGroup: beamlineMotor status: {enabled: true} type: EpicsMotor -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 -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 -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 -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 -mopush2: - desc: 'Monochromator crystal 2 angle' - acquisition: {schedule: sync} - config: {name: mopush2, read_pv: 'X12SA-OP-MO:ROX2.VAL'} - deviceGroup: monitor - status: {enabled: true} - type: EpicsSignalRO -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 -#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 + mobdai: desc: 'Monochromator bender inner motor' acquisition: {schedule: sync} @@ -913,28 +823,122 @@ stroz: deviceGroup: beamlineMotor status: {enabled: true} type: GirderMotorROLL -mokev: - desc: 'Monochromator energy in keV' +motry: + desc: 'OpticsHutch optical table vertical movement' acquisition: {schedule: sync} - config: {name: mokev, read_pv: 'X12SA-OP-MO:ROX2.VAL'} + 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: EnergyKev + 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:ECX1'} + 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:ECX2'} + 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/specMotors.py b/ophyd_devices/epics/devices/specMotors.py index b167dea..34b8c7a 100644 --- a/ophyd_devices/epics/devices/specMotors.py +++ b/ophyd_devices/epics/devices/specMotors.py @@ -130,7 +130,7 @@ class GirderMotorPITCH(PVPositioner): done = Component(EpicsSignal, ":M-DMOV", name="dmov") -class VirtualSignalBase(EpicsSignalRO): +class VirtualEpicsSignalRO(EpicsSignalRO): """This is a test class to create derives signals from one or multiple original signals... """ @@ -141,13 +141,13 @@ class VirtualSignalBase(EpicsSignalRO): 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 + #def describe(self): + # val = self.get() + # d = super().describe() + # d[self.name]["dtype"] = data_type(val) + # return d -class MonoTheta1(VirtualSignalBase): +class MonoTheta1(VirtualEpicsSignalRO): """Converts the pusher motor position to theta angle """ _mono_a0_enc_scale1 = -1.0 @@ -159,7 +159,7 @@ class MonoTheta1(VirtualSignalBase): theta1 = self._mono_a0_enc_scale1 * asin( asin_arg ) / 3.141592 * 180.0 + self._mono_a3_enc_offs1 return theta1 -class MonoTheta2(VirtualSignalBase): +class MonoTheta2(VirtualEpicsSignalRO): """Converts the pusher motor position to theta angle """ _mono_a3_enc_offs2 = -19.7072 @@ -172,14 +172,19 @@ class MonoTheta2(VirtualSignalBase): theta2 = self._mono_a0_enc_scale2 * asin( asin_arg ) / 3.141592 * 180.0 + self._mono_a3_enc_offs2 return theta2 -class EnergyKev(VirtualSignalBase): +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): - theta_deg = atan(50 / val) / 2.0 * 180.0 / 3.141592 - E_keV = -self._mono_hce / self._mono_2d2 / sin(theta_deg/180.0*3.14152) + 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 From c9abd2a9da510247b454aa913f07157b05b5bf3d Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Wed, 23 Nov 2022 11:21:04 +0100 Subject: [PATCH 18/19] Blacked --- ophyd_devices/epics/devices/SpmBase.py | 10 ++-- ophyd_devices/epics/devices/XbpmBase.py | 10 ++-- ophyd_devices/epics/devices/__init__.py | 13 ++++- .../epics/devices/cSaxsVirtualMotors.py | 12 +++-- ophyd_devices/epics/devices/specMotors.py | 53 +++++++++++-------- 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/ophyd_devices/epics/devices/SpmBase.py b/ophyd_devices/epics/devices/SpmBase.py index 502d3c1..3c94f24 100644 --- a/ophyd_devices/epics/devices/SpmBase.py +++ b/ophyd_devices/epics/devices/SpmBase.py @@ -58,7 +58,7 @@ class SpmSim(SpmBase): # 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)) + -((x - mx) ** 2.0 / (2.0 * sx ** 2.0) + (y - my) ** 2.0 / (2.0 * sy ** 2.0)) ) # Generator for dynamic values @@ -74,10 +74,10 @@ class SpmSim(SpmBase): 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 + 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() diff --git a/ophyd_devices/epics/devices/XbpmBase.py b/ophyd_devices/epics/devices/XbpmBase.py index 1cc540b..1cc8ca3 100644 --- a/ophyd_devices/epics/devices/XbpmBase.py +++ b/ophyd_devices/epics/devices/XbpmBase.py @@ -88,7 +88,7 @@ class XbpmSim(XbpmBase): # 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)) + -((x - mx) ** 2.0 / (2.0 * sx ** 2.0) + (y - my) ** 2.0 / (2.0 * sy ** 2.0)) ) # Generator for dynamic values @@ -104,10 +104,10 @@ class XbpmSim(XbpmBase): 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 + 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() diff --git a/ophyd_devices/epics/devices/__init__.py b/ophyd_devices/epics/devices/__init__.py index d3c347b..f1a0cac 100644 --- a/ophyd_devices/epics/devices/__init__.py +++ b/ophyd_devices/epics/devices/__init__.py @@ -3,7 +3,18 @@ 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 +from .specMotors import ( + PmMonoBender, + PmDetectorRotation, + GirderMotorX1, + GirderMotorY1, + GirderMotorROLL, + GirderMotorYAW, + GirderMotorPITCH, + MonoTheta1, + MonoTheta2, + EnergyKev, +) # Standard ophyd classes diff --git a/ophyd_devices/epics/devices/cSaxsVirtualMotors.py b/ophyd_devices/epics/devices/cSaxsVirtualMotors.py index b87825b..94d3b96 100644 --- a/ophyd_devices/epics/devices/cSaxsVirtualMotors.py +++ b/ophyd_devices/epics/devices/cSaxsVirtualMotors.py @@ -1,5 +1,3 @@ - - TABLES_DT_PUSH_DIST_MM = 890 @@ -21,8 +19,12 @@ class DetectorTableTheta(PseudoPositioner): @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) + 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) + 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/specMotors.py b/ophyd_devices/epics/devices/specMotors.py index 34b8c7a..66e9987 100644 --- a/ophyd_devices/epics/devices/specMotors.py +++ b/ophyd_devices/epics/devices/specMotors.py @@ -97,34 +97,43 @@ class PmDetectorRotation(PseudoPositioner): 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") @@ -134,6 +143,7 @@ class VirtualEpicsSignalRO(EpicsSignalRO): """This is a test class to create derives signals from one or multiple original signals... """ + def calc(self, val): return val @@ -141,27 +151,34 @@ class VirtualEpicsSignalRO(EpicsSignalRO): raw = super().get(*args, **kwargs) return self.calc(raw) - #def describe(self): + # 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 + 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 @@ -169,37 +186,27 @@ class MonoTheta2(VirtualEpicsSignalRO): 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 + 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) + _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) + 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 - - - - - - - - - - - - - - - - - From 7adb38ddfc975e6b8b123fccc7a6a13de908844a Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Wed, 23 Nov 2022 12:09:05 +0100 Subject: [PATCH 19/19] Older blacking --- ophyd_devices/epics/devices/SpmBase.py | 10 ++++----- ophyd_devices/epics/devices/XbpmBase.py | 10 ++++----- ophyd_devices/epics/devices/specMotors.py | 26 ++++++++--------------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/ophyd_devices/epics/devices/SpmBase.py b/ophyd_devices/epics/devices/SpmBase.py index 3c94f24..502d3c1 100644 --- a/ophyd_devices/epics/devices/SpmBase.py +++ b/ophyd_devices/epics/devices/SpmBase.py @@ -58,7 +58,7 @@ class SpmSim(SpmBase): # 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)) + -((x - mx) ** 2.0 / (2.0 * sx**2.0) + (y - my) ** 2.0 / (2.0 * sy**2.0)) ) # Generator for dynamic values @@ -74,10 +74,10 @@ class SpmSim(SpmBase): 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 + 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() diff --git a/ophyd_devices/epics/devices/XbpmBase.py b/ophyd_devices/epics/devices/XbpmBase.py index 1cc8ca3..1cc540b 100644 --- a/ophyd_devices/epics/devices/XbpmBase.py +++ b/ophyd_devices/epics/devices/XbpmBase.py @@ -88,7 +88,7 @@ class XbpmSim(XbpmBase): # 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)) + -((x - mx) ** 2.0 / (2.0 * sx**2.0) + (y - my) ** 2.0 / (2.0 * sy**2.0)) ) # Generator for dynamic values @@ -104,10 +104,10 @@ class XbpmSim(XbpmBase): 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 + 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() diff --git a/ophyd_devices/epics/devices/specMotors.py b/ophyd_devices/epics/devices/specMotors.py index 66e9987..b2d54ef 100644 --- a/ophyd_devices/epics/devices/specMotors.py +++ b/ophyd_devices/epics/devices/specMotors.py @@ -95,8 +95,7 @@ class PmDetectorRotation(PseudoPositioner): class GirderMotorX1(PVPositioner): - """Girder X translation pseudo motor - """ + """Girder X translation pseudo motor""" setpoint = Component(EpicsSignal, ":X_SET", name="sp") readback = Component(EpicsSignalRO, ":X1", name="rbv") @@ -104,8 +103,7 @@ class GirderMotorX1(PVPositioner): class GirderMotorY1(PVPositioner): - """Girder Y translation pseudo motor - """ + """Girder Y translation pseudo motor""" setpoint = Component(EpicsSignal, ":Y_SET", name="sp") readback = Component(EpicsSignalRO, ":Y1", name="rbv") @@ -113,8 +111,7 @@ class GirderMotorY1(PVPositioner): class GirderMotorYAW(PVPositioner): - """Girder YAW pseudo motor - """ + """Girder YAW pseudo motor""" setpoint = Component(EpicsSignal, ":YAW_SET", name="sp") readback = Component(EpicsSignalRO, ":YAW1", name="rbv") @@ -122,8 +119,7 @@ class GirderMotorYAW(PVPositioner): class GirderMotorROLL(PVPositioner): - """Girder ROLL pseudo motor - """ + """Girder ROLL pseudo motor""" setpoint = Component(EpicsSignal, ":ROLL_SET", name="sp") readback = Component(EpicsSignalRO, ":ROLL1", name="rbv") @@ -131,8 +127,7 @@ class GirderMotorROLL(PVPositioner): class GirderMotorPITCH(PVPositioner): - """Girder YAW pseudo motor - """ + """Girder YAW pseudo motor""" setpoint = Component(EpicsSignal, ":PITCH_SET", name="sp") readback = Component(EpicsSignalRO, ":PITCH1", name="rbv") @@ -140,7 +135,7 @@ class GirderMotorPITCH(PVPositioner): class VirtualEpicsSignalRO(EpicsSignalRO): - """This is a test class to create derives signals from one or + """This is a test class to create derives signals from one or multiple original signals... """ @@ -159,8 +154,7 @@ class VirtualEpicsSignalRO(EpicsSignalRO): class MonoTheta1(VirtualEpicsSignalRO): - """Converts the pusher motor position to theta angle - """ + """Converts the pusher motor position to theta angle""" _mono_a0_enc_scale1 = -1.0 _mono_a1_lever_length1 = 206.706 @@ -176,8 +170,7 @@ class MonoTheta1(VirtualEpicsSignalRO): class MonoTheta2(VirtualEpicsSignalRO): - """Converts the pusher motor position to theta angle - """ + """Converts the pusher motor position to theta angle""" _mono_a3_enc_offs2 = -19.7072 _mono_a2_pusher_offs2 = 5.93905 @@ -193,8 +186,7 @@ class MonoTheta2(VirtualEpicsSignalRO): class EnergyKev(VirtualEpicsSignalRO): - """Converts the pusher motor position to energy in keV - """ + """Converts the pusher motor position to energy in keV""" _mono_a3_enc_offs2 = -19.7072 _mono_a2_pusher_offs2 = 5.93905