From c92c40d1ef714ef06ea26e1e684d963f8a370b69 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Thu, 9 Jul 2026 13:34:18 +0200 Subject: [PATCH] refactor(nidaq): replace placeholder signals with DynamicSignal statistic groups --- debye_bec/devices/nidaq/nidaq.py | 179 +++++++++++------------------- tests/tests_devices/test_nidaq.py | 26 +++++ 2 files changed, 90 insertions(+), 115 deletions(-) diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index be52d92..8ac0fc6 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -10,7 +10,7 @@ from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import WaitTimeoutError from ophyd_devices import CompareStatus, ProgressSignal, StatusBase, TransitionStatus from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase -from ophyd_devices.sim.sim_signals import SetableSignal +from ophyd_devices.utils.bec_signals import DynamicSignal from debye_bec.devices.nidaq.nidaq_enums import ( EncoderFactors, @@ -27,6 +27,27 @@ if TYPE_CHECKING: # pragma: no cover logger = bec_logger.logger +#: Channels per statistic group. Group sub-signals are emitted as +#: 'nidaq__' (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)] +NIDAQ_STREAM_CHANNELS = [ + "energy", + "enc", + "smpl_abs", + "smpl_fluo", + "ref_abs", + "cisum", + "rle", + "xas_timestamp", + "xrd_timestamp", + "xrd_angle", + "xrd_energy", + "xrd_ai0_mean", + "xrd_ai0_std_dev", +] + class NidaqError(Exception): """Nidaq specific error""" @@ -36,124 +57,43 @@ class NidaqControl(Device): """Nidaq control class with all PVs""" # fmt: off - ### Readback PVs for EpicsEmitter ### - energy = Cpt(SetableSignal, value=0, kind=Kind.normal) + 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) - smpl_abs = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream sample absorption") - smpl_fluo = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream sample fluorescence") - ref_abs = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream reference absorption") - cisum = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter sum") + 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) - ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN") - ai1_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN") - ai2_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN") - ai3_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN") - ai4_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN") - ai5_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN") - ai6_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN") - ai7_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN") + 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_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX") - di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX") - di2_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 2, MAX") - di3_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 3, MAX") - di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX") + enc_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENC", kind=Kind.omitted, doc="EPICS Encoder reading", auto_monitor=True) - ci0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0, MEAN") - ci1_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1, MEAN") - ci2_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2, MEAN") - ci3_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3, MEAN") - ci4_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4, MEAN") - ci5_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5, MEAN") - ci6_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6, MEAN") - ci7_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN") - ci8_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8, MEAN") - ci9_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9, MEAN") - ci10_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10, MEAN") - ci11_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11, MEAN") - ci12_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12, MEAN") - ci13_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13, MEAN") - ci14_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14, MEAN") - ci15_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15, MEAN") - ci16_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16, MEAN") - ci17_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17, MEAN") - - ai0 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI0", kind=Kind.normal, doc="EPICS analog input 0", auto_monitor=True) - ai1 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI1", kind=Kind.normal, doc="EPICS analog input 1", auto_monitor=True) - ai2 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI2", kind=Kind.normal, doc="EPICS analog input 2", auto_monitor=True) - ai3 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI3", kind=Kind.normal, doc="EPICS analog input 3", auto_monitor=True) - ai4 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI4", kind=Kind.normal, doc="EPICS analog input 4", auto_monitor=True) - ai5 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI5", kind=Kind.normal, doc="EPICS analog input 5", auto_monitor=True) - ai6 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI6", kind=Kind.normal, doc="EPICS analog input 6", auto_monitor=True) - ai7 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI7", kind=Kind.normal, doc="EPICS analog input 7", auto_monitor=True) - - ci0 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI0", kind=Kind.normal, doc="EPICS counter input 0", auto_monitor=True) - ci1 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI1", kind=Kind.normal, doc="EPICS counter input 1", auto_monitor=True) - ci2 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI2", kind=Kind.normal, doc="EPICS counter input 2", auto_monitor=True) - ci3 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI3", kind=Kind.normal, doc="EPICS counter input 3", auto_monitor=True) - ci4 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI4", kind=Kind.normal, doc="EPICS counter input 4", auto_monitor=True) - ci5 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI5", kind=Kind.normal, doc="EPICS counter input 5", auto_monitor=True) - ci6 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI6", kind=Kind.normal, doc="EPICS counter input 6", auto_monitor=True) - ci7 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI7", kind=Kind.normal, doc="EPICS counter input 7", auto_monitor=True) - ci8 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI8", kind=Kind.normal, doc="EPICS counter input 8", auto_monitor=True) - ci9 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI9", kind=Kind.normal, doc="EPICS counter input 9", auto_monitor=True) - ci10 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI10", kind=Kind.normal, doc="EPICS counter input 0", auto_monitor=True) - ci11 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI11", kind=Kind.normal, doc="EPICS counter input 1", auto_monitor=True) - ci12 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI12", kind=Kind.normal, doc="EPICS counter input 2", auto_monitor=True) - ci13 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI13", kind=Kind.normal, doc="EPICS counter input 3", auto_monitor=True) - ci14 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI14", kind=Kind.normal, doc="EPICS counter input 4", auto_monitor=True) - ci15 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI15", kind=Kind.normal, doc="EPICS counter input 5", auto_monitor=True) - ci16 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI16", kind=Kind.normal, doc="EPICS counter input 6", auto_monitor=True) - ci17 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI17", kind=Kind.normal, doc="EPICS counter input 7", auto_monitor=True) - - di0 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI0", kind=Kind.normal, doc="EPICS digital input 0", auto_monitor=True) - di1 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI1", kind=Kind.normal, doc="EPICS digital input 1", auto_monitor=True) - di2 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI2", kind=Kind.normal, doc="EPICS digital input 2", auto_monitor=True) - di3 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI3", kind=Kind.normal, doc="EPICS digital input 3", auto_monitor=True) - di4 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI4", kind=Kind.normal, doc="EPICS digital input 4", auto_monitor=True) - - enc_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENC", kind=Kind.normal, doc="EPICS Encoder reading", auto_monitor=True) - - energy_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENERGY", kind=Kind.normal, doc="EPICS Energy reading", auto_monitor=True) - - ### Readback for BEC emitter ### - ai0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, STD") - ai1_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, STD") - ai2_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, STD") - ai3_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, STD") - ai4_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, STD") - ai5_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, STD") - ai6_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, STD") - ai7_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, STD") - - ci0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD") - ci1_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1. STD") - ci2_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2. STD") - ci3_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3. STD") - ci4_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4. STD") - ci5_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5. STD") - ci6_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6. STD") - ci7_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD") - ci8_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8. STD") - ci9_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9. STD") - ci10_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10. STD") - ci11_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11. STD") - ci12_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12. STD") - ci13_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13. STD") - ci14_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14. STD") - ci15_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15. STD") - ci16_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16. STD") - ci17_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17. STD") - - xas_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XAS timestamp") - xrd_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD timestamp") - xrd_angle = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD angle") - xrd_energy = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD energy") - xrd_ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 mean") - xrd_ai0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 std dev") - - enc = Cpt(SetableSignal, value=0, kind=Kind.normal) - rle = Cpt(SetableSignal, value=0, kind=Kind.normal) + energy_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENERGY", kind=Kind.omitted, doc="EPICS Energy reading", auto_monitor=True) ### Control PVs ### @@ -218,6 +158,15 @@ class Nidaq(PSIDeviceBase, NidaqControl): progress_signal = Cpt(ProgressSignal, name="progress_signal") + # fmt: off + ### Statistic groups: emitted as nidaq__ 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") + # fmt: on + USER_ACCESS = ["set_config"] def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs): diff --git a/tests/tests_devices/test_nidaq.py b/tests/tests_devices/test_nidaq.py index 85057a2..84426e9 100644 --- a/tests/tests_devices/test_nidaq.py +++ b/tests/tests_devices/test_nidaq.py @@ -182,3 +182,29 @@ def test_on_complete(mock_nidaq, scan_info_mock): dev.stop() status.wait(timeout=5) assert status.done is True + + +########## DynamicSignal statistic groups ########## + +from ophyd import Kind +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. + """ + 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 dev.enc_epics.kind == Kind.omitted + assert "nidaq_ai0" not in dev.read()