Hallo Liste,
ich war mit dem Vorschlag http://www.fhemwiki.de/wiki/Relative_Mittelwerte_berechnen_und_loggen
nicht so ganz zufrieden, weil die Routine immer erst die Logdateien liest.
(Edit: Ich meinte natürlich NICHT das Modul "average" - das hat eine ganz andere Funktionalität)
Also habe ich mal Folgendes probiert
1. gegeben ein (1-Wire) Device A.OWB mit einem Reading "pressure".
2. definiert das Attribute userReadings als
pressure.4:pressure {ReadingsVal("A.OWB","pressure.3",0)},pressure.3:pressure {ReadingsVal("A.OWB","pressure.2",0)},pressure.2:pressure {ReadingsVal("A.OWB","pressure.1",0)},pressure.1:pressure {ReadingsVal("A.OWB","pressure",0)}
Zentrale Idee dabei: Das reading "pressure" wird bei jedem Update durchgereicht: Zuerst in pressure.1, beim nächsten Schritt in pressure.2 etc. Dann hat man die letzten 4 Readings von pressure im System, und kann ihren Mittelwert problemlos als weiteres userReading anzeigen lassen.
War aber Pustekuchen, pressure.3 enthielt immer dasselbe wie pressure.2. Komisch, denk ich mir, machen wir mal einen weiteren Test:
pressure.4:pressure {ReadingsVal("A.OWB","pressure.3",0)},pressure.3:pressure {ReadingsVal("A.OWB","pressure.2a",0)},pressure.2a:pressure {ReadingsVal("A.OWB","pressure.1",0)},pressure.2:pressure {ReadingsVal("A.OWB","pressure.1",0)},pressure.1:pressure {ReadingsVal("A.OWB","pressure",0)}
Et voila, funktioniert prima: pressure.4, pressure.3, pressure.2 und pressure.1 enthalten die letzten 4 Readings von pressure. Aber, brat mir einer einen Storch, pressure.2a hat immer denselben Wert wie pressure.2.
Außer vagen Vermutungen habe ich bisher dafür keine Erklärung.
LG
pah
die einzelnen user readings werden nach dem parsen des attributes in einem hash abgelegt. das hat zur folge das die reihenfolge der abarbeitung undefiniert ist.
gruss
andre
ps: wo siehst du das average auf logfites zugreift? beim überfliegen des moduls habe ich nichts in der art gesehen.
pps: vielleicht hilft dir auch event-aggregator
Äh, :-[, natürlich nicht das Modul "average" - das hat eine ganz andere Funktionalität. Sondern den Codeschnipsel hier http://www.fhemwiki.de/wiki/Relative_Mittelwerte_berechnen_und_loggen.
(Frag mich, warum ich das Modul genannt habe ? Beginnender Zerfall, allerhöchst urlaubsreif...)
Die Reihenfolge ist übrigens nicht undefiniert, sondern auch eine Hashtabelle folgt ihren klaren Gesetzmäßigkeiten. Und nach denen sollte das eigentlich anders laufen, nämlich tatsächlich von rechts weil das in der Folge eingetragen wird.
LG
pah
Ich berechne gleitende Durchschnitte in den Userreadings folgendermaßen:
wS_gld (Windgeschwindigkeit, n Abtastwerte)
wS_gld {ReadingsVal("$name","wS_gld",0) * (n-1) + ReadingsVal("$name","windSpeed",0)) / n}
Gruß
naturlich gibt es gesetzmäßigkeiten. die sind aber implementierungsabhängig. ohne die jeweilige implementierung genau zu kennen ist die reihe folge unbestimmt.
warum sollte die reihenfolge von der reihenfolge des eintragens abhängen?
eine gute hash funktion wird auch aufsteigend nummerierte werte nicht in das gleiche bucket einsortieren sonder möglichst gleichmäßig verteilen.
gruss
andre
Probiers aus - bei mir wird das wirklich prima durchgereicht.
LG
pah
Zitat von: My-FHEM am 27 Juli 2015, 18:43:54
Ich berechne gleitende Durchschnitte in den Userreadings folgendermaßen:
wS_gld (Windgeschwindigkeit, n Abtastwerte)
wS_gld {ReadingsVal("$name","wS_gld",0) * (n-1) + ReadingsVal("$name","windSpeed",0)) / n}
Gruß
Hi,
so in etwa mach ichs auch. Funktioniert Top.
(Ich habs in der 99er liegen und rechne noch die Zeit mit ein um auszugleichen das eine Sensornachricht verloren geht - annemometer ist an der Grenze des Empfangsbereiches ...)
vg
joerg
Mag ja sein, dass irgendjemand das so rechnet - aber das ist kein gleitender Durchschnitt, sondern höchstens gleitender Unsinn.
In dieser Berechnung werden nämlich _alle_ vorhergehenden Messwerte berücksichtigt, mit abnehmenden Gewichten für die früheren Werte bis auf den ersten. Dabei hängt es sehr stark vom ERSTEN Iterationswert ab, wie stark dieser eingeht - denn die m. Potenz von (n-1)/n fällt für große n nur sehr langsam ab.
Per se handelt es sich bei dieser Berechnung also nicht um eine Glättung mit definierten spektralen Eigenschaften.
LG
pah
ZitatProbiers aus - bei mir wird das wirklich prima durchgereicht.
das ändert nichts daran das es mehr oder weniger zufall ist und sich abhängig von der perl version, den vorher oder danach stattfinden einfüge und lösch operationen auf dem gleichen hash und noch einigen anderen dingen ändern kann. unter umständen schon beim nächsten fhem start.
siehe z.b. jede vernünftige einführung in hash datenstrukturen und im perl fall besonders die perl doku: http://perldoc.perl.org/functions/keys.html (http://perldoc.perl.org/functions/keys.html):
ZitatHash entries are returned in an apparently random order. The actual random order is specific to a given hash; the exact same series of operations on two hashes may result in a different order for each hash. ... Aside from the guarantees provided here the exact details of Perl's hash algorithm and the hash traversal order are subject to change in any release of Perl. ...
die einzig korrekte methode ist in *einem* notify oder user reading den neuen mittelwert zu berechnen und die historie selber zu verwalten. das könnte dann der einfachheit halber auch ein einziges reding sein das per split in ein array deserialisiert wird dann mit pop/unshift oder pop/shift manipuliert und zur ablage wieder in einen string serialisiert wird.
userReadings hierfür zu verwenden ist um es mit deinen worten zu sagen unsinn.
gruss
andre
ZitatMag ja sein, dass irgendjemand das so rechnet - aber das ist kein gleitender Durchschnitt, sondern höchstens gleitender Unsinn.
Charmant wie immer ;)
Grundsätzlich hast Du schon recht, praktisch ergibt diese Zahlenreihe: 60,50,70,20 gleitend 50 und gewichtet 50,47.
Bei stark abweichendem Startwert ist die Differenz beider Methoden initial größer, nähert sich allerdings abhängig von Laufzeit und Anzahl der Samples schnell sehr stark an.
Die Methode von my-fhem ist also pragmatisch und gut, ein (Dein) echter gleitender Durchschnitt ist in einigen Situationen genauer :)
vg
joerg
@herrmannj:
Na, da widerspreche ich aber - es geht nicht darum, die Zahlenwerte irgendwie zu glätten. Sondern darum, Störsignale auf bestimmten Zeitskalen auszufiltern. Und das macht man nicht mit diskreten Laplacetransformationen. Beispielsweise kann man sehr einfach zeigen, dass eine "Annäherung" dieser Transformation an den gleitenden Mittelwert nur unter sehr speziellen Annahmen an den Signalverlauf stattfindet. Das Attribut "gut" kann ich dafür nicht vergeben.
@justme1968:
Prinzipiell richtig, dass es eines ordentlichen Moduls bedarf. Allerdings widerspreche ich auch hier - denn es geht gar nicht um die Speicherreihenfolge im Hash. Sondern um das, was bei Durchlaufen der keys aus dem Hash wieder herauskommt. Bei den wenigen Einträgen, die ich hier habe, gibt es mit hoher Wahrscheinlichkeit beim Anlegen keine Kollisionen, deshalb wird die ordinale Struktur mit eben dieser Wahrscheinlichkeit erhalten. Die "Zufälligkeit", die in der perldoc behauptet wird, kommt erst bei Kollisionen ins Spiel - denn dann hängt die ordinale Reihung von der Reihenfolge des Einspeicherns ab. Quelle: Skriptum zu meiner eigenen Vorlesung "Datenorganisation" aus dem Jahr 1999.
LG
pah
der vorschlag mit dem array lässt sich problemlos in ein user reading stecken oder ein notify. das braucht kein eigenes modul.
sie speicherreihenfolge und die reihenfolge in der die keys durchlaufen werden sind für diese betrachtung identisch.
es kommt auch nicht auf die anzahl der ihm hash gespeicherten werte an. die zufälligkeit wird auch nicht nur behauptet sondern sie ist tatsächlich da, nicht nur bei kollisionen relevant und auch nicht von der reihenfolge der einfüge operationen abhängig.
sogar das folgende einfache beispiel liefert bei jedem start eine zufällige reihenfolge. das gilt auch für nur zwei elemente im hash.
use strict;
use warnings;
use Data::Dumper;
my %hash = ( 1 => 1, 2 => 2, 3 => 3 );
print Dumper keys %hash;
je nach genauer konstellation bei jedem aufruf, manchmal nur bei jedem 5ten oder 10ten.
d.h. selbst das argument das der userReadings hash nur ein mal gefüllt und danach nur noch ausgelesen wird greift nicht weil die initiale reihenfolge schon unbestimmt ist und das ganze hin und her schieben von der korrekten reihenfolge der keys abhängt. sonst überschreibst du dir zum falschen zeitpunkt einen später noch gebrauchten wert.
wann und wo es kollisionen gibt hängt nicht von der anzahl der werte ab sondern nur vom konkreten hash verfahren. ohne kenntnis der verfahrens lassen sich hier nur sehr beschränkt rückschlüsse ziehen.
es ist und bleibt unsinn das verhalten durch spielen mit den reading namen so hin zu drehen das es scheinbar stabil ist. die Anordnung der elemente im hash und die reihenfolge in der die keys durchlaufen werden ist für alle praktischen anwendungen als zufällig anzusehen und ein schlechtes beispiel ist es alle mal. der nächste der es probiert fällt auf die nase und wunder sich.
gruss
andre
Nun gut, ein schlechtes Beispiel ist es (also, Newbies, bitte nicht nachmachen).
Aber die von Dir behauptete Akausalität zwischen verschiedenen Aufrufen gibt es nicht - die kann höchstens durch Data::Dumper erzeugt werden.
LG
pah
ich weiss es ist schwer zuzugeben unrecht zu haben...
use strict;
use warnings;
my %hash = ( 1 => 1, 2 => 2 );
foreach my $k (keys %hash) {
printf "$k ";
}
printf "\n";
Zitat[rojo:/tmp] andre% perl x.pl
2 1
[rojo:/tmp] andre% perl x.pl
2 1
[rojo:/tmp] andre% perl x.pl
1 2
[rojo:/tmp] andre% perl x.pl
2 1
[rojo:/tmp] andre% perl x.pl
1 2
und jetzt sag nicht es liegt am foreach oder sogar am print.
Data::Dumper hat seiteneffekte aber dazu gehört nicht das ein hash, der nicht in direktem zusammen hang steht, sich ändert (das wäre nur ein grund mehr sich nicht auf die hash reihenfolge zu verlassen) und erst recht nicht das sich die reihenfolge einer liste ändert (die ist im gegensatz zum hash wirklich fest).
also zum wiederholen: die anordnung der elemente in einem perl hash und die reihenfolge in der die keys durchlaufen werden ist für alle praktischen anwendungen als zufällig anzusehen.
unter dem link oben ist übrigens auch zu lesen warum das so ist. da wird nicht nur einfach etwas behauptet. und von mir schon gar nicht.
gruss
andre
Nö, das ist gar nicht schwer: Für perl-Implementationen hast Du offenbar Recht
Wenn man ein und dieselbe Hashfunktion verwendet, ist das System immer deterministisch. Perl benutzt aber einen Seed für die Erzeugung des Hashes - und damit hat man keine tatsächlich keine Kontrolle mehr. Oder anders ausgedrückt: Jeder Aufruf von perl verwendet eine andere Hashfunktion.
Interessant dazu das hier: http://stackoverflow.com/questions/6685019/hash-randomization-in-perl-5
LG
pah
das steht doch alles (und noch genauer) in dem von mir verlinkten perldoc artikel bzw. in den dort verlinkten details.
Ach, wenn ich alle Artikel lesen wollte, die mir irgendjemand als Link schickt 8)
LG
pah
da ist es natürlich effizienter stur alle hinweise zu ignorieren bis es nicht mehr geht und auf irgendeine eigene alte vorlesung hinzuweisen in der niemand nachsehen kann...
gruss
andre
Na, da gibt mir einerseits die Lebenserfahrung Recht, und andererseits kann man das sehr wohl nachsehen, das Skript ist öffentlich...
LG
pah
Um das Ganze abzuschließen:
Die saubere Lösung, nämlich die letzten Einträge aufzuheben und darüber einen echten gleitenden Mittelwert zu berechnen, habe ich im Wiki eingestellt. Und zwar ohne Zugriff auf die Log-Dateien.
http://www.fhemwiki.de/wiki/Relative_Mittelwerte_berechnen_und_loggen
LG
pah
@pah
Vielen Dank für die tolle Routine, habe sie bei mir im Einsatz. Allerdings habe ich noch eine Frage dazu: Warum ist das Array auf 25 Werte limitiert?
Ich möchte bei meinen Temperatur-Sensoren einen 6-Stunden-Durchschnitt bilden. Da sind doch 25 History-Werte viel zu wenig (wenn die Temperatur alle 1-2 Minuten aktualisiert wird), oder verstehe ich da was grundlegendes noch nicht?
LG
linuzer
Kein Grund - kann man beliebig hoch setzen.
LG
pah
Für was ist diese Beschränkung eigentlich überhaupt drin? Wenn die gespeicherten History-Werte über den Betrachtungszeitraum ($avtime) geht, werden die Werte doch eh durchgetauscht. Somit ist das Array auch ohne Beschränkung immer genau so groß, wie es für den Betrachtungszeitraum in Abhängigkeit der Wertehäufigkeit sein muss. Oder liege ich falsch?
LG linuzer
Also, nachdem ich diese Funktion jetzt mehrere Tage im Einsatz habe und sie inzwischen vollständig verstanden habe, muss ich mich doch nochmal zu Wort melden, denn die Funktion hat so massive Einschränkungen, dass sie in der Praxis nahezu unbrauchbar wird:
- die oben erwähnte Beschränkung auf 25 History-Werte taugt bei den meisten Sensoren nur für Durchschnittsberechnungen über wenige Minuten. Gut, man kann sie sehr leicht entfernen, aber das macht es nicht besser...
- jedes Mal, wenn FHEM neu startet (und das passiert schon beim bloßen Speichern der Konfiguration) vergisst die Funktion konzeptbedingt alle History-Werte. Der resultierende Durchschnitt ist dann mitnichten ein korrekter Durchschnitt über den gewünschten Zeitraum!
- Apropos Durchschnitt: Die Berechnung liefert ausschließlich bei Sensorwerten, die in regelmäßigen zeitlichen Abständen auftreten einen korrekten Durchschnitt. In allen anderen Fällen (z.B. ein Temperatursensor, der unregelmäßig sendet) produziert sie genauso "gleitenden Unsinn", wie vom Herrn Professor weiter oben kritisiert wurde.
Um die Probleme zu beheben, müsste die Funktion natürlich beim ersten Aufruf erstmal die alten Werte aus dem Logfile lesen, um so von Beginn an eine vollständige History als Berechnungsgrundlage zu haben. Des weiteren muss die Durchschnittsberechnung zeitgewichtet erfolgen. Und natürlich muss die künstliche Beschränkung auf 25 Einträge raus.
So, lange Reder, kurzer Sinn, hier ist die vollständig überarbeitete Funktion, die so ziemlich in allen Lebenslagen einen korrekten gleitenden Durchschnitt berechnet:
package main;
use strict;
use warnings;
use Time::Piece;
use POSIX;
sub
myUtils_Initialize($$)
{
my ($hash) = @_;
}
###############################################################################
#
# Moving average
#
# Aufruf: movingAverage(devicename,readingname,zeitspanne in s,logdevice)
#
###############################################################################
sub movingAverage($$$;$){
my ($name,$reading,$avtime,$logfile) = @_;
my $hash = $defs{$name};
my @new = my ($val,$time) = ($hash->{READINGS}{$reading}{VAL},$hash->{READINGS}{$reading}{TIME});
my $ctime = Time::Piece->strptime($time, "%Y-%m-%d %H:%M:%S");
my $btime = $ctime - $avtime;
my $num;
my $arr;
my $average;
#-- initialize if requested
if( ($avtime eq "-1") ){
$hash->{READINGS}{$reading}{"history"}=undef;
return "$name:$reading initialized.";
}
#-- test for existence
if( !$hash->{READINGS}{$reading}{"history"}){
#-- the first time build the array from the logfile
if( defined $logfile) {
my $lbtime = $btime->strftime("%Y-%m-%d_%H:%M:%S");
(my $letime = $time) =~ s/ /_/;
my $oldverb = $attr{global}{verbose};
$attr{global}{verbose} = 0;
my @logdata = split("\n", fhem("get $logfile - - $lbtime $letime $name:$reading"));
$attr{global}{verbose} = $oldverb;
foreach (@logdata){
my @line = split(" ", $_);
if(defined $line[1] && "$line[1]" ne ""){
$line[0] =~ s/_/ /;
my @tmp = my ($v,$t) = ($line[1],$line[0]);
push(@{$hash->{READINGS}{$reading}{"history"}},\@tmp);
}
}
$num = int(@{$hash->{READINGS}{$reading}{"history"}});
Log 3,"movingAverage array for $name:$reading read from log, $num entries";
}
} else {
$arr=\@{$hash->{READINGS}{$reading}{"history"}};
my $stime = Time::Piece->strptime($arr->[0][1], "%Y-%m-%d %H:%M:%S");
if($stime>$btime){
push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
} else {
my $old = shift(@{$hash->{READINGS}{$reading}{"history"}});
my $xtime = Time::Piece->strptime($old->[1], "%Y-%m-%d %H:%M:%S");
until( $xtime >$btime ){
$old = shift(@{$hash->{READINGS}{$reading}{"history"}});
$xtime = Time::Piece->strptime($old->[1], "%Y-%m-%d %H:%M:%S");
}
}
}
push(@{$hash->{READINGS}{$reading}{"history"}},\@new);
#-- output and time-weighted average
$arr=\@{$hash->{READINGS}{$reading}{"history"}};
$num = int(@{$hash->{READINGS}{$reading}{"history"}});
if($num > 1){
my $SumSec = 0;
my $SumProd = 0;
for(my $i=1;$i<$num;$i++){
my $OldTime = Time::Piece->strptime($arr->[$i-1][1], "%Y-%m-%d %H:%M:%S");
my $Sec = Time::Piece->strptime($arr->[$i][1], "%Y-%m-%d %H:%M:%S") - $OldTime;
$SumProd += $arr->[$i][0] * $Sec;
$SumSec += $Sec;
#Log 3,"[$name moving average] Value = ".$arr->[$i][0]." Time = ".$arr->[$i][1];
}
$average=sprintf( "%5.3f", $SumProd/$SumSec);
} else {
$average=sprintf("%5.3f", $val);
}
Log 3,"[$name moving average] calculated over $num values is $average";
return $average;
}
1;
Vielleicht kann sie jemand ins Wiki übernehmen, ich habe dazu nicht die Berechtigung.
LG
linuzer
das dein fhem beim config speichern neu startet ist ein nebeneffekt der (nicht mehr empfohlen) art wie die die config bearbeitest. aber das ist eine andere diskussion.
unabhängig davon ist der ansatz die alten readings aus dem log zu holen meiner meinung nach immer noch falsch. das funktioniert z.b. nicht wenn die werte log file übergreifend gebraucht werden (jahres oder monats wechsel) oder wenn man die originale gar nicht loggen will.
der von fhem hierfür vorgesehene mechanismus ist einen reading namen zu verwenden der mit einem . beginnt. der ist unsichtbar und wird aber trotzdem mit gespeichert. damit kann man entweder alle nötigen werte direkt in readings ablegen oder z.b. im global SAVE event und/oder global SHUTDOWN wie oben vorgeschlagen alles in ein einziges reading serialisieren. bei global INITIALIZED holt man sich die werte dann wieder.
auch auf das $hash->{READINGS} direkt zuzugreifen ist eigentlich unsauber.
den wiki zugang bekommst du ohne probleme.
gruss
andre
Naja, mein Punkt war, wenn FHEM neu startet, ist die History weg, das ist einfach so. Aber off-topic würde mich interessieren, wie ist denn die empfohlene Art, die Config zu ändern?
Und wenn jemand die Werte gar nicht loggen will, mag das ja so sein, aber dann muss er halt damit leben, dass sich das System erst wieder "warmlaufen" muss. Aber dafür dann einen neuen, eigenen "Logging-Mechanismus" zu erfinden indem man irgend etwas serialisiert ist in meinen Augen widersinnig, dann kann ich die Werte, die ich brauche auch direkt ins Log schreiben. Und die Werte in ein wöchentlich oder monatlich wechselndes Log*file* zu schreiben, führt nicht nur hier zu Problemen beim Wechsel. Die Lösung ist halt eine Log*DB* zu verwenden, damit funktioniert es auch hier.
LG
linuzer
es gibt keinen grund das config file von hand zu editieren. wenn du alles über das web frontend oder per telnet machst wird fhem niemals neu gestartet. ist aber wie gesagt off-topic. es gibt schon viele diskussionen darüber.
ich würde es nicht wiedersinnig nennen wenn es unterm strich mit deutlich mit weniger code zeilen auskommt und dabei auch noch die angesprochenen nachteile vermeidet. aber wie gesagt: es ist nur meine meinung.
gruss
andre
Wir leben in einem freien Land, möge jeder verwenden, was er für besser hält. Darüber hinaus liefert diese neue Funktion natürlich ebenfalls gleitenden Unsinn für nicht äquidistante Messungen, weiil sie kein ordentliches Interpolationsmodell hat.
LG
pah
Hallo zusammen,
ich weiß, der Thread ist schon etwas älter...
Habe mal beide Versionen vom "moving average" getestet.
Was ich nicht verstehe, warum ich pro Änderung des Reading vier Log-Zeilen bekomme..
Hier mein userreading:
brightness.av {movingAverage("Sonnensensor","brightness",900)}
und hier der logauszug:
2016.08.26 10:56:54 3: [Sonnensensor moving average] calculated over 46 values is 99177.841
2016.08.26 10:56:54 3: [Sonnensensor moving average] calculated over 39 values is 100096.628
2016.08.26 10:56:54 3: [Sonnensensor moving average] calculated over 41 values is 100096.628
2016.08.26 10:56:54 3: [Sonnensensor moving average] calculated over 43 values is 100096.628
Viele Grüße
Klaus