Funktion movingAverage2

Begonnen von Holger86, 20 Juli 2021, 15:52:36

Vorheriges Thema - Nächstes Thema

Holger86

Ich benötigte ein gefiltertes Reading von Messwerten, die teilweise Ausreißer enthalten durch Störsignale. Ein gleitender Mittelwert war dafür eine gute Lösung. Im Wiki wird dafür unter https://wiki.fhem.de/wiki/Gleitende_Mittelwerte_berechnen_und_loggen eine Lösung angeboten.
Nun stand ich noch vor dem Problem, dass mein Device diverse verschiedene Readings enthält, die in unterschiedlichen Zeitabständen eintreffen. Jedes Reading triggert jedoch die Funktion für den gleitenden Mittelwert des benötigten Readings und dadurch werden Werte in der unter beschriebenen Version von movingAverage mehrfach verarbeitet, was zu Verfälschungen führt. Ich habe daher den originalen PERL-Code analysiert und für Leute, die mit PERL nicht so vertraut sind auch etwas mehr kommentiert. Anschließend habe ich die Funktion dahingehend erweitert, dass nun nur noch neue Messwerte aufgenommen werden, wenn sich auch deren Zeitstempel im Vergleich zum letzten aufgenommenen Messwert geändert hat.
Nachfolgend der Code der Funktion, die wie im Link oben in 99_MyUtils.pm eingebaut werden muss:
##########################################################
#
#  Moving average 2
#
#  Aufruf: movingAverage2(devicename,readingname,zeitspanne in s)
#
#  Funktion berücksichtigt nun auch, wenn Readings mehrfach gesehen werden und schließt Doppler aus
#
# ACHTUNG! Regulär werden nur max. 25 Werte für den Mittelwert genutzt. Ggf Variable $maxnum anpassen
#

sub movingAverage2($$$){
my ($name,$reading,$avtime) = @_;
# Hash der Geräteinstanz des Geräts $name
my $hash = $defs{$name};
# $hash->{READINGS}{$reading}{VAL} gibt den Wert des aktuellen Readings mit dem Namen $reading des Geräts $name zurück
# $hash->{READINGS}{$reading}{TIME} gibt den Zeitpunkt des aktuellen Readings mit dem Namen $reading des Geräts $name zurück
# Die Werte kommen in die Variablen $val und $time und die wiederum in @new
my @new = my ($val,$time) = ($hash->{READINGS}{$reading}{VAL},$hash->{READINGS}{$reading}{TIME});
# $ctime enthält den Zeitpunkt des Readings in Sekunden seit 00:00 Uhr
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 $maxnum = 25;
my $arr;
my $lastStoredTimeStamp;

#-- initialize if requested
if( ($avtime eq "-1") ){
# Setzt die "history" des Readings zurück
$hash->{READINGS}{$reading}{"history"} = undef;
}

#-- test for existence
if( !$hash->{READINGS}{$reading}{"history"}){
# Hängt beim Array "history" des Readings den Inhalt des Arrays @new an.
push(@{$hash->{READINGS}{$reading}{"history"}}, \@new);
# Schreibt das Array in die Variable $arr
$arr = \@{$hash->{READINGS}{$reading}{"history"}};
return $val;
} else {
# Erweiterung von Holger Linde
# Bei einigen Geräten kommen bestimmte Readings deutlich seltener als andere. Da bei jedem eintreffenden Reading diese Funktion ausgeführt wird
# würden bei der originalen Funktion u. U. die gleichen Readings mehrfach in den gleitenden Mittelwert einfließen. Das soll verhindert werden,
# indem die Zeit des vermeintlich neuen Readings mit der des letzten in "history" aufgezeichneten verglichen werden.
$num = int(@{$hash->{READINGS}{$reading}{"history"}});
$arr = \@{$hash->{READINGS}{$reading}{"history"}};
$lastStoredTimeStamp = $arr->[$num - 1][1];
Log3 $name, 4, "[$name moving average] Zeitstempel aktuelles Reading: $time | Zeitstempel letztes gespeichertes Reading (Nr. $num): $lastStoredTimeStamp";
if ($time ne $lastStoredTimeStamp){
# Schreibt den Zeitpunkt des ersten in "history" verzeichneten Redings in $starttime und rechnet diesen Wert dann in Sekunden seit 00:00 Uhr um
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
if( $stime > $ctime) {
$stime -= 86400;
}
if (($num < $maxnum) && (($ctime - $stime) < $avtime)){
# Hängt beim Array "history" des Readings den Inhalt des Arrays @new an
push(@{$hash->{READINGS}{$reading}{"history"}}, \@new);
} else {
# Entfernt das erste Element beim Array "history" des Readings
shift(@{$hash->{READINGS}{$reading}{"history"}});
# Hängt beim Array "history" des Readings den Inhalt des Arrays @new an
push(@{$hash->{READINGS}{$reading}{"history"}}, \@new);
}

#-- output and average
my $average = 0;
for(my $i = 0; $i < $num; $i++){
$average += $arr->[$i][0];
Log3 $name, 4, "[$name moving average] Value = " . $arr->[$i][0] . " Time = " . $arr->[$i][1];
}
$average=sprintf( "%5.3f", $average/$num);

#--average
Log3 $name, 4, "[$name moving average] calculated over $num values is $average"; 
return $average;
} else {
Log3 $name, 4, "[$name moving average] Wert ist schon berücksichtigt";
}
}
}