Tibber in FHEM einbinden – Viertelstundenpreise nutzen

Als ich vor einiger Zeit zu Tibber gewechselt bin, hatte ich natürlich auch die dynamischen Strompreise in meiner Haussteuerung genutzt. Hierzu gab es auch einen ausführlichen Artikel mit dem entsprechenden FHEM Code zur Nutzung der Tibber API. Seit 1. Oktober werden nun die dynamischen Strompreise nicht mehr stündlich ermittelt, sondern nun gibt es alle 15 Minuten einen neuen Strompreis. Der bisherige Zugriff und die entsprechenden Funktionen laufen aktuell grundsätzlich noch ohne Probleme weiter.

Ich habe die Gelegenheit nun genutzt und stelle meine Funktionen nun Schritt für Schritt um. Parallel optimiere ich die Funktionen ein wenig und nehme hierzu gerne auch eure Anforderungen auf. Die aktuelle Umsetzung ist natürlich in erster Linie an meine Anforderungen ausgerichtet, die allerdings einen Großteil allgemeiner Anforderungen an eine Steuerung mit dynamischen Strompreisen abdecken sollte.

Stündliche Preise in der Tibber App
Viertelstunden Preise in der Tibber App

Zunächst war ich mir nicht sicher, ob die Viertelstunden-Preise tatsächlich ein Mehrwert gegenüber den bisherigen Stundenpreisen bieten. Die ersten Tage haben jedoch gezeigt, dass die Viertelstundenpreise innerhalb einer Stunden durchaus mal 1 bis 2 Cent von einander abweichen können. Damit macht es auch für Großverbraucher Sinn, diese ggf. nicht zur vollen Stunden zu starten, sondern zu einem entsprechend günstigeren Preis kurz vorher oder nachher. Interessant sind die Viertelstundepreise wahrscheinlich auch für Verbraucher, die immer nur ein paar Minuten laufen, dafür aber mehrmals am Tag gestartet werden. Dies können beispielsweise irgendwelche Pumpen sein. Aber auch die Aufladung eine Speichers könnte man ggf. so steuern, dass er zu den günstigsten Viertelstundenpreisen geladen wird.

Im heutigen Beitrag werde ich zunächst die eigentliche Schnittstelle zu Tibber mit meinen Optimierungen bzw. Ergänzungen präsentieren. Weitere Funktionen zur Steuerung von Verbrauchern mittels dynamischer Strompreise werde ich dann später darstellen. Auch hier nehme ich gerne eure Anforderungen auf.

Viertelstunden Strompreise über die Tibber API ermitteln

Wenn ihr noch keinen Zugriff auf die Tibber-API habt, müsst ihr euch auf der Developer Seite zunächst einen Token besorgen. Hierzu meldet ihr euch mit eurem Tibber Zugangsdaten auf der Developer Seite von Tibber an.

FHEM HTTPMOD Modul für den Zugriff auf die Tibber API verwenden

Die Umsetzung in FHEM habe ich mittels HTTPMOD erstellt. Auf Grund der Viertelstundenpreise wird von Tibber nun empfohlen, den Abruf nur einmal am Tag durchzuführen und sich die jeweiligen Preise zwischenzuspeichern. Grundsätzlich benötigt man dann auch die bisherigen reading01 und reading02 nicht mehr. Ich habe sie zunächst noch drin gelassen, wobei ich das Reading „Strompreis“ in „Strompreis_alt“ umbenannt habe. Das reading02 ist ggf. noch in Bezug auf das Datum interessant, so dass direkt sehen kann, wann der letzte Abruf statt gefunden hat.

Die einzige Änderung, die man im HTTPMOD umsetzen muß ist die Ergänzung von „priceInfo(resolution: QUARTER_HOURLY)“ im requestData Attribut. Tibber hat schon angekündigt, dass in einer zukünftigen Version der Wert „QUARTER_HOURLY“ der Defaultwert wird. Wenn ihr also nichts ändern solltet, dann werden irgendwann automatisch die Viertelstundenpreise ermittelt. Allerdings passt dann der übrige Code nicht mehr.

