refacto: remove old configs

This commit is contained in:
2025-07-10 18:24:57 +02:00
parent 1c539a1e9c
commit 173893ec33
7 changed files with 327 additions and 138 deletions

View File

@@ -53,89 +53,89 @@ eiger9m:
enabled: true
readoutPriority: async
softwareTrigger: false
ddg_detectors:
description: DelayGenerator for detector triggering
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
deviceConfig:
prefix: 'delaygen:DG1:'
ddg_config:
delay_burst: 40.e-3
delta_width: 0
additional_triggers: 0
polarity:
- 1 # T0 -> DDG MCS
- 0 # eiger
- 1 # falcon
- 1
- 1
amplitude: 4.5
offset: 0
thres_trig_level: 2.5
set_high_on_exposure: False
set_high_on_stage: False
deviceTags:
- cSAXS
- ddg_detectors
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: false
ddg_mcs:
description: DelayGenerator for mcs triggering
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
deviceConfig:
prefix: 'delaygen:DG2:'
ddg_config:
delay_burst: 0
delta_width: 0
additional_triggers: 1
polarity:
- 1
- 0
- 1
- 1
- 1
amplitude: 4.5
offset: 0
thres_trig_level: 2.5
set_high_on_exposure: False
set_high_on_stage: False
set_trigger_source: EXT_RISING_EDGE
trigger_width: 3.e-3
deviceTags:
- cSAXS
- ddg_mcs
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: false
ddg_fsh:
description: DelayGenerator for fast shutter control
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
deviceConfig:
prefix: 'delaygen:DG3:'
ddg_config:
delay_burst: 0
delta_width: 80.e-3
additional_triggers: 0
polarity:
- 1
- 1
- 1
- 1
- 1
amplitude: 4.5
offset: 0
thres_trig_level: 2.5
set_high_on_exposure: True
set_high_on_stage: False
deviceTags:
- cSAXS
- ddg_fsh
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: false
# ddg_detectors:
# description: DelayGenerator for detector triggering
# deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
# deviceConfig:
# prefix: 'delaygen:DG1:'
# ddg_config:
# delay_burst: 40.e-3
# delta_width: 0
# additional_triggers: 0
# polarity:
# - 1 # T0 -> DDG MCS
# - 0 # eiger
# - 1 # falcon
# - 1
# - 1
# amplitude: 4.5
# offset: 0
# thres_trig_level: 2.5
# set_high_on_exposure: False
# set_high_on_stage: False
# deviceTags:
# - cSAXS
# - ddg_detectors
# onFailure: buffer
# enabled: true
# readoutPriority: async
# softwareTrigger: false
# ddg_mcs:
# description: DelayGenerator for mcs triggering
# deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
# deviceConfig:
# prefix: 'delaygen:DG2:'
# ddg_config:
# delay_burst: 0
# delta_width: 0
# additional_triggers: 1
# polarity:
# - 1
# - 0
# - 1
# - 1
# - 1
# amplitude: 4.5
# offset: 0
# thres_trig_level: 2.5
# set_high_on_exposure: False
# set_high_on_stage: False
# set_trigger_source: EXT_RISING_EDGE
# trigger_width: 3.e-3
# deviceTags:
# - cSAXS
# - ddg_mcs
# onFailure: buffer
# enabled: true
# readoutPriority: async
# softwareTrigger: false
# ddg_fsh:
# description: DelayGenerator for fast shutter control
# deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
# deviceConfig:
# prefix: 'delaygen:DG3:'
# ddg_config:
# delay_burst: 0
# delta_width: 80.e-3
# additional_triggers: 0
# polarity:
# - 1
# - 1
# - 1
# - 1
# - 1
# amplitude: 4.5
# offset: 0
# thres_trig_level: 2.5
# set_high_on_exposure: True
# set_high_on_stage: False
# deviceTags:
# - cSAXS
# - ddg_fsh
# onFailure: buffer
# enabled: true
# readoutPriority: async
# softwareTrigger: false
falcon:
description: Falcon detector x-ray fluoresence
deviceClass: csaxs_bec.devices.epics.falcon_csaxs.FalconcSAXS

View File

@@ -0,0 +1,34 @@
ddg_master:
description: Main delay Generator for triggering
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDGMaster
enabled: true
deviceConfig:
prefix: 'X12SA-CPCL-DDG1:'
onFailure: raise
readOnly: false
readoutPriority: baseline
softwareTrigger: true
samx:
readoutPriority: baseline
deviceClass: ophyd_devices.SimPositioner
deviceConfig:
delay: 1
limits:
- -50
- 50
tolerance: 0.01
update_frequency: 400
deviceTags:
- user motors
enabled: true
readOnly: false
bpm4i:
readoutPriority: monitored
deviceClass: ophyd_devices.SimMonitor
deviceConfig:
deviceTags:
- beamline
enabled: true
readOnly: false

