Stromzähler und 1-Wire, OWServer, OWDevice
Dieser Artikel beschreibt eine Anbindung zweier Leistungsmesser (aka Stromzähler) mit S0 Ausgang über einen 1-wire-S0-Zähler an FHEM.
Das Ziel ist anstatt des monatlichen manuellen Ablesens des Einspeisezählers, eine granulare und automatisierte Aufzeichnung der Leistung einer Photovoltaikanlage zu erhalten.
Datei:FHEMwiki-grafik-s0counter-und-onewire.gif
Zwischen die beiden Wechselrichter und dem Einspeisezähler sind je ein digitaler Stromzähler mit S0-Ausgang geschaltet. Einer dieser Stromzähler liefert 2000 Impulse pro kWh, der andere 1000 Impulse pro kWh.
Diese beiden Stromzähler sind mit einem auf dem 1-Wire Baustein DS2423 basierenden Dual S0 Counter verbunden, der hier über einen USB-Busmaster als serielles Interface mit einer Instanz eines OWServers verbunden ist.
In FHEM ist dieser OWServer wie folgt definiert:
define myOWServer OWServer 192.168.1.12:4304
Es wird hier ein OWServer definiert, der über die IP-Adresse 192.168.1.12 erreichbar ist und dort auf dem Port 4304 auf Anfragen lauscht.
Ist in FHEM das "autocreate" Flag gesetzt, dann findet FHEM den S0-Counter und erzeugt beispielsweise die folgenden Einträge:
define DS2423_58E50F000000 OWDevice 1D.58E50F000000 60 attr DS2423_58E50F000000 model DS2423 attr DS2423_58E50F000000 room OWDevice
Unter "DS2423_58E50F000000" kann man sich nicht direkt etwas vorstellen und anstatt alle 60 Sekunden ein Update zu holen, sollten auch 5 Minuten (=300 Sekunden) reichen. Daher wird das abgeändert zu:
define PVZaehler OWDevice 1D.58E50F000000 300 attr PVZaehler model DS2423 attr PVZaehler room OWDevice
Dann definiert man noch ein Logfile dazu:
define FileLog_PVZaehler FileLog ./log/PVZaehler-%Y.log PVZaehler:.* attr FileLog_PVZaehler logtype text
und findet dort Einträge der Form
... 2014-01-20_16:09:55 PVZaehler counters.A: 282478 2014-01-20_16:09:55 PVZaehler counters.B: 139840 2014-01-20_16:14:58 PVZaehler counters.A: 282478 2014-01-20_16:14:58 PVZaehler counters.B: 139841 ...
Wie man sieht, erscheinen für jeden der beiden Counter in diesem 1-Wire Device eine eigene Zeile mit dem aktuellen Wert des Zählers. Da der erste S0-Zähler 2000 Impulse pro kWh liefert, ist der Wert für "counters.A" auch etwa das Doppelte von "counters.B", dessen zugehöriger S0-Zähler ja nur 1000 Impulse pro kWh liefert.
Eigentlich wäre es wünschenswert, nur eine Zeile mit beiden Werten zu loggen, diese mit der passenden Einheit Watt-Stunden (Wh) zu bezeichnen und auch noch die Summe der beiden Werte aufzuzeichnen.
Die "state" Zeile im Reading des Devices
beinhaltet die Werte beider Counter, aber das "state" Reading verursacht laut CommandRef keinen Event, den man in einer FileLog Definition einfach verwenden könnte.
Es muss also etwas mehr getan werden. Die Idee ist, ein Dummy-Device zu definieren und dazu ein passendes Notify, das auf die "PVZaehler" Events reagiert und
den Wert an dieses Dummy-Device weitergibt, in dessen Filelog die Daten dann aufgezeichnet werden.
Im Einzelnen:
define PVErtrag dummy
definiert das Dummy-Device, das die Erträge der Anlage erhalten soll.
Mit
1: define notify_PVErtrag notify PVZaehler:counters\.A.* { \ 2: my $a = ReadingsVal("PVZaehler","counters.A",0)/2 ;; \ 3: my $b = ReadingsVal("PVZaehler","counters.B",0) ;; \ 4: my $c = $a+$b ;; \ 5: my $text = "WR1: $a Wh, WR2: $b Wh, Gesamt: $c Wh" ;; \ 6: fhem ("set PVErtrag $text") ;; \ 7: }
werden die Daten erzeugt und an das PVErtrag Device geschickt.
In Zeile 1 wird das Notify definiert: "PVZaehler" ist das Device, auf dessen Events reagiert werden soll und "counters\.A.*" der reguläre Ausdruck, auf den das Notify reagieren soll. Der Punkt in "counters.A" ist mit einem Backslash maskiert, da er sonst als Metazeichen für den regulären Ausdruck interpretiert werden würde. Das ".*" bezeichnet x-beliebigen Text.
Wenn nun also das PVZaehler-Device einen Event verursacht, der die Zeichenkette "counters.A" enthält, dann wird der in geschweiften Klammern stehende Perl-Code ausgeführt. Dabei dienen die Backslashes jeweils am Zeilenende als Hinweis für FHEM, dass der Perl-Code noch nicht zu Ende ist, sondern in der nächsten Zeile fortgeführt wird. Ebenso sind entsprechend der CommandRef die Semikolons in der fhem.cfg verdoppelt. Zeile 2 definiert eine Variable $a und weist ihr den Wert des "counters.A" geteilt durch 2 zu; in Zeile 3 erhält die Variable $b den Wert des "counters.B". In Zeile 4 werden die beiden Werte addiert und in Zeile 5 schliesslich zu einem Ausgabetext zusammengesetzt. Zeile 6 sendet dann diesen Ausgabetext an das PVErtrag Device.
Schliesslich wird mit
define FileLog_PVErtrag FileLog ./log/FileLog_PVErtrag-%Y-%m.log PVErtrag attr FileLog_PVErtrag logtype text
das Filelog erzeugt, in dem die Daten abgelegt werden. Hier ein Auszug aus diesem FileLog:
... 2014-01-21_13:15:22 PVErtrag WR1: 141666 Wh, WR2: 140271 Wh, Gesamt: 281937 Wh 2014-01-21_13:20:22 PVErtrag WR1: 141677 Wh, WR2: 140282 Wh, Gesamt: 281959 Wh 2014-01-21_13:25:22 PVErtrag WR1: 141687.5 Wh, WR2: 140293 Wh, Gesamt: 281980.5 Wh 2014-01-21_13:30:22 PVErtrag WR1: 141697 Wh, WR2: 140302 Wh, Gesamt: 281999 Wh 2014-01-21_13:35:22 PVErtrag WR1: 141705.5 Wh, WR2: 140311 Wh, Gesamt: 282016.5 Wh ...
Berechnen, Loggen und Plotten von Strom- und Gasverbrauchswerten
Abweichend von der Verwendung als Ertragszähler hier ein Beispiel mit "userreadings":
Quellle für die userreadings und die Umrechnung: [1]
Eintrag für die benutzerdefinierten Readings und Attribute(CFG):
attr global userattr offset offsetA offsetB
Die Def. des Zählers mit der Berechnung der Verbrauchswerte (CFG):
define OW_Strom_Gas_Zaehler OWDevice 1D.58E50F000000 300 attr OW_Strom_Gas_Zaehler IODev myOWServer attr OW_Strom_Gas_Zaehler model DS2423 attr OW_Strom_Gas_Zaehler offsetA 25003.91 attr OW_Strom_Gas_Zaehler offsetB 12287.94 attr OW_Strom_Gas_Zaehler polls counters.A,counters.B attr OW_Strom_Gas_Zaehler room 1_Energie attr OW_Strom_Gas_Zaehler stateFormat { sprintf("%.3f kWh %.3f m³", ReadingsVal("OW_Strom_Gas_Zaehler","displayA","kWh"), ReadingsVal("OW_Strom_Gas_Zaehler","displayB","m³"));; } attr OW_Strom_Gas_Zaehler userReadings displayA {ReadingsVal("OW_Strom_Gas_Zaehler","counters.A",0)/75.0+AttrVal("OW_Strom_Gas_Zaehler","offsetA",0);;}, consumption_power differential {ReadingsVal("OW_Strom_Gas_Zaehler","counters.A",0)*3600*0.01333333333333;;}, daily_power {ReadingsVal("OW_Strom_Gas_Zaehler","displayA",0)-Value("Dum_DP_Diff_D");;}, displayB {ReadingsVal("OW_Strom_Gas_Zaehler","counters.B",0)/100.0+AttrVal("OW_Strom_Gas_Zaehler","offsetB",0);;}, consumption_gas differential {ReadingsVal("OW_Strom_Gas_Zaehler","counters.B",0)*3600*0.01;;}, daily_gas {ReadingsVal("OW_Strom_Gas_Zaehler","displayB",0)-Value("Dum_DG_Diff_D");;}
Hier müssen wie bereits oben beschrieben die Zählerkonstanten eingetragen werden - im Beispiel ist:
"counters.A" (Strom): "75.0" , was 75 Impulse für 1 KWH entspricht, "0.01333333333333" ist nichts weiter als "1 durch 75 (Imp.)"
"counters.B" (Gas) : "100.0" , was 100 Impulse für 1 m³ entspricht, "0.01" ist nichts weiter als "1 durch 100 (Imp.)"
Autom. Berechnung der Offset- Werte für die realen Zählerstände "DisplayA/B" (CFG):
Diese Funktion setzt automatisch die "Offset"- Werte, indem man den aktuellen Zählerstand
in das entspr. Eingabefeld einträgt und abschickt. Man hat dann mit der Anzeige des "Sollzählerstandes"
in FHEM einen Vergleichswert um zu prüfen, ob die Energiezählung richtig arbeitet oder ob es Ausfälle gab.
# Dummys zum Eingeben der realen Zählerstände: define Offset_A dummy attr Offset_A alias Berechn. Offset A attr Offset_A comment Eingabefeld für Zählerstand Strom zur Berechnung von "offsetA" attr Offset_A group OWDevice attr Offset_A room 1_Energie attr Offset_A setList Zählerstand_Strom:textField attr Offset_A webCmd Zählerstand_Strom define Offset_B dummy attr Offset_B alias Berechn. Offset B attr Offset_B comment Eingabefeld für Zählerstand Strom zur Berechnung von "offsetB" attr Offset_B group OWDevice attr Offset_B room 1_Energie attr Offset_B setList Zählerstand_Gas:textField attr Offset_B webCmd Zählerstand_Gas # Funktion zum Berechnen und Setzen der Offset- Werte: define Func_SetOffset_N notify Offset_A|Offset_B { \ \ my $aufrufer_SetOffset = "$NAME" ;;\ my $valueA = (ReadingsVal("OW_Strom_Gas_Zaehler","counters.A",0)/75.0);;\ my $valueB = (ReadingsVal("OW_Strom_Gas_Zaehler","counters.B",0)/100.0);;\ my $setoffsetA = (ReadingsVal("Offset_A","Zählerstand_Strom",0) - $valueA);;\ my $setoffsetB = (ReadingsVal("Offset_B","Zählerstand_Gas",0) - $valueB);;\ \ if ($aufrufer_SetOffset eq "Offset_A") { \ Log 1, "Setze OW_Strom_Gas_Zaehler.offsetA auf: $setoffsetA";; \ fhem("attr OW_Strom_Gas_Zaehler offsetA $setoffsetA");;\ fhem("set Offset_A $setoffsetA");;\ } \ if ($aufrufer_SetOffset eq "Offset_B") { \ Log 1, "Setze OW_Strom_Gas_Zaehler.offsetB auf: $setoffsetB";; \ fhem("attr OW_Strom_Gas_Zaehler offsetB $setoffsetB");;\ fhem("set Offset_B $setoffsetB");;\ }\ { \ fhem("save");;\ }\ } attr Func_SetOffset_N group Berechnungen attr Func_SetOffset_N room 1_Energie
Auch hier müssen die Zählerkonstanten noch einmal angepasst werden in den Variablen "$valueA" und "$valueB".
Dummies und Aufrufe zur Berechnung des Energieverbrauchs Tag / Monat (CFG):
# Dummys für Daily define Dum_DP_Diff_D dummy attr Dum_DP_Diff_D group Berechnungen attr Dum_DP_Diff_D room 1_Energie define Dum_DG_Diff_D dummy attr Dum_DG_Diff_D group Berechnungen attr Dum_DG_Diff_D room 1_Energie define Dum_Daily_Power_D dummy attr Dum_Daily_Power_D group Berechnungen attr Dum_Daily_Power_D room 1_Energie define Dum_Daily_Gas_D dummy attr Dum_Daily_Gas_D group Berechnungen attr Dum_Daily_Gas_D room 1_Energie # Dummys für Monthly define Dum_MP_Diff_D dummy attr Dum_MP_Diff_D group Berechnungen attr Dum_MP_Diff_D room 1_Energie define Dum_MG_Diff_D dummy attr Dum_MG_Diff_D group Berechnungen attr Dum_MG_Diff_D room 1_Energie define Dum_Monthly_Power_D dummy attr Dum_Monthly_Power_D group Berechnungen attr Dum_Monthly_Power_D room 1_Energie define Dum_Monthly_Gas_D dummy attr Dum_Monthly_Gas_D group Berechnungen attr Dum_Monthly_Gas_D room 1_Energie # Berechnung Energieverbrauch Tag gesamt define Func_Daily_Energy_A at *23:50:00 {prg_Daily_Stat()} attr Func_Daily_Energy_A group Berechnungen attr Func_Daily_Energy_A room 1_Energie # Berechnung Energieverbrauch Monat gesamt define Func_Monthly_Energy_A at *23:55:00 {prg_Monthly_Stat()} attr Func_Monthly_Energy_A group Berechnungen attr Func_Monthly_Energy_A room 1_Energie
Funktionen zum Berechnen und Loggen der Tages- und Monatswerte (99_myUtils.pm)
# Energie Tagesverbräuche berechnen ################################### sub prg_Daily_Stat() { # Zunächst Tagesverbrauch (aktueller Wert - Wert von gestern) berechnen $data{DPval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayA",0) - Value("Dum_DP_Diff_D"); $data{DGval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayB",0) - Value("Dum_DG_Diff_D"); # Tagesverbrauch in DailyPower speichern fhem ("set Dum_Daily_Power_D $data{DPval}"); fhem ("set Dum_Daily_Gas_D $data{DGval}"); # Aktuellen Wert in Daily_Power_Diff speichern (entspricht Zählerstand am Tagesende) $data{DPval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayA",0); $data{DGval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayB",0); fhem ("set Dum_DP_Diff_D $data{DPval}"); fhem ("set Dum_DG_Diff_D $data{DGval}"); fhem("save"); } # Energie Monatsverbräuche berechnen #################################### # Funktion wie bei Tagesverbrauch, wird jedoch nur zum Monats- und Jahresumbruch ausgeführt sub prg_Monthly_Stat() { my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime; my ($sec2,$min2,$hour2,$mday2,$month2,$year2,$wday2,$yday2,$isdst2) = localtime(time + (60*60*24) ); if ( $month2 > $month or $year2 > $year ){ $data{MPval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayA",0) - Value("Dum_MP_Diff_D"); $data{MGval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayB",0) - Value("Dum_MG_Diff_D"); fhem ("set Dum_Monthly_Power_D $data{MPval}"); fhem ("set Dum_Monthly_Gas_D $data{MGval}"); $data{MPval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayA",0); $data{MGval} = ReadingsVal("OW_Strom_Gas_Zaehler","displayB",0); fhem ("set Dum_MP_Diff_D $data{MPval}"); fhem ("set Dum_MG_Diff_D $data{MGval}"); fhem("save"); Log 1, "Monatsber. Energie erfolgt: Monatsumbruch $month2 > $month oder Jahresumbruch $year2 > $year erfüllt"; } }
Log- und Plotzuweisungen (CFG):
define Log_Energy_M FileLog ./log/OW_Strom_Gas_Zaehler-%Y-%m.log OW_Strom_Gas_Zaehler:counters.A.*|OW_Strom_Gas_Zaehler:counters.B.*|OW_Strom_Gas_Zaehler:displayA.*|OW_Strom_Gas_Zaehler:displayB.*|OW_Strom_Gas_Zaehler:consumption_power.*|OW_Strom_Gas_Zaehler:consumption_gas.*|OW_Strom_Gas_Zaehler:daily_power.*|OW_Strom_Gas_Zaehler:daily_gas.* attr Log_Energy_M logtype stromverbrauch:Strom,gasverbrauch:Gas,text:Text attr Log_Energy_M room 4_Logdaten define Log_Energy_Y FileLog ./log/OW_Strom_Gas_Zaehler-%Y.log Dum_DP_Diff_D.*|Dum_DG_Diff_D.*|Dum_Daily_Power_D.*|Dum_Daily_Gas_D.*|Dum_MP_Diff_D.*|Dum_MG_Diff_D.*|Dum_Monthly_Power_D.*|Dum_Monthly_Gas_D.* attr Log_Energy_Y logtype energie_monat:Monat,energie_jahr:Jahr,text:Text attr Log_Energy_Y room 4_Logdaten define 00_Stromverbrauch SVG Log_Energy_M:stromverbrauch:CURRENT attr 00_Stromverbrauch label "Min $data{min1}, Max $data{max1}, Last $data{currval1}" attr 00_Stromverbrauch room 1_Energie define 01_Gasverbrauch SVG Log_Energy_M:gasverbrauch:CURRENT attr 01_Gasverbrauch label "Min $data{min1}, Max $data{max1}, Last $data{currval1}" attr 01_Gasverbrauch room 1_Energie define 02_Energie_Monat SVG Log_Energy_Y:energie_monat:CURRENT attr 02_Energie_Monat fixedrange month attr 02_Energie_Monat label "Min $data{min1}, Max $data{max1}, Last $data{currval1}" attr 02_Energie_Monat room 1_Energie define 03_Energie_Jahr SVG Log_Energy_Y:energie_jahr:CURRENT attr 03_Energie_Jahr fixedrange year attr 03_Energie_Jahr label "Min $data{min1}, Max $data{max1}, Last $data{currval1}" attr 03_Energie_Jahr room 1_Energie
Plotfile Strom (stromverbrauch.gplot):
# Created by FHEMWEB, 2013-05-16 21:45:00 set terminal png transparent size <SIZE> crop set output '<OUT>.png' set xdata time set timefmt "%Y-%m-%d_%H:%M:%S" set xlabel " " set title 'Stromverbrauch Tag' set ytics set y2tics set grid ytics set ylabel "Momentan kW" set y2label "Summiert kWh" #FileLog 4:OW_Strom_Gas_Zaehler.consumption_power\x3a:: #FileLog 4:OW_Strom_Gas_Zaehler.daily_power\x3a::
Plotfile Gas (gasverbrauch.gplot):
# Created by FHEMWEB, 2013-05-16 21:45:00 set terminal png transparent size <SIZE> crop set output '<OUT>.png' set xdata time set timefmt "%Y-%m-%d_%H:%M:%S" set xlabel " " set title 'Gasverbrauch Tag' set ytics set y2tics set grid ytics set ylabel "Momentan m³" set y2label "Summiert m³" #FileLog 4:OW_Strom_Gas_Zaehler.consumption_gas\x3a:: #FileLog 4:OW_Strom_Gas_Zaehler.daily_gas\x3a:: plot "<IN>" using 1:2 axes x1y1 title 'Momentan m³' ls l2fill lw 0.5 with lines,\ "<IN>" using 1:2 axes x1y2 title 'Summiert m³' ls l1 lw 1 with lines,\
Plotfile Monat (energie_monat.gplot):
# Created by FHEM/98_SVG.pm, 2014-02-15 18:52:52 set terminal png transparent size <SIZE> crop set output '<OUT>.png' set xdata time set timefmt "%Y-%m-%d_%H:%M:%S" set xlabel " " set title 'Energieverbrauch Monat' set ytics set y2tics nomirror set grid ytics set ylabel "Stromverbrauch kWh" set y2label "Gasverbrauch m³" #FileLog 3:Dum_Daily_Power_D.*:0: #FileLog 3:Dum_Daily_Gas_D.*:0: #FileLog 3:Dum_Daily_Gas_D.*:0: plot "<IN>" using 1:2 axes x1y1 title 'Strom kWh' ls l3fill lw 1 with bars,\ "<IN>" using 1:2 axes x1y2 title 'Gas m³' ls l2fill lw 1 with points "<IN>" using 1:2 axes x1y2 title '' ls l2 lw 1 with lines
Plotfile Jahr (energie_jahr.gplot):
# Created by FHEM/98_SVG.pm, 2014-05-02 10:04:37 set terminal png transparent size <SIZE> crop set output '<OUT>.png' set xdata time set timefmt "%Y-%m-%d_%H:%M:%S" set xlabel " " set title 'Energieverbrauch Jahr' set ytics set y2tics nomirror set grid ytics set ylabel "Stromverbrauch kWh" set y2label "Gasverbrauch m³" #FileLog 3:Dum_Monthly_Power_D.*:0: #FileLog 3:Dum_Monthly_Gas_D.*:0: plot "<IN>" using 1:2 axes x1y1 title 'Strom kWh' ls l3fill lw 1 with bars,\ "<IN>" using 1:2 axes x1y2 title 'Gas m³' ls l2fill lw 1 with fsteps