AWATTar - Virtueller Stromkunde

Aus FHEMWiki

ACHTUNG, SEITE IN ARBEIT

Motivation

Das relativ junge Unternehmen aWATTar (eine Tochter der durch ihre Thermostate bekannten tado GmbH) hat sich zum Ziel gesetzt, die variablen Strompreise der Leipziger Strombörse EPEX Spot DE an die Endkunden weiterzugeben. Diese werden jeweils täglich um 14:00 für den Folgetag bekannt gegeben und können - bei einem Überangebot von solarer Einspeisung oder Strom aus Windkraftanlagen - auch negativ werden.

aWATTar berechnet den Endkunden dann

Arbeitspreis Cent/kWh = Börsenpreis + 3% vom Absolutwert des Börsenpreises + Netzengelte/Steuern
Grundpreis €/Monat = Netzentgelte/Steuern + Messstellenbetrieb + Aufschlag aWATTar

Typische Werte sind

Arbeitspreis = Börsenpreis + 3% vom Absolutwert des Börsenpreises + 16,71 Cent/kWh
Grundpreis   = 10,01 €/Monat + 5,44 €/Monat + 4,58 €/Monat = 20,03 €/Monat

Für FHEM-Nutzer stellt sich damit die Frage:

Wieviel hätte es mich am heutigen Tag gekostet, aWATTar-Kunde zu sein. Diese Frage wird mit dem hier vorgestellten Code beantwortet.

Strompreis holen und auswerten

Die Strompreise werden mit Hilfe einer Instanz des HTTPMOD-Moduls geholt, und zwar mit den beiden Befehlen "get <Device> today" für den Zeitraum ab der vorhergehenden Mitternacht, und "get <Device> tomorrow" für den Zeitraum ab der kommenden Mitternacht. Letzteres ist deshalb nur möglich nach 14:00 an jedem beliebigen Tag. Wichtig ist daher, ein externes Device anzulegen (z.B. mit dem at-Modul oder mit dem YAAHM-Modul), das um kurz vor Mitternacht die neuen, ab Mitternacht gültigen Strompreise holt.

Warum erst kurz vor Mitternacht? Natürlich, damit die gegenwärtig berechneten Strompreise noch korrekt sind und nicht etwa überschrieben werden.

Warum nur einmal pro Tag? Das ist eigentlich ein Service für aWATTar-Kunden. Wenn eine große Anzahl von Zugriffen erfolgt, wir aWATTar das ganz schnell dicht machen und nur gegen Token zulassen.

Definition

Das Device aWATTar (der Name kann natürlich beliebig angepasst werden) wird mit einem

define aWATTar HTTPMOD https://api.awattar.de/v1/marketdata 0

erzeugt. Wichtig ist die "0": Das Device soll nicht ohne expliziten Befehl die Daten holen.

Danach müssen mehr als 50 Attribute für dieses Device gesetzt werden. Aus Gründen der Übersichtlichkeit habe ich die vollständige Definition unten auf dieser Seite stehen. Tipp: Eine Telnet-Session zu FHEM aufmachen und die ganze Definition hineinkopieren. In den Attributen werden die Funktionen aWATTts2sr undaWATTp2p aufgerufen, diese müssen FHEM natürlich bekannt gemacht werden, siehe unten.

Als Ergebnis der nachfolgenden Definitionen zeigt FHEM als Zustand des Devices zu jeder Zeit

<irgendetwas> € (<aktueller Strompreis> Cent/kWh)

Der erste Teil dieses Zustands ist noch leer - darin werden die aktuellen Kosten des heutigen Tages aufgelistet. Zum Thema <irgendwas> -> Siehe unten.

Konfiguration

Bei dem Device müssen folgende (User-)Attribute gesetzt werden:

grundpreis = der insgesamt in Rechnung gestellte monatliche Grundpreis in €
netzentgelte = der von aWATTar auf den Börsenpreis (+3% absolut) aufgeschlagene Anteil des Arbeitspreises in Cent/kWh

Periodische Ausführung

Die Berechnung des gegenwärtig aktuellen Arbeitspreises erfolgt zu Beginn jeder Stunde. Das wird durch ein Device eCostTicker2 gesteuert, damit wird eine externe Funktion aWATTcp() aufgerufen.

defmod eCostTicker2 at +*01:00 {aWATTcp()}
attr eCostTicker2 alignTime 00:00
attr eCostTicker2 group energyCost
attr eCostTicker2 room Energie

Diese Ticker sorgt dafür, dass zu Beginn jeder Stunde die Neuberechnung des userReading arbeitspreis im Device anstößt. Gleichzeitig wird der bisherige Arbeitspreis in das userReading arbeitspreis_prev verschoben.

Funktionen

Die Funktionen aWATTts2sr, aWATTp2p und aWATTcp müssen FHEM bekannt sein. Beispielsweise kann man sie in die 99_myUtils.pm, oder in eine dezidierte 99_EnergyUtils.pm schreiben

