movingAverage, Ausgabe intervallsmässig resp. Mittelwert nur sporadisch ausgeben

Begonnen von Aurel_B, 23 September 2021, 14:35:08

Vorheriges Thema - Nächstes Thema

Aurel_B

Liebe alle,

ich werte von meiner PV die aktuelle Leistung im Sekundentakt aus. Für Logging und weitere Verarbeitungszwecke ist dieses Intervall viel zu eng, da möchte ich den Mittelwert von z.B. der letzten Minute verwenden. Nun erneuert ja die Funktion "movingAverage" aus https://wiki.fhem.de/wiki/Gleitende_Mittelwerte_berechnen_und_loggen den Mittelwert im gleichen Takt wie auch die Readings stattfinden, damit würde ebenfalls im Sekundentakt ein Logging ausgelöst. Ich habe nun die Funktion etwas abgeändert (und das Limit von maximal 25 Einträgen entfernt). Durch einen (optionalen) 4 Parameter kann angegeben werden, dass nur alle X Aufrufe der Mittelwert zurückgegeben wird (und damit das verwendete userReading - und damit das Logging - aktualisiert wird). So kann ich auch kaskadierte Mittelwerte berechnen, basierend auf einem minütlichen Mittelwert kann ich 15, 60min etc. Werte berechnen. Würde mein minütlicher Mittelwert auch im Sekundentakt aktualisiert werden, so würde z.B. der 60min Mittelwert aus 3600s Werten berechnet werden (ebenfalls im Sekundentakt!). Wenn ich aber nur jede Minute den Mittelwert aktualisiere reichen 60 Werte die ausserdem nur 1x/Minute aktualisiert werden.

Hier ist sie:


