Source code for ophyd_async.epics.testing._devices

from collections.abc import Sequence
from pathlib import Path
from typing import Annotated as A

import numpy as np

from ophyd_async.core import (
    Array1D,
    SignalR,
    SignalRW,
    SignalW,
    StrictEnum,
    SubsetEnum,
    SupersetEnum,
    Table,
    TriggerableCommand,
)
from ophyd_async.epics.core import (
    EpicsDevice,
    PvSuffix,
)

CA_PVA_RECORDS = Path(__file__).parent / "_epics_test_ca_records.db"
PVA_RECORDS = Path(__file__).parent / "_epics_test_pva_records.db"


[docs] class EpicsTestEnum(StrictEnum): """For testing strict enum values in test IOCs.""" A = "Aaa" B = "Bbb" C = "Ccc"
[docs] class EpicsTestSubsetEnum(SubsetEnum): """For testing subset enum values in test IOCs. The backing PV (mbbo with choices Aaa/Bbb/Ccc) has an extra choice ("Ccc") beyond what this enum declares. """ A = "Aaa" B = "Bbb"
[docs] class EpicsTestSupersetEnum(SupersetEnum): """For testing superset enum values in test IOCs. Deliberately points at the same backing PV as `EpicsTestSubsetEnum` (mbbo with choices Aaa/Bbb/Ccc): this enum declares a choice ("Ddd") that does not exist in the backend, which is exactly what a superset enum allows. """ A = "Aaa" B = "Bbb" C = "Ccc" D = "Ddd"
[docs] class EpicsTestTable(Table): a_bool: Array1D[np.bool_] a_int: Array1D[np.int32] a_float: Array1D[np.float64] a_str: Sequence[str] a_enum: Sequence[EpicsTestEnum]
[docs] class EpicsTestCaDevice(EpicsDevice): """Device for use in a channel access test IOC.""" a_int: A[SignalRW[int], PvSuffix("int")] """A thing""" a_float: A[SignalRW[float], PvSuffix("float")] float_prec_0: A[SignalRW[int], PvSuffix("float_prec_0")] a_str: A[SignalRW[str], PvSuffix("str")] longstr: A[SignalRW[str], PvSuffix("longstr")] longstr2: A[SignalRW[str], PvSuffix("longstr2.VAL$")] a_bool: A[SignalRW[bool], PvSuffix("bool")] slowseq: A[SignalRW[int], PvSuffix("slowseq")] enum: A[SignalRW[EpicsTestEnum], PvSuffix("enum")] enum2: A[SignalRW[EpicsTestEnum], PvSuffix("enum2")] subset_enum: A[SignalRW[EpicsTestSubsetEnum], PvSuffix("subset_enum")] superset_enum: A[SignalRW[EpicsTestSupersetEnum], PvSuffix("subset_enum")] enum_str_fallback: A[SignalRW[str], PvSuffix("enum_str_fallback")] bool_unnamed: A[SignalRW[bool], PvSuffix("bool_unnamed")] partialint: A[SignalRW[int], PvSuffix("partialint")] lessint: A[SignalRW[int], PvSuffix("lessint")] uint8a: A[SignalRW[Array1D[np.uint8]], PvSuffix("uint8a")] int16a: A[SignalRW[Array1D[np.int16]], PvSuffix("int16a")] int32a: A[SignalRW[Array1D[np.int32]], PvSuffix("int32a")] float32a: A[SignalRW[Array1D[np.float32]], PvSuffix("float32a")] float64a: A[SignalRW[Array1D[np.float64]], PvSuffix("float64a")] stra: A[SignalRW[Sequence[str]], PvSuffix("stra")] mbb_direct_bit_r: A[SignalR[bool], PvSuffix("mbb_direct.B0")] mbb_direct_bit: A[SignalRW[bool], PvSuffix("mbb_direct_rw.B0")] go: A[TriggerableCommand, PvSuffix("go")]
[docs] class EpicsTestPvaDevice(EpicsTestCaDevice): """Device for use in a pv access test IOC.""" # pva can support all signal types that ca can int8a: A[SignalRW[Array1D[np.int8]], PvSuffix("int8a")] uint16a: A[SignalRW[Array1D[np.uint16]], PvSuffix("uint16a")] uint32a: A[SignalRW[Array1D[np.uint32]], PvSuffix("uint32a")] int64a: A[SignalRW[Array1D[np.int64]], PvSuffix("int64a")] uint64a: A[SignalRW[Array1D[np.uint64]], PvSuffix("uint64a")] table: A[SignalRW[EpicsTestTable], PvSuffix("table")] ntndarray: A[SignalR[np.ndarray], PvSuffix("ntndarray")]
[docs] class EpicsTestPviDevice(EpicsDevice): """Device for use in a generic PVI test IOC, independent of PandA/FastCS. Construct with `with_pvi=True`: signals here are plain type annotations with no `PvSuffix`, discovered and filled in from the IOC's PVI structure by `PviDeviceConnector` at connect time. Most fields (`mbb_direct_bit_r`, `a_float`, `table`, `ntndarray`, `go`) reuse the same attribute names, PVI entries and backing records as `EpicsTestCaDevice`/`EpicsTestPvaDevice`, rather than declaring new synthetic ones. Only `wo_float` is bespoke to this device, as no write-only field exists on those devices to reuse. """ mbb_direct_bit_r: SignalR[bool] a_float: SignalRW[float] wo_float: SignalW[float] table: SignalRW[EpicsTestTable] ntndarray: SignalR[np.ndarray] go: TriggerableCommand
[docs] class EpicsTestPviDisagreeingSuffixDevice(EpicsDevice): """Device whose `PvSuffix` contradicts the PVI structure serving it. `overridden_float` is annotated at the `float_prec_1` record, but the IOC's PVI directory points it at the same record as `a_float`. Two different PVs for one Signal means one of them is wrong, so connecting must raise rather than silently pick either. Construct with `with_pvi=True`. It is a device of its own because it cannot connect: a disagreeing Signal would otherwise stop `EpicsTestPviDevice` connecting at all. """ overridden_float: A[SignalRW[float], PvSuffix("float_prec_1")]