Erweiterung: movingAverage

Begonnen von peter_w, 13 September 2019, 20:48:46

Vorheriges Thema - Nächstes Thema

peter_w

Hallo zusammen,
ich konnte den Codeschnipsel aus dem WIKI:
https://wiki.fhem.de/wiki/Gleitende_Mittelwerte_berechnen_und_loggen
prima brauchen und habe den bei mir eingebaut. Da ich kein Perl Spezialist bin habe ich das darin versteckte "Problem" nicht sofort gesehen und mich über die Ergebnisse gewundert:
Die Funktion geht davon aus das maximal 25 Werte in der gewählten Zeit herein kommen.
Sollten mehr kommen, dann wird der Mittelwert über 25 Werte gebildet egal wie lange die Zeit gewählt wurde.
Da bei mir die 25 locker überschritten wurden, habe ich eine Konstante eingefügt mit der man das Limit ändern kann und eine Warnung in der LOG datei wenn das Limit überschritten wurde.
Vielleicht hilft es ja Jemandem.

sub movingAverage($$$)
{
   use constant MAXARRSIZE => 25;
   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 < MAXARRSIZE)&&( ($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 1,"$i: [$name moving average] Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];
    }
if($num == MAXARRSIZE-1)
{
  Log 4,"[$name moving average] maximum array size reached, maybe logging is to fast ?";
}
    $average=sprintf( "%5.3f", $average/$num);
    #--average
    Log 4,"[$name moving average] calculated over $num values is $average"; 
    return $average;
}


Release  : 5.8
Raspberry Pi 3
CUL V 1.63 CSM868 HomeMatic (SCC)
HM-CC-RT-DN,HM-LC-Bl1PBU-FM,HM-LC-Sw1PBU-FM,HM-SEC-SCo,HM-WDS10-TH-O

Prof. Dr. Peter Henning

ZitatDie Funktion geht davon aus das maximal 25 Werte in der gewählten Zeit herein kommen.
Sollten mehr kommen, dann wird der Mittelwert über 25 Werte gebildet egal wie lange die Zeit gewählt wurde.

Wieso sollte das ein Problem sein ? Im Gegenteil, ein gleitender Mittelwert über mehr als 25 Werte ist ein Problem - die Werte kommen dann viel zu häufig rein.

Eine Erweiterung oder Verbesserung sehe ich in dieser trivialen Änderung nicht.

LG

pah

Nobby1805

2 Fragen dazu:
1. wieso kommen Werte häufiger rein wenn man die Anzahl  der Werte von 25 auf z. B. 50 erhöht?
2. wo muss man Verbose setzen, damit die Logs aus der Subroutine dargestellt werden?

Grüße Nobby
FHEM-Featurelevel: 6   (fhem.pl:23904/2021-03-07) auf Windows 10 Pro mit Strawberry Perl 5.26.1.1-32bit
TabletUI: 2.7.15
IO: 2xHMLAN(0.965)|HMUSB2(0.967)

Torxgewinde

#3
Da der Code aus dem Wikieintrag zwar die Zeit als dritten Parameter übergeben bekommt, aber irgendwie dann doch nur die letzten 25 Werte berücksichtigt, habe ich die Funktion umgeändert und umbenannt in movingAverageN($$$) und einfach nur noch die letzten N-Werte anstatt der Zeit zum Mittelwert berechnen genommen. Das ist IMHO leichter verständlich, als die vorige Implementation.

Folglich sieht eine minimale 99_myUtils.pm so aus:
package main;
  use strict;
  use warnings;
  sub
  MyUtils_Initialize($$)
  {
   my ($hash) = @_;
  }
 
  ###############################################################################
  #
  #  Moving average of last N values
  #
  #  movingAverageN(Devicename, Readingname, HistoryLength)
  #
  ###############################################################################
 
  sub movingAverageN($$$){
    my ($name, $reading, $hLength) = @_;
    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);
   
      #shift until the maximum history size is reached
      while( int(@{$hash->{READINGS}{$reading}{"history"}}) > $hLength ) {
        shift(@{$hash->{READINGS}{$reading}{"history"}});
      }
    }
   
    #length of current history
    my $aLength = int(@{$hash->{READINGS}{$reading}{"history"}});

    #sum up all values and calculate the average
    my $average = 0;
    for(my $i=0; $i<$aLength; $i++){
      $average+=$arr->[$i][0];
      #Log 1,"[$name:reading] Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];
    }
    $average = sprintf("%5.3f", $average/$aLength);

    #Log 1,"[$name:$reading] calculated over $aLength values is $average";
    return $average;
  }
 
  1;