feat(nidaq)!: adopt channel-first signal names as the final convention
This commit is contained in:
1 parent
f7843d055b
commit
7cbdfb5b37
3 files changed
+181
-171
No files matched your search
@@ -30,11 +30,12 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
#: Channels per statistic group. Group sub-signals are emitted as
|
||||
#: 'nidaq_<group>_<channel>' (e.g. 'nidaq_mean_ai0') in BOTH trigger modes.
|
||||
NIDAQ_MEAN_CHANNELS = [f"ai{i}" for i in range(8)] + [f"ci{i}" for i in range(18)]
|
||||
NIDAQ_MAX_CHANNELS = [f"di{i}" for i in range(5)]
|
||||
NIDAQ_STD_DEV_CHANNELS = [f"ai{i}" for i in range(8)] + [f"ci{i}" for i in range(18)]
|
||||
#: FINAL naming convention (channel-first, identical to the backend stream):
|
||||
#: nidaq_ai0_mean, nidaq_ai0_std_dev, nidaq_di0_max, nidaq_energy, ...
|
||||
#: ai/ci channels are two-signal DynamicSignal groups (mean/std_dev); every
|
||||
#: other channel is a single-signal group whose emitted name IS the group name.
|
||||
NIDAQ_AI_CI_CHANNELS = [f"ai{i}" for i in range(8)] + [f"ci{i}" for i in range(18)]
|
||||
NIDAQ_DI_CHANNELS = [f"di{i}_max" for i in range(5)]
|
||||
NIDAQ_STREAM_CHANNELS = [
|
||||
"energy",
|
||||
"enc",
|
||||
@@ -47,34 +48,28 @@ NIDAQ_STREAM_CHANNELS = [
|
||||
"xrd_timestamp",
|
||||
"xrd_angle",
|
||||
"xrd_energy",
|
||||
"xrd_ai0_mean",
|
||||
"xrd_ai0_std_dev",
|
||||
"xrd_ai0",
|
||||
]
|
||||
|
||||
#: Group channel -> device attribute of the live EPICS scalar backing it in
|
||||
#: software-triggered scans. The backend's standby acquisition feeds ALL
|
||||
#: channels (plus energy and encoder), so everything with an EPICS counterpart
|
||||
#: is forwarded; std_dev and the derived stream channels are hardware-only.
|
||||
NIDAQ_SW_FORWARD_SOURCES: dict[str, dict[str, str]] = {
|
||||
"mean": {channel: channel for channel in NIDAQ_MEAN_CHANNELS},
|
||||
"max": {channel: channel for channel in NIDAQ_MAX_CHANNELS},
|
||||
"stream": {"energy": "energy_epics", "enc": "enc_epics"},
|
||||
}
|
||||
|
||||
#: Backend channel name -> (group, group channel) for the unified bridge.
|
||||
#: Covers both backend naming variants: with RLE the per-oscillation statistics
|
||||
#: carry _mean/_std_dev/_max suffixes, without RLE the names are bare.
|
||||
#: Backend channel name -> (group component, put key) for the unified bridge.
|
||||
#: Two-signal groups (ai/ci) take the statistic as key; single-signal groups
|
||||
#: require the FULL emitted name as key (bec_signals single-channel rename).
|
||||
#: Bare ai/ci names (backend no-RLE mode) map onto the mean channel.
|
||||
NIDAQ_BRIDGE_ROUTING: dict[str, tuple[str, str]] = (
|
||||
{f"ai{i}_mean": ("mean", f"ai{i}") for i in range(8)}
|
||||
| {f"ai{i}": ("mean", f"ai{i}") for i in range(8)}
|
||||
| {f"ai{i}_std_dev": ("std_dev", f"ai{i}") for i in range(8)}
|
||||
| {f"ci{i}_mean": ("mean", f"ci{i}") for i in range(18)}
|
||||
| {f"ci{i}": ("mean", f"ci{i}") for i in range(18)}
|
||||
| {f"ci{i}_std_dev": ("std_dev", f"ci{i}") for i in range(18)}
|
||||
| {f"di{i}_max": ("max", f"di{i}") for i in range(5)}
|
||||
| {f"di{i}": ("max", f"di{i}") for i in range(5)}
|
||||
| {channel: ("stream", channel) for channel in NIDAQ_STREAM_CHANNELS}
|
||||
| {"xrd_ai0": ("stream", "xrd_ai0_mean")}
|
||||
{f"{ch}_mean": (ch, "mean") for ch in NIDAQ_AI_CI_CHANNELS}
|
||||
| {f"{ch}_std_dev": (ch, "std_dev") for ch in NIDAQ_AI_CI_CHANNELS}
|
||||
| {ch: (ch, "mean") for ch in NIDAQ_AI_CI_CHANNELS}
|
||||
| {f"di{i}_max": (f"di{i}_max", f"nidaq_di{i}_max") for i in range(5)}
|
||||
| {f"di{i}": (f"di{i}_max", f"nidaq_di{i}_max") for i in range(5)}
|
||||
| {ch: (ch, f"nidaq_{ch}") for ch in NIDAQ_STREAM_CHANNELS}
|
||||
)
|
||||
|
||||
#: Software-mode sources: (group component, put key) -> EPICS scalar attribute.
|
||||
#: std_dev and the derived stream channels have no EPICS counterpart.
|
||||
NIDAQ_SW_FORWARD_SOURCES: dict[tuple[str, str], str] = (
|
||||
{(ch, "mean"): f"{ch}_epics" for ch in NIDAQ_AI_CI_CHANNELS}
|
||||
| {(f"di{i}_max", f"nidaq_di{i}_max"): f"di{i}_epics" for i in range(5)}
|
||||
| {("energy", "nidaq_energy"): "energy_epics", ("enc", "nidaq_enc"): "enc_epics"}
|
||||
)
|
||||
|
||||
|
||||
@@ -86,39 +81,39 @@ class NidaqControl(Device):
|
||||
"""Nidaq control class with all PVs"""
|
||||
|
||||
# fmt: off
|
||||
ai0 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI0", kind=Kind.omitted, doc="EPICS analog input 0", auto_monitor=True)
|
||||
ai1 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI1", kind=Kind.omitted, doc="EPICS analog input 1", auto_monitor=True)
|
||||
ai2 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI2", kind=Kind.omitted, doc="EPICS analog input 2", auto_monitor=True)
|
||||
ai3 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI3", kind=Kind.omitted, doc="EPICS analog input 3", auto_monitor=True)
|
||||
ai4 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI4", kind=Kind.omitted, doc="EPICS analog input 4", auto_monitor=True)
|
||||
ai5 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI5", kind=Kind.omitted, doc="EPICS analog input 5", auto_monitor=True)
|
||||
ai6 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI6", kind=Kind.omitted, doc="EPICS analog input 6", auto_monitor=True)
|
||||
ai7 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI7", kind=Kind.omitted, doc="EPICS analog input 7", auto_monitor=True)
|
||||
ai0_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI0", kind=Kind.omitted, doc="EPICS analog input 0", auto_monitor=True)
|
||||
ai1_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI1", kind=Kind.omitted, doc="EPICS analog input 1", auto_monitor=True)
|
||||
ai2_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI2", kind=Kind.omitted, doc="EPICS analog input 2", auto_monitor=True)
|
||||
ai3_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI3", kind=Kind.omitted, doc="EPICS analog input 3", auto_monitor=True)
|
||||
ai4_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI4", kind=Kind.omitted, doc="EPICS analog input 4", auto_monitor=True)
|
||||
ai5_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI5", kind=Kind.omitted, doc="EPICS analog input 5", auto_monitor=True)
|
||||
ai6_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI6", kind=Kind.omitted, doc="EPICS analog input 6", auto_monitor=True)
|
||||
ai7_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-AI7", kind=Kind.omitted, doc="EPICS analog input 7", auto_monitor=True)
|
||||
|
||||
ci0 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI0", kind=Kind.omitted, doc="EPICS counter input 0", auto_monitor=True)
|
||||
ci1 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI1", kind=Kind.omitted, doc="EPICS counter input 1", auto_monitor=True)
|
||||
ci2 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI2", kind=Kind.omitted, doc="EPICS counter input 2", auto_monitor=True)
|
||||
ci3 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI3", kind=Kind.omitted, doc="EPICS counter input 3", auto_monitor=True)
|
||||
ci4 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI4", kind=Kind.omitted, doc="EPICS counter input 4", auto_monitor=True)
|
||||
ci5 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI5", kind=Kind.omitted, doc="EPICS counter input 5", auto_monitor=True)
|
||||
ci6 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI6", kind=Kind.omitted, doc="EPICS counter input 6", auto_monitor=True)
|
||||
ci7 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI7", kind=Kind.omitted, doc="EPICS counter input 7", auto_monitor=True)
|
||||
ci8 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI8", kind=Kind.omitted, doc="EPICS counter input 8", auto_monitor=True)
|
||||
ci9 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI9", kind=Kind.omitted, doc="EPICS counter input 9", auto_monitor=True)
|
||||
ci10 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI10", kind=Kind.omitted, doc="EPICS counter input 0", auto_monitor=True)
|
||||
ci11 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI11", kind=Kind.omitted, doc="EPICS counter input 1", auto_monitor=True)
|
||||
ci12 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI12", kind=Kind.omitted, doc="EPICS counter input 2", auto_monitor=True)
|
||||
ci13 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI13", kind=Kind.omitted, doc="EPICS counter input 3", auto_monitor=True)
|
||||
ci14 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI14", kind=Kind.omitted, doc="EPICS counter input 4", auto_monitor=True)
|
||||
ci15 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI15", kind=Kind.omitted, doc="EPICS counter input 5", auto_monitor=True)
|
||||
ci16 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI16", kind=Kind.omitted, doc="EPICS counter input 6", auto_monitor=True)
|
||||
ci17 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI17", kind=Kind.omitted, doc="EPICS counter input 7", auto_monitor=True)
|
||||
ci0_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI0", kind=Kind.omitted, doc="EPICS counter input 0", auto_monitor=True)
|
||||
ci1_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI1", kind=Kind.omitted, doc="EPICS counter input 1", auto_monitor=True)
|
||||
ci2_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI2", kind=Kind.omitted, doc="EPICS counter input 2", auto_monitor=True)
|
||||
ci3_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI3", kind=Kind.omitted, doc="EPICS counter input 3", auto_monitor=True)
|
||||
ci4_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI4", kind=Kind.omitted, doc="EPICS counter input 4", auto_monitor=True)
|
||||
ci5_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI5", kind=Kind.omitted, doc="EPICS counter input 5", auto_monitor=True)
|
||||
ci6_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI6", kind=Kind.omitted, doc="EPICS counter input 6", auto_monitor=True)
|
||||
ci7_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI7", kind=Kind.omitted, doc="EPICS counter input 7", auto_monitor=True)
|
||||
ci8_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI8", kind=Kind.omitted, doc="EPICS counter input 8", auto_monitor=True)
|
||||
ci9_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI9", kind=Kind.omitted, doc="EPICS counter input 9", auto_monitor=True)
|
||||
ci10_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI10", kind=Kind.omitted, doc="EPICS counter input 0", auto_monitor=True)
|
||||
ci11_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI11", kind=Kind.omitted, doc="EPICS counter input 1", auto_monitor=True)
|
||||
ci12_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI12", kind=Kind.omitted, doc="EPICS counter input 2", auto_monitor=True)
|
||||
ci13_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI13", kind=Kind.omitted, doc="EPICS counter input 3", auto_monitor=True)
|
||||
ci14_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI14", kind=Kind.omitted, doc="EPICS counter input 4", auto_monitor=True)
|
||||
ci15_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI15", kind=Kind.omitted, doc="EPICS counter input 5", auto_monitor=True)
|
||||
ci16_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI16", kind=Kind.omitted, doc="EPICS counter input 6", auto_monitor=True)
|
||||
ci17_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-CI17", kind=Kind.omitted, doc="EPICS counter input 7", auto_monitor=True)
|
||||
|
||||
di0 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI0", kind=Kind.omitted, doc="EPICS digital input 0", auto_monitor=True)
|
||||
di1 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI1", kind=Kind.omitted, doc="EPICS digital input 1", auto_monitor=True)
|
||||
di2 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI2", kind=Kind.omitted, doc="EPICS digital input 2", auto_monitor=True)
|
||||
di3 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI3", kind=Kind.omitted, doc="EPICS digital input 3", auto_monitor=True)
|
||||
di4 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI4", kind=Kind.omitted, doc="EPICS digital input 4", auto_monitor=True)
|
||||
di0_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-DI0", kind=Kind.omitted, doc="EPICS digital input 0", auto_monitor=True)
|
||||
di1_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-DI1", kind=Kind.omitted, doc="EPICS digital input 1", auto_monitor=True)
|
||||
di2_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-DI2", kind=Kind.omitted, doc="EPICS digital input 2", auto_monitor=True)
|
||||
di3_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-DI3", kind=Kind.omitted, doc="EPICS digital input 3", auto_monitor=True)
|
||||
di4_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-DI4", kind=Kind.omitted, doc="EPICS digital input 4", auto_monitor=True)
|
||||
|
||||
enc_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENC", kind=Kind.omitted, doc="EPICS Encoder reading", auto_monitor=True)
|
||||
|
||||
@@ -188,12 +183,50 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
progress_signal = Cpt(ProgressSignal, name="progress_signal")
|
||||
|
||||
# fmt: off
|
||||
### Statistic groups: emitted as nidaq_<group>_<channel> in both trigger modes ###
|
||||
#TODO decide on how to group signals, statistic groupd, analog/counter/digital etc.
|
||||
mean = Cpt(DynamicSignal, name="mean", ndim=1, max_size=1000, signals=NIDAQ_MEAN_CHANNELS, async_update={"type": "add", "max_shape": [None]}, doc="Per-oscillation means (HW) / synchronous readbacks (SW)")
|
||||
max = Cpt(DynamicSignal, name="max", ndim=1, max_size=1000, signals=NIDAQ_MAX_CHANNELS, async_update={"type": "add", "max_shape": [None]}, doc="Per-oscillation maxima (HW) / synchronous readbacks (SW)")
|
||||
std_dev = Cpt(DynamicSignal, name="std_dev", ndim=1, max_size=1000, signals=NIDAQ_STD_DEV_CHANNELS, async_update={"type": "add", "max_shape": [None]}, doc="Per-oscillation standard deviations (HW only)")
|
||||
stream = Cpt(DynamicSignal, name="stream", ndim=1, max_size=1000, signals=NIDAQ_STREAM_CHANNELS, async_update={"type": "add", "max_shape": [None]}, doc="Energy, encoder, derived and XRD channels")
|
||||
### Per-channel async groups: emitted names are channel-first (nidaq_ai0_mean) ###
|
||||
ai0 = Cpt(DynamicSignal, name="ai0", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai1 = Cpt(DynamicSignal, name="ai1", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai2 = Cpt(DynamicSignal, name="ai2", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai3 = Cpt(DynamicSignal, name="ai3", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai4 = Cpt(DynamicSignal, name="ai4", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai5 = Cpt(DynamicSignal, name="ai5", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai6 = Cpt(DynamicSignal, name="ai6", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ai7 = Cpt(DynamicSignal, name="ai7", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci0 = Cpt(DynamicSignal, name="ci0", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci1 = Cpt(DynamicSignal, name="ci1", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci2 = Cpt(DynamicSignal, name="ci2", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci3 = Cpt(DynamicSignal, name="ci3", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci4 = Cpt(DynamicSignal, name="ci4", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci5 = Cpt(DynamicSignal, name="ci5", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci6 = Cpt(DynamicSignal, name="ci6", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci7 = Cpt(DynamicSignal, name="ci7", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci8 = Cpt(DynamicSignal, name="ci8", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci9 = Cpt(DynamicSignal, name="ci9", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci10 = Cpt(DynamicSignal, name="ci10", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci11 = Cpt(DynamicSignal, name="ci11", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci12 = Cpt(DynamicSignal, name="ci12", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci13 = Cpt(DynamicSignal, name="ci13", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci14 = Cpt(DynamicSignal, name="ci14", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci15 = Cpt(DynamicSignal, name="ci15", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci16 = Cpt(DynamicSignal, name="ci16", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
ci17 = Cpt(DynamicSignal, name="ci17", ndim=1, max_size=1000, signals=["mean", "std_dev"], async_update={"type": "add", "max_shape": [None]})
|
||||
di0_max = Cpt(DynamicSignal, name="di0_max", ndim=1, max_size=1000, signals=["max"], async_update={"type": "add", "max_shape": [None]})
|
||||
di1_max = Cpt(DynamicSignal, name="di1_max", ndim=1, max_size=1000, signals=["max"], async_update={"type": "add", "max_shape": [None]})
|
||||
di2_max = Cpt(DynamicSignal, name="di2_max", ndim=1, max_size=1000, signals=["max"], async_update={"type": "add", "max_shape": [None]})
|
||||
di3_max = Cpt(DynamicSignal, name="di3_max", ndim=1, max_size=1000, signals=["max"], async_update={"type": "add", "max_shape": [None]})
|
||||
di4_max = Cpt(DynamicSignal, name="di4_max", ndim=1, max_size=1000, signals=["max"], async_update={"type": "add", "max_shape": [None]})
|
||||
energy = Cpt(DynamicSignal, name="energy", ndim=1, max_size=1000, signals=["energy"], async_update={"type": "add", "max_shape": [None]})
|
||||
enc = Cpt(DynamicSignal, name="enc", ndim=1, max_size=1000, signals=["enc"], async_update={"type": "add", "max_shape": [None]})
|
||||
smpl_abs = Cpt(DynamicSignal, name="smpl_abs", ndim=1, max_size=1000, signals=["smpl_abs"], async_update={"type": "add", "max_shape": [None]})
|
||||
smpl_fluo = Cpt(DynamicSignal, name="smpl_fluo", ndim=1, max_size=1000, signals=["smpl_fluo"], async_update={"type": "add", "max_shape": [None]})
|
||||
ref_abs = Cpt(DynamicSignal, name="ref_abs", ndim=1, max_size=1000, signals=["ref_abs"], async_update={"type": "add", "max_shape": [None]})
|
||||
cisum = Cpt(DynamicSignal, name="cisum", ndim=1, max_size=1000, signals=["cisum"], async_update={"type": "add", "max_shape": [None]})
|
||||
rle = Cpt(DynamicSignal, name="rle", ndim=1, max_size=1000, signals=["rle"], async_update={"type": "add", "max_shape": [None]})
|
||||
xas_timestamp = Cpt(DynamicSignal, name="xas_timestamp", ndim=1, max_size=1000, signals=["xas_timestamp"], async_update={"type": "add", "max_shape": [None]})
|
||||
xrd_timestamp = Cpt(DynamicSignal, name="xrd_timestamp", ndim=1, max_size=1000, signals=["xrd_timestamp"], async_update={"type": "add", "max_shape": [None]})
|
||||
xrd_angle = Cpt(DynamicSignal, name="xrd_angle", ndim=1, max_size=1000, signals=["xrd_angle"], async_update={"type": "add", "max_shape": [None]})
|
||||
xrd_energy = Cpt(DynamicSignal, name="xrd_energy", ndim=1, max_size=1000, signals=["xrd_energy"], async_update={"type": "add", "max_shape": [None]})
|
||||
xrd_ai0 = Cpt(DynamicSignal, name="xrd_ai0", ndim=1, max_size=1000, signals=["xrd_ai0"], async_update={"type": "add", "max_shape": [None]})
|
||||
# fmt: on
|
||||
|
||||
USER_ACCESS = ["set_config"]
|
||||
@@ -279,15 +312,13 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
):
|
||||
return
|
||||
now = time.time()
|
||||
for group, sources in NIDAQ_SW_FORWARD_SOURCES.items():
|
||||
batch = {
|
||||
channel: {"value": getattr(self, attr).get(), "timestamp": now}
|
||||
for channel, attr in sources.items()
|
||||
}
|
||||
if batch:
|
||||
# Annotate the alignment: these rows are in sync with the
|
||||
# monitored readouts (cf. MCSCardCSAXS acquisition_group logic).
|
||||
self._guarded_group_put(group, batch, acquisition_group="monitored")
|
||||
buckets: dict[str, dict] = defaultdict(dict)
|
||||
for (group, key), attr in NIDAQ_SW_FORWARD_SOURCES.items():
|
||||
buckets[group][key] = {"value": getattr(self, attr).get(), "timestamp": now}
|
||||
for group, batch in buckets.items():
|
||||
# Annotate the alignment: these rows are in sync with the
|
||||
# monitored readouts (cf. MCSCardCSAXS acquisition_group logic).
|
||||
self._guarded_group_put(group, batch, acquisition_group="monitored")
|
||||
|
||||
def _guarded_group_put(
|
||||
self,
|
||||
@@ -391,7 +422,7 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
logger.info(f"Device {self.name}: unified bridge stopped")
|
||||
|
||||
def _forward_backend_message(self, msg) -> None:
|
||||
"""Route one backend DeviceMessage into the statistic groups."""
|
||||
"""Route one backend DeviceMessage into the per-channel groups."""
|
||||
device_prefix = f"{self.name}_"
|
||||
buckets: dict[str, dict] = defaultdict(dict)
|
||||
now = time.time()
|
||||
@@ -405,8 +436,8 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
f"Device {self.name}: skipping unknown backend channel '{signal_name}'"
|
||||
)
|
||||
continue
|
||||
group, group_channel = route
|
||||
buckets[group][group_channel] = {
|
||||
group, key = route
|
||||
buckets[group][key] = {
|
||||
"value": data["value"],
|
||||
"timestamp": data.get("timestamp") or now,
|
||||
}
|
||||
|
||||
@@ -236,10 +236,10 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
self.configuration.get("nidaq", {}).get("nidaq_add_chans", {}).get("value")
|
||||
)
|
||||
|
||||
# The statistic groups are written by the async writer under the device itself,
|
||||
# independent of the readout priority the device is configured with. Compression
|
||||
# (RLE) no longer changes the name: per-oscillation means and raw readbacks both
|
||||
# arrive in the 'mean' group.
|
||||
# The per-channel async groups are written by the async writer under the device
|
||||
# itself, using the FINAL channel-first names (identical to the backend stream).
|
||||
# Compression (RLE) no longer changes the name: the bridge maps the bare no-RLE
|
||||
# channel names onto the mean signals.
|
||||
nidaq_data = "/entry/collection/devices/nidaq"
|
||||
|
||||
measurement_mode = entry.create_group(name="mode")
|
||||
@@ -303,9 +303,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
energy.attrs["NX_class"] = "NXdata"
|
||||
energy.attrs["units"] = "eV"
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="energy", target=f"{nidaq_data}/nidaq_stream_energy/value"
|
||||
)
|
||||
main_data.create_soft_link(name="energy", target=f"{nidaq_data}/nidaq_energy/value")
|
||||
|
||||
##################
|
||||
## i0
|
||||
@@ -316,7 +314,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
i0.attrs["NX_class"] = "NXdata"
|
||||
i0.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i0", target=f"{nidaq_data}/nidaq_mean_ai0/value")
|
||||
main_data.create_soft_link(name="i0", target=f"{nidaq_data}/nidaq_ai0_mean/value")
|
||||
|
||||
##################
|
||||
## i1
|
||||
@@ -327,7 +325,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
i1.attrs["NX_class"] = "NXdata"
|
||||
i1.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i1", target=f"{nidaq_data}/nidaq_mean_ai2/value")
|
||||
main_data.create_soft_link(name="i1", target=f"{nidaq_data}/nidaq_ai2_mean/value")
|
||||
|
||||
##################
|
||||
## i2
|
||||
@@ -338,7 +336,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
i2.attrs["NX_class"] = "NXdata"
|
||||
i2.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i2", target=f"{nidaq_data}/nidaq_mean_ai4/value")
|
||||
main_data.create_soft_link(name="i2", target=f"{nidaq_data}/nidaq_ai4_mean/value")
|
||||
|
||||
##################
|
||||
## ci sum
|
||||
@@ -350,7 +348,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
ci_sum.attrs["units"] = "counts"
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="Fluorescence_Sum", target=f"{nidaq_data}/nidaq_stream_cisum/value"
|
||||
name="Fluorescence_Sum", target=f"{nidaq_data}/nidaq_cisum/value"
|
||||
)
|
||||
|
||||
##################
|
||||
@@ -362,7 +360,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
mu_sample.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="mu_sample", target=f"{nidaq_data}/nidaq_stream_smpl_abs/value"
|
||||
name="mu_sample", target=f"{nidaq_data}/nidaq_smpl_abs/value"
|
||||
)
|
||||
|
||||
##################
|
||||
@@ -374,7 +372,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
mu_sample.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="fluo_sample", target=f"{nidaq_data}/nidaq_stream_smpl_fluo/value"
|
||||
name="fluo_sample", target=f"{nidaq_data}/nidaq_smpl_fluo/value"
|
||||
)
|
||||
|
||||
##################
|
||||
@@ -386,5 +384,5 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
mu_reference.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="mu_reference", target=f"{nidaq_data}/nidaq_stream_ref_abs/value"
|
||||
name="mu_reference", target=f"{nidaq_data}/nidaq_ref_abs/value"
|
||||
)
|
||||
@@ -184,119 +184,100 @@ def test_on_complete(mock_nidaq, scan_info_mock):
|
||||
assert status.done is True
|
||||
|
||||
|
||||
########## DynamicSignal statistic groups ##########
|
||||
########## per-channel DynamicSignal groups (channel-first naming) ##########
|
||||
|
||||
from ophyd import Kind
|
||||
from types import SimpleNamespace
|
||||
|
||||
from ophyd import Kind, Staged
|
||||
from ophyd_devices.utils.bec_signals import DynamicSignal
|
||||
|
||||
|
||||
def test_statistic_groups_and_omitted_epics(mock_nidaq):
|
||||
"""Groups own the unified namespace; EPICS scalars are silent data sources.
|
||||
|
||||
DynamicSignal is deliberate (per the NIDAQ maintainers, the active channel
|
||||
set cannot be predicted upfront): channel names are validated, batch
|
||||
completeness is not.
|
||||
"""
|
||||
def test_channel_groups_and_omitted_epics(mock_nidaq):
|
||||
"""Per-channel groups own the FINAL channel-first namespace; the EPICS
|
||||
scalars are silent (omitted) data sources for the software forwarding."""
|
||||
dev = mock_nidaq
|
||||
assert isinstance(dev.mean, DynamicSignal)
|
||||
assert dev.mean.strict_signal_validation is False
|
||||
assert dev.mean.name == "nidaq_mean"
|
||||
assert dev.max.name == "nidaq_max"
|
||||
assert dev.std_dev.name == "nidaq_std_dev"
|
||||
assert dev.stream.name == "nidaq_stream"
|
||||
# EPICS scalars demoted: no longer in the readout namespace
|
||||
assert dev.ai0.kind == Kind.omitted
|
||||
assert isinstance(dev.ai0, DynamicSignal)
|
||||
assert dev.ai0.name == "nidaq_ai0"
|
||||
assert [name for name, _ in dev.di0_max.signals] == ["nidaq_di0_max"]
|
||||
assert [name for name, _ in dev.energy.signals] == ["nidaq_energy"]
|
||||
assert dev.ai0_epics.kind == Kind.omitted
|
||||
assert dev.enc_epics.kind == Kind.omitted
|
||||
assert "nidaq_ai0" not in dev.read()
|
||||
|
||||
|
||||
########## software-mode forwarding into the statistic groups ##########
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from ophyd import Staged
|
||||
|
||||
|
||||
def test_read_forwards_all_epics_scalars_when_gates_open(mock_nidaq, scan_info_mock):
|
||||
"""Software-triggered scans: the monitored readout forwards ALL EPICS
|
||||
values into the SAME groups the hardware-triggered stream feeds. The
|
||||
measurement bitmasks are irrelevant: the backend's standby acquisition
|
||||
runs every channel."""
|
||||
"""Software mode: ALL EPICS scalars forwarded under the channel-first names."""
|
||||
dev = mock_nidaq
|
||||
dev.ai_chans._read_pv.mock_data = 0b1 # stale hardware-scan bitmask, must be ignored
|
||||
dev.ai0._read_pv.mock_data = 5.0
|
||||
dev.ai1._read_pv.mock_data = 99.0 # not in the bitmask, forwarded anyway
|
||||
dev.ci3._read_pv.mock_data = 42
|
||||
dev.di1._read_pv.mock_data = 0
|
||||
dev.di2._read_pv.mock_data = 1
|
||||
dev.ai0_epics._read_pv.mock_data = 5.0
|
||||
dev.ai1_epics._read_pv.mock_data = 99.0
|
||||
dev.ci3_epics._read_pv.mock_data = 42
|
||||
dev.di2_epics._read_pv.mock_data = 1
|
||||
dev.energy_epics._read_pv.mock_data = 8000.0
|
||||
dev.enc_epics._read_pv.mock_data = 12.5
|
||||
|
||||
scan_info_mock.scan_name = "xas_simple_scan"
|
||||
dev.scan_parameters = scan_info_mock
|
||||
with mock.patch.object(dev.scan_info, "msg", SimpleNamespace(status="open")):
|
||||
# not staged -> no forwarding
|
||||
dev.read()
|
||||
assert dev.mean.get() is None
|
||||
assert dev.ai0.get() is None # not staged
|
||||
|
||||
dev._staged = Staged.yes
|
||||
# backend-driven scan -> no forwarding (the hardware stream owns the groups)
|
||||
dev.read()
|
||||
assert dev.mean.get() is None
|
||||
assert dev.ai0.get() is None # backend-driven scan -> hardware path owns the groups
|
||||
|
||||
# software-triggered scan -> ALL channels forwarded under the unified names
|
||||
scan_info_mock.scan_name = "line_scan"
|
||||
dev.read()
|
||||
out = dev.mean.get()
|
||||
assert len(out.signals) == 26 # all 8 ai + 18 ci, bitmask ignored
|
||||
assert out.signals["nidaq_mean_ai0"]["value"] == 5.0
|
||||
assert out.signals["nidaq_mean_ai1"]["value"] == 99.0
|
||||
assert out.signals["nidaq_mean_ci3"]["value"] == 42
|
||||
assert out.metadata["acquisition_group"] == "monitored"
|
||||
out_max = dev.max.get()
|
||||
assert len(out_max.signals) == 5
|
||||
assert out_max.signals["nidaq_max_di1"]["value"] == 0
|
||||
assert out_max.signals["nidaq_max_di2"]["value"] == 1
|
||||
# energy and encoder are live standby readings -> stream group
|
||||
out_stream = dev.stream.get()
|
||||
assert set(out_stream.signals) == {"nidaq_stream_energy", "nidaq_stream_enc"}
|
||||
assert out_stream.signals["nidaq_stream_energy"]["value"] == 8000.0
|
||||
assert out_stream.signals["nidaq_stream_enc"]["value"] == 12.5
|
||||
assert dev.ai0.get().signals["nidaq_ai0_mean"]["value"] == 5.0
|
||||
assert dev.ai1.get().signals["nidaq_ai1_mean"]["value"] == 99.0
|
||||
assert dev.ci3.get().signals["nidaq_ci3_mean"]["value"] == 42
|
||||
assert dev.ai0.get().metadata["acquisition_group"] == "monitored"
|
||||
assert dev.di2_max.get().signals["nidaq_di2_max"]["value"] == 1
|
||||
assert dev.energy.get().signals["nidaq_energy"]["value"] == 8000.0
|
||||
assert dev.enc.get().signals["nidaq_enc"]["value"] == 12.5
|
||||
# no software source for standard deviations
|
||||
assert dev.std_dev.get() is None
|
||||
|
||||
# scan not open -> no forwarding
|
||||
dev.ai0._read_pv.mock_data = 7.0
|
||||
with mock.patch.object(dev.scan_info, "msg", SimpleNamespace(status="closed")):
|
||||
dev.read()
|
||||
assert dev.mean.get().signals["nidaq_mean_ai0"]["value"] == 5.0
|
||||
assert "nidaq_ai0_std_dev" not in dev.ai0.get().signals
|
||||
dev._staged = Staged.no
|
||||
|
||||
|
||||
def test_guarded_put_reports_unknown_names_once(mock_nidaq, scan_info_mock):
|
||||
"""An undeclared channel name is dropped and reported once per group per scan."""
|
||||
def test_bridge_routes_backend_names_to_channel_groups(mock_nidaq):
|
||||
"""Backend messages (both RLE and bare no-RLE names) land under the final names."""
|
||||
from bec_lib import messages
|
||||
|
||||
dev = mock_nidaq
|
||||
dev._guarded_group_put("mean", {"bogus": {"value": [1.0], "timestamp": 1.0}})
|
||||
assert dev.mean.get() is None
|
||||
assert "mean" in dev._group_put_errors
|
||||
# staging for the next scan resets the reporting
|
||||
msg = messages.DeviceMessage(
|
||||
signals={
|
||||
"nidaq_ai0_mean": {"value": [1.0, 2.0]},
|
||||
"nidaq_ai0_std_dev": {"value": [0.1, 0.2]},
|
||||
"ci3": {"value": [7.0]}, # bare no-RLE name -> mean
|
||||
"di2_max": {"value": [1.0]},
|
||||
"energy": {"value": [8000.0]},
|
||||
"xrd_ai0": {"value": [3.0]},
|
||||
"not_a_channel": {"value": [9.0]},
|
||||
},
|
||||
metadata={"async_update": {"type": "add_slice", "index": 4, "max_shape": [None, 57]}},
|
||||
)
|
||||
dev._forward_backend_message(msg)
|
||||
assert dev.ai0.get().signals["nidaq_ai0_mean"]["value"] == [1.0, 2.0]
|
||||
assert dev.ai0.get().signals["nidaq_ai0_std_dev"]["value"] == [0.1, 0.2]
|
||||
assert dev.ai0.get().metadata["async_update"]["type"] == "add_slice"
|
||||
assert dev.ci3.get().signals["nidaq_ci3_mean"]["value"] == [7.0]
|
||||
assert dev.di2_max.get().signals["nidaq_di2_max"]["value"] == [1.0]
|
||||
assert dev.energy.get().signals["nidaq_energy"]["value"] == [8000.0]
|
||||
assert dev.xrd_ai0.get().signals["nidaq_xrd_ai0"]["value"] == [3.0]
|
||||
assert "not_a_channel" in dev._unknown_bridge_channels
|
||||
|
||||
|
||||
def test_guarded_put_reports_unknown_names_once(mock_nidaq, scan_info_mock):
|
||||
dev = mock_nidaq
|
||||
dev._guarded_group_put("ai0", {"bogus": {"value": [1.0], "timestamp": 1.0}})
|
||||
assert dev.ai0.get() is None
|
||||
assert "ai0" in dev._group_put_errors
|
||||
scan_info_mock.scan_name = "line_scan"
|
||||
dev.scan_info.msg.info.update(scan_info_mock.model_dump())
|
||||
dev.on_stage() # returns early: not a backend-driven scan
|
||||
assert dev._group_put_errors == set()
|
||||
|
||||
|
||||
def test_group_put_notifies_subscribers(mock_nidaq):
|
||||
"""The device server publishes to the per-signal device_async_signal endpoint
|
||||
from BECMessageSignal subscriptions; a put must notify subscribers."""
|
||||
dev = mock_nidaq
|
||||
received = []
|
||||
dev.mean.subscribe(lambda *a, **kw: received.append(kw.get("value")), run=False)
|
||||
dev._guarded_group_put("mean", {"ai0": {"value": 5.0, "timestamp": 1.0}})
|
||||
assert len(received) == 1
|
||||
assert received[0].signals["nidaq_mean_ai0"]["value"] == 5.0
|
||||
|
||||
|
||||
def test_device_manager_is_an_explicit_init_parameter():
|
||||
"""The device server injects device_manager only if it is in the signature;
|
||||
without it the unified bridge has no connector and silently never starts."""
|
||||
|
||||
Reference in new issue
Block a user