Summe aller Einschaltzeiten eines Gerätes: Unterschied zwischen den Versionen

Aus FHEMWiki
K (Gliederungsebenen korrigiert)
Zeile 80: Zeile 80:
############################################################################
############################################################################
# $Id: myUtilsTemplate.pm 7570 2015-01-14 18:31:44Z rudolfkoenig $
# $Id: myUtilsTemplate.pm 7570 2015-01-14 18:31:44Z rudolfkoenig $
#
# Beispiel aus https://wiki.fhem.de/wiki/Summe_aller_Einschaltzeiten_eines_Ger%C3%A4tes


package main;
package main;
Zeile 96: Zeile 96:


############################################################################################################
############################################################################################################
##                          Ermitteln der Einschaltzeiten eines Gerätes                              
##                          Ermitteln der Einschaltzeiten eines Gerätes                              
############################################################################################################
############################################################################################################
# Im DbRep-Device ist das Attribut "userExitFn = switchontime" zu setzen.
# Im DbRep-Device ist das Attribut "userExitFn = switchontime" zu setzen.
Zeile 106: Zeile 106:
#      switch_on_time    -> on-Zeit als formatierte Angabe hhh:mm:ss
#      switch_on_time    -> on-Zeit als formatierte Angabe hhh:mm:ss
#      switch_on_time_sec -> on-Zeit als Sekundenwert
#      switch_on_time_sec -> on-Zeit als Sekundenwert
#  
#  
# Die Routine ist für alle Readings geeignet die Zahlenwerte als Switch-on/off Zustand beinhalten.
# Die Routine ist für alle Readings geeignet die Zahlenwerte als Switch-on/off Zustand beinhalten.
#      Switch-off = 0
#      Switch-off = 0
Zeile 114: Zeile 114:
# aufgerufen. Es wird der Devicename des aufrufenden Device, Reading und Wert des Readings übergeben.
# aufgerufen. Es wird der Devicename des aufrufenden Device, Reading und Wert des Readings übergeben.
# Der Aufrufablauf ist:
# Der Aufrufablauf ist:
# 1. Start: state = running  
# 1. Start: state = running
# 2. Übermittlung jedes generierten Readings
# 2. Übermittlung jedes generierten Readings
# 3. Stop: state = done (bzw. nicht running)
# 3. Stop: state = done (bzw. nicht running)
#
#
###########################################################################################################
###########################################################################################################
my ($esb,$ese,$esl);
my $t = 0;        # Variable für resultierende Sekunden
my $t = 0;                                   # Variable für resultierende Sekunden
my $sekunden = 0; # Variable für sekunden des Readings
my $pattern = "eg.az.fridge_Pwr__power";    # verwendbarer Teilstring des Switch-on/Switch-off Werte  
my $startzeit = 0; # Variable für Beginn des Zeitraums
my $endzeit = 0; # Variable für Ende des Zeitraums
my $letztervalue = 0; # Variable für Value des vorherigen Readings
my $letztersekunden = 0; # Variable für Sekunde des vorherigen Readings
my $initvalue = 0;
my $pattern = "Ttim__state";    # verwendbarer Teilstring des Switch-on/Switch-off Werte
                                             # enthaltenden Readings (entsprechend anpassen !)
                                             # enthaltenden Readings (entsprechend anpassen !)
