Skip to content

Commit

Permalink
Merge pull request #32 from mampfes/smard.de
Browse files Browse the repository at this point in the history
Smard.de
  • Loading branch information
mampfes authored Jul 1, 2023
2 parents 62feba7 + 2d38e1c commit 49c9739
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 12 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ You can choose between multiple sources:

1. Awattar

[Awattar](https://www.awattar.de/services/api) provides a free of charge service for their customers. Market price data is available for Germany and Austria. So far no user identifiation is required.
[Awattar](https://www.awattar.de/services/api) provides a free of charge service for their customers. Market price data is available for Germany and Austria. So far no user identifiation is required.

2. EPEX Spot Web Scraper

This source uses web scraping technologies to retrieve publicly available data from its [website](https://www.epexspot.com/en/market-data).

3. SMARD.de

[SMARD.de](https://www.smard.de) provides a free of charge API to retrieve a lot of information about electricity market including market prices. SMARD.de is serviced by the Bundesnetzagentur, Germany.

If you like this component, please give it a star on [github](https://github.com/mampfes/hacs_epex_spot_awattar).

## Installation
Expand Down
109 changes: 109 additions & 0 deletions custom_components/epex_spot/EPEXSpot/SMARD/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import logging
from datetime import datetime, timedelta, timezone

import requests

# from homeassistant.util import dt

_LOGGER = logging.getLogger(__name__)

MARKET_AREA_MAP = {
"DE-LU": 4169,
"Anrainer DE-LU": 5078,
"BE": 4996,
"NO2": 4997,
"AT": 4170,
"DK1": 252,
"DK2": 253,
"FR": 254,
"IT (North)": 255,
"NL": 256,
"PL": 257,
"CH": 259,
"SI": 260,
"CZ": 261,
"HU": 262,
}


class Marketprice:
UOM_EUR_PER_MWh = "EUR/MWh"

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/2 updates

self._price_eur_per_mwh = float(data[1])

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})"

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

@property
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 self._price_eur_per_mwh / 10


class SMARD:
URL = "https://www.smard.de/app/chart_data"

MARKET_AREAS = MARKET_AREA_MAP.keys()

def __init__(self, market_area):
self._market_area = market_area
self._marketdata = []

@property
def name(self):
return "SMARD.de"

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

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

def fetch(self):
data = self._fetch_data()
self._marketdata = self._extract_marketdata(data["series"])

def _fetch_data(self):
smard_filter = MARKET_AREA_MAP[self._market_area]
smard_region = "DE" # self._market_area
smard_resolution = "hour"

# get available timestamps for given market area
url = f"{self.URL}/{smard_filter}/{smard_region}/index_{smard_resolution}.json"
r = requests.get(url)
r.raise_for_status()

j = r.json()
latest_timestamp = j["timestamps"][-1]

# get available data
url = f"{self.URL}/{smard_filter}/{smard_region}/{smard_filter}_{smard_region}_{smard_resolution}_{latest_timestamp}.json"
r = requests.get(url)
r.raise_for_status()
return r.json()

def _extract_marketdata(self, data):
entries = []
for entry in data:
if entry[1] is not None:
entries.append(Marketprice(entry))
return entries[-72:] # limit number of entries to protect HA recorder
12 changes: 7 additions & 5 deletions custom_components/epex_spot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from homeassistant.util import dt

from .const import (CONF_MARKET_AREA, CONF_SOURCE, CONF_SOURCE_AWATTAR,
CONF_SOURCE_EPEX_SPOT_WEB, CONF_SURCHARGE_ABS,
CONF_SURCHARGE_PERC, CONF_TAX, DEFAULT_SURCHARGE_ABS,
DEFAULT_SURCHARGE_PERC, DEFAULT_TAX, DOMAIN,
UPDATE_SENSORS_SIGNAL)
from .EPEXSpot import Awattar, EPEXSpotWeb
CONF_SOURCE_EPEX_SPOT_WEB, CONF_SOURCE_SMARD_DE,
CONF_SURCHARGE_ABS, CONF_SURCHARGE_PERC, CONF_TAX,
DEFAULT_SURCHARGE_ABS, DEFAULT_SURCHARGE_PERC, DEFAULT_TAX,
DOMAIN, UPDATE_SENSORS_SIGNAL)
from .EPEXSpot import SMARD, Awattar, EPEXSpotWeb

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -169,6 +169,8 @@ def add_entry(self, config_entry: ConfigEntry):
source = EPEXSpotWeb.EPEXSpotWeb(
market_area=config_entry.data[CONF_MARKET_AREA]
)
elif config_entry.data[CONF_SOURCE] == CONF_SOURCE_SMARD_DE:
source = SMARD.SMARD(market_area=config_entry.data[CONF_MARKET_AREA])

source = SourceDecorator(config_entry, source)
self._sources[config_entry.unique_id] = source
Expand Down
17 changes: 12 additions & 5 deletions custom_components/epex_spot/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
from homeassistant.core import callback

from .const import (CONF_MARKET_AREA, CONF_SOURCE, CONF_SOURCE_AWATTAR,
CONF_SOURCE_EPEX_SPOT_WEB, CONF_SURCHARGE_ABS,
CONF_SURCHARGE_PERC, CONF_TAX, DEFAULT_SURCHARGE_ABS,
DEFAULT_SURCHARGE_PERC, DEFAULT_TAX, DOMAIN)
from .EPEXSpot import Awattar, EPEXSpotWeb
CONF_SOURCE_EPEX_SPOT_WEB, CONF_SOURCE_SMARD_DE,
CONF_SURCHARGE_ABS, CONF_SURCHARGE_PERC, CONF_TAX,
DEFAULT_SURCHARGE_ABS, DEFAULT_SURCHARGE_PERC, DEFAULT_TAX,
DOMAIN)
from .EPEXSpot import SMARD, Awattar, EPEXSpotWeb

CONF_SOURCE_LIST = (CONF_SOURCE_AWATTAR, CONF_SOURCE_EPEX_SPOT_WEB)
CONF_SOURCE_LIST = (
CONF_SOURCE_AWATTAR,
CONF_SOURCE_EPEX_SPOT_WEB,
CONF_SOURCE_SMARD_DE,
)


class EpexSpotConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # type: ignore
Expand Down Expand Up @@ -46,6 +51,8 @@ async def async_step_source(self, user_input=None):
areas = Awattar.Awattar.MARKET_AREAS
elif self._source_name == CONF_SOURCE_EPEX_SPOT_WEB:
areas = EPEXSpotWeb.EPEXSpotWeb.MARKET_AREAS
elif self._source_name == CONF_SOURCE_SMARD_DE:
areas = SMARD.SMARD.MARKET_AREAS

data_schema = vol.Schema(
{vol.Required(CONF_MARKET_AREA): vol.In(sorted(areas))}
Expand Down
1 change: 1 addition & 0 deletions custom_components/epex_spot/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# possible values for CONF_SOURCE
CONF_SOURCE_AWATTAR = "Awattar"
CONF_SOURCE_EPEX_SPOT_WEB = "EPEX Spot Web Scraper"
CONF_SOURCE_SMARD_DE = "SMARD.de"

# configuration options for net price calculation
CONF_SURCHARGE_PERC = "percentage_surcharge"
Expand Down
2 changes: 1 addition & 1 deletion custom_components/epex_spot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/mampfes/ha_epex_spot/issues",
"requirements": ["bs4"],
"version": "1.2.0"
"version": "1.3.0"
}
11 changes: 11 additions & 0 deletions custom_components/epex_spot/test_smard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python3

from EPEXSpot import SMARD

service = SMARD.SMARD(market_area="SW")
# print(service.MARKET_AREAS)

service.fetch()
print(f"count = {len(service.marketdata)}")
for e in service.marketdata:
print(f"{e.start_time}: {e.price_eur_per_mwh} {e.UOM_EUR_PER_MWh}")

0 comments on commit 49c9739

Please sign in to comment.