Source code for pyisyox.schema.nodedef

"""Node definition dataclasses for IoX devices.

A :class:`NodeDef` describes the static behaviour of a class of nodes: which
properties exist, which commands are accepted/sent, and which links the node
supports. The same shape applies to native Insteon/Z-Wave nodedefs and PG3
plugin nodedefs — there is no plugin-only field. The structural
key into the lookup table is ``(nodedef_id, family_id, instance_id)``.

A live :class:`Property` value (raw + formatted + uom) is reported by the
controller via ``/api/nodes`` (for native nodes), ``/rest/status`` (the
canonical full table), or WebSocket event frames; it is *not* part of the
nodedef and is kept here only as a separate dataclass.

Source schema: ``/rest/profiles`` instance ``nodedefs[]``.
"""

from __future__ import annotations

from dataclasses import dataclass, field

from pyisyox.schema.cmd import Command


[docs] @dataclass(slots=True) class Property: """A live property value reported by the controller for a node. Attributes: id: Property id (e.g. ``"ST"``, ``"GV1"``). value: Raw value as reported by the controller (string form keeps controller-emitted precision). formatted: Human-readable value (e.g. ``"0.6839 US gallons"``). uom: Unit-of-measure id reported alongside the value. prec: Decimal precision applied to ``value`` (None when not provided). name: Optional display name override (often empty — the nodedef-level ``NodeProperty.name`` is the authoritative label). """ id: str value: str formatted: str = "" uom: str = "" prec: int | None = None name: str = ""
[docs] @dataclass(slots=True) class NodeProperty: """A property slot defined on a nodedef. Attributes: id: Property id (e.g. ``"ST"``, ``"OL"``, ``"CLISPC"``, ``"GV1"``). editor_id: Reference to the editor governing this property's display and (where applicable) write-side validation. name: Human-readable label, inline-resolved by the controller (e.g. ``"Current"`` for Flume's ``GV1``, ``"On Level"`` for Insteon's ``OL``). Authoritative source. hide: Hint that the property should not be surfaced in default UIs. """ id: str editor_id: str name: str = "" hide: bool = False
[docs] @dataclass(slots=True) class NodeCommands: """Commands a nodedef sends and accepts. Attributes: sends: Commands the node *emits* — useful as trigger sources (e.g. ``OnOffControl`` sends ``DON``/``DOF`` on physical press). accepts: Commands the node *receives* — drive the node's controllable HA platform (light/switch/climate/lock/cover/button). """ sends: list[Command] = field(default_factory=list) accepts: list[Command] = field(default_factory=list)
[docs] @dataclass(slots=True) class NodeDef: """The static definition of a node class. Attributes: id: Nodedef identifier (e.g. ``"KeypadDimmer_ADV"``, ``"Thermostat"``, ``"flume2"``, ``"controller"``). family_id: Family id this nodedef belongs to (``"1"`` for Insteon, ``"4"`` for Z-Wave, plugin slot id for PG3 nodedefs). instance_id: Instance id within the family (typically equal to ``family_id`` for built-in families and equal to the plugin slot for PG3 instances). name: Default display name (the ``NDN-<nls>-NAME`` NLS entry). Often empty — the live node carries a user-assigned name; this is just the discovery-time default. ``/rest/profiles`` families resolve it inline; for dynamic Z-Wave nodedefs pyisyox fills it from the family NLS table. properties: Property slots, keyed by property id. cmds: Sent and accepted commands. nls_key: Reference key into the NLS string table (e.g. ``"flume2"``); pyisyox does not need to resolve this — every visible string is already inline-resolved in property/command ``name`` fields and in WS event frames. links: Control and response link references. """ id: str family_id: str instance_id: str name: str = "" properties: dict[str, NodeProperty] = field(default_factory=dict) cmds: NodeCommands = field(default_factory=NodeCommands) nls_key: str | None = None links: NodeLinks = field(default_factory=NodeLinks)
[docs] @classmethod def from_json(cls, raw: dict, family_id: str, instance_id: str) -> NodeDef: """Build a :class:`NodeDef` from a JSON object scoped to its family/instance.""" props_in = raw.get("properties", []) or [] properties: dict[str, NodeProperty] = {} for p in props_in: pid = p.get("id") if not pid: continue properties[pid] = NodeProperty( id=pid, editor_id=p.get("editor", ""), name=p.get("name", ""), hide=bool(p.get("hide", False)), ) cmds_in = raw.get("cmds", {}) or {} cmds = NodeCommands( sends=[Command.from_json(c) for c in cmds_in.get("sends", []) or []], accepts=[Command.from_json(c) for c in cmds_in.get("accepts", []) or []], ) def _link_ids(items: list) -> list[str]: return [ln.get("id", "") if isinstance(ln, dict) else str(ln) for ln in items or []] links_in = raw.get("links", {}) or {} links = NodeLinks( ctl=_link_ids(links_in.get("ctl", [])), rsp=_link_ids(links_in.get("rsp", [])), ) return cls( id=raw["id"], family_id=family_id, instance_id=instance_id, name=raw.get("name", ""), properties=properties, cmds=cmds, nls_key=raw.get("nls"), links=links, )
@property def lookup_key(self) -> tuple[str, str, str]: """The ``(nodedef_id, family_id, instance_id)`` join key used to match a node from ``/api/nodes`` to its definition. """ return (self.id, self.family_id, self.instance_id)