From 31af4273aa2bbfa72f206edcad5a6d51a5cc04ff Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Mon, 7 Sep 2026 11:56:30 +0200 Subject: [PATCH] Fix easyEnergy price period count sensor semantics (#181431) --- homeassistant/components/easyenergy/sensor.py | 10 +-- .../components/easyenergy/strings.json | 4 +- tests/components/easyenergy/test_sensor.py | 72 +++++++++++++++++-- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/easyenergy/sensor.py b/homeassistant/components/easyenergy/sensor.py index e1a1aa4f300b..4d812104a1d9 100644 --- a/homeassistant/components/easyenergy/sensor.py +++ b/homeassistant/components/easyenergy/sensor.py @@ -12,13 +12,7 @@ from homeassistant.components.sensor import ( SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import ( - CURRENCY_EURO, - PERCENTAGE, - UnitOfEnergy, - UnitOfTime, - UnitOfVolume, -) +from homeassistant.const import CURRENCY_EURO, PERCENTAGE, UnitOfEnergy, UnitOfVolume from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -182,14 +176,12 @@ SENSORS: tuple[EasyEnergySensorEntityDescription, ...] = ( key="hours_priced_equal_or_lower", translation_key="hours_priced_equal_or_lower", service_type="today_energy_usage", - native_unit_of_measurement=UnitOfTime.HOURS, value_fn=lambda data: data.energy_today.periods_priced_equal_or_lower, ), EasyEnergySensorEntityDescription( key="hours_priced_equal_or_higher", translation_key="hours_priced_equal_or_higher", service_type="today_energy_return", - native_unit_of_measurement=UnitOfTime.HOURS, value_fn=lambda data: data.energy_today.return_periods_priced_equal_or_higher, ), ) diff --git a/homeassistant/components/easyenergy/strings.json b/homeassistant/components/easyenergy/strings.json index cab60617ea96..24d820b2c514 100644 --- a/homeassistant/components/easyenergy/strings.json +++ b/homeassistant/components/easyenergy/strings.json @@ -24,10 +24,10 @@ "name": "Time of highest price - today" }, "hours_priced_equal_or_higher": { - "name": "Hours priced equal or higher than current - today" + "name": "Periods priced equal or higher than current - today" }, "hours_priced_equal_or_lower": { - "name": "Hours priced equal or lower than current - today" + "name": "Periods priced equal or lower than current - today" }, "lowest_price_time": { "name": "Time of lowest price - today" diff --git a/tests/components/easyenergy/test_sensor.py b/tests/components/easyenergy/test_sensor.py index 8964ffa143ad..790f68242655 100644 --- a/tests/components/easyenergy/test_sensor.py +++ b/tests/components/easyenergy/test_sensor.py @@ -1,8 +1,9 @@ """Tests for the sensors provided by the easyEnergy integration.""" +from datetime import timedelta from unittest.mock import MagicMock -from easyenergy import EasyEnergyNoDataError +from easyenergy import EasyEnergyNoDataError, Electricity import pytest from homeassistant.components.easyenergy.const import DOMAIN @@ -29,8 +30,9 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_load_json_object_fixture @pytest.mark.freeze_time("2026-04-19 13:00:00+00:00") @@ -128,7 +130,7 @@ async def test_energy_usage_today( assert not device_entry.model assert not device_entry.sw_version - # Usage hours priced equal or lower sensor + # Usage periods priced equal or lower sensor state = hass.states.get( "sensor.easyenergy_today_energy_usage_hours_priced_equal_or_lower" ) @@ -141,9 +143,10 @@ async def test_energy_usage_today( entry.unique_id == f"{entry_id}_today_energy_usage_hours_priced_equal_or_lower" ) assert state.state == "2" + assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Energy market price" - " - Usage Hours priced equal or lower than current - today" + " - Usage Periods priced equal or lower than current - today" ) assert ATTR_DEVICE_CLASS not in state.attributes @@ -243,7 +246,7 @@ async def test_energy_return_today( assert not device_entry.model assert not device_entry.sw_version - # Return hours priced equal or higher sensor + # Return periods priced equal or higher sensor state = hass.states.get( "sensor.easyenergy_today_energy_return_hours_priced_equal_or_higher" ) @@ -257,9 +260,10 @@ async def test_energy_return_today( == f"{entry_id}_today_energy_return_hours_priced_equal_or_higher" ) assert state.state == "23" + assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Energy market price" - " - Return Hours priced equal or higher than current - today" + " - Return Periods priced equal or higher than current - today" ) assert ATTR_DEVICE_CLASS not in state.attributes @@ -321,3 +325,59 @@ async def test_no_gas_today( state = hass.states.get("sensor.easyenergy_today_gas_current_hour_price") assert state assert state.state == STATE_UNKNOWN + + +@pytest.mark.freeze_time("2026-04-19 00:00:00+00:00") +@pytest.mark.parametrize( + ("minutes", "granularity"), + [ + pytest.param(60, "hour", id="hourly"), + pytest.param(15, "quarter", id="quarter-hourly"), + ], +) +@pytest.mark.parametrize( + ("sensor", "expected_state"), + [ + pytest.param("usage_hours_priced_equal_or_lower", "3", id="usage"), + pytest.param("return_hours_priced_equal_or_higher", "2", id="return"), + ], +) +async def test_price_period_counts( + hass: HomeAssistant, + mock_easyenergy: MagicMock, + mock_config_entry: MockConfigEntry, + minutes: int, + granularity: str, + sensor: str, + expected_state: str, +) -> None: + """Test inclusive counts for distinct usage and return prices at any interval size.""" + data = await async_load_json_object_fixture(hass, "today_energy.json", DOMAIN) + start = dt_util.utcnow() + interval = timedelta(minutes=minutes) + prices = [ + { + **data["prices"][0], + "from": (start + index * interval).isoformat(), + "until": (start + (index + 1) * interval).isoformat(), + "granularity": granularity, + "priceIncVat": usage_price, + "invoicePrice": return_price, + } + for index, (usage_price, return_price) in enumerate( + [(0.2, 0.3), (-0.1, 0.1), (0.2, 0.3), (0.4, 0.2)] + ) + ] + mock_easyenergy.energy_prices.return_value = Electricity.from_dict( + prices, price_key="priceIncVat", return_price_key="invoicePrice" + ) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(f"sensor.easyenergy_today_energy_{sensor}") + assert state + assert state.state == expected_state + assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes + assert ATTR_DEVICE_CLASS not in state.attributes + assert ATTR_STATE_CLASS not in state.attributes