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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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