Skip to content

Commit

Permalink
Merge pull request #148 from suaveolent/main
Browse files Browse the repository at this point in the history
Change from ct/kWh to currency/kWh
  • Loading branch information
suaveolent authored Nov 4, 2024
2 parents cdd5b4e + da2749b commit b100b6e
Show file tree
Hide file tree
Showing 16 changed files with 151 additions and 155 deletions.
31 changes: 16 additions & 15 deletions custom_components/epex_spot/EPEXSpot/Awattar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import logging
"""Awattar API."""

from datetime import datetime, timedelta, timezone
import logging

import aiohttp
from homeassistant.util import dt

from homeassistant.util import dt as dt_util

from ...const import EUR_PER_MWH, UOM_EUR_PER_KWH

_LOGGER = logging.getLogger(__name__)


class Marketprice:
UOM_EUR_PER_MWh = "EUR/MWh"
"""Marketprice class for Awattar."""

def __init__(self, data):
assert data["unit"].lower() == self.UOM_EUR_PER_MWh.lower()
assert data["unit"].lower() == EUR_PER_MWH.lower()
self._start_time = datetime.fromtimestamp(
data["start_timestamp"] / 1000, tz=timezone.utc
)
self._end_time = datetime.fromtimestamp(
data["end_timestamp"] / 1000, tz=timezone.utc
)
self._price_eur_per_mwh = float(data["marketprice"])
self._price_per_kwh = round(float(data["marketprice"]) / 1000.0, 6)

def __repr__(self):
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_eur_per_mwh} {self.UOM_EUR_PER_MWh})" # noqa: E501
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_per_kwh} {UOM_EUR_PER_KWH})" # noqa: E501

@property
def start_time(self):
Expand All @@ -32,12 +37,8 @@ def end_time(self):
return self._end_time

@property
def price_eur_per_mwh(self):
return self._price_eur_per_mwh

@property
def price_ct_per_kwh(self):
return round(self._price_eur_per_mwh / 10, 3)
def price_per_kwh(self):
return self._price_per_kwh


def toEpochMilliSec(dt: datetime) -> int:
Expand Down Expand Up @@ -80,9 +81,9 @@ async def fetch(self):
self._marketdata = self._extract_marketdata(data["data"])

async def _fetch_data(self, url):
start = dt.now().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(
days=1
)
start = dt_util.now().replace(
hour=0, minute=0, second=0, microsecond=0
) - timedelta(days=1)
end = start + timedelta(days=3)
async with self._session.get(
url, params={"start": toEpochMilliSec(start), "end": toEpochMilliSec(end)}
Expand Down
21 changes: 10 additions & 11 deletions custom_components/epex_spot/EPEXSpot/EPEXSpotWeb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import logging
"""EPEX Spot Web Scraper."""

from datetime import datetime, timedelta, timezone
import logging
from zoneinfo import ZoneInfo

import aiohttp
from bs4 import BeautifulSoup

from ...const import UOM_EUR_PER_KWH, UOM_MWH

_LOGGER = logging.getLogger(__name__)


Expand All @@ -24,8 +28,7 @@ def _as_date(v):


class Marketprice:
UOM_EUR_PER_MWh = "EUR/MWh"
UOM_MWh = "MWh"
"""Marketprice class for EPEX Spot Web."""

def __init__(
self, start_time, end_time, buy_volume_mwh, sell_volume_mwh, volume_mwh, price
Expand All @@ -35,10 +38,10 @@ def __init__(
self._buy_volume_mwh = _to_float(buy_volume_mwh)
self._sell_volume_mwh = _to_float(sell_volume_mwh)
self._volume_mwh = _to_float(volume_mwh)
self._price_eur_per_mwh = _to_float(price)
self._price_per_kwh = round(_to_float(price) / 1000.0, 6)

def __repr__(self):
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, buy_volume_mwh: {self._buy_volume_mwh} {self.UOM_MWh}, sell_volume_mwh: {self._sell_volume_mwh} {self.UOM_MWh}, volume_mwh: {self._volume_mwh} {self.UOM_MWh}, marketprice: {self._price_eur_per_mwh} {self.UOM_EUR_PER_MWh})" # noqa: E501
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, buy_volume_mwh: {self._buy_volume_mwh} {UOM_MWH}, sell_volume_mwh: {self._sell_volume_mwh} {UOM_MWH}, volume_mwh: {self._volume_mwh} {UOM_MWH}, marketprice: {self._price_per_kwh} {UOM_EUR_PER_KWH})" # noqa: E501

@property
def start_time(self):
Expand All @@ -49,12 +52,8 @@ def end_time(self):
return self._end_time

@property
def price_eur_per_mwh(self):
return self._price_eur_per_mwh

@property
def price_ct_per_kwh(self):
return round(self._price_eur_per_mwh / 10, 3)
def price_per_kwh(self):
return self._price_per_kwh

@property
def buy_volume_mwh(self):
Expand Down
22 changes: 11 additions & 11 deletions custom_components/epex_spot/EPEXSpot/SMARD/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import logging
"""SMARD.de API."""

from datetime import datetime, timedelta, timezone
import logging

import aiohttp

from ...const import UOM_EUR_PER_KWH

# from homeassistant.util import dt

_LOGGER = logging.getLogger(__name__)
Expand All @@ -27,18 +31,18 @@


class Marketprice:
UOM_EUR_PER_MWh = "EUR/MWh"
"""Marketprice class for SMARD.de."""

def __init__(self, data):
self._start_time = datetime.fromtimestamp(data[0] / 1000, tz=timezone.utc)
self._end_time = self._start_time + timedelta(
hours=1
) # TODO: this will not work for 1/2h updates

self._price_eur_per_mwh = float(data[1])
self._price_per_kwh = round(float(data[1]) / 1000.0, 6)

def __repr__(self):
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_eur_per_mwh} {self.UOM_EUR_PER_MWh})" # noqa: E501
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_per_kwh} {UOM_EUR_PER_KWH})" # noqa: E501