View File

@@ -1,30 +1,30 @@
ddg_detectors:
description: DelayGenerator for detector triggering
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
deviceConfig:
prefix: 'X12SA-CPCL-DDG3:'
ddg_config:
delay_burst: 40.e-3
delta_width: 0
additional_triggers: 0
polarity:
- 1 # T0 -> DDG MCS
- 0 # eiger
- 1 # falcon
- 1
- 1
amplitude: 4.5
offset: 0
thres_trig_level: 2.5
set_high_on_exposure: False
set_high_on_stage: False
deviceTags:
- cSAXS
- ddg_detectors
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: True
# ddg_detectors:
# description: DelayGenerator for detector triggering
# deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DelayGeneratorcSAXS
# deviceConfig:
# prefix: 'X12SA-CPCL-DDG3:'
# ddg_config:
# delay_burst: 40.e-3
# delta_width: 0
# additional_triggers: 0
# polarity:
# - 1 # T0 -> DDG MCS
# - 0 # eiger
# - 1 # falcon
# - 1
# - 1
# amplitude: 4.5
# offset: 0
# thres_trig_level: 2.5
# set_high_on_exposure: False
# set_high_on_stage: False
# deviceTags:
# - cSAXS
# - ddg_detectors
# onFailure: buffer
# enabled: true
# readoutPriority: async
# softwareTrigger: True
bpm4i:
readoutPriority: monitored
deviceClass: ophyd_devices.SimMonitor

View File

@@ -1 +1 @@
from .ddg_master import DDGMaster
from .ddg_1 import DDG1

View File

