Skip to content

Add event platform to isy994 integration#169782

Draft
shbatm wants to merge 8 commits into
home-assistant:devfrom
shbatm:isy994-button-events
Draft

Add event platform to isy994 integration#169782
shbatm wants to merge 8 commits into
home-assistant:devfrom
shbatm:isy994-button-events

Conversation

@shbatm

@shbatm shbatm commented May 5, 2026

Copy link
Copy Markdown
Contributor

HOLD

Two blocking issues:

1. Need resolution on home-assistant/architecture#1377
2. Identified issue during parallel development on hacs-udi-iox (next gen version of this integration) that event entities will trigger on every websocket connection/blip causing spurious automation triggers. The pyisyox fix is shbatm/pyisyox#170 but this has not been backported to PyISY yet: automicus/PyISY#512.

Proposed change

Adds an `event` entity per Insteon load / keypad-button node so physical button presses (`on`, `off`, `fast_on`, `fast_off`, `fade_up`, `fade_down`, `fade_stop`) are exposed as first-class entities. This is the approach reviewers requested in #169694 in lieu of hand-rolled device triggers — once this lands, the core event integration provides device triggers for these entities automatically.

Implementation notes:

  • New parallel-classification mechanism in `helpers.py`: `NODE_PARALLEL_PLATFORMS` lets a node match an additional platform (here `Platform.EVENT`) on top of its primary classification. Reuses the existing `NODE_FILTERS` matchers (`_check_for_node_def`, `_check_for_insteon_type`, `_check_for_zwave_cat`) — same machinery, just non-exclusive.
  • Filter covers both `_ADV` and legacy node defs for SwitchLinc / KeypadLinc / InLineLinc / BallastLinc families, plus Insteon type prefixes derived from observed eisy hardware to catch pre-5.x firmware variants. Z-Wave categories are left empty for now, ready for expansion in the future.
  • `KeypadButton_ADV` sub-buttons are disabled by default (a typical KeypadLinc exposes 6-8 of them; most users only automate a few). Sub-button friendly names strip the parent device prefix to avoid duplication and defer to Home Assistant to manage the naming blocks.
  • Existing `isy994_control` bus event continues to fire from the load entity (switch/light) for the same node — overriding `async_on_control` in the event entity prevents double-firing.
  • Existing per-button sensor entities (`KeypadButton` → `Platform.SENSOR` in `NODE_FILTERS`) are unchanged so existing automations keep working.

A future PR will mark both the `isy994_control` bus event and the per-button sensor entities deprecated, with the standard deprecation cycle, don't want to cloud this change with Breaking Changes since more work is needed to be able to get rid of that bus event firing from the entity.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (`ruff format homeassistant tests`)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: `python3 -m script.hassfest`.
  • New or updated dependencies have been added to `requirements_all.txt`.
    Updated by running `python3 -m script.gen_requirements_all`.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

Adds an event entity per Insteon load / keypad-button node so physical
button presses (on, off, fast on/off, fade up/down/stop) are exposed
as first-class entities. Replaces the device-trigger approach proposed
in home-assistant#169694 per reviewer feedback — device triggers will fall out of
the new entities automatically once that PR is reworked on top of
this one.

The existing isy994_control bus event still fires from the load
entity (switch/light) for the same node, and the existing per-button
sensor entities are unchanged, so existing automations keep working.
A future PR will mark both the bus event and the per-button sensors
deprecated.
@home-assistant

home-assistant Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Hey there @bdraco, mind taking a look at this pull request as it has been labeled with an integration (isy994) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of isy994 can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant mark-draft Mark the pull request as draft.
  • @home-assistant ready-for-review Remove the draft status from the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign isy994 Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant update-branch Update the pull request branch with the base branch.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) on the pull request.

@shbatm
shbatm marked this pull request as draft May 5, 2026 00:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an event platform to the isy994 integration to expose physical Insteon button/control presses (on/off/fast/fade) as first-class Home Assistant event entities, enabling automation triggers directly from entities rather than relying on the legacy isy994_control bus event.

Changes:

  • Introduces a new event platform (ISYButtonEvent) that maps pyisy control events to HA event_type values and disables KeypadLinc sub-button entities by default.
  • Extends node categorization to allow parallel platform classification (nodes can be classified into Platform.EVENT in addition to their primary platform).
  • Adds tests + snapshots for event entity creation, default-disable behavior, and control→event mapping; also centralizes the isy994_control event name into a constant.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
homeassistant/components/isy994/const.py Adds EVENT_ISY994_CONTROL, introduces NODE_PARALLEL_PLATFORMS, and defines event-node filters for parallel classification.
homeassistant/components/isy994/helpers.py Implements parallel node classification so nodes can also be tagged for Platform.EVENT.
homeassistant/components/isy994/models.py Ensures IsyData tracks parallel-platform nodes and includes event-entity unique IDs for registry cleanup.
homeassistant/components/isy994/entity.py Replaces the hardcoded "isy994_control" event name with EVENT_ISY994_CONTROL.
homeassistant/components/isy994/event.py Adds the new event entity implementation and control→event_type mapping.
homeassistant/components/isy994/strings.json Adds translations for the new event entity and its event_type attribute states.
tests/components/isy994/test_event.py Adds functional tests and a snapshot test for the new event platform.
tests/components/isy994/snapshots/test_event.ambr Adds snapshots covering the created event entities and their registry entries/states.

Comment thread homeassistant/components/isy994/event.py
Event entities only need control_events; status_events would trigger
unnecessary async_write_ha_state on every node level change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread homeassistant/components/isy994/event.py
Comment thread homeassistant/components/isy994/const.py Outdated
shbatm and others added 2 commits May 5, 2026 09:36
Disambiguates from the existing button-platform unique IDs (per review).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Inherit async_added_to_hass from ISYNodeEntity so status_events keep
availability in sync when the node is enabled/disabled. The overridden
async_on_control still skips bus.fire to avoid double-firing
isy994_control alongside the load entity.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Copilot AI review requested due to automatic review settings May 5, 2026 09:40
Avoids per-status-update state writes the earlier review flagged while
keeping availability in sync via a NC_NODE_ENABLED-filtered subscription,
matching the pattern in ISYAuxControlEntity.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment on lines +42 to +50
CONTROL_TO_EVENT_TYPE: Final[dict[str, str]] = {
CMD_ON: "on",
CMD_OFF: "off",
CMD_ON_FAST: "fast_on",
CMD_OFF_FAST: "fast_off",
CMD_FADE_UP: "fade_up",
CMD_FADE_DOWN: "fade_down",
CMD_FADE_STOP: "fade_stop",
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does every device have every event type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every device type added in the node filters has these basic event types. Even on/off switches can send fade up/down/stop which are Insteon's equivalent of hold / release. It may get more complicated if/when this gets expanded to Z-Wave and Node Server supports, but for Insteon--these are core events.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How recognizable are the fade terms for insteon users? would it make more sense to use hold/release, which might be more understandable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're standard language in the Universal Devices Admin Console:
image
image

Control defs from the eisy itself:

<control ID="33"><name>FDUP</name><label>Fade Up</label></control>
<control ID="34"><name>FDDOWN</name><label>Fade Down</label></control>
<control ID="35"><name>FDSTOP</name><label>Fade Stop</label></control>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How clear are these for the end user? As in, if this now only makes it clear for the people who access the console and those people would understand a variant with hold/release, wouldn't that make it easier for a broader audience? Like I am also not sure how often insteon is actually programmed by people or by installers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right that is good to mention, thanks for that! So from what I understand you want to keep the insteon naming?

Let me also add some context from our side as well, for example there's an architectural proposal for button event entities at home-assistant/architecture#1377. The proposal would aim to make it easier for people to automate. Another cool use case this would unlock is for example if you have a hue remote and an insteon remote, and you can target both and be like, if any is pressed and held, do x. This is just a proposal so it might not make it, but it might.

In any case, thank you for the context, I will drop this in the core team chat to see what others think about this

@shbatm shbatm May 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. I've been keeping an eye on that discussion too, it would be great to standardize but seems like everything has its quirks.

Leaving for record here: the supported UDI eisy "Button press"-type event commands are here. The insteon integration would align with most.

My bias would be to keep the Insteon naming but will align to alternative if needed. With the basic matter support proposed there would need to be some additional entities added here to separate out the on/off triggers for dimmers (right now one node vs two like matter/z-wave).

Let me know how you decide to proceed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I just discussed this in the core team, and the conclusion is is that we rather use recognizable names (ideally from that proposal) instead of adding more new ones. https://discord.com/channels/330944238910963714/1475552721515053268/1501253582744322161

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I added a comment over there (home-assistant/architecture#1377 (comment)). I think I'll have to sit on this until that proposal gets a little more solidified on the path forward.

@shbatm shbatm Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joostlek, following up on this now that the arch discussion has finished. I want to agree one thing before conforming to the rest of the proposal.

The proposal includes:

A note on directional buttons

Some physical buttons report direction (e.g. up/down, on/off) from a single source. The recommended approach is to expose these as separate BUTTON entities, one per direction. This composes naturally with the standard event types and keeps automations straightforward. Including a direction event data field on a single entity is a valid alternative for cases where splitting into multiple entities does not make sense, but this is not part of the standard.

I advocated for this specifically because Insteon Keypadlinc Buttons are one physical button that changes the direction they emit based on the sequence in which they are pressed (see comment on the arch discussion ), and can't be separated into two logical entities.

For ease of implementation and consistency within the integration, are you OK with keeping the same, direction field approach for the other paddle switches too?

Justification: these are reported as one entity from the controller (unlike Z-Wave which has up and down scene controls separate), and we would have to rely on home assistant separating everything into two entities and tracking both states. There are opportunities for race conditions on Long presses if we try and separate these. For example, FDSTOP/ long_press_end sometimes gets lost on the network if someone switches from holding up to holding down quickly. If these were separate entities, one might get stuck in a held down state without custom logic to guard against.

Comment thread homeassistant/components/isy994/event.py
Comment on lines +121 to +122
if hasattr(self._node, "control_events"):
self._control_handler = self._node.control_events.subscribe(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would node not have control events?

@shbatm shbatm May 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little bit of a typing guard -- _node could also be a Group class from PyISY, which does not have the "control_events" attribute. A Group shouldn't be assigned to the Event platform but was guarding here to be safe. These hasattr checks and typing quirks throughout this integration are on my roadmap to eliminate with an eventual migration to pyisyox, but was aiming to stay consistent with current practice here. I think the filter in helpers.py should ensure only Node instances reach this code, so if you want me to drop it I can.

Comment thread homeassistant/components/isy994/strings.json Outdated
Comment thread homeassistant/components/isy994/strings.json Outdated
Comment thread homeassistant/components/isy994/strings.json Outdated
@home-assistant
home-assistant Bot marked this pull request as draft May 5, 2026 10:23
@home-assistant

home-assistant Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

- Remove redundant button entity name (device class default is used)
- Sentence case for fade/fast event_type labels
- Use common::state::on/off references for on/off labels
- Move EVENT_BUTTON_UNIQUE_ID_SUFFIX from const.py to event.py
  (platform-specific constant); models.py imports it from event.py

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@shbatm
shbatm marked this pull request as ready for review May 5, 2026 12:39
Copilot AI review requested due to automatic review settings May 5, 2026 12:39
@home-assistant
home-assistant Bot requested a review from joostlek May 5, 2026 12:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings July 20, 2026 18:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Comment on lines +149 to +152
event_type = CONTROL_TO_EVENT_TYPE.get(event.control)
if event_type is None:
return
self._trigger_event(event_type)
Comment on lines +80 to +84
for entry in er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id
):
if entry.disabled_by:
entity_registry.async_update_entity(entry.entity_id, disabled_by=None)
Comment on lines +357 to +361
FILTER_INSTEON_TYPE: [
"1.14.",
"1.32.",
"1.45.",
"1.65.",
Comment thread homeassistant/components/isy994/helpers.py Outdated
Comment on lines +45 to +49
CONTROL_TO_EVENT_TYPE: Final[dict[str, str]] = {
CMD_ON: "on",
CMD_OFF: "off",
CMD_ON_FAST: "fast_on",
CMD_OFF_FAST: "fast_off",
Comment on lines +76 to +77
if name.startswith(parent_name):
return name[len(parent_name) :].lstrip(" -_:.") or name
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Copilot AI review requested due to automatic review settings July 20, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

homeassistant/components/isy994/event.py:152

  • Gate event emission until PyISY reports the WebSocket as fully synchronized. The integration is still pinned to pyisy 3.6.1, which predates automicus/PyISY#513, and this handler never checks the synchronized connection state; consequently the controller's replayed DON/DOF frames trigger automations on startup, reload, and reconnect. This needs the released PyISY backport, a connection-state guard here, and regression coverage before these entities are enabled.
        event_type = CONTROL_TO_EVENT_TYPE.get(event.control)
        if event_type is None:
            return
        self._trigger_event(event_type)

homeassistant/components/isy994/event.py:78

  • Strip the parent name only when it ends at a label boundary. The current raw prefix check turns unrelated names such as parent Hall and button Hallway B into way B, producing a corrupted entity name.
    if name.startswith(parent_name):
        return name[len(parent_name) :].lstrip(" -_:.") or name
    return name

Comment on lines +124 to +135
if hasattr(self._node, "control_events"):
self._control_handler = self._node.control_events.subscribe(
self.async_on_control
)
self._change_handler = self._node.isy.nodes.status_events.subscribe(
self._async_on_availability_change,
event_filter={
TAG_ADDRESS: self._node.address,
ATTR_ACTION: NC_NODE_ENABLED,
},
key=self.unique_id,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants