Source code for ophyd_async.epics.testing._pvi_nested_devices

from pathlib import Path
from typing import Annotated as A

from ophyd_async.core import (
    Device,
    DeviceMap,
    DeviceVector,
    SignalRW,
    TriggerableCommand,
)
from ophyd_async.epics.core import EpicsDevice, PvSuffix

PVI_NESTED_RECORDS = Path(__file__).parent / "_pvi_nested_records.db"


[docs] class EpicsTestPviLeafDevice(Device): """Leaf sub-device for `child` and `device_vector` on EpicsTestPviNestedDevice.""" signal_rw: SignalRW[int] signal_x: TriggerableCommand
[docs] class EpicsTestPviNestedDevice(EpicsDevice): """Real-IOC-backed structural PVI test device. Exercises PviDeviceConnector's handling of nested sub-devices, DeviceVector of devices, DeviceVector of signals, DeviceVector of commands, and an Optional signal absent from the served tree -- the structural mechanics that tests/unit_tests/epics/pvi/test_pvi.py's Block1-Block5 classes exercise only against a *mocked* PVI tree. Construct with `with_pvi=True`. The served PVI tree also includes an `extra_devices` DeviceVector that is deliberately *not* annotated here, to prove that PviDeviceConnector fills in undeclared DeviceVectors of devices dynamically. Legacy list-based vector encoding (`PviTree._handle_legacy_entry`) is deliberately not covered here: it's exclusively produced by pandablocks-ioc's own hand-rolled PVA server, not something a QSRV2/db-file-based IOC can construct declaratively, so it stays covered by the existing mock-only unit test instead. """ signal_rw: SignalRW[int] signal_x: TriggerableCommand child: EpicsTestPviLeafDevice device_vector: DeviceVector[EpicsTestPviLeafDevice] signal_vector: DeviceVector[SignalRW[float]] command_vector: DeviceVector[TriggerableCommand] optional_signal: SignalRW[int] | None
[docs] class EpicsTestPviMapDevice(EpicsDevice): """Real-IOC-backed `DeviceMap` test device, served under the `mapd:` group. Proves that a `DeviceMap` is created only from an explicit `DeviceMap[...]` annotation and filled from a node's *normal* named entries, keyed by name (`signal_map` -> `{"a", "b"}`, `device_map` -> `{"one", "two"}`), with each entry type-checked against the map's element type -- the counterpart to `EpicsTestPviNestedDevice`'s integer-keyed `DeviceVector`s. Construct with `with_pvi=True` and a prefix ending `mapd:`. """ signal_map: DeviceMap[SignalRW[float]] device_map: DeviceMap[EpicsTestPviLeafDevice]
[docs] class EpicsTestPviAgreeingPvSuffixDevice(EpicsDevice): """Connects to the same served PVI tree as `EpicsTestPviNestedDevice`. Addresses its children with a `PvSuffix` *as well as* via PVI, naming the same PVs the served tree does. PVI is the one that fills them; the annotations are only cross-checked against it, so connecting succeeds. Construct with `with_pvi=True`. """ signal_rw: A[SignalRW[int], PvSuffix("signal_rw")] child: A[EpicsTestPviLeafDevice, PvSuffix("child:")]
[docs] class EpicsTestPviDisagreeingDeviceDevice(EpicsDevice): """Connects to the same served PVI tree as `EpicsTestPviNestedDevice`. Gives `child` a `PvSuffix` naming a different PVI PV to the served one, so connecting must raise rather than silently take PVI's. The sub-device counterpart of `EpicsTestPviDisagreeingSuffixDevice`, which covers a disagreeing Signal. Construct with `with_pvi=True`. """ child: A[EpicsTestPviLeafDevice, PvSuffix("not_child:")]
[docs] class EpicsTestPviMapOverVectorDevice(EpicsDevice): """Connects to the same served PVI tree as `EpicsTestPviNestedDevice`. Annotates `device_vector` -- which PVI serves as integer-keyed "__N" entries -- as a `DeviceMap`. A `DeviceMap` can only hold the named entries of a node, so its "__N" children have nowhere to go and connecting must raise rather than yield a map that has silently dropped them. Construct with `with_pvi=True`. """ device_vector: DeviceMap[EpicsTestPviLeafDevice]
[docs] class EpicsTestPviNestedDeviceMissingChild(EpicsDevice): """Connects to the same served PVI tree as EpicsTestPviNestedDevice. Declares an extra required (non-Optional) sub-device the server doesn't actually provide -- demonstrates that connecting raises a clear RuntimeError when a required field can't be resolved via PVI (the mirror image of `optional_signal` above, which is allowed to be absent). """ signal_rw: SignalRW[int] missing_child: EpicsTestPviLeafDevice