diff --git a/homeassistant/components/litterrobot/coordinator.py b/homeassistant/components/litterrobot/coordinator.py index d4a56b83af2..8e33d675a6d 100644 --- a/homeassistant/components/litterrobot/coordinator.py +++ b/homeassistant/components/litterrobot/coordinator.py @@ -13,6 +13,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -43,6 +44,7 @@ class LitterRobotDataUpdateCoordinator(DataUpdateCoordinator[None]): ) self.account = Account(websession=async_get_clientsession(hass)) + self.previous_members: set[str] = set() async def _async_update_data(self) -> None: """Update all device states from the Litter-Robot API.""" @@ -63,6 +65,22 @@ class LitterRobotDataUpdateCoordinator(DataUpdateCoordinator[None]): translation_placeholders={"error": str(ex)}, ) from ex + current_members = {robot.serial for robot in self.account.robots} | { + pet.id for pet in self.account.pets + } + if stale_members := self.previous_members - current_members: + device_registry = dr.async_get(self.hass) + for device_id in stale_members: + device = device_registry.async_get_device( + identifiers={(DOMAIN, device_id)} + ) + if device: + device_registry.async_update_device( + device_id=device.id, + remove_config_entry_id=self.config_entry.entry_id, + ) + self.previous_members = current_members + async def _async_setup(self) -> None: """Set up the coordinator.""" try: diff --git a/homeassistant/components/litterrobot/quality_scale.yaml b/homeassistant/components/litterrobot/quality_scale.yaml index 2d9c9edb5ef..6500573dea7 100644 --- a/homeassistant/components/litterrobot/quality_scale.yaml +++ b/homeassistant/components/litterrobot/quality_scale.yaml @@ -64,12 +64,7 @@ rules: status: done comment: | This integration doesn't have any cases where raising an issue is needed - stale-devices: - status: todo - comment: | - Currently handled via async_remove_config_entry_device, - but we should be able to remove devices automatically - + stale-devices: done # Platinum async-dependency: done inject-websession: done diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py index 906fd4c37bd..3d016090834 100644 --- a/tests/components/litterrobot/test_init.py +++ b/tests/components/litterrobot/test_init.py @@ -256,12 +256,12 @@ async def test_dynamic_devices( device_registry: dr.DeviceRegistry, freezer: FrozenDateTimeFactory, ) -> None: - """Test new device found.""" + """Test dynamic addition of new devices and removal of stale devices.""" delta_time = timedelta(seconds=305) # 5 minutes + 5 delta seconds entry = await setup_integration(hass, mock_account) - # First check -> there is 1 device in total + # First check -> 1 device created assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 1 mock_account.robots.extend([LitterRobot4(data=ROBOT_4_DATA, account=mock_account)]) @@ -270,5 +270,14 @@ async def test_dynamic_devices( async_fire_time_changed(hass) await hass.async_block_till_done() - # Second check -> added one device + # Second check -> added 1 device assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 2 + + mock_account.robots.pop(0) + + freezer.tick(delta_time) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + # Third check -> removed 1 device + assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 1