defmod myTibber HTTPMOD https://api.tibber.com/v1-beta/gql 86400
attr myTibber DbLogExclude .*
attr myTibber DbLogInclude Strompreis,TodayAverage,TodayMin
attr myTibber alignTime 13:05
attr myTibber event-on-change-reading .*
attr myTibber reading1JSON data_viewer_homes_01_currentSubscription_priceInfo_current_total
attr myTibber reading1Name Strompreis_alt
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 HIER KOMMT EUER TOKEN REIN
attr myTibber requestData { "query": "{viewer {homes {currentSubscription {priceInfo(resolution: QUARTER_HOURLY) {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 Energie,PV_E_Auto
attr myTibber showBody 1
attr myTibber showError 1
attr myTibber userReadings Strompreis {getStrompreis("Today")},\
TodayAverage {getStrompreis("Today","avg",0,24)},\
TomorrowAverage {getStrompreis("Tomorrow","avg",0,24)},\
TodayMin {getStrompreis("Today","min",0,24,"hour")},\
TodayMinNext {getStrompreis("Today","min","","","hour")},\
TodayMinMorning {getStrompreis("Today","min",0,9,"hour")},\
TodayMax {getStrompreis("Today","max",0,24,"hour")},\
TodayMaxNext {getStrompreis("Today","max","","","hour")},\
TodayMaxMorning {getStrompreis("Today","max",0,9,"hour")},\
TodayMaxEvening {getStrompreis("Today","max",16,24,"hour")},\
TomorrowMin {getStrompreis("Tomorrow","min",0,24,"hour")},\
TomorrowMax {getStrompreis("Today","max",0,24,"hour")},\
UhrzeitMaxPreis {getStrompreisUhrzeit("Today","max",0,24,"hour")},\
UhrzeitMaxNextPreis {getStrompreisUhrzeit("Today","max","","","hour")},\
UhrzeitMaxMorningPreis {getStrompreisUhrzeit("Today","max",0,9,"hour")},\
UhrzeitNextMinPreis {getStrompreisUhrzeit("Today","min","","","hour")},\
TodayMin_Quarter {getStrompreis("Today","min",0,24)},\
TodayMinNext_Quarter {getStrompreis("Today","min")},\
TodayMax_Quarter {getStrompreis("Today","max",0,24)},\
TodayMaxNext_Quarter {getStrompreis("Today","max")},\
TodayMaxMorning_Quarter {getStrompreis("Today","max",0,9)},\
TodayMaxEvening_Quarter {getStrompreis("Today","max",16,24)},\
TomorrowMin_Quarter {getStrompreis("Tomorrow","min",0,24)},\
TomorrowMax_Quarter {getStrompreis("Today","max",0,24)},\
UhrzeitMaxPreis_Quarter {getStrompreisUhrzeit("Today","max",0,24)},\
UhrzeitMaxNextPreis_Quarter {getStrompreisUhrzeit("Today","max")},\
UhrzeitMaxMorningPreis_Quarter {getStrompreisUhrzeit("Today","max",0,9)},\
UhrzeitNextMinPreis_Quarter {getStrompreisUhrzeit("Today","min")}

Im HTTPMOD habe ich noch zahlreiche User-Readings definiert, die man zur Steuerung über die dynamischen Strompreise nutzen kann. Die alten User-Readings habe ich gegenüber der alten Version nicht verändert. Somit liefert „TodayMin“ den minimalen Strompreis des aktuellen Tages aus den Stundenpreisen. Das Reading „TodayMin_Quarter“ liefert den minimalen Viertelstundenpreis.

Wenn ihr also den neuen Code des HTTPMOD verwendet, dann funktioniert alles andere weiter, wie bisher. Natürlich nur, wenn ihr meine ursprünglichen User-Readings verwendet habt.

Ermittelte Preise zur Bearbeitung im Device zwischenspeichern

Nun müssen die ermittelten Preise wieder in entsprechende Readings umgewandelt werden. Auch hier habe ich die alten Werte „TodayEnergy“, „TomorrowEnergy“ usw. beibehalten. Wie bisher liefern diese Readings alle Stundenpreise. So ist sichergestellt, dass weitere Funktionen, die das „myTibber“ Device nutzen auch weiterhin funktionieren. Zusätzlich werden nun entsprechende Werte mit dem Anhängsel „_Quarter“ erstellt. Hier sind alle Viertelstundenwerte zu finden.

Die entsprechende Funktion muß in der 99_myUtils.ppm abgelegt werden und sieht wie folgt aus. Die Datenbankfunktionen habe ich aktuell heraus genommen, da ich sie bisher nicht für Darstellungen genutzt habe. Hier liefere ich sicherlich nochmal eine entsprechende Version nach.

#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 = "";
   my $today_total_quarter = "";
   my $today_energy_quarter = "";
   my $today_tax_quarter = "";
   my $tomorrow_total_quarter = "";
   my $tomorrow_energy_quarter = "";
   my $tomorrow_tax_quarter = "";
   my $today_total_hour = 0;
   my $today_energy_hour = 0;
   my $today_tax_hour = 0;
   my $tomorrow_total_hour = 0;
   my $tomorrow_energy_hour = 0;
   my $tomorrow_tax_hour = 0;

   for(my $j=0, my $anz_quarter=0; $j<96; $j++, $anz_quarter++)
   {
      # Werte ermitteln
	  my $value_today_total_quarter = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{today}[ $j ]->{total};
	  my $value_today_energy_quarter = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{today}[ $j ]->{energy};
	  my $value_today_tax_quarter = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{today}[ $j ]->{tax};
	  my $value_tomorrow_total_quarter = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{tomorrow}[ $j ]->{total};
	  my $value_tomorrow_energy_quarter = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{tomorrow}[ $j ]->{energy};
	  my $value_tomorrow_tax_quarter = $json->{data}->{viewer}->{homes}[0]->{currentSubscription}->{priceInfo}->{tomorrow}[ $j ]->{tax};
	  
	  # Viertelstundenwerte für Stundenwert addieren
	  $today_total_hour += $value_today_total_quarter;
      $today_energy_hour += $value_today_energy_quarter;
      $today_tax_hour += $value_today_tax_quarter;
	  
	  # Wenn Stundenwert erreicht, dann diesen in Zeichenkette wandeln
	  if($anz_quarter == 3)
	  {
	    # Durchschnittlichen Stundenwert ermitteln
	    $today_total_hour /= 4;
		$today_energy_hour /= 4;
		$today_tax_hour /= 4;
	    
	  	$today_total = $today_total.sprintf("%.4f", $today_total_hour)."|";
	  	$today_energy = $today_energy.sprintf("%.4f", $today_energy_hour)."|";
	  	$today_tax = $today_tax.sprintf("%.4f", $today_tax_hour)."|";
		
		#Log3 undef, 3, "TibberDaten:".$j.":".$today_total;
		
		$today_total_hour = 0;
		$today_energy_hour = 0;
		$today_tax_hour = 0;
	  }
	  
	  #Zeichenkette für Viertelstundewerte aufbauen
	  $today_total_quarter = $today_total_quarter.$value_today_total_quarter."|";
	  $today_energy_quarter = $today_energy_quarter.$value_today_energy_quarter."|";
	  $today_tax_quarter = $today_tax_quarter.$value_today_tax_quarter."|";
	  	  
	  if(defined $value_tomorrow_total_quarter)
	  {
	     $tomorrow_total_hour += $value_tomorrow_total_quarter;
         $tomorrow_energy_hour += $value_tomorrow_energy_quarter;
         $tomorrow_tax_hour += $value_tomorrow_tax_quarter;
	  
  		 if($anz_quarter == 3)
		 {
		 	# Durchschnittlichen Stundenwert ermitteln
	        $tomorrow_total_hour /= 4;
		    $tomorrow_energy_hour /= 4;
		    $tomorrow_tax_hour /= 4;
		 
		    $tomorrow_total = $tomorrow_total.sprintf("%.4f", $tomorrow_total_hour)."|";
			$tomorrow_energy = $tomorrow_energy.sprintf("%.4f", $tomorrow_energy_hour)."|";
			$tomorrow_tax = $tomorrow_tax.sprintf("%.4f", $tomorrow_tax_hour)."|";
			
			$tomorrow_total_hour = 0;
		    $tomorrow_energy_hour = 0;
		    $tomorrow_tax_hour = 0;
		 }

	     $tomorrow_total_quarter = $tomorrow_total_quarter.$value_tomorrow_total_quarter."|";
	     $tomorrow_energy_quarter = $tomorrow_energy_quarter.$value_tomorrow_energy_quarter."|";
	     $tomorrow_tax_quarter = $tomorrow_tax_quarter.$value_tomorrow_tax_quarter."|";
	  }
	  else
	  {
	  	 $tomorrow_total = $tomorrow_energy = $tomorrow_tax = "NV";
		 $tomorrow_total_quarter = $tomorrow_energy_quarter = $tomorrow_tax_quarter = "NV";
	  }
	  	  
	  if($anz_quarter == 3)
	  {
	     $anz_quarter = -1;
	  }
   }
   
   fhem("setreading myTibber TodayTotal_Quarter $today_total_quarter");
   fhem("setreading myTibber TodayEnergy_Quarter $today_energy_quarter");
   fhem("setreading myTibber TodayTax_Quarter $today_tax_quarter");
   
   fhem("setreading myTibber TomorrowTotal_Quarter $tomorrow_total_quarter");
   fhem("setreading myTibber TomorrowEnergy_Quarter $tomorrow_energy_quarter");
   fhem("setreading myTibber TomorrowTax_Quarter $tomorrow_tax_quarter");

   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");
}

