Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions custom_components/omie/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,15 @@ def _omie_schedule_refresh(self):
now_cet = utcnow().astimezone(CET)
none_before = now_cet.replace(hour=cet_hour, minute=cet_minute, second=self._schedule_second,
microsecond=self._schedule_microsecond)
next_hour = now_cet.replace(minute=0, second=self._schedule_second, microsecond=self._schedule_microsecond) + timedelta(hours=1)
in_15m = now_cet + timedelta(minutes=15)
next_quarter_hour = in_15m.replace(minute=in_15m.minute // 15 * 15, second=self._schedule_second,
microsecond=self._schedule_microsecond)

# next hour or the none_before time, whichever is soonest
next_refresh = (none_before if cet_hour == now_cet.hour and none_before > now_cet else next_hour).astimezone()
next_refresh = (none_before if cet_hour == now_cet.hour and none_before > now_cet else next_quarter_hour).astimezone()

_LOGGER.debug("%s: _schedule_refresh scheduling an update at %s (none_before=%s, next_hour=%s)", self.name,
next_refresh,
none_before, next_hour)
_LOGGER.debug("%s: _schedule_refresh scheduling an update at %s (none_before=%s, next_quarter_hour=%s)", self.name,
next_refresh, none_before, next_quarter_hour)
self._unsub_refresh = event.async_track_point_in_utc_time(self.hass, self.__job, next_refresh)

def _wait_for_none_before(self) -> bool:
Expand All @@ -135,7 +136,7 @@ def _data_is_fresh(self) -> bool:


def spot_price(client_session: ClientSession, get_market_date: DateFactory) -> UpdateMethod[SpotData]:
async def fetch() -> OMIEResults[SpotData] | None:
async def fetch() -> OMIEResults[SpotData]:
return await pyomie_spot(client_session, get_market_date())

return fetch
41 changes: 18 additions & 23 deletions custom_components/omie/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify, utcnow
from pyomie.model import SpotData, OMIEResults
from pyomie.util import localize_quarter_hourly_data
from pytz.tzinfo import StaticTzInfo

from . import OMIECoordinators
Expand Down Expand Up @@ -76,21 +77,21 @@ def update() -> None:
self._attr_extra_state_attributes = None
return

cet_today_hourly_data = _localize_hourly_data(today_data, self._series)
cet_tomorrow_hourly_data = _localize_hourly_data(tomorrow_data, self._series)
cet_yesterday_hourly_data = _localize_hourly_data(yesterday_data, self._series)
cet_today_hourly_data = _localize_quarter_hourly_data(today_data, self._series)
cet_tomorrow_hourly_data = _localize_quarter_hourly_data(tomorrow_data, self._series)
cet_yesterday_hourly_data = _localize_quarter_hourly_data(yesterday_data, self._series)
cet_hourly_data = cet_yesterday_hourly_data | cet_today_hourly_data | cet_tomorrow_hourly_data

local_tz = pytz.timezone(self.hass.config.time_zone)
now = utcnow().astimezone(local_tz)
today = now.date()
tomorrow = today + timedelta(days=1)

local_today_hourly_data = {h: cet_hourly_data.get(h.astimezone(CET)) for h in _day_hours(today, local_tz)}
local_tomorrow_hourly_data = {h: cet_hourly_data.get(h.astimezone(CET)) for h in _day_hours(tomorrow, local_tz)}
local_start_of_hour = local_tz.normalize(now.replace(minute=0, second=0, microsecond=0))
local_today_hourly_data = {h: cet_hourly_data.get(h.astimezone(CET)) for h in _day_quarter_hours(today, local_tz)}
local_tomorrow_hourly_data = {h: cet_hourly_data.get(h.astimezone(CET)) for h in _day_quarter_hours(tomorrow, local_tz)}
local_start_of_quarter_hour = local_tz.normalize(now.replace(minute=now.minute // 15 * 15, second=0, microsecond=0))

self._attr_native_value = local_today_hourly_data.get(local_start_of_hour)
self._attr_native_value = local_today_hourly_data.get(local_start_of_quarter_hour)
self._attr_extra_state_attributes = {
'OMIE_today_average': _day_average(cet_today_hourly_data),
'today_provisional': None in local_today_hourly_data.values(),
Expand Down Expand Up @@ -122,31 +123,25 @@ def update() -> None:
return True


def _localize_hourly_data(results: OMIEResults[SpotData], series_name: str) -> dict[datetime, float]:
def _localize_quarter_hourly_data(results: OMIEResults[SpotData], series_name: str) -> dict[datetime, float]:
"""Localize incoming quarter-hourly data to the CET timezone."""
if results is None:
return {}
else:
market_date = results.market_date
quarter_hourly_data: list[float] = getattr(results.contents, series_name)

hours_in_day = int(len(quarter_hourly_data) / 4) # between 23 and 25 (inclusive) due to DST changeover
midnight = CET.localize(datetime(market_date.year, market_date.month, market_date.day))
market_date = results.market_date
quarter_hourly_data: list[float] = getattr(results.contents, series_name)

return {
hour_start: hour_average
for hour in range(hours_in_day)
for quarter_hour in [hour * 4]
for hour_start in [CET.normalize(midnight + timedelta(hours=hour))]
for hour_average in [round(statistics.mean(quarter_hourly_data[quarter_hour:quarter_hour + 4]), 2)]
}
return {
datetime.fromisoformat(date_str): value
for date_str, value in localize_quarter_hourly_data(market_date, quarter_hourly_data).items()
}


def _day_hours(day: date, tz: StaticTzInfo) -> list[datetime]:
def _day_quarter_hours(day: date, tz: StaticTzInfo) -> list[datetime]:
"""Returns a list of every hour in the given date, normalized to the given time zone."""
zero = tz.localize(datetime(day.year, day.month, day.day))
hours = [tz.normalize(zero + timedelta(hours=h)) for h in range(25)]
return [h for h in hours if h.date() == day] # 25th hour only occurs once a year
quarter = [tz.normalize(zero + timedelta(hours=m // 4, minutes=(m % 4) * 15)) for m in range(25 * 4)]
return [qh for qh in quarter if qh.date() == day] # >96th quarter-hour only occurs once a year


def _day_average(hours_in_day: dict[datetime, float]) -> float | None:
Expand Down
19 changes: 10 additions & 9 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ packages = [
python = ">=3.12,<3.14"
homeassistant = ">=2025.1.0"
pytz = "^2025.2"
pyomie = "~1.0.0"
pyomie = "1.1.3"

[build-system]
requires = ["poetry-core"]
Expand Down