my $runde;
sub switchontime($$$)
{
my ($name,$reading,$value) = @_;
my $hash = $defs{$name};
# $value = int  $value;    #schöner für Anzeigen
if($reading eq "state" && $value eq "running")    # der Select wurde gestartet
{   
$letztervalue = -1;
$runde = 0;
my ($devn,$devread) = split("__",$pattern); # den aktuellen Zustand des Devices ermitteln
$devread = uc ($devread);
$initvalue = InternalVal("$devn","$devread",99);
if ($initvalue == 99)
{
Log3 $name, 1, "$name - der Patternname scheint falsch zu sein";
}
my $jetzt = TimeNow();  # aktuelle Zeit = Ende des Abfragezeitraums ermitteln
my $lstr = (split("__",$jetzt))[0];
my ($year, $month, $day, $hour, $min, $sec) = ($lstr =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
# Umwandeln in Epochesekunden
$endzeit = timelocal($sec, $min, $hour, $day, $month-1, $year-1900);
$startzeit = $endzeit - AttrVal("Rep.powerOnTime","timeDiffToNow",98); # Beginn des Abfragezeitraums ermitteln
 
Log3 $name, 3, "$name - UserExitFn \"ontime\" has been called.";
($sec,$min,$hour) = localtime($startzeit);
Log3 $name, 3, "Start der Abfrage $startzeit $hour:$min:$sec"; 
($sec,$min,$hour) = localtime($endzeit);
Log3 $name, 3, "Ende  der Abfrage $endzeit $hour:$min:$sec"; 
$t = 0;
return;
}
$runde = $runde +1;
if($reading =~ /$pattern/)  # den Timestamp unabhängig vom Status festhalten
{
my $lstr = (split("__",$reading))[0];
my ($year, $month, $day, $hour, $min, $sec) = ($lstr =~ /(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)/);
# Umwandeln in Epochesekunden
$sekunden = timelocal($sec, $min, $hour, $day, $month-1, $year-1900);
Log3 $name, 4, "t:$t s:$sekunden value:$value $hour:$min:$sec ";
if($letztervalue == -1) # wenn jüngster Wert (erstes reading)
{
if($value != 0) #  an
{
$t = $endzeit;
}
}
if ($value == $letztervalue)
{
Log3 $name, 2, "$name - Ein value ist doppelt";
}
if( $value != 0 && $value != $letztervalue) # Gerät wurde eingeschaltet
{
$t = $t - $sekunden;
}
if($value == 0 && $value != $letztervalue)  # Gerät wurde ausgeschaltet
{
$t = $t + $sekunden;
}
# Value sichern für endberechnung und doppelte Werte
$letztervalue = $value;
$letztersekunden = $sekunden;
}
if($reading eq "state" && $value ne "running") # die Selektion ist beendet, Summensekunden in Format hhh:mm:ss umwamdeln
{
if ($letztervalue == 0) # am Ende der Auswertung ist Status immer noch switch-on
$t =  $t - $startzeit;
}
if ($letztervalue == -1)  #wenn keine Werte kamen
{
if( $initvalue != 0 ) # Gerät ganze Zeit an
{
$t = AttrVal("Rep.powerOnTime","timeDiffToNow",98);
Log3 $name, 5, "Gerät ganze Zeit an";
}
if($initvalue == 0 )  # Gerät ganze Zeit aus
{
$t = 0;
Log3 $name, 5, "Gerät ganze Zeit an";
}
}
fhem("setreading $name switch_on_time_sec $t");
my $m = int $t/60;
my $s = $t - ($m*60);
my $h = int $m/60;
$m = $m - ($h*60);
my $timestring = sprintf("%03d:%02d:%02d",$h,$m,$s);
fhem("setreading $name switch_on_time $timestring (hhh:mm:ss)");
Log3 $name, 3, "$name - switch-on time was $timestring (hhh:mm:ss)";
}


sub switchontime($$$) {
my ($name,$reading,$value) = @_;
my $hash = $defs{$name};
if($reading eq "state" && $value eq "running") {
    # der Select wurde gestartet
    Log3 $name, 3, "$name - UserExitFn \"ontime\" has been called.";
    $t = 0;
    return;
}
if($reading =~ /$pattern/) {
    # den Timestamp des letzten Payload-Readings unabhängig vom Status
    # (switch-on/switch-off) festhalten
    my $lstr = (split("__",$reading))[0];
    my ($year, $month, $day, $hour, $min, $sec) = ($lstr =~ /(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)/);
 
    # Umwandeln in Epochesekunden
    $esl = timelocal($sec, $min, $hour, $day, $month-1, $year-1900);
}
if($reading =~ /$pattern/ && $value != 0 && !$esb) {
    # Gerät wurde eingeschaltet
    # Beginn Zeitperiode (Unix-Timestamp) ermitteln
    my $bstr = (split("__",$reading))[0];
    my ($year, $month, $day, $hour, $min, $sec) = ($bstr =~ /(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)/);
 
    # Umwandeln in Epochesekunden Beginn
    $esb = timelocal($sec, $min, $hour, $day, $month-1, $year-1900);
    Log3 $name, 4, "$name - Epoche start: $esb (Reading: $reading)";
}
if($reading =~ /$pattern/ && $value == 0 && $esb) {
    # Gerät wurde ausgeschaltet
    # Ende Zeitperiode (Unix-Timestamp) ermitteln
    my $estr = (split("__",$reading))[0];
    my ($year, $month, $day, $hour, $min, $sec) = ($estr =~ /(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)/);
 
    # Umwandeln in Epochesekunden Ende
    $ese = timelocal($sec, $min, $hour, $day, $month-1, $year-1900);
    Log3 $name, 4, "$name - Epoche end: $ese (Reading: $reading)";
    # Einschaltzeit in Sekunden summieren (Auswerterichtung DESC - neuester TS -> ältester TS)
    $t = $t + ($esb - $ese);
    Log3 $name, 4, "$name - switch-on time summary: $t seconds";
    undef $esb;
    return;
}
if($reading eq "state" && $value ne "running") {
    # die Selektion ist beendet, Summensekunden in Format hhh:mm:ss umwamdeln
    if ($esb) {
        # am Ende der Auswertung ist Status immer noch switch-on
# den Last-Timestamp verwenden
$t = $t + ($esb - $esl);
undef $esb;
    }
    fhem("setreading $name switch_on_time_sec $t");
    my $m = int $t/60;
    my $s = $t - ($m*60);
    my $h = int $m/60;
    $m = $m - ($h*60);
    my $timestring = sprintf("%03d:%02d:%02d",$h,$m,$s);
    fhem("setreading $name switch_on_time $timestring (hhh:mm:ss)");
    Log3 $name, 3, "$name - switch-on time was $timestring (hhh:mm:ss)";
}
return;
return;
}
}


