Source code for instro.psu.config

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator

from instro.lib.config import (
    FilePublisherConfig,
    NominalCorePublisherConfig,
    PublisherConfigType,
    TimingConfig,
    build_publisher,
)
from instro.lib.registry import DriverEntry, IdnPattern
from instro.lib.transports.visa import VisaConfig
from instro.lib.types import DeviceInfo

if TYPE_CHECKING:
    from instro.lib.publishers import Publisher
    from instro.psu.psu import PSUDriverBase

__all__ = [
    "DeviceInfo",
    "FilePublisherConfig",
    "NominalCorePublisherConfig",
    "PSUConfig",
    "TimingConfig",
    "VisaDriverConfig",
]

PSU_VENDOR_REGISTRY: dict[str, DriverEntry] = {
    "BK9115": DriverEntry("instro.psu.drivers.bk_9115.BK9115", (IdnPattern(("B&K PRECISION",), r"^9115", 1),)),
    "BK914X": DriverEntry("instro.psu.drivers.bk_914x.BK914X", (IdnPattern(("B&K PRECISION",), r"^914\d", 3),)),
    # E36100, N5700 and Genesys patterns follow the programming manuals; not yet confirmed on hardware.
    "KeysightE36100": DriverEntry(
        "instro.psu.drivers.keysight_e36100.KeysightE36100",
        (IdnPattern(("KEYSIGHT TECHNOLOGIES", "AGILENT TECHNOLOGIES"), r"^E361\d\d", 1),),
    ),
    "KeysightN5700": DriverEntry(
        "instro.psu.drivers.keysight_n5700.KeysightN5700",
        (IdnPattern(("KEYSIGHT TECHNOLOGIES", "AGILENT TECHNOLOGIES"), r"^N57\d\d", 1),),
    ),
    "RigolDP800": DriverEntry(
        "instro.psu.drivers.rigol_dp800.RigolDP800",
        (
            IdnPattern(("RIGOL TECHNOLOGIES",), r"^DP811", 1),
            IdnPattern(("RIGOL TECHNOLOGIES",), r"^DP821", 2),
            IdnPattern(("RIGOL TECHNOLOGIES",), r"^DP83[12]", 3),
        ),
    ),
    # CH3 has no SCPI voltage/current access, so discovery reports 2 programmable channels (#406).
    "SiglentSPD3303": DriverEntry(
        "instro.psu.drivers.siglent_spd3303.SiglentSPD3303",
        (IdnPattern(("SIGLENT TECHNOLOGIES",), r"^SPD3303", 2),),
    ),
    "SimulatedPSU": DriverEntry("instro.psu.drivers.simulated.SimulatedPSU"),
    "TDKLambdaGenesys": DriverEntry(
        "instro.psu.drivers.tdk_lambda_genesys.TDKLambdaGenesys",
        (IdnPattern(("LAMBDA",), r"^GEN", 1),),
    ),
}


[docs] class VisaDriverConfig(BaseModel): """Driver config for a VISA-connected PSU.""" model_config = ConfigDict(extra="forbid") connection_type: Literal["visa"] = "visa" name: str = Field(description="PSU vendor/model key.") num_channels: int = Field(ge=1, description="Number of output channels.") visa: VisaConfig @field_validator("name") @classmethod def name_must_be_registered(cls, v: str) -> str: if v not in PSU_VENDOR_REGISTRY: raise ValueError(f"unknown driver {v!r}") return v
[docs] class PSUConfig(BaseModel): """Validated config for constructing an InstroPSU from JSON.""" model_config = ConfigDict(extra="forbid") version: Literal[1] = 1 instrument: Literal["InstroPSU"] = "InstroPSU" device: DeviceInfo driver: VisaDriverConfig timing: TimingConfig | None = None publishers: list[PublisherConfigType] = Field(default_factory=list)
[docs] def resolve_psu_from_config( config: PSUConfig, ) -> tuple[str, PSUDriverBase, int, list[Publisher], float | None]: """Resolve a validated PSUConfig into the ``(name, driver, num_channels, config_publishers, poll_interval)`` InstroPSU needs.""" driver_cls = PSU_VENDOR_REGISTRY[config.driver.name].load() driver: PSUDriverBase = driver_cls(config.driver.visa) # type: ignore[call-arg] config_publishers = [build_publisher(p) for p in config.publishers] poll_interval = config.timing.poll_interval if config.timing is not None else None return config.device.name, driver, config.driver.num_channels, config_publishers, poll_interval