@@ -0,0 +1,145 @@
import atexit
import time
from threading import Event, Thread
from bec_lib.logger import bec_logger
from ophyd import DeviceStatus, StatusBase
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
CHANNELREFERENCE,
OUTPUTPOLARITY,
STATUSBITS,
TRIGGERSOURCE,
AllChannelNames,
ChannelConfig,
DelayGeneratorCSAXS,
StatusBitsCompareStatus,
)
from csaxs_bec.devices.epics.delay_generator_csaxs.error_registry import ERROR_CODES
logger = bec_logger.logger
_DEFAULT_CHANNEL_CONFIG: ChannelConfig = {
"amplitude": 5.0,
"offset": 0.0,
"polarity": OUTPUTPOLARITY.POSITIVE,
"mode": "ttl",
}
DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
"t0": _DEFAULT_CHANNEL_CONFIG,
"ab": _DEFAULT_CHANNEL_CONFIG,
"cd": _DEFAULT_CHANNEL_CONFIG,
"ef": _DEFAULT_CHANNEL_CONFIG,
"gh": _DEFAULT_CHANNEL_CONFIG,
}
DEFAULT_TRIGGER_SOURCE: TRIGGERSOURCE = TRIGGERSOURCE.SINGLE_SHOT
DEFAULT_DEAD_TIMES_S = {"ab": 1e-3, "cd": 1e-3, "ef": 1e-3, "gh": 1e-3}
class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
"""
Implementation of DelayGeneratorCSAXS for the CSAXS master trigger delay generator at X12SA-CPCL-DDG1
"""
# pylint: disable=attribute-defined-outside-init
def on_connected(self) -> None:
"""Set the default values on the device - intended to overwrite everything to a usable default state.
Sets DEFAULT_IO_CONFIG into each channel,
sets the trigger source to DEFAULT_TRIGGER_SOURCE,
and turns off burst mode"""
self.burst_disable() # it is possible to miss setting settings if burst is enabled
for channel, config in DEFAULT_IO_CONFIG.items():
self.set_io_values(channel, **config)
self.set_trigger(DEFAULT_TRIGGER_SOURCE)
self.set_references_for_channels(
[
("A", CHANNELREFERENCE.T0),
("B", CHANNELREFERENCE.A),
("C", CHANNELREFERENCE.T0),
("D", CHANNELREFERENCE.C),
("E", CHANNELREFERENCE.D), # One extra pulse once shutter closes for MCS
("F", CHANNELREFERENCE.E),
("G", CHANNELREFERENCE.T0),
("H", CHANNELREFERENCE.G),
]
)
# Start background thread to poll status register
# self._status_polling_stop_event = Event()
# self._status_polling_thread = Thread(target=self._poll_status)
# self._status_polling_thread.start()
# atexit.register(self.on_destroy)
# def _poll_status(self):
# """The status register has to be actively pulled, triggering the proc_status results in
# event_status being updated, which in turn allows the StatusBitsCompareStatus from on_trigger
# to be updated and eventually resolve."""
# dispatcher = self.state.proc_status.cl.get_dispatcher()
# event = dispatcher.stop_event
# while not (self._status_polling_stop_event.is_set() or event.is_set()):
# try:
# # Call with timeout to avoid blocking in shutdown
# self.state.proc_status.put(1, timeout=1)
# except TimeoutError:
# # If any of the stop events are set, stop polling
# if self._status_polling_stop_event.is_set() or event.is_set():
# logger.info("Exiting _poll_status thread loop for DDG.")
# break
# time.sleep(1 / 5) # poll the status at 5 Hz
def on_stage(self) -> DeviceStatus | StatusBase | None:
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
readout_time = self.scan_info.msg.scan_parameters["readout_time"]
if readout_time is not None and readout_time != 0:
# If we are given a single readout time from BEC, use it for all 4 channels
pulse_widths = [exp_time - readout_time] * len(DEFAULT_DEAD_TIMES_S)
else:
# Otherwise, derive the pulse widths from the default dead times defined above
pulse_widths = [exp_time - DEFAULT_DEAD_TIMES_S[ch] for ch in DEFAULT_DEAD_TIMES_S]
logger.info(f"setting pulse widths to {pulse_widths}")
self.set_delay_pairs(["ab", "cd", "ef", "gh"], delay=0, width=pulse_widths)
self.burst_enable(count=frames_per_trigger, delay=0, period=exp_time)
def on_trigger(self) -> DeviceStatus | StatusBase | None:
""" Note, we need to add a delay to the StatusBits callback on the event_status.
If we don't then subsequent triggers may reach the DDG too early, and will be ignored. To
avoid this, we've added the option to specify a delay via add_delay, default here is 50ms."""
st = StatusBase()#StatusBitsCompareStatus(self.state.event_status, STATUSBITS.END_OF_BURST, run=False)
self.cancel_on_stop(st)
self.trigger_shot.put(1, use_complete=True)
time.sleep(self.scan_info.msg.scan_parameters["exp_time"])
timer = 0
# TODO make asynchronous and nicer!
while st.done is False:
self.state.proc_status.put(1,use_complete=True)
# Do I need to give this time to update? ask Xiaoqiang!!
event_status = self.state.event_status.get()
if (STATUSBITS(event_status) & STATUSBITS.END_OF_BURST) == STATUSBITS.END_OF_BURST:
st.set_finished()
timer += 0.1
time.sleep(0.1)
if timer > 1:
st.set_exception(TimeoutError(f"Device {self.name} failed to finish trigger"))
break
time.sleep(0.05)
return st
def on_destroy(self) -> None:
return
if getattr(self, "_status_polling_stop_event", None) is not None:
self._status_polling_stop_event.set()
if getattr(self, "_status_polling_thread", None) is not None:
self._status_polling_thread.join(timeout=3)
def on_stop(self) -> None:
"""Stop the delay generator by setting the burst mode to 0"""
self.stop_ddg()
if __name__ == "__main__":
ddg = DDG1(name="ddg", prefix="X12SA-CPCL-DDG1:")
ddg.wait_for_connection(all_signals=True, timeout=30)
ddg.summary()

View File