@property
def start_time(self):
Expand All @@ -49,12 +53,8 @@ def end_time(self):
return self._end_time

@property
def price_eur_per_mwh(self):
return self._price_eur_per_mwh

@property
def price_ct_per_kwh(self):
return round(self._price_eur_per_mwh / 10, 3)
def price_per_kwh(self):
return self._price_per_kwh


class SMARD:
Expand Down Expand Up @@ -119,7 +119,7 @@ async def fetch(self):
# thats yesterday and today
self._marketdata = entries[
-48:
] # limit number of entries to protect HA recorder
] # limit number of entries to protect HA recorder
else:
# latest data is tomorrow, return 72 entries
# thats yesterday, today and tomorrow
Expand Down
21 changes: 11 additions & 10 deletions custom_components/epex_spot/EPEXSpot/Tibber/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import aiohttp
"""Tibber API."""

from datetime import datetime, timedelta

import aiohttp

from ...const import UOM_EUR_PER_KWH

TIBBER_QUERY = """
{
viewer {
Expand Down Expand Up @@ -30,17 +35,17 @@


class Marketprice:
UOM_CT_PER_kWh = "ct/kWh"
"""Marketprice class for Tibber."""

def __init__(self, data):
self._start_time = datetime.fromisoformat(data["startsAt"])
self._end_time = self._start_time + timedelta(hours=1)
# Tibber already returns the actual net price for the customer
# so we can use that
self._price_ct_per_kwh = round(float(data["total"]) * 100, 3)
self._price_per_kwh = round(float(data["total"]), 6)

def __repr__(self):
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_ct_per_kwh} {self.UOM_CT_PER_kWh})" # noqa: E501
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_per_kwh} {UOM_EUR_PER_KWH})" # noqa: E501

@property
def start_time(self):
Expand All @@ -51,12 +56,8 @@ def end_time(self):
return self._end_time

@property
def price_eur_per_mwh(self):
return round(self._price_ct_per_kwh * 10, 2)

@property
def price_ct_per_kwh(self):
return self._price_ct_per_kwh
def price_per_kwh(self):
return self._price_per_kwh


class Tibber:
Expand Down
26 changes: 13 additions & 13 deletions custom_components/epex_spot/EPEXSpot/smartENERGY/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
"""smartENERGY API."""

from datetime import datetime, timedelta
import logging

import aiohttp

from ...const import CT_PER_KWH

_LOGGER = logging.getLogger(__name__)


class Marketprice:
UOM_CT_PER_kWh = "ct/kWh"
"""Marketprice class for smartENERGY."""

def __init__(self, duration, data):
self._start_time = datetime.fromisoformat(data["date"])
self._end_time = self._start_time + timedelta(minutes=duration)
# price includes austrian vat (20%) -> remove to be consistent with other data sources
self._price_ct_per_kwh = round(float(data["value"]) / 1.2, 3)
self._price_per_kwh = round(float(data["value"]) / 100.0 / 1.2, 6)

def __repr__(self):
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_ct_per_kwh} {self.UOM_CT_PER_kWh})" # noqa: E501
return f"{self.__class__.__name__}(start: {self._start_time.isoformat()}, end: {self._end_time.isoformat()}, marketprice: {self._price_per_kwh} {CT_PER_KWH})" # noqa: E501

@property
def start_time(self):
Expand All @@ -30,12 +34,8 @@ def set_end_time(self, end_time):
self._end_time = end_time

@property
def price_eur_per_mwh(self):
return round(self._price_ct_per_kwh * 10, 2)

@property
def price_ct_per_kwh(self):
return self._price_ct_per_kwh
def price_per_kwh(self):
return self._price_per_kwh


class smartENERGY:
Expand All @@ -46,7 +46,7 @@ class smartENERGY:
def __init__(self, market_area, session: aiohttp.ClientSession):
self._session = session
self._market_area = market_area
self._duration = 15 # default value, can be overwritten by API response
self._duration = 15 # default value, can be overwritten by API response
self._marketdata = []

@property
Expand All @@ -72,7 +72,7 @@ def marketdata(self):
async def fetch(self):
data = await self._fetch_data(self.URL)
self._duration = data["interval"]
assert data["unit"].lower() == Marketprice.UOM_CT_PER_kWh.lower()
assert data["unit"].lower() == CT_PER_KWH.lower()
marketdata = self._extract_marketdata(data["data"])
# override duration and compress data
self._duration = 60
Expand All @@ -96,12 +96,12 @@ def _compress_marketdata(self, data):
if start == None:
start = entry
continue
is_price_equal = start.price_ct_per_kwh == entry.price_ct_per_kwh
is_price_equal = start.price_per_kwh == entry.price_per_kwh
is_continuation = start.end_time == entry.start_time
max_start_time = start.start_time + timedelta(minutes=self._duration)
is_same_hour = entry.start_time < max_start_time

if (is_price_equal & is_continuation & is_same_hour):
if is_price_equal & is_continuation & is_same_hour:
start.set_end_time(entry.end_time)
else:
entries.append(start)
Expand Down
24 changes: 14 additions & 10 deletions custom_components/epex_spot/SourceShell.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import aiohttp
"""SourceShell"""

from datetime import timedelta
import logging
from typing import Any

import aiohttp

from homeassistant.config_entries import ConfigEntry
from homeassistant.util import dt

from .const import (
CONF_DURATION,
CONF_EARLIEST_START_TIME,
CONF_EARLIEST_START_POST,
CONF_LATEST_END_TIME,
CONF_EARLIEST_START_TIME,
CONF_LATEST_END_POST,
CONF_LATEST_END_TIME,
CONF_MARKET_AREA,
CONF_SOURCE,
CONF_SOURCE_AWATTAR,
Expand All @@ -28,7 +31,7 @@
DEFAULT_TAX,
EMPTY_EXTREME_PRICE_INTERVAL_RESP,
)
from .EPEXSpot import SMARD, Awattar, EPEXSpotWeb, smartENERGY, Tibber
from .EPEXSpot import SMARD, Awattar, EPEXSpotWeb, Tibber, smartENERGY
from .extreme_price_interval import find_extreme_price_interval, get_start_times

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -131,12 +134,12 @@ def update_time(self):
self.marketdata,
)
sorted_sorted_marketdata_today = sorted(
sorted_marketdata_today, key=lambda e: e.price_eur_per_mwh
sorted_marketdata_today, key=lambda e: e.price_per_kwh
)
self._sorted_marketdata_today = sorted_sorted_marketdata_today

def to_net_price(self, price_eur_per_mwh):
net_p = price_eur_per_mwh / 10 # convert from EUR/MWh to ct/kWh
def to_net_price(self, price_per_kwh):
net_p = price_per_kwh

# Tibber already reaturns the net price for the customer
if "Tibber API" not in self.name:
Expand All @@ -151,7 +154,7 @@ def to_net_price(self, price_eur_per_mwh):
net_p += surcharge_abs
net_p *= 1 + (tax / 100)

return round(net_p, 3)
return round(net_p, 6)

def find_extreme_price_interval(self, call_data, cmp):
duration: timedelta = call_data[CONF_DURATION]
Expand All @@ -173,10 +176,11 @@ def find_extreme_price_interval(self, call_data, cmp):
if result is None:
return EMPTY_EXTREME_PRICE_INTERVAL_RESP

_LOGGER.error(f"result: {result}")

return {
"start": result["start"],
"end": result["start"] + duration,
"price_eur_per_mwh": result["price_per_hour"],
"price_ct_per_kwh": round(result["price_per_hour"] / 10, 3),
"price_per_kwh": round(result["price_per_hour"] / 1000, 6),
"net_price_ct_per_kwh": self.to_net_price(result["price_per_hour"]),
}
Loading

0 comments on commit b100b6e

Please sign in to comment.