-
Notifications
You must be signed in to change notification settings - Fork 0
S0 Zähler mit ConfigurableFirmata
Dieser Artikel beschreibt die Einbindung eines S0-Zählers in FHEM über das Modul FRM_IN
.
- Zähler mit S0-Ausgang
- Arduino mit ConfigurableFirmata oder RCSwitchFirmata
- Host-Rechner mit aktueller FHEM-Installation inkl. dem Modul FRM_IN
-
S0+
am Zähler wird mit einem digitalen Pin des Arduino verbunden. -
S0-
am Zähler wird mit Masse (GND
) am Arduino verbunden. - Der Arduino wird mit dem Host-Rechner verbunden.
- ConfigurableFirmata oder RCSwitchFirmata in der Arduino IDE öffnen
- Firmata-Features
DigitalInputFirmata
undFirmataExt
einschalten:
#include <utility/DigitalInputFirmata.h>
DigitalInputFirmata digitalInput;
#include <utility/FirmataExt.h>
FirmataExt firmataExt;
- Sketch auf den Arduino hochladen
Definition eines Gerätes counter
, das die Signale des Stromzählers zählt und eines Logs log_counter
, das den Stromverbrauch in einem Log speichert:
define log_counter FileLog %L/counter_%Y-%m.log counter.*power:.*
define firmata FRM /dev/ttyUSB0@57600
define counter FRM_IN 11
attr counter internal-pullup on
attr counter count-mode falling
attr counter event-min-interval power:1
attr counter oldreadings time
attr counter stateFormat power
attr counter userReadings\
time:reading:.off { Time::HiRes::time() },\
power:time:.+ { sprintf("%.2f W", 3600 / (ReadingsVal('counter', 'time', undef) - OldReadingsVal('counter', 'time', undef))) }
Dieses Attribut ist notwendig, wenn Zähler und Arduino wie oben beschrieben verbunden werden. Andernfalls ist das Signal hochohmig ("floating") und kann willkürlich zwischen LOW
und HIGH
hin- und herspringen.
Dieses Attribut begrenzt den Zähler auf 1 Zählvorgang pro Sekunde. Das entspricht bei 1 Signal pro 1 Wh einem Verbrauch von 3,6 kW. Dadurch können ungewollte Fehlmessungen verhindert werden.
Das userReading time
reagiert auf FHEM-Benachrichtigungen, die auf den regulären Ausdruck reading:.off
passen (.
steht für 1 beliebiges Zeichen, also z.B. auch 1 Leerzeichen). Diese Benachrichtigungen werden vom Firmata-Modul versendet, sobald vom Arduino ein LOW
-Signal (0 V) eintrifft. HIGH
-Signale hingegen werden ignoriert. Im Sketch ConfigurableFirmata
wird ein LOW
immer nur gesendet, wenn der vorige Wert HIGH
war, das entspricht also einer fallenden Flanke.
Time::HiRes::time()
ruft den aktuellen Zeitstempel Mikrosekunden-genau vom System ab. Dieser Wert wird im Reading gespeichert.
Die Zeile attr counter oldreadings time
bewirkt, dass für time
nicht nur auf den aktuellen Wert, sondern auch auf den vorigen Wert zugegriffen werden kann.
Das userReading power
reagiert auf Änderungen von time
. Der Wert für power
wird wie folgt berechnet:
-
OldReadingsVal('counter', 'time', undef)
: den vorigen Wert des userReadingstime
laden, das ergibt also den Zeitstempel der letzten fallenden Flanke. - Den aktuellen Wert des userReadings
time
laden und die Differenz bilden; damit haben wir die Dauer zwischen zwei fallenden Flanken in Sekunden, z.B.2.345
- Berechnung des Stromverbrauchs. Der Stromzähler gibt 1 Signal pro Wh ab, also 1000 Signale pro kWh; eine Stunde hat 3600 Sekunden. Aus der gemessenen Dauer (x Sekunden) berechne ich die momentane Leistung
p
in W:
p W = 1 Wh / x s
<=> p W = 3600 Wh / x * 3600s
<=> p W = 3,6 kWh / x * 1h
<=> p W = 3,6 kW / x
<=> p W = 3600 W / x
<=> p = 3600 / x
-
sprintf("%.2f",
...: Das Ergebnis wird bei 2 Nachkommastellen abgeschnitten und in einen String umgewandelt. -
" W"
: Einheit ergänzen (falls man später vielleicht mal auf kW umstellt)
Definition eines Diagramms unter Verwendung der Datei www/gplot/my_leistung_energie.gplot
(siehe unten):
define counter_diagram weblink fileplot log_counter:my_leistung_energie:CURRENT
attr counter_diagram title "Stromverbrauch"
attr counter_diagram label "Leistung (W)"::"Energie (Wh)"
attr counter_diagram plotfunction 4:counter.power::$fld[3]>3600?0:$fld[3] 4:counter.power::$cnt[0]
Das erste Argument 4:counter.power::$fld[3]>3600?0:$fld[3]
beschreibt die Darstellung der Leistung. Ausgewertet wird der Wert in Spalte 4
des Logfiles in allen Zeilen, die den regulären Ausdruck counter.power
erfüllen. Dargestellt wird dieser Wert ($fld[3]
), sofern er höchstens 3600 ist, andernfalls 0.
Das zweite Argument 4:counter.power::$cnt[0]
beschreibt die Darstellung der Energie. Der dargestellte Wert ist hier nicht der Wert aus dem Log, sondern die Summe der Vorkommnisse von Zeilen mit counter.power
. Da der Stromzähler pro verbrauchter Wattstunde ein Signal abgibt, entspricht jede Zeile im Log einer Wattstunde. Die verbraucht Energie entspricht also der Summe aller Zeilen.
Achtung: Ein Argument von plotfunction
darf keine Leerzeichen enthalten!
Dieser Artikel basiert auf einem Stromzähler, der 1 Signal pro Wattstunde abgibt. Alternativ können
- Stromzähler mit anderen Frequenzen, oder
- andere Zähler, z.B. für Gas oder Wasser eingesetzt werden.
Anstelle eines Arduinos ist ebenso ein Raspberry Pi mit dem Modul RPI_GPIO denkbar.
Vorteile:
- FHEM kann auch auf dem Raspberry Pi laufen
- Alle Pins unterstützen Interrupts
Nachteile:
- Je nach Auslastung reagiert ein Raspberry Pi nicht sofort auf Signale, sodass die Zeitstempel verfälscht sein können
Beispiel-Konfiguration:
define counter RPI_GPIO 26
attr counter direction input
attr counter pud_resistor up
attr counter interrupt falling
attr counter event-min-interval power:1
attr counter stateFormat time
attr counter userReadings\
time:reading:.off { Time::HiRes::time() },\
power:time:.+ { sprintf("%.2f W", 3600 / (ReadingsVal('counter', 'time', undef) - OldReadingsVal('counter', 'time', undef))) }
Es kann passieren, dass ein FHEM-Modul die Messung eines S0-Signals verzögert. Es erscheint dann später im Log, und Berechnungen, die auf dem Zeitstempel basieren (z.B. die Ermittlung des Stroms (W) aus der Energie (Wh)) werden verfälscht. Ein Extrembeispiel ist ein Backup von FHEM im laufenden Betrieb. Eine Fritz!Box 7390 hat in einem Test dafür ca. 20 Minuten gebraucht. Einige S0-Signale wurden gepuffert und nach 20 Minuten verarbeitet (mit den verspäteten Zeitstempeln). Die meisten Signale sind aber verloren gegangen. Auch das Zeichnen von Diagrammen kann eine Verzögerung von einigen Sekunden bedeuten.
- USB: Ist das Kabel zwischen Arduino und Host-Rechner zu lang und/oder nicht ausreichend geschirmt, kann es zu Verbindungsabbrüchen kommen.
- Ethernet: Durch die Ethernet-Unterstützung wird der Arduino-Sketch leicht sehr groß. Wenn zu wenig freier Speicher zur Verfügung steht, kann der Arduino sich instabil verhalten. Zu beachten ist, dass sowohl IP- als auch MAC-Adresse korrekt eingetragen sind und die Verbindung zwischen Arudino und Host-Rechner nicht durch eine Firewall o.ä. verhindert wird. Befindet sich ein Router zwischen den Geräten, kann ein Portforwarding-Eintrag notwendig sein.
- Ursprung dieses Artikels: Wie kann ich einige S0-Zähler mit fhem auf einer Fritz!Box 7390 auslesen?
Die folgende Konfiguration beschreibt Auslesen, Darstellung und Überwachung von 12 Stromzählern, die über einen Arduino Mega2560 an einer Fritz!Box 7390 angeschlossen sind.
##
# Firmata Basis-Modul für die Kommunikation mit dem Arduino
##
define firmata FRM /dev/ttyACM0@57600
attr firmata room Zähler
##
# 1 Log pro Tag, für alle Zähler zusammen
##
define log_zaehler FileLog %L/zaehler_%Y-%m-%d.log counter_.*power:.*
attr log_zaehler room Zähler
##
# 1 Diagramm je Zähler und 1 Diagramm für den gesamten Haushalt
##
define erzeugung_zaehler notify erzeugung_zaehler {\
createCounter("kueche", 23, "Küche", "log_zaehler", "Bereiche", "02");;\
createCounter("og", 25, "Obergeschoss", "log_zaehler", "Bereiche", "04");;\
createCounter("eg", 27, "Erdgeschoss", "log_zaehler", "Bereiche", "01");;\
createCounter("dg", 29, "Dachgeschoss", "log_zaehler", "Bereiche", "05");;\
createCounter("ug", 31, "Keller", "log_zaehler", "Bereiche", "06");;\
createCounter("heizung", 33, "Heizung", "log_zaehler", "Geräte", "11");;\
createCounter("waschen", 35, "Waschmaschine", "log_zaehler", "Geräte", "09");;\
createCounter("trocknen", 37, "Trockner", "log_zaehler", "Geräte", "10");;\
createCounter("haustechnik", 39, "Haustechnik", "log_zaehler", "Geräte", "12");;\
createCounter("garten", 41, "Garten", "log_zaehler", "Bereiche", "08");;\
createCounter("spuelen", 43, "Spülmaschine", "log_zaehler", "Geräte", "03");;\
createCounter("bad", 45, "Bäder", "log_zaehler", "Bereiche", "07");;\
createWeblink("haushalt", "counter_.*", "Haushalt", "log_zaehler", "Haushalt", "Haushalt");;\
}
attr erzeugung_zaehler room Zähler
trigger erzeugung_zaehler
##
# Aktueller Stromverbrauch
##
define zaehler_haushalt_jetzt Dummy
attr zaehler_haushalt_jetzt room Aktuell
attr zaehler_haushalt_jetzt alias Stromverbrauch (aktuell)
define zaehler_haushalt_jetzt_erzeugung at +*00:01 { my $power = 12 * countLogEntries("300", "log_zaehler", "4:::");; fhem("set zaehler_haushalt_jetzt $power W") }
attr zaehler_haushalt_jetzt_erzeugung room Zähler
##
# Zähler für den Energieverbrauch des gesamten Haushaltes
##
define energie_haushalt Dummy
attr energie_haushalt room Zähler
##
# Log für den Energieverbrauch des gesamten Haushaltes
##
define log_energie FileLog %L/energie_%Y.log energie_haushalt.*
attr log_energie room Zähler
##
# Ermittlung des Energieverbrauches pro Tag für den gesamten Haushalt
##
define ermittlung_energie_haushalt_tag at *23:59:00 { my $c = countLogEntries("86400", "log_zaehler", "4:::");; fhem("set energie_haushalt day $c Wh") }
attr ermittlung_energie_haushalt_tag room Zähler
##
# Diagramm zur Anzeige des Energieverbrauches pro Tag für den gesamten Haushalt
##
define weblink_energie_haushalt_tag SVG log_energie:my_energie:CURRENT
attr weblink_energie_haushalt_tag alias Diagramm-Stromverbrauch-Tag
attr weblink_energie_haushalt_tag room Monatsübersicht
attr weblink_energie_haushalt_tag fixedrange month
attr weblink_energie_haushalt_tag label "Energie (Wh) | ⌀ $data{avg1}"
attr weblink_energie_haushalt_tag title "Haushalt"
attr weblink_energie_haushalt_tag plotfunction 4:energie_haushalt.day::
##
# Ermittlung des Energieverbrauches pro Woche für den gesamten Haushalt
##
define ermittlung_energie_haushalt_woche at *23:59:10 { if ($wday==0) { my $c = sumLogEntries("604800", "log_energie", "4:energie_haushalt.day::");; fhem("set energie_haushalt week $c Wh") } }
attr ermittlung_energie_haushalt_woche room Zähler
##
# Automatischer Reset von Firmata, falls die Verbindung zum Arduino dauerhaft verloren geht
##
define firmata_disconnected_listener notify firmata:DISCONNECTED.* { Log(3, "firmata was disconnected, waiting 15 seconds for automatic reset...");; fhem "define firmata_reconnector at +00:00:15 trigger firmata_reconnect";; fhem "attr firmata_reconnector room Zähler" }
attr firmata_disconnected_listener room Zähler
define firmata_reconnect notify firmata_reconnect { my $v = Value("firmata");; if ($v eq "disconnected") { Log(3, "firmata is still disconnected, now resetting");; fhem "set firmata reset" } else { Log(3, "No need to reset firmata: state is <$v>");; } }
attr firmata_reconnect room Zähler
zaehler_haushalt_jetzt_erzeugung
berechnet den Durchschnittsverbrauch (in Watt) der letzten 5 Minuten. Beispiel: Wir nehmen einen Verbrauch von 500 W an. In einer Stunde wären das 500 Wh. In 5 Minuten kommen 500/12=41,66 Wh zusammen. Für jede Wattstunde steht eine Zeile im Log, für die Dauer von 5 Minuten also 41 Zeilen. zaehler_haushalt_jetzt_erzeugung
zählt die Anzahl der Zeilen der letzten 300 Sekunden (5 Minuten) und rechnet die auf 1 Stunde hoch (*12). Damit kommt man auf 60*12=492. Das ist also der durchschnittliche Verbrauch der letzten 5 Minuten. Aktualisiert wird er jede Minute.
##############################################
# $Id: 99_myUtils.pm $
package main;
use strict;
use warnings;
use POSIX;
sub myUtils_Initialize() {
my ($hash) = @_;
}
sub createCounter {
my ($device_id, $pin, $name, $logdevice, $plotroom, $sortby) = @_;
my $counter_id = "counter_$device_id";
fhem "define $counter_id FRM_IN $pin";
fhem "attr $counter_id internal-pullup on";
fhem "attr $counter_id count-mode falling";
fhem "attr $counter_id event-min-interval power:1";
fhem "attr $counter_id oldreadings time";
fhem "attr $counter_id userReadings time:reading:.off { Time::HiRes::time() }, power:time:.+ { sprintf('%.2f W', 3600 / (ReadingsVal('$counter_id', 'time', undef) - OldReadingsVal('$counter_id', 'time', undef))) }";
fhem "attr $counter_id stateFormat { getCounterStateFormat('$counter_id') }";
fhem "attr $counter_id alias Stromzähler $name";
fhem "attr $counter_id room Zähler";
createWeblink($device_id, $counter_id, $name, $logdevice, $plotroom, $sortby);
}
sub getCounterStateFormat {
my ($device) = @_;
my $reading = 'power';
my $value = sprintf("%.0f", ReadingsNum($device, $reading, undef));
my $age = sprintf("%.0f", ReadingsAge($device, $reading, undef) / 60);
return "< 1 W" if ($age >= 60);
return "$value W" if ($age < 5);
return sprintf("< %.0f W", 60 / $age);
}
sub createWeblink {
my ($device_id, $counter_id, $name, $logdevice, $room, $sortby) = @_;
my $diagram_id = "weblink_$device_id";
fhem "define $diagram_id weblink fileplot $logdevice:my_leistung_energie:CURRENT";
fhem "attr $diagram_id alias Diagramm-$name";
fhem "attr $diagram_id sortby $sortby";
fhem "attr $diagram_id room $room";
fhem "attr $diagram_id title \"$name\"";
fhem "attr $diagram_id label \"Leistung (W)\"::\"Energie (Wh)\"";
fhem "attr $diagram_id plotfunction 4:$counter_id.power::\$fld[3]>3600?0:\$fld[3] 4:$counter_id.power::\$cnt[0]";
}
sub countLogEntries {
my ($offset, $logfile, $cspec) = @_;
my $count = getLogEntries($offset, $logfile, $cspec);
return $count;
}
sub sumLogEntries {
my ($offset, $logfile, $cspec) = @_;
my @entries = getLogEntries($offset, $logfile, $cspec);
my $sum = 0;
foreach (@entries) {
my @line = split(" ", $_);
if (defined $line[1] && "$line[1]" ne "") {
$sum += $line[1];
}
}
return $sum;
}
sub getLogEntries {
my ($offset, $logfile, $cspec) = @_;
my $start = strftime("%Y-%m-%d_%H:%M:%S", localtime(time()-$offset));
my $end = strftime("%Y-%m-%d_%H:%M:%S", localtime());
my $loglevel = $attr{global}{verbose};
$attr{global}{verbose} = 0;
my @logdata = split("\n", fhem("get $logfile - - $start $end $cspec"));
$attr{global}{verbose} = $loglevel;
return @logdata;
}
1;
set terminal size <SIZE>
set title '<TL>'
set ylabel "Leistung (W)"
set y2label "Energie (Wh)"
#FileLog <SPEC1>
#FileLog <SPEC2>
plot axes x1y1 title '<L1>' with lines ls l0,\
axes x1y2 title '<L2>' with lines ls l1fill
set terminal size <SIZE>
set title '<TL>'
set ylabel "Energie (Wh)"
set yrange [0:]
set y2range [0:]
#FileLog <SPEC1>
plot axes x1y2 title '<L1>' with lines ls l1fill