Tibber in FHEM einbinden – Beste Zeiten für niedrige Strompreise ermitteln

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()
{
   if(InternalVal("myTibber", "httpbody", "") eq "")
      {return;}
   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 nächste volle Stunde 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)
	{
	   my $NeuLaufzeitEnde = $LaufzeitEnde - ($h - $MinHour);
	   if($NeuLaufzeitEnde <= 0)
	   {
	      $MinHour = 24;
	   }
	   else
	   {
	     $MinHour = $h+1;
		 $LaufzeitEnde = $NeuLaufzeitEnde; 
	   }
	}

        my $LaufzeitIndex = ($MinHour + $LaufzeitEnde) <= 24 ? $MinHour + $LaufzeitEnde - 1 : ($MinHour + $LaufzeitEnde) <= 48 ? ($PreiseMorgen[0] eq "NV" ? 23 : $MinHour + $LaufzeitEnde - 1) : 47; 
		
	@AllePreise = @AllePreise[$MinHour..$LaufzeitIndex];
	
	my $MinZeitIndex = MinTibberZeitfenster(\@AllePreise, $Laufzeit);
	
	if($MinZeitIndex + $MinHour > 24) # Uhrzeit ist am nächsten Tag
	{
		$MinZeit = sprintf("%02d", $MinZeitIndex + $MinHour - 24).":00";
	}
	else
	{
                $MinZeit = sprintf("%02d", $MinZeitIndex + $MinHour).":00";
	}
	
	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 Werbelink nutzt.

