diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index d12d90a02c3..c285ab83bd1 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -28,6 +28,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -97,6 +98,37 @@ async def async_setup_entry( for device in data.devices.values() ] ) + remove_stale_devices(hass, entry, data.devices) + + +def remove_stale_devices( + hass: HomeAssistant, + config_entry: ConfigEntry, + devices: dict[str, SomeComfortDevice], +) -> None: + """Remove stale devices from device registry.""" + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ) + all_device_ids: list = [] + for device in devices.values(): + all_device_ids.append(device.deviceid) + + for device_entry in device_entries: + device_id: str | None = None + + for identifier in device_entry.identifiers: + device_id = identifier[1] + break + + if device_id is None or device_id not in all_device_ids: + # If device_id is None an invalid device entry was found for this config entry. + # If the device_id is not in existing device ids it's a stale device entry. + # Remove config entry from this device entry in either case. + device_registry.async_update_device( + device_entry.id, remove_config_entry_id=config_entry.entry_id + ) class HoneywellUSThermostat(ClimateEntity): diff --git a/tests/components/honeywell/test_init.py b/tests/components/honeywell/test_init.py index f7629fa958e..e5afe311295 100644 --- a/tests/components/honeywell/test_init.py +++ b/tests/components/honeywell/test_init.py @@ -12,6 +12,7 @@ from homeassistant.components.honeywell.const import ( from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from . import init_integration @@ -33,7 +34,10 @@ async def test_setup_entry(hass: HomeAssistant, config_entry: MockConfigEntry) - async def test_setup_multiple_thermostats( - hass: HomeAssistant, config_entry: MockConfigEntry, location, another_device + hass: HomeAssistant, + config_entry: MockConfigEntry, + location: MagicMock, + another_device: MagicMock, ) -> None: """Test that the config form is shown.""" location.devices_by_id[another_device.deviceid] = another_device @@ -50,8 +54,8 @@ async def test_setup_multiple_thermostats_with_same_deviceid( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_entry: MockConfigEntry, - device, - client, + device: MagicMock, + client: MagicMock, ) -> None: """Test Honeywell TCC API returning duplicate device IDs.""" mock_location2 = create_autospec(aiosomecomfort.Location, instance=True) @@ -115,3 +119,48 @@ async def test_no_devices( client.locations_by_id = {} await init_integration(hass, config_entry) assert config_entry.state is ConfigEntryState.SETUP_ERROR + + +async def test_remove_stale_device( + hass: HomeAssistant, + config_entry: MockConfigEntry, + location: MagicMock, + another_device: MagicMock, + client: MagicMock, +) -> None: + """Test that the stale device is removed.""" + location.devices_by_id[another_device.deviceid] = another_device + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + assert ( + hass.states.async_entity_ids_count() == 6 + ) # 2 climate entities; 4 sensor entities + + device_registry = dr.async_get(hass) + device_entry = dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ) + assert len(device_entry) == 2 + assert any((DOMAIN, 1234567) in device.identifiers for device in device_entry) + assert any((DOMAIN, 7654321) in device.identifiers for device in device_entry) + + assert await config_entry.async_unload(hass) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.NOT_LOADED + + del location.devices_by_id[another_device.deviceid] + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + assert ( + hass.states.async_entity_ids_count() == 3 + ) # 1 climate entities; 2 sensor entities + + device_entry = dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ) + assert len(device_entry) == 1 + assert any((DOMAIN, 1234567) in device.identifiers for device in device_entry)