From 701eb032115f2f468d71c1c331ae18a2f1b3f4a8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 27 Jul 2026 17:39:49 +0200 Subject: [PATCH] Update MQTT device registry event handling (#176965) --- homeassistant/components/mqtt/entity.py | 23 ++++----------- tests/components/mqtt/test_discovery.py | 38 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/mqtt/entity.py b/homeassistant/components/mqtt/entity.py index 65edaf103317..70c8283744f5 100644 --- a/homeassistant/components/mqtt/entity.py +++ b/homeassistant/components/mqtt/entity.py @@ -959,9 +959,7 @@ class MqttDiscoveryDeviceUpdateMixin(ABC): self, event: Event[EventDeviceRegistryUpdatedData] ) -> None: """Handle the manual removal of a device.""" - if self._skip_device_removal or not async_removed_from_device( - self.hass, event, cast(str, self._device_id), self._config_entry_id - ): + if self._skip_device_removal or not async_removed_from_device(event): return # Prevent a second cleanup round after the device is removed self._remove_device_updated() @@ -1747,20 +1745,11 @@ def update_device( @callback def async_removed_from_device( - hass: HomeAssistant, event: Event[EventDeviceRegistryUpdatedData], - mqtt_device_id: str, - config_entry_id: str, ) -> bool: - """Check if the passed event indicates MQTT was removed from a device.""" - if event.data["action"] == "update": - if "config_entries" not in event.data["changes"]: - return False - device_registry = dr.async_get(hass) - if ( - device_entry := device_registry.async_get(mqtt_device_id) - ) and config_entry_id in device_entry.config_entries: - # Not removed from device - return False + """Check if the passed event indicates MQTT was removed from a device. - return True + A device is associated with a single config entry, so MQTT is removed from a + device only when the device itself is removed. + """ + return event.data["action"] == "remove" diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 26abd4018071..b8241c860b79 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -26,6 +26,7 @@ from homeassistant.components.mqtt.discovery import ( MQTTDiscoveryPayload, async_start, ) +from homeassistant.components.mqtt.entity import async_removed_from_device from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.components.mqtt.schemas import ( DEVICE_DISCOVERY_SCHEMA, @@ -3495,3 +3496,40 @@ async def test_shared_qos_with_device_discovery( mqtt_mock.async_subscribe.assert_has_calls( [call("foobar/sensors/bla2/state", ANY, qos, "utf-8", ANY)] ) + + +@pytest.mark.parametrize( + ("event_data", "expected"), + [ + pytest.param( + {"action": "remove", "device_id": "abc", "device": {}}, + True, + id="remove", + ), + pytest.param( + { + "action": "update", + "device_id": "abc", + "changes": {"config_entry_id": "mqtt_entry_id"}, + }, + False, + id="update-config-entry", + ), + pytest.param( + {"action": "update", "device_id": "abc", "changes": {"name": "New name"}}, + False, + id="update-other", + ), + ], +) +async def test_async_removed_from_device( + event_data: dr.EventDeviceRegistryUpdatedData, + expected: bool, +) -> None: + """MQTT leaves a device only when the device is removed. + + A device belongs to a single config entry, so a config-entry change on an update + event is not a removal; only a 'remove' action is. + """ + event = Event(dr.EVENT_DEVICE_REGISTRY_UPDATED, event_data) + assert async_removed_from_device(event) is expected