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 #128

Closed
wants to merge 13 commits into from
29 changes: 14 additions & 15 deletions custom_components/epex_spot/EPEXSpot/Awattar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import logging
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, 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 +35,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 +79,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
43 changes: 28 additions & 15 deletions custom_components/epex_spot/EPEXSpot/EPEXSpotWeb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import logging
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 @@ -16,25 +18,34 @@ def _as_date(v):
return v.strftime("%Y-%m-%d")


MARKET_AREA_MAP = {"GB-30": {"market_area": "GB", "duration": 30}}
MARKET_AREA_MAP = {
"GB-30": {"auction": "30-call-GB", "market_area": "GB", "duration": 30},
"GB": {"auction": "GB", "market_area": "GB", "duration": 60},
"CH": {"auction": "CH", "market_area": "CH", "duration": 60},
}


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
self,
start_time,
end_time,
buy_volume_mwh,
sell_volume_mwh,
volume_mwh,
price_mwh,
):
self._start_time = start_time
self._end_time = end_time
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_mwh) / 1000, 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} {self.UOM_MWh}, volume_mwh: {self._volume_mwh} {self.UOM_MWh}, marketprice: {self._price_per_kwh} {UOM_EUR_PER_KWH})" # noqa: E501

@property
def start_time(self):
Expand All @@ -45,12 +56,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 round(self._price_per_kwh, 4)

@property
def buy_volume_mwh(self):
Expand Down Expand Up @@ -100,9 +107,11 @@ def __init__(self, market_area, session: aiohttp.ClientSession):
if item is None:
self._int_market_area = market_area
self._duration = 60
self._auction = "MRC"
else:
self._int_market_area = item["market_area"]
self._duration = item["duration"]
self._auction = item["auction"]

self._marketdata = []

Expand Down Expand Up @@ -150,10 +159,14 @@ async def _fetch_day(self, delivery_date):

async def _fetch_data(self, delivery_date):
trading_date = delivery_date - timedelta(days=1)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0" # noqa
}
params = {
"market_area": self._int_market_area,
"trading_date": _as_date(trading_date),
"delivery_date": _as_date(delivery_date),
"auction": self._auction,
# "underlying_year": None,
"modality": "Auction",
"sub_modality": "DayAhead",
Expand Down Expand Up @@ -187,7 +200,7 @@ async def _fetch_data(self, delivery_date):
}

async with self._session.post(
self.URL, params=params, data=data, verify_ssl=False
self.URL, headers=headers, params=params, data=data, verify_ssl=False
) as resp:
resp.raise_for_status()
return await resp.json()
Expand Down Expand Up @@ -244,7 +257,7 @@ def _extract_table_data(self, delivery_date, data):
buy_volume_mwh=buy_volume_col.string,
sell_volume_mwh=sell_volume_col.string,
volume_mwh=volume_col.string,
price=price_col.string,
price_mwh=price_col.string,
)
)
start_time = end_time
Expand Down
20 changes: 9 additions & 11 deletions custom_components/epex_spot/EPEXSpot/SMARD/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import logging
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 +29,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, 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 +51,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 +117,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
114 changes: 114 additions & 0 deletions custom_components/epex_spot/EPEXSpot/Tibber/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from datetime import datetime, timedelta

import aiohttp

from ...const import UOM_EUR_PER_KWH

TIBBER_QUERY = """
{
viewer {
homes {
currentSubscription{
priceInfo{
today {
total
energy
tax
startsAt
currency
}
tomorrow {
total
energy
tax
startsAt
currency
}
}
}
}
}
}
"""


class Marketprice:
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_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_per_kwh} {UOM_EUR_PER_KWH})" # noqa: E501

@property
def start_time(self):
return self._start_time

@property
def end_time(self):
return self._end_time

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


class Tibber:
# DEMO_TOKEN = "5K4MVS-OjfWhK_4yrjOlFe1F6kJXPVf7eQYggo8ebAE"
# 123456789.123456789.123456789.123456789.123
URL = "https://api.tibber.com/v1-beta/gql"

MARKET_AREAS = ("de", "nl", "no", "se")

def __init__(self, market_area, token: str, session: aiohttp.ClientSession):
self._session = session
self._token = token
self._market_area = market_area
self._duration = 60
self._marketdata = []

@property
def name(self):
return "Tibber API v1-beta"

@property
def market_area(self):
return self._market_area

@property
def duration(self):
return self._duration

@property
def currency(self):
return "EUR"

@property
def marketdata(self):
return self._marketdata

async def fetch(self):
data = await self._fetch_data(self.URL)
self._marketdata = self._extract_marketdata(
data["data"]["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]
)

async def _fetch_data(self, url):
async with self._session.post(
self.URL,
data={"query": TIBBER_QUERY},
headers={"Authorization": "Bearer {}".format(self._token)},
) as resp:
resp.raise_for_status()
return await resp.json()

def _extract_marketdata(self, data):
entries = []
for entry in data["today"]:
entries.append(Marketprice(entry))
for entry in data["tomorrow"]:
entries.append(Marketprice(entry))
return entries
Loading