Skip to content

Example 4 — SCPI device: annotated attributes + per-attribute filler data #405

Description

@coretl

Part of the tutorial examples (#388). Tutorial 3 — declarative: a NON-introspectable device whose attributes are declared as annotated hints, with each attribute's getter/setter provisioned from per-attribute filler metadata (extras mechanism, #388 decision 3) via ControllerFiller.

Runs on the temperature-controller backend — its text protocol is already SCPI-shaped, so no new sim is needed and, crucially, no introspection is invented. A SCPI device does not describe itself; that is why you hand-annotate, and keeping it non-introspectable preserves the contrast with the introspectable Eiger tutorial (#391).

This tutorial also carries composition + methods (the old "io= pattern" tutorial is gone): it walks the full multi-ramp temperature controller — ramp sub-controllers / ControllerVector, the voltage @scan, the cancel-all @command — declared via SCPIParam, drawing on the composition example (controllers.py, #390).

Scope

  • Add src/fastcs/demo/scpi.py: an example SCPIController + SCPIParam extras vocabulary. This lives in the demo (protocol) layer, NOT core FastCS (decision 3: core ships no extras vocabulary for 1.0) — it demonstrates how a third party builds a protocol layer on the filler's (child, extras) yield.
  • Add src/fastcs/demo/temperature_scpi.py: the full temperature controller as annotated hints — Annotated[AttrRW[float], SCPIParam("P", precision=3, units="W", description=…)] — with the SCPIController's ControllerFiller building each attribute's getter/setter (and applying .meta) from that static metadata; plus sub-controllers/vector + @scan + @command.
  • Unit tests against the sim, including the negative case below.

SCPIParam — one spec object per attribute (ADR 0014)

The canonical demo of the common declarative spelling.

  • SCPIParam(param: str, **kwargs: Unpack[Meta]) (Meta = the superset TypedDict) is the single place an attribute's whole spec is written: the command token plus all generic metadata (description, precision, units, limits…). It stores a .meta.
  • Exclusive spec source for its attribute — the filler does not also merge a separate FloatMeta/Meta extra on the same hint.
  • The filler builds the getter/setter and passes .meta into the constructed attribute, runtime-validating it against the datatype.
  • Named SCPIParam (a binding extra you instantiate, sibling of PvSuffix), not SCPIMeta — the *Meta suffix is reserved for the Unpack-able TypedDicts.
  • Show a valid case, and (in a test) that supplying precision for a str attribute raises with a message naming the attribute + field.

Acceptance

uv run --locked tox -e pre-commit,type-checking,tests green.

ADR: docs/explanations/decisions/0013-declarative-procedural-split-and-controller-filler.md, 0014
Parent: #388
Blocked by: #394

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions