diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 2e6ff4f0b34..c86c4ae5688 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -21,6 +21,7 @@ from zwave_js_server.model.notification import ( from zwave_js_server.model.value import Value, ValueNotification from homeassistant.components.hassio import AddonError, AddonManager, AddonState +from homeassistant.components.persistent_notification import async_create from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_DEVICE_ID, @@ -290,6 +291,11 @@ class DriverEvents: controller.on("node removed", self.controller_events.async_on_node_removed) ) + # listen for identify events for the controller + self.config_entry.async_on_unload( + controller.on("identify", self.controller_events.async_on_identify) + ) + async def async_setup_platform(self, platform: Platform) -> None: """Set up platform if needed.""" if platform not in self.platform_setup_tasks: @@ -417,6 +423,41 @@ class ControllerEvents: else: self.remove_device(device) + @callback + def async_on_identify(self, event: dict) -> None: + """Handle identify event.""" + # Get node device + node: ZwaveNode = event["node"] + dev_id = get_device_id(self.driver_events.driver, node) + device = self.dev_reg.async_get_device(identifiers={dev_id}) + assert device + device_name = device.name_by_user or device.name + home_id = self.driver_events.driver.controller.home_id + # We do this because we know at this point the controller has its home ID as + # as it is part of the device ID + assert home_id + # In case the user has multiple networks, we should give them more information + # about the network for the controller being identified. + identifier = "" + if len(self.hass.config_entries.async_entries(DOMAIN)) > 1: + if str(home_id) != self.config_entry.title: + identifier = ( + f"`{self.config_entry.title}`, with the home ID `{home_id}`, " + ) + else: + identifier = f"with the home ID `{home_id}` " + async_create( + self.hass, + ( + f"`{device_name}` has just requested the controller for your Z-Wave " + f"network {identifier}to identify itself. No action is needed from " + "you other than to note the source of the request, and you can safely " + "dismiss this notification when ready." + ), + "New Z-Wave Identify Controller Request", + f"{DOMAIN}.identify_controller.{dev_id[1]}", + ) + @callback def register_node_in_dev_reg(self, node: ZwaveNode) -> dr.DeviceEntry: """Register node in dev reg.""" diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index c421e043413..212ac9d751e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -11,6 +11,7 @@ from zwave_js_server.model.node import Node from zwave_js_server.model.version import VersionInfo from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.components.persistent_notification import async_dismiss from homeassistant.components.zwave_js import DOMAIN from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState @@ -25,7 +26,7 @@ from homeassistant.helpers import ( from .common import AIR_TEMPERATURE_SENSOR, EATON_RF9640_ENTITY -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_get_persistent_notifications @pytest.fixture(name="connect_timeout") @@ -1501,3 +1502,51 @@ async def test_disabled_entity_on_value_removed( } == new_unavailable_entities ) + + +async def test_identify_event( + hass: HomeAssistant, client, multisensor_6, integration +) -> None: + """Test controller identify event.""" + # One config entry scenario + event = Event( + type="identify", + data={ + "source": "controller", + "event": "identify", + "nodeId": multisensor_6.node_id, + }, + ) + dev_id = get_device_id(client.driver, multisensor_6) + msg_id = f"{DOMAIN}.identify_controller.{dev_id[1]}" + + client.driver.controller.receive_event(event) + notifications = async_get_persistent_notifications(hass) + assert len(notifications) == 1 + assert list(notifications)[0] == msg_id + assert notifications[msg_id]["message"].startswith("`Multisensor 6`") + assert "with the home ID" not in notifications[msg_id]["message"] + async_dismiss(hass, msg_id) + + # Add mock config entry to simulate having multiple entries + new_entry = MockConfigEntry(domain=DOMAIN) + new_entry.add_to_hass(hass) + + # Test case where config entry title and home ID don't match + client.driver.controller.receive_event(event) + notifications = async_get_persistent_notifications(hass) + assert len(notifications) == 1 + assert list(notifications)[0] == msg_id + assert ( + "network `Mock Title`, with the home ID `3245146787`" + in notifications[msg_id]["message"] + ) + async_dismiss(hass, msg_id) + + # Test case where config entry title and home ID do match + hass.config_entries.async_update_entry(integration, title="3245146787") + client.driver.controller.receive_event(event) + notifications = async_get_persistent_notifications(hass) + assert len(notifications) == 1 + assert list(notifications)[0] == msg_id + assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]