#  aWATTar timestamp to something readable
sub aWATTts2sr($){
  my ($val)=@_;
  return POSIX::strftime("%Y-%m-%d %H:%M:%S",localtime($val/1000));
}
#  aWATTar working price calculation
sub aWATTp2p($){
  my ($val)=@_;
  my $n = AttrVal("aWATTar","netzentgelte",0);
  my $p = $val/10+0.03*abs($val/10)+ $n;
  return sprintf("%.4f",$p);
}
#  aWATTar current working price
sub aWATTcp(){
  my ($val)=@_;
  my $s=sprintf("%02d",POSIX::strftime("%H",localtime())+1);
  my $p = ReadingsVal("aWATTar","data".$s."_price",0);
  fhem("setreading aWATTar arbeitspreis_prev ".ReadingsVal("aWATTar","arbeitspreis",0));
  fhem("setreading aWATTar arbeitspreis $p");
}

Virtuelle Stromkosten berechnen

Als Ergebnis der nachfolgenden Definitionen zeigt FHEM als Zustand des Devices aWATTar zu jeder Zeit

<Stromkosten heute bis zum gegenwärtigen Zeitpunkt> € (<aktueller Strompreis> Cent/kWh)

Ausführung

Benötigt wird ein Device, das in mehr oder weniger regelmäßigen Abständen den Stromverbrauch in Kilowattstunden per Event meldet. Das muss nicht einmal der täglich Verbrauch sein, weil im Nachfolgenden immer der Differenzbetrag zur vorigen Messung genommen wird. Einschränkung: Wir gehen davon aus, dass das Messintervall kleiner als eine Stunde ist, also höchstens ein Tarifwechsel zwischen zwei Messungen liegt.

Für die weitere Erläuterung nehmen wir an, dass es sich bei dem Messgerät um das Device E.Verb handelt, und dass die gemessene Energie sich im Reading energy befindet. Man definiert also ein DOIF:

defmod eCostTicker1 DOIF \
([E.Verb:"energy"])\
({my $e=ReadingsVal("E.Verb","energy",0);;\
 aWATTrc($e);;\
})\
DOELSEIF([00:00:02])\
(setreading aWATTar sumD 0.0,\
setreading aWATTar energy_prev 0.0)\
attr eCostTicker1 do always
attr eCostTicker1 group energyCost
attr eCostTicker1 room Energie
attr eCostTicker1 stateFormat {sprintf("cmd %d at %s",ReadingsVal("eCostTicker1","cmd_nr",""),ReadingsTimestamp("eCostTicker1","cmd_nr",""))}

mit dem bei jedem Update der Energiemessung die Kostenfunktion aufgerufen wird.

Funktionen

Die externe Funktion (siehe oben zur Platzierung) wird mit einen neuen Energieverbrauchswert aufgerufen. Sie überprüft, ob sich während der letzten Messperiode der Preis geändert hat, wenn ja, wird eine lineare Interpolation vorgenommen. Wenn nein, wird einfach der gegenwärtige Strompreis verwendet und die jetzt erzielten Kosten der Summe hinzugefügt.

#  aWATTar running cost
sub aWATTrc($){
 my ($e)=@_;
 my $ep=ReadingsVal("aWATTar","energy_prev",0);
 my $et=ReadingsTimestamp("aWATTar","energy_prev",0);
 my $cc=ReadingsVal("aWATTar","sumD",0);
 my $sp=time_str2num($et);
 my $sn=time();
 my $hp=int($sp/3600);
 my $hn=int($sn/3600);
 my $delta_c;
 #-- no interpolation necessary, both readings within the hour
 if( $hp == $hn ){
   $delta_c=($e-$ep)*ReadingsVal("aWATTar","arbeitspreis",0)/100+$cc;
 }else{
   my $x1 = $hn*3600-$sp;
   my $x2 = $sn-$hn*3600;
   $delta_c=$cc+($e-$ep)/(100*($x1+$x2))*(
     ReadingsVal("aWATTar","arbeitspreis",0)*$x1
    +ReadingsVal("aWATTar","arbeitspreis_prev",0)*$x2);
 }
 fhem("setreading aWATTar deltaE ".sprintf("%.2f",($e-$ep)));
 fhem("setreading aWATTar energy_prev ".$e);
 fhem("setreading aWATTar sumD ".sprintf("%.2f",$delta_c));
}

Vollständige Definition des Devices aWATTar

