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.2   (fhem.pl:28227/2023-11-29) auf Windows 10 Pro mit Strawberry Perl 5.32.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.

Das Bildschirmfoto zeigt einen gleitenden Mittelwert in grün einer Shelly-Zwischenstecker-Steckdose mit Leistungsmessung und Tasmota. Es wird über die letzten zehn Werte gemittelt.Du darfst diesen Dateianhang nicht ansehen.

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;

Zur Info wie es verwendet werden kann noch das define der Shellysteckdose, die über MQTT ausgelesen wird. Relevant ist das userReading.
defmod shellystecker01 MQTT2_DEVICE
attr shellystecker01 IODev Mosquitto
attr shellystecker01 alias Tiefkühlschrank
attr shellystecker01 devStateIcon on:on off:off offline:light_question
attr shellystecker01 devicetopic Keller/shellystecker01
attr shellystecker01 event-min-interval ENERGY_Power:10,.*:3600
attr shellystecker01 event-on-change-reading .*
attr shellystecker01 group Tiefkuehlschrank
attr shellystecker01 icon freezer
attr shellystecker01 readingList $DEVICETOPIC/tele/LWT:.*Offline { availability=>"offline" }\
$DEVICETOPIC/tele/LWT:.*Online { availability=>"online" }\
$DEVICETOPIC/tele/STATE:.* { json2nameValue($EVENT) }\
$DEVICETOPIC/tele/SENSOR:.* { json2nameValue($EVENT) }\
$DEVICETOPIC/tele/INFO1:.* { json2nameValue($EVENT) }\
$DEVICETOPIC/tele/INFO2:.* { json2nameValue($EVENT) }\
$DEVICETOPIC/tele/INFO3:.* { json2nameValue($EVENT) }\
\
$DEVICETOPIC/stat/POWER:.*OFF { state=>"off" }\
$DEVICETOPIC/stat/POWER:.*ON { state=>"on" }
attr shellystecker01 room Keller
attr shellystecker01 setList on $DEVICETOPIC/cmnd/POWER1 ON\
off $DEVICETOPIC/cmnd/POWER1 OFF
attr shellystecker01 stateFormat { if (ReadingsVal($name, "availability", "") eq "offline") {\
    return 'offline';;\
  } else {\
    return ReadingsVal($name, "state", "???");; }\
}
attr shellystecker01 userReadings Leistung_Mittelwert:ENERGY_Power.* {\
sprintf("%.1f", movingAverageN("$NAME", "ENERGY_Power", 10));;\
}

Torxgewinde

#4
Irgendwie ging mir das Verhalten der Wiki-Lösung gegen die Intuition. Deswegen habe ich zwei weitere Funktionen definiert: movingAverageT und movingAverageN2. Beide Funktionen gewichten, falls die Werte nicht in zeitlich gleichen Abständen reinkommen. Das heißt, wenn ein Wert lange gültig war, wiegt er schwerer als wenn ein Wert nur ganz kurz gültig war. Die Funktion movingAverageT entspricht dem, was ich eigentlich unter dem MovingAverage mit einem Zeitlimit verstanden habe. Es werden nur die Werte in MovingAverage berücksichtigt, die auch neu genug sind (der dritte Parameter) und es können beliebig viele Werte in der Zeit anfallen, die dann auch alle zeitlich gewichtet berücksichtigt werden.

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;
  }
 
  ###############################################################################
  #
  #  Moving average of last N values, weigh values by time
  #
  #  movingAverageN2(Devicename, Readingname, HistoryLength)
  #
  ###############################################################################
  sub movingAverageN2($$$){
    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"}});
   
    #if history currently consists of a single value, just return it
    if ($aLength <= 1) {
      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 over the array from index 0 to N-2
    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);;
  }
 
  ###############################################################################
  #
  #  Moving average of last X seconds, weigh values by time
  #
  #  movingAverageT(Devicename, Readingname, Seconds)
  #
  ###############################################################################
  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 over the array from index 0 to N-2
    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;

Zum Testen, habe ich folgendes Device genommen:
defmod ma dummy
attr ma readingList wert
attr ma setList wert
attr ma userReadings wert_av:wert.* {\
    sprintf("%.1f", movingAverageT("$NAME", "wert", 60));;\
}

