Wie bereits schon in anderen Berichten erwähnt, sind wir seit Mitte April Tibber-Kunde und nutzen die dynamischen Strompreise. Nun habe ich die ersten Versuche gestartet, die Informationen zu den günstigsten Strompreisen in meine Haussteuerung „FHEM“ einzubinden.
Wer also auch Tibber nutzt und seine großen Verbraucher zu den niedrigsten Strompreise laufen lassen möchte, der sollte sich meine Umsetzung in FHEM einmal genauer anschauen. Ich habe versucht, den Code so allgemein zu halten, dass er auch für diejenigen nutzbar ist, die keine PV-Anlage haben.
Bei mir werden die Großverbraucher wie Waschmaschine, Spülmaschine und das Ladegerät für unseren eUp! über das SMA Sunny Portal gesteuert. Im Sommer können diese Geräte dann idealerweise zu großen Teilen mit Sonnenenenergie betrieben werden. Im Winter wird das schon schwieriger. Insbesondere die Spülmaschine muß auch im Sommer durchaus zu Zeiten gestartet werden, in denen nicht viel Sonne scheint. Dann wäre es interessant, diese beispielsweise in der Nacht laufen zu lassen, wenn der Strompreis besonders günstig ist.
Meine persönlichen Rahmenbedingungen und Lösungsansätze zur Einbindung von Tibber in FHEM
WIe schon erwähnt, werden meine großen Verbraucher in erster Linie über die PV-Anlage angesteuert. Hierzu habe ich die jeweiligen Geräte über schaltbare Steckdosen in das SMA Portal eingebunden. Sobald die PV-Steuerung erkennt, das ein Gerät gestartet wurde, wird dieser zunächst über die Steckdose wieder abgeschaltet und in den Automatik-Modus versetzt. Das SMA Portal berechnet dann auf Basis der Wettervorhersage den Zeitpunkt mit optimalen Sonnenenergie und startet das Gerät entsprechend.
Da es hierbei ein paar Problemstellungen gibt, die das Portal bisher nicht optimal gelöste hat und da es immer mal vorkommt, dass diese Steuerung ausgehebelt werden muß, habe ich in der Haussteuerung einen Dummy-Schalter für die Geräte vorgesehen, die meist auch mit entsprechenden physischen Tastern verbunden sind. Wenn meine Frau also die Waschmaschine sofort starten möchte und nicht auf die Sonne warten will, dann betätigt sie den entsprechenden Taster und die Haussteuerung schaltet dann die zugehörige Steckdose wieder an. Diese Funktionalität habe ich auf in meinem Lösungsansatz für die Tibber-Steuerung genutzt.
Meine Idee beruht nun darauf, dass ich eine Funktion aufrufe, der ich Parameter zu Laufzeit und Start- sowie Endzeitpunkt mitgebe und die dann für den aktuellen Tag bzw. den Folgetag den Zeitraum ermittelt, der in Summe den niedrigsten Strompreis hat.
HIerzu muss man wissen, dass Tibber in der Regel um 13:00 Uhr herum die Preise für den nächsten Tag ermittelt. Vor 13:00 Uhr kennt man also nur die stündlichen Preise des aktuellen Tages.
Meine Lösung unterteilt sich somit in folgende Teilbereiche:
- Ermitteln der stündlichen Preise
- Funktion zur Ermittlung des günstigsten Zeitraums
- Schaltmechanismus zur Steuerung der Großverbraucher zur günstigsten Zeit
Die entsprechenden Teillösungen werde ich nun in den folgenden Kapiteln darstellen und erläutern. Da ich nicht mer der beste Programmierprofi bin, könnte man die ein oder andere Lösung wahrscheinlich auch noch effizienter umsetzen. Aber da die Lösung bei mir funktioniert – soweit ich es bisher testen konnte – reicht es für meine Zwecke aus ;-).
Stündliche Tibber-Strompreise in FHEM ermitteln
Tibber bietet eine API mit mehreren Abfrageoptionen. So gibt es eine Schnittstelle, mit der man die stündlichen Preise abfragen kann. Damit man die API nutzen kann, muß man sich auf der Developerseite mit seinem Account anmelden. Hier bekommt man dann einen Token, der beim Aufruf der API genutzt werden muss.
Den Abruf der API habe ich mittels HTTPMOD erstellt. Als Basis hatte ich im FHEM-Forum ein paar Definitionen gefunden, die ich genutzt aber für mich leicht modifiziert habe. Mein HTTPMOD sieht wie folgt aus:
defmod myTibber HTTPMOD https://api.tibber.com/v1-beta/gql 3600 attr myTibber alignTime 00:05 attr myTibber reading1JSON data_viewer_homes_01_currentSubscription_priceInfo_current_total attr myTibber reading1Name Strompreis attr myTibber reading1OExpr $val*100 attr myTibber reading2-1Name Datum attr myTibber reading2-2Name Uhrzeit attr myTibber reading2JSON data_viewer_homes_01_currentSubscription_priceInfo_current_startsAt attr myTibber reading2Regex startsAt":"([\d+-]+)T(\d\d:\d\d) attr myTibber replacement1Mode text attr myTibber replacement1Regex %%Token%% attr myTibber replacement1Value PERSÖNLICHER_TOKEN attr myTibber requestData { "query": "{viewer {homes {currentSubscription {priceInfo {current {total energy tax startsAt} today {total energy tax startsAt } tomorrow {total energy tax startsAt }}}}}}" } attr myTibber requestHeader1 Content-Type: application/json attr myTibber requestHeader2 Authorization: Bearer %%Token%% attr myTibber room PV_E_Auto attr myTibber showBody 1 attr myTibber showError 1 attr myTibber userReadings TodayAverage {use List::Util qw(sum);;;;\ my @Preise = split /\|/, ReadingsVal("myTibber","TodayTotal",0.25);;;; return (100 * sum(@Preise) / @Preise);;;; },\ TomorrowAverage {use List::Util qw(sum);;;;\ my @Preise = split /\|/, ReadingsVal("myTibber","TomorrowTotal",0.25);;;; return $Preise[0] eq "NV" ? "NV" : (100 * sum(@Preise) / @Preise);;;; },\ TodayMin {use List::Util qw(min);;;; my @Preise = split /\|/, ReadingsVal("myTibber","TodayTotal",0.25);;;; return (min @Preise);;;;}
Mit den Reading-Definitionen ermittle ich den aktuellen Strompreis sowie das Datum und die Uhrzeit. Wobei ich Datum und Uhrzeit tatsächlich nicht verwende. Ich habe den Code drin gelassen, damit man vielleicht ein wenig besser versteht, wie man diese Werte aus der Tibber-API ermitteln kann. Zusätzlich habe ich noch die Userreadins „TodayAverage“, „TomorrowAverage“ und „TodayMin“ mit entsprechenden Perl-Funktionen ermittelt.
Man könnte grundsätzlich für jede Stunde automatisch ein Reading von HTTPMOD erstellen lassen. Das war mir persönlich aber zu aufwendig für die weitere Verarbeitung der Daten. Stattdessen habe ich quasi den Source-Code der Daten genutzt, die im Body des html-Aufrufs zu finden sind. Dafür habe ich das Attribut „showBody 1“ eingestellt.
Mit einer selbst geschriebenen Form baue ich die einzelnen Stundenwerte in eine verkettete Zeichenkette um (Preis0|Preis1|Preis3| usw.) und speichere diese Informationen als zusätzliche Readings in dem HTTPMOD Device.
Das Ergebnis sieht dann wie folgt aus:

Mit folgender Funktion erstelle ich die Umwandlung aus dem htmlbody in die Readings:
#Tibber per HTTP auswerten sub TibberDaten() { my $json = decode_json(InternalVal("myTibber", "httpbody", "")); my $today_total = ""; my $today_energy = ""; my $today_tax = ""; my $tomorrow_total = ""; my $tomorrow_energy = ""; my $tomorrow_tax = ""; # Alte Einträge erst einmal löschen #my $timedelete = TimeNow(); my $timedelete = strftime("%F",localtime(time))." 00:00"; my $time_tomorrow = strftime("%F",localtime(time+86400)); my ($year_today,$mon_today,$day_today) = $timedelete =~ m/(\d\d\d\d)-(\d\d)-(\d\d)/; my ($year_tomorrow,$mon_tomorrow,$day_tomorrow) = $time_tomorrow =~ m/(\d\d\d\d)-(\d\d)-(\d\d)/; Log3 undef, 3, "Aufruf von TibberDaten um ".localtime()." mit Löschzeit ".$timedelete." und time_tomorrow ".$time_tomorrow; fhem "set DBRep_PV sqlCmd delete from history where DEVICE = 'myTibber' and READING like 'to%' AND TIMESTAMP>='".$timedelete."'"; for(my $j=0; $j<24;$j++) { # Werte ermitteln my $value_today_total = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{today}[$j]->{total}; my $value_today_energy = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{today}[$j]->{energy}; my $value_today_tax = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{today}[$j]->{tax}; my $value_tomorrow_total = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{tomorrow}[$j]->{total}; my $value_tomorrow_energy = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{tomorrow}[$j]->{energy}; my $value_tomorrow_tax = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{tomorrow}[$j]->{tax}; $today_total = $today_total.$value_today_total."|"; $today_energy = $today_energy.$value_today_energy."|"; $today_tax = $today_tax.$value_today_tax."|"; if(defined $value_tomorrow_total) { $tomorrow_total = $tomorrow_total.$value_tomorrow_total."|"; $tomorrow_energy = $tomorrow_energy.$value_tomorrow_energy."|"; $tomorrow_tax = $tomorrow_tax.$value_tomorrow_tax."|"; } else { $tomorrow_total = $tomorrow_energy = $tomorrow_tax = "NV"; } #Timestamp für den Stundenwert my $timestamp_today = ($j < 10) ? "$year_today-$mon_today-$day_today 0".$j.":00:00" : "$year_today-$mon_today-$day_today $j:00:00"; my $timestamp_tomorrow = ($j < 10) ? "$year_tomorrow-$mon_tomorrow-$day_tomorrow 0".$j.":00:00" : "$year_tomorrow-$mon_tomorrow-$day_tomorrow $j:00:00"; # Werte in der Datenbank loggen fhem "set DBLog_PV addCacheLine ".$timestamp_today."|myTibber|addlog|today_total:".$j."|today_total|".$value_today_total."|"; fhem "set DBLog_PV addCacheLine ".$timestamp_today."|myTibber|addlog|today_energy:".$j."|today_energy|".$value_today_energy."|"; fhem "set DBLog_PV addCacheLine ".$timestamp_today."|myTibber|addlog|today_tax:".$j."|today_tax|".$value_today_tax."|"; if(defined $value_tomorrow_total) { fhem "set DBLog_PV addCacheLine ".$timestamp_tomorrow."|myTibber|addlog|tomorrow_total:".$j."|tomorrow_total|".$value_tomorrow_total."|"; fhem "set DBLog_PV addCacheLine ".$timestamp_tomorrow."|myTibber|addlog|tomorrow_energy:".$j."|tomorrow_energy|".$value_tomorrow_energy."|"; fhem "set DBLog_PV addCacheLine ".$timestamp_tomorrow."|myTibber|addlog|tomorrow_tax:".$j."|tomorrow_tax|".$value_tomorrow_tax."|"; } } fhem("setreading myTibber TodayTotal $today_total"); fhem("setreading myTibber TodayEnergy $today_energy"); fhem("setreading myTibber TodayTax $today_tax"); fhem("setreading myTibber TomorrowTotal $tomorrow_total"); fhem("setreading myTibber TomorrowEnergy $tomorrow_energy"); fhem("setreading myTibber TomorrowTax $tomorrow_tax"); Log3 undef, 3, "TibberDaten:".$today_total; }
In dem Code findet ihr auch einige Datenbank-Funktionen, die dazu dienen soll, die Stundenpreise zu speichern und dann in einem Diagramm darstellen zu können. Dazu bin ich aber noch nicht gekommen. Für die eigentliche Umwandlung kann man natürlich den ganzen Datenbank-Code weglassen.
Nun haben wir also die Funktionen zur Aufbereitung der stündlichen Strompreise. Jetzt muß die Funktion nur noch regelmäßig aufgefrufen werden. Hierzu verwende ich auch ein DOIF.
<UPDATE 19.07.> Da die neuen Preise für den Folgetag manchmal auch erst nach 14:00 verfügbar sind, habe ich noch ein weiteres Zeitfenster in dem folgenden DOIF eingebaut. <ENDE UPDATE>
defmod diTibberDaten DOIF ([00:10]) ({TibberDaten()})\ DOELSEIF (([13:05-14:00, +0:05] or [14:15-23:45, +0:15]) and [myTibber:TomorrowTotal] eq "NV") ({TibberDaten()})\ attr diTibberDaten do always
Mit diesem DOIF wird die Umwandlungsfunktion einmal in der Nacht um 00:01 Uhr aufgerufen, damit die Tagesdaten aktualisiert werden. Da Tibber immer ab ca. 13:00 Uhr die Daten für den Folgetag bereit stellt, rufe ich die Funktion ab 13:05 Uhr dann alle 5 Minuten auf, bis die Daten für den Folgetag gefüllt sind.
Günstigsten Zeitraum mit den geringsten Stromkosten ermitteln
Nachdem wir nun immer die aktuellen Strompreise verfügbar haben, können wir den Zeitraum mit den geringsten Stromkosten ermitteln. Ich habe mir für meine Zwecke eine Funktion definiert, die 3 Parameter bekommt:
- Frühester Startzeitpunkt
Da tagsüber ja in der Regel der PV-Strom zur Verfügung steht, würde ich beispielsweise Verbraucher erst dann schalten, wenn nicht mehr genug PV-Strom zu erwarten ist. Ich nehme dazu die Zeitfenster, die für die Großverbraucher im SMA-Portal definiert von mir definiert wurden. Startzeitpunkt der Waschmaschine ist beispielsweise um 8:00 Uhr und die Maschine sollte spätestens um 16:00 fertig sein, weil dann meist noch der Trockner läuft. Wird also die Waschmaschine nach etwa 14:00 Uhr gestartet, würde diese an diesem Tag nicht mehr mit der Automatik vom SMA-Portal gestartet, da sie bis 16:00 nicht fertig wird.
Also würde ich die Startzeit für die Tibber-Funktion auf 14:00 Uhr einstellen. - Laufzeit
Dieser Parameter gibt die maximale Laufzeit des Verbrauchers in Stunden an. Die Laufzeit unserer Waschmaschine kann bis zu ca. 140 Minuten dauern. Also würde man den Parameter auf 3 setzen. - Endzeitpunkt bzw. Anzahl Stunden nach dem frühesten Startzeitpunkt bis zu dem der Verbraucher beendet sein soll. Wenn die Waschmaschine also ab 14:00 Uhr starten, bis 8:00 Uhr morgens fertig sein soll und die Laufzeit mit 3 Stunden angegeben wurde, dann wird der Parameter auf 18 gesetzt. 14:00 Uhr plus 18 Stunden sind 8:00 Uhr.
In der Funktion habe ich noch eine Berechnung vorgenommen, falls die Parameter zeitlich nicht passen. Sind es beispielsweise 15:00 Uhr und man ruft die Funktion mit einer Startzeit von 14:00 Uhr auf, dann wird die früheste Startzeit auf 15:02 Uhr gesetzt. 2 Minuten nach der aktuellen Zeit, damit ein möglicher DOIF Event auch wirklich getriggert wird. Ähnliches gilt für das Ende. Die Preise stehen ja maximal für den nächsten Tag bis 23:00 Uhr zur Verfügung.
Nachfolgend findet ihr die Funktionen, die ich für die Ermittlung des besten Startzeitpunkts nutze. Hierbei habe ich einen besonderen Trick angewendet, der von DOIF-Funktionen richtig berechnet wird. Es können Startzeiten nach 24:00 Uhr vorkommen, die dann quasi die Uhrzeit am Folgetag darstellen. Eine Rückgabewert von 27:00 Uhr ist also 3:00 Uhr morgens am nächsten Tag.
<UPDATE 19.07.> In der Funktion MinStromTime habe ich noch einen Bug entfernt, der die Zeiten für den Folgetag nicht richtig berechnet hat.<UPDATE ENDE>
# Aus dem Array mit den Strompreisen des definierten Zeitfenster wird der Zeitraum mit dem niedrigsten Preis ermittelt # Parameter1: Preisarray # Parameter2: Laufzeit in Stunden # Return: Der StartIndex innerhalb der übergebenen Strompreise # sub MinTibberZeitfenster { my ($Strompreise, $Laufzeit) = @_; my $anz = @{$Strompreise}; my @PreisIntervall; Log3 undef, 3, "MinTibberZeitfenster: Strompreise=@{$Strompreise} Laufzeit=$Laufzeit"; for (my $i = 0; $i < ($anz - $Laufzeit +1); $i++) { @PreisIntervall[$i] = sum @{$Strompreise}[$i..$i+$Laufzeit-1]; #Log3 undef, 3, "Preisintervall Summe: @PreisIntervall[$i]"; } my $MinPreis = min @PreisIntervall; my $MinIndex = first_index { $_ eq $MinPreis } @PreisIntervall; Log3 undef, 3, "MinTibberZeitfenster: $MinPreis MinIndex=$MinIndex"; return $MinIndex; } # Günstigsten Strompreis für eine Dauer von X Minuten finden # Parameter: # MinHour: FrühesterStart (Beispiel: 15 für 15:00 Uhr), # Laufzeit: Laufzeit in Stunden, (Da es nur Stundenpreise gibt, kann die Laufzeit immer auf Stunden aufgerundet werden) # Laufzeit_Ende: Anzahl Stunden nach frühestem Start (Beispiel: 12 für 12 Stunden nach frühester Start). # Der Wert gibt dann quasi das Ende der Laufzeit an # Es wird immer die Startzeit für den aktuellen Tag angenommen und wenn die aktuelle Zeit nach dem frühesten Start liegt, # wird die früheste Startzeit auf die aktuelle Zeit zzgl. 2 Minuten gesetzt # Die Funktion ermittelt dann die Uhrzeit, in der der günstigste Strom für die Dauer von Laufzeit zu erwarten ist # # Beispiel: MinStromTime(15, 3, 24) -> Ermittelt den günstigsten Strom für 3 Stunden Laufzeit, # der am gleichen Tag nach 15:00 Uhr liegt und im Zeitfenster bis 15:00 + 24 Stunden - also am nächsten Tag um 15:00 Uhr liegt # sub MinStromTime($$$) { my ($MinHour,$Laufzeit,$LaufzeitEnde) = @_; my @PreiseHeute = split /\|/, ReadingsVal("myTibber","TodayTotal",0.25); my @PreiseMorgen = split /\|/, ReadingsVal("myTibber","TomorrowTotal",0.25); my @AllePreise = (@PreiseHeute, @PreiseMorgen); my ($tmp, $m, $h, $tmp, $tmp, $tmp, $tmp, $tmp, $tmp) = localtime(time); my $MinZeit = ""; # Falls die Mindestzeit vor der akt. Zeit liegt Mindestzeit auf akt. Zeit setzen if($h >= $MinHour) { $LaufzeitEnde = $LaufzeitEnde - ($h - $MinHour); $MinHour = $h; } my $LaufzeitIndex = ($MinHour + $LaufzeitEnde) <= 48 ? $MinHour + $LaufzeitEnde - 1 : 47; @AllePreise = @AllePreise[$h..$LaufzeitIndex]; my $MinZeitIndex = MinTibberZeitfenster(\@AllePreise, $Laufzeit); if($MinZeitIndex + $h > 24) # Uhrzeit ist am nächsten Tag { $MinZeit = sprintf("%02d", $MinZeitIndex + $h - 24).":00"; } else { $m = $MinZeitIndex == 0 && $MinHour == $h ? $m + 2 : 0; # Uhrzeit entspricht Aufrufzeit, daher 2 Minuten aufschlagen $MinZeit = sprintf("%02d", $MinZeitIndex + $h).":".sprintf("%02d", $m); } Log3 undef, 3, "MindestStrompreis: Aktuelle Zeit:$h:$m Startzeit:$MinZeit"; return $MinZeit; }
Verbraucher zum günstigsten Zeitpunkt anschalten
Grundsätzlich hätte man nun eine Funktionionalität mit der man sich den günstigsten Startzeitpunkt für seine Großverbraucher berechnen lassen kann. Meist dürfte diese Zeit aber nicht sehr günstig liegen, beispielsweise in der Nacht. Also muß man sich einen Mechanismus überlegen, mit dem man den Verbraucher starten kann und der dann in einen Automatikmodus wechselt.
Bei mir macht das im Prinzip die PV-Steuerung des Sunny Portal. Hier gibt es eine Anlauferkennung und sobald ein Gerät gestartet wurde, wird die damit verbundene Steckdose ausgeschaltet. Wenn dann genug Sonne vorhanden ist, wird die Steckdose wieder eingeschaltet.
Man kann das natürlich auch manuell machen. Erst schaltet man die Maschine ein und danach schaltet man die entsprechende Steckdose direkt wieder aus. Mit einem DOIF kann man dann die Steckdose wieder zum ermittelten Startzeitpunkt einschalten. Hat man eine schaltbare Steckdose, die auch den Stromverbrauch messen kann, dann hat man auch die Möglichkeit, sich eine Anlauferkennung zu programmieren.
Mit einem DOIF könnte man dann ermitteln, ob die Maschine beispielsweise einen bestimmten Verbrauch hat. Hier würde man einen möglichst niedrigen Werte nehmen ( z.B. 10Wh). Erkennt das DOIF dann den Start der Maschine kann darüber die entsprechende Steckdose ausgeschaltet werden.
Zum Starten der Maschinen habe ich den oben definierten DOIF „diTibberDaten“ genutzt und entsprechend erweitert. Die Geräte „keWaschmaschine“ und „ku_Spuelmaschine“ sind die jeweiligen Fritz-Steckdosen.
defmod diTibberDaten DOIF ([00:10]) ({TibberDaten()})\ DOELSEIF (([13:05-14:00, +0:05] or [14:15-23:45, +0:15]) and [myTibber:TomorrowTotal] eq "NV") ({TibberDaten()})\ DOELSEIF ([keWaschmaschine] eq "aus" and [{MinStromTime(14,3,18)}] )\ (set keWaschmaschine an)\ DOELSEIF ([ku_Spuelmaschine] eq "aus" and [{MinStromTime(15,3,24)}] )\ (set ku_Spuelmaschine an) attr diTibberDaten do always
Das Ergebnis des DOIF mit den Readings, in denen die berechneten Startzeiten entahlten sind, sieht dann wie folgt aus. Nicht irritieren lassen, da ich in meinem DOIF mit den oben beschriebenen Dummy-Schaltern arbeite. Wäre eigentlich nicht nötig aber tue ich quasi so, als ob jemand den entsprechenden Taster betätigt, der die Automatik überschreibt und das Gerät sofort einschaltet.

