pyisyox.runtime.events module¶
WebSocket event parsing and dispatch.
Two transports — legacy /rest/subscribe (raw XML) and modern
/api/events/subscribe (JSON-wrapped, adds PG3 spolisy channel)
— wrap the same <Event> payload, so parse_event_frame()
accepts either. System events use underscore-prefixed control ids
(_5, _28, …) — see SystemEventControl.
Decoupled from the WS reader (pyisyox.runtime.ws) so the
dispatcher can be tested with synthetic frames.
- class SystemEventControl(*values)[source]¶
Bases:
StrEnumIoX WebSocket “system” control codes (underscore-prefixed).
Property updates use the property id (
"ST","GV1", …) with a populatednode_address. System events use one of these underscore-prefixed codes with an emptynode_address.Codes
_0-_23are the full ISY-994 set from the ISY994 Developer Cookbook §8.5;_24-_28are IoX-6 additions (system editors, the modern Z-Wave / ZigBee / Matter drivers, system upgrade) not in that document — tracked from UDI’s internalUDEvents.htaxonomy. Newer IoX firmware may emit further codes; those aren’t enumerated, andlabel()passes them through verbatim so logs still identify them.- HEARTBEAT¶
Periodic heartbeat.
<action>is the duration in seconds until the next expected heartbeat (use it to detect a stalled stream). No<eventInfo>.
- TRIGGER¶
Trigger events — program status, variable change/init, schedule change, key/info-string pushes, “get status” refresh signal.
<action>discriminates; seeTriggerAction.
- DRIVER_SPECIFIC¶
Driver-specific events — payload depends on the underlying protocol driver. Not modelled.
- NODE_LIFECYCLE¶
Node / scene / folder lifecycle — add / remove / rename / enable / revise / comm-error / etc.
<action>carries the verb; seeNodeLifecycleActionandNODE_LIFECYCLE_EVENT_INFO_TAGS.
- SYSTEM_CONFIG¶
System configuration updated — time / NTP / notifications / batch-mode / battery-write-mode.
<action>0-6; seeSystemConfigAction.
- SYSTEM_STATUS¶
Controller-side busy/idle/safe-mode status.
<action>0-3; seepyisyox.constants.SystemStatus.
- INTERNET_ACCESS¶
Internet-access status — disabled / enabled (
<eventInfo>= external URL) / failed. SeeInternetAccessStatus.
- PROGRESS¶
Progress report during long-running operations (device programming, restore, device-adder).
<action>1 / 2.1 / 2.2 / 2.3; seeProgressAction. The_7A/_7Mdevice-write sub-codes also ride through on this control — seeDeviceWriteAction.
- SECURITY_SYSTEM¶
Security-system event — connected / disconnected / armed-* / disarmed. See
SecuritySystemAction.
- SYSTEM_ALERT¶
System alert event — “not implemented and should be ignored” per the cookbook.
- OPENADR¶
OpenADR / Flex-Your-Power events — ISY994 Z-Series demand-response.
- CLIMATE¶
Climate / weather events — required the ISY994 WeatherBug module; not present on eisy.
- AMI_SEP¶
AMI/SEP energy events — ISY994 only (see the Energy Management Developer’s Manual).
- ENERGY_MONITORING¶
External energy-monitoring (Brultech) — ISY994 only; on later firmware these are folded into node events instead.
- UPB_LINKER¶
UPB linker events — UPB-enabled units only.
- UPB_DEVICE_ADDER¶
UPB device-adder state — UPB-enabled units only.
- UPB_DEVICE_STATUS¶
UPB device-status events — UPB-enabled units only.
- GAS_METER¶
Gas-meter events — ISY994 only.
- ZIGBEE¶
Legacy ZigBee events — ISY994-era driver. See
ZIGBEE_UYB(_27) for the IoX-6+ ZigBee driver used on eisy.
- ELK¶
ELK alarm-panel events — requires the ELK module (see the ELK Integration Developer’s Manual).
- DEVICE_LINKER¶
Device-linker events —
<action>1 (status) / 2 (cleared). SeeDeviceLinkerAction.
- ZWAVE¶
Legacy Z-Wave integration events — ISY994-era driver. See
ZMATTER_ZWAVE(_25) for the IoX-6+ ZMatter Z-Wave driver used on eisy.
- BILLING¶
Billing events — ISY994 ZS-series only.
- PORTAL¶
Portal events — portal socket-connection / account-registration status when a portal module is installed.
- SYSTEM_EDITOR¶
System editor changed — fired when a “system editor” (e.g.
_sys_notify_short) is updated.<node>carries the editor name.<action>isSystemEditorAction. IoX-6 addition.
- ZMATTER_ZWAVE¶
ZMatter Z-Wave events — IoX-6+ Z-Wave driver on eisy hardware.
<action>is dotted ("{category}.{type}"); category numbers are system-status (1), discovery (2), general-status (3), general-error (4), S2 (5), OTA (6), backup/restore (7), device- interview (8), button-detect (9), logger (10). Sub-action details aren’t modelled — the dotted string passes through verbatim. Distinct fromZWAVE(_21, ISY994-era driver).
- SYSTEM_UPGRADE¶
System-upgrade lifecycle —
<action>isSystemUpgradeAction(active / inactive / available / reboot-required). IoX-6 addition.
- ZIGBEE_UYB¶
ZigBee events — IoX-6+ ZigBee driver on eisy hardware. Same dotted
"{category}.{type}"action shape asZMATTER_ZWAVE(minus the logger sub-category). Distinct fromZIGBEE(_18, ISY994-era driver).
- MATTER_STATUS¶
Matter network status — IoX-6+ Matter driver.
<action>is dotted; active sub-categories are 1 (system status), 2 (discovery), 3 (RX/TX), 8 (device interview). Not in the ISY994 cookbook.Note:
_28is also reserved in UDI’s source for Profile change events (actions 1-8 — profile/editor/nodedef/linkdef updated/deleted) — but no firmware path fires those today (placeholders since Dec 2024 per UDI). Don’t subscribe expecting them.
- class TriggerAction(*values)[source]¶
Bases:
StrEnumAction codes carried in
SystemEventControl.TRIGGER(_1) frames — ISY994 Developer Cookbook §8.5.3.<action>discriminates what the frame is; pyisyox only routes onPROGRAM_STATUS/VARIABLE_VALUE/VARIABLE_INIT.- PROGRAM_STATUS¶
Program status changed — handled by
_apply_program_status.<eventInfo>carries the program<id>, enabled/run-at-reboot flags, last run/finish times, and a bitwise<s>status.
- GET_STATUS¶
“Get status” — the controller is telling subscribers to re-poll everything (e.g. after a config change). No payload.
- KEY_CHANGED¶
A key changed.
nodecarries the key.
- INFO_STRING¶
An info string.
nodecarries the key;<eventInfo>is the text.
- IR_LEARN_MODE¶
IR learn mode toggled. No payload.
- SCHEDULE¶
A schedule’s status changed.
nodecarries the key.
- VARIABLE_VALUE¶
Variable value changed — handled by
_apply_variable_change.<eventInfo>carries<var type id><val><ts>.
- VARIABLE_INIT¶
Variable init (restore-on-startup) value changed — same handler / payload shape as
VARIABLE_VALUE, applied toinit.
- KEY¶
The current subscription key, sent once right after a new subscription is established.
<eventInfo>is the key.
- VARIABLE_TABLE_CHANGED¶
Variable table structurally changed — fires when a variable is added or removed, or when its precision is changed (a metadata change that the per-value
VARIABLE_VALUE/VARIABLE_INITframes don’t cover).<eventInfo>carries<var><type>N</type><id>0</id></var>(id=0 is the wildcard sentinel — “this whole type’s table changed”). The right response is to re-fetch/api/variables/{type}for the affected type so the registry mirrors the controller’s metadata (precision in particular — the wirevalfrom the per-value frames is raw, and a stale precision will mis-render the value). The dispatcher recognises it and firesEventDispatcher.add_variable_table_change_listener()callbacks; the dispatcher itself does not re-fetch.
- class ProgressAction(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.PROGRESS(_7) frames — Cookbook §8.5.9.<eventInfo>is free-text progress detail.- UPDATE¶
Generic progress update.
- DEVICE_ADDER_INFO¶
Device-adder info (UPB only).
- DEVICE_ADDER_WARN¶
Device-adder warning (UPB only).
- DEVICE_ADDER_ERROR¶
Device-adder error (UPB only).
- class SystemConfigAction(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.SYSTEM_CONFIG(_4) frames — Cookbook §8.5.6.- TIME_CHANGED¶
- TIME_CONFIG_CHANGED¶
- NTP_SETTINGS_UPDATED¶
- NOTIFICATIONS_SETTINGS_UPDATED¶
- NTP_COMM_ERROR¶
- BATCH_MODE_UPDATED¶
Batch mode toggled —
<eventInfo><status>is"1"/"0".
- BATTERY_WRITE_MODE_UPDATED¶
Battery-powered-write mode toggled —
<eventInfo><status>is"1"/"0".
- class InternetAccessStatus(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.INTERNET_ACCESS(_6) frames — Cookbook §8.5.8.- DISABLED¶
- ENABLED¶
Enabled —
<eventInfo>is the external URL.
- FAILED¶
- class SecuritySystemAction(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.SECURITY_SYSTEM(_8) frames — Cookbook §8.5.10.nodeand<eventInfo>are null.- DISCONNECTED¶
- CONNECTED¶
- DISARMED¶
- ARMED_AWAY¶
- ARMED_STAY¶
- ARMED_STAY_INSTANT¶
- ARMED_NIGHT¶
- ARMED_NIGHT_INSTANT¶
- ARMED_VACATION¶
- class DeviceLinkerAction(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.DEVICE_LINKER(_20) frames — Cookbook §8.5.22 (udievnts.xsd).- STATUS¶
Linking status update —
<eventInfo>carries device-linker info.
- CLEARED¶
The device-linking list was cleared. No payload.
- class SystemUpgradeAction(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.SYSTEM_UPGRADE(_26) frames — IoX-6 firmware-upgrade lifecycle.- ACTIVE¶
Upgrade in progress.
- INACTIVE¶
Upgrade not active (post-completion or idle).
- AVAILABLE¶
A new upgrade is available to install.
- REBOOT_REQUIRED¶
Upgrade applied; reboot required to take effect.
- class SystemEditorAction(*values)[source]¶
Bases:
StrEnumAction codes on
SystemEventControl.SYSTEM_EDITOR(_24) frames. The<node>slot carries the editor name (e.g._sys_notify_short).- EDITOR_CHANGED¶
A system editor’s contents changed.
- class DeviceWriteAction(*values)[source]¶
Bases:
StrEnumDevice-write sub-codes that ride through on
_7(SystemEventControl.PROGRESS) frames — PyISY 3.x surfaced these asNodeChangeAction.DEVICE_WRITING/DEVICE_MEMORY.Unlike the other action enums, these are control-value sub-codes (they have the
_prefix and arrive in the<control>slot), not<action>values — the dispatcher doesn’t route them; they pass through as plain control events.<eventInfo>child tags per code are inDEVICE_WRITE_PROGRESS_EVENT_INFO_TAGS.- PROGRESS¶
Device-writing progress message —
<eventInfo>carries<message>.
- MEMORY¶
Raw Insteon memory write —
<eventInfo>carries<memory>/<cmd1>/<cmd2>/<value>.hacs-udi-iox’s backlight entities subscribe to this to catch memory-write echoes.
- class NodeLifecycleAction(*values)[source]¶
Bases:
StrEnumVerbs the eisy emits via
<control>_3</control>events — ISY994 Developer Cookbook §8.5.5 (“Node Changed/Updated”). PyISY 3.x keeps the same mapping.<eventInfo>child tags per verb are inNODE_LIFECYCLE_EVENT_INFO_TAGS.ENcarries anenabledboolean in<eventInfo>— there’s no separate “disabled” verb; the same code handles both transitions.- NODE_ADDED¶
Node added.
<eventInfo>carries<nodeName>plus a<nodeType>that is itself the full<node>element — seeNodeLifecycleEvent.node_xml.
- NODE_REMOVED¶
Node removed (device deleted from the controller).
- NODE_RENAMED¶
Node renamed (display name changed).
- NODE_MOVED¶
Node moved into a Scene.
- LINK_CHANGED¶
Link changed (within a scene). Not supported by the controller — kept for documentation; never observed.
- NODE_REMOVED_FROM_GROUP¶
Node removed from a Scene.
- PARENT_CHANGED¶
Parent (primary node) changed.
- NODE_ENABLED¶
Node enabled/disabled — direction is in
eventInfo.enabled.
- POWER_INFO_CHANGED¶
Power-info changed —
<eventInfo>carries<deviceClass>/<wattage>/<dcPeriod>.
- DEVICE_ID_CHANGED¶
Device ID changed. Not implemented by the controller — kept for documentation.
- DEVICE_PROPERTY_CHANGED¶
Device property changed — UPB only.
- PENDING_DEVICE_OP¶
Pending device operation queued, awaiting commit. On Insteon a write (e.g. changing backlight level) surfaces
WHfirst, thenPROGRAMMING_DEVICE(WD) while the value is written; a property-update event arrives separately once it lands.
- PROGRAMMING_DEVICE¶
The controller is carrying out a programming/write operation on this node (follows
PENDING_DEVICE_OP). Cookbook name: “Programming Device”. Not a completion signal — watch the subsequent property-update event for the new value.
- NODE_REVISED¶
Node revised — drastically changed (UPB-style); the consumer should discard cached info for the node and rebuild it.
<eventInfo>carries the full<node>structure.
- NODE_TYPE_INFO_CHANGED¶
Supported-type info changed — the node’s nodedef assignment was reassigned (e.g. a node server’s
changeNode, or a device driver detecting new capabilities). The primary signal that a cached nodedef → entity mapping is stale. Not fired for/rest/profilesdefinition updates or moves, or at startup migration — those rewrite the profile DB without notifying.
- ALL_NODES_ADDED¶
All nodes for a single device have been added (bulk). Fired after an include / re-pair so consumers can coalesce a single refresh per device instead of per child node.
- LINK_UPDATED¶
Scene link updated — a link’s properties (on-level / ramp rate) changed for an existing scene member.
- DISCOVERING_NODES¶
Discovering nodes (linking in progress). No node.
- NODE_DISCOVERY_COMPLETE¶
Node discovery complete. No node.
- NODE_ERROR¶
Node communication error (device unreachable).
- NODE_ERROR_CLEARED¶
A previously-reported node communication error was cleared (cookbook: “Clear Node Error / Comm. Errors Cleared”) — the companion to
NODE_ERROR.
- FOLDER_ADDED¶
Folder added.
- FOLDER_REMOVED¶
Folder removed.
- FOLDER_RENAMED¶
Folder renamed —
<eventInfo>carries<newName>.
- GROUP_ADDED¶
Scene (group) added —
<eventInfo>carries<groupName>/<groupType>.
- GROUP_REMOVED¶
Scene (group) removed.
- GROUP_RENAMED¶
Scene (group) renamed —
<eventInfo>carries<newName>.
- NET_RENAMED¶
A networking-module resource was renamed (
node= the new name). Doesn’t affect the node registry.
- NODE_LIFECYCLE_EVENT_INFO_TAGS: dict[NodeLifecycleAction, tuple[str, ...]]¶
<eventInfo>child element names carried by each lifecycle verb (per the UDI notification table). An empty tuple means the frame carries only the node address. Reference metadata for consumers that want to parse the payload — pyisyox itself only parses the<node>element onNODE_ADDED(seeNodeLifecycleEvent.node_xml).
- DEVICE_WRITE_PROGRESS_EVENT_INFO_TAGS: dict[DeviceWriteAction, tuple[str, ...]]¶
<eventInfo>child tags carried by eachDeviceWriteActioncontrol code. The dispatcher doesn’t route these — reference metadata for consumers that subscribe to_7A/_7Mcontrol events directly.
- describe_system_event(control, action)[source]¶
Render a
<control>/<action>pair from a system event frame as a friendly"<control_label> = <action_label>"string.Resolves both halves to their enum names where one applies:
"_5"/"0"→"system_status = not_busy""_1"/"0"→"trigger = program_status""_3"/"WH"→"node_lifecycle = pending_device_op""_4"/"5"→"system_config = batch_mode_updated""_8"/"AW"→"security_system = armed_away""_20"/"2"→"device_linker = cleared""_0"/"90"→"heartbeat = 90"(action = seconds to the next heartbeat; not enumerated)"_28"/"1.3"→"matter_status = 1.3"(no enum)"_26"/"2"→"system_upgrade = inactive""_24"/"1"→"system_editor = editor_changed""_99"/"x"→"_99 = x"(control we don’t recognise — both halves pass through verbatim)
Intended for the debug logging consumers do over raw event frames (so a line reads
system_status = busyinstead ofsystem_status = 1); not part of any dispatch path. Property- update frames (non-underscore control) aren’t system events — this just echoes them back unchanged if you pass one.
- class NodeLifecycleEvent(action, node_address, raw_action, seqnum, node_xml=None, enabled=None)[source]¶
Bases:
objectA high-level summary of a
<control>_3</control>lifecycle frame.Emitted alongside the raw
Eventwhenever the dispatcher sees one of the actions inNodeLifecycleAction. Consumers subscribe viapyisyox.controller.Controller.add_node_lifecycle_listener()to drive their own reload UX (HA Core’s Repair issue, etc.).- Variables:
action (pyisyox.runtime.events.NodeLifecycleAction | str) – The lifecycle verb (typed enum). Unknown verbs come through as a plain string via
raw_action.node_address (str) – Wire address of the affected node. Empty string only for system-wide signals (none observed yet).
raw_action (str) – The string action value verbatim, in case a new verb appears that isn’t yet in
NodeLifecycleAction.node_xml (str | None) – For
NDactions, the inner<node>element text from<eventInfo>.Nonefor verbs that don’t include the full element. Consumers wanting the parsed shape can pass this toparse_lifecycle_node_xml().enabled (bool | None) – For
EN(NODE_ENABLED) actions, the new enabled/disabled state from<eventInfo><enabled>— the same value already written back toNode.enabled.Nonefor every other verb (and forENframes that omit the flag).
- Parameters:
- action: NodeLifecycleAction | str¶
- property requires_reload: bool¶
True for verbs that invalidate the cached node/group/folder registry.
Reload-worthy:
ND/NR/NN(node added/removed/renamed — the registry’s set or display names are stale),EN(enabled/disabled — the entity’s property shape may change),RV(revised — discard and rebuild this node),NI(supported-type info changed — the node’s nodedef assignment was reassigned, so the cached nodedef→entity mapping is stale; per UDI’s notification taxonomy this is the primary signal for profile-related node changes),AA(all-nodes-added bulk signal after a device include),RG(removed from scene — membership changed),SC(node-discovery complete — new nodes may have appeared), and the folder/scene tree verbsFD/FR/FN/GD/GR/GN(thegroups/foldersregistries are stale).Softer signals — informational, don’t trigger reload UX:
MV(added to scene),CL(link changed — not supported),LU(scene link’s on-level/ramp updated — property change, not shape change),PC(parent changed),PI(power info),DI(device id — not implemented),DP(UPB property),WH(pending op),WD(programming device — a property-update event follows),SN(discovering nodes — wait forSC),CE/NE(comm error/cleared — no shape change),WR(a networking resource was renamed — doesn’t touch nodes).
- class Event(seqnum, timestamp, control, action, node_address, formatted_action='', formatted_name='', uom='', precision=None, event_info='')[source]¶
Bases:
objectOne parsed event frame.
- Variables:
seqnum (int) – Event sequence number from the eisy. Monotonic per connection; resets on reconnect.
timestamp (str) – ISO 8601 timestamp string from the frame (preserved verbatim — consumer parses if needed).
control (str) – Property id (
"ST","GV1", …) or system code ("_5","_28", …).action (str) – Raw value as reported (string form preserves the controller’s precision representation).
node_address (str) – Wire address of the affected node, or empty string for system events.
formatted_action (str) – Human-readable display value (e.g.
"0.6839 US gallons"). Empty when the controller didn’t supply one (system events typically don’t).formatted_name (str) – Display name of the property (e.g.
"Current"). Empty when not provided.uom (str) – Unit-of-measure id from
<action uom="...">.precision (int | None) – Decimal precision from
<action prec="...">, orNoneif absent. (Wire keys it as"prec"; Python attribute spells it out.)event_info (str) – Inner
<eventInfo>XML preserved verbatim. Empty string when the frame had no<eventInfo>element or when its content was empty. Consumers that need the structured payload (e.g. variable change frames carrying<var type="..." id="...">, or controller logs in CDATA) parse this themselves — the IoX wire schema differs across system control codes and pyisyox stays neutral.
- Parameters:
- parse_event_frame(raw)[source]¶
Decode a single WebSocket frame to an
Event.Accepts either:
Raw XML —
<?xml...?><Event...>...</Event>(legacy/rest/subscribe).JSON envelope —
{"type": "event", "data": "<xml>"}(modern/api/events/subscribe). Othertypevalues (e.g."spolisy"PG3 service status) returnNone— they’re not property updates and the dispatcher ignores them.
Returns
Nonefor keep-alive nulls, malformed XML, or non-event JSON envelopes. Does not raise on parse failures so a single bad frame can’t crash the read loop.
- class ProgramRunState(*values)[source]¶
Bases:
IntEnumLow-nibble of the
<s>byte on a program-status frame.Cookbook §8.5.3: exactly one of three run-clause states per frame, ORed with a
ProgramEvalState(high nibble) in the byte. Absent (Noneon the event) when the high nibble isProgramEvalState.NOT_LOADED— the program errored so there’s no clause currently running.- IDLE¶
- THEN¶
- ELSE¶
- class ProgramEvalState(*values)[source]¶
Bases:
IntEnumHigh-nibble of the
<s>byte on a program-status frame.Cookbook §8.5.3: the program’s last if-clause evaluation result. The
status: boolfield onProgramStatusEventderives from the same source (the<on/>/<off/>element) but this enum disambiguates the three “not really True/False” cases that the bool collapses.Note
NOT_LOADEDis the cookbook’s literal label, but in practice the controller emits0xF0when the program failed to compile or hit a runtime error — not (only) when it hasn’t been loaded yet. Treat this as the program-error sentinel; seeProgramRunStatefor whyrun_stateisNonein this case.- UNKNOWN¶
- TRUE¶
- FALSE¶
- NOT_LOADED¶
- class ProgramStatusEvent(address, status, running, seqnum, run_state=None, eval_state=None, enabled=None, run_at_startup=None)[source]¶
Bases:
objectA program toggled true/false on the controller.
Emitted by
EventDispatcherwhenever a<control>_1</control>frame with<action>0</action>arrives carrying a program id in its<eventInfo>. The matchingpyisyox.client.ProgramRecordis mutated in place before listeners fire, so consumers readingprogram.statusfrom a callback see the updated value.- Variables:
address (str) – Program id (4-character hex, zero-padded to match
/api/programs).status (bool) –
Truewhen the cookbook<s>byte’s eval state isProgramEvalState.TRUE(the if-clause matched on the most recent evaluation);FalseforProgramEvalState.FALSE. ForProgramEvalState.UNKNOWN/ProgramEvalState.NOT_LOADED(and frames with no<s>byte) the dispatcher carries forward the priorrecord.statusso a transient unknown doesn’t flip the entity. Wire-shape note: the<on/>/<off/>elements that ride along on the same frame are the enabled flag, not the status — seeenabled.running (int | None) – Raw
<s>byte the eisy sent, orNoneif absent. Cookbook §8.5.3: the byte is a bitwise OR of aProgramRunState(low nibble) and aProgramEvalState(high nibble); userun_state/eval_statefor the typed view.run_state (pyisyox.runtime.events.ProgramRunState | None) – Decoded low nibble —
IDLE/THEN/ELSE, orNonewhen the program isn’t loaded (eval_state == NOT_LOADED) or the wire byte was absent / unrecognised.eval_state (pyisyox.runtime.events.ProgramEvalState | None) – Decoded high nibble —
UNKNOWN/TRUE/FALSE/NOT_LOADED, orNonewhen the wire byte was absent / unrecognised. Disambiguates the three “not really True/False” cases thatstatus: boolcollapses.NOT_LOADEDis the cookbook label for what is in practice the program-errored sentinel — seeProgramEvalState.enabled (bool | None) – New
enabledstate when the frame carried an<on/>/<off/>element —Truefor<on/>,Falsefor<off/>.Nonewhen the frame omitted both (some “ran”-only frames carry only<r>/<f>/<s>— see cookbook §8.5.3). The matching record’spyisyox.client.ProgramRecord.enabledis updated in-place before listeners fire when this is non-None.run_at_startup (bool | None) – New
run_at_startupstate when the frame carried an<rr/>(True) or<nr/>(False) element.Nonewhen the frame omitted both. Mirror of the enabled-flag pattern; the record’spyisyox.client.ProgramRecord.run_at_startupis updated in-place before listeners fire.
- Parameters:
address (str)
status (bool)
running (int | None)
seqnum (int)
run_state (ProgramRunState | None)
eval_state (ProgramEvalState | None)
enabled (bool | None)
run_at_startup (bool | None)
- run_state: ProgramRunState | None¶
- eval_state: ProgramEvalState | None¶
- class VariableTableChangeEvent(type_id, seqnum)[source]¶
Bases:
objectA
_1/ action"9"system event — variable table changed.The eisy fires this when a variable is added, removed, or has its precision changed (a structural / metadata change that the per-value
TriggerAction.VARIABLE_VALUE/VARIABLE_INITframes don’t carry).<eventInfo>payload:<var><type>N</type><id>0</id></var>
id=0is the wildcard sentinel — “this whole type’s table changed”, not a specific variable. Consumers should re-fetch/api/variables/{type_id}to pick up the new metadata (precision in particular — the per-value frames carry rawvaland a stale precision will mis-render every variable of this type until the registry is refreshed).
- class EventDispatcher(nodes, programs=None, variables=None, groups=None)[source]¶
Bases:
objectRoutes parsed
Eventinstances into a node registry + listener callbacks.The dispatcher is intentionally not coupled to the WebSocket transport —
feed()accepts a raw frame and does the parse + route + emit dance. The actual WS read loop lives inpyisyox.runtime.ws; tests can drive the dispatcher directly with synthetic frames.- Parameters:
nodes (dict[str, NodeRecord])
programs (dict[str, ProgramRecord] | None)
variables (dict[str, dict[str, VariableRecord]] | None)
groups (dict[str, GroupRecord] | None)
- update_groups(groups)[source]¶
(Re)build the member→groups reverse index from a group registry.
Called from
__init__and again bypyisyox.controller.Controller.refresh()—refresh()replacesLoadResult.groupswith a fresh dict (unlikenodes, which is mutated in place), so the index has to be rebuilt or scene-membership changes from a reload lifecycle event would be missed (new members never re-emit; removed members still would).- Parameters:
groups (dict[str, GroupRecord])
- Return type:
None
- add_program_status_listener(callback)[source]¶
Register
callbackto fire on every program-status frame (<control>_1</control>action"0").The dispatcher updates the matching
pyisyox.client.ProgramRecordin place before firing, so consumers readingprogram.statusfrom the callback see the new value.- Returns:
An unsubscribe function.
- Parameters:
callback (Callable[[ProgramStatusEvent], None])
- Return type:
Callable[[], None]
- add_variable_table_change_listener(callback)[source]¶
Register
callbackto fire on every variable-table-change frame (<control>_1</control>action"9").Fired when a variable is added, removed, or has its precision changed on the controller. The dispatcher itself does not re-fetch the variable table — the listener is the seam where consumers wire in a focused re-fetch (e.g. by calling
Controller.refresh()) so the registry mirrors the new metadata. SeeVariableTableChangeEventfor the payload.- Returns:
An unsubscribe function.
- Parameters:
callback (Callable[[VariableTableChangeEvent], None])
- Return type:
Callable[[], None]
- add_lifecycle_listener(callback)[source]¶
Register
callbackto fire on every parsedNodeLifecycleEvent(<control>_3</control>frames).Use this to drive reload UX: HA Core typically registers a Repair issue when it sees a lifecycle event with
requires_reload=True, prompting the user to reload the integration when convenient. The dispatcher does not update the node registry on lifecycle events — consumers decide whether to callpyisyox.controller.Controller.refresh()or live with a stale view until manual reload.- Returns:
An unsubscribe function.
- Parameters:
callback (Callable[[NodeLifecycleEvent], None])
- Return type:
Callable[[], None]
- feed(raw_frame)[source]¶
Parse one frame, apply the property update, fan out to listeners.
Returns the parsed
Eventfor callers that want to peek (e.g. for sequence-number tracking), orNonewhen the frame couldn’t be parsed (malformed XML, non-event envelope, keep-alive null). Never raises on bad input — a single bad frame must not crash the read loop.