NEWS
Thermostat Automatisierung mit mehreren Bedingungen
-
Hallo zusammen,
mein Ziel ist es momentan mittels Node-Red meine Heizkörperthermostate so zu programmieren, dass sie morgens ein- und abends wieder ausschalten und dabei bei zwischen Wochenenden und Arbeitswoche zu unterscheiden. Dafür nutze ich zur Zeit die Inject Nodes als Timer und gebe die Target-Temperature Werte an meine Thermostate weiter. Das funktioniert auch soweit.
Zusätzlich würde ich nun gerne eine Anwesenheitskontrolle integrieren. Mit der FRITZ!Box-Node bin ich jetzt soweit, dass ich erkenne, sobald die Smartphones von meiner Freundin und mir im WLAN verbunden sind.
Ich habe nun versucht mit verschiedenen switch und change-Nodes einen Flow zu erstellen, der nun die Zeitabfrage mit der Anwesenheitskontrolle verknüpft und somit die Thermostate steuert, jedoch wird das Ganze mit meinen "Standard" Nodes sehr unübersichtlich und ich habe es nicht geschafft alles nach meinen Vorstellungen funktionsfähig zu kriegen.
Ich könnte mir gut vorstellen, dass ich mithilfe der function-Node vieles vereinfachen könnte, indem ich dort die Bedingungen hineinschreibe und verknüpfe. Leider habe ich so gut wie keine Progammiererfahrung und habe mich daher noch nicht daran getraut. Es würde mir sehr helfen, wenn jemand beispielhaft ein kleines Script für die function-Node schreiben könnte, welches die oben genannten Bedingungen erfassen kann und mir am Ende als Resultat die gewünschte Soll-Temperatur für die Thermostate rausgibt.
Gibt es sonst noch einen guten Weg, wie ich mich als Programmieranfänger mit der function-Node vertraut machen kann?
Gerne gebe ich noch weitere Infos über mein System an, falls das benötigt wird.
Liebe Grüße
cpt-pommes -
@cpt-pommes Na ich finde es sehr schade - wenn Du nun wieder codieren willst. Ich denke es liegt nicht an der Programmierung, sondern weil Du es nicht strukturiert angehst.
Eine function Node ist reines Programmieren in Javascript. Als Eingang hast Du ein msg Objekt und gibts halt auch wieder eines aus. Aber wie gesagt, ich würde es gar nicht so anfangen, sondern lieber die Standard-Nodes nutzen. Ggf- müsstest Du halt eher mit dem Kontext arbeiten um die Zustände zu kombinieren.
-
@mickym Danke für den Tipp! Ich habe mich mal etwas zum Thema Kontext eingelesen, da ich es vorher noch nicht benutzt habe und ich denke, dass es einiges vereinfachen wird.
Wie könnte ich es denn am besten handhaben, wenn ich nur bei der Abwesenheit von allen Personen die Heizung herunterfahre und bei allen anderen Zuständen (einer von beiden ist da bzw. beide sind da) hochfahre. Kann ich diese Bedingungen auch gleichzeitig abfragen oder muss das hintereinander passieren?
-
@cpt-pommes Ob hinter einander oder nicht, hängt von der Logik ab - Du musst halt thematisch sauber arbeiten. Sprich kümmere Dich erst mal darum den Abwesentheitsstatus sauber zu definieren und dann kümmerst Dich um die Trigger.
Im Prinzip macht man so etwas mit Objekten. Du nimmst einfach ein Objekt Anwesenheit, was bereits true ist, wenn nur eine Person anwesend ist.
Ich empfehle Dir dazu die Lightscheduler Nodes für jedes Thermostat - da kannst Du grafisch einstellen, wann du heizen willst und wann nicht. Da kannst Du dann sogar mehrfache Zeitfenster pro Wochentag definieren.
Im Prinzip finde ich so einen Flow - wenn pro Heizung nur eine Anwesenheit und eine Abwesenheit Temperatur gesetzt wird sehr einfach.
Die Lightscheduler Nodes sind folgende: node-red-contrib-light-scheduler
Wenn du noch verschiedene Temperaturen willst, kannst du noch die Filternodes mit verschiedenen Temperaturen hinten dran hängen.
Hier nochmal eine Version, dass man das auch übersichtlich mit Standard-Nodes machen kann ohne viel zu codieren (OK ein paar JSONATA Kenntnisse sollte man sich aneignen). Dieser Flow arbeitet direkt mit einer Flowvariable, die den Heartbeat während der Abwesenheit blockiert. Ich hab den mal auf 5 Minuten eingestellt. Ich finde das nicht unübersichtlich.
Ein Beispiel wieder mal wie toll man die Moments Bibliothek nutzen kann. Siehe auch: https://forum.iobroker.net/post/720234
Hier nochmal elegant - alle Zeitfenster in einem JSONATA-Ausdruck - als Alternative:
Fazit: Du kannst natürlich alles in function Nodes programmieren - aber damit machst Du den Sinn und die Möglichkeit die Logik grafisch nachzubilden kaputt.
Wenn Du Javascript lernen willst - dann kannst Du Dir hier das Tutorial anschauen: https://www.w3schools.com/js/default.asp
Spezialitäten der function Node hast Du ja in der Hilfe https://nodered.org/docs/user-guide/writing-functions
Aber wie gesagt - meide function Nodes so gut es geht. Es gibt bestimmte Situationen, wo das Sinn macht, aber wie gesagt mach Dir Dein Node-Red nicht kaputt in dem Du programmierst.
-
@mickym Danke erstmal für diese ausführliche Antwort und Hilfe! Wie auf dem Silbertablett serviert und dann auch noch Auswahlmöglichkeiten.
Ich werde mich erstmal daransetzen die einzelnen Möglichkeiten nachzuvollziehen, hätte anfangs nicht gedacht, dass es mit den Standard Nodes doch so übersichtlich möglich ist. Die light-scheduler Nodes sind auch ein super Extra dafür.
Du hast natürlich recht, wenn ich alles mit der Function-Node mache, geht am Ende der Sinn hinter Node-Red verloren. Ich habe mir das so überlegt, da ich mir allgemein mehr Erfahrungen in Sachen programmieren aneignen möchte, aber die Function-Node ist da wirklich nicht der beste Ansatz.
Ich werde berichten, ob alles geklappt hat, vielen Dank nochmals!
-
@cpt-pommes Die Funktionen der Nodes sind mir soweit klar. Ich bin im Lightscheduler Flow jedoch über eine Sache gestolpert:
In der Change-Node "OR ?" wird das Paket der join-Node so aufgetrennt, dass nur true für die Anwesenheit mindestens einer Person herausgegeben wird, ansonsten false.
Wie ergibt sich das aus der folgenden Zeile ?$reduce(payload.*,function($i, $j){$i or $j})
Wofür stehen i und j hierbei?
Das ganze taucht in dem Standard-Node Flow auch nochmal in ähnlicher Weise auf:
payload ~> $reduce (function($A, $i){$A or $i})
Hier wird mir die Funktion der Zeile in der Change-Node nicht klar.
-
@cpt-pommes Nun musst in der JSONATA-Doku schauen: https://docs.jsonata.org/higher-order-functions#reduce
Die Funktion ist also so definiert:
$reduce(array, function [, init])
Ziel der reduce Funktion ist es ein Array in einen Wert zusammenzufassen oder zu reduzieren.
Im ersten Fall wird das Array direkt angegeben (also payload.*), im zweiten Fall ist die payload direkt ein Array und wird über den Verkettungsoperator (Chain: https://docs.jsonata.org/other-operators) rein gegeben. Sprich bei dem Verkettungsoperator kannst Du den Eingabewert weglassen, deswegen siehst Du da nur die Funktion.
Wenn Du Dir die Doku weiter anschaust, wie muss die Funktion bei $reduce definiert sein:
myfunc($accumulator, $value[, $index[, $array]])
Das heißt der erste Parameter ist der akkumulierte Wert, der 2. Wert ist der Wert der mit dem bis dahin akkumulierten Wert verknüpft wird, Im ersten Fall $i, im 2. Fall ist das $A, der dann letztlich als Ergebnis zurückgegeben wird.
Im Prinzip ist die $reduce Funktion das gleiche wie in der JOIN Node mit Sequenz reduzieren. Da kannst Dir auch die Hilfe anschauen zur JOIN Node anschauen. Dort wurde als Beispiel das Bilden eines Mittelwertes genutzt.
Das heißt was in der JOIN Node $A ist ist der erste Parameter, die payload der Wert mit dem der akkumulierte Wert verknüpft wird.
Bei der Anwesenheit wird also ein Array aus allen Werten gebildet und dann miteinander verknüpft.
Ich empfehle immer die try Seite von JSONATA - damit kannst Du das quasi in Echtzeit sehen.
Wenn Du Dir das Objekt aus der JOIN Node auswählst und das Array als payload einer Eigenschaft in das try Fenster kopierst:
Die Ausgabe weißt Du also einer Eigenschaft payload zu:
dann schaut das im JSONATA try Fenster so aus.
Dann kannst Du die Befehle nach und nach nachvollziehen. Mit dem Punkt als Mapping und dem * das uns die Eigenschaft egal ist, erhalten wir also das array:
Das heißt damit bekommt man ein Array nur mit den Werten.
Nun nehmen wir mal zur Abwechslung den chain Operator und die $reduce funktion dieses Mal habe ich $A für den akkumulierten (Ergebnis) Wert und $value mit dem jedes Element mit dem akkumulierten Wert verknüpft wird :
Kann man damit auch direkt testen.
Es passiert also folgendes.
- $A = undefined mit erstem Wert $value = false also $A or $value ergibt false.
- $A = false mit dem zweiten Wert $value = false also $A or $value ergibt weiter false.
Schau Dir doch auch in der ChangeNode bei Nutzung von JSONATA auch immer die Hilfe zu den Funktionen an:
Also hier die Hilfe zu der $reduce Funktion.
Du siehst die Change Node mit JSONATA ist wesentlich mächtiger, als es auf den ersten Blick aussieht, so dass man sich eine Menge Codiererei in Javascript wie bei function Nodes sparen kann.
- Wenn man sich damit beschäftigt - kannst Du gegenüber normalem Javascript Code eine Menge Zeilen sparen.
Wenn Du Lust hast, kannst Dir ja mal das kleine JSONATA Tutorial anschauen, was ich mit @Damrak2022 im Februar durchgemacht habe: https://forum.iobroker.net/post/941919
-
@mickym Okay, also es wird ein array als input reduziert anhand der folgenden Funktion. Ich musste zunächst einmal noch verstehen, dass der "or" Operator nur true herausgibt, wenn mindestens einer der Werte true ist, da hat es bei mir noch am Grundwissen gefehlt.
Habe ein bisschen mit dem Exerciser rumgespielt.
Die folgende Funktion würde also jeden einzelnen Wert mit allen übrigen vergleichen und sobald eine dieser Kombinationen/Verknüpfungen aus dem kompletten array true ist, true herausgeben? Damit könnte man also auch noch mehr Personen zur Anwesenheitskontrolle hinzufügen und es würde immer die Heizung eingeschaltet werden, sobald mindestens eine Person da ist?
$reduce (function($i, $j){$i or $j})
Mit dieser hier sollte dann das vorherige Kombinationsergebnis mit dem nächsten Wert verglichen werden, wobei der erste $A undefined ist, da es nur einen Wert gibt, also kein Vergleich möglich.
$reduce (function($A, $i){$A or $i})
Würde auch so eine Funktion Sinn machen? Habe noch nicht ganz verstanden, wofür $j steht.
$reduce (function($A, $j){$A or $j})
Noch eine andere Frage: Was bewirkt beim "Zeitfenster Heizen" an Wochentagen die erste change Regel? Also "Setze msg.payload auf/nach []" Wird dadurch soetwas wie ein Vektor erstellt, der dann mit den folgenden Regeln gefüllt wird und für [0],[1],[2],[3] usw wird eine neue Variable hinzugefügt?
Diese ist super um das ganze mit der CHange Node zu lösen. Wird hier 'minute' dafür herangezogen um den Abgleich anhand der Minuten zu machen? Wenn zB "08:05" dort stehen würde anstatt "08:00" und 'hour' am Ende, dann würde bereits ab 08:01 Uhr false herausgegeben werden?
[ $moment().isBetween( $moment("06:00", "HH:mm"), $moment("08:00", "HH:mm"), 'minute', '[)' ), $moment().isBetween( $moment("11:00", "HH:mm"), $moment("13:00", "HH:mm"), 'minute', '[)' ), $moment().isBetween( $moment("18:00", "HH:mm"), $moment("23:00", "HH:mm"), 'minute', '[)' ) ]~> $reduce (function($A, $i){$A or $i})
Meine Heizungen machen jedenfalls was sie sollen und ich verstehe so langsam auch warum
-
@cpt-pommes sagte in Thermostat Automatisierung mit mehreren Bedingungen:
Die folgende Funktion würde also jeden einzelnen Wert mit allen übrigen vergleichen und sobald eine dieser Kombinationen/Verknüpfungen aus dem kompletten array true ist, true herausgeben? Damit könnte man also auch noch mehr Personen zur Anwesenheitskontrolle hinzufügen und es würde immer die Heizung eingeschaltet werden, sobald mindestens eine Person da ist?
Ja - Du kannst das um beliebige Personen erweitern - funktioniert immer sobald eine Person da ist. Ich denke, dass das auch so gewünscht ist.
Nochmal:
myfunc($accumulator, $value[, $index[, $array]])
Die function innerhalb der reduce function definiert die Bedeutung der Variablen in der Reihenfolge, in der sie bei der Definition definiert werden. Der 1. Wert ist der akkumulierte Wert (egal ob der $i,$j,$A, $accumulator oder $Ergebnis heißt), der 2. Wert ist der Wert ist der Wert mit dem durch das Array gegangen wird (egal ob der $i,$j,$A, $value oder $wert heißt). Insofern ist es egal wie die Variable heißt (in JSONATA müssen nur alle Variablen mit einem $-Zeichen beginnen). Die Bedeutung der jeweiligen Variablen ergibt sich aus der Position bei der Definition der Funktion
$reduce(function($accumulator,$value){$accumulator or $value})
Noch eine andere Frage: Was bewirkt beim "Zeitfenster Heizen" an Wochentagen die erste change Regel? Also "Setze msg.payload auf/nach []" Wird dadurch soetwas wie ein Vektor erstellt, der dann mit den folgenden Regeln gefüllt wird und für [0],[1],[2],[3] usw wird eine neue Variable hinzugefügt?
Die Payload wird als leeres Array definiert. Normalerweise ist die payload ein skalarer Wert und durch die Definition eines leeren Arrays - kann ich in den anschließenden Regeln auf die Positionen des Arrays setzen. Wenn Du die erste Regel wegschmeisst, dann wirst Du sehen, dass es eine Fehlermeldung gibt, dass ein skalarer Wert nicht in ein Objekt gewandelt werden kann. Deshalb wird die payload am Anfang als leeres Array definiert. Sobald Du also diese Fehlermeldung bekommst, kannst Du durch ein leeres Objekt oder leeres Array diesen Fehler umgehen. Wenn Du die payload nicht als leeres Array definierst und dann versuchst mit payload[0] etc. ein Element eines Arrays definieren willst - erhälst Du folgende Fehlermeldungen:
Also wenn Du solche Fehlermeldungen hast, dann weißt Du zukünftig, wie Du sie vermeiden kannst.Diese ist super um das ganze mit der CHange Node zu lösen. Wird hier 'minute' dafür herangezogen um den Abgleich anhand der Minuten zu machen? Wenn zB "08:05" dort stehen würde anstatt "08:00" und 'hour' am Ende, dann würde bereits ab 08:01 Uhr false herausgegeben werden?
08:00 ist ein schlechtes Beispiel weil wir es schon exkludiert haben. Sprich um 08:00 wird schon auf false umgeschaltet (s. Klammern). Gesetzt den Fall Du würdest es inkludieren, dann wird mit hour nur die Stunden betrachtet. Es handelt sich also um die Angabe der Granularität - wenn Du auf hour machen würdest wird nur geschaut ob es 08:xx ist - das heisst es ist true egal ob es 08:10 oder 08:30 ist. Wenn dieser Parameter nicht definiert wird, wird bis auf Millisekunden-Ebene überprüft.
Hier mal die Originialdoku: https://momentjs.com/docs/#/query/is-between/
Wichtig ist dass die Ränder der Zeiträume per Default ausgeschlossen werden, wenn Du die Klammern nicht angibst. Der letzte Satz in diesem Kapitel:
So nun zur Granularität:
Wenn man dieses Beispiel also verstehen will, sieht man im 1. Beispiel dass ein false rauskommt obwohl der 20. Oktober 2010 ja zwischen dem 1. Januar 2010 und dem 1.Januar 2012 liegt. Das liegt daran - dass ohne Angaben der Klammern die Ränder ausgeschlossen werden. Da wir als Granulariät das Jahr haben ist das ganze Jahr 2010 und das ganze Jahr 2012 ausgeschlossen und deshalb ergibt das 1. Beispiel false - weil das ganze Jahr 2010 ausgeschlossen wird, AUCH wenn der 20.Oktober 2010 dazwischen liegt.
Im unteren Beispiel ist der Bereich 20.10.2010 zwischen dem 31.12.2009 und 1.1.2012 ist true, weil die kompletten Jahre 2010 und 2011 gültig sind.