1;
1;
</source>
Nun ist der Suchpattern des Beispiels in der Zeile:
<source lang="perl">
my $pattern = "eg.az.fridge_Pwr__power";
</source>
so abzuändern, dass ein auswertbarer Teilstring des Readingsnamens mit den Switch-on/Switch-off Informationen enthalten ist.
Im Fall dass das Device "Ttim" und das Reading "state" heißt, wäre dies z.B.:
<source lang="perl">
my $pattern = "Ttim__state";
</source>
Wenn man sich nicht sicher ist, kann durch die Ausführung von:
<pre>
set <Name> fetchrows history
</pre>
geprüft werden welche Readings im DbRep-Device erstellt werden und benutzt den Teilstring nach dem führenden Datum/Zeit-String des Readings.
Also zum Beispiel:
'''Reading:''' 2017-11-22_11-08-02__eg.az.fridge_Pwr__power  -> '''Teilstring für $pattern:''' eg.az.fridge_Pwr__power
Nun die Utils-Datei laden mit:
<pre>
reload 99_myOntimeUtils.pm
</pre>
oder FHEM Restart.
Die Auswertung wird nun gestartet mit:                                [[Datei:switch_readings.PNG|right|thumb|300px|zusätzliche Readings history]]
<pre>
set Rep.powerOnTime fetchrows history
</pre>
</pre>
                
                

Version vom 29. Januar 2018, 08:16 Uhr

Dieses Beispiel ermittelt aus der Datenbank die Einschaltdauer eines Kühlschrankes (über einen Zwischenstecker HM-ES-PMSw1-Pl) innerhalb der letzten 24 Stunden. Wie üblich kann der Auswertungszeitraum über die Time-spezifischen Attribute abgeändert werden.

Es wird angenommen, dass "Aus" = 0 ist und "Ein" ein von 0 abweichender Wert (1 oder eine andere Zahl).

Es sind zwei Komponenten notwendig, ein DbRep-Device und eine Subroutine "switchontime" in der 99_myUtils. Diese Auswertungsroutine kann abweichend davon auch in einer eigenen 99_xxxUtils.pm, z.B. 99_myOntimeUtils.pm, eingebaut werden. Diese Datei ist in das FHEM-Verzeichnis zu kopieren (üblicherweise /opt/fhem/FHEM).

DbRep-Device

Das DbRep-Device wird entsprechend dieser Raw-Definition angelegt:

define Rep.powerOnTime DbRep LogDB
attr Rep.powerOnTime aggregation no
attr Rep.powerOnTime comment Ermittlung der Anschaltdauer (Switch-on Time) eines Gerätes (z.B. Kühlschrank) \
innerhalb der letzten 24 Stunden.\
Es wird die Auswertungsroutine "switchontime" im Attribut "userExitFn" benötigt \
(in 99_myOntimeUtils.pm enthalten).\
\
Start erfolgt mit:      "set <;Name>; fetchrows history"
attr Rep.powerOnTime devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.powerOnTime device eg.az.fridge_Pwr
attr Rep.powerOnTime event-on-update-reading state,switch_on_time_sec
attr Rep.powerOnTime reading power
attr Rep.powerOnTime room DbLog
attr Rep.powerOnTime timeDiffToNow 86400
attr Rep.powerOnTime userExitFn switchontime
attr Rep.powerOnTime verbose 2

Natürlich ist das angegeben DbLog-Device (LogDB) in der Definition, sowie die Attribute device und reading den eigenen Gegebenheiten anzupassen. Wichtig ist die Angabe:

attr Rep.powerOnTime userExitFn switchontime

was die Aktivierung der userExit-Funktion bzw. den Aufruf der Auswertungsroutine "switchontime" bewirkt.

Die Daten in der Datenbank befinden sich im Device "eg.az.fridge_Pwr" und dem Reading "power". Daraus resultiert dass nach einem

set <Name> fetchrows history

Readings mit folgenden Muster generiert werden.

set <Name> fetchrows history
2017-11-22_16-26-50__eg.az.fridge_Pwr__power  0  2017-11-22 18:33:50 
2017-11-22_16-29-54__eg.az.fridge_Pwr__power  0  2017-11-22 18:33:50 
2017-11-22_16-32-43__eg.az.fridge_Pwr__power  57.83  2017-11-22 18:33:50 
2017-11-22_16-35-18__eg.az.fridge_Pwr__power  51.09  2017-11-22 18:33:50 
2017-11-22_16-37-39__eg.az.fridge_Pwr__power  50.13  2017-11-22 18:33:50 
2017-11-22_16-39-45__eg.az.fridge_Pwr__power  50.17  2017-11-22 18:33:50 
2017-11-22_16-42-40__eg.az.fridge_Pwr__power  49.59  2017-11-22 18:33:50 
2017-11-22_16-45-22__eg.az.fridge_Pwr__power  48.57  2017-11-22 18:33:50 
2017-11-22_16-47-48__eg.az.fridge_Pwr__power  47.48  2017-11-22 18:33:50 
2017-11-22_16-50-01__eg.az.fridge_Pwr__power  46.97  2017-11-22 18:33:50 
2017-11-22_16-53-03__eg.az.fridge_Pwr__power  46.02  2017-11-22 18:33:50 
2017-11-22_16-53-05__eg.az.fridge_Pwr__power  11.09  2017-11-22 18:33:50 
2017-11-22_16-53-13__eg.az.fridge_Pwr__power  0  2017-11-22 18:33:50
2017-11-22_18-14-05__eg.az.fridge_Pwr__power  0 2017-11-22 18:33:50 
2017-11-22_18-16-39__eg.az.fridge_Pwr__power  0 2017-11-22 18:33:50 
2017-11-22_18-18-58__eg.az.fridge_Pwr__power  0 2017-11-22 18:33:50 
2017-11-22_18-21-03__eg.az.fridge_Pwr__power  0 2017-11-22 18:33:50 
2017-11-22_18-23-58__eg.az.fridge_Pwr__power  0 2017-11-22 18:33:50 
2017-11-22_18-26-08__eg.az.fridge_Pwr__power  56.37 2017-11-22 18:33:50 
2017-11-22_18-26-38__eg.az.fridge_Pwr__power  43.22 2017-11-22 18:33:50 
2017-11-22_18-29-04__eg.az.fridge_Pwr__power  50.92 2017-11-22 18:33:50 
2017-11-22_18-31-16__eg.az.fridge_Pwr__power  50.12 2017-11-22 18:33:50

Auswertungsroutine "switchontime"

Die nachfolgende Auswertungsroutine wird z.B. in der Datei 99_myOntimeUtils.pm gespeichert. Der Code ist mit Kommentaren angereichert um den Ablauf der Auswertung verständlich darzustellen und dadurch eine abgewndelte Nutzung in eigenen Projekten zu erleichtern.

Den Code in die Datei kopieren:

<source lang="perl">

  1. $Id: myUtilsTemplate.pm 7570 2015-01-14 18:31:44Z rudolfkoenig $
  2. Beispiel aus https://wiki.fhem.de/wiki/Summe_aller_Einschaltzeiten_eines_Ger%C3%A4tes

package main;

use strict; use warnings; use POSIX; use Time::Local;

sub myOntimeUtils_Initialize($$) {

 my ($hash) = @_;

}

    1. Ermitteln der Einschaltzeiten eines Gerätes
  1. Im DbRep-Device ist das Attribut "userExitFn = switchontime" zu setzen.
  2. Auswertung wird im DbRep gestartet mit:
  3. set <DbRep-Device> fetchrows history
  4. Die resultierende Switch-on Zeit wird im aufrufenden DbRep-Device in Readings dargestellt:
  5. switch_on_time -> on-Zeit als formatierte Angabe hhh:mm:ss
  6. switch_on_time_sec -> on-Zeit als Sekundenwert
  7. Die Routine ist für alle Readings geeignet die Zahlenwerte als Switch-on/off Zustand beinhalten.
  8. Switch-off = 0
  9. Swich-off  != 0
  10. Nach der Erstellung jedes einzelnen Readings wird die im Attribut "userExitFn" hinterlegte Funktion
  11. aufgerufen. Es wird der Devicename des aufrufenden Device, Reading und Wert des Readings übergeben.
  12. Der Aufrufablauf ist:
  13. 1. Start: state = running
  14. 2. Übermittlung jedes generierten Readings
  15. 3. Stop: state = done (bzw. nicht running)

my $t = 0; # Variable für resultierende Sekunden my $sekunden = 0; # Variable für sekunden des Readings my $startzeit = 0; # Variable für Beginn des Zeitraums my $endzeit = 0; # Variable für Ende des Zeitraums my $letztervalue = 0; # Variable für Value des vorherigen Readings my $letztersekunden = 0; # Variable für Sekunde des vorherigen Readings my $initvalue = 0; my $pattern = "Ttim__state"; # verwendbarer Teilstring des Switch-on/Switch-off Werte

                                            # enthaltenden Readings (entsprechend anpassen !)

my $runde;

sub switchontime($$$) { my ($name,$reading,$value) = @_; my $hash = $defs{$name}; # $value = int $value; #schöner für Anzeigen

if($reading eq "state" && $value eq "running") # der Select wurde gestartet { $letztervalue = -1; $runde = 0; my ($devn,$devread) = split("__",$pattern); # den aktuellen Zustand des Devices ermitteln $devread = uc ($devread); $initvalue = InternalVal("$devn","$devread",99); if ($initvalue == 99) { Log3 $name, 1, "$name - der Patternname scheint falsch zu sein"; }

my $jetzt = TimeNow(); # aktuelle Zeit = Ende des Abfragezeitraums ermitteln

my $lstr = (split("__",$jetzt))[0]; my ($year, $month, $day, $hour, $min, $sec) = ($lstr =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/); # Umwandeln in Epochesekunden

$endzeit = timelocal($sec, $min, $hour, $day, $month-1, $year-1900); $startzeit = $endzeit - AttrVal("Rep.powerOnTime","timeDiffToNow",98); # Beginn des Abfragezeitraums ermitteln

Log3 $name, 3, "$name - UserExitFn \"ontime\" has been called."; ($sec,$min,$hour) = localtime($startzeit); Log3 $name, 3, "Start der Abfrage $startzeit $hour:$min:$sec"; ($sec,$min,$hour) = localtime($endzeit); Log3 $name, 3, "Ende der Abfrage $endzeit $hour:$min:$sec";

$t = 0; return; } $runde = $runde +1; if($reading =~ /$pattern/) # den Timestamp unabhängig vom Status festhalten { my $lstr = (split("__",$reading))[0]; my ($year, $month, $day, $hour, $min, $sec) = ($lstr =~ /(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)/); # Umwandeln in Epochesekunden $sekunden = timelocal($sec, $min, $hour, $day, $month-1, $year-1900); Log3 $name, 4, "t:$t s:$sekunden value:$value $hour:$min:$sec ";

if($letztervalue == -1) # wenn jüngster Wert (erstes reading) { if($value != 0) # an { $t = $endzeit; } } if ($value == $letztervalue) { Log3 $name, 2, "$name - Ein value ist doppelt"; } if( $value != 0 && $value != $letztervalue) # Gerät wurde eingeschaltet { $t = $t - $sekunden; } if($value == 0 && $value != $letztervalue) # Gerät wurde ausgeschaltet { $t = $t + $sekunden; }

# Value sichern für endberechnung und doppelte Werte $letztervalue = $value; $letztersekunden = $sekunden; } if($reading eq "state" && $value ne "running") # die Selektion ist beendet, Summensekunden in Format hhh:mm:ss umwamdeln { if ($letztervalue == 0) # am Ende der Auswertung ist Status immer noch switch-on { $t = $t - $startzeit;

} if ($letztervalue == -1) #wenn keine Werte kamen { if( $initvalue != 0 ) # Gerät ganze Zeit an { $t = AttrVal("Rep.powerOnTime","timeDiffToNow",98); Log3 $name, 5, "Gerät ganze Zeit an"; } if($initvalue == 0 ) # Gerät ganze Zeit aus { $t = 0; Log3 $name, 5, "Gerät ganze Zeit an"; } } fhem("setreading $name switch_on_time_sec $t");

my $m = int $t/60; my $s = $t - ($m*60); my $h = int $m/60; $m = $m - ($h*60); my $timestring = sprintf("%03d:%02d:%02d",$h,$m,$s);

fhem("setreading $name switch_on_time $timestring (hhh:mm:ss)");

Log3 $name, 3, "$name - switch-on time was $timestring (hhh:mm:ss)"; }

return; }

1;


Es werden zwei zusätzliche Readings im Device Rep.powerOnTime generiert, switch_on_time und switch_on_time_sec. Das Reading switch_on_time_sec ist die summarische "EIN"-Zeit in Sekunden der letzten 24 Stunden und kann gut zur weiteren Auswertung genutzt werden. switch_on_time ist eine in hhh:mm:ss formatierte Angabe des Readingswertes von switch_on_time_sec zur leichteren Lesbarkeit.

Natürlich kann man switch_on_time_sec wiederum loggen um z.B. daraus eine grafische Anzeige zu erstellen, die die Entwicklung der täglichen "EIN"-Zeiten über einen längeren Zeitraum darstellen könnte (z.B. Heizzeiten).

Mit kleinen Abwandlungen könnten zum Beispiel auch die täglichen Raumlüftungszeiten (Fenter "open", "closed") ausgewertet und überwacht werden. Wird die vorgegeben "open"-Zeit unterschritten, könnte ein Alarm ausgelöst werden.

Wie üblich können die Auswertungszeitgrenzen durch die Zeit-spezifischen Attribute timestamp_begin, timestamp_end usw. den Anforderungen entsprechend abgeändert werden.