Skip to content

feat(events): expand IoX-6 system event taxonomy from UDI's UDEvents.h#176

Merged
shbatm merged 1 commit into
mainfrom
feat/events-iox6-system-codes
May 20, 2026
Merged

feat(events): expand IoX-6 system event taxonomy from UDI's UDEvents.h#176
shbatm merged 1 commit into
mainfrom
feat/events-iox6-system-codes

Conversation

@shbatm

@shbatm shbatm commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the system-event vocabulary UDI shipped in their subscription-events reference doc (drawn from firmware UDEvents.h). Pure diagnostic / observability work — the dispatcher already passed unknown system controls through _log_system_event; this gives them friendly labels, typed enum members, and (for _3/NI and _3/AA) the reload-worthy flag they should have had.

  • SystemEventControl: + _24 SYSTEM_EDITOR, _25 ZMATTER_ZWAVE, _26 SYSTEM_UPGRADE, _27 ZIGBEE_UYB. Re-label existing _18 ZIGBEE / _21 ZWAVE as ISY994-era legacy drivers (their IoX-6 successors are _27 / _25). Document _28's shared-with-never-fired Profile events caveat.
  • NodeLifecycleAction: + NI (NODE_TYPE_INFO_CHANGED — the primary signal a node's nodedef assignment was reassigned), AA (ALL_NODES_ADDED — bulk include-complete), LU (LINK_UPDATED — scene link's on-level / ramp changed). NI + AA flip requires_reload=True; LU is a soft signal.
  • New SystemUpgradeAction (_26: active / inactive / available / reboot-required) and SystemEditorAction (_24: editor-changed). Both wired into _SYSTEM_ACTION_ENUMS so describe_system_event resolves them in debug logs.

_25 / _27 sub-action enums are deliberately deferred — UDI's doc points at a separate zwave.md we don't have. They pass through with the new dotted-action label (zmatter_zwave = 1.3 etc.).

Test plan

  • pytest — 887 passed, no skips
  • ruff check pyisyox tests clean
  • mypy pyisyox clean
  • pre-commit run on all touched files green (incl. pylint, codespell, prettier)
  • New cases in test_describe_system_event cover each new control + action
  • New _3/NI, _3/AA, _3/LU dispatcher smoke tests
  • test_lifecycle_requires_reload_taxonomy extended to keep the reload/soft split exhaustive

🤖 Generated with Claude Code

Adds the system-event vocabulary UDI shipped in their subscription-events
reference doc (system editor / ZMatter Z-Wave / system upgrade / new ZigBee
driver) and the three node-lifecycle verbs we'd been missing — most
importantly _3/NI (the primary nodedef-changed signal). All additions are
diagnostic-only: the dispatcher already routed unknown system controls
through `_log_system_event`; this just gives them friendly labels and
typed enum members. `_3/NI` and `_3/AA` flip `requires_reload=True` since
they invalidate the cached nodedef→entity mapping (UDI's taxonomy calls
`NI` the primary event for profile-related changes).

* SystemEventControl: + `_24` SYSTEM_EDITOR, `_25` ZMATTER_ZWAVE,
  `_26` SYSTEM_UPGRADE, `_27` ZIGBEE_UYB. Relabel `_18` ZIGBEE and
  `_21` ZWAVE as the legacy ISY994-era drivers (their IoX-6 successors
  are `_27` / `_25`). Document `_28`'s Profile-events overlap (defined
  but never fired per UDI).
* NodeLifecycleAction: + `NI` (NODE_TYPE_INFO_CHANGED), `AA`
  (ALL_NODES_ADDED), `LU` (LINK_UPDATED) with event-info tag rows.
* SystemUpgradeAction (`_26`) + SystemEditorAction (`_24`) enums,
  wired into `_SYSTEM_ACTION_ENUMS` so `describe_system_event` resolves
  them.
* `_25` / `_27` sub-categories aren't modelled — UDI's doc points at a
  separate `zwave.md` we don't have. They pass through verbatim with
  the new dotted-action label.

Source: UDI subscription-events reference (`UDEvents.h` taxonomy).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown

Claude finished @shbatm's task in 2m 52s —— View job


Code Review

  • Gathering context
  • Reading changed files
  • Analyzing code
  • Posting review

Overall this is a high-quality, well-structured diagnostic/observability PR. The taxonomy additions are correct, thoroughly documented, and the test coverage is comprehensive. A few observations below, mostly minor.


SystemEventControl additions (_24_27) — events.py:135–162

Clean. The four new members (SYSTEM_EDITOR, ZMATTER_ZWAVE, SYSTEM_UPGRADE, ZIGBEE_UYB) follow the established pattern. The decision to leave _25/_27 out of _SYSTEM_ACTION_ENUMS (no sub-action enum yet) while still giving them typed enum members is the right tradeoff — they'll produce zmatter_zwave = 1.3 in logs rather than _25 = 1.3, which is already a win.

The re-labeling of ZIGBEE = "_18" and ZWAVE = "_21" as ISY994-era legacy drivers is a useful heads-up for consumers. Cross-references via :attr: make it easy to navigate.


_28 dual-reservation note — events.py:158–162

The caveat about Profile-change actions (1-8) being reserved in UDI source but never fired is valuable to document inline. One minor thought: since this affects both the MATTER_STATUS entry and the profile-reload story, it could also be worth one line in the CLAUDE.md "Deferred / out of scope" section if you ever want to surface it to contributors. Not blocking.


SystemUpgradeAction / SystemEditorActionevents.py:315–345

SystemEditorAction has a single value (EDITOR_CHANGED = "1"). This is fine as-is — consistent with the "emit what we've observed" approach. If UDI's source has more _24 actions, they'll surface in logs as their raw string until added, which is correct behavior.

The REBOOT_REQUIRED = "4" name is clear and consumer-friendly. No issues.


NodeLifecycleAction additions (NI, AA, LU) — events.py:428–439

Placement is fine. All three are inserted between NODE_REVISED and DISCOVERING_NODES, which is a reasonable documentation grouping.

One question on NODE_TYPE_INFO_CHANGED = "NI": The docstring correctly notes it fires for PG3 changeNode / driver capability updates but not for /rest/profiles definition rewrites. The implication for requires_reload=True is that a consumer calling Controller.refresh() in response would re-load the node registry — but a profile change driving the NI would also need refresh_profile() to pull the new profile content. Does UDI's taxonomy say anything about whether the profile reload is already in progress server-side when NI fires, or does the consumer need to initiate both? Worth a doc note if known. Not blocking — this is a consumer-policy question.

LINK_UPDATED = "LU" requires_reload=False is the right call. On-level/ramp-rate is a property, not a shape change. The soft-signal classification is consistent with PROGRAMMING_DEVICE / PENDING_DEVICE_OP.


NODE_LIFECYCLE_EVENT_INFO_TAGSevents.py:500–502

All three new verbs use (). For NODE_TYPE_INFO_CHANGED and ALL_NODES_ADDED this is reasonable given no wire captures are available. For LINK_UPDATED, UDI's internal notification table may document payload fields (which scene member, what changed). If LU frames carry <linkType> or <linkNode> tags similar to NODE_MOVED ("MV"), the empty tuple would be incomplete reference metadata. Not a runtime bug, but worth revisiting once you have a hardware capture of a scene-link edit.


requires_reloadevents.py:733–749

Correct. NODE_TYPE_INFO_CHANGED and ALL_NODES_ADDEDTrue. LINK_UPDATED not in the set → False. The exhaustive set-equality test in test_lifecycle_requires_reload_taxonomy prevents future additions from silently missing a classification.

Pre-existing asymmetry (not introduced here): NODE_MOVED = "MV" (node added to a scene) is a soft signal, while NODE_REMOVED_FROM_GROUP = "RG" (node removed from a scene) is reload-worthy. Both operations change scene membership, so the asymmetry is slightly surprising. If this is intentional (e.g. adding a member can't invalidate a cached group aggregate in the same way removal can), a doc note would help future readers. Low priority since it pre-dates this PR.


_SYSTEM_ACTION_ENUMSevents.py:529–540

The two new entries (SYSTEM_EDITOR → SystemEditorAction, SYSTEM_UPGRADE → SystemUpgradeAction) are in the right place. ZMATTER_ZWAVE and ZIGBEE_UYB are correctly absent (dotted numeric action codes aren't modelled). This means describe_system_event("_25", "1.3")"zmatter_zwave = 1.3", which is the stated design.


Exports (__init__.py, runtime/__init__.py)

SystemEditorAction and SystemUpgradeAction are exported; NodeLifecycleAction was already there (the new members ride with it). Correct.


Tests

  • test_describe_system_event parametrize covers _24/_25/_26/_27 plus the three new _3 lifecycle verbs — good.
  • test_lifecycle_listener_fires_on_node_type_info_changed and test_lifecycle_listener_fires_on_all_nodes_added both assert requires_reload=True directly — exactly the right invariant to pin.
  • test_link_updated_is_not_reload_worthy pins the soft-signal classification.
  • test_lifecycle_requires_reload_taxonomy uses set-equality to ensure no new verb slips through unclassified — this is the standout design choice in the test suite.
  • The test_enum_label_known_returns_friendly_name and test_enum_label_unknown_passes_through_raw_value parametrize lists in test_events_internal.py are updated for the new enums. ✓

Summary

Approve. The taxonomy additions are accurate, documentation is thorough (including the _28 dual-reservation caveat), test coverage is exhaustive, and the deferred _25/_27 sub-action enums are explicitly noted. The open question on NI + refresh_profile() interaction and the LU eventInfo tags are worth tracking but don't block merging.

@shbatm
shbatm merged commit e9cf954 into main May 20, 2026
5 checks passed
@shbatm
shbatm deleted the feat/events-iox6-system-codes branch May 20, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant