Skip to content

Commit

Permalink
Add service to fetch prices
Browse files Browse the repository at this point in the history
  • Loading branch information
Roeland authored and Roeland committed Sep 10, 2024
1 parent 5d7014c commit 60a00b3
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 17 deletions.
20 changes: 12 additions & 8 deletions custom_components/entsoe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@
import logging

from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import ConfigType
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import CONF_COORDINATOR, CONF_VAT_VALUE, DOMAIN, CONF_API_KEY, CONF_AREA, CONF_MODIFYER, DEFAULT_MODIFYER, CALCULATION_MODE, CONF_CALCULATION_MODE

from .const import CONF_VAT_VALUE, DOMAIN, CONF_API_KEY, CONF_AREA, CONF_MODIFYER, DEFAULT_MODIFYER, CALCULATION_MODE, CONF_CALCULATION_MODE
from .coordinator import EntsoeCoordinator
from .services import async_setup_services

_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR]

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up ENTSO-e services."""

async_setup_services(hass)

return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the ENTSO-e prices component from a config entry."""
Expand All @@ -24,10 +33,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
calculation_mode = entry.options.get(CONF_CALCULATION_MODE, CALCULATION_MODE["default"])
entsoe_coordinator = EntsoeCoordinator(hass, api_key=api_key, area = area, modifyer = modifyer, calculation_mode=calculation_mode, VAT=vat)

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
CONF_COORDINATOR: entsoe_coordinator,
}
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = entsoe_coordinator

# Fetch initial data, so we have data when entities subscribe and set up the platform
await entsoe_coordinator.async_config_entry_first_refresh()
Expand All @@ -39,10 +45,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


Expand Down
1 change: 0 additions & 1 deletion custom_components/entsoe/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
CONF_API_KEY = "api_key"
CONF_ENTITY_NAME = "name"
CONF_AREA = "area"
CONF_COORDINATOR = "coordinator"
CONF_MODIFYER = "modifyer"
CONF_CURRENCY = "currency"
CONF_ADVANCED_OPTIONS = "advanced_options"
Expand Down
21 changes: 15 additions & 6 deletions custom_components/entsoe/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async def _async_update_data(self) -> dict:
self.logger.debug(f"received data = {data}")

if data is not None:
parsed_data = self.parse_hourprices(dict(list(data.items())[-48:]))
parsed_data = self.parse_hourprices(data)
self.logger.debug(f"received pricing data from entso-e for {len(data)} hours")
self.filtered_hourprices = self._filter_calculated_hourprices(parsed_data)
return parsed_data
Expand Down Expand Up @@ -137,19 +137,25 @@ def api_update(self, start_date, end_date, api_key):
return client.query_day_ahead_prices(
country_code=self.area, start=start_date, end=end_date
)

async def get_energy_prices(self, start_date, end_date):
#check if we have the data already
if len(self.get_data(start_date)) == 24 and len(self.get_data(end_date)) == 24:
self.logger.debug(f'return prices from coordinator cache.')
return {k: v for k, v in self.data.items() if k.date() >= start_date.date() and k.date() <= end_date.date()}
return await self.fetch_prices(start_date, end_date)

def today_data_available(self):
return len(self.get_data_today()) == 24

def _filter_calculated_hourprices(self, data):
hourprices = data
if self.calculation_mode == CALCULATION_MODE["rotation"]:
return { hour: price for hour, price in hourprices.items() if hour >= self.today and hour < self.today + timedelta(days=1) }
return { hour: price for hour, price in data.items() if hour >= self.today and hour < self.today + timedelta(days=1) }
elif self.calculation_mode == CALCULATION_MODE["sliding"]:
now = dt.now().replace(minute=0, second=0, microsecond=0)
return { hour: price for hour, price in hourprices.items() if hour >= now }
return { hour: price for hour, price in data.items() if hour >= now }
elif self.calculation_mode == CALCULATION_MODE["publish"]:
return hourprices
return dict(list(data.items())[-48:])

def get_prices_today(self):
return self.get_timestamped_prices(self.get_data_today())
Expand All @@ -158,8 +164,11 @@ def get_prices_tomorrow(self):
return self.get_timestamped_prices(self.get_data_tomorrow())

def get_prices(self):
return self.get_timestamped_prices(self.data)
return self.get_timestamped_prices(dict(list(self.data.items())[-48:]))

def get_data(self, date):
return {k: v for k, v in self.data.items() if k.date() == date.date()}

def get_data_today(self):
return {k: v for k, v in self.data.items() if k.date() == self.today.date()}

Expand Down
4 changes: 2 additions & 2 deletions custom_components/entsoe/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import utcnow

from .const import ATTRIBUTION, CONF_COORDINATOR, CONF_ENTITY_NAME, DOMAIN, DEFAULT_CURRENCY, CONF_CURRENCY
from .const import ATTRIBUTION, CONF_ENTITY_NAME, DOMAIN, DEFAULT_CURRENCY, CONF_CURRENCY
from .coordinator import EntsoeCoordinator

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -113,7 +113,7 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up ENTSO-e price sensor entries."""
entsoe_coordinator = hass.data[DOMAIN][config_entry.entry_id][CONF_COORDINATOR]
entsoe_coordinator = hass.data[DOMAIN][config_entry.entry_id]

entities = []
entity = {}
Expand Down
133 changes: 133 additions & 0 deletions custom_components/entsoe/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""The Entso-e services."""

from __future__ import annotations

from datetime import date, datetime
from functools import partial
from typing import Final

import voluptuous as vol
import logging

from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import selector
from homeassistant.util import dt as dt_util

from .const import DOMAIN
from .coordinator import EntsoeCoordinator

_LOGGER = logging.getLogger(__name__)

ATTR_CONFIG_ENTRY: Final = "config_entry"
ATTR_START: Final = "start"
ATTR_END: Final = "end"

ENERGY_SERVICE_NAME: Final = "get_energy_prices"
SERVICE_SCHEMA: Final = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY): selector.ConfigEntrySelector(
{
"integration": DOMAIN,
}
),
vol.Optional(ATTR_START): str,
vol.Optional(ATTR_END): str,
}
)


def __get_date(date_input: str | None) -> date | datetime:
"""Get date."""
if not date_input:
return dt_util.now().date()

if value := dt_util.parse_datetime(date_input):
return value

raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_date",
translation_placeholders={
"date": date_input,
},
)


def __serialize_prices(prices) -> ServiceResponse:
"""Serialize prices."""
return {
"prices": [
{
"timestamp": dt.isoformat(),
"price": price
}
for dt, price in prices.items()
]
}


def __get_coordinator(
hass: HomeAssistant, call: ServiceCall
) -> EntsoeCoordinator:
"""Get the coordinator from the entry."""
entry_id: str = call.data[ATTR_CONFIG_ENTRY]
entry: ConfigEntry | None = hass.config_entries.async_get_entry(entry_id)

if not entry:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_config_entry",
translation_placeholders={
"config_entry": entry_id,
},
)
if entry.state != ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="unloaded_config_entry",
translation_placeholders={
"config_entry": entry.title,
},
)

coordinator: EntsoeCoordinator = hass.data[DOMAIN][entry_id]
return coordinator


async def __get_prices(
call: ServiceCall,
*,
hass: HomeAssistant,
) -> ServiceResponse:
coordinator = __get_coordinator(hass, call)

start = __get_date(call.data.get(ATTR_START))
end = __get_date(call.data.get(ATTR_END))

data = await coordinator.get_energy_prices(
start_date=start,
end_date=end,
)

return __serialize_prices(data)


@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up Entso-e services."""

hass.services.async_register(
DOMAIN,
ENERGY_SERVICE_NAME,
partial(__get_prices, hass=hass),
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
17 changes: 17 additions & 0 deletions custom_components/entsoe/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
get_energy_prices:
fields:
config_entry:
required: true
selector:
config_entry:
integration: entsoe
start:
required: false
example: "2023-01-01 00:00:00"
selector:
datetime:
end:
required: false
example: "2023-01-01 00:00:00"
selector:
datetime:

0 comments on commit 60a00b3

Please sign in to comment.