defmod aWATTar HTTPMOD https://api.awattar.de/v1/marketdata 0
attr aWATTar userattr grundpreis netzentgelte
attr aWATTar event-on-update-reading touch,state,data_start,data_end,sumD
attr aWATTar extractAllJSON 0
attr aWATTar get01Name data_tomorrow
attr aWATTar get01URL https://api.awattar.de/v1/marketdata?start=%%start_tomorrow%%
attr aWATTar get02Name data_today
attr aWATTar get02URL https://api.awattar.de/v1/marketdata?start=%%start_today%%
attr aWATTar group energyCost
attr aWATTar grundpreis 20.03
attr aWATTar icon measure_power
attr aWATTar netzentgelte 16.71
attr aWATTar reading01JSON data_01_start_timestamp
attr aWATTar reading01Name data_start
attr aWATTar reading01OExpr main::aWATTts2sr($val)
attr aWATTar reading02JSON data_24_end_timestamp
attr aWATTar reading02Name data_end
attr aWATTar reading02OExpr main::aWATTts2sr($val)
attr aWATTar reading11JSON data_01_marketprice
attr aWATTar reading11Name data01_price
attr aWATTar reading11OExpr aWATTp2p($val)
attr aWATTar reading12JSON data_02_marketprice
attr aWATTar reading12Name data02_price
attr aWATTar reading12OExpr aWATTp2p($val)
attr aWATTar reading13JSON data_03_marketprice
attr aWATTar reading13Name data03_price
attr aWATTar reading13OExpr aWATTp2p($val)
attr aWATTar reading14JSON data_04_marketprice
attr aWATTar reading14Name data04_price
attr aWATTar reading14OExpr aWATTp2p($val)
attr aWATTar reading15JSON data_05_marketprice
attr aWATTar reading15Name data05_price
attr aWATTar reading15OExpr aWATTp2p($val)
attr aWATTar reading16JSON data_06_marketprice
attr aWATTar reading16Name data06_price
attr aWATTar reading16OExpr aWATTp2p($val)
attr aWATTar reading17JSON data_07_marketprice
attr aWATTar reading17Name data07_price
attr aWATTar reading17OExpr aWATTp2p($val)
attr aWATTar reading18JSON data_08_marketprice
attr aWATTar reading18Name data08_price
attr aWATTar reading18OExpr aWATTp2p($val)
attr aWATTar reading19JSON data_09_marketprice
attr aWATTar reading19Name data09_price
attr aWATTar reading19OExpr aWATTp2p($val)
attr aWATTar reading20JSON data_10_marketprice
attr aWATTar reading20Name data10_price
attr aWATTar reading20OExpr aWATTp2p($val)
attr aWATTar reading21JSON data_11_marketprice
attr aWATTar reading21Name data11_price
attr aWATTar reading21OExpr aWATTp2p($val)
attr aWATTar reading22JSON data_12_marketprice
attr aWATTar reading22Name data12_price
attr aWATTar reading22OExpr aWATTp2p($val)
attr aWATTar reading23JSON data_13_marketprice
attr aWATTar reading23Name data13_price
attr aWATTar reading23OExpr aWATTp2p($val)
attr aWATTar reading24JSON data_14_marketprice
attr aWATTar reading24Name data14_price
attr aWATTar reading24OExpr aWATTp2p($val)
attr aWATTar reading25JSON data_15_marketprice
attr aWATTar reading25Name data15_price
attr aWATTar reading25OExpr aWATTp2p($val)
attr aWATTar reading26JSON data_16_marketprice
attr aWATTar reading26Name data16_price 
attr aWATTar reading26OExpr aWATTp2p($val)
attr aWATTar reading27JSON data_17_marketprice
attr aWATTar reading27Name data17_price
attr aWATTar reading27OExpr aWATTp2p($val)
attr aWATTar reading28JSON data_18_marketprice
attr aWATTar reading28Name data18_price
attr aWATTar reading28OExpr aWATTp2p($val)
attr aWATTar reading29JSON data_19_marketprice
attr aWATTar reading29Name data19_price
attr aWATTar reading29OExpr aWATTp2p($val)
attr aWATTar reading30JSON data_20_marketprice
attr aWATTar reading30Name data20_price
attr aWATTar reading30OExpr aWATTp2p($val)
attr aWATTar reading31JSON data_21_marketprice
attr aWATTar reading31Name data21_price
attr aWATTar reading31OExpr aWATTp2p($val)
attr aWATTar reading32JSON data_22_marketprice
attr aWATTar reading32Name data22_price
attr aWATTar reading32OExpr aWATTp2p($val)
attr aWATTar reading33JSON data_23_marketprice
attr aWATTar reading33Name data23_price
attr aWATTar reading33OExpr aWATTp2p($val)
attr aWATTar reading34JSON data_24_marketprice
attr aWATTar reading34Name data24_price
attr aWATTar reading34OExpr aWATTp2p($val)
attr aWATTar replacement01Mode expression
attr aWATTar replacement01Regex %%start_tomorrow%%
attr aWATTar replacement01Value (timelocal(localtime(time()-time()%86400+86400))-2*3600)."000"
attr aWATTar replacement02Mode expression
attr aWATTar replacement02Regex %%start_today%%
attr aWATTar replacement02Value (timelocal(localtime(time()-time()%86400))-2*3600)."000"
attr aWATTar room Energie
attr aWATTar stateFormat sumD € (arbeitspreis Cent/kWh)
attr aWATTar userReadings arbeitspreis:none {},\
arbeitspreis_prev:none {},\
basispreis:none {},\
sumD:none {},\
deltaE:none {},\
energy_prev:none {}