diff --git a/homeassistant/components/sofar/__init__.py b/homeassistant/components/sofar/__init__.py index e336d9ba9de3..e1c77f8154a0 100644 --- a/homeassistant/components/sofar/__init__.py +++ b/homeassistant/components/sofar/__init__.py @@ -15,7 +15,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_HOST, CONF_PORT, Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryError from homeassistant.helpers import ( config_validation as cv, @@ -29,6 +29,7 @@ from .const import ( BATTERY_COMPONENTS, CONF_UNIT_ID, DOMAIN, + METER_ENERGY, SCAN_INTERVAL, SETTINGS_SCAN_INTERVAL, ) @@ -51,6 +52,7 @@ _IDENTITY_ATTEMPTS = 3 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) +@callback def _async_remove_stale_waiting_time(hass: HomeAssistant, serial: str) -> None: """Drop the removed waiting-time entity so it doesn't linger unavailable.""" registry = er.async_get(hass) @@ -61,6 +63,24 @@ def _async_remove_stale_waiting_time(hass: HomeAssistant, serial: str) -> None: registry.async_remove(entity_id) +@callback +def _async_remove_denied_meter_energy( + hass: HomeAssistant, serial: str, served: frozenset[str] +) -> None: + """Drop meter sensors a model denies, so they don't linger unavailable.""" + if METER_ENERGY in served: + return + registry = er.async_get(hass) + for description in SENSOR_DESCRIPTIONS: + if description.component != METER_ENERGY: + continue + entity_id = registry.async_get_entity_id( + SENSOR_DOMAIN, DOMAIN, f"{serial}_{description.key}" + ) + if entity_id is not None: + registry.async_remove(entity_id) + + async def _async_read_identity(entry: SofarConfigEntry, device: SofarInverter) -> None: """Read identity once, retrying a few times against a transient blip.""" for attempt in range(_IDENTITY_ATTEMPTS): @@ -156,6 +176,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: SofarConfigEntry) -> boo config_entry_id=entry.entry_id, **readings.device_info ) entry.runtime_data = SofarRuntimeData(readings, settings, inverter.id) + _async_remove_denied_meter_energy( + hass, serial, entry.runtime_data.served_components + ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/sofar/const.py b/homeassistant/components/sofar/const.py index 627e1ba50f95..6e93f65e8a0d 100644 --- a/homeassistant/components/sofar/const.py +++ b/homeassistant/components/sofar/const.py @@ -11,6 +11,8 @@ SETTINGS_SCAN_INTERVAL = 60 CONF_UNIT_ID = "unit_id" +METER_ENERGY = "meter_energy" + BATTERY_COMPONENTS = { n: "battery_1_2" if n <= 2 else "battery_3_8" for n in range(1, 9) } diff --git a/homeassistant/components/sofar/sensor.py b/homeassistant/components/sofar/sensor.py index 70cece2e980d..bd3024519426 100644 --- a/homeassistant/components/sofar/sensor.py +++ b/homeassistant/components/sofar/sensor.py @@ -31,7 +31,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import BATTERY_COMPONENTS +from .const import BATTERY_COMPONENTS, METER_ENERGY from .coordinator import SofarConfigEntry from .entity import SofarEntity, SofarEntityDescription @@ -1153,7 +1153,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ), SofarSensorDescription( key="load_consumption_today", - component="meter_energy", + component=METER_ENERGY, translation_key="load_consumption_today", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1163,7 +1163,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ), SofarSensorDescription( key="load_consumption_total", - component="meter_energy", + component=METER_ENERGY, translation_key="load_consumption_total", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1172,7 +1172,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ), SofarSensorDescription( key="import_energy_today", - component="meter_energy", + component=METER_ENERGY, translation_key="import_energy_today", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1182,7 +1182,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ), SofarSensorDescription( key="import_energy_total", - component="meter_energy", + component=METER_ENERGY, translation_key="import_energy_total", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1191,7 +1191,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ), SofarSensorDescription( key="export_energy_today", - component="meter_energy", + component=METER_ENERGY, translation_key="export_energy_today", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1201,7 +1201,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ), SofarSensorDescription( key="export_energy_total", - component="meter_energy", + component=METER_ENERGY, translation_key="export_energy_total", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, diff --git a/tests/components/sofar/test_init.py b/tests/components/sofar/test_init.py index 1e08aed2e165..c6b1efe4aea0 100644 --- a/tests/components/sofar/test_init.py +++ b/tests/components/sofar/test_init.py @@ -255,6 +255,37 @@ async def test_setup_keeps_meter_sensors_a_model_serves( assert hass.states.get(entity_id).state == "1000.0" +async def test_setup_removes_meter_sensors_a_model_denies( + hass: HomeAssistant, + mock_connection: MockModbusConnection, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test an upgrade drops meter sensors the model turns out to deny.""" + mock_config_entry.add_to_hass(hass) + deny_meter_energy(mock_connection.for_unit(1)) + stale = [ + entity_registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + f"{MOCK_SERIAL}_{key}", + config_entry=mock_config_entry, + ).entity_id + for key in METER_ENERGY_KEYS + ] + + with patch( + "homeassistant.components.sofar.async_get_unit", + side_effect=lambda hass, entry, params, unit_id: mock_connection.for_unit( + unit_id + ), + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done(wait_background_tasks=True) + + assert all(entity_registry.async_get(entity_id) is None for entity_id in stale) + + async def test_setup_keeps_meter_sensors_when_no_mask_is_published( hass: HomeAssistant, mock_connection: MockModbusConnection,