fhem_apo

Bei mir funktioniert movingAverageT nicht richtig bzw. ich bekomme es nicht hin. Habe ein zigbee-Temperatursensor und möchte Mittelwerte der Temperatur auslesen (nach 10, 15 und 30 Minuten; später dann nach 1 und 2 Tagen).
Folgende userreadings habe ich angelegt:
temp.avT.10m {movingAverageT("TempSensWohnzimmer", "temperature", 600)},
temp.avT.15m {movingAverageT("TempSensWohnzimmer", "temperature", 900)},
temp.avT.30m {movingAverageT("TempSensWohnzimmer", "temperature", 1800)}
Die Werte sind aber immer gleich:
temp.avT.10m   23.982 2023-05-21 10:19:52
temp.avT.15m   23.982 2023-05-21 10:19:52
temp.avT.30m   23.982 2023-05-21 10:19:52
Was mache ich falsch? Kann man das Array irgendwie auslesen?

Danke für die Antwort(en) 

ps. movingAverage ohne "T" funktioniert. Da ich aber für einen Gartensensor über 2 Tage den Mittelwert benötige, benötige ich movingAverageT

fhem_apo

movingAverageT scheint immer auf das gleich array ..."history" zuzugreifen. Ich habe jetzt in der subroutine überall "history" zu "history".$hSeconds geändert, so dass für jeden Aufruf von movingAverageT für das gleiche Reading mit einem anderen Zeitintervall ein neues Array angelegt wird.   

Torxgewinde

#7
Gute Idee! Ich hatte bei der Funktion movingAverageT bisher nicht vorgesehen, dass man gleich mehrere Mittelwerte pro Reading gut gebrauchen könnte und so geht's ja. Es werden zwar identische Werte in mehreren Arrays abgelegt und dabei ein wenig RAM nicht optimal genutzt, aber ein Array frisst ja auch nicht viel Speicher.

Ich habe es mal direkt eingebaut, es sieht dann 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.N.$hLength"}) {
      @{$hash->{READINGS}{$reading}{"history.N.$hLength"}} = \@new;
    }

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

    #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.N.$hLength"}}, \@new);
   
      #shift until the maximum history size is reached
      while( int(@{$hash->{READINGS}{$reading}{"history.N.$hLength"}}) > $hLength ) {
        shift(@{$hash->{READINGS}{$reading}{"history.N.$hLength"}});
      }
    }
   
    #length of current history
    my $aLength = int(@{$hash->{READINGS}{$reading}{"history.N.$hLength"}});

    #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;
  }
 
  ###############################################################################
  #
  #  Moving average of last N values, weigh values by time
  #
  #  movingAverageN2(Devicename, Readingname, HistoryLength)
  #
  ###############################################################################
  sub movingAverageN2($$$){
    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.N2.$hLength"}) {
      @{$hash->{READINGS}{$reading}{"history.N2.$hLength"}} = \@new;
    }

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

    #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.N2.$hLength"}}, \@new);
   
      #shift until the maximum history size is reached
      while( int(@{$hash->{READINGS}{$reading}{"history.N2.$hLength"}}) > $hLength ) {
        shift(@{$hash->{READINGS}{$reading}{"history.N2.$hLength"}});
      }
    }
   
    #length of current history
    my $aLength = int(@{$hash->{READINGS}{$reading}{"history.N2.$hLength"}});
   
    #if history currently consists of a single value, just return it
    if ($aLength <= 1) {
      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);;
  }
 
  ###############################################################################
  #
  #  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.T.$hSeconds"}) {
      @{$hash->{READINGS}{$reading}{"history.T.$hSeconds"}} = \@new;
    }

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

    #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.T.$hSeconds"}}, \@new);
     
      #remove entries that are too old
      while( time() - time_str2num((\@{$hash->{READINGS}{$reading}{"history.T.$hSeconds"}})->[0][1]) > $hSeconds ) {
        #Log 1,"[$name:$reading] removing reading, it is too old";
        shift(@{$hash->{READINGS}{$reading}{"history.T.$hSeconds"}});
      }
    }
   
    #length of current history
    my $aLength = int(@{$hash->{READINGS}{$reading}{"history.T.$hSeconds"}});
   
    #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;