refactor(ddg2): add check for negative pulse widths.

This commit is contained in:
2025-08-05 12:31:42 +02:00
parent a01593fa4b
commit 3c9192d6a5
4 changed files with 69 additions and 15 deletions
@@ -42,6 +42,7 @@ from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import ( from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
CHANNELREFERENCE, CHANNELREFERENCE,
OUTPUTPOLARITY, OUTPUTPOLARITY,
PROC_EVENT_MODE,
STATUSBITS, STATUSBITS,
TRIGGERSOURCE, TRIGGERSOURCE,
AllChannelNames, AllChannelNames,
@@ -122,6 +123,8 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
self.set_io_values(channel, **config) self.set_io_values(channel, **config)
self.set_trigger(DEFAULT_TRIGGER_SOURCE) self.set_trigger(DEFAULT_TRIGGER_SOURCE)
self.set_references_for_channels(DEFAULT_REFERENCES) self.set_references_for_channels(DEFAULT_REFERENCES)
# Set proc status to passively update with 5Hz (0.2s)
self.state.proc_status_mode.put(PROC_EVENT_MODE.FREQ_5HZ)
def on_stage(self) -> DeviceStatus | StatusBase | None: def on_stage(self) -> DeviceStatus | StatusBase | None:
""" """
@@ -153,6 +156,8 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
If we don't then subsequent triggers may reach the DDG too early, and will be ignored. To 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. avoid this, we've added the option to specify a delay via add_delay, default here is 50ms.
""" """
status = CompareStatus(self.state.event_status, STATUSBITS.NONE)
self.cancel_on_stop(status)
mcs = self.device_manager.devices.get("mcs", None) mcs = self.device_manager.devices.get("mcs", None)
if mcs is None: if mcs is None:
logger.info(f"Did not find mcs card in current session") logger.info(f"Did not find mcs card in current session")
@@ -172,22 +177,23 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
status_acquiring.wait( status_acquiring.wait(
timeout=10 timeout=10
) # 2 s wait for mcs card to start should be more than enough.. ) # 2 s wait for mcs card to start should be more than enough..
status.wait(timeout=10)
st = DeviceStatus(self) # Default timeout of 5 seconds + exposure time * frames_per_trigger
self.cancel_on_stop(st) timeout = 5 + self.scan_info.msg.scan_parameters.get(
"exp_time", 0.1
) * self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
status = CompareStatus(self.state.event_status, STATUSBITS.END_OF_DELAY, timeout=timeout)
self.cancel_on_stop(status)
self.trigger_shot.put(1, use_complete=True) self.trigger_shot.put(1, use_complete=True)
time.sleep(self.scan_info.msg.scan_parameters["exp_time"])
self.cancel_on_stop(st)
status = self.wait_for_status(status=st, bit_event=STATUSBITS.END_OF_DELAY, timeout=10)
return status return status
def wait_for_status( def wait_for_status(
self, status: StatusBase, bit_event: STATUSBITS, timeout: float = 2 self, status: DeviceStatus, bit_event: STATUSBITS, timeout: float = 2
) -> None: ) -> None:
"""Wait for a event status bit to be set. """Wait for a event status bit to be set.
Args: Args:
status (StatusBase): The status object to update. status (DeviceStatus): The status object to update.
bit_event (STATUSBITS): The event status bit to wait for. bit_event (STATUSBITS): The event status bit to wait for.
timeout (float): Maximum time to wait for the event status bit to be set. timeout (float): Maximum time to wait for the event status bit to be set.
""" """
@@ -100,6 +100,10 @@ class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"] frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
# a = t0 # a = t0
# a has reference to t0, b has reference to a # a has reference to t0, b has reference to a
if any(exp_time < rt for rt in DEFAULT_READOUT_TIMES.values()):
raise ValueError(
f"Exposure time {exp_time} is too short for the readout times {DEFAULT_READOUT_TIMES}"
)
burst_pulse_width = exp_time - DEFAULT_READOUT_TIMES["ab"] burst_pulse_width = exp_time - DEFAULT_READOUT_TIMES["ab"]
self.set_delay_pairs(channel="ab", delay=0, width=burst_pulse_width) self.set_delay_pairs(channel="ab", delay=0, width=burst_pulse_width)
self.burst_enable(count=frames_per_trigger, delay=0, period=exp_time) self.burst_enable(count=frames_per_trigger, delay=0, period=exp_time)
@@ -6,8 +6,8 @@ https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
""" """
import enum import enum
from typing import Literal, TypedDict
import time import time
from typing import Literal, TypedDict
from bec_lib.logger import bec_logger from bec_lib.logger import bec_logger
from ophyd import Component as Cpt from ophyd import Component as Cpt
@@ -76,9 +76,26 @@ class OUTPUTPOLARITY(enum.Enum):
POSITIVE = 1 POSITIVE = 1
class PROC_EVENT_MODE(int, enum.Enum):
"""Read mode for MCS channels."""
PASSIVE = 0
EVENT = 1
IO_INTR = 2
FREQ_0_1HZ = 3
FREQ_0_2HZ = 4
FREQ_0_5HZ = 5
FREQ_1HZ = 6
FREQ_2HZ = 7
FREQ_5HZ = 8
FREQ_10HZ = 9
FREQ_100HZ = 10
class STATUSBITS(enum.IntFlag): class STATUSBITS(enum.IntFlag):
"""Bit flags for the status signal of the delay generator.""" """Bit flags for the status signal of the delay generator."""
NONE = 0 << 0 # No status bits set.
TRIG = 1 << 0 # Got a trigger. TRIG = 1 << 0 # Got a trigger.
RATE = 1 << 1 # Got a trigger while a delay or burst was in progress. RATE = 1 << 1 # Got a trigger while a delay or burst was in progress.
END_OF_DELAY = 1 << 2 # A delay cycle has completed. END_OF_DELAY = 1 << 2 # A delay cycle has completed.
@@ -91,6 +108,7 @@ class STATUSBITS(enum.IntFlag):
def describe(self) -> dict: def describe(self) -> dict:
"""Return a description of the status bits.""" """Return a description of the status bits."""
descriptions = { descriptions = {
STATUSBITS.NONE: "No status bits set.",
STATUSBITS.TRIG: "Got a trigger.", STATUSBITS.TRIG: "Got a trigger.",
STATUSBITS.RATE: "Got a trigger while a delay or burst was in progress.", STATUSBITS.RATE: "Got a trigger while a delay or burst was in progress.",
STATUSBITS.END_OF_DELAY: "A delay cycle has completed.", STATUSBITS.END_OF_DELAY: "A delay cycle has completed.",
@@ -114,7 +132,7 @@ class StatusBitsCompareStatus(SubscriptionStatus):
*args, *args,
event_type=None, event_type=None,
timeout: float | None = None, timeout: float | None = None,
add_delay:float|None = None, add_delay: float | None = None,
settle_time: float = 0, settle_time: float = 0,
run: bool = True, run: bool = True,
**kwargs, **kwargs,
@@ -137,9 +155,9 @@ class StatusBitsCompareStatus(SubscriptionStatus):
"""Callback for subscription status""" """Callback for subscription status"""
obj = kwargs.get("obj", None) obj = kwargs.get("obj", None)
if obj is None: if obj is None:
name = 'no object received' name = "no object received"
else: else:
name=obj.name name = obj.name
if any((STATUSBITS(value) & state) == state for state in self._raise_states): if any((STATUSBITS(value) & state) == state for state in self._raise_states):
self.set_exception( self.set_exception(
ValueError( ValueError(
@@ -147,7 +165,7 @@ class StatusBitsCompareStatus(SubscriptionStatus):
) )
) )
return False return False
if self._add_delay !=0: if self._add_delay != 0:
time.sleep(self._add_delay) time.sleep(self._add_delay)
return (STATUSBITS(value) & self._value) == self._value return (STATUSBITS(value) & self._value) == self._value
@@ -378,17 +396,24 @@ class DelayGeneratorEventStatus(Device):
"EventStatusLI", "EventStatusLI",
name="event_status", name="event_status",
kind=Kind.omitted, kind=Kind.omitted,
auto_monitor=True,
doc="Event status register for the delay generator", doc="Event status register for the delay generator",
) )
proc_status = Cpt( proc_status = Cpt(
EpicsSignal, EpicsSignal,
"EventStatusLI.PROC", "EventStatusLI.PROC",
name="proc_status", name="proc_status",
auto_monitor=True,
kind=Kind.omitted, kind=Kind.omitted,
doc="Poll and flush the latest event status register entry from the HW to the event_status signal", doc="Poll and flush the latest event status register entry from the HW to the event_status signal",
) )
proc_status_mode = Cpt(
EpicsSignal,
"EventStatusLI.SCAN",
kind=Kind.omitted,
doc="Readout mode for transferring data from status buffer to the event_status signal.",
)
class DelayGeneratorCSAXS(Device): class DelayGeneratorCSAXS(Device):
""" """
@@ -403,6 +428,13 @@ class DelayGeneratorCSAXS(Device):
In addition, the io layer allows setting amplitude, offset and polarity for each pair. In addition, the io layer allows setting amplitude, offset and polarity for each pair.
""" """
# USER_ACCESS = [
# "set_channel_reference",
# "set_references_for_channels",
# "set_io_values",
# "set_trigger",
# ]
_pv_timeout: float = 5 # Default timeout for PV operations in seconds _pv_timeout: float = 5 # Default timeout for PV operations in seconds
# Front Panel # Front Panel
@@ -686,11 +718,23 @@ class DelayGeneratorCSAXS(Device):
}[channel] }[channel]
def set_channel_reference(self, channel: LiteralChannels, reference_channel: CHANNELREFERENCE): def set_channel_reference(self, channel: LiteralChannels, reference_channel: CHANNELREFERENCE):
"""Set the reference channel for a specific channel.
Args:
channel (LiteralChannels): The channel to set the reference for.
reference_channel (CHANNELREFERENCE): The reference channel to set.
"""
self._get_literal_channel(channel).reference.put(reference_channel.value) self._get_literal_channel(channel).reference.put(reference_channel.value)
def set_references_for_channels( def set_references_for_channels(
self, channels_and_refs: list[tuple[LiteralChannels, CHANNELREFERENCE]] self, channels_and_refs: list[tuple[LiteralChannels, CHANNELREFERENCE]]
): ):
"""Set the reference channels for multiple channels.
Args:
channels_and_refs (list[tuple[LiteralChannels, CHANNELREFERENCE]]): A list of
tuples where each tuple contains a channel and its corresponding reference channel.
"""
for ch, ref in channels_and_refs: for ch, ref in channels_and_refs:
self.set_channel_reference(ch, ref) self.set_channel_reference(ch, ref)
@@ -193,7 +193,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
self.counter_updated.append(signal.name) self.counter_updated.append(signal.name)
received_all_updates = set(self.counter_updated) == set(self.counter_mapping.keys()) received_all_updates = set(self.counter_updated) == set(self.counter_mapping.keys())
if received_all_updates: if received_all_updates:
self.ready_to_read.put(1) # Reset happens from DDG class! self.ready_to_read.put(READYTOREAD.DONE) # Reset happens from DDG class!
self.counter_updated.clear() self.counter_updated.clear()
def _progress_update(self, value, **kwargs) -> None: def _progress_update(self, value, **kwargs) -> None: