diff --git a/homeassistant/components/plugwise/coordinator.py b/homeassistant/components/plugwise/coordinator.py index bc12ef4443b..8958ecae930 100644 --- a/homeassistant/components/plugwise/coordinator.py +++ b/homeassistant/components/plugwise/coordinator.py @@ -16,11 +16,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryError +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, LOGGER +from .const import DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, GATEWAY_ID, LOGGER class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]): @@ -83,7 +84,46 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]): except UnsupportedDeviceError as err: raise ConfigEntryError("Device with unsupported firmware") from err else: - self.new_devices = set(data.devices) - self._current_devices - self._current_devices = set(data.devices) + self._async_add_remove_devices(data, self.config_entry) return data + + def _async_add_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None: + """Add new Plugwise devices, remove non-existing devices.""" + # Check for new or removed devices + self.new_devices = set(data.devices) - self._current_devices + removed_devices = self._current_devices - set(data.devices) + self._current_devices = set(data.devices) + + if removed_devices: + self._async_remove_devices(data, entry) + + def _async_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None: + """Clean registries when removed devices found.""" + device_reg = dr.async_get(self.hass) + device_list = dr.async_entries_for_config_entry( + device_reg, self.config_entry.entry_id + ) + # via_device cannot be None, this will result in the deletion + # of other Plugwise Gateways when present! + via_device: str = "" + for device_entry in device_list: + if device_entry.identifiers: + item = list(list(device_entry.identifiers)[0]) + if item[0] == DOMAIN: + # First find the Plugwise via_device, this is always the first device + if item[1] == data.gateway[GATEWAY_ID]: + via_device = device_entry.id + elif ( # then remove the connected orphaned device(s) + device_entry.via_device_id == via_device + and item[1] not in data.devices + ): + device_reg.async_update_device( + device_entry.id, remove_config_entry_id=entry.entry_id + ) + LOGGER.debug( + "Removed %s device %s %s from device_registry", + DOMAIN, + device_entry.model, + item[1], + ) diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index d3f23a18285..26aedf864dc 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -39,7 +39,7 @@ TOM = { "hardware": "1", "location": "f871b8c4d63549319221e294e4f88074", "model": "Tom/Floor", - "name": "Tom Badkamer", + "name": "Tom Zolder", "sensors": { "battery": 99, "temperature": 18.6, @@ -258,3 +258,30 @@ async def test_update_device( for device_entry in list(device_registry.devices.values()): item_list.extend(x[1] for x in device_entry.identifiers) assert "01234567890abcdefghijklmnopqrstu" in item_list + + # Remove the existing Tom/Floor + data.devices.pop("1772a4ea304041adb83f357b751341ff") + with patch(HA_PLUGWISE_SMILE_ASYNC_UPDATE, return_value=data): + async_fire_time_changed(hass, utcnow + timedelta(minutes=1)) + await hass.async_block_till_done() + + assert ( + len( + er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + ) + == 29 + ) + assert ( + len( + dr.async_entries_for_config_entry( + device_registry, mock_config_entry.entry_id + ) + ) + == 6 + ) + item_list: list[str] = [] + for device_entry in list(device_registry.devices.values()): + item_list.extend(x[1] for x in device_entry.identifiers) + assert "1772a4ea304041adb83f357b751341ff" not in item_list