AWATTar - Virtueller Stromkunde
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
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",""))}
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änder hat, wenn ja, wir 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))-3600)."000" attr aWATTar replacement02Mode expression attr aWATTar replacement02Regex %%start_today%% attr aWATTar replacement02Value (timelocal(localtime(time()-time()%86400))-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 {}