@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,7 +25,6 @@ logger = bec_logger.logger
|
||||
|
||||
__all__ = [
|
||||
"X07MAUndulator",
|
||||
"PGMMonochromator",
|
||||
"PGMOtFScan",
|
||||
"VacuumValve",
|
||||
"X07MAExitSlit",
|
||||
|
||||
@@ -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 ###########
|
||||
|
||||
Reference in New Issue
Block a user