Damit haben wir im Prinzip schon den Großteil der benötigten Routinen. Nun fehlen nur noch die Funktionen, damit die Daten regelmäßig aktualisiert werden und die folgende Funktion, damit die User-Readings ermittelt werden können.

# Ermittlung von dynamischen Strompreisen aus dem Device "myTibber"
# 
# tag : Today oder Tomorrow
# minmax: min,max,act,avg ; wenn leer, dann aktueller Wert
# von: Stunde ab der ein Preis ermittelt wird; wenn leer dann aktuelle Zeit
# bis: Stunde bis zu der ein Preis ermittelt wird; wenn leer, dann bis zum Ende des Tages
# mode: wenn leer dann Nutzung der Viertelstundenpreise / hour bei Nutzung der Stundenpreise
#
# Beispiele: 
# 
# 1. Minimalen Strompreis von heute in der Zeit von 0:00 bis 9:00 ermitteln und die Stundenpreise nutzen
# getStrompreis("Today", "min", 0, 9, "hour")
#
# 2. Maximalen Strompreis von morgen in der Zeit von 00:00 bis 00:00 ermitteln und die Viertelstundenpreise nutzen
# getStrompreis("Tomorrow", "max", 0, 24)
#
# 3. Aktuelle Strompreis
# getStrompreis("Today")
#
sub getStrompreis
{
	my ($tag, $minmax, $von, $bis, $mode ) = @_;
	
	$tag = (defined($tag) and ($tag eq "Today" or $tag eq "Tomorrow") ? $tag : "Today" );
	$mode = ((!defined($mode) or $mode ne "hour") ? "_Quarter" : "");
	$minmax = ((defined($minmax) and $minmax ne "") ? $minmax : "act");
	$von = ((defined($von) and $von ne "") ? ($mode eq "_Quarter" ? $von*4 : $von) : ($mode eq "_Quarter" ? strftime("%H",localtime)*4+round(strftime("%M",localtime)/15,0) : strftime("%H",localtime)));
	$bis = ((defined($bis) and $bis ne "") ? ($mode eq "_Quarter" ? $bis*4-1 : $bis-1) : ($minmax eq "act" ? $von : ($mode eq "_Quarter" ? 95 : 23) ));
	
	Log3 undef, 3, "getStrompreis: Parameter: ".$tag." minmax:".$minmax." von:".$von." bis:".$bis." mode:".$mode;
	
	my @Preise = split /\|/, ReadingsVal("myTibber",$tag."Total".$mode,0.25);
	Log3 undef, 3, "getStrompreis: ".@Preise;
	
	# Preis zu Uhrzeit
	@Preise=@Preise[$von..$bis];
	Log3 undef, 3, "getStrompreis: in Uhrzeit".@Preise;
	
	# Min / Max Absolut
	return ($minmax eq "avg" ? round((sum(@Preise) / @Preise),4) : $minmax eq "min" ? min(@Preise) : max(@Preise))*100;
}

