Update MQTT device registry event handling (#176965)

This commit is contained in:
Erik Montnemery
2026-07-27 17:39:49 +02:00
committed by GitHub
parent 19215fd706
commit 701eb03211
2 changed files with 44 additions and 17 deletions
+6 -17
View File
@@ -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"
+38
View File
@@ -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