From 02e5f2c234cdda8268f30bfad48793ada77e9d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Sun, 15 Feb 2026 15:18:07 +0100 Subject: [PATCH] tibber refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/tibber/__init__.py | 21 +++++++-------- .../components/tibber/coordinator.py | 27 ++++++++++++++++++- homeassistant/components/tibber/sensor.py | 10 +++---- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index 29a16428f5b..cbacb97ba0f 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass import logging import aiohttp @@ -38,8 +38,8 @@ class TibberRuntimeData: """Runtime data for Tibber API entries.""" session: OAuth2Session - data_api_coordinator: TibberDataAPICoordinator | None = field(default=None) - data_coordinator: TibberDataCoordinator | None = field(default=None) + data_api_coordinator: TibberDataAPICoordinator + data_coordinator: TibberDataCoordinator _client: tibber.Tibber | None = None async def async_get_client(self, hass: HomeAssistant) -> tibber.Tibber: @@ -101,8 +101,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: TibberConfigEntry) -> bo except ClientError as err: raise ConfigEntryNotReady from err + data_api_coordinator = TibberDataAPICoordinator(hass, entry) + await data_api_coordinator.async_config_entry_first_refresh() + data_coordinator = TibberDataCoordinator(hass, entry, entry.runtime_data) + await data_coordinator.async_config_entry_first_refresh() + await data_coordinator.update_listeners(dt_util.utcnow()) entry.runtime_data = TibberRuntimeData( session=session, + data_api_coordinator=data_api_coordinator, + data_coordinator=data_coordinator, ) tibber_connection = await entry.runtime_data.async_get_client(hass) @@ -125,14 +132,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: TibberConfigEntry) -> bo except tibber.FatalHttpExceptionError as err: raise ConfigEntryNotReady("Fatal HTTP error from Tibber API") from err - data_api_coordinator = TibberDataAPICoordinator(hass, entry) - await data_api_coordinator.async_config_entry_first_refresh() - entry.runtime_data.data_api_coordinator = data_api_coordinator - - data_coordinator = TibberDataCoordinator(hass, entry, entry.runtime_data) - await data_coordinator.async_config_entry_first_refresh() - entry.runtime_data.data_coordinator = data_coordinator - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/tibber/coordinator.py b/homeassistant/components/tibber/coordinator.py index 116cb4c2617..b6521b6fd0a 100644 --- a/homeassistant/components/tibber/coordinator.py +++ b/homeassistant/components/tibber/coordinator.py @@ -27,6 +27,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP, UnitOfEnergy from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util from homeassistant.util.unit_conversion import EnergyConverter @@ -109,9 +110,33 @@ class TibberDataCoordinator(DataUpdateCoordinator[dict[str, TibberHomeData]]): _LOGGER, config_entry=config_entry, name="Tibber", - update_interval=timedelta(minutes=15), ) self._runtime_data = runtime_data + self._listener_unsub: Callable[[], None] | None = None + + def _get_next_15_interval(self, now: datetime) -> datetime: + """Compute next time we need to notify listeners (minutes 0, 15, 30, 45).""" + next_run = dt_util.utcnow() + timedelta(minutes=15) + next_minute = next_run.minute // 15 * 15 + return next_run.replace( + minute=next_minute, second=0, microsecond=0, tzinfo=dt_util.UTC + ) + + async def async_shutdown(self) -> None: + """Cancel any scheduled listener updates.""" + await super().async_shutdown() + if self._listener_unsub: + self._listener_unsub() + self._listener_unsub = None + + async def update_listeners(self, now: datetime) -> None: + """Notify listeners at 15-min boundaries.""" + self._listener_unsub = async_track_point_in_utc_time( + self.hass, + self.update_listeners, + self._get_next_15_interval(now), + ) + self.async_update_listeners() async def _async_update_data(self) -> dict[str, TibberHomeData]: """Update data via API and return per-home data for sensors.""" diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 582b5e9558e..4cb90d930d0 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -113,7 +113,7 @@ RT_SENSORS: tuple[SensorEntityDescription, ...] = ( translation_key="accumulated_consumption", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=SensorStateClass.TOTAL, ), SensorEntityDescription( key="accumulatedConsumptionLastHour", @@ -641,14 +641,14 @@ async def _async_setup_graphql_sensors( entry: TibberConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: - """Set up the Tibber GraphQL-based sensors.""" + """Set up the Tibber sensor.""" tibber_connection = await entry.runtime_data.async_get_client(hass) entity_registry = er.async_get(hass) + coordinator: TibberDataCoordinator = entry.runtime_data.data_coordinator entities: list[TibberSensor] = [] - coordinator = entry.runtime_data.data_coordinator for home in tibber_connection.get_homes(only_active=False): try: await home.update_info() @@ -663,7 +663,7 @@ async def _async_setup_graphql_sensors( _LOGGER.error("Error connecting to Tibber home: %s ", err) raise PlatformNotReady from err - if coordinator is not None and home.has_active_subscription: + if home.has_active_subscription: entities.extend(TibberSensor(home, coordinator, desc) for desc in SENSORS) if home.has_real_time_consumption: @@ -689,8 +689,6 @@ def _setup_data_api_sensors( """Set up sensors backed by the Tibber Data API.""" coordinator = entry.runtime_data.data_api_coordinator - if coordinator is None: - return entities: list[TibberDataAPISensor] = [] api_sensors = {sensor.key: sensor for sensor in DATA_API_SENSORS}