# Ermittlung der Uhrzeit, zu der ein definierter Strompreis gültig wird. Device = "myTibber"
# 
# tag : Today oder Tomorrow
# minmax: min,max,act; wenn leer, dann aktueller Wert
# von: Stunde ab der ein Preis ermittelt wird; wenn leer dann aktuelle Zeit
# bis: Stunde bis zu der ein Preis ermittelt wird; wenn leer, dann bis zum Ende des Tages
# mode: wenn leer dann Nutzung der Viertelstundenpreise / hour bei Nutzung der Stundenpreise
#
# Beispiele siehe getStrompreis()
# 

sub getStrompreisUhrzeit
{
	my ($tag, $minmax, $von, $bis, $mode ) = @_;
	
	$tag = (defined($tag) and ($tag eq "Today" or $tag eq "Tomorrow") ? $tag : "Today" );
	$mode = ((!defined($mode) or $mode ne "hour") ? "_Quarter" : "");
	$minmax = ((defined($minmax) and $minmax ne "") ? $minmax : "act");
	$von = ((defined($von) and $von ne "") ? ($mode eq "_Quarter" ? $von*4 : $von) : ($mode eq "_Quarter" ? strftime("%H",localtime)*4+round(strftime("%M",localtime)/15,0) : strftime("%H",localtime)));
	$bis = ((defined($bis) and $bis ne "") ? ($mode eq "_Quarter" ? $bis*4 : $bis) : ($minmax eq "act" ? $von : ($mode eq "_Quarter" ? 95 : 23) ));
	
    my @Preise = split /\|/, ReadingsVal("myTibber",$tag."Total".$mode,0.25);

	my $preis_min = $von;
	my $preis_max = $von;
	
	for(my $j=$von; $j<$bis; $j++)
	{
	  if ($Preise[$j] > $Preise[$preis_max]) 
	  {
		  $preis_max = $j;
	  }
	   
      if ($Preise[$j] <= $Preise[$preis_min]) 
	  {
		  $preis_min = $j;
      }
	}
	
	my $preis = ($minmax eq "min" ? $preis_min : $preis_max);

	if($mode eq "_Quarter")
	{
	   my $hour = int($preis / 4);
	   my $quarter = ($preis-$hour*4)*15;
	   
	   return ($hour < 10 ? "0".$hour.":".($quarter == 0 ? "00" : $quarter) : $hour.":".($quarter == 0 ? "00" : $quarter));
	}
	else
	{
		return ($preis < 10 ? "0".$preis.":00" : $preis.":00");
	}
}

