From 2b09a51f468758d4fe10209e0842bec939118bc2 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sat, 13 Sep 2025 11:04:49 +0200 Subject: [PATCH 01/12] add electricity tariffs for EKZ and Group E --- .../electricity_tariffs/ekz/__init__.py | 0 .../modules/electricity_tariffs/ekz/config.py | 18 +++++ .../modules/electricity_tariffs/ekz/tariff.py | 71 +++++++++++++++++++ .../electricity_tariffs/group_e/__init__.py | 0 .../electricity_tariffs/group_e/config.py | 17 +++++ .../electricity_tariffs/group_e/tariff.py | 63 ++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 packages/modules/electricity_tariffs/ekz/__init__.py create mode 100644 packages/modules/electricity_tariffs/ekz/config.py create mode 100644 packages/modules/electricity_tariffs/ekz/tariff.py create mode 100644 packages/modules/electricity_tariffs/group_e/__init__.py create mode 100644 packages/modules/electricity_tariffs/group_e/config.py create mode 100644 packages/modules/electricity_tariffs/group_e/tariff.py diff --git a/packages/modules/electricity_tariffs/ekz/__init__.py b/packages/modules/electricity_tariffs/ekz/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/electricity_tariffs/ekz/config.py b/packages/modules/electricity_tariffs/ekz/config.py new file mode 100644 index 0000000000..edd1c4ceea --- /dev/null +++ b/packages/modules/electricity_tariffs/ekz/config.py @@ -0,0 +1,18 @@ +from typing import Optional + + +class EkzTariffConfiguration: + def __init__(self): + self.country = "ch" + self.unit = "rp" + +class EkzTariff: + def __init__(self, + name: str = "EKZ (CH)", + type: str = "ekz", + official: bool = False, + configuration: EkzTariffConfiguration = None) -> None: + self.name = name + self.type = type + self.official = official + self.configuration = configuration or EkzTariffConfiguration() diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py new file mode 100644 index 0000000000..ee054b1ab5 --- /dev/null +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +from datetime import datetime, timezone, timedelta +from dateutil import tz +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.ekz.config import EkzTariffConfiguration +from modules.electricity_tariffs.ekz.config import EkzTariff + +# Combine power and grid prices, convert to kWh +def addPrices (power: dict, grid: dict) -> tuple[str, float]: + timestamp = str(int(datetime.strptime(power['start_timestamp'],"%Y-%m-%dT%H:%M:%S%z")\ + .astimezone(tz.tzutc()).timestamp())) + power_price = power['electricity'][1]['value'] + grid_price = grid['grid'][0]['value'] + return (timestamp, (power_price+grid_price)/1000) + +# Read prices from EKZ API +def readApi() -> list[tuple[str, float]]: + endpoint="https://api.tariffs.ekz.ch/v1/tariffs" + tariff_power="electricity_dynamic" + tariff_grid="grid_400D_inclFees" + utcnow = datetime.now(timezone.utc) + startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00Z")) + endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00Z")) + session=req.get_http_session() + power_raw = session.get( + url=endpoint + + f"?tariff_name={tariff_power}&start_timestamp={startDate}&end_timestamp={endDate}", + ).json()["prices"] + grid_raw = session.get( + url=endpoint + + f"?tariff_name={tariff_grid}&start_timestamp={startDate}&end_timestamp={endDate}", + ).json()["prices"] + return list(map(addPrices, power_raw, grid_raw)) + +# Aggregate 15min prices to hourly prices by taking the maximum price in each hour +def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: + hourlyPrices = [] + currentHourPrices = [] + currentTimestamp = 0 + for p in quarterlyPrices: + time = datetime.fromtimestamp(int(p[0])) + if time.minute == 0: + if len(currentHourPrices) > 0: + hourlyPrices.append((currentTimestamp, max(currentHourPrices))) + currentHourPrices = [] + currentTimestamp = p[0] + else: + currentHourPrices.append(p[1]) + if len(currentHourPrices) > 0: + hourlyPrices.append((currentTimestamp, max(currentHourPrices))) + return hourlyPrices + +def fetch_prices (config: EkzTariffConfiguration) -> Dict[str, float]: + # Fetch electricity prices from EKZ API + # API Reference: https://api.tariffs.ekz.ch/swagger + pricelist = readApi() + hourly_list = aggregatePrices(pricelist) + prices: Dict[str, float] = dict(hourly_list) + return prices + +def create_electricity_tariff(config: EkzTariff): + def updater(): + return TariffState(prices=fetch_prices(config.configuration)) + return updater + + +device_descriptor = DeviceDescriptor(configuration_factory=EkzTariff) diff --git a/packages/modules/electricity_tariffs/group_e/__init__.py b/packages/modules/electricity_tariffs/group_e/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/electricity_tariffs/group_e/config.py b/packages/modules/electricity_tariffs/group_e/config.py new file mode 100644 index 0000000000..c694644fe2 --- /dev/null +++ b/packages/modules/electricity_tariffs/group_e/config.py @@ -0,0 +1,17 @@ +from typing import Optional + + +class GroupeETariffConfiguration: + def __init__(self): + self.country = "ch" + +class GroupeETariff: + def __init__(self, + name: str = "Groupe E (CH)", + type: str = "groupe_e", + official: bool = False, + configuration: GroupeETariffConfiguration = None) -> None: + self.name = name + self.type = type + self.official = official + self.configuration = configuration or GroupeETariffConfiguration() diff --git a/packages/modules/electricity_tariffs/group_e/tariff.py b/packages/modules/electricity_tariffs/group_e/tariff.py new file mode 100644 index 0000000000..bf921ef61e --- /dev/null +++ b/packages/modules/electricity_tariffs/group_e/tariff.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +from datetime import datetime, timezone, timedelta +from dateutil import tz +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.groupe_e.config import GroupeETariffConfiguration +from modules.electricity_tariffs.groupe_e.config import GroupeETariff + +# Combine power and grid prices, convert to kWh +def transformPrices (power: dict) -> tuple[str, float]: + timestamp = str(int(datetime.strptime(power['start_timestamp'],"%Y-%m-%dT%H:%M:%S%z")\ + .astimezone(tz.tzutc()).timestamp())) + power_price = power['vario_plus'] + return (timestamp, power_price/100000) + +# Read prices from Groupe E API +def readApi() -> list[tuple[str, float]]: + endpoint="https://api.tariffs.groupe-e.ch/v1/tariffs" + utcnow = datetime.now(timezone.utc) + startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00+02:00")) + endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00+02:00")) + session=req.get_http_session() + power_raw = session.get( + url=endpoint + + f"?start_timestamp={startDate}&end_timestamp={endDate}", + ).json() + return list(map(transformPrices, power_raw)) + +# Aggregate 15min prices to hourly prices by taking the maximum price in each hour +def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: + hourlyPrices = [] + currentHourPrices = [] + currentTimestamp = 0 + for p in quarterlyPrices: + time = datetime.fromtimestamp(int(p[0])) + if time.minute == 0: + if len(currentHourPrices) > 0: + hourlyPrices.append((currentTimestamp, max(currentHourPrices))) + currentHourPrices = [] + currentTimestamp = p[0] + else: + currentHourPrices.append(p[1]) + if len(currentHourPrices) > 0: + hourlyPrices.append((currentTimestamp, max(currentHourPrices))) + return hourlyPrices + +def fetch_prices (config: GroupeETariffConfiguration) -> Dict[str, float]: + # Fetch electricity prices from EKZ API + pricelist = readApi() + hourly_list = aggregatePrices(pricelist) + prices: Dict[str, float] = dict(hourly_list) + return prices + +def create_electricity_tariff(config: GroupeETariff): + def updater(): + return TariffState(prices=fetch_prices(config.configuration)) + return updater + + +device_descriptor = DeviceDescriptor(configuration_factory=GroupeETariff) From ed371b154da05fcfcd3ef3b204d019e71ee70905 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sat, 13 Sep 2025 18:01:06 +0200 Subject: [PATCH 02/12] fix lint errors --- packages/modules/electricity_tariffs/ekz/tariff.py | 2 +- .../modules/electricity_tariffs/group_e/tariff.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index ee054b1ab5..9c4ba6cfef 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -24,7 +24,7 @@ def readApi() -> list[tuple[str, float]]: tariff_grid="grid_400D_inclFees" utcnow = datetime.now(timezone.utc) startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00Z")) - endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00Z")) + endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00Z")) session=req.get_http_session() power_raw = session.get( url=endpoint + diff --git a/packages/modules/electricity_tariffs/group_e/tariff.py b/packages/modules/electricity_tariffs/group_e/tariff.py index bf921ef61e..f6206e2679 100644 --- a/packages/modules/electricity_tariffs/group_e/tariff.py +++ b/packages/modules/electricity_tariffs/group_e/tariff.py @@ -10,18 +10,19 @@ from modules.electricity_tariffs.groupe_e.config import GroupeETariff # Combine power and grid prices, convert to kWh -def transformPrices (power: dict) -> tuple[str, float]: - timestamp = str(int(datetime.strptime(power['start_timestamp'],"%Y-%m-%dT%H:%M:%S%z")\ +def transformPrices(power: dict) -> tuple[str, float]: + timestamp = str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") .astimezone(tz.tzutc()).timestamp())) power_price = power['vario_plus'] return (timestamp, power_price/100000) + # Read prices from Groupe E API def readApi() -> list[tuple[str, float]]: endpoint="https://api.tariffs.groupe-e.ch/v1/tariffs" utcnow = datetime.now(timezone.utc) startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00+02:00")) - endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00+02:00")) + endDate = quote((utcnow + timedelta(days = 2)).strftime("%Y-%m-%dT%H:00:00+02:00")) session=req.get_http_session() power_raw = session.get( url=endpoint + @@ -29,8 +30,9 @@ def readApi() -> list[tuple[str, float]]: ).json() return list(map(transformPrices, power_raw)) + # Aggregate 15min prices to hourly prices by taking the maximum price in each hour -def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: +def aggregatePrices(quarterlyPrices) -> list[tuple[str, float]]: hourlyPrices = [] currentHourPrices = [] currentTimestamp = 0 @@ -47,7 +49,7 @@ def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: hourlyPrices.append((currentTimestamp, max(currentHourPrices))) return hourlyPrices -def fetch_prices (config: GroupeETariffConfiguration) -> Dict[str, float]: +def fetch_prices(config: GroupeETariffConfiguration) -> Dict[str, float]: # Fetch electricity prices from EKZ API pricelist = readApi() hourly_list = aggregatePrices(pricelist) From 36dc915a6bb0a921e305045cf5e80599b04c9d8c Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sun, 14 Sep 2025 08:21:07 +0200 Subject: [PATCH 03/12] fix github lint issues --- .../modules/electricity_tariffs/group_e/tariff.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/modules/electricity_tariffs/group_e/tariff.py b/packages/modules/electricity_tariffs/group_e/tariff.py index f6206e2679..17a99bda86 100644 --- a/packages/modules/electricity_tariffs/group_e/tariff.py +++ b/packages/modules/electricity_tariffs/group_e/tariff.py @@ -9,6 +9,7 @@ from modules.electricity_tariffs.groupe_e.config import GroupeETariffConfiguration from modules.electricity_tariffs.groupe_e.config import GroupeETariff + # Combine power and grid prices, convert to kWh def transformPrices(power: dict) -> tuple[str, float]: timestamp = str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") @@ -19,14 +20,14 @@ def transformPrices(power: dict) -> tuple[str, float]: # Read prices from Groupe E API def readApi() -> list[tuple[str, float]]: - endpoint="https://api.tariffs.groupe-e.ch/v1/tariffs" + endpoint = "https://api.tariffs.groupe-e.ch/v1/tariffs" utcnow = datetime.now(timezone.utc) startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00+02:00")) - endDate = quote((utcnow + timedelta(days = 2)).strftime("%Y-%m-%dT%H:00:00+02:00")) - session=req.get_http_session() + endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00+02:00")) + session = req.get_http_session() power_raw = session.get( url=endpoint + - f"?start_timestamp={startDate}&end_timestamp={endDate}", + f"?start_timestamp={startDate}&end_timestamp={endDate}", ).json() return list(map(transformPrices, power_raw)) @@ -46,9 +47,10 @@ def aggregatePrices(quarterlyPrices) -> list[tuple[str, float]]: else: currentHourPrices.append(p[1]) if len(currentHourPrices) > 0: - hourlyPrices.append((currentTimestamp, max(currentHourPrices))) + hourlyPrices.append((currentTimestamp, max(currentHourPrices))) return hourlyPrices + def fetch_prices(config: GroupeETariffConfiguration) -> Dict[str, float]: # Fetch electricity prices from EKZ API pricelist = readApi() @@ -56,6 +58,7 @@ def fetch_prices(config: GroupeETariffConfiguration) -> Dict[str, float]: prices: Dict[str, float] = dict(hourly_list) return prices + def create_electricity_tariff(config: GroupeETariff): def updater(): return TariffState(prices=fetch_prices(config.configuration)) From 696fbaecfbf7ed1774ce4195749f93a2d860138d Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sun, 14 Sep 2025 08:42:17 +0200 Subject: [PATCH 04/12] fix github flake issues --- packages/modules/electricity_tariffs/ekz/config.py | 1 + packages/modules/electricity_tariffs/ekz/tariff.py | 13 +++++++++---- .../modules/electricity_tariffs/group_e/config.py | 4 +--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/modules/electricity_tariffs/ekz/config.py b/packages/modules/electricity_tariffs/ekz/config.py index edd1c4ceea..53dcd3b1c5 100644 --- a/packages/modules/electricity_tariffs/ekz/config.py +++ b/packages/modules/electricity_tariffs/ekz/config.py @@ -6,6 +6,7 @@ def __init__(self): self.country = "ch" self.unit = "rp" + class EkzTariff: def __init__(self, name: str = "EKZ (CH)", diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index 9c4ba6cfef..1db4d330a0 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -9,6 +9,7 @@ from modules.electricity_tariffs.ekz.config import EkzTariffConfiguration from modules.electricity_tariffs.ekz.config import EkzTariff + # Combine power and grid prices, convert to kWh def addPrices (power: dict, grid: dict) -> tuple[str, float]: timestamp = str(int(datetime.strptime(power['start_timestamp'],"%Y-%m-%dT%H:%M:%S%z")\ @@ -17,15 +18,16 @@ def addPrices (power: dict, grid: dict) -> tuple[str, float]: grid_price = grid['grid'][0]['value'] return (timestamp, (power_price+grid_price)/1000) + # Read prices from EKZ API def readApi() -> list[tuple[str, float]]: - endpoint="https://api.tariffs.ekz.ch/v1/tariffs" - tariff_power="electricity_dynamic" - tariff_grid="grid_400D_inclFees" + endpoint = "https://api.tariffs.ekz.ch/v1/tariffs" + tariff_power = "electricity_dynamic" + tariff_grid = "grid_400D_inclFees" utcnow = datetime.now(timezone.utc) startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00Z")) endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00Z")) - session=req.get_http_session() + session = req.get_http_session() power_raw = session.get( url=endpoint + f"?tariff_name={tariff_power}&start_timestamp={startDate}&end_timestamp={endDate}", @@ -36,6 +38,7 @@ def readApi() -> list[tuple[str, float]]: ).json()["prices"] return list(map(addPrices, power_raw, grid_raw)) + # Aggregate 15min prices to hourly prices by taking the maximum price in each hour def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: hourlyPrices = [] @@ -54,6 +57,7 @@ def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: hourlyPrices.append((currentTimestamp, max(currentHourPrices))) return hourlyPrices + def fetch_prices (config: EkzTariffConfiguration) -> Dict[str, float]: # Fetch electricity prices from EKZ API # API Reference: https://api.tariffs.ekz.ch/swagger @@ -62,6 +66,7 @@ def fetch_prices (config: EkzTariffConfiguration) -> Dict[str, float]: prices: Dict[str, float] = dict(hourly_list) return prices + def create_electricity_tariff(config: EkzTariff): def updater(): return TariffState(prices=fetch_prices(config.configuration)) diff --git a/packages/modules/electricity_tariffs/group_e/config.py b/packages/modules/electricity_tariffs/group_e/config.py index c694644fe2..af7b45d81c 100644 --- a/packages/modules/electricity_tariffs/group_e/config.py +++ b/packages/modules/electricity_tariffs/group_e/config.py @@ -1,10 +1,8 @@ -from typing import Optional - - class GroupeETariffConfiguration: def __init__(self): self.country = "ch" + class GroupeETariff: def __init__(self, name: str = "Groupe E (CH)", From ce520905fcaa57e46f45cea4cb1b5702449ed163 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sun, 14 Sep 2025 08:49:20 +0200 Subject: [PATCH 05/12] fix github flake issues --- packages/modules/electricity_tariffs/ekz/config.py | 3 --- packages/modules/electricity_tariffs/ekz/tariff.py | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/modules/electricity_tariffs/ekz/config.py b/packages/modules/electricity_tariffs/ekz/config.py index 53dcd3b1c5..cffea9a795 100644 --- a/packages/modules/electricity_tariffs/ekz/config.py +++ b/packages/modules/electricity_tariffs/ekz/config.py @@ -1,6 +1,3 @@ -from typing import Optional - - class EkzTariffConfiguration: def __init__(self): self.country = "ch" diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index 1db4d330a0..e61067787b 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -11,8 +11,8 @@ # Combine power and grid prices, convert to kWh -def addPrices (power: dict, grid: dict) -> tuple[str, float]: - timestamp = str(int(datetime.strptime(power['start_timestamp'],"%Y-%m-%dT%H:%M:%S%z")\ +def addPrices(power: dict, grid: dict) -> tuple[str, float]: + timestamp = str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") .astimezone(tz.tzutc()).timestamp())) power_price = power['electricity'][1]['value'] grid_price = grid['grid'][0]['value'] @@ -30,7 +30,7 @@ def readApi() -> list[tuple[str, float]]: session = req.get_http_session() power_raw = session.get( url=endpoint + - f"?tariff_name={tariff_power}&start_timestamp={startDate}&end_timestamp={endDate}", + f"?tariff_name={tariff_power}&start_timestamp={startDate}&end_timestamp={endDate}", ).json()["prices"] grid_raw = session.get( url=endpoint + @@ -40,7 +40,7 @@ def readApi() -> list[tuple[str, float]]: # Aggregate 15min prices to hourly prices by taking the maximum price in each hour -def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: +def aggregatePrices(quarterlyPrices) -> list[tuple[str, float]]: hourlyPrices = [] currentHourPrices = [] currentTimestamp = 0 @@ -58,7 +58,7 @@ def aggregatePrices (quarterlyPrices) -> list[tuple[str, float]]: return hourlyPrices -def fetch_prices (config: EkzTariffConfiguration) -> Dict[str, float]: +def fetch_prices(config: EkzTariffConfiguration) -> Dict[str, float]: # Fetch electricity prices from EKZ API # API Reference: https://api.tariffs.ekz.ch/swagger pricelist = readApi() From 3ae6bf6035cc201aa4cf4e526412ebef67297a7b Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sun, 14 Sep 2025 08:52:41 +0200 Subject: [PATCH 06/12] fix github flake issues --- packages/modules/electricity_tariffs/ekz/tariff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index e61067787b..144e9c1b18 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -34,7 +34,7 @@ def readApi() -> list[tuple[str, float]]: ).json()["prices"] grid_raw = session.get( url=endpoint + - f"?tariff_name={tariff_grid}&start_timestamp={startDate}&end_timestamp={endDate}", + f"?tariff_name={tariff_grid}&start_timestamp={startDate}&end_timestamp={endDate}", ).json()["prices"] return list(map(addPrices, power_raw, grid_raw)) From 7b10a3c36b579158ab916dd6de25136d3118fcb5 Mon Sep 17 00:00:00 2001 From: cshagen Date: Thu, 23 Oct 2025 12:34:07 +0200 Subject: [PATCH 07/12] Update packages/modules/electricity_tariffs/group_e/tariff.py Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> --- packages/modules/electricity_tariffs/group_e/tariff.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/electricity_tariffs/group_e/tariff.py b/packages/modules/electricity_tariffs/group_e/tariff.py index 17a99bda86..ddca957c00 100644 --- a/packages/modules/electricity_tariffs/group_e/tariff.py +++ b/packages/modules/electricity_tariffs/group_e/tariff.py @@ -6,8 +6,8 @@ from modules.common import req from modules.common.abstract_device import DeviceDescriptor from modules.common.component_state import TariffState -from modules.electricity_tariffs.groupe_e.config import GroupeETariffConfiguration -from modules.electricity_tariffs.groupe_e.config import GroupeETariff +from modules.electricity_tariffs.group_e.config import GroupeETariffConfiguration +from modules.electricity_tariffs.group_e.config import GroupeETariff # Combine power and grid prices, convert to kWh From 3b0d0bc016124ba08c41b493974adc02002cb26a Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Fri, 24 Oct 2025 21:33:23 +0200 Subject: [PATCH 08/12] switch to 15min interval --- .../modules/electricity_tariffs/{group_e => groupe_e}/__init__.py | 0 .../modules/electricity_tariffs/{group_e => groupe_e}/config.py | 0 .../modules/electricity_tariffs/{group_e => groupe_e}/tariff.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename packages/modules/electricity_tariffs/{group_e => groupe_e}/__init__.py (100%) rename packages/modules/electricity_tariffs/{group_e => groupe_e}/config.py (100%) rename packages/modules/electricity_tariffs/{group_e => groupe_e}/tariff.py (100%) diff --git a/packages/modules/electricity_tariffs/group_e/__init__.py b/packages/modules/electricity_tariffs/groupe_e/__init__.py similarity index 100% rename from packages/modules/electricity_tariffs/group_e/__init__.py rename to packages/modules/electricity_tariffs/groupe_e/__init__.py diff --git a/packages/modules/electricity_tariffs/group_e/config.py b/packages/modules/electricity_tariffs/groupe_e/config.py similarity index 100% rename from packages/modules/electricity_tariffs/group_e/config.py rename to packages/modules/electricity_tariffs/groupe_e/config.py diff --git a/packages/modules/electricity_tariffs/group_e/tariff.py b/packages/modules/electricity_tariffs/groupe_e/tariff.py similarity index 100% rename from packages/modules/electricity_tariffs/group_e/tariff.py rename to packages/modules/electricity_tariffs/groupe_e/tariff.py From 18d362ee364001a079493011378f392cada56830 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Fri, 24 Oct 2025 21:50:44 +0200 Subject: [PATCH 09/12] 15 minute intervals for tariffs --- .../modules/electricity_tariffs/ekz/tariff.py | 22 +------------------ .../electricity_tariffs/groupe_e/tariff.py | 22 +------------------ 2 files changed, 2 insertions(+), 42 deletions(-) diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index 144e9c1b18..306942025c 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -39,31 +39,11 @@ def readApi() -> list[tuple[str, float]]: return list(map(addPrices, power_raw, grid_raw)) -# Aggregate 15min prices to hourly prices by taking the maximum price in each hour -def aggregatePrices(quarterlyPrices) -> list[tuple[str, float]]: - hourlyPrices = [] - currentHourPrices = [] - currentTimestamp = 0 - for p in quarterlyPrices: - time = datetime.fromtimestamp(int(p[0])) - if time.minute == 0: - if len(currentHourPrices) > 0: - hourlyPrices.append((currentTimestamp, max(currentHourPrices))) - currentHourPrices = [] - currentTimestamp = p[0] - else: - currentHourPrices.append(p[1]) - if len(currentHourPrices) > 0: - hourlyPrices.append((currentTimestamp, max(currentHourPrices))) - return hourlyPrices - - def fetch_prices(config: EkzTariffConfiguration) -> Dict[str, float]: # Fetch electricity prices from EKZ API # API Reference: https://api.tariffs.ekz.ch/swagger pricelist = readApi() - hourly_list = aggregatePrices(pricelist) - prices: Dict[str, float] = dict(hourly_list) + prices: Dict[str, float] = dict(pricelist) return prices diff --git a/packages/modules/electricity_tariffs/groupe_e/tariff.py b/packages/modules/electricity_tariffs/groupe_e/tariff.py index ddca957c00..175175645a 100644 --- a/packages/modules/electricity_tariffs/groupe_e/tariff.py +++ b/packages/modules/electricity_tariffs/groupe_e/tariff.py @@ -32,30 +32,10 @@ def readApi() -> list[tuple[str, float]]: return list(map(transformPrices, power_raw)) -# Aggregate 15min prices to hourly prices by taking the maximum price in each hour -def aggregatePrices(quarterlyPrices) -> list[tuple[str, float]]: - hourlyPrices = [] - currentHourPrices = [] - currentTimestamp = 0 - for p in quarterlyPrices: - time = datetime.fromtimestamp(int(p[0])) - if time.minute == 0: - if len(currentHourPrices) > 0: - hourlyPrices.append((currentTimestamp, max(currentHourPrices))) - currentHourPrices = [] - currentTimestamp = p[0] - else: - currentHourPrices.append(p[1]) - if len(currentHourPrices) > 0: - hourlyPrices.append((currentTimestamp, max(currentHourPrices))) - return hourlyPrices - - def fetch_prices(config: GroupeETariffConfiguration) -> Dict[str, float]: # Fetch electricity prices from EKZ API pricelist = readApi() - hourly_list = aggregatePrices(pricelist) - prices: Dict[str, float] = dict(hourly_list) + prices: Dict[str, float] = dict(pricelist) return prices From 5e48c56f173f80da195c03c591851032ae0a9666 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Fri, 24 Oct 2025 21:57:44 +0200 Subject: [PATCH 10/12] 15 minute intervals for tariffs --- packages/modules/electricity_tariffs/groupe_e/tariff.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/electricity_tariffs/groupe_e/tariff.py b/packages/modules/electricity_tariffs/groupe_e/tariff.py index 175175645a..04e2b506d0 100644 --- a/packages/modules/electricity_tariffs/groupe_e/tariff.py +++ b/packages/modules/electricity_tariffs/groupe_e/tariff.py @@ -6,8 +6,8 @@ from modules.common import req from modules.common.abstract_device import DeviceDescriptor from modules.common.component_state import TariffState -from modules.electricity_tariffs.group_e.config import GroupeETariffConfiguration -from modules.electricity_tariffs.group_e.config import GroupeETariff +from modules.electricity_tariffs.groupe_e.config import GroupeETariffConfiguration +from modules.electricity_tariffs.groupe_e.config import GroupeETariff # Combine power and grid prices, convert to kWh From fa698186203edc4ca772b4b24de5f54e49d230c8 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Sun, 26 Oct 2025 16:01:52 +0100 Subject: [PATCH 11/12] fix ekz price calculation --- packages/modules/electricity_tariffs/ekz/tariff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index 306942025c..e27dbe800c 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -15,7 +15,7 @@ def addPrices(power: dict, grid: dict) -> tuple[str, float]: timestamp = str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") .astimezone(tz.tzutc()).timestamp())) power_price = power['electricity'][1]['value'] - grid_price = grid['grid'][0]['value'] + grid_price = grid['grid'][1]['value'] return (timestamp, (power_price+grid_price)/1000) From a6886b568e2c69e1b69f6e6eaca5aeab4fde2121 Mon Sep 17 00:00:00 2001 From: Claus Hagen Date: Mon, 27 Oct 2025 13:33:22 +0100 Subject: [PATCH 12/12] update ekz and group-e prices modeules --- .../modules/electricity_tariffs/ekz/tariff.py | 26 +++++++++--------- .../electricity_tariffs/groupe_e/tariff.py | 27 +++++++++---------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/modules/electricity_tariffs/ekz/tariff.py b/packages/modules/electricity_tariffs/ekz/tariff.py index e27dbe800c..b33e010b5b 100644 --- a/packages/modules/electricity_tariffs/ekz/tariff.py +++ b/packages/modules/electricity_tariffs/ekz/tariff.py @@ -10,13 +10,10 @@ from modules.electricity_tariffs.ekz.config import EkzTariff -# Combine power and grid prices, convert to kWh -def addPrices(power: dict, grid: dict) -> tuple[str, float]: - timestamp = str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") - .astimezone(tz.tzutc()).timestamp())) - power_price = power['electricity'][1]['value'] - grid_price = grid['grid'][1]['value'] - return (timestamp, (power_price+grid_price)/1000) +# Extract timestamp from power price entry +def timestamp(power): + return str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") + .astimezone(tz.tzutc()).timestamp())) # Read prices from EKZ API @@ -25,23 +22,24 @@ def readApi() -> list[tuple[str, float]]: tariff_power = "electricity_dynamic" tariff_grid = "grid_400D_inclFees" utcnow = datetime.now(timezone.utc) - startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00Z")) - endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00Z")) + startDate = utcnow.strftime("%Y-%m-%dT%H:00:00Z") + endDate = (utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00Z") session = req.get_http_session() power_raw = session.get( url=endpoint + - f"?tariff_name={tariff_power}&start_timestamp={startDate}&end_timestamp={endDate}", + f"?tariff_name={tariff_power}&start_timestamp={quote(startDate)}&end_timestamp={quote(endDate)}", ).json()["prices"] grid_raw = session.get( url=endpoint + - f"?tariff_name={tariff_grid}&start_timestamp={startDate}&end_timestamp={endDate}", + f"?tariff_name={tariff_grid}&start_timestamp={quote(startDate)}&end_timestamp={quote(endDate)}", ).json()["prices"] - return list(map(addPrices, power_raw, grid_raw)) + return [(timestamp(power), (power['electricity'][1]['value']+grid['grid'][1]['value'])/1000) + for power, grid in zip(power_raw, grid_raw)] +# Fetch electricity prices from EKZ API +# API Reference: https://api.tariffs.ekz.ch/swagger def fetch_prices(config: EkzTariffConfiguration) -> Dict[str, float]: - # Fetch electricity prices from EKZ API - # API Reference: https://api.tariffs.ekz.ch/swagger pricelist = readApi() prices: Dict[str, float] = dict(pricelist) return prices diff --git a/packages/modules/electricity_tariffs/groupe_e/tariff.py b/packages/modules/electricity_tariffs/groupe_e/tariff.py index 04e2b506d0..f4ee04ba73 100644 --- a/packages/modules/electricity_tariffs/groupe_e/tariff.py +++ b/packages/modules/electricity_tariffs/groupe_e/tariff.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from datetime import datetime, timezone, timedelta +from datetime import datetime, timedelta from dateutil import tz from urllib.parse import quote from typing import Dict @@ -10,30 +10,29 @@ from modules.electricity_tariffs.groupe_e.config import GroupeETariff -# Combine power and grid prices, convert to kWh -def transformPrices(power: dict) -> tuple[str, float]: - timestamp = str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") - .astimezone(tz.tzutc()).timestamp())) - power_price = power['vario_plus'] - return (timestamp, power_price/100000) +# Extract timestamp from power price entry +def timestamp(power): + return str(int(datetime.strptime(power['start_timestamp'], "%Y-%m-%dT%H:%M:%S%z") + .astimezone(tz.tzutc()).timestamp())) # Read prices from Groupe E API def readApi() -> list[tuple[str, float]]: endpoint = "https://api.tariffs.groupe-e.ch/v1/tariffs" - utcnow = datetime.now(timezone.utc) - startDate = quote(utcnow.strftime("%Y-%m-%dT%H:00:00+02:00")) - endDate = quote((utcnow + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00+02:00")) + tariffName = "vario_plus" + startDate = datetime.now().strftime("%Y-%m-%dT%H:00:00+01:00") + endDate = (datetime.now() + timedelta(days=2)).strftime("%Y-%m-%dT%H:00:00+01:00") session = req.get_http_session() - power_raw = session.get( + prices_raw = session.get( url=endpoint + - f"?start_timestamp={startDate}&end_timestamp={endDate}", + f"?start_timestamp={ quote(startDate) }&end_timestamp={ quote(endDate) }", ).json() - return list(map(transformPrices, power_raw)) + return [(timestamp(power), (power[tariffName]/100000)) + for power in prices_raw] +# Fetch prices and return as a dictionary def fetch_prices(config: GroupeETariffConfiguration) -> Dict[str, float]: - # Fetch electricity prices from EKZ API pricelist = readApi() prices: Dict[str, float] = dict(pricelist) return prices