Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change from ct/kWh to currency/kWh #148

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
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