mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
fix rotation
This commit is contained in:
parent
0116d5ad62
commit
1dc2457564
1 changed files with 79 additions and 122 deletions
|
|
@ -170,134 +170,12 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[TibberPricesData])
|
||||||
async_track_time_change(hass, self._async_refresh_hourly, minute=0, second=0)
|
async_track_time_change(hass, self._async_refresh_hourly, minute=0, second=0)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Schedule data rotation at midnight
|
|
||||||
self._remove_update_listeners.append(
|
|
||||||
async_track_time_change(hass, self._async_handle_midnight_rotation, hour=0, minute=0, second=0)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_shutdown(self) -> None:
|
async def async_shutdown(self) -> None:
|
||||||
"""Clean up coordinator on shutdown."""
|
"""Clean up coordinator on shutdown."""
|
||||||
await super().async_shutdown()
|
await super().async_shutdown()
|
||||||
for listener in self._remove_update_listeners:
|
for listener in self._remove_update_listeners:
|
||||||
listener()
|
listener()
|
||||||
|
|
||||||
async def _async_handle_midnight_rotation(self, _now: datetime | None = None) -> None:
|
|
||||||
"""Handle data rotation at midnight."""
|
|
||||||
if not self._cached_price_data:
|
|
||||||
LOGGER.debug("No cached price data available for midnight rotation")
|
|
||||||
return
|
|
||||||
|
|
||||||
async with self._rotation_lock: # Ensure rotation operations are protected
|
|
||||||
try:
|
|
||||||
LOGGER.debug("Starting midnight data rotation")
|
|
||||||
subscription = self._cached_price_data["data"]["viewer"]["homes"][0]["currentSubscription"]
|
|
||||||
price_info = subscription["priceInfo"]
|
|
||||||
|
|
||||||
# Move today's data to yesterday
|
|
||||||
if today_data := price_info.get("today"):
|
|
||||||
LOGGER.debug("Moving today's data (%d entries) to yesterday", len(today_data))
|
|
||||||
price_info["yesterday"] = today_data
|
|
||||||
else:
|
|
||||||
LOGGER.debug("No today's data available to move to yesterday")
|
|
||||||
|
|
||||||
# Move tomorrow's data to today
|
|
||||||
if tomorrow_data := price_info.get("tomorrow"):
|
|
||||||
LOGGER.debug("Moving tomorrow's data (%d entries) to today", len(tomorrow_data))
|
|
||||||
price_info["today"] = tomorrow_data
|
|
||||||
price_info["tomorrow"] = []
|
|
||||||
else:
|
|
||||||
LOGGER.warning("No tomorrow's data available to move to today, clearing today's data")
|
|
||||||
price_info["today"] = []
|
|
||||||
|
|
||||||
# Store the rotated data
|
|
||||||
await self._store_cache()
|
|
||||||
LOGGER.debug("Completed midnight data rotation")
|
|
||||||
|
|
||||||
# No need to schedule immediate refresh - tomorrow's data won't be available yet
|
|
||||||
# We'll wait for the regular update cycle between 13:00-15:00
|
|
||||||
|
|
||||||
except (KeyError, TypeError, ValueError) as ex:
|
|
||||||
LOGGER.error("Error during midnight data rotation: %s", ex)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _recover_timestamp(
|
|
||||||
self,
|
|
||||||
data: TibberPricesData | None,
|
|
||||||
stored_timestamp: str | None,
|
|
||||||
rating_type: str | None = None,
|
|
||||||
) -> datetime | None:
|
|
||||||
"""Recover timestamp from data or stored value."""
|
|
||||||
if stored_timestamp:
|
|
||||||
return dt_util.parse_datetime(stored_timestamp)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if rating_type:
|
|
||||||
timestamp = self._get_latest_timestamp_from_rating_type(data, rating_type)
|
|
||||||
else:
|
|
||||||
timestamp = _get_latest_timestamp_from_prices(data)
|
|
||||||
|
|
||||||
if timestamp:
|
|
||||||
LOGGER.debug(
|
|
||||||
"Recovered %s timestamp from data: %s",
|
|
||||||
rating_type or "price",
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return timestamp
|
|
||||||
|
|
||||||
async def _async_initialize(self) -> None:
|
|
||||||
"""Load stored data."""
|
|
||||||
stored = await self._store.async_load()
|
|
||||||
if stored is None:
|
|
||||||
LOGGER.warning("No cache file found or cache is empty on startup.")
|
|
||||||
else:
|
|
||||||
LOGGER.debug("Loading stored data: %s", stored)
|
|
||||||
|
|
||||||
if stored:
|
|
||||||
# Load cached data
|
|
||||||
self._cached_price_data = cast("TibberPricesData", stored.get("price_data"))
|
|
||||||
self._cached_rating_data_hourly = cast("TibberPricesData", stored.get("rating_data_hourly"))
|
|
||||||
self._cached_rating_data_daily = cast("TibberPricesData", stored.get("rating_data_daily"))
|
|
||||||
self._cached_rating_data_monthly = cast("TibberPricesData", stored.get("rating_data_monthly"))
|
|
||||||
|
|
||||||
# Recover timestamps
|
|
||||||
self._last_price_update = self._recover_timestamp(self._cached_price_data, stored.get("last_price_update"))
|
|
||||||
self._last_rating_update_hourly = self._recover_timestamp(
|
|
||||||
self._cached_rating_data_hourly,
|
|
||||||
stored.get("last_rating_update_hourly"),
|
|
||||||
"hourly",
|
|
||||||
)
|
|
||||||
self._last_rating_update_daily = self._recover_timestamp(
|
|
||||||
self._cached_rating_data_daily,
|
|
||||||
stored.get("last_rating_update_daily"),
|
|
||||||
"daily",
|
|
||||||
)
|
|
||||||
self._last_rating_update_monthly = self._recover_timestamp(
|
|
||||||
self._cached_rating_data_monthly,
|
|
||||||
stored.get("last_rating_update_monthly"),
|
|
||||||
"monthly",
|
|
||||||
)
|
|
||||||
|
|
||||||
LOGGER.debug(
|
|
||||||
"Loaded stored cache data - Price update: %s, Rating hourly: %s, daily: %s, monthly: %s",
|
|
||||||
self._last_price_update,
|
|
||||||
self._last_rating_update_hourly,
|
|
||||||
self._last_rating_update_daily,
|
|
||||||
self._last_rating_update_monthly,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Defensive: warn if any required data is missing
|
|
||||||
if self._cached_price_data is None:
|
|
||||||
LOGGER.warning("Cached price data missing after cache load!")
|
|
||||||
if self._last_price_update is None:
|
|
||||||
LOGGER.warning("Price update timestamp missing after cache load!")
|
|
||||||
else:
|
|
||||||
LOGGER.info("No cache loaded; will fetch fresh data on first update.")
|
|
||||||
|
|
||||||
async def _async_refresh_hourly(self, now: datetime | None = None) -> None:
|
async def _async_refresh_hourly(self, now: datetime | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Handle the hourly refresh.
|
Handle the hourly refresh.
|
||||||
|
|
@ -990,3 +868,82 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[TibberPricesData])
|
||||||
min_tomorrow_intervals_15min = 96
|
min_tomorrow_intervals_15min = 96
|
||||||
tomorrow_interval_counts = {min_tomorrow_intervals_hourly, min_tomorrow_intervals_15min}
|
tomorrow_interval_counts = {min_tomorrow_intervals_hourly, min_tomorrow_intervals_15min}
|
||||||
return interval_count in tomorrow_interval_counts
|
return interval_count in tomorrow_interval_counts
|
||||||
|
|
||||||
|
async def _async_initialize(self) -> None:
|
||||||
|
"""Load stored data."""
|
||||||
|
stored = await self._store.async_load()
|
||||||
|
if stored is None:
|
||||||
|
LOGGER.warning("No cache file found or cache is empty on startup.")
|
||||||
|
else:
|
||||||
|
LOGGER.debug("Loading stored data: %s", stored)
|
||||||
|
|
||||||
|
if stored:
|
||||||
|
# Load cached data
|
||||||
|
self._cached_price_data = cast("TibberPricesData", stored.get("price_data"))
|
||||||
|
self._cached_rating_data_hourly = cast("TibberPricesData", stored.get("rating_data_hourly"))
|
||||||
|
self._cached_rating_data_daily = cast("TibberPricesData", stored.get("rating_data_daily"))
|
||||||
|
self._cached_rating_data_monthly = cast("TibberPricesData", stored.get("rating_data_monthly"))
|
||||||
|
|
||||||
|
# Recover timestamps
|
||||||
|
self._last_price_update = self._recover_timestamp(self._cached_price_data, stored.get("last_price_update"))
|
||||||
|
self._last_rating_update_hourly = self._recover_timestamp(
|
||||||
|
self._cached_rating_data_hourly,
|
||||||
|
stored.get("last_rating_update_hourly"),
|
||||||
|
"hourly",
|
||||||
|
)
|
||||||
|
self._last_rating_update_daily = self._recover_timestamp(
|
||||||
|
self._cached_rating_data_daily,
|
||||||
|
stored.get("last_rating_update_daily"),
|
||||||
|
"daily",
|
||||||
|
)
|
||||||
|
self._last_rating_update_monthly = self._recover_timestamp(
|
||||||
|
self._cached_rating_data_monthly,
|
||||||
|
stored.get("last_rating_update_monthly"),
|
||||||
|
"monthly",
|
||||||
|
)
|
||||||
|
|
||||||
|
LOGGER.debug(
|
||||||
|
"Loaded stored cache data - Price update: %s, Rating hourly: %s, daily: %s, monthly: %s",
|
||||||
|
self._last_price_update,
|
||||||
|
self._last_rating_update_hourly,
|
||||||
|
self._last_rating_update_daily,
|
||||||
|
self._last_rating_update_monthly,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Defensive: warn if any required data is missing
|
||||||
|
if self._cached_price_data is None:
|
||||||
|
LOGGER.warning("Cached price data missing after cache load!")
|
||||||
|
if self._last_price_update is None:
|
||||||
|
LOGGER.warning("Price update timestamp missing after cache load!")
|
||||||
|
else:
|
||||||
|
LOGGER.info("No cache loaded; will fetch fresh data on first update.")
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _recover_timestamp(
|
||||||
|
self,
|
||||||
|
data: TibberPricesData | None,
|
||||||
|
stored_timestamp: str | None,
|
||||||
|
rating_type: str | None = None,
|
||||||
|
) -> datetime | None:
|
||||||
|
"""Recover timestamp from data or stored value."""
|
||||||
|
if stored_timestamp:
|
||||||
|
return dt_util.parse_datetime(stored_timestamp)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if rating_type:
|
||||||
|
timestamp = self._get_latest_timestamp_from_rating_type(data, rating_type)
|
||||||
|
else:
|
||||||
|
timestamp = _get_latest_timestamp_from_prices(data)
|
||||||
|
|
||||||
|
if timestamp:
|
||||||
|
LOGGER.debug(
|
||||||
|
"Recovered %s timestamp from data: %s",
|
||||||
|
rating_type or "price",
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return timestamp
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue