pyisyox.runtime.node module

Runtime NodeNodeRecord + 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: object

User-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 parsed Profile.

Parameters:
classmethod from_record(record, profile, client)[source]

Resolve the nodedef for record and construct a Node.

Parameters:
Return type:

Node

property address: str

Wire address — e.g. "3D 7D 87 1" or "n010_84dd4c2c24c3b7".

property name: str

User-assigned label (set in eisy admin UI).

property nodedef_id: str

The nodedef id (e.g. "KeypadDimmer_ADV", "flume2").

property family_id: str

Family id — "1" for native Insteon/Z-Wave, slot id for plugins.

property instance_id: str

Instance id within the family.

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 — use nodedef instead.

property parent_address: str | None

Tree-hierarchy parent (containing folder). None at 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. None for primaries — so primary_address is not None reads as “sub-node” and primary_address or address as the device-grouping address.

property enabled: bool

Whether the eisy considers this node active.

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 OL as a UOM-100 0-255 byte is surfaced as the UOM-51 0-100% the I_OL editor (and the /cmd write 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 way properties is.

Returns None when 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 read node.status.value (a string) and parse it themselves; the property keeps the structured shape so callers can also reach .uom, .formatted, etc.

property nodedef: NodeDef | None

The resolved NodeDef, or None if unresolved.

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, …). Use has_flag() for individual bit checks rather than reading this directly. Returns 0 when the controller didn’t carry a value for this node — treat 0 as “no bits set” rather than “unknown”.

has_flag(flag)[source]

Return True if every bit in flag is set on this node.

flag may be OR’d; combined values must have every bit set.

Parameters:

flag (NodeFlag)

Return type:

bool

property protocol: Protocol

Transport-protocol classification from family_id.

Returns NODE_SERVER for any non-core family id (PG3 plugin nodes report a slot id here), UNKNOWN for recognised but unmapped core families. Classifies transport, not device class — use is_thermostat etc. for capability.

property is_thermostat: bool

True if the node accepts climate-mode or setpoint commands.

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+ DoorLock variants that drive via DON/DOF).

property is_fan: bool

True for multi-speed fan controllers (nodedef id contains "Fan").

Fan nodes are a subset of dimmable (FanLincMotor accepts DON with a {0, 25, 75, 100} subset), so platform classification should check is_fan before is_dimmable.

property is_dimmable: bool

True if the node has a multilevel ST state and accepts a parameterized DON.

Three conditions must all hold for a real dimmer:

  1. ST editor reports a multilevel range (not a binary {0, 100} subset). Relay nodedefs accept DON with an ignored level param, so DON’s editor alone is unreliable — ST is the source of truth for “can the node hold a non-binary level”.

  2. The nodedef accepts DON. Some Insteon nodedefs (RemoteLinc2_ADV scene buttons, IMETER_SOLO meters) carry a multilevel ST editor — meaningful for the device’s own bookkeeping — but only accept WDU / QUERY, so they can’t actually be commanded on. Without this check is_dimmable returns True for them and consumers route them onto the LIGHT platform where DON-based turn_on silently fails.

  3. The accepted DON declares at least one parameter (the on-level). HA’s light platform sets brightness with DON <level>; a parameterless DON cannot take one, so the node is on/off-only even with a multilevel ST (some node-server nodedefs set level via a separate SETST / SETOL command instead — issue #64 / Virtual#11). Real Insteon/Z-Wave dimmers declare DON with an optional on-level param, so they still qualify.

property is_battery_node: bool

True if the node reports BATLVL but no ST.

Battery-powered Insteon sensors (motion, leak, open/close) match this — they have no on/off primary state.

property zwave_props: ZWaveProperties | None

Parsed ZWaveProperties for Z-Wave / Z-Matter nodes; None for 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.

Parameters:
Return type:

None

async set_climate_mode(mode)[source]

Set HVAC mode. Accepts enum names ("Heat", "Cool", "Auto", "Program Auto", …) or raw ints. The editor for CLIMD enforces subset membership (e.g. excludes "Fan Only" on devices that don’t support it).

Parameters:

mode (str | int)

Return type:

None

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.

Parameters:

mode (str | int)

Return type:

None

async secure_lock()[source]

Issue a secure-lock command (Z-Wave / Insteon I2CS).

Return type:

None

async secure_unlock()[source]

Issue a secure-unlock command.

Return type:

None

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).

Parameters:

val (int | str)

Return type:

None

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 stop_manual_dimming()[source]

End manual dimming (legacy Insteon SMAN).

Return type:

None

async rename(name)[source]

Rename this node. The controller emits a _3 lifecycle frame with action="NN" on success.

Parameters:

name (str)

Return type:

None

to_dict()[source]

Flatten this node to a JSON-compatible dict (record + protocol).

Return type:

dict[str, Any]

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/...). Raises NodeCommandError on non-Z-Wave nodes or controller failure; ISYResponseParseError on malformed bodies.

Parameters:

number (int)

Return type:

dict[str, int]

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 NodeCommandError on rejection so failures aren’t silent.

Parameters:
Return type:

None

async set_zwave_lock_code(user_num, code)[source]

Program a Z-Wave lock’s user-code slot. Raises NodeCommandError on a failed envelope.

Parameters:
Return type:

None

async delete_zwave_lock_code(user_num)[source]

Clear a Z-Wave lock’s user-code slot.

Parameters:

user_num (int)

Return type:

None

async set_enabled(enabled)[source]

Enable or disable this node on the controller.

On success the local enabled flag is updated optimistically; the controller also emits a _3 action="EN" lifecycle.

Parameters:

enabled (bool)

Return type:

None

exception NodeCommandError[source]

Bases: Exception

Raised 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.py imports from _commands.py, never the reverse.