Stromverbrauch der letzten Stunde überwachen, Abschalten bei zuviel Strom

Begonnen von Torxgewinde, 25 August 2016, 21:48:08

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Ein Hauswasserwerk soll überwacht werden. Es soll verhindert werden, dass es zu einer Überschwemmung kommt. Bei Wasser an einem Sensor am Boden oder Dauerlauf des Wasserwerkes wird das Wasserwerk abgeschaltet und es werden E-Mails versendet. Das Wasserwerk kann entweder an einem lokalem Schalter an der Funksteckdose oder per Weboberfläche wieder eingeschaltet werden.

In dieser Beschreibung werden:

  • viele Readings aus einem PERL Codeblock angelegt und ausgewertet. Mit dieser Methode sind auch generell anderen Berechnungen einfach, zum Beispiel sowas wie gleitende Mittelwertsberechnungen ohne LogFiles, ohne Annäherungsformel und mit Speicherung der Werte über einen FHEM Neustart hinweg.
  • DevStateIcons mit angepassten regulären Ausdrücken zur intuitiveren Darstellung genutzt. Auch sind Klicks aus die DevStateIcons genutzt um Befehle auszuführen.
  • userReadings genutzt um den State der definierten Module zu überschreiben.

Die möglichen, hiermit abgedeckten Fehler sind:

  • Leckage am Aufstellort (=Garage)
  • Einer der außenliegenden Wasserhähne im Garten ist geöffnet oder Leck

Hardware:

  • FHEM installiert auf einem Raspberry Pi 3 (in einem angemessen dichtem Gehäuse).
  • Wassersensor an GPIO des RPi angebunden
  • AVM Fritzbox + DECT 200 Funksteckdose
  • Hauswasserwerk

Zu Fehler #1:
Der Raspberry Pi wird ohnehin in der Nähe des Wasserwerkes aufgestellt. Ein Wassersensor ist in diesem Fall direkt über ein Kabel an die GPIOs des RPi angeschlossen. Wie der Wassersensor funktioniert und aussieht ist ein einem separatem Posting beschrieben. Letztendlich wird der Widerstand des Sensors mit einem Python-Skript ermittelt und an FHEM via HTTP-GET weitergeleitet. Es wird nur ein Zahlwert an FHEM übermittelt, die Skalierung auf 0 bis 100% Feuchte und Entscheidung ob der Zahlwert nun Wasser oder Trockenheit signalisiert wird in FHEM vorgenommen.

In FHEM wird für den Wassersensor ein ,,dummy" angelegt, das Reading ,,raw" wird via HTTP aus dem Python Skript heraus aktualisiert. Die URL für diese Aufruf kann man mit einem Browser (=IE, Firefox, Chrome, Safari, ...) testen und bei dem ,,dummy"-device prüfen ob der Zahlwert übernommen wird. Den Zeitstempel setzt FHEM automatisch.

define Wassersensor dummy
attr Wassersensor alias Wassersensor
attr Wassersensor devStateIcon Trocken:10px-kreis-gruen Wasser:10px-kreis-rot
attr Wassersensor event-on-change-reading state
attr Wassersensor group Wassersensor
attr Wassersensor icon humidity
attr Wassersensor room Garage
attr Wassersensor userReadings percent {\
use Math::Round;;\
my $raw = ReadingsVal("Wassersensor","raw",-1);;;;\
my $max = 1550000;;;;\
my $min = 500;;;;\
100 - round((($raw-$min)/($max-$min))*100);;;;\
},\
state {\
if (ReadingsVal("Wassersensor","percent",-1) > 95) {"Wasser"} else {"Trocken"}\
}


Ok, das Reading ,,raw" spiegelt also den Zahlwert des Sensors wieder.
Es gilt zu berechnen ob der Sensor 100% feucht oder 0% feucht ist und es soll mittels Schwellwert entschieden werden ob das nun ,,Wasser" oder ,,Trocken" bedeutet. Hier wird das hier über ,,userReading" realisiert. Da der Prozentwert gerundet wird, muss eventuell auch noch die passende Perl Library installiert werden. Das geht mit folgenden Shell-Befehl:

# sudo apt-get install libmath-round-perl

Die Werte für maximalen und minimalen Zählerstand (=roher Sensorwert) sind experimentell ermittelt, indem einmal der Sensor ganz trocken ist (=$max Zahlwert) und einmal ganz nass ($min Zahlwert). Die Wert hängen von dem verwendeten Rechner und der verfügbaren Rechenzeit für das Python Skript ab (heißt die Auslastung des Rechners sollte besser nicht über 100% gehen). Den Schwellwert habe ich auch experimentell ermittelt und bei meinem Sensor auf 95% festgelegt. Wird mehr als 95% Feuchte gemeldet, wird das Reading ,,state" des dummy-devices auf ,,Wasser" gesetzt, ist der Sensorwert geringer als 95%, signalisiert der Zustand des dummy-devices ,,Trocken".

Alternativ zur Nutzung der GPIOs könnte man einen ESP8266 nutzen und dieser könnte auch die gleiche HTTP-GET Methode zum Setzen des Sensorwertes über das Netzwerk hinweg nutzen. Beim ESP8266 hätte man einen echten ADC um den Messwert aufzunehmen. Da aber viele Wege nach Rom führen, geht's erstmal weiter mit Diesem.

Jetzt weiß man in FHEM ob der Sensor ,,Trocken" oder ,,Wasser" ist. Immer wenn der Sensor ,,Wasser" meldet, soll das Hauswasserwerk abgeschaltet werden. Dem Hauswasserwerk den Strom abzudrehen ist ein sicherer Zustand, da dann keine weitere Menge Wasser mehr gefördert wird.

Das Schalten des Hauswasserwerkes erfordert einen Schalter in der Zuleitung. Als Aktor fungiert in diesem Fall ein AVM DECT 200. Wichtig ist es die maximale Strombelastbarkeit des Aktors im Auge zu behalten. Bei mehr als 10A Strom sollte man einen anderen Aktor nutzen da der DECT 200 nur für maximal 10A ausgelegt ist (=maximal 2300W Leistung, die meisten Hauswasserwerke sollten damit funktionieren). Wie man den DECT 200 in FHEM einrichtet beschreibe ich nicht näher.

Der Aktor repräsentiert für FHEM das Hauswasserwerk, deswegen heißt der Aktor in FHEM ,,Hauswasserwerk". Das Hauswasserwerk kann man über FHEM ein- und ausschalten aber auch direkt am Zwischenstecker mit einem kleinem Taster (Nützlich um nach einem Abschalten auch ohne PC wieder Wasser einzuschalten). Wenn am Zwischenstecker geschaltet wird, dauert es ein paar Minuten bis sich dieser Zustand in FHEM widerspiegelt, die Funktion bleibt mit der Lösung erhalten. Generell eignen sich aber auch alle anderen Zwischenstecker.

define FritzBoxHAServer FBAHAHTTP fritz.box
attr FritzBoxHAServer fritzbox-user root
attr FritzBoxHAServer group Fritzbox
attr FritzBoxHAServer icon it_router
attr FritzBoxHAServer polltime 60

define Wasserwerk FBDECT FritzBoxHAServer:XXXXXXXX powerMeter,tempSensor,switch
attr Wasserwerk IODev FritzBoxHAServer
attr Wasserwerk devStateIcon on:general_an@green:off off:general_aus@red:on
attr Wasserwerk event-min-interval power:120
attr Wasserwerk event-on-change-reading state
attr Wasserwerk group Wasserwerk
attr Wasserwerk icon sani_domestic_waterworks
attr Wasserwerk room FBDECT,Garage
attr Wasserwerk sortby 100
attr Wasserwerk webCmd :


Die Reaktionen auf das Event ,,Wasser" vom Gerät ,,Wassersensor" ist mit zwei ,,Notifies" gelöst. Ein ,,Notify" versendet eine E-Mail, das andere Notify schaltet das Wasserwerk ab.
Abweichend zu den häufig zu findenden Lösungen ist bei den Notifies bereits über den regulären Ausdruck ,,Wassersensor:Wasser" passend abgeprüft ob der Sensorwert ,,Wasser" ist. Die häufigere Variante prüft in dem Codeblock ob das $EVENT einem bestimmten Wert entspricht, was aber durch den regulären Ausdruck erledigt werden kann.

