From cd44bf2ed8a2b9477b00d7ff196535ee120b7f43 Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 15:13:35 -0700 Subject: [PATCH 1/9] Improve/fix Insteon event handling This fixes bug #162946. Insteon sensor events are missing the data about the event. In the case of the leak sensor, the heartbeat event looked the same regardless of whether it was from the device heartbeat or the heartbeat manager missed heartbeat event. This needs pyinsteon pull request https://github.com/pyinsteon/pyinsteon/pull/442 in order to have the full data in the event. This also cleans up the event names by removed the redundant "event" suffix on most of the events and also simplifies the event processing code. Both the existing events and the new events without the "event" suffix are fired for compatibility with existing automations. A deprecation warning is added to the event data. --- homeassistant/components/insteon/const.py | 7 +- homeassistant/components/insteon/utils.py | 84 ++++++++++++++++------- 2 files changed, 64 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index 11e1943aa736d6..32c23fb1298d0e 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -128,11 +128,10 @@ "p", ] -EVENT_GROUP_ON = "insteon.button_on" -EVENT_GROUP_OFF = "insteon.button_off" -EVENT_GROUP_ON_FAST = "insteon.button_on_fast" -EVENT_GROUP_OFF_FAST = "insteon.button_off_fast" EVENT_CONF_BUTTON = "button" +EVENT_CONF_BATTERY = "battery" +EVENT_CONF_HEARTBEAT = "heartbeat" +EVENT_CONF_MOISTURE = "moisture" STATE_NAME_LABEL_MAP = { DIMMABLE_LIGHT_MAIN: "Main", diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 229ed007d0e594..25bdbabc74b8f6 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -10,6 +10,18 @@ from pyinsteon.address import Address from pyinsteon.constants import ALDBStatus, DeviceAction from pyinsteon.device_types.device_base import Device +from pyinsteon.events import ( + HEARTBEAT_EVENT, + LEAK_DRY_EVENT, + LEAK_WET_EVENT, + LOW_BATTERY_EVENT, + OFF_EVENT, + OFF_FAST_EVENT, + ON_EVENT, + ON_FAST_EVENT, + Event, +) +from serial.tools import list_ports from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT, Event from homeassistant.components import usb @@ -21,11 +33,10 @@ from .const import ( DOMAIN, + EVENT_CONF_BATTERY, EVENT_CONF_BUTTON, - EVENT_GROUP_OFF, - EVENT_GROUP_OFF_FAST, - EVENT_GROUP_ON, - EVENT_GROUP_ON_FAST, + EVENT_CONF_HEARTBEAT, + EVENT_CONF_MOISTURE, SIGNAL_ADD_ENTITIES, ) from .ipdb import get_device_platform_groups, get_device_platforms @@ -52,30 +63,57 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None: @callback def async_fire_insteon_event( - name: str, address: Address, group: int, button: str | None = None + name: str, + address: Address, + group: int, + button: str | None = None, + low_battery: bool | None = None, + heartbeat: bool | None = None, + dry: bool | None = None, ): - # Firing an event when a button is pressed. - if button and button[-2] == "_": - button_id = button[-1].lower() - else: - button_id = None - + event = name schema = {CONF_ADDRESS: address, "group": group} - if button_id: - schema[EVENT_CONF_BUTTON] = button_id - if name == ON_EVENT: - event = EVENT_GROUP_ON - elif name == OFF_EVENT: - event = EVENT_GROUP_OFF - elif name == ON_FAST_EVENT: - event = EVENT_GROUP_ON_FAST - elif name == OFF_FAST_EVENT: - event = EVENT_GROUP_OFF_FAST - else: - event = f"insteon.{name}" + + # Prefix button events with "button_" and add the button number to the event data. + if ( + name in (ON_EVENT, OFF_EVENT, ON_FAST_EVENT, OFF_FAST_EVENT) + and button is not None + and button[-2] == "_" + ): + schema[EVENT_CONF_BUTTON] = button[-1].lower() + event = f"button_{event}" + + # Low battery + if name == LOW_BATTERY_EVENT and low_battery is not None: + schema[EVENT_CONF_BATTERY] = "low" if low_battery else "ok" + + # Heartbeat missed + if name == HEARTBEAT_EVENT and heartbeat is not None: + schema[EVENT_CONF_HEARTBEAT] = "missed" if heartbeat else "received" + + # Wet / dry for leak sensors + if name in (LEAK_WET_EVENT, LEAK_DRY_EVENT) and dry is not None: + schema[EVENT_CONF_MOISTURE] = "wet" if not dry else "dry" + + # Prefix the event name with "insteon." to avoid conflicts with other integrations and to make it clear that the event is related to Insteon devices. + event = f"insteon.{event}" + legacy_event = event + + # Remove the redundant "_event" suffix from the event name. + if event[-6:] == "_event": + event = event[:-6] + + # Fire the event along with the data about the device address, group, button, etc _LOGGER.debug("Firing event %s with %s", event, schema) hass.bus.async_fire(event, schema) + # For backward compatibility with custom automations that may be using the old event names, we will also fire an event with the old event name. + schema["deprecated"] = ( + "Use the event name without the '_event' suffix instead, as the old event name will eventually be removed in a future release." + ) + _LOGGER.debug("Firing event %s with %s", legacy_event, schema) + hass.bus.async_fire(legacy_event, schema) + if str(device.address).startswith("X10"): return From 9c0b5a4bd98307fd5e15289fefad4f95be78b26b Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 15:39:40 -0700 Subject: [PATCH 2/9] Update homeassistant/components/insteon/utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/insteon/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 25bdbabc74b8f6..c6617193eed72d 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -22,7 +22,6 @@ Event, ) from serial.tools import list_ports -from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT, Event from homeassistant.components import usb from homeassistant.const import CONF_ADDRESS, Platform From 8002fbc2aa875ce959e79e88c9f8be9d8dcef245 Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 16:25:30 -0700 Subject: [PATCH 3/9] make copilot suggested changes rename heartbeat to heartbeat_missed. make a legacy copy of the event schema. only send legacy event if it's different from new event. --- homeassistant/components/insteon/utils.py | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index c6617193eed72d..091801cc3ad73e 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -21,8 +21,6 @@ ON_FAST_EVENT, Event, ) -from serial.tools import list_ports - from homeassistant.components import usb from homeassistant.const import CONF_ADDRESS, Platform from homeassistant.core import HomeAssistant, callback @@ -67,7 +65,7 @@ def async_fire_insteon_event( group: int, button: str | None = None, low_battery: bool | None = None, - heartbeat: bool | None = None, + heartbeat_missed: bool | None = None, dry: bool | None = None, ): event = name @@ -87,8 +85,8 @@ def async_fire_insteon_event( schema[EVENT_CONF_BATTERY] = "low" if low_battery else "ok" # Heartbeat missed - if name == HEARTBEAT_EVENT and heartbeat is not None: - schema[EVENT_CONF_HEARTBEAT] = "missed" if heartbeat else "received" + if name == HEARTBEAT_EVENT and heartbeat_missed is not None: + schema[EVENT_CONF_HEARTBEAT] = "missed" if heartbeat_missed else "received" # Wet / dry for leak sensors if name in (LEAK_WET_EVENT, LEAK_DRY_EVENT) and dry is not None: @@ -106,12 +104,16 @@ def async_fire_insteon_event( _LOGGER.debug("Firing event %s with %s", event, schema) hass.bus.async_fire(event, schema) - # For backward compatibility with custom automations that may be using the old event names, we will also fire an event with the old event name. - schema["deprecated"] = ( - "Use the event name without the '_event' suffix instead, as the old event name will eventually be removed in a future release." - ) - _LOGGER.debug("Firing event %s with %s", legacy_event, schema) - hass.bus.async_fire(legacy_event, schema) + if legacy_event != event: + # For backward compatibility with custom automations that may be using the old event names, we will also fire an event with the old event name. + legacy_schema = { + **schema, + "deprecated": ( + "Use the event name without the '_event' suffix instead, as the old event name will eventually be removed in a future release." + ), + } + _LOGGER.debug("Firing event %s with %s", legacy_event, legacy_schema) + hass.bus.async_fire(legacy_event, legacy_schema) if str(device.address).startswith("X10"): return From 098352e9972a3e26d985d5bc76bc9c6c78e27885 Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 16:32:53 -0700 Subject: [PATCH 4/9] split long comment into two lines --- homeassistant/components/insteon/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 091801cc3ad73e..8cf791df2ab83a 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -105,7 +105,8 @@ def async_fire_insteon_event( hass.bus.async_fire(event, schema) if legacy_event != event: - # For backward compatibility with custom automations that may be using the old event names, we will also fire an event with the old event name. + # For backward compatibility with custom automations that may be using the old event names, + # we will also fire an event with the old event name. legacy_schema = { **schema, "deprecated": ( From c91bf81d6fe139a06e05e41b31f3cf343c1ad94f Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 16:53:42 -0700 Subject: [PATCH 5/9] add blank line after import --- homeassistant/components/insteon/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 8cf791df2ab83a..ea26ece7958c91 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -21,6 +21,7 @@ ON_FAST_EVENT, Event, ) + from homeassistant.components import usb from homeassistant.const import CONF_ADDRESS, Platform from homeassistant.core import HomeAssistant, callback From 9337eb6eea457e85ba227077844bedbf66743a26 Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 17:05:10 -0700 Subject: [PATCH 6/9] more copilot suggested changes --- homeassistant/components/insteon/utils.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index ea26ece7958c91..70245fea875f74 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -73,13 +73,10 @@ def async_fire_insteon_event( schema = {CONF_ADDRESS: address, "group": group} # Prefix button events with "button_" and add the button number to the event data. - if ( - name in (ON_EVENT, OFF_EVENT, ON_FAST_EVENT, OFF_FAST_EVENT) - and button is not None - and button[-2] == "_" - ): - schema[EVENT_CONF_BUTTON] = button[-1].lower() + if name in (ON_EVENT, OFF_EVENT, ON_FAST_EVENT, OFF_FAST_EVENT): event = f"button_{event}" + if button is not None and len(button) >= 2 and button[-2] == "_": + schema[EVENT_CONF_BUTTON] = button[-1].lower() # Low battery if name == LOW_BATTERY_EVENT and low_battery is not None: @@ -97,9 +94,7 @@ def async_fire_insteon_event( event = f"insteon.{event}" legacy_event = event - # Remove the redundant "_event" suffix from the event name. - if event[-6:] == "_event": - event = event[:-6] + event = event.removesuffix("_event") # Fire the event along with the data about the device address, group, button, etc _LOGGER.debug("Firing event %s with %s", event, schema) From 412e38399908de2a91a906eb88a55eaa712d5205 Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 17:26:32 -0700 Subject: [PATCH 7/9] Add Insteon test for event propagation --- tests/components/insteon/test_utils.py | 145 +++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 tests/components/insteon/test_utils.py diff --git a/tests/components/insteon/test_utils.py b/tests/components/insteon/test_utils.py new file mode 100644 index 00000000000000..722d34f2a7bb7f --- /dev/null +++ b/tests/components/insteon/test_utils.py @@ -0,0 +1,145 @@ +"""Tests for Insteon utils.""" + +from __future__ import annotations + +from types import SimpleNamespace +from unittest.mock import MagicMock + +from pyinsteon.address import Address +from pyinsteon.events import ( + HEARTBEAT_EVENT, + LEAK_DRY_EVENT, + LEAK_WET_EVENT, + LOW_BATTERY_EVENT, + OFF_EVENT, + ON_EVENT, + Event, +) +import pytest + +from homeassistant.components.insteon.const import ( + EVENT_CONF_BATTERY, + EVENT_CONF_BUTTON, + EVENT_CONF_HEARTBEAT, + EVENT_CONF_MOISTURE, +) +from homeassistant.components.insteon.utils import add_insteon_events +from homeassistant.const import CONF_ADDRESS +from homeassistant.core import HomeAssistant + +from tests.common import async_capture_events + + +def test_add_insteon_events_skips_x10_devices(hass: HomeAssistant) -> None: + """X10 devices should not register Insteon event subscribers.""" + mock_event = MagicMock() + x10_address = MagicMock() + x10_address.__str__ = MagicMock(return_value="X10.A.1") + + device = SimpleNamespace(address=x10_address, events={"on": mock_event}) + + add_insteon_events(hass, device) + + mock_event.subscribe.assert_not_called() + + +def test_add_insteon_events_registers_string_keyed_events(hass: HomeAssistant) -> None: + """Events keyed by name should subscribe the HA bus callback.""" + addr = Address("11.11.11") + mock_event = MagicMock() + device = SimpleNamespace( + address=addr, + events={"any_key": mock_event}, + ) + + add_insteon_events(hass, device) + + mock_event.subscribe.assert_called_once() + (listener,), kwargs = mock_event.subscribe.call_args + assert kwargs.get("force_strong_ref") is True + assert callable(listener) + + +def test_add_insteon_events_registers_grouped_events(hass: HomeAssistant) -> None: + """Events nested under an integer group should each be registered.""" + addr = Address("22.22.22") + ev_a = MagicMock() + ev_b = MagicMock() + device = SimpleNamespace(address=addr, events={3: {"a": ev_a, "b": ev_b}}) + + add_insteon_events(hass, device) + + ev_a.subscribe.assert_called_once() + ev_b.subscribe.assert_called_once() + + +async def test_add_insteon_events_on_event_fires_bus(hass: HomeAssistant) -> None: + """ON events should fire new and legacy Insteon bus event names.""" + new_events = async_capture_events(hass, "insteon.button_on") + legacy_events = async_capture_events(hass, "insteon.button_on_event") + + addr = Address("33.33.33") + on_event = Event(ON_EVENT, addr, group=4, button="button_2") + device = SimpleNamespace(address=addr, events={"on": on_event}) + + add_insteon_events(hass, device) + on_event.trigger(255) + await hass.async_block_till_done() + + assert len(new_events) == 1 + assert new_events[0].data[CONF_ADDRESS] == addr.id + assert new_events[0].data["group"] == 4 + assert new_events[0].data[EVENT_CONF_BUTTON] == "2" + + assert len(legacy_events) == 1 + assert "deprecated" in legacy_events[0].data + + +async def test_add_insteon_events_off_event_fires_bus(hass: HomeAssistant) -> None: + """OFF events use the button_ prefix and suffix stripping.""" + captured = async_capture_events(hass, "insteon.button_off") + + addr = Address("44.44.44") + off_event = Event(OFF_EVENT, addr, group=0, button="xy") + device = SimpleNamespace(address=addr, events={"off": off_event}) + + add_insteon_events(hass, device) + off_event.trigger(0) + await hass.async_block_till_done() + + assert len(captured) == 1 + assert EVENT_CONF_BUTTON not in captured[0].data + + +@pytest.mark.parametrize( + ("event_name", "kwargs", "expected_key", "expected_value"), + [ + (LOW_BATTERY_EVENT, {"low_battery": True}, EVENT_CONF_BATTERY, "low"), + (LOW_BATTERY_EVENT, {"low_battery": False}, EVENT_CONF_BATTERY, "ok"), + (HEARTBEAT_EVENT, {"heartbeat_missed": True}, EVENT_CONF_HEARTBEAT, "missed"), + (HEARTBEAT_EVENT, {"heartbeat_missed": False}, EVENT_CONF_HEARTBEAT, "received"), + (LEAK_WET_EVENT, {"dry": False}, EVENT_CONF_MOISTURE, "wet"), + (LEAK_DRY_EVENT, {"dry": True}, EVENT_CONF_MOISTURE, "dry"), + ], +) +async def test_add_insteon_events_listener_optional_fields( + hass: HomeAssistant, + event_name: str, + kwargs: dict[str, bool], + expected_key: str, + expected_value: str, +) -> None: + """Optional subscriber kwargs map to Insteon event schema fields.""" + mock_event = MagicMock() + addr = Address("55.55.55") + device = SimpleNamespace(address=addr, events={"evt": mock_event}) + + add_insteon_events(hass, device) + (listener,) = mock_event.subscribe.call_args[0] + + captured = async_capture_events(hass, f"insteon.{event_name.removesuffix('_event')}") + listener(event_name, addr.id, 1, **kwargs) + await hass.async_block_till_done() + + assert len(captured) == 1 + assert captured[0].data[expected_key] == expected_value From b5417a39b5b50015e8d4a7932d85f1178ed60235 Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 17:36:54 -0700 Subject: [PATCH 8/9] Reformat code for shorter lines --- tests/components/insteon/test_utils.py | 49 ++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/tests/components/insteon/test_utils.py b/tests/components/insteon/test_utils.py index 722d34f2a7bb7f..9ba0aa67ecb9aa 100644 --- a/tests/components/insteon/test_utils.py +++ b/tests/components/insteon/test_utils.py @@ -114,12 +114,46 @@ async def test_add_insteon_events_off_event_fires_bus(hass: HomeAssistant) -> No @pytest.mark.parametrize( ("event_name", "kwargs", "expected_key", "expected_value"), [ - (LOW_BATTERY_EVENT, {"low_battery": True}, EVENT_CONF_BATTERY, "low"), - (LOW_BATTERY_EVENT, {"low_battery": False}, EVENT_CONF_BATTERY, "ok"), - (HEARTBEAT_EVENT, {"heartbeat_missed": True}, EVENT_CONF_HEARTBEAT, "missed"), - (HEARTBEAT_EVENT, {"heartbeat_missed": False}, EVENT_CONF_HEARTBEAT, "received"), - (LEAK_WET_EVENT, {"dry": False}, EVENT_CONF_MOISTURE, "wet"), - (LEAK_DRY_EVENT, {"dry": True}, EVENT_CONF_MOISTURE, "dry"), + # Low battery status events + ( + LOW_BATTERY_EVENT, + {"low_battery": True}, + EVENT_CONF_BATTERY, + "low" + ), + ( + LOW_BATTERY_EVENT, + {"low_battery": False}, + EVENT_CONF_BATTERY,"ok" + ), + + # Heartbeat events + ( + HEARTBEAT_EVENT, + {"heartbeat_missed": True}, + EVENT_CONF_HEARTBEAT, + "missed" + ), + ( + HEARTBEAT_EVENT, + {"heartbeat_missed": False}, + EVENT_CONF_HEARTBEAT, + "received" + ), + + # Leak sensor wet/dry events + ( + LEAK_WET_EVENT, + {"dry": False}, + EVENT_CONF_MOISTURE, + "wet" + ), + ( + LEAK_DRY_EVENT, + {"dry": True}, + EVENT_CONF_MOISTURE, + "dry" + ), ], ) async def test_add_insteon_events_listener_optional_fields( @@ -137,7 +171,8 @@ async def test_add_insteon_events_listener_optional_fields( add_insteon_events(hass, device) (listener,) = mock_event.subscribe.call_args[0] - captured = async_capture_events(hass, f"insteon.{event_name.removesuffix('_event')}") + captured = async_capture_events( + hass, f"insteon.{event_name.removesuffix('_event')}") listener(event_name, addr.id, 1, **kwargs) await hass.async_block_till_done() From 31c985c54587cf6e4d58ddbce635ca58be9791dc Mon Sep 17 00:00:00 2001 From: Stuart Wyatt Date: Mon, 20 Apr 2026 17:42:37 -0700 Subject: [PATCH 9/9] Use mixed/inconsistent formatting to pass prek --- tests/components/insteon/test_utils.py | 44 +++++--------------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/tests/components/insteon/test_utils.py b/tests/components/insteon/test_utils.py index 9ba0aa67ecb9aa..a48c777047bdff 100644 --- a/tests/components/insteon/test_utils.py +++ b/tests/components/insteon/test_utils.py @@ -114,46 +114,17 @@ async def test_add_insteon_events_off_event_fires_bus(hass: HomeAssistant) -> No @pytest.mark.parametrize( ("event_name", "kwargs", "expected_key", "expected_value"), [ - # Low battery status events - ( - LOW_BATTERY_EVENT, - {"low_battery": True}, - EVENT_CONF_BATTERY, - "low" - ), - ( - LOW_BATTERY_EVENT, - {"low_battery": False}, - EVENT_CONF_BATTERY,"ok" - ), - - # Heartbeat events - ( - HEARTBEAT_EVENT, - {"heartbeat_missed": True}, - EVENT_CONF_HEARTBEAT, - "missed" - ), + (LOW_BATTERY_EVENT, {"low_battery": True}, EVENT_CONF_BATTERY, "low"), + (LOW_BATTERY_EVENT, {"low_battery": False}, EVENT_CONF_BATTERY, "ok"), + (HEARTBEAT_EVENT, {"heartbeat_missed": True}, EVENT_CONF_HEARTBEAT, "missed"), ( HEARTBEAT_EVENT, {"heartbeat_missed": False}, EVENT_CONF_HEARTBEAT, - "received" - ), - - # Leak sensor wet/dry events - ( - LEAK_WET_EVENT, - {"dry": False}, - EVENT_CONF_MOISTURE, - "wet" - ), - ( - LEAK_DRY_EVENT, - {"dry": True}, - EVENT_CONF_MOISTURE, - "dry" + "received", ), + (LEAK_WET_EVENT, {"dry": False}, EVENT_CONF_MOISTURE, "wet"), + (LEAK_DRY_EVENT, {"dry": True}, EVENT_CONF_MOISTURE, "dry"), ], ) async def test_add_insteon_events_listener_optional_fields( @@ -172,7 +143,8 @@ async def test_add_insteon_events_listener_optional_fields( (listener,) = mock_event.subscribe.call_args[0] captured = async_capture_events( - hass, f"insteon.{event_name.removesuffix('_event')}") + hass, f"insteon.{event_name.removesuffix('_event')}" + ) listener(event_name, addr.id, 1, **kwargs) await hass.async_block_till_done()