pyisyox.runtime.node module¶
Runtime Node — NodeRecord + NodeDef + client.
The primary user-facing device handle. Exposes structural fields,
current properties (updated in place by the WS dispatcher), and
Node.send_command() for editor-validated command dispatch.
Commands go through the legacy /rest/nodes/{addr}/cmd/... XML
surface — no /api/* equivalent has been observed.
- class Node(record, nodedef, profile, client)[source]¶
Bases:
objectUser-facing handle around one node from a
LoadResult.Construct via
Node.from_record()rather than the bare constructor so the editor resolver and nodedef are wired automatically from the parsedProfile.- Parameters:
record (NodeRecord)
nodedef (NodeDef | None)
profile (Profile)
client (IoXClient)
- classmethod from_record(record, profile, client)[source]¶
Resolve the nodedef for
recordand construct a Node.- Parameters:
record (NodeRecord)
profile (Profile)
client (IoXClient)
- Return type:
- property type: str¶
IoX type triple, e.g.
"1.65.69.0"for KeypadLinc dimmer.Plugin nodes carry a placeholder (Flume reports
"1.2.3.4"); consumers should not rely on it for plugin classification — usenodedefinstead.
- property parent_address: str | None¶
Tree-hierarchy parent (containing folder).
Noneat root.Distinct from
primary_address:<parent>is the folder,<pnode>is the device primary for multi-button hardware.
- property primary_address: str | None¶
Device primary for sub-button nodes (from
<pnode>).Sub-buttons of multi-button devices (KeypadLinc, RemoteLinc, FanLinc) carry the primary’s address.
Nonefor primaries — soprimary_address is not Nonereads as “sub-node” andprimary_address or addressas the device-grouping address.
- property properties: dict[str, NodePropertyValue]¶
Live property values, keyed by property id (e.g.
"ST").Each value is UOM-normalised to its nodedef editor’s canonical unit — e.g. an Insteon dimmer reporting
OLas a UOM-100 0-255 byte is surfaced as the UOM-51 0-100% theI_OLeditor (and the/cmdwrite surface) uses. Values already matching the editor pass through unchanged.
- property status: NodePropertyValue | None¶
Shortcut for
properties[PROP_STATUS]— the node’s primary status reading ("ST"), UOM-normalised the same waypropertiesis.Returns
Nonewhen the node hasn’t reported ST yet (common for write-only Insteon controllers and plugin nodes that don’t advertise ST). Consumers that want a scalar should readnode.status.value(a string) and parse it themselves; the property keeps the structured shape so callers can also reach.uom,.formatted, etc.
- property flag: int¶
Raw node-flag bitfield from the controller’s node table.
Bit meanings live in
pyisyox.constants.NodeFlag(NEW,IN_ERR,DEVICE_ROOT, …). Usehas_flag()for individual bit checks rather than reading this directly. Returns0when the controller didn’t carry a value for this node — treat0as “no bits set” rather than “unknown”.
- has_flag(flag)[source]¶
Return
Trueif every bit inflagis set on this node.flagmay be OR’d; combined values must have every bit set.
- property protocol: Protocol¶
Transport-protocol classification from
family_id.Returns
NODE_SERVERfor any non-core family id (PG3 plugin nodes report a slot id here),UNKNOWNfor recognised but unmapped core families. Classifies transport, not device class — useis_thermostatetc. for capability.
- property is_lock: bool¶
True for door/deadbolt locks.
Two tells: nodedef accepts
SECMD(Z-Wave / Insteon I2CS), or nodedef id contains"Lock"(IoX 6+DoorLockvariants that drive viaDON/DOF).
- property is_fan: bool¶
True for multi-speed fan controllers (nodedef id contains
"Fan").Fan nodes are a subset of dimmable (
FanLincMotoracceptsDONwith a{0, 25, 75, 100}subset), so platform classification should checkis_fanbeforeis_dimmable.
- property is_dimmable: bool¶
True if the node has a multilevel
STstate and accepts a parameterizedDON.Three conditions must all hold for a real dimmer:
STeditor reports a multilevel range (not a binary{0, 100}subset). Relay nodedefs acceptDONwith an ignored level param, soDON’s editor alone is unreliable —STis the source of truth for “can the node hold a non-binary level”.The nodedef accepts
DON. Some Insteon nodedefs (RemoteLinc2_ADV scene buttons, IMETER_SOLO meters) carry a multilevelSTeditor — meaningful for the device’s own bookkeeping — but only acceptWDU/QUERY, so they can’t actually be commanded on. Without this checkis_dimmablereturns True for them and consumers route them onto the LIGHT platform where DON-based turn_on silently fails.The accepted
DONdeclares at least one parameter (the on-level). HA’s light platform sets brightness withDON <level>; a parameterlessDONcannot take one, so the node is on/off-only even with a multilevelST(some node-server nodedefs set level via a separateSETST/SETOLcommand instead — issue #64 / Virtual#11). Real Insteon/Z-Wave dimmers declareDONwith an optional on-level param, so they still qualify.
- property is_battery_node: bool¶
True if the node reports
BATLVLbut noST.Battery-powered Insteon sensors (motion, leak, open/close) match this — they have no on/off primary state.
- property zwave_props: ZWaveProperties | None¶
Parsed
ZWavePropertiesfor Z-Wave / Z-Matter nodes;Nonefor Insteon and other families.
- async send_command(command_id, *params)[source]¶
Send a command, with editor-codec parameter validation.
Each parameter is sent as
/{value}/{uom}using the UOM its editor declares (the eisy web-UI convention —/cmd/DON/75/51). Parameters whose editor carries no real unit (UOM"0"or unset) are sent bare.When the node has no resolved nodedef (dynamically provisioned Z-Wave/Z-Matter nodes whose
UZW*defs aren’t in/rest/profiles), params pass through verbatim (numeric → int, no UOM) so the node stays controllable without validation.
- async set_climate_mode(mode)[source]¶
Set HVAC mode. Accepts enum names (
"Heat","Cool","Auto","Program Auto", …) or raw ints. The editor forCLIMDenforces subset membership (e.g. excludes"Fan Only"on devices that don’t support it).
- async set_climate_setpoint_heat(val)[source]¶
Set the heat setpoint. The codec scales by
prec(or doubles for legacy UOM-101 half-degree editors).- Parameters:
val (float)
- Return type:
None
- async set_climate_setpoint_cool(val)[source]¶
Set the cool setpoint.
- Parameters:
val (float)
- Return type:
None
- async set_fan_mode(mode)[source]¶
Set fan mode. Accepts enum names (
"Auto","On","Auto High", …) or raw ints.
- async set_on_level(val)[source]¶
Set the remembered on-level via
OL(0-100 percent).- Parameters:
val (int)
- Return type:
None
- async set_ramp_rate(val)[source]¶
Set the device’s ramp rate.
Insteon: 0-31 index into the IoX ramp-rate table. Z-Wave: seconds. The editor enforces the per-device range.
- Parameters:
val (int)
- Return type:
None
- async set_backlight(val)[source]¶
Set keypad/switch backlight intensity.
Two encodings driven by the BL editor’s UOM: UOM 100 → 0-100%, UOM 25 → integer index (or enum-name string the codec resolves).
- async start_manual_dimming()[source]¶
Begin manual dimming (legacy Insteon
BMAN).The IoX docs prefer the
FADE_*family for new code.- Return type:
None
- async rename(name)[source]¶
Rename this node. The controller emits a
_3lifecycle frame withaction="NN"on success.- Parameters:
name (str)
- Return type:
None
- async get_zwave_parameter(number)[source]¶
Request parameter
number; return{parameter, size, value}.Family id picks the wire prefix (
"4"→/rest/zwave/...,"12"→/rest/zmatter/zwave/...). RaisesNodeCommandErroron non-Z-Wave nodes or controller failure;ISYResponseParseErroron malformed bodies.
- async set_zwave_parameter(number, value, size)[source]¶
Write parameter
number(size 1/2/4 bytes) on this Z-Wave node.The post-write report arrives asynchronously on the WS stream. Raises
NodeCommandErroron rejection so failures aren’t silent.
- async set_zwave_lock_code(user_num, code)[source]¶
Program a Z-Wave lock’s user-code slot. Raises
NodeCommandErroron a failed envelope.
- exception NodeCommandError[source]¶
Bases:
ExceptionRaised when a command can’t be sent — unknown command id, missing parameter, validation failure, or no nodedef resolved for this node.
Defined here (not in
node.py) to keep the module dependency one-way:node.pyimports from_commands.py, never the reverse.