52 comments

  1. Danke fürs entwickeln und teilen.

    Ich bekomme in meiner Umgebung eine Fehlermeldung (für jeden definierten Eintrag innerhalb der DOIF).

    „ERROR evaluating {MinStromTime(14,3,17)}: Can’t call method „first_index“ without a package or object reference at ./FHEM/99_myUtils.pm line 124″

    Das Paket „liblist-moreutils-perl“ welches die first_index Methode enthält ist auch vorhanden.

    Eine Idee?

    Lieben Dank vorab.

  2. In meiner myUtils Datei habe ich folgende Anweisung ganz oben eingefügt:

    use List::MoreUtils qw{
    true first_index
    };

  3. Danke, hat funktioniert 😉

  4. Danke für die informative Seite leider bekomme ich in der 99_myUtils.pm bei der Funktion sub MinTibberZeitfenster

    syntax error at ./FHEM/99_myUtils.pm line 156, near „sum @“ Global symbol „$i“ requires explicit package name (did you forget to declare „my $i“?) at ./FHEM/99_myUtils.pm line 156. Global symbol „$i“ requires explicit package name (did you forget to declare „my $i“?) at ./FHEM/99_myUtils.pm line 156. Global symbol „@PreisIntervall“ requires explicit package name (did you forget to declare „my @PreisIntervall“?) at ./FHEM/99_myUtils.pm line 160. Global symbol „@PreisIntervall“ requires explicit package name (did you forget to declare „my @PreisIntervall“?) at ./FHEM/99_myUtils.pm line 161. Type of arg 1 to List::MoreUtils::firstidx must be block or sub {} (not reference constructor) at ./FHEM/99_myUtils.pm line 161, near „@PreisIntervall;“ syntax error at ./FHEM/99_myUtils.pm line 166, near „}“ Can’t use global @_ in „my“ at ./FHEM/99_myUtils.pm line 184, near „= @_“ syntax error at ./FHEM/99_myUtils.pm line 217, near „}“

    Könntest Du mir bitte helfen?

  5. Danke für die Erklärungen und das Teilen.
    Ich habe versucht, die Auswertung nachzubauen.
    Das Device myTibber wurde erstellt und bekommt über das DOIF die entsprechenden Daten.
    Bei der Erstellung von den Readings bekomme ich bei der Bearbeitung der 99_myUtils mehrere Fehler. Auch bei der Berechnung der günstigen Zeitpunkte bekomme ich eine Anzahl Fehler. Da ich absoluter Laie in Programmierung bin, kannst Du mir bei den Fehlern helfen?
    Fehlerliste: Can’t redeclare „my“ in „my“ at ./FHEM/99_myUtils.pm line 281, near „my“ syntax error at ./FHEM/99_myUtils.pm line 350, near „}“ Can’t use global @_ in „my“ at ./FHEM/99_myUtils.pm line 358, near „= @_“ Global symbol „$Strompreise“ requires explicit package name (did you forget to declare „my $Strompreise“?) at ./FHEM/99_myUtils.pm line 359. Type of arg 1 to List::MoreUtils::XS::firstidx must be block or sub {} (not reference constructor) at ./FHEM/99_myUtils.pm line 371, near „@PreisIntervall;“ syntax error at ./FHEM/99_myUtils.pm line 376, near „}“ Can’t use global @_ in „my“ at ./FHEM/99_myUtils.pm line 392, near „= @_“ syntax error at ./FHEM/99_myUtils.pm line 425, near „}“ ./FHEM/99_myUtils.pm has too many errors.
    Vielen Dank!

  6. Das sieht nach irgendwelchen Klammerfehlern aus.Ich habe den Code testweise bei mir auch nochmal eingefügt und dabei keinen Fehler erhalten. Du musst auch darauf achten, dass am Ende der Datei auf jeden Fall der Eintrag „1;“ in der letzten Zeile steht. Nicht das diese Zeile ggf. überschrieben wurde.

  7. Den Code habe ich nochmal gestestet und sollte eigentlich funktionieren. Schau mal, ob du am Anfang der 99_myUtils.pm folgende Anweisung eingebaut hast:

    use List::Util qw( sum );

  8. Moin ,
    danke für das tolle Script … Frage: ich möchte ein Gerät morgens ab 04:00 Uhr einschalten lassen es läuft 2 Stunden und soll bis 9 Fertig sein.
    Der Aufruf dürfte dann doch nur Zeiten zwischen 4 und 7 Uhr ausgeben oder
    {MinStromTime(4,2,3)}

    ich bekomme aber aktuell 21:00 als Wert zurück.
    Was mach ich da falsch?

  9. Vielen Dank, das wars. Jetzt funktioniert es.

  10. Hallo Micha,
    das Script ist aktuell so ausgelegt, dass es selbst die beste Startzeit ermittelt. Die Angabe des ersten Parameters für die früheste Startzeit habe ich eingebaut,damit die Geräte bei mir nicht starten, während der Homemanager noch versucht, das Gerät mit PV-Strom laufen zu lassen. Dieser Parameter gibt die früheste Startzeit am gleichen Tag an. Wenn man also „4“ angibt, dann würde die Berechnung so erfolgen, dass eine Zeit nach 4:00 Uhr gesucht wird. Wenn man die Funktion um 6:00 Uhr aufruft, dann wird die früheste Zeit durch das Script von 4:00 Uhr auf 6:00 gesetzt.

    Wenn ich dich richtig verstanden habe, möchtest du wahrscheinlich, dass beispielsweise eine Waschmaschine spätestens morgens um 9:00 Uhr fertig ist und bis dahin in einem Zeitfenster mit den günstigsten Strompreisen läuft. Angenommen die Maschine läuft insgesamt 2 Stunden, dann sollten die Parameter nach folgenden Kriterien ermittelt werden:
    – Parameter1: gibt es eine Zeit am aktuellen Tag an der die Maschine frühestens starten soll? Dann sollte diese Zeit genommen werden
    – Parameter2: Laufzeit der Maschine, in diesem Fall 2
    – Parameter3: Ende der LAufzeit im Verhältnis zum Parameter1 !

    Wenn du also die Funktion um 11:00 Uhr aufrufst und möchtest, dass die Maschine erst nachts starten soll, dann müsste folgender Aufruf passen: MinStromTime(24,2,9). Der Start würde nicht vor 24:00 erfolgen und mit den 2 Stunden Laufzeit wäre die Maschine spätestens um 9:00 fertig. In dem Zeitfenster von 24:00 bsi 9:00 wird dann die beste Zeit heraus gesucht, wann die Maschine laufen soll. Wenn ich dies bei mir jetzt aufrufe, würde 5:00 Uhr als Startzeit ermittelt.

    Da erst nach 13:00 Uhr die Preise für den nächsten Tag bekannt sind, nehmen wir mal an, dass die Funktion um 14:00 Uhr aufgerufen wird und es egal ist, wann die Maschine startet, dieser aber spätestens am nächsten Tag um 09:00 Uhr fertig sein soll, dann würde der Aufruf wie folgt aussehen: MinStromTime(14,2,19). Der dritte Paramater bedeutet, dass bei einem Start um 14:00 Uhr die Maschine um 14Uhr plus 19Stunden fertig sein soll. Auch mit diesem Aufruf kommt bei mir aktuell 5:00 Uhr heraus, da anscheinend zwischen 5:00 Uhr und 7:00 Uhr der günstigste Strompreis im gessamten Zeitfenster ermittelt wurde.

  11. Hallo Danke für die Ausführungen ich hab mir alles sorgfältig schon oben in der Anleitung durchgelesen.

    Der Aufruf {MinStromTime(4,2,5)} sollte doch aber auch ab 4 Uhr morgens bis 9 Uhr morgens die Startzeit der günstigsten 2 Stunden raus werfen oder warum macht er das nicht ….?

    Die Waschmaschine darf nicht zu lange „fertig“ sein 😉 sagt die Frau daher eben erst ab 04:00 Starten ..

    Mit MinStromTime(24,2,9) komme ich aktuell auch auf 05:00 Uhr

  12. Alles klar. ICh müsste nochmal genauer meinen Code durchgehen aber versuche mal als ersten Parameter 28 anzugeben. Also quasi 24:00 + 4:00. Dann wird er aktuell wahrscheinlich auch 5:00 Uhr ausgeben. Morgen dann nochmal testen. Problem ist nur, wenn man vormittags die Funktion mit MinStromTime(28,2,5) aufruft, dann gibt es noch keine Preise für den Folgetag und wenn ich mir meinen Code gerade nochmal abgeschaut habe, wird der Aufruf vormittags wahrscheinlich auf einen Fehler laufen. Da müsste ich mir nochmal etwas zu einfallen lassen.

  13. Hallo Jürgen,
    dein Tibber-Freunde-Code funktioniert nicht auf Check24. Weißt du, woran das liegt? Alt vielleicht?

  14. Warum der Code dort nicht funktioniert kann ich nicht sagen. Ich habe gerade in der App nochmal einen neuen Code erzeugt.

    Tibber Freunde Einladung Der Code müsste demnach g0iornq9 lauten. Vielleicht funktionieren diese Codes aber auch nur direkt bei Tibber. So hatte ich mich damals auch bei Tibber angemeldet.

  15. Hi Jürgen,
    ich beobachte deine Tibber Lösung auf meinem FHEM jetzt seit ein paar tagen.
    Ich hab das Problem, das sich in meinem DOIF die Zeiten nicht aktualisieren nur nach FHEM Neustart passt es im DOIF mit der Startzeit. Nach einem Tag steht aber dann wieder 00:00 als Uhrzeit drin.
    Wenn ich die Funktion z.B. MinStromTime(28,2,5) manuell aufrufe dann passt das Ergebnis.
    Das DOIF wird aber nicht aktualisiert bei mir.
    Kannst du mir einen TIP geben?

    Hier mein 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 ([TibberBrauchwasser] eq „AUS“ and [{MinStromTime(24,3,20)}] )
    (set TibberBrauchwasser EIN)
    DOELSEIF ([TibberWaschmaschine] eq „AUS“ and [{MinStromTime(28,2,5)}] )
    (set TibberWaschmaschine EIN)
    DOELSEIF ([TibberGeschirspueler] eq „AUS“ and [{MinStromTime(24,2,20)}] )
    (set TibberGeschirspueler EIN)

  16. Eigentlich sieht der Code richtig aus. Es könnte jedoch sein, dass sich unser „Trick“ mit den 24 bzw. 28 fehlerhaft auf die neue Zeitberechnung auswirkt. Wenn um 4:00 Uhr morgens der Trigger ausgelöst und die neue Zeit berechnet wird, dann gibt es noch keine Strompreise für die gewünschte Zeit. Wenn ich mich richtig erinnere, dann gibt die Funktion in diesem Fall 0:00 zurück.Quasi die früheste Zeit für den Folgetag.

    Wenn du die Waschmaschine tatsächlich immer Nachts laden willst und möglichst nicht vor 4:00 Uhr, dann könnte ggf. auch folgender DOIF funktionieren:

    DOELSEIF ([TibberWaschmaschine] eq „AUS“ and [{MinStromTime(4,2,5)}] and [0:00-9:00] )

    Also wieder „4“ für frühestens 4 Uhr und durch die zusätzliche Zeitangabe sollte die Funktion nur in der Zeit von 0:00 bis 9:00 aufgefrufen werden. Testen konnte ich das allerdings im Moment nicht.

  17. Ich hab das selbe Problem wie Micha: Ich möchte, dass das Auto 4h zwischen 18 und 6 Uhr läd, also {MinStromTime(18,4,12)}. Beim manuellen auslösen der Funktion klappt es einmal, danach hab ich immer Startzeit am nächsten Tag Mitternacht. Da ich meine PV-Anlage mitlogge und diese die doofe Angewohnheit hat, die Log-Intervalle immer wieder zu verkürzen, habe ich mir schon vor paar Jahren einen automatisierten täglichen Restart eingebaut {fhem(’shutdown restart‘)}. Diesen habe ich jetzt auf 15 Uhr gelegt, seit dem wird der optimale Zeitraum auch über Mitternacht hinaus täglich korrekt angegeben und ausgelöst. Vielleicht hilft das ja.

  18. Ich habe gerade nochmal meinen aktuellen Code mit dem Code im Blog verglichen. Tatsächlich hatte ich wohl bei mir noch Fehler behoben, die ich im Blogtext nicht geändert habe. Die Funktion MinStromTime habe ich nun im Text angepasst und wahrscheinlich sind die Probleme damit behoben.

  19. Ich habe nun nochmal einen Fehler in der Funktion MinStromTime behoben. Die Berechnung des MinZeitIndex war nicht korrekt, wenn die Startzeit für den gleichen Tag ermittelt wird. Darüber hinaus hatte ich bei der letzten Änderung noch eine Variable übersehen, die auch ausgetauscht werden muß.

    Also am besten nochmal die Funktion komplett neu übernehmen. Ich hoffe, dass ich dann alle Fehler gefunden habe, werde es bei mir aber auch weiter mit euren Zeitangaben beobachten.

    Wenn man einmal dran ist. Nach Erfassung dieses Kommentars habe ich noch einen Fehler beseitigt,der aber wohl nur eingetreten ist, wenn man 0 als mindest Startzeit angegeben hat.

  20. Moin.

    Ist es nicht ungeschickt das die Readings Strompreis und TodayMin unterschiedliche EInheiten haben?
    wenn sie gleich wären könnte man doch viel einfacher vergleichen in ner DOIF.

  21. Ich habe bei mir die Userreadings bearbeitet.
    Damit Min, Max und Preis die gleiche Einheit haben.
    Aus Min und max kann man immer ein Delta ausrechnen bei dem sich das laden überhaupt lohn. Pauschal immer dann wenn es billig ist macht kein Sinn.
    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 (100 * sum(min @Preise));;},
    TodayMax {use List::Util qw(min);; my @Preise = split /\|/, ReadingsVal(„myTibber“,“TodayTotal“,0.25);; return (100 * sum(max @Preise));;}

    Leider werden Maximalwerte aus der Vergangenheit geholt, oder das muss sich noch einschleifen über den Tag.
    Das Delta habe ich noch nicht verwurstelt aber aktuell glaube ich es ganz simplel gelöst zu haben:
    ([myTibber:Strompreis] == [myTibber:TodayMin] and [myTibber:TodayMin] < 20)
    (
    system('/opt/e3dcset/load.sh &');
    (set sbot send @Basti 🔋 Achtung: Der Hausakku wird auf 100% geladen!)
    )
    DOELSEIF ([myTibber:Strompreis] == [myTibber:TodayMin] and [S10E:Batterieladezustand] <= 60)
    (
    system('/opt/e3dcset/load70.sh &');
    (set sbot send @Basti Achtung: 🔋 Der Hausakku wird mit 4KWh beladen!)
    )
    DOELSE
    system('/opt/e3dcset/auto.sh &');
    (set sbot send @Basti 🔋 Der Hausakku wird nicht geladen.)

  22. Das mit dem use List::MoreUtils qw{true first_index}; und use List::Util qw( sum ); war auch für mich der entscheidende Tip.
    Dazu musste ich dann noch die Bibliotheken mit:
    sudo apt-get -y install perl-base liblist-moreutils-perl installieren.

    Frage: Lässt sich irgendwie der Zählerstand des Stromzählers auch mit dem tibber pulse auslesen ?

  23. Vielen Dank für den berechtigten Hinweis. Das kann man ganz leicht hinbekommen, in dem man entweder beim Strompreis das „*100“ weglässt oder bei der Definition von TodayMin die Multiplikation mit 100 ergänzt.

  24. Danke für die Bereitstellung deines Codes. Ich bin auch gerade dabei noch ein paar Optimierungen, insbesondere zum Laden unserer E-Autos zu realisieren. So teste ich gerade eine Funktion, die mir berechnet, wieviel PV-Energie nötig ist, damit diese in Verbindung mit dem aktuellen Netzstrompreis einen günstigeren Preis liefert, als der günstigste Netzstrompreis.

  25. Mit dem Pulse kann man keinen Zählerstand ablesen. Über die API könnte man aber jede Stunde den gemessenen Verbrauch ermitteln. Da es aber auch schon mal Aussetzer gibt, kann es leichte Differenzen zum tatsächlichen Zählerstand geben.

  26. Hallo,
    habt Ihr auch manchmal das Problem, dass wegen eines Timeouts die Preise vom Tibber nicht abgerufen werden können? Ich würde daher die Abfrage gerne so umbauen, dass jede Minute ein weiterer Versuch gestartet wird falls die Abfrage um 5 Minuten nach der vollen Stunde nicht erfolgreich war.
    Danke und viele Grüße,
    Michael

  27. Guten Morgen, ich habs zunächst einfach mit einem Setzen des interval Attribut des httpmod auf 300 gelöst. So hab ich zwar eine Menge Anfragen bei Tibber jedoch ist die Wahrscheinlichkeit dass es zumindest einmal pro Stunde keinen Timeout gibt höher.
    LG, Michael

  28. Hast du schon eine Idee, wie du die ganzen userreadings Zeilen wie TodayTotal und TomorrowTotal wieder „auseinander“ ziehst für das Dashboard und du die ganzen Werte visualisierst?

  29. Bisher hatte ich leider nicht die Zeit, mein Dashboard zu erweitern und die Tibber Daten anzuzeigen. Für die einfache Nutzung besteht grundsätzlich die Möglichkeit, dass ich beispielsweise die Total-Werte doch in einzelne Readings packe. Ich bin mir aber nicht sicher, ob man tatsächlich alle Werte benötigt. Aktuelle zeige ich auf meinem Dashboard immer den aktuellen Preis sowie den Tagesschnitt an. Damit bekomme ich eine grobe Vorstellung, wie gut der aktuelle Preis ist. Man könnte dann natürlich noch anzeigen, um wieviel Uhr der günstigste Preis zu erwarten ist oder wann der günstigste Preis in einem vordefinierten Zeitfenster zu erwarten ist.

    Mein Bestreben ist es aber eigentlich, dass man sich um solche Infos möglichst gar nicht kümmern muß und entsprechende Automatisierungen für die beste Stromnutzung zuständig sind. Klappt natürlich nur, wenn entsprechende Verbraucher in die Haussteuerung eingebunden sind.

  30. Bezüglich möglicher Timeouts bzw. verzögerter Bereitstellung der Daten von Tibber könnte man auch das DOIF, welches die Funktion zur Aufbereitung der Daten aufruft, nochmal anpassen:

    ([00:10]) ({TibberDaten()})
    DOELSEIF (([13:05-14:00, +0:05] or [14:15-23:45, +0:15]) and [myTibber:TomorrowTotal] eq "NV") (set myTibber reread) ({TibberDaten()})

    Damit wird HTPPMOD quasi „von Hand aufgerufen“. In diesem Fall also alle 5 Minuten in der Zeit von 13:00 bis 14:00 Uhr und danach alle 15 Minuten, solange bis die Werte vorhanden sind. Man sollte dann aber sicherheitshalber auch einen Waittimer im DOIF einstellen. Beispielsweise mit :0,30:

    Damit wird HTTPMOD sofort aufgerufen und 30 Sekunden später die Funktion zur Datenaufbereitung.

  31. Hallo Jürgen, tolle Arbeit hast du hier wieder geleistet. Aber irgendwie bekomme ich das ganze bei mir nicht richtig zum Laufen. Der Strompreis bzw. die httpbody Daten werden soweit ich sehe bei mir korrekt abgefragt, aber die Berechnung der Average und Min Werte erfolgt nicht, in den Readings werden nur die Initialwerte angezeigt (also immer 25). Die Readings für Energy, Tax und Total erscheinen gar nicht, obwohl sie im httpbody enthalten sind. Hast du irgendeine Idee woran das liegen könnte?
    In der 99_myUtils.pm habe ich use List::MoreUtils qw{true first_index};
    use List::Util qw( sum ); oberhalb von package main; eingefügt und ganz am Ende der Datei auch die 1; enthalten (oder muss die 1; nach jeder Subroutine rein?).
    VG, Tom

  32. Ok, hat sich erledigt. Heute Nacht hat die Aktualisierung funktioniert und nun werden auch alle Werte korrekt angezeigt. Vielen Dank für die tolle Einbindung in FHEM.
    VG, Tom

  33. Danke erstmal für die Bereitstellung deiner Umsetzung in FHEM. Hast Du eine Idee. wie man die stündlichenWerte z.B. als Balkendiagramm im FTUI anzeigen könnte? Das Chart Widget benötigt ja z.B. ein Filelog, Simplechart ebenfalls. Finde es nur irgendwie doof, die Werte, die ja nur 1 x pro Tag aktualisiert werden in ein Filelog zu schreiben, nur damit man den aktuellen Tag im FTUI anzeigen kann.
    Alternativ die stündlichen Werte als Text anzeigen – finde ich aber auch nicht soooo nice…
    VG, Jörg

  34. Hallo Jörg,

    ich nehme für die Werte ohne filelog bzw. DBLog meistens ein data-type=“label“.

    Aber auch
    data-type=“knob“
    data-type=“thermostat“ oder
    data-type=“slider“

    Und für Werte min/max/avg oder auch day/month/year habe ich noch data-type=“rotor“ im Einsatz.

    Grüße
    Jörg

  35. Heute wurden die avg, min, max Werte nicht richtig berechnet. Im doif diTibberDaten haben ich folgenden Fehlereintrag bei den readings:
    error: {TibberDaten()}: malformed JSON string, neither tag, array, object, number, string or atom, at character offset 0 (before „(end of string)“) at ./FHEM/99_myUtils.pm line 147.

    Und im 99_myUtils steht in Zeile 147
    my $json = decode_json(InternalVal(„myTibber“, „httpbody“, „“));

    Kann da evtl. Jürgen oder jemand anderes weiterhelfen? Eigentlich hatte es seit einer Woche soweit funktioniert.

    VG,
    Tom

  36. Danke für den Hinweis. Bei mir gabe es neulich auch Probleme. Wahrscheinlich müssen noch ein paar Fehlersituationen abgefangen werden.

    Dein Fehler lag wahrscheinlich daran, dass keine Daten von Tibber geliefert wurden. Der httbody ist dann leer und die JSON-Funktion läuft auf den Fehler. Ich habe bei mir auf die Schnelle folgenden Code in die Funktion TibberDaten() direkt am Anfang vor allen anderen Anweisungen eingebaut:

    if(InternalVal("myTibber", "httpbody", "") eq "")
    {return;}

    Das ist allerdings nicht die beste Lösung, weil jetzt die Readings für die Durschnittswerte nicht mehr richtig ermittelt werden. Wenn keine Daten von Tibber geliefert werden, dann müsste man wahrscheinich prüfen, ob die aktuellen Tageswerte noch gültig sind oder ob man diese auf „NV“ setzen müsste. Dann wird es natürlich weitere Folgefehler geben. ICh muß mir erst einmal ein paar Gedanken machen, was wann passieren könnte und wie man mit den Fehlern umgeht. Bei mir gab es die Tage auch das Problem, dass überhaupt keine Werte geliefert wurden und irgendwann die Userreadings für die Tagesdurchschnitte nicht mehr richtig berechnet wurden.

    Wahrscheinlich müsste man die Funktion TibberDaten häufiger aufrufen, wenn man feststellt,dass es keine Daten gibt. Vielleicht komme ich am Wochenende dazu, noch ein paar Optimierungen einzubauen. Ansonsten hoffe ich, dass Tibber nicht zu oft Probleme in der Datenbereitstellung hat.

  37. Alles klar, Danke Jürgen, Heute hat es wieder normal funktioniert. Aber du hast vermutlich recht, wenn die Daten zu spät kommen, laufen die Berechnungen ins Leere…

  38. Hallo Jürgen,

    bin gerade zufällig auf Deine Lösung gestoßen und werde die dieses Wochenende mal testen. Danke für die Bereitstellung.

    Du schreibst weiter ob, daß man vom Tibber-Pulse keine Live-DAten abrufen kann. Da stimmt so nicht. Man muß lediglich den WebServer des Tibber-Pulse auf „Dauer-EIN“ einstellen. Dann kann man mit einem modifizierten Obis-Modul alle vom Zähler zur Verfügung gestellten Daten (aktueller Verbrauch/Einspeisung und die Zählerstände für Bezug/Einspeisung) auslesen und muß nicht über die Tibber Cloud.

    siehe FHEM Forumsbeitrag hier:
    https://forum.fhem.de/index.php?topic=133358.0

  39. Hallo Jürgen, Danke für diesen Ansatz!

    ich laufe allerdings in ein Problem mit der Auswertungsfunktion TibberDaten().

    Das doif (diTibberDaten) wirft mir eine Fehlermeldung raus:
    {TibberDaten()}: Undefined subroutine &main::decode_json called at ./FHEM/99_myUtils.pm line 22.

    Scheinbar hab nur ich (?) dieses Problem.

    Any idea wo ich suchen kann?

    VG Stefan

  40. Hallo Stefan,
    ich vergesse in meinen Beispielen tatsächlich immer wieder, dass zusätzliche Bibliotheken importiert werden müssen.

    Versuche mal den Code use JSON::XS; am Anfang in der Datei aufzunehmen. Ich hoffe, dass reicht dann, damit es funktioniert.

  41. Damit bin ich etwas weiter, so richtig bis zum Ende aber noch nicht:
    {TibberDaten()}: malformed JSON string, neither tag, array, object, number, string or atom, at character offset 0 (before „(end of string)“) at ./FHEM/99_myUtils.pm line 24.

    Ich hatte schon vorher versucht den decode_json Aufruf auf JSON::XS:decode_json zu ändern, bin aber auf den gleichen Fehler gelaufen.

    Die Bibliotheken sind übrigens auf dem System installiert. Ich lasse diese fhem Instanz im Moment aus einem Docker Container laufen damit ich „spielen“ kann bis das stabil läuft (um nicht die Production Umgebung zu torpedieren). Ich bin nicht sicher ob das eine Ursache sein könnte, dass ich das im Moment aus einem Docker Container laufen habe …

    Aber Danke für die Unterstützung!

  42. Mir ist noch etwas aufgefallen, was ich im Code des Blogtextes bisher nicht angepasst habe (gerade nachgeholt).

    Wenn in dem HTTP kein JSON-Code enthalten ist, dann funktioniert die Funktion nicht. Bisher hatte ich das Problem nur vereinzelt, weil die Tibber Schnittstelle nicht funktionierte.

    Bau mal als ersten Aufruf in TibberDaten() den folgenden Code ein. Allerdings ist das eigentliche Problem dann im HTTPMOD zu suchen. Den JSON COde sollte man hier eigentlich nach einer Abfrage sehen können (Attribut showBody muss auf 1 stehen).

    if(InternalVal("myTibber", "httpbody", "") eq "")
    {return;}

  43. Die Fehlermeldung bin ich damit los. Allerdings „fliege“ ich dann bei jedem TibberDaten() Aufruf direkt raus, der eigentliche Code von TibberDaten() wird nie ausgeführt. Ich sehe keine entsprechenden Readings die dann sonst eigentlich angelegt würden.
    In myTibber sehe ich die Daten in httpbody, das Attribut showBody steht ohnehin auf „1“.
    Warum auch immer, ich bekomme den httpbody nicht ausgelesen, obwohl die Daten vorhanden sind.

  44. Dann lass uns nochmal Schritt für Schritt vorgehen:
    1) Du siehst im HTTPMOD einen Body-Text der mit {"data":{"viewer":{"homes":[{"currentSubscription":{"priceInfo":{"current": beginnt
    2) TibberDaten wird sofort beendet, da er anscheinend einen leeren httpbody liest

    Das kann aus meiner Sicht dann evtl. nur an irgendwelchen Namen oder Groß-/Kleinschreibungen liegen. Daher würde ich mal wie folgt vorgehen:
    1) Name des HTTPMOD mit dem Aufruf in TibberDaten: InternalVal("myTibber", "httpbody", "") vergleichen. Müsste beides MyTibber heißen, wenn vom Code kopiert.
    2) Test ob der Code zum Auslesen grundsätzlich funktioniert. Dazu folgendes in die Kommandozeile von FHEM eingeben. Dann müsste der Text aus dem HTTPMOD angezeigt werden:
    {InternalVal("myTibber", "httpbody", "")}
    3) Wenn beides richtig ist bzw. funktioniert, dann müsste der decode_json in der Funktion TibberDaten eigentlich auch ausgeführt werden. Das kann man natürlich auch nochmal über die Kommandozeile testen, in dem man folgenden Code ausführt:
    {decode_json(InternalVal("myTibber", "httpbody", ""))}
    Das Ergebnis müsste dann etwas wie HASH(0xa681c78) sein.

  45. Hi, Jürgen!
    Das ist jetzt wieder so ein Effekt den ich nicht mag ! 😉
    Ich habe mir Deine Schritte 1) bis 3) angesehen, mutig wie ich war habe den Befehl aus 3) einfach in die Kommandozeile getippt: HASH(0x1234) kam dabei raus.
    Nächster Schritt: ich habe die Zeile, die den leeren httpbody abgefangen hat, wieder auskommentiert.
    Und rate was passiert: es hat jetzt funktioniert!
    So weit so gut, ich mag es halt einfach nicht wenn ich nicht nachvollziehen kann warum etwas auf einmal funktioniert.
    Aber egal, Du hast mir sehr geholfen. Ich habe vor allen Dingen dabei doch ein wenig mehr vom System an sich verstanden.
    Herzliches Dankeschön dafür, ich gebe Feedback sobald ich das gesamte System am Laufen habe.

    VG Stefan

  46. Hallo Jürgen,
    danke für die tolle Arbeit – hab heute mir das Ganze mal ins mein FHEM integriert und es hat auf Anhieb funktioniert. Ich selber hab einen E3DC-Speicher den ich über MODBUS ans FHEM angebunden habe, d.h. damit hab ich die Informationen wie voll der Speicher gerade ist etc…
    Werde also versuchen dein Coding zu verstehen und wenn möglich anzupassen um bei der Berechnung der idealen Startzeit für einen Verbraucher nicht nur den Tibber-Preis sondern auch den Füllstand der E3DC zu berücksichtigen – weil der Strom im Speicher ist natürlich der günstigste. Gerne Denkanstösse von Dir wie du das umsetzen würdest. Danke nochmal für die echt gute und steile Vorlage !!
    Gruß,
    Rolf

  47. Vielen Dank für das Feedback.
    Wenn ich es richtig verstehe, dann würdest du gerne den besten Startzeitpunkt eines Großverbrauchers unter Berücksichtigung des SOC deines Speichers zu ermitteln. Die Frage wäre also, wie lange würde der Großverbraucher mit dem aktuellen Ladestand (unter Berücksichtigung des Grundverbrauchs) aus dem Speicher versorgt werden. Aus meiner Sicht müsste man zunächst ermitteln, wieviel SOC für den Grundverbrauch benötigt werden, bis der Speicher wieder geladen wird. Dann könnte man berechnen, wieviel im Speicher noch für den Großverbraucher übrig sind. Nun kann man berechnen, wie lange der Großverbraucher mit der berechneten Akkuleistung versorgt werden kann. Im einfachsten Fall würde man die Zeit, die man dabei ermittelt von der Laufzeit des Großverbrauchers abziehen und hätte dann die reine Laufzeit für den Großverbraucher und könnte die Startzeit mit meiner Funktion nutzen.

    Das hat aus meiner Sicht aber ein paar Tücken. Denn das würde nur funktionieren, wenn man die Speichernutzung zeitgenau steuern kann, was wahrscheinlich nicht geht. Also würde, egal zu welcher Zeit, der Großverbraucher immer zuerst den Akku komplett nutzen. Damit wäre es eigentlich geschickter, einen Startzeitpunkt zu ermitteln, in die Preise besonders hoch sind. Meine Funktion müsste also dahin gehend angepasst werden, dass nicht der Zeitpunkt für die günstigsten Preise ermittelt wird, sondern der Zeitpunkt der höchsten Preise. Die Herausforderung ist also, die optimale Zeit zu finden in der der Akku zu möglichen hohen Sttrompreisen genutzt wird, wenn dieser aber nicht für die gesamte Laufzeit ausreicht, den Startzeitpunkt so zu wählen, dass der Rest in eine möglichst günstige Zeit fällt.

    Ich hoffe, dass meine Gedanken einigermaßen verständlich sind. Wäre wahrscheinlich eine Optimierungsfunktion, die man einem Mathematiker geben sollte ;-).

  48. Ich schraube gerade auch an der Baustelle…

    Was man im Hinterkopf behalten sollte:
    – es macht wenig Sinn den Speicher mit dem Auto zu Zeiten zu entladen an denen der Strom sowieso günstig ist (und der Speicher dann leer ist sobald der Strom wieder teuer ist, und noch kein PV Überschuss vorhanden)
    – wenn man noch den Wirkungsgrad der Speicher/Inverter Kombination einbezieht wird es noch komplexer. Dann muss nämlich noch berücksichtigt werden wann, welcher Preis im Bezug gilt während der Speicher geladen, und welcher wenn das Auto aus dem Speicher geladen wird.

    Da ich mein Auto Tags (Büro) sowieso nicht laden kann, sondern nur Nachts habe ich die Idee das Auto aus dem Speicher zu laden „geparkt“. Ich lade es zu Nachts günstigen Zeiten aus dem Netz (dabei Speicher gesperrt), bzw am Wochenende direkt aus der PV Anlage.

  49. Hallo Jürgen, ich würde gerne meine Verbrauchsdaten aus der Tibber-API in eine Excel vom Akkudokotor einlesen. Kennst du eine einfache Möglichkeit das umzusetzen? Das PV-Tool würde mir mit meinen persönlichen Daten ermitteln welche Konstellation für eine PV-Anlage + Speicher für mein Haus am sinnvollsten wäre. Meine Kenntnisse reichen dazu leider nicht aus – die Tibber-API hab. Ich soweit verstanden wie ich die Daten angezeigt bekomme – nur die Anzeige dort ist mies und die stündlichen Wattzahlen müssten in die Zeittabelle der Csv korrekt übertragen werden… Ist das ein interessantes Projekt für dich? Oder kannst du mir einen Tipp geben – würde mich sehr freuen und wäre sicherlich auch für andere eine Hilfe. Gruß Stefan

  50. Hallo Stefan, da ich aktuell zu wenig Zeit habe, habe ich mal versucht eine schnelle Lösung für dich zu entwickeln bzw. eine Vorgehensweise zu beschreiben. Ist jetzt noch nicht die perfekte Lösung, hilft dir aber ggf. weiter.

    Ich würde die Developer Seite von Tibber nutzen. Dort kann man sich ja anmelden und diverse Abfragen testen. Damit du deine eigenen Daten bekommst, musst du den persönlichen Token laden. Als Abfrage habe ich die Consumption Abfrage als Muster genutzt und wie folgt gekürzt.

    {
      viewer {
        homes {
          consumption(resolution: HOURLY, last: 10) {
            nodes {
              from
              consumption
            }
          }
        }
      }
    }
    

    Damit wird dann quasi für jede Stunde ein Verbrauchswert angezeigt. In meinem Beispiel für die letzten 10 Stunden. Man muß hier also einen entsprechend hohen Wert eingeben, damit man alle Stunden eines Jahres bekommt.

    Nun muß das ganze in csv bzw. in eine Exce-Datei. Hierfür fand ich die Webseite https://csvjson.com/json2csv am besten. Hier kann man den JSON Code in eine CSV-Darstellung umwandeln und asl Datei herunterladen. Beim kopieren des Codes aus der Tibberseite sollte nur der Teil zwischen den eckigen Klammern kopiert werden.

    Aus meinem Beispiel:

    [{
                    "from": "2024-04-21T07:00:00.000+02:00",
                    "consumption": 0.012
                  },
                  {
                    "from": "2024-04-21T08:00:00.000+02:00",
                    "consumption": 0.137
                  },
                  {
                    "from": "2024-04-21T09:00:00.000+02:00",
                    "consumption": 0.163
                  },
                  {
                    "from": "2024-04-21T10:00:00.000+02:00",
                    "consumption": 0.073
                  },
                  {
                    "from": "2024-04-21T11:00:00.000+02:00",
                    "consumption": 0.009
                  },
                  {
                    "from": "2024-04-21T12:00:00.000+02:00",
                    "consumption": 0
                  },
                  {
                    "from": "2024-04-21T13:00:00.000+02:00",
                    "consumption": 0.089
                  },
                  {
                    "from": "2024-04-21T14:00:00.000+02:00",
                    "consumption": 0.75
                  },
                  {
                    "from": "2024-04-21T15:00:00.000+02:00",
                    "consumption": 1.29
                  },
                  {
                    "from": "2024-04-21T16:00:00.000+02:00",
                    "consumption": 0
                  }]

    Den Rest habe ich jetzt nicht mehr getestet. Im Prinzip würde ich die Datei jetzt in Excel öffnen. Wenn es richtig funktioniert sollte man zwei Spalten haben. Dann müsstest du nur die Spalte mit den Verbrauchswerten an die richtige Stelle in das Template vom Akkudoktor kopieren. Sollte alles in einer Spalte landen, dann müsste man mit Suchen/Ersetzen mittels Regex-Ausdrücken die Infos zum Datum entfernen. Wie gesagt, eigentlich sollte der Import der csv Datei eigentlich dafür sorgen, dass die Infos in zwei Spalten landen (als Trenner am besten das Semikolon auswählen).

    Ich hoffe, dass dies in der Kürze verständlich war und du das so nutzen kannst.

  51. Ich würde die Implementierung auch gerne nutzen.
    Jedoch verstehe ich nicht, wo ich meinen Token eingeben muss.
    Ich habe ihn anstelle des „PERSÖNLICHER_TOKEN“ im Reading „attr myTibber replacement1Value PERSÖNLICHER_TOKEN“ eingegeben.
    Aber bei mir bekommt er keine Verbindung zur API hin.
    Was mache ich falsch?

  52. Wo der Fehler liegt, kann ich auf die Entfernung nicht erkennen. Die Vorgehensweise ist jedenfalls richtig. Gibt es denn irgendwelche Fehlermeldungen oder hast du mal ins Logfile geschaut? Evtl. das Attribute „verbose“ mal höher stellen und ins Logfile schauen.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

x

Check Also

Ecoflow Powerstream Lade- und Einspeisesteuerung mit FHEM

Seit einiger Zeit habe ich eine Ecoflow Delta2 in Verbindung mit einem Ecoflow Powerstream im ...