Markisensteuerung: Hysterese 2.0, gewichteter Mittelwert, Standardabweichung von Sensordaten
Verfasst: 05.05.2020, 01:15
Hintergrund für dieses Script war eine ausgefeilte Sonnenschutz-Steuerung von 2 großen Markisen und div. Rollladen nach Innentemperatur, Außentemperatur, Helligkeit, Uhrzeit, Wind, Regen und Blendwirkung einer untergehenden Sonne. Bei Temperaturwerten kann man leicht über eine einfache Hysterese steuern. Bei den Helligkeitswerten sieht dies schon anders aus, da Wolken kurzfristig die Sonne verdecken können. Man möchte dann natürlich nicht, dass Markisen und Rollladen ständig hin- und herfahren. Eine Lösung wäre ein Mittelwert über eine möglichst definierte Zeiteinheit. Noch besser ein gewichteter Mittelwert, der die letzten Daten mit einer höheren Wichtung versieht. Noch spezieller benötigt man es allerdings beim Thema Wind und einer einteiligen Markise der Dimension 7,50 x 4 m. Das ist ein Segel, welches bei Wind enorme Kräfte entwickeln kann. Insbesondere bei böigem Wind möchte man abschätzen, in welchen Dimension diese Böen erwartbar sind. Also Statistik.
Grundlage ist eine gezielte Sammlung von Umweltdaten über einen definierten Zeitraum. Das kann man so übrigens für sämtliche Sensordaten adaptieren.
Der obige Scriptteil "missbraucht" hiefür eine String-Systemvariable als universellen Datenspeicher. Die Daten werden mit einem genauen Zeitstempel versehen und können somit gezielt abgeschnitten werden. Neue Daten werden vorn angefügt, alte Daten fliegen hinten raus. Weiterhin ist eine Auswertung nach genau definierten Zeitabschnitten möglich, was gerade bei Wind-Daten sehr sinnvoll ist. Ich verwende diese Datensammlung für alle Umweltdaten in unterschiedlichen Intervallen. Beispiel: Min/Max-Werte der Außentemperatur der letzten genau 12 und 24 Stunden.
Der zweite Scriptteil berechnet den Mittelwert, den umgekehrt gewichteten Mittelwert, die Varianz und die Standardabweichung für zwei genau definierte Intervalle. Im rund 95%-Bereich der Windwerte (also +-Standardabweichung *2) liegen nahezu alle Windböen. Die konkrete Steuerung der Markisen erfolgt dabei bei einer Windschwelle als oberen Hysteresepunkt und als unteren Hysteresepunkt müssen die 95%-Werte für Long und Short unter genau diesen Wert fallen. Der Shortwert steigt dabei sehr schnell an und der Longwert fällt nach starken Böen langsamer ab. Hört sich ggf. etwas kompliziert an, funktioniert aber extrem zuverlässig. Insbesondere auch dann, wenn man mal nicht vor Ort ist.
Beispiel: Die obere Windschwelle wird mit 30 km/h festgelegt. Steigt der gemessene Windwert einmalig über diesen Wert, wird Wind=true. Sinken WindLong95 und WindShort95 einmalig zusammen unter 30 km/h wird Wind=false. Das funktioniert natürlich auch für Zwischenwerte, um eine Markise beispielsweise nur halb herauszufahren.
Viel Spaß, bytelander
Grundlage ist eine gezielte Sammlung von Umweltdaten über einen definierten Zeitraum. Das kann man so übrigens für sämtliche Sensordaten adaptieren.
Code: Alles auswählen
!- liefert Wind-Werte der letzten 6h, bytelander - 05.05.2020
string DeviceChannelName = "Wetterstation:1";
string LogVariable = "UmweltWindLog";
string LogValue = "WIND_SPEED";
integer SecondsLog = 21600;
real Faktor = 2.0;
!- aktuelle Uhrzeit über Datum und 24-Stunden Zeit als Integer in Sekunden
integer now = system.Date("%F %X").ToTime().ToInteger();
!- aktuelle LogVariable auslesen
string list = dom.GetObject(LogVariable).Value();
string index;
integer count = 1; !- da es bei Eventauslösung immer ein (neues) Element gibt
!- neues Element an den Anfang der neuen Liste stellen
string newlist = dom.GetObject(DeviceChannelName).DPByHssDP(LogValue).Timestamp().ToTime().ToInteger().ToString()+"#"+dom.GetObject(DeviceChannelName).DPByHssDP(LogValue).Value().Round(1).ToString().RTrim("0").RTrim(".");
!- durch alle Listenelemente, um bereits gespeicherte Elemente, die noch nicht zu alt sind, in die neue Liste aufzunehmen, Liste wird HINTEN um zu alte Elemente abgeschnitten
foreach (index, list){
!- Zeitauswertung für alle ALTEN Listenelemente
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=SecondsLog){
newlist=newlist+"\t"+index;
count=count+1;
}
}
dom.GetObject(LogVariable).State(newlist); !- neue LOG-Liste schreiben
!- WriteLine(newlist.Replace("\t","@"));
Code: Alles auswählen
!------------------- Mittelwert, gewichteter Mittelwert, Varianz und Standardabweichung für Long --------------------
string GMWLogVariable = "GMWWindLong"; !- Variable für gewichteten Mittelwert
string WindLong95Variable = "WindLong95"; !- Variable für WindLong95
integer GMWSecondsLog = 5400; !- Zeitspanne für gewichteten Mittelwert in Sekunden: 5400 (90 Minuten)
real sumMW;
!- Anzahl Elemente für Zeiteinheit ermitteln und "normalen" Mittelwert berechnen
integer count = 0;
foreach (index, newlist){
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=GMWSecondsLog){
sumMW = sumMW + index.StrValueByIndex("#", 1).ToFloat();
count=count+1;
}
}
real MWLong = sumMW/count; !- Mittelwert für Long
real sumVQ;
foreach (index, newlist){
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=GMWSecondsLog){
sumVQ = sumVQ + ((index.StrValueByIndex("#", 1).ToFloat() - MWLong) * (index.StrValueByIndex("#", 1).ToFloat() - MWLong));
}
}
real VarianzLong = sumVQ/count;
real StandAbwLong = VarianzLong.Sqrt();
real WindLong95 = MWLong+(StandAbwLong*Faktor);
dom.GetObject(WindLong95Variable).State(WindLong95.Round(2)); !- WindLong95 schreiben
real countGMW = ((count*(count+1))/2);
real sumGMW;
integer c = 0; !- Zähler für Gewichtung
foreach (index, newlist){
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=GMWSecondsLog){
sumGMW=sumGMW+(index.StrValueByIndex("#", 1).ToFloat()*(count-c));
c=c+1;
}
}
real GMW = sumGMW/countGMW;
dom.GetObject(GMWLogVariable).State(GMW.Round(2)); !- neuen gewichteten Mittelwert für Long schreiben
!- WriteLine(GMW.Round(2));
!------------------- Mittelwert, gewichteter Mittelwert, Varianz und Standardabweichung für Short --------------------
string GMWLogVariable = "GMWWindShort"; !- Variable für gewichteten Mittelwert
string WindShort95Variable = "WindShort95"; !- Variable für WindLong95
integer GMWSecondsLog = 1800; !- Zeitspanne für gewichteten Mittelwert in Sekunden: 1800 (30 Minuten)
real sumMW;
!- Anzahl Elemente für Zeiteinheit ermitteln und "normalen" Mittelwert berechnen
integer count = 0;
foreach (index, newlist){
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=GMWSecondsLog){
sumMW = sumMW + index.StrValueByIndex("#", 1).ToFloat();
count=count+1;
}
}
real MWShort = sumMW/count; !- Mittelwert für Short
real sumVQ;
foreach (index, newlist){
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=GMWSecondsLog){
sumVQ = sumVQ + ((index.StrValueByIndex("#", 1).ToFloat() - MWShort) * (index.StrValueByIndex("#", 1).ToFloat() - MWShort));
}
}
real VarianzShort = sumVQ/count;
real StandAbwShort = VarianzShort.Sqrt();
real WindShort95 = MWShort+(StandAbwShort*Faktor);
dom.GetObject(WindShort95Variable).State(WindShort95.Round(2)); !- WindShort95 schreiben
real countGMW = ((count*(count+1))/2);
real sumGMW;
integer c = 0; !- Zähler für Gewichtung
foreach (index, newlist){
if ((now-index.StrValueByIndex("#", 0).ToInteger())<=GMWSecondsLog){
sumGMW=sumGMW+(index.StrValueByIndex("#", 1).ToFloat()*(count-c));
c=c+1;
}
}
real GMW = sumGMW/countGMW;
dom.GetObject(GMWLogVariable).State(GMW.Round(2)); !- neuen gewichteten Mittelwert für Short schreiben
!- WriteLine(GMW.Round(2));
Viel Spaß, bytelander