Gleitende Mittelwerte berechnen und loggen: Unterschied zwischen den Versionen

Aus FHEMWiki
(→‎Funktion movingAverage() aufrufen und nutzen: userReadings ... hinten ein s)
K (Beispiele, wo möglich, auf <syntaxhighlight> umgestellt)
 
(6 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 9: Zeile 9:
== Gleitender Mittelwert für beliebige Readings ==
== Gleitender Mittelwert für beliebige Readings ==
=== Subroutine movingAverage in 99_MyUtils anlegen ===
=== Subroutine movingAverage in 99_MyUtils anlegen ===
Zuerst muss die Subroutine ''movingAverage'' in die 99_MyUtils übernommen werden. Wenn noch nicht vorhanden, sollte die Datei dann so aussehen:
Es werden die Funktionen ''movingAverage'' und ''movingAverageT'' in die 99_MyUtils Datei übernommen werden. Wenn noch nicht vorhanden, sollte die Datei dann so aussehen, der übliche Speicherort ist ''/opt/fhem/FHEM/99_MyUtils.pm''


  <nowiki>package main;
  <syntaxhighlight lang="Perl">package main;
use strict;
  use strict;
use warnings;
  use warnings;
use POSIX;
  sub
sub
  MyUtils_Initialize($$)
MyUtils_Initialize($$)
  {
{
  my ($hash) = @_;
  my ($hash) = @_;
  }
}
  ###############################################################################
###############################################################################
  #
#
  #  Moving average  
#  Moving average  
  #
#
  #  Aufruf: movingAverage(devicename,readingname,zeitspanne in s)
#  Aufruf: movingAverage(devicename,readingname,zeitspanne in s)
  #
#
  ###############################################################################
  ###############################################################################
 
   
  sub movingAverage($$$){
  sub movingAverage($$$){
    my ($name,$reading,$avtime) = @_;
     my ($name,$reading,$avtime) = @_;
    my $hash = $defs{$name};
    my @new = my ($val,$time) = ($hash->{READINGS}{$reading}{VAL},$hash->{READINGS}{$reading}{TIME});
    my ($cyear, $cmonth, $cday, $chour, $cmin, $csec) = $time =~ /(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)/;
    my $ctime = $csec+60*$cmin+3600*$chour;
    my $num;
    my $arr;
    #-- initialize if requested
    if( ($avtime eq "-1") ){
      $hash->{READINGS}{$reading}{"history"}=undef;
    }
    #-- test for existence
    if( !$hash->{READINGS}{$reading}{"history"}){
        #Log 1,"ARRAY CREATED";
        push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
        $num = 1;
        $arr=\@{$hash->{READINGS}{$reading}{"history"}};
    } else {
        $num = int(@{$hash->{READINGS}{$reading}{"history"}});
        $arr=\@{$hash->{READINGS}{$reading}{"history"}};
        my $starttime = $arr->[0][1];
        my ($syear, $smonth, $sday, $shour, $smin, $ssec) = $starttime =~ /(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)/;
        my $stime = $ssec+60*$smin+3600*$shour;
        #-- correct for daybreak
        $stime-=86400
          if( $stime > $ctime);
        if( ($num < 25)&&( ($ctime-$stime)<$avtime) ){
          #Log 1,"ARRAY has $num elements, adding another one";
          push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
        }else{
          shift(@{$hash->{READINGS}{$reading}{"history"}});
          push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
        }
      }
      #-- output and average
      my $average = 0;
      for(my $i=0;$i<$num;$i++){
        $average+=$arr->[$i][0];
        Log 4,"[$name moving average] Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];
      }
      $average=sprintf( "%5.3f", $average/$num);
      #--average
      Log 4,"[$name moving average] calculated over $num values is $average";  
      return $average;
  }
 
  ###############################################################################
  #
  # Moving average of last X seconds, weigh values by time
  #
  # movingAverageT(Devicename, Readingname, HistoryLength)
  #
  ###############################################################################
  sub movingAverageT($$$){
     my ($name, $reading, $hSeconds) = @_;
     my $hash = $defs{$name};
     my $hash = $defs{$name};
     my @new = my ($val,$time) = ($hash->{READINGS}{$reading}{VAL},$hash->{READINGS}{$reading}{TIME});
     my @new = my ($val, $time) = ($hash->{READINGS}{$reading}{VAL}, $hash->{READINGS}{$reading}{TIME});
     my ($cyear, $cmonth, $cday, $chour, $cmin, $csec) = $time =~ /(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)/;
      
    my $ctime = $csec+60*$cmin+3600*$chour;
     #create new hash "history" if not already existing
    my $num;
     if ( !$hash->{READINGS}{$reading}{"history"}) {
    my $arr;
       @{$hash->{READINGS}{$reading}{"history"}} = \@new;
     #-- initialize if requested
     if( ($avtime eq "-1") ){
       $hash->{READINGS}{$reading}{"history"}=undef;
     }
     }
     #-- test for existence
 
     if( !$hash->{READINGS}{$reading}{"history"}){
     #get reference to hash "history"
      #Log 1,"ARRAY CREATED";
     my $arr = \@{$hash->{READINGS}{$reading}{"history"}};
      push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
 
      $num = 1;
    #just add values if timestamp differs to last item in history
      $arr=\@{$hash->{READINGS}{$reading}{"history"}};
    if( "$time" ne "$arr->[-1][1]" ) {
    } else {
      #append the new entry
      $num = int(@{$hash->{READINGS}{$reading}{"history"}});
      push(@{$hash->{READINGS}{$reading}{"history"}}, \@new);
      $arr=\@{$hash->{READINGS}{$reading}{"history"}};
     
      my $starttime = $arr->[0][1];
      #remove entries that are too old
      my ($syear, $smonth, $sday, $shour, $smin, $ssec) = $starttime =~ /(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)/;
      while( time() - time_str2num((\@{$hash->{READINGS}{$reading}{"history"}})->[0][1]) > $hSeconds ) {
      my $stime = $ssec+60*$smin+3600*$shour;
        #Log 1,"[$name:$reading] removing reading, it is too old";
      #-- correct for daybreak
        shift(@{$hash->{READINGS}{$reading}{"history"}});
      $stime-=86400
      }
        if( $stime > $ctime);
    }
      if( ($num < 25)&&( ($ctime-$stime)<$avtime) ){
   
        #Log 1,"ARRAY has $num elements, adding another one";
    #length of current history
        push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
    my $aLength = int(@{$hash->{READINGS}{$reading}{"history"}});
      }else{
   
        shift(@{$hash->{READINGS}{$reading}{"history"}});
    #if history currently consists of a single value, just return it
        push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
    if ($aLength <= 1) {
      }
      #Log 1,"[$name:$reading] Average of a single value is the single value: $val";
    }
      return sprintf("%5.3f", $val);
    #-- output and average
    }
    my $average = 0;
 
    for(my $i=0;$i<$num;$i++){
    #sum up all values multiplied with fraction of duration, a value that was valid longer weighs more
      $average+=$arr->[$i][0];
    my $average = 0;
      Log 4,"[$name moving average] Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];  
    my $oldest = $arr->[0][1];
     }
    my $newest = $arr->[-1][1];
    $average=sprintf( "%5.3f", $average/$num);
    #Log 1,"[$name:$reading] Oldest Timestamp = ".$oldest.", Newest Timestamp: ".$newest;
    #--average
 
    Log 4,"[$name moving average] calculated over $num values is $average";
    $oldest = time_str2num($oldest);
    return $average;
    $newest = time_str2num($newest);
    my $timespan = $newest - $oldest;
    #Log 1,"[$name:$reading] Total Timespan: ".$timespan;
   
    #iterate of the array from index 0 to N-1
    for(my $i=0; $i<$aLength-1; $i++){
      #Log 1,"[$name:$reading] Index: $i, Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];
      
      my $thisTime = time_str2num($arr->[$i][1]);
      my $nextTime = time_str2num($arr->[$i+1][1]);
      my $diffTime = $nextTime - $thisTime;
      my $fraction = $diffTime / $timespan;
     
      #Log 1,"[$name:$reading] diffTime = $diffTime, Fraction: $fraction";
     
      $average += $arr->[$i][0] * $fraction;
    }
    #Log 1,"[$name:$reading] Average: $average";
 
    return sprintf("%5.3f", $average);;
   }
   }
   
  1;</syntaxhighlight>
1;</nowiki>


=== Funktion movingAverage() aufrufen und nutzen ===
=== Funktion movingAverage() aufrufen und nutzen ===
Zeile 90: Zeile 158:


Dieses schreibt bei jeder Aktualisierung von "pressure" einen neuen Eintrag in sein Array - allerdings maximal sechs Werte, dann wird immer der älteste Eintrag gelöscht. Damit ist sichergestellt, dass diese mindestens 1800 Sekunden abdecken - also sechs Einträge.
Dieses schreibt bei jeder Aktualisierung von "pressure" einen neuen Eintrag in sein Array - allerdings maximal sechs Werte, dann wird immer der älteste Eintrag gelöscht. Damit ist sichergestellt, dass diese mindestens 1800 Sekunden abdecken - also sechs Einträge.
=== Funktion movingAverageT() aufrufen und nutzen ===
Bei jedem Aufruf schiebt die Funktion ''movingAverageT'' den gegenwärtigen Wert sowie den Zeitpunkt eines Readings (zweiter Parameter) ans Ende eines Arrays und verwirft Einträge die älter als das Zeitlimit aus dem dritten Parameter sind. Die Anzahl der Einträge im Array ist nicht limitiert und es wird nur anhand des Zeitstempels entschieden, ob ein Wert im Array verbleibt. Über die Werte im Array wird dann gemittelt und der Wert zurückgegeben.
Zur Anwendung empfiehlt sich, dem gewünschten Device ein userReading zu geben:
:<code>attr devicename userReadings readingname.av {movingAverageT("devicename", "readingname", Zeitspanne_in_Sekunden)}</code>
Dieses wird dann automatisch bei jedem neuen Wert des Readings mit dem neuen gleitenden Mittelwert über die vergangene Zeitspanne gefüllt.
Beispiel: Das Reading "pressure" des Devices "A.OWB" wird alle 5 Minuten aktualisiert. Das Device bekommt nun ein userReading:
:<code>attr A.OWB userReadings pressure.av {movingAverageT("A.OWB", "pressure", 1800)}</code>
Dieses schreibt bei jeder Aktualisierung von "pressure" einen neuen Eintrag an das ende seines Array und löscht zu alte Werte am Anfang des Array wieder heraus.


== Gleitender Mittelwert aus Log-Dateien ==
== Gleitender Mittelwert aus Log-Dateien ==
=== Subroutine myAverage() in 99_MyUtils anlegen ===
=== Subroutine myAverage() in 99_MyUtils anlegen ===
Zuerst muss die Subroutine myAverage in die 99_MyUtils übernommen werden. Wenn noch nicht vorhanden, sollte die Datei dann so aussehen:
Zuerst muss die Subroutine myAverage in die 99_MyUtils übernommen werden. Wenn noch nicht vorhanden, sollte die Datei dann so aussehen:
 
<syntaxhighlight lang="Perl">
  <nowiki>package main;
  package main;
use strict;
  use strict;
use warnings;
  use warnings;
use POSIX;
  sub
sub
  MyUtils_Initialize($$)
MyUtils_Initialize($$)
  {
{
  my ($hash) = @_;
  my ($hash) = @_;
  }
}
  ##########################################################
##########################################################
  # myAverage
# myAverage
  # berechnet den Mittelwert aus LogFiles über einen beliebigen Zeitraum
# berechnet den Mittelwert aus LogFiles über einen beliebigen Zeitraum
  sub
sub
  myAverage($$$)
myAverage($$$)
  {
{
  my ($offset,$logfile,$cspec) = @_;
  my ($offset,$logfile,$cspec) = @_;
  my $period_s = strftime &quot;%Y-%m-%d\x5f%H:%M:%S&quot;, localtime(time-$offset);
  my $period_s = strftime &quot;%Y-%m-%d\x5f%H:%M:%S&quot;, localtime(time-$offset);
  my $period_e = strftime &quot;%Y-%m-%d\x5f%H:%M:%S&quot;, localtime;
  my $period_e = strftime &quot;%Y-%m-%d\x5f%H:%M:%S&quot;, localtime;
  my $oll = $attr{global}{verbose};
  my $oll = $attr{global}{verbose};
  $attr{global}{verbose} = 0;  
  $attr{global}{verbose} = 0;  
  my @logdata = split(&quot;\n&quot;, fhem(&quot;get $logfile - - $period_s $period_e $cspec&quot;));
  my @logdata = split(&quot;\n&quot;, fhem(&quot;get $logfile - - $period_s $period_e $cspec&quot;));
  $attr{global}{verbose} = $oll;  
  $attr{global}{verbose} = $oll;  
  my ($cnt, $cum, $avg) = (0)x3;
  my ($cnt, $cum, $avg) = (0)x3;
  foreach (@logdata){
  foreach (@logdata){
    my @line = split(&quot; &quot;, $_);
  my @line = split(&quot; &quot;, $_);
    if(defined $line[1] &amp;&amp; $line[1] ne &quot;&quot;){
  if(defined $line[1] &amp;&amp; &quot;$line[1]&quot; ne &quot;&quot;){
    $cnt += 1;
    $cnt += 1;
    $cum += $line[1];
    $cum += $line[1];
    }
   }
   }
  if(&quot;$cnt&quot; &gt; 0){$avg = sprintf(&quot;%0.1f&quot;, $cum/$cnt)};
  Log 4, (&quot;myAverage: File: $logfile, Field: $cspec, Period: $period_s bis $period_e, Count: $cnt, Cum: $cum, Average: $avg&quot;);
  return $avg;
   }
   }
   if(&quot;$cnt&quot; &gt; 0){$avg = sprintf(&quot;%0.1f&quot;, $cum/$cnt)};
   ##########################################################
  Log 4, (&quot;myAverage: File: $logfile, Field: $cspec, Period: $period_s bis $period_e, Count: $cnt, Cum: $cum, Average: $avg&quot;);
  1;
  return $avg;
</syntaxhighlight>
}
##########################################################
1;</nowiki>


=== Funktion myAverage() aufrufen und nutzen ===
=== Funktion myAverage() aufrufen und nutzen ===
Zeile 138: Zeile 219:


  <nowiki>fhem&gt; {myAverage(&quot;10800&quot;, &quot;FileLog_KS300&quot;, &quot;8:::&quot;)}
  <nowiki>fhem&gt; {myAverage(&quot;10800&quot;, &quot;FileLog_KS300&quot;, &quot;8:::&quot;)}
6.3
  6.3
fhem&gt; {myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;)}
  fhem&gt; {myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;)}
-2.7
  -2.7
fhem&gt;</nowiki>
  fhem&gt;</nowiki>


Mit DbLog:
Mit DbLog:


  <nowiki>fhem&gt; {myAverage(&quot;10800&quot;, &quot;myDbLog&quot;, &quot;KS300:wind::&quot;)}
  <nowiki>fhem&gt; {myAverage(&quot;10800&quot;, &quot;myDbLog&quot;, &quot;KS300:wind::&quot;)}
5.9
  5.9
fhem&gt; {myAverage(&quot;86400&quot;, &quot;myDbLog&quot;, &quot;KS300:temperature::&quot;)}
  fhem&gt; {myAverage(&quot;86400&quot;, &quot;myDbLog&quot;, &quot;KS300:temperature::&quot;)}
-2.7
  -2.7
fhem&gt;</nowiki>
  fhem&gt;</nowiki>


Ähnlich ist die Verwendung in perl-Code in der [[Konfiguration|fhem.cfg]]:
Ähnlich ist die Verwendung in perl-Code in der [[Konfiguration|fhem.cfg]]:
Zeile 157: Zeile 238:


  <nowiki>define KS300_T_notify notify KS300:temperature.* {\
  <nowiki>define KS300_T_notify notify KS300:temperature.* {\
  fhem('trigger KS300 average-temp: '.myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;));;\
  fhem('trigger KS300 average-temp: '.myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;));;\
}</nowiki>
  }</nowiki>
<!-- korrigiert lt. http://forum.fhem.de/index.php/topic,11622.msg170331.html#msg170331 -->
<!-- korrigiert lt. http://forum.fhem.de/index.php/topic,11622.msg170331.html#msg170331 -->


Analog dazu wäre mit den Winddaten zu verfahren.
Analog dazu wäre mit den Winddaten zu verfahren.


=== Eigenes Filelog und Plot ===
== Gleitender Mittelwert und Minimum und Maximum aus Log-Dateien ==
 
Manchmal interessieren einem auch Minimum und Maximum Werte über einen bestimmten Zeitraum, die Funktion ''myAverage'' kann einfach entsprechend abgeändert werden:
 
=== Subroutine avgMinMaxAusLog() in 99_MyUtils anlegen ===
<syntaxhighlight lang="Perl">
  # Berechnet den Mittelwert, Minimum und Maximum aus LogFiles über einen beliebigen Zeitraum
  # Args: $offset = Offset in Sekunden
  # $logfile = LogFile, z.B. DbLog Device
  # $cspec = column-spec, siehe https://fhem.de/commandref_DE.html#DbLog -> get
  sub avgMinMaxAusLog($$$) {
  my($offset, $logfile, $cspec) = @_;
  my $period_s = strftime "%Y-%m-%d\x5f%H:%M:%S", localtime(time - $offset);
  my $period_e = strftime "%Y-%m-%d\x5f%H:%M:%S", localtime;
  my $oll = $attr { global } { verbose };
  $attr { global } { verbose } = 0;
  my @logdata = split("\n", fhem("get $logfile - - $period_s $period_e $cspec"));
  $attr { global } { verbose } = $oll;
  my ($cnt, $cum, $avg, $min, $max) = (0)x5;
  foreach(@logdata) {
  my @line = split(" ", $_);
  if (defined $line[1] && $line[1] ne "") {
  $cnt += 1;
  $cum += $line[1];
  # Beim ersten Eintrag min/max setzen
  if ($cnt == 1) {
  $min = $line[1];
  $max = $line[1];
  # Bei den darauffolgenden Einträgen $min/max ermitteln
  } else {
  if ($line[1] < $min) { $min = $line[1]; }
  if ($line[1] > $max) { $max = $line[1]; }
  }
  }
  }
  if ("$cnt" > 0) {
  $avg = sprintf("%0.1f", $cum / $cnt)
  };
  Log 4, ("avgMinMaxAusLog: File: $logfile, Field: $cspec, Period: $period_s bis $period_e, Count: $cnt, Cum: $cum, Average: $avg, Minimum: $min, Maximum: $max");
  return ($avg, $min, $max);
  }
</syntaxhighlight>
Anwendung wie oben. Statt nur dem Mittelwert wird eine Liste zurückgeliefert mit Mittelwert, Minimum und Maximum. Verwendung z.B. wie folgt:
 
<nowiki>my ($avg, $min, $max) = myAverage(&quot;10800&quot;, &quot;FileLog_KS300&quot;, &quot;8:::&quot;);;\</nowiki>
 
== Eigenes Filelog und Plot ==
==== Definitionen ====
==== Definitionen ====
Ich schreibe ein komplett eigenes Logfile und lasse über eine spezielle gplot-Definition die Temperatur- und Winddaten loggen und plotten.
Ich schreibe ein komplett eigenes Logfile und lasse über eine spezielle gplot-Definition die Temperatur- und Winddaten loggen und plotten.


<nowiki>define KS300_W dummy
<syntaxhighlight lang="Perl">
define KS300_W_notify notify KS300:wind.* {\
define KS300_W dummy
  my $avg = myAverage(&quot;10800&quot;, &quot;FileLog_KS300&quot;, &quot;8:::&quot;);;\
  define KS300_W_notify notify KS300:wind.* {\
  my $event = sprintf(&quot;Wc:&#160;%0.1f Wd:&#160;%0.1f Wm:&#160;%0.1f Wlh:&#160;%0.1f&quot;, ReadingsVal(&quot;KS300&quot;,&quot;wind&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;W_avg_day&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;W_avg_month&quot;,&quot;0&quot;), $avg);;\
  my $avg = myAverage(&quot;10800&quot;, &quot;FileLog_KS300&quot;, &quot;8:::&quot;);;\
  Log 2, (&quot;K300_W: Event: $event&quot;);;\
  my $event = sprintf(&quot;Wc:&#160;%0.1f Wd:&#160;%0.1f Wm:&#160;%0.1f Wlh:&#160;%0.1f&quot;, ReadingsVal(&quot;KS300&quot;,&quot;wind&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;W_avg_day&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;W_avg_month&quot;,&quot;0&quot;), $avg);;\
  fhem(&quot;trigger KS300_W data: $event&quot;);;\
  Log 2, (&quot;K300_W: Event: $event&quot;);;\
  fhem(&quot;trigger KS300 average-wind: $avg&quot;);;\
  fhem(&quot;trigger KS300_W data: $event&quot;);;\
}
  fhem(&quot;trigger KS300 average-wind: $avg&quot;);;\
define FileLog_KS300_W FileLog ./log/KS300_W-%Y.log KS300_W.*
  }
attr FileLog_KS300_W logtype text
  define FileLog_KS300_W FileLog ./log/KS300_W-%Y.log KS300_W.*
attr FileLog_KS300_W room Logs
  attr FileLog_KS300_W logtype text
define KS300_TH dummy
  attr FileLog_KS300_W room Logs
define KS300_TH_notify notify KS300:temperature.* {\
  define KS300_TH dummy
  my $avg = myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;);;\
  define KS300_TH_notify notify KS300:temperature.* {\
  my $event = sprintf(&quot;Tc:&#160;%0.1f Hc:&#160;%0.1f Tavg:&#160;%0.1f&quot;, ReadingsVal(&quot;KS300&quot;,&quot;temperature&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;humidity&quot;,&quot;0&quot;), $avg);;\
  my $avg = myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;);;\
  Log 2, (&quot;K300_TH: Event: $event&quot;);;\
  my $event = sprintf(&quot;Tc:&#160;%0.1f Hc:&#160;%0.1f Tavg:&#160;%0.1f&quot;, ReadingsVal(&quot;KS300&quot;,&quot;temperature&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;humidity&quot;,&quot;0&quot;), $avg);;\
  fhem(&quot;trigger KS300_TH data: $event&quot;);;\
  Log 2, (&quot;K300_TH: Event: $event&quot;);;\
  fhem('trigger KS300 average-temp: '.$avg);;\
  fhem(&quot;trigger KS300_TH data: $event&quot;);;\
}
  fhem('trigger KS300 average-temp: '.$avg);;\
define FileLog_KS300_TH FileLog ./log/KS300_TH-%Y.log KS300_TH.*
  }
attr FileLog_KS300_TH logtype text
  define FileLog_KS300_TH FileLog ./log/KS300_TH-%Y.log KS300_TH.*
attr FileLog_KS300_TH room Logs</nowiki>
  attr FileLog_KS300_TH logtype text
  attr FileLog_KS300_TH room Logs</nowiki>
Die Definitionen für die Plots schauen so aus:
Die Definitionen für die Plots schauen so aus:


  <nowiki>define weblink_KS300_W weblink fileplot FileLog_KS300_W:ks300_wind:CURRENT
  <nowiki>define weblink_KS300_W weblink fileplot FileLog_KS300_W:ks300_wind:CURRENT
attr weblink_KS300_W label &quot;Wind - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
  attr weblink_KS300_W label &quot;Wind - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
attr weblink_KS300_W room Dachterasse
  attr weblink_KS300_W room Dachterasse
define weblink_KS300_TH weblink fileplot FileLog_KS300_TH:ks300_temphum:CURRENT
  define weblink_KS300_TH weblink fileplot FileLog_KS300_TH:ks300_temphum:CURRENT
attr weblink_KS300_TH label &quot;Temperatur/Feuchte - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
  attr weblink_KS300_TH label &quot;Temperatur/Feuchte - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
attr weblink_KS300_TH room Dachterasse
  attr weblink_KS300_TH room Dachterasse
define weblink_KS300_R weblink fileplot FileLog_KS300:ks300_rain10:CURRENT
  define weblink_KS300_R weblink fileplot FileLog_KS300:ks300_rain10:CURRENT
attr weblink_KS300_R label &quot;Regen - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
  attr weblink_KS300_R label &quot;Regen - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
attr weblink_KS300_R room Dachterasse</nowiki>
  attr weblink_KS300_R room Dachterasse
</syntaxhighlight>


==== Angepasste gplot-Dateien ====
==== Angepasste gplot-Dateien ====
Zeile 205: Zeile 334:
ks300_wind.gplot
ks300_wind.gplot


<nowiki>############################
<syntaxhighlight lang="Perl">
# Display the wind values of a KS300.
  ############################
# Corresponding FileLog definition:
  # Display the wind values of a KS300.
# define &lt;filelogname&gt; FileLog ./log/KS300_W-%Y.log KS300_W.*
  # Corresponding FileLog definition:
set terminal png transparent size &lt;SIZE&gt; crop
  # define &lt;filelogname&gt; FileLog ./log/KS300_W-%Y.log KS300_W.*
set output '&lt;OUT&gt;.png'
  set terminal png transparent size &lt;SIZE&gt; crop
set xdata time
  set output '&lt;OUT&gt;.png'
set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
  set xdata time
set xlabel &quot; &quot;
  set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
set ytics nomirror
  set xlabel &quot; &quot;
#set y2tics
  set ytics nomirror
set ytics
  #set y2tics
set title '&lt;L1&gt;'
  set ytics
set grid xtics ytics
  set title '&lt;L1&gt;'
set yrange [0:]
  set grid xtics ytics
set y2range [0:]
  set yrange [0:]
set ylabel &quot;Wind (aktuell) [km/h]&quot;
  set y2range [0:]
set y2label &quot;Wind (gemittelt) [km/h]&quot;
  set ylabel &quot;Wind (aktuell) [km/h]&quot;
set format y2 &quot;%0.1f&quot;
  set y2label &quot;Wind (gemittelt) [km/h]&quot;
#FileLog 5:Wc\x3a:0:
  set format y2 &quot;%0.1f&quot;
#FileLog 11:Wlh\x3a:0:
  #FileLog 5:Wc\x3a:0:
plot &quot;&lt;IN&gt;&quot; using 1:5 ls l7fill axes x1y1 title 'Wind (aktuell)' with lines,\
  #FileLog 11:Wlh\x3a:0:
    &quot;&lt;IN&gt;&quot; using 1:11 ls l5 axes x1y2 title 'Wind (3h-Mittel)' with steps,\</nowiki>
  plot &quot;&lt;IN&gt;&quot; using 1:5 ls l7fill axes x1y1 title 'Wind (aktuell)' with lines,\
    &quot;&lt;IN&gt;&quot; using 1:11 ls l5 axes x1y2 title 'Wind (3h-Mittel)' with steps,\</syntaxhighlight>


ks300_temphum.gplot
ks300_temphum.gplot
 
<syntaxhighlight lang="Perl">
<nowiki>############################
  ############################
# Display the Temperature und Humidity values of a KS300.
  # Display the Temperature und Humidity values of a KS300.
# Corresponding FileLog definition:
  # Corresponding FileLog definition:
# define &lt;filelogname&gt; FileLog ./log/KS300_TH-%Y.log KS300_TH.*
  # define &lt;filelogname&gt; FileLog ./log/KS300_TH-%Y.log KS300_TH.*
set terminal png transparent size &lt;SIZE&gt; crop
  set terminal png transparent size &lt;SIZE&gt; crop
set output '&lt;OUT&gt;.png'
  set output '&lt;OUT&gt;.png'
set xdata time
  set xdata time
set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
  set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
set xlabel &quot; &quot;
  set xlabel &quot; &quot;
set ytics nomirror
  set ytics nomirror
set y2tics
  set y2tics
#set ytics
  #set ytics
set title '&lt;L1&gt;'
  set title '&lt;L1&gt;'
set grid xtics y2tics
  set grid xtics y2tics
set y2label &quot;Temperatur [°C]&quot;
  set y2label &quot;Temperatur [°C]&quot;
set ylabel &quot;Luftfeuchte [%]&quot;
  set ylabel &quot;Luftfeuchte [%]&quot;
#FileLog 5:Tc\x3a:0:
  #FileLog 5:Tc\x3a:0:
#FileLog 9:Tavg\x3a:0:
  #FileLog 9:Tavg\x3a:0:
#FileLog 7:Hc\x3a:0:
  #FileLog 7:Hc\x3a:0:
plot &quot;&lt;IN&gt;&quot; using 1:5 axes x1y2 ls l0 title 'Temperatur' with lines,\
  plot &quot;&lt;IN&gt;&quot; using 1:5 axes x1y2 ls l0 title 'Temperatur' with lines,\
    &quot;&lt;IN&gt;&quot; using 1:9 axes x1y2 ls l10 title 'Temperatur (24h-Mittel)' with lines,\
    &quot;&lt;IN&gt;&quot; using 1:9 axes x1y2 ls l10 title 'Temperatur (24h-Mittel)' with lines,\
    &quot;&lt;IN&gt;&quot; using 1:7 axes x1y1 ls l2fill title 'Luftfeuchte' with lines</nowiki>
    &quot;&lt;IN&gt;&quot; using 1:7 axes x1y1 ls l2fill title 'Luftfeuchte' with lines
</syntaxhighlight>
Den Plot für den Regen habe ich noch um Punkt-Plots für den israining-Status erweitert, so dass Punkte bei "yes" geplottet werden.
Den Plot für den Regen habe ich noch um Punkt-Plots für den israining-Status erweitert, so dass Punkte bei "yes" geplottet werden.


ks300_rain10.gplot
ks300_rain10.gplot
 
<syntaxhighlight lang="Perl">
<nowiki>############################
  ############################
# Display the Rain values of a KS300.
  # Display the Rain values of a KS300.
# Corresponding FileLog definition:
  # Corresponding FileLog definition:
# define &lt;filelogname&gt; FileLog ./log/KS300-%Y.log KS300:T:.*
  # define &lt;filelogname&gt; FileLog ./log/KS300-%Y.log KS300:T:.*
set terminal png transparent size &lt;SIZE&gt; crop
  set terminal png transparent size &lt;SIZE&gt; crop
set output '&lt;OUT&gt;.png'
  set output '&lt;OUT&gt;.png'
set xdata time
  set xdata time
set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
  set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
set xlabel &quot; &quot;
  set xlabel &quot; &quot;
set ytics nomirror
  set ytics nomirror
#set y2tics
  #set y2tics
set ytics
  set ytics
set title '&lt;L1&gt;'
  set title '&lt;L1&gt;'
set grid xtics ytics
  set grid xtics ytics
set y2range [0.5:1.5]
  set y2range [0.5:1.5]
set y2tics (&quot;&quot; 0, &quot;Regen&quot; 1)
  set y2tics (&quot;&quot; 0, &quot;Regen&quot; 1)
set pointsize 7
  set pointsize 7
set ylabel &quot;Regen (l/m²)&quot;
  set ylabel &quot;Regen (l/m²)&quot;
set yrange [0:]
  set yrange [0:]
# Computing Rain/h and Rain/d values by accumulating the changes.
  # Computing Rain/h and Rain/d values by accumulating the changes.
#FileLog 10:IR\x3a:0:delta-h
  #FileLog 10:IR\x3a:0:delta-h
#FileLog 10:IR\x3a:0:delta-d
  #FileLog 10:IR\x3a:0:delta-d
#FileLog 12:IR:0:$fld[11]=~&quot;yes&quot;?1:0
  #FileLog 12:IR:0:$fld[11]=~&quot;yes&quot;?1:0
plot &quot;&lt;grep -v avg_ &lt;IN&gt; | perl -ane '\
  plot &quot;&lt;grep -v avg_ &lt;IN&gt; | perl -ane '\
    @a = split(\&quot;[_:]\&quot;, $F[0]);\
      @a = split(\&quot;[_:]\&quot;, $F[0]);\
    if(defined($lh) &amp;&amp; $lh ne $a[1])\
      if(defined($lh) &amp;&amp; $lh ne $a[1])\
      { printf(\&quot;${ld}_$lh:30:00&#160;%f\n\&quot;, $hv); $hv = 0; }\
      { printf(\&quot;${ld}_$lh:30:00&#160;%f\n\&quot;, $hv); $hv = 0; }\
    if($lv) { $hv += ($F[9]-$lv); }\
      if($lv) { $hv += ($F[9]-$lv); }\
    $lh = $a[1]; $ld = $a[0]; $lv = $F[9];\
      $lh = $a[1]; $ld = $a[0]; $lv = $F[9];\
    END { printf(\&quot;${ld}_$lh:30:00&#160;%f\n\&quot;, $hv) }'&quot;\
      END { printf(\&quot;${ld}_$lh:30:00&#160;%f\n\&quot;, $hv) }'&quot;\
    using 1:2 axes x1y1 ls l1fill title 'Regen pro Stunde' with histeps,\
    using 1:2 axes x1y1 ls l1fill title 'Regen pro Stunde' with histeps,\
    &quot;&lt;grep -v avg_ &lt;IN&gt; | perl -ane '\
    &quot;&lt;grep -v avg_ &lt;IN&gt; | perl -ane '\
    @a = split(\&quot;[_]\&quot;, $F[0]);\
      @a = split(\&quot;[_]\&quot;, $F[0]);\
    if(defined($ld) &amp;&amp; $ld ne $a[0]) {\
      if(defined($ld) &amp;&amp; $ld ne $a[0]) {\
      printf(\&quot;${ld}_12:00:00&#160;%f\n\&quot;, $dv); $dv = 0; }\
      printf(\&quot;${ld}_12:00:00&#160;%f\n\&quot;, $dv); $dv = 0; }\
      if($lv) { $dv += ($F[9]-$lv); }\
      if($lv) { $dv += ($F[9]-$lv); }\
      $ld = $a[0]; $lv = $F[9];\
      $ld = $a[0]; $lv = $F[9];\
      END {printf(\&quot;${ld}_12:00:00&#160;%f\n\&quot;, $dv)}'&quot;\
      END {printf(\&quot;${ld}_12:00:00&#160;%f\n\&quot;, $dv)}'&quot;\
    using 1:2 axes x1y1 ls l7 title 'Regen pro Tag' with histeps
    using 1:2 axes x1y1 ls l7 title 'Regen pro Tag' with histeps
    &quot;&lt;grep -v avg_ &lt;IN&gt; | awk '{print $1, $12==\&quot;yes\&quot;? 1&#160;: 0; }'&quot;\
    &quot;&lt;grep -v avg_ &lt;IN&gt; | awk '{print $1, $12==\&quot;yes\&quot;? 1&#160;: 0; }'&quot;\
    using 1:2 axes x1y2 ls l11fill title 'Regen aktuell' with points</nowiki>
    using 1:2 axes x1y2 ls l11fill title 'Regen aktuell' with points
</syntaxhighlight>
Eventuell müssen in den gplots noch die Linienstile/Farben den eigenen Bedürfnissen angepasst werden. Ich verwende hier eigene Farben und Füllungen.
Eventuell müssen in den gplots noch die Linienstile/Farben den eigenen Bedürfnissen angepasst werden. Ich verwende hier eigene Farben und Füllungen.



Aktuelle Version vom 12. April 2023, 17:47 Uhr

Für verschiedene Wettersensoren ist es oftmals wünschenswert, gleitende Mittelwerte (fälschlich auch als "relative" Mittelwerte bezeichnet) über ein bestimmtes Zeitfenster auszugeben oder in den Logs zu speichern und zu plotten. Beispiele:

  • Die momentanen Winddaten eines KS300 sind oft stark schwankend. Hier wäre ein Mittelwert z.B. der letzten 2-3 Stunden interessant.
  • Für die Außentemperatur ist anhand eines Mittelwertes der letzten 24 Stunden evtl. ein gewisser Trend der generellen Entwicklung der Temperatur ableitbar.

Mit Modulen wie rain oder average ist konzeptbedingt nur ein Mittelwert von fixen Zeiträumen (aktueller Tag, Monat, etc.) möglich.

Im Nachfolgenden werden deshalb zwei Programme vorgestellt, die solche gleitenden Mittelwerte bereitstellen. Beide Programme werden in die Datei 99_MyUtils.pm eingetragen und dann von FHEM aufgerufen. Das erste Programm macht dies für beliebige Readings beliebiger Devices, das zweite Programm greift auf beliebige File- oder DbLogs zurück.

Gleitender Mittelwert für beliebige Readings

Subroutine movingAverage in 99_MyUtils anlegen

Es werden die Funktionen movingAverage und movingAverageT in die 99_MyUtils Datei übernommen werden. Wenn noch nicht vorhanden, sollte die Datei dann so aussehen, der übliche Speicherort ist /opt/fhem/FHEM/99_MyUtils.pm

package main;
  use strict;
  use warnings;
  sub
  MyUtils_Initialize($$)
  {
   my ($hash) = @_;
  }
  ###############################################################################
  #
  #  Moving average 
  #
  #  Aufruf: movingAverage(devicename,readingname,zeitspanne in s)
  #
  ###############################################################################
  
  sub movingAverage($$$){
     my ($name,$reading,$avtime) = @_;
     my $hash = $defs{$name};
     my @new = my ($val,$time) = ($hash->{READINGS}{$reading}{VAL},$hash->{READINGS}{$reading}{TIME});
     my ($cyear, $cmonth, $cday, $chour, $cmin, $csec) = $time =~ /(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)/;
     my $ctime = $csec+60*$cmin+3600*$chour;
     my $num;
     my $arr;
     #-- initialize if requested
     if( ($avtime eq "-1") ){
       $hash->{READINGS}{$reading}{"history"}=undef;
     }
     #-- test for existence
     if( !$hash->{READINGS}{$reading}{"history"}){
        #Log 1,"ARRAY CREATED";
        push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
        $num = 1;
        $arr=\@{$hash->{READINGS}{$reading}{"history"}};
     } else {
        $num = int(@{$hash->{READINGS}{$reading}{"history"}});
        $arr=\@{$hash->{READINGS}{$reading}{"history"}};
        my $starttime = $arr->[0][1];
        my ($syear, $smonth, $sday, $shour, $smin, $ssec) = $starttime =~ /(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)/;
        my $stime = $ssec+60*$smin+3600*$shour;
        #-- correct for daybreak
        $stime-=86400 
          if( $stime > $ctime);
        if( ($num < 25)&&( ($ctime-$stime)<$avtime) ){
          #Log 1,"ARRAY has $num elements, adding another one";
          push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
        }else{
          shift(@{$hash->{READINGS}{$reading}{"history"}});
          push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
        }
      }
      #-- output and average
      my $average = 0;
      for(my $i=0;$i<$num;$i++){
        $average+=$arr->[$i][0];
        Log 4,"[$name moving average] Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1]; 
      }
      $average=sprintf( "%5.3f", $average/$num);
      #--average
      Log 4,"[$name moving average] calculated over $num values is $average";  
      return $average;
   }

  ###############################################################################
  #
  #  Moving average of last X seconds, weigh values by time
  #
  #  movingAverageT(Devicename, Readingname, HistoryLength)
  #
  ###############################################################################
  sub movingAverageT($$$){
    my ($name, $reading, $hSeconds) = @_;
    my $hash = $defs{$name};
    my @new = my ($val, $time) = ($hash->{READINGS}{$reading}{VAL}, $hash->{READINGS}{$reading}{TIME});
    
    #create new hash "history" if not already existing
    if ( !$hash->{READINGS}{$reading}{"history"}) {
      @{$hash->{READINGS}{$reading}{"history"}} = \@new;
    }

    #get reference to hash "history"
    my $arr = \@{$hash->{READINGS}{$reading}{"history"}};

    #just add values if timestamp differs to last item in history
    if( "$time" ne "$arr->[-1][1]" ) {
      #append the new entry
      push(@{$hash->{READINGS}{$reading}{"history"}}, \@new);
      
      #remove entries that are too old
      while( time() - time_str2num((\@{$hash->{READINGS}{$reading}{"history"}})->[0][1]) > $hSeconds ) {
        #Log 1,"[$name:$reading] removing reading, it is too old";
        shift(@{$hash->{READINGS}{$reading}{"history"}});
      }
    }
    
    #length of current history
    my $aLength = int(@{$hash->{READINGS}{$reading}{"history"}});
    
    #if history currently consists of a single value, just return it
    if ($aLength <= 1) {
      #Log 1,"[$name:$reading] Average of a single value is the single value: $val";
      return sprintf("%5.3f", $val);
    }

    #sum up all values multiplied with fraction of duration, a value that was valid longer weighs more
    my $average = 0;
    my $oldest = $arr->[0][1];
    my $newest = $arr->[-1][1];
    #Log 1,"[$name:$reading] Oldest Timestamp = ".$oldest.", Newest Timestamp: ".$newest;

    $oldest = time_str2num($oldest);
    $newest = time_str2num($newest);
    my $timespan = $newest - $oldest;
    #Log 1,"[$name:$reading] Total Timespan: ".$timespan;
    
    #iterate of the array from index 0 to N-1
    for(my $i=0; $i<$aLength-1; $i++){
      #Log 1,"[$name:$reading] Index: $i, Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];
     
      my $thisTime = time_str2num($arr->[$i][1]);
      my $nextTime = time_str2num($arr->[$i+1][1]);
      my $diffTime = $nextTime - $thisTime;
      my $fraction = $diffTime / $timespan;
      
      #Log 1,"[$name:$reading] diffTime = $diffTime, Fraction: $fraction";
      
      $average += $arr->[$i][0] * $fraction;
    }
    #Log 1,"[$name:$reading] Average: $average";

    return sprintf("%5.3f", $average);;
  }
    
  1;

Funktion movingAverage() aufrufen und nutzen

Bei jedem Aufruf schiebt die obige Funktion den gegenwärtigen Wert sowie den Zeitpunkt eines Readings (zweiter Parameter) ans Ende eines Arrays (maximal 25 Einträge) und wirft dafür den ersten Eintrag heraus. Die Anzahl der Einträge im Array wird so gewählt, dass die Zeitpunkte der Readings größer oder gleich der gewünschten Zeitspanne für die Mittelung sind. Über diese Werte wird dann gemittelt und der Wert zurückgegeben.

Zur Anwendung empfiehlt sich, dem gewünschten Device ein userReading zu geben:

attr devicename userReadings readingname.av {movingAverage("devicename","readingname",zeitspanne)}

Dieses wird dann automatisch bei jedem neuen Wert des Readings mit dem neuen gleitenden Mittelwert über die vergangene Zeitspanne gefüllt.

Beispiel: Das Reading "pressure" des Devices "A.OWB" wird alle 5 Minuten aktualisiert. Das Device bekommt nun ein userReading:

attr A.OWB userReadings pressure.av {movingAverage("A.OWB","pressure",1800)}

Dieses schreibt bei jeder Aktualisierung von "pressure" einen neuen Eintrag in sein Array - allerdings maximal sechs Werte, dann wird immer der älteste Eintrag gelöscht. Damit ist sichergestellt, dass diese mindestens 1800 Sekunden abdecken - also sechs Einträge.

Funktion movingAverageT() aufrufen und nutzen

Bei jedem Aufruf schiebt die Funktion movingAverageT den gegenwärtigen Wert sowie den Zeitpunkt eines Readings (zweiter Parameter) ans Ende eines Arrays und verwirft Einträge die älter als das Zeitlimit aus dem dritten Parameter sind. Die Anzahl der Einträge im Array ist nicht limitiert und es wird nur anhand des Zeitstempels entschieden, ob ein Wert im Array verbleibt. Über die Werte im Array wird dann gemittelt und der Wert zurückgegeben.

Zur Anwendung empfiehlt sich, dem gewünschten Device ein userReading zu geben:

attr devicename userReadings readingname.av {movingAverageT("devicename", "readingname", Zeitspanne_in_Sekunden)}

Dieses wird dann automatisch bei jedem neuen Wert des Readings mit dem neuen gleitenden Mittelwert über die vergangene Zeitspanne gefüllt.

Beispiel: Das Reading "pressure" des Devices "A.OWB" wird alle 5 Minuten aktualisiert. Das Device bekommt nun ein userReading:

attr A.OWB userReadings pressure.av {movingAverageT("A.OWB", "pressure", 1800)}

Dieses schreibt bei jeder Aktualisierung von "pressure" einen neuen Eintrag an das ende seines Array und löscht zu alte Werte am Anfang des Array wieder heraus.

Gleitender Mittelwert aus Log-Dateien

Subroutine myAverage() in 99_MyUtils anlegen

Zuerst muss die Subroutine myAverage in die 99_MyUtils übernommen werden. Wenn noch nicht vorhanden, sollte die Datei dann so aussehen:

 package main;
  use strict;
  use warnings;
  sub
  MyUtils_Initialize($$)
  {
   my ($hash) = @_;
  }
  ##########################################################
  # myAverage
  # berechnet den Mittelwert aus LogFiles über einen beliebigen Zeitraum
  sub
  myAverage($$$)
  {
   my ($offset,$logfile,$cspec) = @_;
   my $period_s = strftime &quot;%Y-%m-%d\x5f%H:%M:%S&quot;, localtime(time-$offset);
   my $period_e = strftime &quot;%Y-%m-%d\x5f%H:%M:%S&quot;, localtime;
   my $oll = $attr{global}{verbose};
   $attr{global}{verbose} = 0; 
   my @logdata = split(&quot;\n&quot;, fhem(&quot;get $logfile - - $period_s $period_e $cspec&quot;));
   $attr{global}{verbose} = $oll; 
   my ($cnt, $cum, $avg) = (0)x3;
   foreach (@logdata){
    my @line = split(&quot; &quot;, $_);
    if(defined $line[1] &amp;&amp; $line[1] ne &quot;&quot;){
     $cnt += 1;
     $cum += $line[1];
    }
   }
   if(&quot;$cnt&quot; &gt; 0){$avg = sprintf(&quot;%0.1f&quot;, $cum/$cnt)};
   Log 4, (&quot;myAverage: File: $logfile, Field: $cspec, Period: $period_s bis $period_e, Count: $cnt, Cum: $cum, Average: $avg&quot;);
   return $avg;
  }
  ##########################################################
  1;

Funktion myAverage() aufrufen und nutzen

Jetzt kann die Funktion myAverage(<offset>, <logfile>, <column-spec>) ganz normal auf der FHEM-Konsole (telnet localhost 7072) genutzt werden. Hier z.B. die Ausgabe des Mittelwertes des Windsensors, eines KS300, über die letzten drei Stunden (Angabe in 3*3600s) oder der Temperaturmittelwert der letzten 24 Stunden.

Mit FileLog:

fhem> {myAverage("10800", "FileLog_KS300", "8:::")}
  6.3
  fhem> {myAverage("86400", "FileLog_KS300", "4:::")}
  -2.7
  fhem>

Mit DbLog:

fhem> {myAverage("10800", "myDbLog", "KS300:wind::")}
  5.9
  fhem> {myAverage("86400", "myDbLog", "KS300:temperature::")}
  -2.7
  fhem>

Ähnlich ist die Verwendung in perl-Code in der fhem.cfg:

my $avg = myAverage("10800", "FileLog_KS300", "8:::");;\

Ein notify, welches bei jedem neuen Sensorwert die Mitteltemperatur der letzten 24 Stunden als Event "average-temp xy" ins Logfile schreibt, sieht dann wie folgt aus:

define KS300_T_notify notify KS300:temperature.* {\
   fhem('trigger KS300 average-temp: '.myAverage("86400", "FileLog_KS300", "4:::"));;\
  }

Analog dazu wäre mit den Winddaten zu verfahren.

Gleitender Mittelwert und Minimum und Maximum aus Log-Dateien

Manchmal interessieren einem auch Minimum und Maximum Werte über einen bestimmten Zeitraum, die Funktion myAverage kann einfach entsprechend abgeändert werden:

Subroutine avgMinMaxAusLog() in 99_MyUtils anlegen

  # Berechnet den Mittelwert, Minimum und Maximum aus LogFiles über einen beliebigen Zeitraum
  # Args: $offset = Offset in Sekunden
  #		$logfile = LogFile, z.B. DbLog Device
  #		$cspec = column-spec, siehe https://fhem.de/commandref_DE.html#DbLog -> get
  sub avgMinMaxAusLog($$$) {
  	my($offset, $logfile, $cspec) = @_;
  	my $period_s = strftime "%Y-%m-%d\x5f%H:%M:%S", localtime(time - $offset);
  	my $period_e = strftime "%Y-%m-%d\x5f%H:%M:%S", localtime;
  	my $oll = $attr { global } { verbose };
  	$attr { global } { verbose } = 0;
  	my @logdata = split("\n", fhem("get $logfile - - $period_s $period_e $cspec"));
  	$attr { global } { verbose } = $oll;
  	my ($cnt, $cum, $avg, $min, $max) = (0)x5;
  	foreach(@logdata) {
  		my @line = split(" ", $_);
  		if (defined $line[1] && $line[1] ne "") {
  			$cnt += 1;
  			$cum += $line[1];
  			# Beim ersten Eintrag min/max setzen
  			if ($cnt == 1) {
  				$min = $line[1];
  				$max = $line[1];
  			# Bei den darauffolgenden Einträgen $min/max ermitteln
  			} else {
  				if ($line[1] < $min) { $min = $line[1]; }
  				if ($line[1] > $max) { $max = $line[1]; }
  			}
  		}
  	}
  	if ("$cnt" > 0) {
  		$avg = sprintf("%0.1f", $cum / $cnt)
  	};
  	Log 4, ("avgMinMaxAusLog: File: $logfile, Field: $cspec, Period: $period_s bis $period_e, Count: $cnt, Cum: $cum, Average: $avg, Minimum: $min, Maximum: $max");
  	return ($avg, $min, $max);
  }

Anwendung wie oben. Statt nur dem Mittelwert wird eine Liste zurückgeliefert mit Mittelwert, Minimum und Maximum. Verwendung z.B. wie folgt:

my ($avg, $min, $max) = myAverage("10800", "FileLog_KS300", "8:::");;\

Eigenes Filelog und Plot

Definitionen

Ich schreibe ein komplett eigenes Logfile und lasse über eine spezielle gplot-Definition die Temperatur- und Winddaten loggen und plotten.

define KS300_W dummy
  define KS300_W_notify notify KS300:wind.* {\
   my $avg = myAverage(&quot;10800&quot;, &quot;FileLog_KS300&quot;, &quot;8:::&quot;);;\
   my $event = sprintf(&quot;Wc:&#160;%0.1f Wd:&#160;%0.1f Wm:&#160;%0.1f Wlh:&#160;%0.1f&quot;, ReadingsVal(&quot;KS300&quot;,&quot;wind&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;W_avg_day&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;W_avg_month&quot;,&quot;0&quot;), $avg);;\
   Log 2, (&quot;K300_W: Event: $event&quot;);;\
   fhem(&quot;trigger KS300_W data: $event&quot;);;\
   fhem(&quot;trigger KS300 average-wind: $avg&quot;);;\
  }
  define FileLog_KS300_W FileLog ./log/KS300_W-%Y.log KS300_W.*
  attr FileLog_KS300_W logtype text
  attr FileLog_KS300_W room Logs
  define KS300_TH dummy
  define KS300_TH_notify notify KS300:temperature.* {\
   my $avg = myAverage(&quot;86400&quot;, &quot;FileLog_KS300&quot;, &quot;4:::&quot;);;\
   my $event = sprintf(&quot;Tc:&#160;%0.1f Hc:&#160;%0.1f Tavg:&#160;%0.1f&quot;, ReadingsVal(&quot;KS300&quot;,&quot;temperature&quot;,&quot;0&quot;), ReadingsVal(&quot;KS300&quot;,&quot;humidity&quot;,&quot;0&quot;), $avg);;\
   Log 2, (&quot;K300_TH: Event: $event&quot;);;\
   fhem(&quot;trigger KS300_TH data: $event&quot;);;\
   fhem('trigger KS300 average-temp: '.$avg);;\
  }
  define FileLog_KS300_TH FileLog ./log/KS300_TH-%Y.log KS300_TH.*
  attr FileLog_KS300_TH logtype text
  attr FileLog_KS300_TH room Logs</nowiki>
Die Definitionen für die Plots schauen so aus:

 <nowiki>define weblink_KS300_W weblink fileplot FileLog_KS300_W:ks300_wind:CURRENT
  attr weblink_KS300_W label &quot;Wind - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
  attr weblink_KS300_W room Dachterasse
  define weblink_KS300_TH weblink fileplot FileLog_KS300_TH:ks300_temphum:CURRENT
  attr weblink_KS300_TH label &quot;Temperatur/Feuchte - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
  attr weblink_KS300_TH room Dachterasse
  define weblink_KS300_R weblink fileplot FileLog_KS300:ks300_rain10:CURRENT
  attr weblink_KS300_R label &quot;Regen - Min: $data{min1}, Max: $data{max1}, Last: $data{currval1}&quot;
  attr weblink_KS300_R room Dachterasse

Angepasste gplot-Dateien

Nun noch die speziellen gplot-Dateien. ks300_wind.gplot

  ############################
  # Display the wind values of a KS300.
  # Corresponding FileLog definition:
  # define &lt;filelogname&gt; FileLog ./log/KS300_W-%Y.log KS300_W.*
  set terminal png transparent size &lt;SIZE&gt; crop
  set output '&lt;OUT&gt;.png'
  set xdata time
  set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
  set xlabel &quot; &quot;
  set ytics nomirror
  #set y2tics
  set ytics
  set title '&lt;L1&gt;'
  set grid xtics ytics
  set yrange [0:]
  set y2range [0:]
  set ylabel &quot;Wind (aktuell) [km/h]&quot;
  set y2label &quot;Wind (gemittelt) [km/h]&quot;
  set format y2 &quot;%0.1f&quot;
  #FileLog 5:Wc\x3a:0:
  #FileLog 11:Wlh\x3a:0:
  plot &quot;&lt;IN&gt;&quot; using 1:5 ls l7fill axes x1y1 title 'Wind (aktuell)' with lines,\
     &quot;&lt;IN&gt;&quot; using 1:11 ls l5 axes x1y2 title 'Wind (3h-Mittel)' with steps,\

ks300_temphum.gplot

  ############################
  # Display the Temperature und Humidity values of a KS300.
  # Corresponding FileLog definition:
  # define &lt;filelogname&gt; FileLog ./log/KS300_TH-%Y.log KS300_TH.*
  set terminal png transparent size &lt;SIZE&gt; crop
  set output '&lt;OUT&gt;.png'
  set xdata time
  set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
  set xlabel &quot; &quot;
  set ytics nomirror
  set y2tics
  #set ytics
  set title '&lt;L1&gt;'
  set grid xtics y2tics
  set y2label &quot;Temperatur [°C]&quot;
  set ylabel &quot;Luftfeuchte [%]&quot;
  #FileLog 5:Tc\x3a:0:
  #FileLog 9:Tavg\x3a:0:
  #FileLog 7:Hc\x3a:0:
  plot &quot;&lt;IN&gt;&quot; using 1:5 axes x1y2 ls l0 title 'Temperatur' with lines,\
     &quot;&lt;IN&gt;&quot; using 1:9 axes x1y2 ls l10 title 'Temperatur (24h-Mittel)' with lines,\
     &quot;&lt;IN&gt;&quot; using 1:7 axes x1y1 ls l2fill title 'Luftfeuchte' with lines

Den Plot für den Regen habe ich noch um Punkt-Plots für den israining-Status erweitert, so dass Punkte bei "yes" geplottet werden.

ks300_rain10.gplot

  ############################
  # Display the Rain values of a KS300.
  # Corresponding FileLog definition:
  # define &lt;filelogname&gt; FileLog ./log/KS300-%Y.log KS300:T:.*
  set terminal png transparent size &lt;SIZE&gt; crop
  set output '&lt;OUT&gt;.png'
  set xdata time
  set timefmt &quot;%Y-%m-%d_%H:%M:%S&quot;
  set xlabel &quot; &quot;
  set ytics nomirror
  #set y2tics
  set ytics
  set title '&lt;L1&gt;'
  set grid xtics ytics
  set y2range [0.5:1.5]
  set y2tics (&quot;&quot; 0, &quot;Regen&quot; 1)
  set pointsize 7
  set ylabel &quot;Regen (l/)&quot;
  set yrange [0:]
  # Computing Rain/h and Rain/d values by accumulating the changes.
  #FileLog 10:IR\x3a:0:delta-h
  #FileLog 10:IR\x3a:0:delta-d
  #FileLog 12:IR:0:$fld[11]=~&quot;yes&quot;?1:0
  plot &quot;&lt;grep -v avg_ &lt;IN&gt; | perl -ane '\
      @a = split(\&quot;[_:]\&quot;, $F[0]);\
      if(defined($lh) &amp;&amp; $lh ne $a[1])\
       { printf(\&quot;${ld}_$lh:30:00&#160;%f\n\&quot;, $hv); $hv = 0; }\
      if($lv) { $hv += ($F[9]-$lv); }\
      $lh = $a[1]; $ld = $a[0]; $lv = $F[9];\
      END { printf(\&quot;${ld}_$lh:30:00&#160;%f\n\&quot;, $hv) }'&quot;\
     using 1:2 axes x1y1 ls l1fill title 'Regen pro Stunde' with histeps,\
     &quot;&lt;grep -v avg_ &lt;IN&gt; | perl -ane '\
      @a = split(\&quot;[_]\&quot;, $F[0]);\
      if(defined($ld) &amp;&amp; $ld ne $a[0]) {\
       printf(\&quot;${ld}_12:00:00&#160;%f\n\&quot;, $dv); $dv = 0; }\
       if($lv) { $dv += ($F[9]-$lv); }\
       $ld = $a[0]; $lv = $F[9];\
       END {printf(\&quot;${ld}_12:00:00&#160;%f\n\&quot;, $dv)}'&quot;\
     using 1:2 axes x1y1 ls l7 title 'Regen pro Tag' with histeps
     &quot;&lt;grep -v avg_ &lt;IN&gt; | awk '{print $1, $12==\&quot;yes\&quot;? 1&#160;: 0; }'&quot;\
     using 1:2 axes x1y2 ls l11fill title 'Regen aktuell' with points

Eventuell müssen in den gplots noch die Linienstile/Farben den eigenen Bedürfnissen angepasst werden. Ich verwende hier eigene Farben und Füllungen.