Die beiden letzten Timer sind einmal für die Waschmaschine und einmal für die Spülmaschine. Die Spülmaschine würde frühestens um 15:00 Uhr starten. Da ich das Modul um 18:22 Uhr nochmal aktualisiert habe, wäre die früheste Startzeit also 18:24 Uhr. Im Gegensatz zu der Waschmaschine habe ich das Ende der Spülmaschine mit 24 Stunden nach dem Startzeitpunkt deutlich länger definiert, als die Zeit für die Waschmaschine. Das sorgt dafür, dass die beste Zeit für die Waschmaschine 04:00 Uhr morgens ist und die Spülmaschine mittags um 12:00 gestartet würde.
Mit der nachfolgenden Darstellung der Readings aus dem HTTPMOD-Device kann man die Zeiten ermittelt wurden, nochmals nachvollziehen. Die 3 Stunden mit dem geringsten Strompreis für die Spülmaschine sind tatsächlich um 12:00 Uhr zu finden. Damit sollte meine Funktion hoffentlich richtig funktionieren.

Ich hoffe, dass ich euch ein paar erste Ideen zur Nutzung von Tibber in Verbindung mit FHEM geben konnte. Bei Fragen oder falls es Probleme mit meinen Funktionen geben sollte, nutzt gerne die Kommentarfunktion.
Solltet ihr euch nun auch entschließen, demnächst zu Tibber zu wechseln, würde ich mich freuen, wenn ihr meinen Empfehlungslink nutzt. Damit bekommt wir beide dann 50€ für den Tibber-Shop.