Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Roeland authored and Roeland committed Oct 4, 2024
2 parents bb58c5b + cffd4a7 commit eecb7e7
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 22 deletions.
4 changes: 4 additions & 0 deletions custom_components/entsoe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
CALCULATION_MODE,
CONF_API_KEY,
CONF_AREA,
CONF_ENERGY_SCALE,
CONF_CALCULATION_MODE,
CONF_MODIFYER,
CONF_VAT_VALUE,
DEFAULT_MODIFYER,
DEFAULT_ENERGY_SCALE,
DOMAIN,
)
from .coordinator import EntsoeCoordinator
Expand All @@ -40,6 +42,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Initialise the coordinator and save it as domain-data
api_key = entry.options[CONF_API_KEY]
area = entry.options[CONF_AREA]
energy_scale = entry.options.get(CONF_ENERGY_SCALE, DEFAULT_ENERGY_SCALE)
modifyer = entry.options.get(CONF_MODIFYER, DEFAULT_MODIFYER)
vat = entry.options.get(CONF_VAT_VALUE, 0)
calculation_mode = entry.options.get(
Expand All @@ -49,6 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass,
api_key=api_key,
area=area,
energy_scale=energy_scale,
modifyer=modifyer,
calculation_mode=calculation_mode,
VAT=vat,
Expand Down
27 changes: 17 additions & 10 deletions custom_components/entsoe/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ def _base_request(

return response

def _remove_namespace(self, tree):
"""Remove namespaces in the passed XML tree for easier tag searching."""
for elem in tree.iter():
# Remove the namespace if present
if "}" in elem.tag:
elem.tag = elem.tag.split("}", 1)[1]
return tree

def query_day_ahead_prices(
self, country_code: Union[Area, str], start: datetime, end: datetime
) -> str:
Expand All @@ -62,30 +70,29 @@ def query_day_ahead_prices(

if response.status_code == 200:
try:
xml_data = response.content
root = ET.fromstring(xml_data)
ns = {"ns": "urn:iec62325.351:tc57wg16:451-3:publicationdocument:7:0"}
root = self._remove_namespace(ET.fromstring(response.content))
_LOGGER.debug(f"content: {root}")
series = {}

# Extract TimeSeries data
for timeseries in root.findall("ns:TimeSeries", ns):
period = timeseries.find("ns:Period", ns)
resolution = period.find("ns:resolution", ns).text
for timeseries in root.findall(".//TimeSeries"):
period = timeseries.find(".//Period")
resolution = period.find(".//resolution").text

if resolution != "PT60M":
continue

start_time = period.find("ns:timeInterval/ns:start", ns).text
start_time = period.find(".//timeInterval/start").text

date = (
datetime.strptime(start_time, "%Y-%m-%dT%H:%MZ")
.replace(tzinfo=pytz.UTC)
.astimezone()
)

for point in period.findall("ns:Point", ns):
position = point.find("ns:position", ns).text
price = point.find("ns:price.amount", ns).text
for point in period.findall(".//Point"):
position = point.find(".//position").text
price = point.find(".//price.amount").text
hour = int(position) - 1
series[date + timedelta(hours=hour)] = float(price)

Expand Down
22 changes: 22 additions & 0 deletions custom_components/entsoe/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@

from .const import (
AREA_INFO,
ENERGY_SCALES,
CALCULATION_MODE,
CONF_ADVANCED_OPTIONS,
CONF_API_KEY,
CONF_AREA,
CONF_CALCULATION_MODE,
CONF_CURRENCY,
CONF_ENERGY_SCALE,
CONF_ENTITY_NAME,
CONF_MODIFYER,
CONF_VAT_VALUE,
DEFAULT_CURRENCY,
DEFAULT_ENERGY_SCALE,
DEFAULT_MODIFYER,
DOMAIN,
UNIQUE_ID,
Expand All @@ -47,6 +50,7 @@ def __init__(self):
self.api_key = None
self.modifyer = None
self.currency = None
self.energy_scale = None
self.name = ""

VERSION = 1
Expand Down Expand Up @@ -87,6 +91,7 @@ async def async_step_user(
user_input[CONF_VAT_VALUE] = 0
user_input[CONF_MODIFYER] = DEFAULT_MODIFYER
user_input[CONF_CURRENCY] = DEFAULT_CURRENCY
user_input[CONF_ENERGY_SCALE] = DEFAULT_ENERGY_SCALE
user_input[CONF_CALCULATION_MODE] = CALCULATION_MODE["default"]
if not already_configured:
return self.async_create_entry(
Expand All @@ -97,6 +102,7 @@ async def async_step_user(
CONF_AREA: user_input[CONF_AREA],
CONF_MODIFYER: user_input[CONF_MODIFYER],
CONF_CURRENCY: user_input[CONF_CURRENCY],
CONF_ENERGY_SCALE: user_input[CONF_ENERGY_SCALE],
CONF_ADVANCED_OPTIONS: user_input[CONF_ADVANCED_OPTIONS],
CONF_VAT_VALUE: user_input[CONF_VAT_VALUE],
CONF_ENTITY_NAME: user_input[CONF_ENTITY_NAME],
Expand Down Expand Up @@ -160,6 +166,9 @@ async def async_step_extra(self, user_input=None):
if user_input[CONF_CURRENCY] in (None, ""):
user_input[CONF_CURRENCY] = DEFAULT_CURRENCY

if user_input[CONF_ENERGY_SCALE] in (None, ""):
user_input[CONF_ENERGY_SCALE] = DEFAULT_ENERGY_SCALE

template_ok = await self._valid_template(user_input[CONF_MODIFYER])

if not already_configured:
Expand All @@ -173,6 +182,7 @@ async def async_step_extra(self, user_input=None):
CONF_AREA: user_input[CONF_AREA],
CONF_MODIFYER: user_input[CONF_MODIFYER],
CONF_CURRENCY: user_input[CONF_CURRENCY],
CONF_ENERGY_SCALE: user_input[CONF_ENERGY_SCALE],
CONF_VAT_VALUE: user_input[CONF_VAT_VALUE],
CONF_ENTITY_NAME: user_input[CONF_ENTITY_NAME],
CONF_CALCULATION_MODE: user_input[
Expand All @@ -198,6 +208,9 @@ async def async_step_extra(self, user_input=None):
vol.Optional(CONF_CURRENCY, default=DEFAULT_CURRENCY): vol.All(
vol.Coerce(str)
),
vol.Optional(CONF_ENERGY_SCALE, default=DEFAULT_ENERGY_SCALE): vol.In(
list(ENERGY_SCALES.keys())
),
vol.Optional(
CONF_CALCULATION_MODE, default=CALCULATION_MODE["default"]
): SelectSelector(
Expand Down Expand Up @@ -260,6 +273,9 @@ async def async_step_init(
if user_input[CONF_CURRENCY] in (None, ""):
user_input[CONF_CURRENCY] = DEFAULT_CURRENCY

if user_input[CONF_ENERGY_SCALE] in (None, ""):
user_input[CONF_ENERGY_SCALE] = DEFAULT_ENERGY_SCALE

template_ok = await self._valid_template(user_input[CONF_MODIFYER])

if template_ok:
Expand Down Expand Up @@ -305,6 +321,12 @@ async def async_step_init(
CONF_CURRENCY, DEFAULT_CURRENCY
),
): vol.All(vol.Coerce(str)),
vol.Optional(
CONF_ENERGY_SCALE,
default=self.config_entry.options.get(
CONF_ENERGY_SCALE, DEFAULT_ENERGY_SCALE
),
): vol.In(list(ENERGY_SCALES.keys())),
vol.Optional(
CONF_CALCULATION_MODE,
default=calculation_mode_default,
Expand Down
4 changes: 4 additions & 0 deletions custom_components/entsoe/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
CONF_AREA = "area"
CONF_MODIFYER = "modifyer"
CONF_CURRENCY = "currency"
CONF_ENERGY_SCALE = "energy_scale"
CONF_ADVANCED_OPTIONS = "advanced_options"
CONF_CALCULATION_MODE = "calculation_mode"
CONF_VAT_VALUE = "VAT_value"

DEFAULT_MODIFYER = "{{current_price}}"
DEFAULT_CURRENCY = CURRENCY_EURO
DEFAULT_ENERGY_SCALE = "kWh"

# default is only for internal use / backwards compatibility
CALCULATION_MODE = {
Expand All @@ -25,6 +27,8 @@
"publish": "publish",
}

ENERGY_SCALES = { "kWh": 1000, "MWh": 1 }

# Commented ones are not working at entsoe
AREA_INFO = {
"AT": {"code": "AT", "name": "Austria", "VAT": 0.21, "Currency": "EUR"},
Expand Down
8 changes: 5 additions & 3 deletions custom_components/entsoe/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from requests.exceptions import HTTPError

from .api_client import EntsoeClient
from .const import AREA_INFO, CALCULATION_MODE, DEFAULT_MODIFYER
from .const import AREA_INFO, ENERGY_SCALES, CALCULATION_MODE, DEFAULT_MODIFYER

# depending on timezone les than 24 hours could be returned.
MIN_HOURS = 20
Expand All @@ -26,6 +26,7 @@ def __init__(
hass: HomeAssistant,
api_key,
area,
energy_scale,
modifyer,
calculation_mode=CALCULATION_MODE["default"],
VAT=0,
Expand All @@ -35,6 +36,7 @@ def __init__(
self.api_key = api_key
self.modifyer = modifyer
self.area = AREA_INFO[area]["code"]
self.energy_scale = energy_scale
self.calculation_mode = calculation_mode
self.vat = VAT
self.today = None
Expand Down Expand Up @@ -64,10 +66,10 @@ def calc_price(self, value, fake_dt=None, no_template=False) -> float:
# Used to inject the current hour.
# so template can be simplified using now
if no_template:
price = round(value / 1000, 5)
price = round(value / ENERGY_SCALES[self.energy_scale], 5)
return price

price = value / 1000
price = value / ENERGY_SCALES[self.energy_scale]
if fake_dt is not None:

def faker():
Expand Down
19 changes: 11 additions & 8 deletions custom_components/entsoe/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfEnergy
from homeassistant.const import PERCENTAGE
from homeassistant.core import HassJob, HomeAssistant
from homeassistant.helpers import event
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
Expand All @@ -28,8 +28,10 @@
from .const import (
ATTRIBUTION,
CONF_CURRENCY,
CONF_ENERGY_SCALE,
CONF_ENTITY_NAME,
DEFAULT_CURRENCY,
DEFAULT_ENERGY_SCALE,
DOMAIN,
)
from .coordinator import EntsoeCoordinator
Expand All @@ -44,13 +46,13 @@ class EntsoeEntityDescription(SensorEntityDescription):
value_fn: Callable[[dict], StateType] = None


def sensor_descriptions(currency: str) -> tuple[EntsoeEntityDescription, ...]:
def sensor_descriptions(currency: str, energy_scale: str) -> tuple[EntsoeEntityDescription, ...]:
"""Construct EntsoeEntityDescription."""
return (
EntsoeEntityDescription(
key="current_price",
name="Current electricity market price",
native_unit_of_measurement=f"{currency}/{UnitOfEnergy.KILO_WATT_HOUR}",
native_unit_of_measurement=f"{currency}/{energy_scale}",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:currency-eur",
suggested_display_precision=3,
Expand All @@ -59,7 +61,7 @@ def sensor_descriptions(currency: str) -> tuple[EntsoeEntityDescription, ...]:
EntsoeEntityDescription(
key="next_hour_price",
name="Next hour electricity market price",
native_unit_of_measurement=f"{currency}/{UnitOfEnergy.KILO_WATT_HOUR}",
native_unit_of_measurement=f"{currency}/{energy_scale}",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:currency-eur",
suggested_display_precision=3,
Expand All @@ -68,7 +70,7 @@ def sensor_descriptions(currency: str) -> tuple[EntsoeEntityDescription, ...]:
EntsoeEntityDescription(
key="min_price",
name="Lowest energy price",
native_unit_of_measurement=f"{currency}/{UnitOfEnergy.KILO_WATT_HOUR}",
native_unit_of_measurement=f"{currency}/{energy_scale}",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:currency-eur",
suggested_display_precision=3,
Expand All @@ -77,7 +79,7 @@ def sensor_descriptions(currency: str) -> tuple[EntsoeEntityDescription, ...]:
EntsoeEntityDescription(
key="max_price",
name="Highest energy price",
native_unit_of_measurement=f"{currency}/{UnitOfEnergy.KILO_WATT_HOUR}",
native_unit_of_measurement=f"{currency}/{energy_scale}",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:currency-eur",
suggested_display_precision=3,
Expand All @@ -86,7 +88,7 @@ def sensor_descriptions(currency: str) -> tuple[EntsoeEntityDescription, ...]:
EntsoeEntityDescription(
key="avg_price",
name="Average electricity price",
native_unit_of_measurement=f"{currency}/{UnitOfEnergy.KILO_WATT_HOUR}",
native_unit_of_measurement=f"{currency}/{energy_scale}",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:currency-eur",
suggested_display_precision=3,
Expand Down Expand Up @@ -129,7 +131,8 @@ async def async_setup_entry(
entities = []
entity = {}
for description in sensor_descriptions(
currency=config_entry.options.get(CONF_CURRENCY, DEFAULT_CURRENCY)
currency=config_entry.options.get(CONF_CURRENCY, DEFAULT_CURRENCY),
energy_scale=config_entry.options.get(CONF_ENERGY_SCALE, DEFAULT_ENERGY_SCALE)
):
entity = description
entities.append(
Expand Down
5 changes: 4 additions & 1 deletion custom_components/entsoe/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
"advanced_options": "I want to set VAT, template and calculation method (next step)",
"modifyer": "Price Modifyer Template (Optional)",
"currency": "Currency of the modified price (Optional)",
"energy_scale": "Energy scale (Optional)",
"name": "Name (Optional)"
}
},
"extra": {
"data": {
"VAT_value": "VAT tariff",
"modifyer": "Price Modifyer Template (Optional)",
"currency": "Currency of the modified price (Optional)"
"currency": "Currency of the modified price (Optional)",
"energy_scale": "Energy scale (Optional)"
}
}
},
Expand All @@ -35,6 +37,7 @@
"area": "Area*",
"modifyer": "Price Modifyer Template (Optional)",
"currency": "Currency of the modified price (Optional)",
"energy_scale": "Energy scale (Optional)",
"VAT_value": "VAT tariff",
"name": "Name (Optional)"
}
Expand Down

0 comments on commit eecb7e7

Please sign in to comment.