sub movingAverage($$$;$) {
    my ( $name, $reading, $avtime, $printintervall ) = @_;
    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" ) ) {
        #-- Use a unique array name based on intervall used
        $hash->{READINGS}{$reading}{"history_".$avtime} = undef;
    }

    #-- test for existence
    if ( !$hash->{READINGS}{$reading}{"history_".$avtime} ) {

        #Log 1,"ARRAY CREATED";
        push( @{ $hash->{READINGS}{$reading}{"history_".$avtime} }, \@new );
        $num                                        = 1;
        $arr                                        = \@{ $hash->{READINGS}{$reading}{"history_".$avtime} };
        $hash->{READINGS}{$reading}{"lastAvgPrint"} = 0 unless !defined $printintervall;
    }
    else {
        $num = int( @{ $hash->{READINGS}{$reading}{"history_".$avtime} } );
        $arr = \@{ $hash->{READINGS}{$reading}{"history_".$avtime} };
        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 ( ( $ctime - $stime ) < $avtime ) {

            #Log 1,"ARRAY has $num elements, adding another one";
            push( @{ $hash->{READINGS}{$reading}{"history_".$avtime} }, \@new );
        }
        else {
            shift( @{ $hash->{READINGS}{$reading}{"history_".$avtime} } );
            push( @{ $hash->{READINGS}{$reading}{"history_".$avtime} }, \@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";

    #-- If printintervall is defined only print average if intervall has been reached
    if ( defined $printintervall ) {
        if ( $hash->{READINGS}{$reading}{"lastAvgPrint"} <= 0 ) {
            $hash->{READINGS}{$reading}{"lastAvgPrint"} = $printintervall;
            return $average;
        }
        else {
            $hash->{READINGS}{$reading}{"lastAvgPrint"}--;
            return;
        }
    }
    return $average;
}


Anwendung ist super einfach:

attr Kostal userReadings AC_Power_avg_1min {movingAverage("Kostal", "Inverter_Generation_P_Actual", 60, 30)}

Inverter_Generation_P_Actual wird jede 2 Sekunden aktualisiert. Ich möchte den Mittelwert der letzten Minute (60s == dritter Parameter) im Reading AC_Power_avg_1min jede Minute aktualisiert haben, der 4 Parameter muss also 30 (30*2s == 60s) sein.

Und so sieht das kaskadiert aus:
attr Kostal userReadings AC_Power_avg_1min {movingAverage("Kostal", "Inverter_Generation_P_Actual", 60, 30)},AC_Power_avg_15min:AC_Power_avg_1min.* {movingAverage("Kostal", "AC_Power_avg_1min", 15)},AC_Power_avg_60min:AC_Power_avg_1min.* {movingAverage("Kostal", "AC_Power_avg_1min", 60)}

Die Readings für die 15min/60min Intervalle werden per Trigger an AC_Power_avg_1min gebunden so dass die Mittelwertberechnung nur stattfindet, wenn der 1min Mittelwert aktualisiert wird.

Vielleicht ist es für jemanden anderen auch hilfreich?

Edit: mein Code hatte bislang einen Fehler: wenn mehr als ein Mittelwert pro Reading mit "movingAverage" berechnet werden soll, so kam es bislang zu einem Durcheinander da nur 1 Array pro Reading verwendet wurde und dann - falls > 1 Mittelwert pro Reading verwendet wird - es zu Konflikten kommt. Habe das so gelöst, dass der Arrayname die Anzahl Intervalle angehängt bekommt, also z.B.:

alt

AC_Power_avg_1min {movingAverage("Kostal", "Inverter_Generation_P_Actual", 60, 30)} und AC_Power_avg_10min {movingAverage("Kostal", "Inverter_Generation_P_Actual", 600, 30)}

führen zu einem Problem da beide movingAverage Aufrufe in $defs{$name}{READINGS}{"history"} schreiben.

neu

Es wird $defs{$name}{READINGS}{"history_".$avtime} verwendet. $avtime entspricht dem Intervall, in obigem Beispiel also 60 resp. 600. So werden separate Instanzen verwendet. Der Code oben ist aktualisiert und sollte so funktionieren.

Für das Debugging kann {Dumper($defs{"Kostal"}{READINGS})} in die Cmdline eingegeben werden, dann wird der ganze "READINGS" hash ausgegeben.

Aurel_B

Das mit den kaskadierenden Messungen funktioniert nicht richtig, da habe ich einen Denkfehler drin, muss da zuerst nochmals über die Bücher und werde den Post updaten wenn es soweit ist.

Edit: ich hab's! Das userReading für die 15/60min Intervall darf nur getriggert werden, wenn AC_Power_avg_1min auch tatsächlich aktualisiert wird (also im 60s Takt). Ohne diesen Trigger werden die 15/60min Intervalle auch im Sekundentakt gefüllt/berechnet was nicht Sinn der Sache ist...

Aurel_B

Und so sieht ein "real-life" Beispiel anhand meiner PV aus: wie man sieht, scheint eine Unterscheidung in 5 und 15min Durchschnittswerte nicht wirklich sinnvoll zu sein. Ich bleibe daher bei 1, 15 und 60min und benutze diese Werte, um z.B. zu entscheiden, ob ein Grossverbraucher eingeschaltet werden soll.

Aurel_B

Nachtrag: es gibt sehr wohl einen Unterschied zwischen 5 und 15min, mein Code war falsch. Habe ihn im initialen Post korrigiert. Hier die aktualisierte Graphik wo man sehr schön sieht, dass die Graphen immer geglätteter werden je länger die Sampling Periode ist.

Prof. Dr. Peter Henning

Als Betreiber einer PV-Anlage seit mehr einem Jahrzehnt kann ich sagen: Es ist vollkommener Unsinn, die Leistung im Sekundentakt auszuwerten. Das belastet FHEM (und das Logging) enorm und enthält keine sinnvolle Information. Viel sinnvoller ist, in Intervallen von (sagen wir) einer Minute die eingespeiste Energie abzufragen und den Zuwachs durch die vergangene Zeit zu dividieren - das ist die mittlere Leistung.

Als Autor des ursprünglichen movingAverage Codes kann ich sagen: Das ist m.E. zu kompliziert gelöst


LG

pah

Aurel_B

hmmm, ich denke nicht, dass sich das pauschal beantworten lässt. Viele Wege führen nach Rom, manche sind kurz und langweilig, andere mühsam und erlebnisreich :-)
In meinem Anwendungsfall würde die Mittelwertbildung aus der Differenz zweier kumulierter Zählerstände zu Problemen führen (resp. führte zu Problemen): wir haben ein MFH mit momentan 1 WR und 6 Zählern, in Zukunft werden es noch mehr sein. Die verwendeten Zähler haben eine Auflösung von 10Wh für die kumulierten Zählerstände. Je nach Wohnung fällt aber der Verbrauch während Minuten oder sogar länger unter 10W. D.h. ich hätte Sprünge da die Auflösung von 10Wh nicht genügen würde. Ausserdem berechnen sich gewisse Readings aus 5 oder mehr Readings, durch die niedrige Auflösung kann die addierte Abweichung durchaus beträchtlich sein und zu unsinnigen Werten führen. Das müsste ich irgendwie wieder abfangen: entweder ich bilde mir aus diesen Mittelwerten wieder einen gleitenden Mittelwert oder ich nehme ein flexibles Erfassungsintervall wie z.B. beim Arducounter, müsste dafür aber regelmässig die Werte pollen um zu schauen, wann sich der Zähler erhöht hat. Und da beisst sich der Hund in den eigenen Schwanz....
Im Moment werden 6 Geräte im 2 Sekundentakt abgefragt. Das führt zu einer Auslastung von ca 4% von fhem (das Thema "blocking" lasse ich mal aussen vor). Loggen tue ich nur das allernötigste und räume die DB regelmässig auf. Die Last steht daher momentan nicht so im Vordergrund, kann sich vielleicht auch wieder ändern? Ich werde das Abfrageintervall wohl auf 5 Sekunden hochsetzen. Es ist aber ganz hilfreich zu sehen, dass es auch mit 2 Sekunden klappt.
Für mein Szenario ist das mit der modifizierten movingAverage Funktion eine nachvollziehbare Lösung die momentan gut passt, das kann sich wieder ändern. Vielen Dank Ihnen für die Funktion!