Funktionen zur Ermittlung der dynamischen Preise regelmäßig aufrufen

Das HTTPMOD Device „myTibber“ wird in der neuen Version nur noch einmal am Tag aufgerufen. Da die neuen Preise nicht immer zur gleichen Zeit zur Verfügung stehen, hatte ich bereits in meiner ursprünglichen Version eine Funktion realisiert, die solange die Schnittstelle abruft, bis die Preise für den nächsten Tag ermittelt werden konnten.

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

attr diTibberDaten do always
attr diTibberDaten wait 0,60:0,60:

Damit im Device „myTibber“ weiterhin die aktuelle Uhrzeit dargestellt wird, die gerade für die Ermittlung des aktuellen Preises gültig ist, schreibe ich diese nun regelmäßig in das Reading „Uhrzeit“. Damit wird dann auch das Device getriggert, so dass sich alle User-Readings neu berechnen.

defmod diTibberNextPrice DOIF ([+:15])\
  (setreading myTibber Uhrzeit {(strftime("%H:%M",localtime))})
attr diTibberNextPrice do always

Damit solltet ihr nun alle Basisfunktionen haben, die zur Ermittlung und Nutzung der Viertelstunden-Preise benötigt werden. Sobald ich bei mir die eigentlichen Funktionen, die ich zur Steuerung verwende, umgestellt habe, gibt es einen neuen Artikel. Hierzu gehört beispielsweise das Einschalten der Spülmaschine in der Nacht zu günstigsten Preisen oder das Aufladen der E-Autos.

Ich wünsche euch viel Spaß mit meiner Umsetzung und freue mich auf Fragen und weitere Anregungen.

6 Kommentare

  1. Hallo Jürgen, erst mal danke für die Veröffentlichung des Updates. Ich habe mich heute gleich daran gemacht. Ganz funktioniert es aber noch nicht:

    attr myTibber userReadings Strompreis {getStrompreis(„Today“)},\

    Muss die Funktion getStrompreis() nicht noch in die myUtils?

  2. Herzlichen Dank für den Hinweis. Da habe ich irgendwie komplett gepennt. Das war ja echt blöd. Habe die beiden Funktionen nun ergänzt und hoffe, dass sie keine Fehler mehr enthalten. Hatte heute noch etwas bei mir angepasst.

  3. Danke, das hilft schon etwas weiter. Ich habe die zwei get… -Funktionen noch in die 99_myUtils.pm kopiert. Danach bekomme ich immer noch eine Fehlermeldung

  4. Den Code kann ich hier leider nicht eingeben, dann kommt Fehler 403. Daher habe ich die Info an deine Mailadresse aus dem Impressum geschickt.

  5. Ok, eine Mail ist bei mir aber noch nicht eingetroffen. Fehler 403 deutet irgendwie auf Zugriffsfehler bei einer URL hin. Hast du im HTTPMOD deinen Authorisierungscode an der entsprechenden Stelle eingetragen?

  6. Der Fehler trat auf, als ich versuchte, die Meldungen in diesem Feld hier zu übermitteln. Die Mail müsste jetzt durchgegangen sein, es war ein Tippfehler in der Adresse.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert