From f1d53e6a3da6c36279076e4978c97a9d3e3e3753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20L=C3=BCtkemeier?= Date: Sun, 2 Feb 2025 17:08:46 +0100 Subject: [PATCH 1/3] add ostrom electricity tariff --- .../electricity_tariffs/ostrom/__init__.py | 0 .../electricity_tariffs/ostrom/config.py | 20 +++++++++ .../electricity_tariffs/ostrom/tariff.py | 45 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 packages/modules/electricity_tariffs/ostrom/__init__.py create mode 100644 packages/modules/electricity_tariffs/ostrom/config.py create mode 100644 packages/modules/electricity_tariffs/ostrom/tariff.py diff --git a/packages/modules/electricity_tariffs/ostrom/__init__.py b/packages/modules/electricity_tariffs/ostrom/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/electricity_tariffs/ostrom/config.py b/packages/modules/electricity_tariffs/ostrom/config.py new file mode 100644 index 0000000000..ed561b7a63 --- /dev/null +++ b/packages/modules/electricity_tariffs/ostrom/config.py @@ -0,0 +1,20 @@ +from typing import Optional + +class OstromTariffConfiguration: + def __init__(self, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + zip: Optional[str] = None): + self.client_id = client_id + self.client_secret = client_secret + self.zip = zip + + +class OstromTariff: + def __init__(self, + name: str = "ostrom", + type: str = "ostrom", + configuration: OstromTariffConfiguration = None) -> None: + self.name = name + self.type = type + self.configuration = configuration or OstromTariffConfiguration() diff --git a/packages/modules/electricity_tariffs/ostrom/tariff.py b/packages/modules/electricity_tariffs/ostrom/tariff.py new file mode 100644 index 0000000000..3bf48e1866 --- /dev/null +++ b/packages/modules/electricity_tariffs/ostrom/tariff.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +from base64 import b64encode +from datetime import datetime, timezone, timedelta +from urllib.parse import quote +from typing import Dict +from modules.common import req +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_state import TariffState +from modules.electricity_tariffs.ostrom.config import OstromTariffConfiguration +from modules.electricity_tariffs.ostrom.config import OstromTariff + +def fetch_prices(config: OstromTariffConfiguration) -> Dict[int, float]: + access_token = req.get_http_session().post( + url = "https://auth.production.ostrom-api.io/oauth2/token", + data = { "grant_type": "client_credentials" }, + headers = { + "accept": "application/json", + "content-type": "application/x-www-form-urlencoded", + "authorization": "Basic " + b64encode((config.client_id + ":" + config.client_secret).encode()).decode() + } + ).json()["access_token"] + utcnow = datetime.now(timezone.utc) + startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00.000Z")) + endDate = quote((utcnow + timedelta(days=1)).strftime("%Y-%m-%dT%H:00:00.000Z")) + raw_prices = req.get_http_session().get( + url = f"https://production.ostrom-api.io/spot-prices?startDate={startDate}&endDate={endDate}&resolution=HOUR&zip={config.zip}", + headers = { + "accept": "application/json", + "authorization": "Bearer " + access_token + } + ).json()["data"] + prices: Dict[int, float] = {} + for raw_price in raw_prices: + # Note: with Python >= 3.11, we can use: timestamp = datetime.fromisoformat(raw_price["date"]).timestamp() + timestamp = datetime.strptime(raw_price["date"], "%Y-%m-%dT%H:%M:%S.000Z").replace(tzinfo=timezone.utc).timestamp() + price = float(raw_price["grossKwhPrice"] + raw_price["grossKwhTaxAndLevies"]) / 100000 # ct/kWh --> EUR/Wh + prices.update({str(int(timestamp)): price}) + return prices + +def create_electricity_tariff(config: OstromTariff): + def updater(): + return TariffState(prices=fetch_prices(config.configuration)) + return updater + +device_descriptor = DeviceDescriptor(configuration_factory=OstromTariff) From 139f887341b027250d3bc87d726f36f255770778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20L=C3=BCtkemeier?= Date: Mon, 3 Feb 2025 18:26:25 +0100 Subject: [PATCH 2/3] fix flake8 style checks --- .../electricity_tariffs/ostrom/config.py | 1 + .../electricity_tariffs/ostrom/tariff.py | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/modules/electricity_tariffs/ostrom/config.py b/packages/modules/electricity_tariffs/ostrom/config.py index ed561b7a63..26fd3ef1d8 100644 --- a/packages/modules/electricity_tariffs/ostrom/config.py +++ b/packages/modules/electricity_tariffs/ostrom/config.py @@ -1,5 +1,6 @@ from typing import Optional + class OstromTariffConfiguration: def __init__(self, client_id: Optional[str] = None, diff --git a/packages/modules/electricity_tariffs/ostrom/tariff.py b/packages/modules/electricity_tariffs/ostrom/tariff.py index 3bf48e1866..8f70273dc5 100644 --- a/packages/modules/electricity_tariffs/ostrom/tariff.py +++ b/packages/modules/electricity_tariffs/ostrom/tariff.py @@ -9,11 +9,12 @@ from modules.electricity_tariffs.ostrom.config import OstromTariffConfiguration from modules.electricity_tariffs.ostrom.config import OstromTariff + def fetch_prices(config: OstromTariffConfiguration) -> Dict[int, float]: access_token = req.get_http_session().post( - url = "https://auth.production.ostrom-api.io/oauth2/token", - data = { "grant_type": "client_credentials" }, - headers = { + url="https://auth.production.ostrom-api.io/oauth2/token", + data={"grant_type": "client_credentials"}, + headers={ "accept": "application/json", "content-type": "application/x-www-form-urlencoded", "authorization": "Basic " + b64encode((config.client_id + ":" + config.client_secret).encode()).decode() @@ -21,10 +22,11 @@ def fetch_prices(config: OstromTariffConfiguration) -> Dict[int, float]: ).json()["access_token"] utcnow = datetime.now(timezone.utc) startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00.000Z")) - endDate = quote((utcnow + timedelta(days=1)).strftime("%Y-%m-%dT%H:00:00.000Z")) + endDate = quote((utcnow + timedelta(days=1)).strftime("%Y-%m-%dT%H:00:00.000Z")) raw_prices = req.get_http_session().get( - url = f"https://production.ostrom-api.io/spot-prices?startDate={startDate}&endDate={endDate}&resolution=HOUR&zip={config.zip}", - headers = { + url="https://production.ostrom-api.io/spot-prices?" + + f"startDate={startDate}&endDate={endDate}&resolution=HOUR&zip={config.zip}", + headers={ "accept": "application/json", "authorization": "Bearer " + access_token } @@ -32,14 +34,17 @@ def fetch_prices(config: OstromTariffConfiguration) -> Dict[int, float]: prices: Dict[int, float] = {} for raw_price in raw_prices: # Note: with Python >= 3.11, we can use: timestamp = datetime.fromisoformat(raw_price["date"]).timestamp() - timestamp = datetime.strptime(raw_price["date"], "%Y-%m-%dT%H:%M:%S.000Z").replace(tzinfo=timezone.utc).timestamp() - price = float(raw_price["grossKwhPrice"] + raw_price["grossKwhTaxAndLevies"]) / 100000 # ct/kWh --> EUR/Wh + timestamp = datetime.strptime(raw_price["date"], "%Y-%m-%dT%H:%M:%S.000Z")\ + .replace(tzinfo=timezone.utc).timestamp() + price = float(raw_price["grossKwhPrice"] + raw_price["grossKwhTaxAndLevies"]) / 100000 # ct/kWh --> EUR/Wh prices.update({str(int(timestamp)): price}) return prices + def create_electricity_tariff(config: OstromTariff): def updater(): return TariffState(prices=fetch_prices(config.configuration)) return updater + device_descriptor = DeviceDescriptor(configuration_factory=OstromTariff) From a15a99a7054c978690842c96a2fc038caf4bd3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20L=C3=BCtkemeier?= Date: Tue, 4 Feb 2025 19:47:50 +0100 Subject: [PATCH 3/3] fix request when no zip code provided --- packages/modules/electricity_tariffs/ostrom/tariff.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/modules/electricity_tariffs/ostrom/tariff.py b/packages/modules/electricity_tariffs/ostrom/tariff.py index 8f70273dc5..d3c9629586 100644 --- a/packages/modules/electricity_tariffs/ostrom/tariff.py +++ b/packages/modules/electricity_tariffs/ostrom/tariff.py @@ -23,9 +23,13 @@ def fetch_prices(config: OstromTariffConfiguration) -> Dict[int, float]: utcnow = datetime.now(timezone.utc) startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00.000Z")) endDate = quote((utcnow + timedelta(days=1)).strftime("%Y-%m-%dT%H:00:00.000Z")) + if config.zip: + zip = f"&zip={config.zip}" + else: + zip = "" raw_prices = req.get_http_session().get( url="https://production.ostrom-api.io/spot-prices?" + - f"startDate={startDate}&endDate={endDate}&resolution=HOUR&zip={config.zip}", + f"startDate={startDate}&endDate={endDate}&resolution=HOUR{zip}", headers={ "accept": "application/json", "authorization": "Bearer " + access_token