-
Notifications
You must be signed in to change notification settings - Fork 116
Electricity tariffs (EKZ, Groupe E) #2757
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
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2b09a51
add electricity tariffs for EKZ and Group E
cshagen ed371b1
fix lint errors
cshagen 36dc915
fix github lint issues
cshagen 696fbae
fix github flake issues
cshagen ce52090
fix github flake issues
cshagen 3ae6bf6
fix github flake issues
cshagen 7b10a3c
Update packages/modules/electricity_tariffs/group_e/tariff.py
cshagen 3b0d0bc
switch to 15min interval
cshagen 18d362e
15 minute intervals for tariffs
cshagen 5e48c56
15 minute intervals for tariffs
cshagen fa69818
fix ekz price calculation
cshagen a6886b5
update ekz and group-e prices modeules
cshagen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| #!/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) | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| #!/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 | ||
|
cshagen marked this conversation as resolved.
|
||
|
|
||
|
|
||
| # 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) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unterstützung von 15-Minuten Preis-Intervallen ist schon in Vorbereitung, daher würde ich auf diese Optimierung verzichten.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, ich kann das gerne rausnehmen, solange es keine Probleme mit der aktuellen Implementierung gibt.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Die Aktuelle Implementierung hat sicher Probleme, weil sie bei der Niedrigpreis-Suche alle Zeitslots benutzt, bei der Entscheidung ob geladen werden soll aber immer den ersten der aktuellen Stunde, so dass im Zweifel nicht geladen wird.
Wenn Du das drin lässt musst Du es halt dann noch mal anfassen, wenn #2801 gemerged ist...
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Es wäre gut zu wissen, ob die beiden Changes gemeinsam gemerged werden. Im Moment sieht es so aus, als wäre dieser Change für 2.1.9alpha2 eingeplant. #2801 aber noch nicht. D.h. ich mache dann nochmal ein Update sobald #2801 drin ist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ich habe übersehen, dass EKZ 15-Minuten Intervalle benutzt. Ich habe es in Alpha 3 verschoben, da soll das Merging nächste Woche beginnen. Dann merge ich das zusammen.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LKuemmel: Ist zwar nur 'ne Kleinigkeit, aber dieseer PR gehört prinzipiell auch mit dazu: #807
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Soll ich dann die Aggregation auf 1h-Intervalle noch rausnehmen?