From 4f2a5126e7aed4162e187f417ffac75735573fa9 Mon Sep 17 00:00:00 2001 From: x07ma Date: Mon, 6 Jul 2026 13:43:03 +0200 Subject: [PATCH] f --- xtreme_bec/device_configs/endstation.yaml | 4 +- xtreme_bec/device_configs/optics.yaml | 4 +- xtreme_bec/devices/adc_signals.py | 104 ++++++++++++---------- xtreme_bec/devices/pgm.py | 7 +- xtreme_bec/devices/x07ma_devices.py | 1 - xtreme_bec/scans/otf_scan.py | 53 ++++++++--- 6 files changed, 105 insertions(+), 68 deletions(-) diff --git a/xtreme_bec/device_configs/endstation.yaml b/xtreme_bec/device_configs/endstation.yaml index f1f03d0..d516346 100644 --- a/xtreme_bec/device_configs/endstation.yaml +++ b/xtreme_bec/device_configs/endstation.yaml @@ -122,9 +122,9 @@ keithley_3: signals: readoutPriority: monitored description: 'ADC signals' - deviceClass: xtreme_bec.devices.X07MAAnalogSignals + deviceClass: xtreme_bec.devices.adc_signals.ADCSignals deviceConfig: - prefix: 'X07MA-ES1-AI:' + prefix: 'X07MA-' onFailure: retry enabled: true readOnly: false diff --git a/xtreme_bec/device_configs/optics.yaml b/xtreme_bec/device_configs/optics.yaml index 1ecee3d..03dbe57 100644 --- a/xtreme_bec/device_configs/optics.yaml +++ b/xtreme_bec/device_configs/optics.yaml @@ -79,7 +79,7 @@ mono: mirror_theta: readoutPriority: baseline description: "Mirror theta" - deviceClass: ophyd_devices.EpicsMotorVME + deviceClass: ophyd_devices.EpicsUserMotorVME deviceConfig: prefix: "X07MA:m1" onFailure: retry @@ -92,7 +92,7 @@ mirror_theta: grating_beta: readoutPriority: baseline description: "Grating beta" - deviceClass: ophyd_devices.EpicsMotorVME + deviceClass: ophyd_devices.EpicsUserMotorVME deviceConfig: prefix: "X07MA:m2" onFailure: retry diff --git a/xtreme_bec/devices/adc_signals.py b/xtreme_bec/devices/adc_signals.py index 3860ec9..606069a 100644 --- a/xtreme_bec/devices/adc_signals.py +++ b/xtreme_bec/devices/adc_signals.py @@ -1,6 +1,7 @@ from __future__ import annotations import threading +import time from ophyd import Component as Cpt from ophyd import Device, EpicsSignalRO, Kind, Signal @@ -57,16 +58,16 @@ class ADCSignals(PSIDeviceBase, Device): ADC inputs """ - s1 = Cpt(EpicsSignalRO, "SIGNAL0", kind=Kind.hinted, auto_monitor=True) - s2 = Cpt(EpicsSignalRO, "SIGNAL1", kind=Kind.hinted, auto_monitor=True) - s3 = Cpt(EpicsSignalRO, "SIGNAL2", kind=Kind.hinted, auto_monitor=True) - s4 = Cpt(EpicsSignalRO, "SIGNAL3", kind=Kind.hinted, auto_monitor=True) - s5 = Cpt(EpicsSignalRO, "SIGNAL4", kind=Kind.hinted, auto_monitor=True) - s6 = Cpt(EpicsSignalRO, "SIGNAL5", kind=Kind.hinted, auto_monitor=True) - s7 = Cpt(EpicsSignalRO, "SIGNAL6", kind=Kind.hinted, auto_monitor=True) + s1 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL0", kind=Kind.hinted, auto_monitor=True) + s2 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL1", kind=Kind.hinted, auto_monitor=True) + s3 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL2", kind=Kind.hinted, auto_monitor=True) + s4 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL3", kind=Kind.hinted, auto_monitor=True) + s5 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL4", kind=Kind.hinted, auto_monitor=True) + s6 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL5", kind=Kind.hinted, auto_monitor=True) + s7 = Cpt(EpicsSignalRO, "ES1-AI:SIGNAL6", kind=Kind.hinted, auto_monitor=True) norm_tey = Cpt(NormTEYSignals, name="norm_tey", kind=Kind.hinted) norm_diode = Cpt(NormDIODESignals, name="norm_diode", kind=Kind.hinted) - energy_cerbk = Cpt(EpicsSignalRO, "X07MA-PGM:CERBK", kind=Kind.omitted, auto_monitor=True) + energy_cerbk = Cpt(EpicsSignalRO, "PGM:CERBK", kind=Kind.omitted, auto_monitor=True) store_updates = Cpt(Signal, value=0, kind=Kind.config) update_window = Cpt(Signal, value=0.1, kind=Kind.config) @@ -88,14 +89,14 @@ class ADCSignals(PSIDeviceBase, Device): ndim=1, async_update={"type": "add", "max_shape": [None]}, max_size=1000, - docs="Aggregated data signals from the X07MA ADC inputs.", + doc="Aggregated data signals from the X07MA ADC inputs.", ) def on_init(self) -> None: """ Called when the device is initialized. """ - self._buffer = [] + self._buffer: list[dict] = [] self._buffer_lock = threading.RLock() self._flush_timer = None @@ -143,31 +144,44 @@ class ADCSignals(PSIDeviceBase, Device): value: The new value of the signal that triggered this callback. **kwargs: Additional keyword arguments passed by the signal subscription. """ - if not self.store_updates.get(): - return + try: + if not self.store_updates.get(): + return + timestamp = time.time + data: dict[str, dict[str, float]] = { + # type: ignore + "s1": {"value": self.s1.get(), "timestamp": timestamp}, + "s2": {"value": self.s2.get(), "timestamp": timestamp}, + "s3": {"value": self.s3.get(), "timestamp": timestamp}, + "s4": {"value": self.s4.get(), "timestamp": timestamp}, + "s5": {"value": self.s5.get(), "timestamp": timestamp}, + "s6": {"value": self.s6.get(), "timestamp": timestamp}, + "s7": {"value": self.s7.get(), "timestamp": timestamp}, + "energy_cerbk": {"value": self.energy_cerbk.get(), "timestamp": timestamp}, + } + data["norm_tey"] = { + "value": ( + data["s1"]["value"] / data["s2"]["value"] if data["s2"]["value"] != 0 else 0 + ), + "timestamp": timestamp, + } + data["norm_diode"] = { + "value": ( + data["s3"]["value"] / data["s2"]["value"] if data["s2"]["value"] != 0 else 0 + ), + "timestamp": timestamp, + } - data: dict[str, float] = { - # type: ignore - "s1": self.s1.get(), - "s2": self.s2.get(), - "s3": self.s3.get(), - "s4": self.s4.get(), - "s5": self.s5.get(), - "s6": self.s6.get(), - "s7": self.s7.get(), - "energy_cerbk": self.energy_cerbk.get(), - } - data["norm_tey"] = data["s1"] / data["s2"] if data["s2"] != 0 else 0 - data["norm_diode"] = data["s3"] / data["s2"] if data["s2"] != 0 else 0 - - with self._buffer_lock: - self._buffer.append(data) - if self._flush_timer is None: - self._flush_timer = threading.Timer( - float(self.update_window.get()), self._flush_buffer - ) - self._flush_timer.daemon = True - self._flush_timer.start() + with self._buffer_lock: + self._buffer.append(data) + if self._flush_timer is None: + self._flush_timer = threading.Timer( + float(self.update_window.get()), self._flush_buffer + ) + self._flush_timer.daemon = True + self._flush_timer.start() + except Exception as exc: + print(exc) def _flush_buffer(self) -> None: """ @@ -183,19 +197,11 @@ class ADCSignals(PSIDeviceBase, Device): if not buffered: return - batched = { - key: [entry[key] for entry in buffered] - for key in ( - "s1", - "s2", - "s3", - "s4", - "s5", - "s6", - "s7", - "norm_tey", - "norm_diode", - "energy_cerbk", - ) - } + batched = {} + for key in buffered[0].keys(): + batched[key] = { + "value": [d[key]["value"] for d in buffered], + "timestamp": [d[key]["timestamp"] for d in buffered], + } + self.data.put(batched) diff --git a/xtreme_bec/devices/pgm.py b/xtreme_bec/devices/pgm.py index f9cae3b..85a1610 100644 --- a/xtreme_bec/devices/pgm.py +++ b/xtreme_bec/devices/pgm.py @@ -2,6 +2,7 @@ from __future__ import annotations import time +import numpy as np from bec_lib import bec_logger from ophyd import Component as Cpt from ophyd import EpicsSignal, EpicsSignalRO, Kind, PVPositioner, Signal @@ -116,6 +117,8 @@ class PGMMonochromator(PSIDeviceBase, PGMMonochromatorBase): Called while staging the device. Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object. """ + self._progress_done_emitted = False + self.energy_range = [] def on_unstage(self) -> DeviceStatus | StatusBase | None: """Called while unstaging the device.""" @@ -134,6 +137,7 @@ class PGMMonochromator(PSIDeviceBase, PGMMonochromatorBase): def on_stop(self) -> None: """Called when the device is stopped.""" + self.energy_range = [] def on_destroy(self) -> None: """Called when the device is destroyed. Cleanup resources here.""" @@ -228,7 +232,8 @@ class PGMMonochromator(PSIDeviceBase, PGMMonochromatorBase): if e1 == e2: progress = 100.0 else: - progress = (float(value) - e1) / (e2 - e1) * 100.0 + progress = np.round((float(value) - e1) / (e2 - e1) * 100.0) + print(progress) progress = max(0.0, min(100.0, progress)) self.progress.put(value=progress, max_value=100, done=False) diff --git a/xtreme_bec/devices/x07ma_devices.py b/xtreme_bec/devices/x07ma_devices.py index c488b3a..a5c94c2 100644 --- a/xtreme_bec/devices/x07ma_devices.py +++ b/xtreme_bec/devices/x07ma_devices.py @@ -25,7 +25,6 @@ logger = bec_logger.logger __all__ = [ "X07MAUndulator", - "PGMMonochromator", "PGMOtFScan", "VacuumValve", "X07MAExitSlit", diff --git a/xtreme_bec/scans/otf_scan.py b/xtreme_bec/scans/otf_scan.py index 5e91099..3be1de7 100644 --- a/xtreme_bec/scans/otf_scan.py +++ b/xtreme_bec/scans/otf_scan.py @@ -71,6 +71,7 @@ class OTFScan(ScanBase): self.time = time self.mono = self.dev["mono"] self.max_speed = 0.038 + self.default_velocity = 0.08 self.update_scan_info() @@ -81,19 +82,7 @@ class OTFScan(ScanBase): before the scan is opened, such as preparing the positions (if not done already) or setting up the devices. """ - theta_speed, beta_speed = self.dev.mono.get_max_speeds( - e1=self.e1, e2=self.e2, travel_time=self.time * 60 - ) - self.dev.mono.set_progress_range(e1=self.e1, e2=self.e2) - self.actions.set( - device=[ - self.dev.mirror_theta.base_velocity, - self.dev.grating_beta.base_velocity, - self.dev.mirror_theta.velocity, - self.dev.grating_beta.velocity, - ], - value=[theta_speed / 10, beta_speed / 10, theta_speed, beta_speed], - ) + # We set the readout priority of the adc signals to async so that the monitored readout # does not trigger a readout of the adc signals. The adc signals are going through the # async readout path. @@ -135,8 +124,32 @@ class OTFScan(ScanBase): """ self.actions.pre_scan_all_devices() + self.actions.set( + device=[ + self.dev.mirror_theta.base_velocity, + self.dev.grating_beta.base_velocity, + self.dev.mirror_theta.velocity, + self.dev.grating_beta.velocity, + ], + value=[ + self.default_velocity / 10, + self.default_velocity / 10, + self.default_velocity, + self.default_velocity, + ], + ) + # Move to the first energy self.mono.set(self.e1).wait() + + theta_speed, beta_speed = self.dev.mono.get_max_speeds( + e1=self.e1, e2=self.e2, travel_time=self.time * 60 + ) + self.dev.mono.set_progress_range(e1=self.e1, e2=self.e2) + self.dev.mirror_theta.base_velocity.put(theta_speed / 10) + self.dev.grating_beta.base_velocity.put(beta_speed / 10) + self.dev.mirror_theta.velocity.put(theta_speed) + self.dev.grating_beta.velocity.put(beta_speed) self.mono.sync_with_undulator.put(1) @scan_hook @@ -190,6 +203,20 @@ class OTFScan(ScanBase): """ self.dev.signals.store_updates.put(0) self.mono.sync_with_undulator.put(0) + self.actions.set( + device=[ + self.dev.mirror_theta.base_velocity, + self.dev.grating_beta.base_velocity, + self.dev.mirror_theta.velocity, + self.dev.grating_beta.velocity, + ], + value=[ + self.default_velocity / 10, + self.default_velocity / 10, + self.default_velocity, + self.default_velocity, + ], + ) ####################################################### ######### Helper methods for the scan logic ###########