Tibber-1/4-Stunden-Integration
Verfasst: Mi Okt 01, 2025 5:33 pm
Moin,
Wer auch immer die GUI-Integration von Tibber (und Co.) gemacht hat: Was für eine saubere Arbeit!
Ich habe immer noch nicht den Hintern zur Umstellung auf 2.1 hochgekriegt (wenngleich heute die SD-Karte bestellt).
Um von der heutigen Umstellung der dynamischen Stromtarife in Deutschland auf 15-Minuten-Takt zu profitieren, musste ich sehr wenig anpassen.
Unten der diff für diejenigen, die Shell-Access zu ihrer Box haben:
Nach dem Einpflegen einmal /var/www/html/openWB/ramdisk/etprovidergraphlist löschen, damit die Preise neu gezogen werden - that's it!
Wer auch immer die GUI-Integration von Tibber (und Co.) gemacht hat: Was für eine saubere Arbeit!
Ich habe immer noch nicht den Hintern zur Umstellung auf 2.1 hochgekriegt (wenngleich heute die SD-Karte bestellt).
Um von der heutigen Umstellung der dynamischen Stromtarife in Deutschland auf 15-Minuten-Takt zu profitieren, musste ich sehr wenig anpassen.
Unten der diff für diejenigen, die Shell-Access zu ihrer Box haben:
Code: Alles auswählen
diff --git a/modules/et_tibber/stromtarifinfo/infopage.php b/modules/et_tibber/stromtarifinfo/infopage.php
index ff344573..cba90029 100755
--- a/modules/et_tibber/stromtarifinfo/infopage.php
+++ b/modules/et_tibber/stromtarifinfo/infopage.php
@@ -230,13 +230,13 @@
initialDataRead = false;
var tibberQueryHead = '{ "query": "{viewer {name home(id:\\"' + tibberHomeID + '\\") {';
var tibberQueryGetAdress = 'address {address1 postalCode city} ';
- var tibberQueryGetPriceInfo = 'currentSubscription {priceInfo {current{total energy tax startsAt} today {total startsAt} tomorrow {total startsAt}}} ';
+ var tibberQueryGetPriceInfo = 'currentSubscription {priceInfo(resolution: QUARTER_HOURLY) {current{total energy tax startsAt} today {total startsAt} tomorrow {total startsAt}}} ';
} else {
var tibberQueryHead = '{ "query": "{viewer {home(id:\\"' + tibberHomeID + '\\") {';
var tibberQueryGetAdress = '';
var tibberQueryGetPriceInfo = '';
}
- var tibberQueryGetHourlyConsumption = 'cons_hourly: consumption(resolution: HOURLY, after:\\"' + timeStringBase64 + '\\", first: 25) {nodes {from to cost unitPrice unitPriceVAT consumption}}';
+ var tibberQueryGetHourlyConsumption = 'cons_hourly: consumption(resolution: HOURLY, after:\\"' + timeStringBase64 + '\\", first: 100) {nodes {from to cost unitPrice unitPriceVAT consumption}}';
var tibberQueryTail = '}}}" }';
var tibberQuery = tibberQueryHead + tibberQueryGetAdress + tibberQueryGetPriceInfo + tibberQueryGetHourlyConsumption + tibberQueryTail;
diff --git a/modules/et_tibber/tibbergetprices.py b/modules/et_tibber/tibbergetprices.py
index dd247139..3450d55f 100755
--- a/modules/et_tibber/tibbergetprices.py
+++ b/modules/et_tibber/tibbergetprices.py
@@ -108,7 +108,8 @@ def _exit_on_invalid_price_data(error, current_module_name):
file_current_price.write(str(_failure_price) + '\n')
file_pricelist.write('%s\n' % current_module_name) # erster Eintrag ist für Preisliste verantwortliches Modul
now = datetime.now(timezone.utc) # timezone-aware datetime-object in UTC
- timestamp = now.replace(minute=0, second=0, microsecond=0) # volle Stunde
+ quarter_hour = (now.minute // 15) * 15
+ timestamp = now.replace(minute=quarter_hour, second=0, microsecond=0) # volle Stunde
for i in range(9):
file_pricelist.write('%d, %f\n' % (timestamp.timestamp(), _failure_price))
timestamp = timestamp + timedelta(hours=1)
@@ -178,7 +179,7 @@ def _try_api_call(max_tries=3, delay=5, backoff=2, exceptions=(Exception,), hook
@_try_api_call()
def _readAPI(token, id):
headers = {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}
- data = '{ "query": "{viewer {home(id:\\"' + id + '\\") {currentSubscription {priceInfo {today {total startsAt} tomorrow {total startsAt}}}}}}" }'
+ data = '{ "query": "{viewer {home(id:\\"' + id + '\\") {currentSubscription {priceInfo(resolution: QUARTER_HOURLY) {today {total startsAt} tomorrow {total startsAt}}}}}}" }'
# Timeout für Verbindung = 2 sek und Antwort = 6 sek
response = requests.post('https://api.tibber.com/v1-beta/gql', headers=headers, data=data, timeout=(2, 6))
return response
@@ -191,34 +192,36 @@ def _get_utcfromtimestamp(timestamp):
return datetime_obj
def _cleanup_pricelist(pricelist):
- # bereinigt sortierte Preisliste, löscht Einträge die älter als aktuelle Stunde sind
+ # bereinigt sortierte Preisliste, löscht Einträge die älter als aktuelle Viertelstunde sind
# und über morgen hinausgehen
- # wenn der erste Preis nicht für die aktuelle Stunde ist, wird leere Liste zurückgegeben
+ # wenn der erste Preis nicht für die aktuelle Viertelstunde ist, wird leere Liste zurückgegeben
# prüft auf Abstand der Preise: ist dieser >1h, wird Liste ab diesem Punkt abgeschnitten
if len(pricelist) > 0:
now = datetime.now(timezone.utc) # timezone-aware datetime-object in UTC
- now_full_hour = now.replace(minute=0, second=0, microsecond=0) # volle Stunde
- now_full_hour_timestamp = datetime.timestamp(now_full_hour)
+ # Runde Minuten auf die nächste Viertelstunde (0, 15, 30, 45)
+ quarter_hour = (now.minute // 15) * 15
+ now_quarter_hour = now.replace(minute=quarter_hour, second=0, microsecond=0)
+ now_quarter_hour_timestamp = datetime.timestamp(now_quarter_hour)
# zuerst filtern auf "ab diese Stunde" bis "längstens morgen"
for index, price in enumerate(pricelist[:]): # über Kopie der Liste iterieren, um das Original zu manipulieren
try:
starttime_utc = _get_utcfromtimestamp(float(price[0])) # Start-Zeitstempel aus Preisliste umwandeln
except:
raise TypeError('Zeitstempel-Umwandlung fehlgeschlagen') from None
- # ältere als aktuelle Stunde und weiter als morgen löschen
- if (float(price[0]) < now_full_hour_timestamp) or (starttime_utc.date() > now.date() + timedelta(days=1)):
+ # ältere als aktuelle Viertelstunde und weiter als morgen löschen
+ if (float(price[0]) < now_quarter_hour_timestamp) or (starttime_utc.date() > now.date() + timedelta(days=1)):
pricelist.remove(price)
- # jetzt prüfen auf Start mit aktueller Stunde und Stundenabstände
+ # jetzt prüfen auf Start mit aktueller Viertelstunde und Viertelstundenabstände
if len(pricelist) > 0:
timestamp_prev = float(pricelist[0][0]) # erster Listeneintrag
starttime_utc = _get_utcfromtimestamp(float(pricelist[0][0]))
- if _get_utcfromtimestamp(timestamp_prev) == now_full_hour: # erster Preis ist der von aktueller Stunde
+ if _get_utcfromtimestamp(timestamp_prev) == now_quarter_hour: # erster Preis ist der von aktueller Stunde
for index, price in enumerate(pricelist[:]): # über Kopie der Liste iterieren, um das Original zu manipulieren
if index > 0:
timestamp = float(price[0])
secondsdiff = timestamp - timestamp_prev
timestamp_prev = float(price[0])
- if secondsdiff != 3600.0: # ist Abstand <> 1h dann ab hier Liste löschen
+ if secondsdiff != 900.0: # ist Abstand <> 1h dann ab hier Liste löschen
del pricelist[index:]
break
else:
@@ -262,7 +265,8 @@ def _get_updated_pricelist():
_write_log_entry("Tibber-Preisliste extrahiert", 1)
# alle Zeiten in UTC verarbeiten
now = datetime.now(timezone.utc) # timezone-aware datetime-object in UTC
- now_full_hour = now.replace(minute=0, second=0, microsecond=0) # volle Stunde
+ quarter_hour = (now.minute // 15) * 15
+ now_quarter_hour = now.replace(minute=quarter_hour, second=0, microsecond=0) # volle Stunde
_write_log_entry('Formatiere und analysiere Preisliste', 1)
pricelist = []
for price_data in sorted_marketprices: