f
CI for xtreme_bec / test (push) Successful in 35s

This commit is contained in:
x07ma
2026-07-06 13:43:03 +02:00
parent cb4fd4e529
commit 4f2a5126e7
6 changed files with 105 additions and 68 deletions
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+55 -49
View File
@@ -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)
+6 -1
View File
@@ -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)
-1
View File
@@ -25,7 +25,6 @@ logger = bec_logger.logger
__all__ = [
"X07MAUndulator",
"PGMMonochromator",
"PGMOtFScan",
"VacuumValve",
"X07MAExitSlit",
+40 -13
View File
@@ -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 ###########