FHEM > Codeschnipsel

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

(1/2) > >>

ansgru:
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:


--- Code: ---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;
}

--- Ende Code ---

Anwendung ist super einfach:


--- Code: ---attr Kostal userReadings AC_Power_avg_1min {movingAverage("Kostal", "Inverter_Generation_P_Actual", 60, 30)}
--- Ende Code ---

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:

--- Code: ---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)}
--- Ende Code ---

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


--- Code: ---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)}
--- Ende Code ---

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.

ansgru:
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...

ansgru:
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.

ansgru:
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

Navigation

[0] Themen-Index

[#] Nächste Seite

Zur normalen Ansicht wechseln