define WasserPerEMailMelden notify Wassersensor:Wasser {\
my $raw = ReadingsVal("Wassersensor","raw",-1);;;;\
my $per = ReadingsVal("Wassersensor","percent",-1);;;;\
email('torxgewinde@domain.tld', "Wasser", "Der Wassersensor meldet Wasser ($per %, #$raw)");;;;\
\
fhem('set WasserPerEMailMelden inactive');;;;\
}
attr WasserPerEMailMelden alias Wasser per Email melden
attr WasserPerEMailMelden disable 0
attr WasserPerEMailMelden group Wasserwerk
attr WasserPerEMailMelden icon message_mail
attr WasserPerEMailMelden room Garage
attr WasserPerEMailMelden sortby 400


define WasserwerkAbschalten notify Wassersensor:Wasser|WasserwerkBetriebsdauer:PumpStop:.Yes.* {\
  fhem("define tmp1 at +00:00:02 set WasserwerkBetriebsdauer reset");;;;\
  fhem("set WasserwerkEinschalten inactive");;;;\
  fhem("set Wasserwerk off");;;;\
  #fhem("sleep 9;;;; set WasserwerkEinschalten active");;;;\
  fhem("define tmp2 at +00:00:02 set WasserwerkEinschalten active");;;;\
}
attr WasserwerkAbschalten alias Wasserwerk abschalten
attr WasserwerkAbschalten disable 0
attr WasserwerkAbschalten group Wasserwerk
attr WasserwerkAbschalten icon hue_room_garage
attr WasserwerkAbschalten room Garage
attr WasserwerkAbschalten sortby 300


Bei dem Notify zum Abschalten wird auf die beiden oben angeführten Fehler das Hauswasserwerk abgeschaltet. Deswegen gibt es hier zwei reguläre Ausdrücke auf die hin der Codeblock ausgeführt wird.

Zu Fehler #2:
Wie gerade beschrieben, wird auch auf diesen Fehler hin das Wasserwerk abgeschaltet. Das besonderen an dieser Fehlerabschaltung ist, dass die Verbrauchswerte des Zwischensteckers als Abschaltkriterium genutzt werden.
Als Regel formuliert realisieren die folgenden Konfigurationen: ,,Wenn der Stromverbrauch in der letzten Stunde größer als XX Wh wird, dann schalte ab".

Einfach nur auf die Schaltevents oder aktuellen Verbrauchspegel (=Watt) des Zwischensteckers zu reagieren geht nicht, da das Hauswasserwerk seinen eigenen Druckschalter hat, eventuell mal kurz nachfördert und dann kurz abschaltet und dann wieder einschaltet.

Damit herausgefunden werden kann wieviel Strom in der letzten Stunde verbraucht wurde und auch mit einer feinen Auflösung im minutenmaßstab reagiert werden kann, werden die Verbrauchswerte jede Minute mit einem ,,at"-device aufgenommen, in eine Historie gespeichert und zu alte Werte verworfen. Da FHEM Arrays und dergleichen nicht in der Webdarstellung auflistet, sind diese vielen Messwerte automatisch erzeugt und als Readings sichtbar. So kann man auch optisch gut erkennen, welche Werte in die Berechnung einfließen und man verliert die Werte nicht wenn FHEM mal kurz gestoppt und wieder gestartet wird. Die Variablen werden aufgrund ihrer großen Zahl mit PERL Befehlen im Codeblock erzeugt.

define WasserwerkBetriebsdauer at +*00:01:00 { \
  my $hash = $main::defs{WasserwerkBetriebsdauer};;;;\
  my $val=0;;;;\
  \
  #Wie lang ist die History?\
  my $length = ReadingsNum('WasserwerkBetriebsdauer', "histLength", 10);;;;\
\
  # schiebe alle History Werte...\
  for (my $i=($length-1);; $i>0;; $i--) {\
    my $j = $i+1;;;;\
$val = ReadingsNum('WasserwerkBetriebsdauer', "hist".sprintf("%02d",$i), 0);;;;\
    setReadingsVal($hash, "hist".sprintf("%02d",$j), $val, $hash->{READINGS}{"hist".sprintf("%02d",$i)}{TIME});;;;\
  }\
  \
  # lese aktuellen Wert aus, schreibe ihn an die erste Stelle\
  $val = ReadingsNum('Wasserwerk', 'energy', 0);;;;\
  setReadingsVal($hash, "hist".sprintf("%02d",1), $val, TimeNow());;;;\
  \
  # Merkwuerdigerweise wurde immer ein unerklaerlicher Eintrag ins Log geschrieben, wenn dieser Log Befehl nicht hier steht...\
  Log 5, "Print $val";;;;\
}
attr WasserwerkBetriebsdauer alias Betriebsdauer
attr WasserwerkBetriebsdauer devStateIcon NOK.*:10px-kreis-rot:reset OK.*:10px-kreis-gruen
attr WasserwerkBetriebsdauer group Wasserwerk
attr WasserwerkBetriebsdauer icon edit_settings
attr WasserwerkBetriebsdauer room Garage
attr WasserwerkBetriebsdauer sortby 200
attr WasserwerkBetriebsdauer userReadings energy {\
  # Dies ist die im Zeitraum "Interval * histLength" real aufgenommene Energiemenge in Wh\
  my $length = ReadingsNum('WasserwerkBetriebsdauer', "histLength", 10);;;;\
  ReadingsNum("WasserwerkBetriebsdauer", "hist".sprintf("%02d",1), 0)\
    - ReadingsNum("WasserwerkBetriebsdauer", "hist".sprintf("%02d",$length), 0);;;;\
},\
\
\
percent {\
  use Math::Round;;;;\
\
  # Erlaubte Energie die im Zeitraum verbraucht werden darf in Wh\
  my $max = ReadingsNum("WasserwerkBetriebsdauer", "maxEnergy", 100);;;;\
  my $energy = ReadingsNum("WasserwerkBetriebsdauer", "energy", 0);;;;\
  \
  # Ergbnis kann auch groesser 100% werden, das ist aber OK und schaltet dann die Pumpe ab\
  round(($energy / $max)*100);;;;\
},\
\
\
PumpStop {\
  # Ist die Pumpe mehr als die erlaubten 100% an?\
  if (ReadingsNum("WasserwerkBetriebsdauer", "percent", 0) >= 100) {\
"Yes" }\
  else { \
"No";;;;\
  }\
},\
\
state {\
  my $percent = ReadingsNum("WasserwerkBetriebsdauer", "percent", 0);;;;\
  my $PumpStop = ReadingsVal("WasserwerkBetriebsdauer", "PumpStop", "unknown");;;;\
  \
  if ("$PumpStop" eq "Yes") {\
    "NOK ($percent%)";;;;\
  } else {\
    "OK ($percent%)";;;;\
  }\
}\


Wieviele Werte man aus der Vergangenheit berücksichtigen will, parametriert man mit dem Reading ,,histLength" und der Zeitangabe im ,,at"-device. Ändert man den Wert histLength, so kann man die Zahl der Messwerte verändern und damit den Zeitraum verlängern oder verkürzen. Eigentlich würde es Sinn ergeben diesen Wert (histLength) nicht als Readings, sondern als Attribut zu realisieren, aber das habe ich so erstmal nicht realisiert. Auch die Zeitangabe im ,,at" liegt bei einer Minute ganz gut und belasse ich so, dann ist es recht einfach und histLength ist identisch zum überwachten Zeitraum in Minuten.

Die Überwachung des Schwellwertes ist wieder in einem Codeblock realisiert. Auch hier werden mehrere Readings der Reihe nach berechnet und aktualisiert. Teilweise bauen die Werte aufeinander auf, deswegen ist die Reihenfolge in dem Codeblock wichtig. Zuerst wird die Berechnet wieviel Energie in dem Überwachten Zeitraum verbraucht wurde. Dazu wird der Wert von vor einer Stunde (=hier identisch des Überwachten Zeitraumes) vom letzten Verbrauchswert abgezogen, schon hat man die in der letzten Stunde verbrauchte energie berechnet. Der Wert wird als Reading ,,energy" dargestellt.

Die erlaubte Energiemenge in Wh gibt man in dem Reading ,,maxEnergy" an. Wieder könnte man diesen Wert auch bei den Attributen unterbringen, aber ich hatte mich mit den Readings besser ausgekannt. Vergleicht man die real Verbrauchte Energiemenge mit der erlaubten, kann man einen prozentualen Wert errechnen. Der Wert kann überschritten werden, dementsprechend können auch mehr als 100% errechnet werden. Die Berechnung wird im Reading ,,percent" gespeichert.

Die Schwellertüberwachung ist mit dem userReading ,,PumpStop" realisiert. Dort wird gerpüft, ob bereits mehr als die erlaubten ,,maxEnergy" Energiemenge angegeben wurde. ,,PumpStop" wechselt zwischen ,,Yes" oder ,,No", ,,Yes" falls die Pumpe stoppen soll.

Das UserReadings ,,state" überschreibt das eigentlich bereits vorhandene ,,Internal". So kann man das Icon in der Webdarstellung schön auswählen (dort wird mittels einer Regex entschieden welches icon eingeblendet werden soll) und auch ist in Klammern dem String der prozentuale Wert angefügt. Ist man mit einer Endgerät mit Mauszeiger unterwegs, wird der String ,,STATE" als Tooltip eingeblendet. Da der Vorgang sehr langsam ist, fand ich den Tooltip interessant als Information, das wichtigste ist allerdings das DevStateIcon, da man es schnell als OK, oder nicht OK wahrnehmen kann.

Schaltet das DevStateIcon auf ,,NOK (irgendwas%)", wird das Icon in der Webdarstellung von Grün auf Rot geschaltet. Zudem ist bei dem roten Icon ein Befehl hinterlegt, der den Sensor zurücksetzt. Das Rücksetzen wird über einen cmdalias abgefangen und in einen Befehl zum Rücksetzten umgesetzt.

Wird erkannt, dass ,,PumpStop" ein Abschalten des Hauswasserwerkes auslösen soll, wird der reguläre Ausdruck in dem Notify ,,WasserwerkAbschalten" zu dem Event passen und damit der zugehörige Befehl ausgeführt, die Pumpe schaltet daraufhin ab.

Zudem wird ein weiteres ,,Notify" getriggert, nämlich das Notify ,,DauerAnPerEMailMelden". Dort wird eine E-Mail versendet. Da eventuell weitere Male das Event ausgelöst wird, schaltet sich das ,,notify" selbst auf inaktiv und reagiert nicht auf weitere Events der gleichen Art.

define DauerAnPerEMailMelden notify WasserwerkBetriebsdauer:PumpStop:.Yes.* {\
  email('torxgewinde@domain.tld', "Pumpe zu lange an", "Die Pumpe läuft zu lange am Stück!");;;;\
  # sperre gegen weitere E-Mails\
  fhem('set DauerAnPerEMailMelden inactive');;;;\
}
attr DauerAnPerEMailMelden alias Dauerlauf per Email melden
attr DauerAnPerEMailMelden group Wasserwerk
attr DauerAnPerEMailMelden icon message_mail
attr DauerAnPerEMailMelden room Garage
attr DauerAnPerEMailMelden sortby 500


Wiedereinschalten:
Ist das Hauswasserwerk aus, so will man es beizeiten wieder einschalten. Entweder über die FHEM-Weboberfläche oder mit dem am Zwischenstecker vorhandenem Schalter. Der Wechsel nach ON wird genutzt um die Alarmmeldungen und die Sensorwerte wieder aktiv zu schalten. Der Nutzer hat entschieden, dass ein Wiedereinschalten OK und gewünscht ist. In dem Notify werden die E-Mail Meldungen wieder aktiv geschaltet und der Stromverbrauch wird auf 0 Wh gesetzt indem die gesamten Werte aus dem überwachten Zeitraum auf den aktuellen Verbrauchswert gesetzt werden.

