From d54312de238df067cfc790ff3337cf35d26b51f7 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Tue, 21 Jan 2025 21:30:33 +0000 Subject: [PATCH 1/6] fixed hour tariffs --- .../fixedhours/__init__.py | 0 .../electricity_tariffs/fixedhours/config.py | 38 ++++++++++++ .../electricity_tariffs/fixedhours/tariff.py | 60 +++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 packages/modules/electricity_tariffs/fixedhours/__init__.py create mode 100644 packages/modules/electricity_tariffs/fixedhours/config.py create mode 100644 packages/modules/electricity_tariffs/fixedhours/tariff.py diff --git a/packages/modules/electricity_tariffs/fixedhours/__init__.py b/packages/modules/electricity_tariffs/fixedhours/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/electricity_tariffs/fixedhours/config.py b/packages/modules/electricity_tariffs/fixedhours/config.py new file mode 100644 index 0000000000..836d7e59e5 --- /dev/null +++ b/packages/modules/electricity_tariffs/fixedhours/config.py @@ -0,0 +1,38 @@ +from typing import Optional, List, Dict + + +class FixedHoursTariffConfiguration: + def __init__(self, default_price: Optional[float] = None, tariffs: Optional[List[Dict[str, any]]] = None): + self.default_price = default_price + self.tariffs = tariffs + ''' + Example configuration: + "tariffs": [ + { + "name": "high_tariff", + "price": 0.20, + "active_times": { + "quarters": [1, 2, 3, 4], # applicable quarters + "times": [("08:00", "12:00"), ("18:00", "22:00")] # active times during the day + } + }, + { + "name": "low_tariff", + "price": 0.05, + "active_times": { + "quarters": [1, 2, 3, 4], # applicable quarters + "times": [("00:00", "06:00"), ("22:00", "23:59")] # active times during the day + } + } + ] + ''' + + +class FixedHoursTariff: + def __init__(self, + name: str = "Feste Tarifstunden (z.b. §14a EnWG Modul3)", + type: str = "fixedhours", + configuration: FixedHoursTariffConfiguration = None) -> None: + self.name = name + self.type = type + self.configuration = configuration or FixedHoursTariffConfiguration() diff --git a/packages/modules/electricity_tariffs/fixedhours/tariff.py b/packages/modules/electricity_tariffs/fixedhours/tariff.py new file mode 100644 index 0000000000..d68de36af5 --- /dev/null +++ b/packages/modules/electricity_tariffs/fixedhours/tariff.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import logging +import datetime +import time + +from modules.electricity_tariffs.fixedhours.config import FixedHoursTariff, FixedHoursTariffConfiguration +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_state import TariffState +# from modules.common.configurable_tariff import ConfigurableTariff + +log = logging.getLogger(__name__) + + +def to_time(time_str): + return datetime.datetime.strptime(time_str, "%H:%M").time() + + +def validate_tariff_times(config): + time_slots = [] + for tariff in config.tariffs: + for start, end in tariff["active_times"]["times"]: + start_time = to_time(start) + end_time = to_time(end) + for existing_start, existing_end in time_slots: + if (start_time < existing_end and end_time > existing_start): + raise ValueError(f"Overlapping time window detected: {start} - {end} in tariff '{tariff['name']}'") + time_slots.append((start_time, end_time)) + + +def fetch(config: FixedHoursTariffConfiguration) -> None: + validate_tariff_times(config) + + current_time = datetime.datetime.now().replace(minute=0, second=0, microsecond=0) + prices = {} + + for i in range(24): # get prices for the next 24 hours + time_slot = current_time + datetime.timedelta(hours=i) + epoch_time = int(time.mktime(time_slot.timetuple())) + quarter = (current_time.month - 1) // 3 + 1 + price = config.default_price/1000 + + for tariff in config.tariffs: + active_times = [(to_time(start), to_time(end)) for start, end in tariff["active_times"]["times"]] + if (any(start <= time_slot.time() < end for start, end in active_times) and + quarter in tariff["active_times"]["quarters"]): + price = tariff["price"]/1000 + break # Break since we found a matching tariff + + prices[str(epoch_time)] = price + + return TariffState(prices=prices) + + +def create_electricity_tariff(config: FixedHoursTariff): + def updater(): + return fetch(config.configuration) + return updater + + +device_descriptor = DeviceDescriptor(configuration_factory=FixedHoursTariff) From a9b5bcb64e2499e47c201ebe29e1f657928dd704 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 23 Jan 2025 12:30:00 +0100 Subject: [PATCH 2/6] rename fixed hour tariff --- .../{fixedhours => fixed_hours}/__init__.py | 0 .../electricity_tariffs/{fixedhours => fixed_hours}/config.py | 4 ++-- .../electricity_tariffs/{fixedhours => fixed_hours}/tariff.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/modules/electricity_tariffs/{fixedhours => fixed_hours}/__init__.py (100%) rename packages/modules/electricity_tariffs/{fixedhours => fixed_hours}/config.py (93%) rename packages/modules/electricity_tariffs/{fixedhours => fixed_hours}/tariff.py (95%) diff --git a/packages/modules/electricity_tariffs/fixedhours/__init__.py b/packages/modules/electricity_tariffs/fixed_hours/__init__.py similarity index 100% rename from packages/modules/electricity_tariffs/fixedhours/__init__.py rename to packages/modules/electricity_tariffs/fixed_hours/__init__.py diff --git a/packages/modules/electricity_tariffs/fixedhours/config.py b/packages/modules/electricity_tariffs/fixed_hours/config.py similarity index 93% rename from packages/modules/electricity_tariffs/fixedhours/config.py rename to packages/modules/electricity_tariffs/fixed_hours/config.py index 836d7e59e5..37587bf12b 100644 --- a/packages/modules/electricity_tariffs/fixedhours/config.py +++ b/packages/modules/electricity_tariffs/fixed_hours/config.py @@ -2,7 +2,7 @@ class FixedHoursTariffConfiguration: - def __init__(self, default_price: Optional[float] = None, tariffs: Optional[List[Dict[str, any]]] = None): + def __init__(self, default_price: Optional[float] = None, tariffs: List[Dict[str, any]] = []) -> None: self.default_price = default_price self.tariffs = tariffs ''' @@ -31,7 +31,7 @@ def __init__(self, default_price: Optional[float] = None, tariffs: Optional[List class FixedHoursTariff: def __init__(self, name: str = "Feste Tarifstunden (z.b. §14a EnWG Modul3)", - type: str = "fixedhours", + type: str = "fixed_hours", configuration: FixedHoursTariffConfiguration = None) -> None: self.name = name self.type = type diff --git a/packages/modules/electricity_tariffs/fixedhours/tariff.py b/packages/modules/electricity_tariffs/fixed_hours/tariff.py similarity index 95% rename from packages/modules/electricity_tariffs/fixedhours/tariff.py rename to packages/modules/electricity_tariffs/fixed_hours/tariff.py index d68de36af5..f4edb9f3d7 100644 --- a/packages/modules/electricity_tariffs/fixedhours/tariff.py +++ b/packages/modules/electricity_tariffs/fixed_hours/tariff.py @@ -3,7 +3,7 @@ import datetime import time -from modules.electricity_tariffs.fixedhours.config import FixedHoursTariff, FixedHoursTariffConfiguration +from modules.electricity_tariffs.fixed_hours.config import FixedHoursTariff, FixedHoursTariffConfiguration from modules.common.abstract_device import DeviceDescriptor from modules.common.component_state import TariffState # from modules.common.configurable_tariff import ConfigurableTariff From 08a35ec5f6882fd0a17d88071e4759ba7003262f Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 23 Jan 2025 21:51:30 +0000 Subject: [PATCH 3/6] fix midnight --- packages/modules/electricity_tariffs/fixed_hours/tariff.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/modules/electricity_tariffs/fixed_hours/tariff.py b/packages/modules/electricity_tariffs/fixed_hours/tariff.py index f4edb9f3d7..08d16e698d 100644 --- a/packages/modules/electricity_tariffs/fixed_hours/tariff.py +++ b/packages/modules/electricity_tariffs/fixed_hours/tariff.py @@ -12,6 +12,8 @@ def to_time(time_str): + if time_str == "24:00": + return datetime.time(23, 59, 59) return datetime.datetime.strptime(time_str, "%H:%M").time() From 0983d5e09cce8096aefeb3b7c2c0b5de28f56a5c Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 23 Jan 2025 22:00:09 +0000 Subject: [PATCH 4/6] consider quater when checking overlapping tariff windows --- packages/modules/electricity_tariffs/fixed_hours/tariff.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/modules/electricity_tariffs/fixed_hours/tariff.py b/packages/modules/electricity_tariffs/fixed_hours/tariff.py index 08d16e698d..e27b1ab595 100644 --- a/packages/modules/electricity_tariffs/fixed_hours/tariff.py +++ b/packages/modules/electricity_tariffs/fixed_hours/tariff.py @@ -23,10 +23,11 @@ def validate_tariff_times(config): for start, end in tariff["active_times"]["times"]: start_time = to_time(start) end_time = to_time(end) - for existing_start, existing_end in time_slots: - if (start_time < existing_end and end_time > existing_start): + for existing_start, existing_end, existing_quarters in time_slots: + if (start_time < existing_end and end_time > existing_start and + any(quarter in tariff["active_times"]["quarters"] for quarter in existing_quarters)): raise ValueError(f"Overlapping time window detected: {start} - {end} in tariff '{tariff['name']}'") - time_slots.append((start_time, end_time)) + time_slots.append((start_time, end_time, tariff["active_times"]["quarters"])) def fetch(config: FixedHoursTariffConfiguration) -> None: From 89993c3db17ac6a898376dba356cdbdcdddbdc88 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Tue, 28 Jan 2025 20:42:52 +0000 Subject: [PATCH 5/6] replace fixed quarters with date range --- .../electricity_tariffs/fixed_hours/config.py | 4 +-- .../electricity_tariffs/fixed_hours/tariff.py | 34 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/modules/electricity_tariffs/fixed_hours/config.py b/packages/modules/electricity_tariffs/fixed_hours/config.py index 37587bf12b..e9c15419b6 100644 --- a/packages/modules/electricity_tariffs/fixed_hours/config.py +++ b/packages/modules/electricity_tariffs/fixed_hours/config.py @@ -12,7 +12,7 @@ def __init__(self, default_price: Optional[float] = None, tariffs: List[Dict[str "name": "high_tariff", "price": 0.20, "active_times": { - "quarters": [1, 2, 3, 4], # applicable quarters + "dates": [("01-01", "31-03"), ("01-07", "30-09")], # applicable date ranges (day-month) "times": [("08:00", "12:00"), ("18:00", "22:00")] # active times during the day } }, @@ -20,7 +20,7 @@ def __init__(self, default_price: Optional[float] = None, tariffs: List[Dict[str "name": "low_tariff", "price": 0.05, "active_times": { - "quarters": [1, 2, 3, 4], # applicable quarters + "dates": [("01-04", "30-06"), ("01-10", "31-12")], # applicable date ranges (day-month) "times": [("00:00", "06:00"), ("22:00", "23:59")] # active times during the day } } diff --git a/packages/modules/electricity_tariffs/fixed_hours/tariff.py b/packages/modules/electricity_tariffs/fixed_hours/tariff.py index e27b1ab595..7a5daa03dd 100644 --- a/packages/modules/electricity_tariffs/fixed_hours/tariff.py +++ b/packages/modules/electricity_tariffs/fixed_hours/tariff.py @@ -2,6 +2,7 @@ import logging import datetime import time +from typing import List, Tuple, Dict from modules.electricity_tariffs.fixed_hours.config import FixedHoursTariff, FixedHoursTariffConfiguration from modules.common.abstract_device import DeviceDescriptor @@ -11,42 +12,49 @@ log = logging.getLogger(__name__) -def to_time(time_str): +def to_time(time_str: str) -> datetime.time: if time_str == "24:00": return datetime.time(23, 59, 59) return datetime.datetime.strptime(time_str, "%H:%M").time() -def validate_tariff_times(config): - time_slots = [] +def to_date(date_str: str, time_slot: datetime.datetime) -> datetime.date: + date = datetime.datetime.strptime(date_str, "%d-%m").date().replace(year=datetime.datetime.now().year) + if date.year < time_slot.year: # Beim Jahreswechsel das korrekte Jahr setzen + date = date.replace(year=time_slot.year) + return date + + +def validate_tariff_times(config: FixedHoursTariffConfiguration) -> None: + time_slots: List[Tuple[datetime.time, datetime.time, List[Tuple[str, str]]]] = [] for tariff in config.tariffs: for start, end in tariff["active_times"]["times"]: start_time = to_time(start) end_time = to_time(end) - for existing_start, existing_end, existing_quarters in time_slots: + for existing_start, existing_end, existing_dates in time_slots: if (start_time < existing_end and end_time > existing_start and - any(quarter in tariff["active_times"]["quarters"] for quarter in existing_quarters)): + any(start <= existing_end and end >= existing_start for start, end in existing_dates)): raise ValueError(f"Overlapping time window detected: {start} - {end} in tariff '{tariff['name']}'") - time_slots.append((start_time, end_time, tariff["active_times"]["quarters"])) + time_slots.append((start_time, end_time, tariff["active_times"]["dates"])) -def fetch(config: FixedHoursTariffConfiguration) -> None: +def fetch(config: FixedHoursTariffConfiguration) -> TariffState: validate_tariff_times(config) current_time = datetime.datetime.now().replace(minute=0, second=0, microsecond=0) - prices = {} + prices: Dict[str, float] = {} for i in range(24): # get prices for the next 24 hours time_slot = current_time + datetime.timedelta(hours=i) epoch_time = int(time.mktime(time_slot.timetuple())) - quarter = (current_time.month - 1) // 3 + 1 - price = config.default_price/1000 + price = config.default_price / 1000 for tariff in config.tariffs: active_times = [(to_time(start), to_time(end)) for start, end in tariff["active_times"]["times"]] + active_dates = [(to_date(start, time_slot), to_date(end, time_slot)) for start, end in tariff["active_times"]["dates"]] if (any(start <= time_slot.time() < end for start, end in active_times) and - quarter in tariff["active_times"]["quarters"]): - price = tariff["price"]/1000 + any(start <= time_slot.date() <= end for start, end in active_dates)): + price = tariff["price"] / 1000 break # Break since we found a matching tariff prices[str(epoch_time)] = price @@ -55,7 +63,7 @@ def fetch(config: FixedHoursTariffConfiguration) -> None: def create_electricity_tariff(config: FixedHoursTariff): - def updater(): + def updater() -> TariffState: return fetch(config.configuration) return updater From 07f4e981ac827c75a8a23ee3a02180f29570cdcf Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Tue, 28 Jan 2025 20:43:41 +0000 Subject: [PATCH 6/6] fix formatting --- packages/modules/electricity_tariffs/fixed_hours/tariff.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/modules/electricity_tariffs/fixed_hours/tariff.py b/packages/modules/electricity_tariffs/fixed_hours/tariff.py index 7a5daa03dd..0a73ccb125 100644 --- a/packages/modules/electricity_tariffs/fixed_hours/tariff.py +++ b/packages/modules/electricity_tariffs/fixed_hours/tariff.py @@ -51,7 +51,10 @@ def fetch(config: FixedHoursTariffConfiguration) -> TariffState: for tariff in config.tariffs: active_times = [(to_time(start), to_time(end)) for start, end in tariff["active_times"]["times"]] - active_dates = [(to_date(start, time_slot), to_date(end, time_slot)) for start, end in tariff["active_times"]["dates"]] + active_dates = [ + (to_date(start, time_slot), to_date(end, time_slot)) + for start, end in tariff["active_times"]["dates"] + ] if (any(start <= time_slot.time() < end for start, end in active_times) and any(start <= time_slot.date() <= end for start, end in active_dates)): price = tariff["price"] / 1000