diff --git a/homeassistant/components/mqtt/entity.py b/homeassistant/components/mqtt/entity.py index 5b22c4acb8a4bf..8d1643a17830ff 100644 --- a/homeassistant/components/mqtt/entity.py +++ b/homeassistant/components/mqtt/entity.py @@ -31,7 +31,11 @@ CONF_VALUE_TEMPLATE, ) from homeassistant.core import Event, HassJobType, HomeAssistant, callback -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import ( + area_registry as ar, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.device_registry import ( DeviceEntry, DeviceInfo, @@ -1367,6 +1371,7 @@ class MqttEntity( _default_name: str | None _entity_id_format: str _update_registry_entity_id: str | None = None + _update_registry_area_id: str | None = None def __init__( self, @@ -1427,6 +1432,24 @@ def _init_entity_registry(self, discovery_data: DiscoveryInfoType | None) -> Non # if a deleted entity was found self._update_registry_entity_id = self.entity_id + if ( + deleted_entry + and ( + device_info := device_info_from_specifications( + self._config.get(CONF_DEVICE) + ) + ) + is not None + and dr.async_get(self.hass).deleted_devices.get_entry( + identifiers=device_info["identifiers"], + connections=device_info["connections"], + ) + and (suggested_area := device_info.get(ATTR_SUGGESTED_AREA)) + ): + self._update_registry_area_id = ( + ar.async_get(self.hass).async_get_or_create(suggested_area).id + ) + if ( self._config[CONF_ENABLED_BY_DEFAULT] and deleted_entry @@ -1437,8 +1460,7 @@ def _init_entity_registry(self, discovery_data: DiscoveryInfoType | None) -> Non entity_platform, DOMAIN, self.unique_id ) entity_registry.async_update_entity( - recreated_entry.entity_id, - disabled_by=None, + recreated_entry.entity_id, disabled_by=None ) if discovery_data is None: @@ -1466,12 +1488,26 @@ def _init_entity_registry(self, discovery_data: DiscoveryInfoType | None) -> Non @final async def async_added_to_hass(self) -> None: """Subscribe to MQTT events.""" - if self._update_registry_entity_id is not None: + if ( + self._update_registry_entity_id is not None + or self._update_registry_area_id is not None + ): entity_registry = er.async_get(self.hass) - entity_registry.async_update_entity( - self.entity_id, new_entity_id=self._update_registry_entity_id + entry = entity_registry.async_update_entity( + self.entity_id, + new_entity_id=self._update_registry_entity_id or UNDEFINED, + area_id=self._update_registry_area_id or UNDEFINED, ) + if ( + self._update_registry_area_id is not None + and entry.device_id is not None + ): + device_registry = dr.async_get(self.hass) + device_registry.async_update_device( + entry.device_id, area_id=self._update_registry_area_id or UNDEFINED + ) self._update_registry_entity_id = None + self._update_registry_area_id = None await super().async_added_to_hass() await self._async_finish_update_config() diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index dfecd2891a1df1..d479373797d6dc 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -526,13 +526,17 @@ async def test_registry_not_enabled_by_default( assert entry.disabled -async def test_registry_enable_not_enabled_by_default_entity( +async def test_registry_reconfigure_disabled_or_removed_mqtt_device( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, ) -> None: - """Test enabling an entity that was not enabled by default.""" + """Test reconfiguring an entity that was not enabled or removed. + + Assert if a disabled entity can be re-enabled, + and that the suggested area can be changed or deleted devices. + """ await mqtt_mock_entry() discovery_topic = "homeassistant/sensor/bla/config" @@ -543,7 +547,11 @@ async def test_registry_enable_not_enabled_by_default_entity( "enabled_by_default": False, "unique_id": "very_unique", "default_entity_id": "sensor.test", - "device": {"identifiers": "very_unique_device", "name": "test"}, + "device": { + "identifiers": "very_unique_device", + "name": "test", + "suggested_area": "Kitchen", + }, } ) config_enabled = json.json_dumps( @@ -553,7 +561,11 @@ async def test_registry_enable_not_enabled_by_default_entity( "enabled_by_default": True, "unique_id": "very_unique", "default_entity_id": "sensor.test", - "device": {"identifiers": "very_unique_device", "name": "test"}, + "device": { + "identifiers": "very_unique_device", + "name": "test", + "suggested_area": "Bedroom", + }, } ) config_enabled_new_entity_name = json.json_dumps( @@ -563,19 +575,24 @@ async def test_registry_enable_not_enabled_by_default_entity( "enabled_by_default": True, "unique_id": "very_unique", "default_entity_id": "sensor.test_new", - "device": {"identifiers": "very_unique_device", "name": "test"}, + "device": { + "identifiers": "very_unique_device", + "name": "test", + "suggested_area": "Attic", + }, } ) async_fire_mqtt_message(hass, discovery_topic, config_disabled) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.test") assert state is None entry = entity_registry.async_get("sensor.test") assert entry is not None assert entry.disabled assert (device_id := entry.device_id) - assert device_registry.async_get(device_id) is not None + device_registry_item = device_registry.async_get(device_id) + assert device_registry_item.area_id == "kitchen" # Remove the entity and device # At this stage no entry existed during the initialization @@ -588,24 +605,30 @@ async def test_registry_enable_not_enabled_by_default_entity( # Rediscover the previous deleted entity and allow it to be enabled async_fire_mqtt_message(hass, discovery_topic, config_enabled) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.test") assert state is not None entry = entity_registry.async_get("sensor.test") assert entry is not None assert not entry.disabled - assert device_registry.async_get(device_id) is not None + assert entry.area_id == "bedroom" + assert (device_id := entry.device_id) + device_registry_item = device_registry.async_get(device_id) + assert device_registry_item.area_id == "bedroom" # Update entity to not be enabled by default # The entity should stay available as it was enabled before + # The area should not update async_fire_mqtt_message(hass, discovery_topic, config_disabled) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.test") assert state is not None entry = entity_registry.async_get("sensor.test") assert entry is not None assert not entry.disabled - assert device_registry.async_get(device_id) is not None + assert entry.area_id == "bedroom" + device_registry_item = device_registry.async_get(device_id) + assert device_registry_item.area_id == "bedroom" # Delete the entity again async_fire_mqtt_message(hass, discovery_topic, "") @@ -615,15 +638,17 @@ async def test_registry_enable_not_enabled_by_default_entity( # Assert device is cleaned up assert device_registry.async_get(device_id) is None - # Repeat the re-discovery, with a new entity name + # Repeat the re-discovery, with a new entity name and suggested area async_fire_mqtt_message(hass, discovery_topic, config_enabled_new_entity_name) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.test_new") assert state is not None entry = entity_registry.async_get("sensor.test_new") assert entry is not None + assert entry.area_id == "attic" assert not entry.disabled - assert device_registry.async_get(device_id) is not None + device_registry_item = device_registry.async_get(device_id) + assert device_registry_item.area_id == "attic" @pytest.mark.parametrize(