From 214daa1ebbc824430245cf4730555a48772a4aed Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Fri, 2 Oct 2026 10:22:09 +0200 Subject: [PATCH] Only create Zonneplan entities for contracted energy types (#183844) --- .../components/zonneplan/binary_sensor.py | 3 ++ homeassistant/components/zonneplan/sensor.py | 10 ++++- .../zonneplan/snapshots/test_sensor.ambr | 36 ++++++++++++++++ tests/components/zonneplan/test_sensor.py | 41 +++++++++---------- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/zonneplan/binary_sensor.py b/homeassistant/components/zonneplan/binary_sensor.py index 67b66e1fd2a9..fd9328fbcf8d 100644 --- a/homeassistant/components/zonneplan/binary_sensor.py +++ b/homeassistant/components/zonneplan/binary_sensor.py @@ -28,6 +28,7 @@ class ZonneplanBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes a Zonneplan binary sensor.""" is_on_fn: Callable[[ZonneplanCoordinator], bool | None] + supported_fn: Callable[[ZonneplanCoordinator], bool] BINARY_SENSORS: tuple[ZonneplanBinarySensorEntityDescription, ...] = ( @@ -46,6 +47,7 @@ BINARY_SENSORS: tuple[ZonneplanBinarySensorEntityDescription, ...] = ( ) else None ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), ), ) @@ -95,6 +97,7 @@ async def async_setup_entry( *( ZonneplanBinarySensor(coordinator, description) for description in BINARY_SENSORS + if description.supported_fn(coordinator) ), *( ZonneplanBatteryBinarySensor( diff --git a/homeassistant/components/zonneplan/sensor.py b/homeassistant/components/zonneplan/sensor.py index 92273521371b..a840dbc4a060 100644 --- a/homeassistant/components/zonneplan/sensor.py +++ b/homeassistant/components/zonneplan/sensor.py @@ -33,7 +33,7 @@ class ZonneplanPriceSensorEntityDescription(SensorEntityDescription): """Describes a Zonneplan price sensor.""" value_fn: Callable[[ZonneplanCoordinator], float | str | datetime | None] - supported_fn: Callable[[ZonneplanCoordinator], bool] | None = None + supported_fn: Callable[[ZonneplanCoordinator], bool] ZONNEPLAN_SENSORS: tuple[ZonneplanPriceSensorEntityDescription, ...] = ( @@ -303,6 +303,7 @@ class ZonneplanUsageSensorEntityDescription(SensorEntityDescription): group_fn: Callable[[ZonneplanData], ElectricityChartGroup | GasChartGroup | None] value_fn: Callable[[ZonneplanData], Decimal | None] + supported_fn: Callable[[ZonneplanData], bool] ZONNEPLAN_USAGE_SENSORS: tuple[ZonneplanUsageSensorEntityDescription, ...] = ( @@ -320,6 +321,7 @@ ZONNEPLAN_USAGE_SENSORS: tuple[ZonneplanUsageSensorEntityDescription, ...] = ( if data.electricity_usage and (group := data.electricity_usage.group) else None ), + supported_fn=lambda data: data.electricity_usage is not None, ), ZonneplanUsageSensorEntityDescription( key="electricity_produced_this_month", @@ -335,6 +337,7 @@ ZONNEPLAN_USAGE_SENSORS: tuple[ZonneplanUsageSensorEntityDescription, ...] = ( if data.electricity_usage and (group := data.electricity_usage.group) else None ), + supported_fn=lambda data: data.electricity_usage is not None, ), ZonneplanUsageSensorEntityDescription( key="gas_delivered_this_month", @@ -348,6 +351,7 @@ ZONNEPLAN_USAGE_SENSORS: tuple[ZonneplanUsageSensorEntityDescription, ...] = ( if data.gas_usage and (group := data.gas_usage.group) else None ), + supported_fn=lambda data: data.gas_usage is not None, ), ZonneplanUsageSensorEntityDescription( key="electricity_cost_this_month", @@ -366,6 +370,7 @@ ZONNEPLAN_USAGE_SENSORS: tuple[ZonneplanUsageSensorEntityDescription, ...] = ( and group.has_data else None ), + supported_fn=lambda data: data.electricity_usage is not None, ), ZonneplanUsageSensorEntityDescription( key="gas_cost_this_month", @@ -380,6 +385,7 @@ ZONNEPLAN_USAGE_SENSORS: tuple[ZonneplanUsageSensorEntityDescription, ...] = ( if data.gas_usage and (group := data.gas_usage.group) and group.has_data else None ), + supported_fn=lambda data: data.gas_usage is not None, ), ) @@ -397,10 +403,12 @@ async def async_setup_entry( *( ZonneplanPriceSensor(coordinator, description) for description in ZONNEPLAN_SENSORS + if description.supported_fn(coordinator) ), *( ZonneplanUsageSensor(coordinator, description) for description in ZONNEPLAN_USAGE_SENSORS + if description.supported_fn(coordinator.data) ), ] ) diff --git a/tests/components/zonneplan/snapshots/test_sensor.ambr b/tests/components/zonneplan/snapshots/test_sensor.ambr index 5b18ff8c7199..ce1be6824eba 100644 --- a/tests/components/zonneplan/snapshots/test_sensor.ambr +++ b/tests/components/zonneplan/snapshots/test_sensor.ambr @@ -1,4 +1,40 @@ # serializer version: 1 +# name: test_entities_not_created_for_missing_market_segment[missing_electricity] + list([ + 'sensor.zonneplan_gas_cost_this_month', + 'sensor.zonneplan_gas_price_daily', + 'sensor.zonneplan_gas_used_this_month', + ]) +# --- +# name: test_entities_not_created_for_missing_market_segment[missing_gas] + list([ + 'binary_sensor.thuisbatterij_backup_power_active', + 'binary_sensor.thuisbatterij_grid_congestion', + 'binary_sensor.thuisbatterij_home_optimization_active', + 'binary_sensor.thuisbatterij_load_balancing_overload', + 'binary_sensor.zonneplan_electricity_price_low', + 'sensor.zonneplan_current_electricity_price', + 'sensor.zonneplan_current_electricity_tariff_group', + 'sensor.zonneplan_current_sustainability_score', + 'sensor.zonneplan_electricity_cost_this_month', + 'sensor.zonneplan_electricity_price_low_today_end_time', + 'sensor.zonneplan_electricity_price_low_today_start_time', + 'sensor.zonneplan_electricity_price_low_tomorrow_end_time', + 'sensor.zonneplan_electricity_price_low_tomorrow_start_time', + 'sensor.zonneplan_electricity_prices_tomorrow_status', + 'sensor.zonneplan_electricity_returned_this_month', + 'sensor.zonneplan_electricity_used_this_month', + 'sensor.zonneplan_highest_electricity_price_today', + 'sensor.zonneplan_highest_electricity_price_tomorrow', + 'sensor.zonneplan_lowest_electricity_price_today', + 'sensor.zonneplan_lowest_electricity_price_tomorrow', + 'sensor.zonneplan_next_hour_electricity_price', + ]) +# --- +# name: test_entities_not_created_for_missing_market_segment[no_energy_contract] + list([ + ]) +# --- # name: test_sensor[prices_incoming][sensor.zonneplan_current_electricity_price-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ diff --git a/tests/components/zonneplan/test_sensor.py b/tests/components/zonneplan/test_sensor.py index 67e7065c872e..7a39700a8ee6 100644 --- a/tests/components/zonneplan/test_sensor.py +++ b/tests/components/zonneplan/test_sensor.py @@ -52,32 +52,22 @@ async def test_sensor( @pytest.mark.parametrize( - ("missing_market_segment", "entity_id"), + "missing_market_segments", [ - pytest.param( - "electricity", - "sensor.zonneplan_current_electricity_price", - id="missing_electricity", - ), - pytest.param("gas", "sensor.zonneplan_gas_price_daily", id="missing_gas"), - pytest.param( - "electricity", - "sensor.zonneplan_electricity_used_this_month", - id="missing_electricity_usage", - ), - pytest.param( - "gas", "sensor.zonneplan_gas_used_this_month", id="missing_gas_usage" - ), + pytest.param({"electricity"}, id="missing_electricity"), + pytest.param({"gas"}, id="missing_gas"), + pytest.param({"electricity", "gas"}, id="no_energy_contract"), ], ) -async def test_sensor_unknown_for_missing_market_segment( +async def test_entities_not_created_for_missing_market_segment( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_zonneplan_client: AsyncMock, - missing_market_segment: str, - entity_id: str, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + missing_market_segments: set[str], ) -> None: - """Test a sensor is unknown when its market segment isn't on the account.""" + """Test no entities are created for a market segment that isn't on the account.""" mock_zonneplan_client.async_get_account.return_value = dataclasses.replace( MOCK_ACCOUNT, address_groups=[ @@ -86,7 +76,7 @@ async def test_sensor_unknown_for_missing_market_segment( connections=[ connection for connection in address_group.connections - if connection.market_segment != missing_market_segment + if connection.market_segment not in missing_market_segments ], ) for address_group in MOCK_ACCOUNT.address_groups @@ -97,8 +87,15 @@ async def test_sensor_unknown_for_missing_market_segment( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - assert (state := hass.states.get(entity_id)) - assert state.state == STATE_UNKNOWN + assert ( + sorted( + entity.entity_id + for entity in er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + ) + == snapshot + ) @pytest.mark.parametrize(