define WasserwerkEinschalten notify Wasserwerk:on {\
  # Was muss alles passieren, wenn der Strom zum Wasserwerk wieder eingeschaltet wird?\
  \
  # Scheint ja alles wieder gut zu sein, der Betriebsdauerzähler kann auf 0 gesetzt werden\
  fhem("set WasserwerkBetriebsdauer reset");;;;\
  \
  # Alle Warnmelder sind wieder zu aktivieren\
  fhem("set WasserwerkAbschalten active");;;;\
  fhem("set WasserPerEMailMelden active");;;;\
  fhem("set DauerAnPerEMailMelden active");;;;\
}
attr WasserwerkEinschalten alias Wasserwerk wird eingeschaltet
attr WasserwerkEinschalten disable 0
attr WasserwerkEinschalten group Wasserwerk
attr WasserwerkEinschalten icon hue_room_garage
attr WasserwerkEinschalten room Garage
attr WasserwerkEinschalten sortby 290


Da diese Funktion zum Zurücksetzen des Stromverbrauchszählers öfters genutzt werden kann, ist der Befehl mit einem ,,cmdalias" hinzudefiniert worden. Realisiert ist das mit ,,WasserwerkBetriebsdauerReset", so dass nach definieren dieses alias ein Rücksetzten mittels ,,set WasserwerkBetriebsdauer reset" möglich ist.

define WasserwerkBetriebsdauerReset cmdalias set WasserwerkBetriebsdauer reset AS {\
  my $hash = $main::defs{WasserwerkBetriebsdauer};;;;\
  my $length = ReadingsNum('WasserwerkBetriebsdauer', "histLength", 10);;;;\
  my $val = ReadingsNum('Wasserwerk', "energy", 0);;;;\
\
  # frischen Wert ueber alle Einträge schreiben\
  for (my $i=1;; $i<=$length;; $i++) {\
    setReadingsVal($hash, "hist".sprintf("%02d",$i), $val, TimeNow());;;;\
  }\
  \
  # und wieder aktivieren, falls es deaktiviert war\
  fhem("set WasserwerkBetriebsdauer active")\
}
attr WasserwerkBetriebsdauerReset group Wasserwerk


Zeitraum für automatische Bewässerung berücksichtigen:
Zu einer bestimmten Zeit an jedem Tag, ist das Wasserwerk gewollt für eine Weile an. Es wird in diesem Zeitraum der Garten bewässert. In diesem Zeitraum ist eine Überwachung der Betriebsdauer nicht gewünscht. Um diese Überwachung gezielt zu stoppen, wird mit einem ,,at", das nur für einen bestimmten Zeitraum am Tag aktiv ist, immer wieder der Betriebsdauerzähler zurückgesetzt.

define BetriebsdauerIgnorieren at +*00:01:00 set WasserwerkBetriebsdauer reset
attr BetriebsdauerIgnorieren alias Betriebsdauer ignorieren
attr BetriebsdauerIgnorieren devStateIcon inactive:general_aus_fuer_zeit@grey:active disabled:general_aus_fuer_zeit@green:active Next.*:general_an_fuer_zeit@orange:inactive
attr BetriebsdauerIgnorieren disabledForIntervals 00:00-20:00, 22:00-24:00
attr BetriebsdauerIgnorieren group Wasserwerk
attr BetriebsdauerIgnorieren icon hourglass
attr BetriebsdauerIgnorieren room Garage
attr BetriebsdauerIgnorieren sortby 210


Mittels der Angaben passender ,,groups" sind die Befehle in der Weboberfläche einigermaßen sortiert und vor allem verschwinden mittels weiterer ,,alias" Angaben die kryptischen Namen aus der Darstellung. Diese kosmetischen Schritte gestalten die Darstellungen deutlich hübscher