@@ -38,7 +38,7 @@ DEFAULT_TRIGGER_SOURCE: TRIGGERSOURCE = TRIGGERSOURCE.SINGLE_SHOT
DEFAULT_DEAD_TIMES_S = {"ab": 1e-3, "cd": 1e-3, "ef": 1e-3, "gh": 1e-3}
class DDGMaster(PSIDeviceBase, DelayGeneratorCSAXS):
class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
"""
Implementation of DelayGeneratorCSAXS for the CSAXS master trigger delay generator at X12SA-CPCL-DDG1
"""
@@ -59,34 +59,12 @@ class DDGMaster(PSIDeviceBase, DelayGeneratorCSAXS):
("B", CHANNELREFERENCE.A),
("C", CHANNELREFERENCE.T0),
("D", CHANNELREFERENCE.C),
("E", CHANNELREFERENCE.T0),
("E", CHANNELREFERENCE.T0),# One extra pulse once shutter closes for MCS
("F", CHANNELREFERENCE.E),
("G", CHANNELREFERENCE.T0),
("H", CHANNELREFERENCE.G),
]
)
# Start background thread to poll status register
self._status_polling_stop_event = Event()
self._status_polling_thread = Thread(target=self._poll_status)
self._status_polling_thread.start()
atexit.register(self.on_destroy)
def _poll_status(self):
"""The status register has to be actively pulled, triggering the proc_status results in
event_status being updated, which in turn allows the StatusBitsCompareStatus from on_trigger
to be updated and eventually resolve."""
dispatcher = self.state.proc_status.cl.get_dispatcher()
event = dispatcher.stop_event
while not (self._status_polling_stop_event.is_set() or event.is_set()):
try:
# Call with timeout to avoid blocking in shutdown
self.state.proc_status.put(1, timeout=1)
except TimeoutError:
# If any of the stop events are set, stop polling
if self._status_polling_stop_event.is_set() or event.is_set():
logger.info("Exiting _poll_status thread loop for DDG.")
break
time.sleep(1 / 5) # poll the status at 5 Hz
def on_stage(self) -> DeviceStatus | StatusBase | None:
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
@@ -104,12 +82,31 @@ class DDGMaster(PSIDeviceBase, DelayGeneratorCSAXS):
self.burst_enable(count=frames_per_trigger, delay=0, period=exp_time)
def on_trigger(self) -> DeviceStatus | StatusBase | None:
st = StatusBitsCompareStatus(self.state.event_status, STATUSBITS.END_OF_BURST, run=False)
""" Note, we need to add a delay to the StatusBits callback on the event_status.
If we don't then subsequent triggers may reach the DDG too early, and will be ignored. To
avoid this, we've added the option to specify a delay via add_delay, default here is 50ms."""
st = StatusBase()#StatusBitsCompareStatus(self.state.event_status, STATUSBITS.END_OF_BURST, run=False)
self.cancel_on_stop(st)
self.trigger_shot.put(1)
self.trigger_shot.put(1, use_complete=True)
time.sleep(self.scan_info.msg.scan_parameters["exp_time"])
timer = 0
# TODO make asynchronous and nicer!
while st.done is False:
self.state.proc_status.put(1,use_complete=True)
# Do I need to give this time to update? ask Xiaoqiang!!
event_status = self.state.event_status.get()
if (STATUSBITS(event_status) & STATUSBITS.END_OF_BURST) == STATUSBITS.END_OF_BURST:
st.set_finished()
timer += 0.1
time.sleep(0.1)
if timer > 1:
st.set_exception(TimeoutError(f"Device {self.name} failed to finish trigger"))
break
time.sleep(0.05)
return st
def on_destroy(self) -> None:
return
if getattr(self, "_status_polling_stop_event", None) is not None:
self._status_polling_stop_event.set()
if getattr(self, "_status_polling_thread", None) is not None:
@@ -121,6 +118,6 @@ class DDGMaster(PSIDeviceBase, DelayGeneratorCSAXS):
if __name__ == "__main__":
ddg = DDGMaster(name="ddg", prefix="X12SA-CPCL-DDG1:")
ddg = DDG1(name="ddg", prefix="X12SA-CPCL-DDG1:")
ddg.wait_for_connection(all_signals=True, timeout=30)
ddg.summary()

View File

@@ -7,6 +7,7 @@ https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
import enum
from typing import Literal, TypedDict
import time
from bec_lib.logger import bec_logger
from ophyd import Component as Cpt
@@ -113,6 +114,7 @@ class StatusBitsCompareStatus(SubscriptionStatus):
*args,
event_type=None,
timeout: float | None = None,
add_delay:float|None = None,
settle_time: float = 0,
run: bool = True,
**kwargs,
@@ -120,6 +122,7 @@ class StatusBitsCompareStatus(SubscriptionStatus):
"""Initialize the compare status with a signal."""
self._signal = signal
self._value = value
self._add_delay = add_delay or 0
self._raise_states = raise_states or []
super().__init__(
device=signal,
@@ -132,6 +135,12 @@ class StatusBitsCompareStatus(SubscriptionStatus):
def _compare_callback(self, value, **kwargs) -> bool:
"""Callback for subscription status"""
obj = kwargs.get("obj", None)
if obj is None:
name = 'no object received'
else:
name=obj.name
logger.info(f"Receive update for {name} with value {value}")
if any((STATUSBITS(value) & state) == state for state in self._raise_states):
self.set_exception(
ValueError(
@@ -139,6 +148,10 @@ class StatusBitsCompareStatus(SubscriptionStatus):
)
)
return False
if self._add_delay !=0:
logger.info(f"Sleeping for {self._add_delay} for {name}")
time.sleep(self._add_delay)
return (STATUSBITS(value) & self._value) == self._value