diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 2688e27c0ac..1ebf2ba44e4 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -37,7 +37,8 @@ from homeassistant.const import ( UnitOfInformation, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -569,7 +570,7 @@ async def async_setup_entry( # noqa: C901 is_enabled = check_legacy_resource( f"{_type}_{argument}", legacy_resources ) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( net_addr_coordinator, @@ -584,7 +585,7 @@ async def async_setup_entry( # noqa: C901 if _type == "last_boot": argument = "" is_enabled = check_legacy_resource(f"{_type}_{argument}", legacy_resources) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( boot_time_coordinator, @@ -599,7 +600,7 @@ async def async_setup_entry( # noqa: C901 if _type.startswith("load_"): argument = "" is_enabled = check_legacy_resource(f"{_type}_{argument}", legacy_resources) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( system_load_coordinator, @@ -614,7 +615,7 @@ async def async_setup_entry( # noqa: C901 if _type.startswith("memory_"): argument = "" is_enabled = check_legacy_resource(f"{_type}_{argument}", legacy_resources) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( memory_coordinator, @@ -630,7 +631,7 @@ async def async_setup_entry( # noqa: C901 is_enabled = check_legacy_resource( f"{_type}_{argument}", legacy_resources ) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( net_io_coordinator, @@ -674,7 +675,7 @@ async def async_setup_entry( # noqa: C901 if _type == "processor_use": argument = "" is_enabled = check_legacy_resource(f"{_type}_{argument}", legacy_resources) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( processor_coordinator, @@ -692,7 +693,7 @@ async def async_setup_entry( # noqa: C901 continue argument = "" is_enabled = check_legacy_resource(f"{_type}_{argument}", legacy_resources) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( cpu_temp_coordinator, @@ -707,7 +708,7 @@ async def async_setup_entry( # noqa: C901 if _type.startswith("swap_"): argument = "" is_enabled = check_legacy_resource(f"{_type}_{argument}", legacy_resources) - loaded_resources.add(f"{_type}_{argument}") + loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( swap_coordinator, @@ -729,6 +730,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources, ) if check_resource not in loaded_resources: + loaded_resources.add(check_resource) split_index = resource.rfind("_") _type = resource[:split_index] argument = resource[split_index + 1 :] @@ -764,6 +766,27 @@ async def async_setup_entry( # noqa: C901 for coordinator in hass.data[DOMAIN_COORDINATORS].values(): await coordinator.async_request_refresh() + @callback + def clean_obsolete_entities() -> None: + """Remove entities which are disabled and not supported from setup.""" + entity_registry = er.async_get(hass) + entities = entity_registry.entities.get_entries_for_config_entry_id( + entry.entry_id + ) + for entity in entities: + if ( + entity.unique_id not in loaded_resources + and entity.disabled is True + and ( + entity_id := entity_registry.async_get_entity_id( + SENSOR_DOMAIN, DOMAIN, entity.unique_id + ) + ) + ): + entity_registry.async_remove(entity_id) + + clean_obsolete_entities() + async_add_entities(entities) diff --git a/tests/components/systemmonitor/test_sensor.py b/tests/components/systemmonitor/test_sensor.py index 9bd5a96accb..4c0c5e179b1 100644 --- a/tests/components/systemmonitor/test_sensor.py +++ b/tests/components/systemmonitor/test_sensor.py @@ -550,3 +550,76 @@ async def test_cpu_percentage_is_zero_returns_unknown( cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") assert cpu_sensor is not None assert cpu_sensor.state == "15" + + +async def test_remove_obsolete_entities( + hass: HomeAssistant, + mock_psutil: Mock, + mock_added_config_entry: ConfigEntry, + caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, + entity_registry: er.EntityRegistry, +) -> None: + """Test we remove sensors that are not actual and disabled.""" + cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") + assert cpu_sensor is None + cpu_sensor_entity = entity_registry.async_get("sensor.system_monitor_processor_use") + assert cpu_sensor_entity.disabled is True + + assert ( + len( + entity_registry.entities.get_entries_for_config_entry_id( + mock_added_config_entry.entry_id + ) + ) + == 37 + ) + + entity_registry.async_update_entity( + "sensor.system_monitor_processor_use", disabled_by=None + ) + freezer.tick(timedelta(minutes=5)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + # Fake an entity which should be removed as not supported and disabled + entity_registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "network_out_veth12345", + disabled_by=er.RegistryEntryDisabler.INTEGRATION, + config_entry=mock_added_config_entry, + has_entity_name=True, + device_id=cpu_sensor_entity.device_id, + translation_key="network_out", + ) + # Fake an entity which should not be removed as not supported but not disabled + entity_registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "network_out_veth54321", + disabled_by=None, + config_entry=mock_added_config_entry, + has_entity_name=True, + device_id=cpu_sensor_entity.device_id, + translation_key="network_out", + ) + await hass.config_entries.async_reload(mock_added_config_entry.entry_id) + await hass.async_block_till_done() + + assert ( + len( + entity_registry.entities.get_entries_for_config_entry_id( + mock_added_config_entry.entry_id + ) + ) + == 38 + ) + + assert ( + entity_registry.async_get("sensor.systemmonitor_network_out_veth12345") is None + ) + assert ( + entity_registry.async_get("sensor.systemmonitor_network_out_veth54321") + is not None + )