Einleitung
Hinweis: Diese Erklärung basiert auf dem StateEngine Plugin im offiziellen Master >= 1.6 Plugins Repository. Das Plugin war früher unter dem Namen AutoBlind bekannt, das noch auf der Github-Seite des Autors verfügbar AutoBlind 1.4.0 ist. Alle Konfigurationsattribute, die mit se_ (stateengine) beginnen, waren ursprünglich as_ (autostate)!
So mal ganz zu Beginn.. was ist überhaupt eine State Engine..!? Es gibt mehrere Möglichkeiten, eine solche zu definieren. Das Stateengine Plugin arbeitet dabei mit einer Hierarchie von „States“, also Zuständen, die nacheinander abgearbeitet werden. Jeder Zustand besteht dabei aus zwei Teilen.
- „Conditions“: Voraussetzungen/Bedingungen, die erfüllt sein müssen, um den Zustand einzunehmen und somit die damit verbundenen Aktionen auszuführen. Genau genommen werden diese Voraussetzungen als „Condition Sets“ deklariert, also als Gruppe von Voraussetzungen. Nur wenn alle Voraussetzungen einer Gruppe erfüllt sind, wird der Zustand eingenommen. Beispiel: Der Zustand „Heiß“ soll nur dann eingenommen werden, wenn es draußen entsprechend heiß ist (z.b. Temperatur > 22 Grad) UND die Sonne beim Fenster rein scheint (z.b. Winkel der Sonne liegt zwischen zwei Werten).
- „Actions“: Aktionen, die ausgeführt werden sollen, wenn ein Zustand eingenommen wird. Im Beispiel „Heiß“ könnten also zB Jalousien geschlossen werden. Außerdem könnte die Klimaanlage aktiviert werden. Im Normalfall werden immer alle Aktionen eines Zustands der Reihe nach ausgeführt.
Sind die Voraussetzungen für einen Zustand nicht erfüllt, wird hierarchisch der nächste Zustand gecheckt. Sind auch dort nicht alle Voraussetzungen erfüllt, geht es zum Zustand darunter, usw. Für die Beschattung könnten die Zustände im einfachsten Fall, wie im unteren Beispiel, aus dem Zustand „Heiß“ (Schließen) und „Normal“ (Öffnen) bestehen. Möchte man es etwas flexibler gestalten, fügt man noch z.B. einen Zustand für manuellen Betrieb ein, der es ermöglicht, die Jalousien mittels Schalter trotzdem (für eine bestimmte Zeit) manuell zu fahren. Dazu braucht es dann also einen „Suspend“ Zustand, der hierarchisch ganz oben (also im yaml File als erstes) definiert werden muss. Dieser Zustand hätte beispielsweise als Voraussetzung, dass innerhalb der letzten x Stunden ein Schalter betätigt wurde. Sobald dann z.B. die Zeit für diesen manuellen Modus abgelaufen ist, kann der Zustand nicht mehr eingenommen werden. Ist es dann immer noch heiß, wird der „Heiß“ Zustand eingenommen. Ist es inzwischen kühler, wird der Normalzustand eingenommen.
Es gibt nun zwei Strategien, wie diese Zustandsmaschine eingesetzt werden kann:
- global/generell: Man könnte ein eigenes Item mit den Regeln erstellen, das sich einfach mal um Temperatur und Sonnenstand kümmert. Das ist bei einem Haus aber nicht sonderlich sinnvoll, da ja nicht immer alle Jalousien geschlossen werden sollen, sondern nur die, wo die Sonne auch herein scheint. Insofern wäre ein individueller Einsatz (nächster Punkt) sinnvoller. Wenn es aber beispielsweise um eine Gartenbewässerung geht, muss vielleicht nicht jeder Sprinkler oder Tropfschlauch individuell geschaltet werden, sondern alles soll gleichzeitig aktiviert werden, sobald die Erde trocken ist. Hier könnte man also ein Item namens „Bewässerung“ erstellen, unter dem dann das Regelwerk mit zwei Zuständen (an und aus oder trocken/nass, wie auch immer man sie nennen will) definiert wird.
- individuell: Hierbei wird für jedes Gerät/Item, das individuell gesteuert werden soll, ein eigenes Regelwerk definiert. Im „Heiß“ Szenario würde man also unterhalb jeder Jalousie oder Jalousiegruppe mit entsprechender Himmelsrichtung ein Regelwerk erstellen. Die Klimaanlage würde ebenfalls ein eigenes Regelwerk erhalten, da sie beispielsweise nur auf die Innentemperatur reagieren sollte und nicht auf die Sonneneinstrahlung.
Wir beginnen mit einer einfachen Jalousie, für die wir eine Sonnenverfolgung (z.B. Temperatur ist hoch und Sonne strahlt auf Jalousie -> Jalousie wird geschlossen) und einen Standardzustand (Sonne scheint nicht auf Jalousie oder ist nicht stark genug -> Jalousie bleibt offen) erstellen wollen:
blinds:
type: foo
room:
type: foo
name: Blinds in Room XYZ
blind_height: # standard item to set the height
knx_send: 2/1/14
knx_dpt: 5001
visu: 'yes'
type: num
knx_cache: 2/1/20
blind_lamella: # standard item to set the lamella angle
knx_send: 2/1/15
knx_dpt: 5001
visu: 'yes'
type: num
knx_cache: 2/1/21
stateengine: # everything below contains relevant items for the stateengine
type: foo
rules: # Here are the rules for the different states.
type: bool
se_plugin: active # activates the plugin for this item
Bis jetzt passiert noch gar nichts. Zum einen sind keine Regeln/Zustände definiert, zum anderen wird die Evaluierung der Zustandsmaschine auch gar nie getriggert. Fügen wir also ein „eval_trigger“ im Item „rules“ ein, um die Auswertung der Regeln auszulösen:
rules:
eval: True
eval_trigger:
- blinds.triggeritem # an item that might trigger every 2 minutes using cycle: 120 = 1
Nun werden die Zustände ausgewertet, sobald das Item blinds.triggeritem seinen Wert ändert. Idealerweise setzt man für dieses ein cycle Attribut, damit es regelmäßig die Evaluierung der Stateengine anstößt. Da noch keine Regeln definiert sind, wird allerdings immer noch nichts passieren 😉 Fügen wir also einen einfachen Zustand namens suntracking und einen Zustand namens „standard“ hinzu. Am Ende sollte immer ein Zustand ohne Bedingungen als „Standard“-Zustand vorhanden sein.
rules:
suntrack:
enter_temperature: # This is our condition set. It needs to be called "enter". You can add a _name if want. If you need multiple condition sets, you HAVE to add different _name(s)
# if all three conditions are true the state will be entered
se_min_sun_altitude: 20 # if altitude is at least 20 degrees.
se_max_sun_altitude: 120 # if altitude is max 120 degrees.
se_min_temperature: 22 # if temperature is at least 22 degrees.
on_enter_or_stay: # these actions will get triggered every time the enter-conditions are met.
se_action_height: # action for the item "height" that has to be defined in the rules section
- 'function: set' # The height item will get set to a specific value
- 'to: 100' # The value the height item is set to
standard: # this has no conditions. So if no state defined before is entered, this will be entered
on_enter_or_stay:
se_action_height: # the blind height gets set to 0
- 'function: set'
- 'to: 0'
- 'order: 1'
Die se_action_ Angaben definieren Aktionen, die mit den entsprechenden Items ausgeführt werden sollen. Es ist notwendig, die passenden Items (für die Jalousienhöhe) unter „rules“ zu definieren, sonst weiß das Plugin nicht, welche Items zu ändern sind.
rules:
se_item_height: ...blind_height # height item, defined relatively, you can also use absolute definitions of course
Das Gleiche gilt für die Items, die ausgewertet werden, um in den Zustand einzutreten, in unserem Beispiel sind das Sonnenstand und Temperatur:
rules:
se_item_sun_altitude: weather.sun.altitude # refer to an item updated by a weather station or weather plugin
se_item_temperature: weather.temperature # refer to an item updated by a weather station or weather plugin
Bis hierhin sollte klar sein, dass alle Elemente, auf die in den Zustandsdefinitionen verwiesen wird, am Anfang des Regelabschnitts durch „se_item_“ und einen möglichst sinnvollen Namen definiert werden müssen. In den Aktionen selbst verweisen wir wieder auf diese Namen, aber diesmal nicht mit „se_item_“, sondern mit „se_action_“.
Ähnlich verhält es sich mit den Items, die für den Eintritt in einen Zustand relevant sind. So muss die Temperatur als „se_item_temperature“ definiert werden. Für die Zustandsauswertung ersetzen wir den „item“-Teil durch eine Bedingung wie „min“, „max“, „agemin“, „agemax“, etc. Genaueres dazu steht in der Dokumentation des Plugins.
Zusammengefasst sieht der Code wie folgt aus:
blinds:
triggeritem:
type: bool
name: Trigger
cycle: 120 = 1
enforce_updates: True
room:
name: Blinds in Room XYZ
blind_height:
knx_send: 2/1/14
knx_dpt: 5001
visu: 'yes'
type: num
knx_cache: 2/1/20
blind_lamella:
knx_send: 2/1/15
knx_dpt: 5001
visu: 'yes'
type: num
knx_cache: 2/1/21
stateengine:
rules:
type: bool
se_plugin: active
se_item_height: ...blind_height
se_item_lamella: ...blind_lamella
se_item_altitude: weather.sun.altitude
se_item_temperature: weather.temperature
eval: True
eval_trigger:
- blinds.triggeritem
suntrack:
enter:
type: foo
se_min_sun_altitude: 20
se_max_sun_altitude: 120
se_min_temperature: 22
on_enter_or_stay:
se_action_height:
- 'function: set'
- 'to: 100'
standard:
on_enter_or_stay:
se_action_height:
- 'function: set'
- 'to: 0'
Nachdem wir das Plugin in der plugin.yaml aktiviert haben, haben wir nun eine Jalousie, die sich schließt, wenn die Temperatur über 22 Grad liegt und die Sonne eine bestimmte Höhe hat. Andernfalls werden die Jalousien geöffnet.
Stateengine Definitionen wiederverwerten
In der Regel möchten wir einige Zustandsdefinitionen für mehr als ein Item verwenden. Beispielsweise sollen Zustände für manuelle Bedienung oder Sperrfunktionen (Es werden keine Evaluierungen durchgeführt) für alle Items/Geräte gleich genutzt werden. Außerdem wollen wir die Bedingungen zur Sonnenhöhe und Temperatur für alle Jalousien im Haus heranziehen. Wir möchten dann lediglich noch individuell auf die Himmelsrichtung eingehen. Aus diesem Grund können wir Zustände auf globaler Basis definieren und diese Zustände im Abschnitt „rules“ entweder mittels „se_use“ oder „struct“ (seit SmarthomeNG 1.6) referenzieren. Das Stateengine-Plugin stellt standardmäßig einige allgemein nützliche Zustände zur Verfügung, die auf die folgende Weise einfach implementiert werden können.
blinds:
room:
blind_height:
type: num
stateengine:
remark: >-
With "general" some relevant standard definitions are created. The release, lock and suspend states are
useful states provided by the plugin, too. The last two are defined in the etc/struct.yaml file manually.
struct:
- stateengine.general
- stateengine.state_release
- stateengine.state_lock
- stateengine.state_suspend
- se_suntrack
- se_standard
manuell:
remark: You have to setup the manual item based on your items (e.g. blind_height, etc.). This is important to evaluate the "suspend" state.
type: bool
name: manuelle bedienung
eval_trigger:
- ...blind_height
se_manual_invert: True
rules:
se_item_height: ...blind_height
remark: The previous structs already define some useful eval_triggers. If you want to add additional ones, don't forget the first line and define your trigger as a list!
eval_trigger:
- merge_unique*
- weather.temperature
Vor smarthomeNG 1.7 musste der eval_trigger für jedes Item überschrieben werden, da die Attributliste nicht kombiniert wurde. Der eval_trigger hätte wie folgt ausgesehen:
rules:
se_item_height: ...blind_height
eval_trigger:
- ..lock
- ..manuell
- ..release
- ..retrigger
- weather.temperature
Nützliche Zustände dank Plugin-Structs
Was hat es nun mit state_release, state_lock und state_suspend auf sich..!? Das sind drei Zustände, die wie erwähnt normalerweise für alle Zustandsmaschinen relevant sind.
- release: Wird das Item „release“ unter dem stateengine Item (also im Beispiel blinds.room.stateengine.release) aktiviert (auf True gesetzt), wird der aktuelle Zustand verlassen und eine erneute Evaluierung der Zustandsmaschine forciert. Dies ist beispielsweise sinnvoll, wenn man frühzeitig ein manuelles Aussetzen abbrechen möchte (z.B. wenn man mittels Taster die Jalousie gesteuert hat, nun aber doch die Jalousie in einen anderen Zustand versetzen will)
- lock: Durch Aktivieren des Items „lock“ unter dem stateengine Item (also blinds.room.stateengine.lock) wird die Evaluierung ausgesetzt. Es könnten nun also Bedingungen für Zustände erfüllt sein, aber es wird nichts passieren – die Jalousien machen einfach nichts bzw. lassen sich normal über Schalter steuern – bis das „lock“ Item wieder deaktiviert, also auf False gesetzt wird.
- suspend: Alle Items, die im eval_trigger des „manuell“ Items gelistet sind, können dafür sorgen, dass ein manueller Pause-Modus aktiviert wird. Dieser wird für eine bestimmte Zeit beibehalten, die über ein entsprechendes Item eingestellt werden kann. Nach Ablauf der Zeit wird der Zustand verlassen und die Zustandsmaschine neu evaluiert.
Die Vorlagen für suntrack und standard States müssen nicht als Items (items/<xyz>.yaml), sondern können in structs/global_structs.yaml definiert und eben mittels struct: eingebunden werden. Die structs se_suntrack und se_standard haben dabei 1:1 die gleichen Einträge wie im obigen Beispiel.
Der konkrete Ablauf
Das folgende Diagramm zeigt die hierarchische Auswertung der Zustände. Eine ähnliche Visualisierung bietet auch die Weboberfläche des Plugins – einfach ausprobieren!
Was bedeutet das nun genau..!?!?
Wir können jederzeit die Zustandsevaluierung stoppen, indem das „lock“ Item auf True gesetzt wird. Möchten wir weitermachen, setzen wir es wieder auf False. Immer, wenn wir das Item blind_height ändern (z.B. über einen KNX Taster oder die Visu), sind wir im manuellen Modus, der für eine bestimmte Zeit beibehalten wird – es kann also noch so heiß oder kalt werden, die Jalousie bleibt bis zum Ablauf der „suspend_time“ (eigenes Item) auf der manuell definierten Höhe. Hier ist eine kleine Eigenheit zu beachten, wenn z.B. ein Jalousieaktor immer seinen aktuellen Wert sendet, sobald die Jalousie gefahren wurde (also auch durch eine Aktion in einem Zustand) – dazu mehr in der Dokumentation des Plugins! Ist weder Sperren, noch Pausieren aktiv, könnte die Jalousie zufahren, wenn die Sonne eine bestimmte Höhe und die Temperatur einen gewissen Mindestwert haben. Wenn nicht, fährt die Jalousie auf.
Individualisieren von Zuständen
Soweit, so gut. Jetzt haben wir aber noch nicht die Thematik der Himmelsrichtung besprochen. Wie einleitend erwähnt, sollen ja z.B. die Jalousien nur dann zufahren, wenn die Sonne einen bestimmten Azimut-Wert hat, also von der passenden Himmelsrichtung her kommt. Jalousien im Osten sollen also am Vormittag reagieren, die im Westen erst am Abend. Dies können wir auf zwei Arten angehen:
- Nutzen der vielen Zusatzfunktionen des Plugins: Damit wird es richtig komplex. Man könnte Templates, Variablen, eval-Funktionen, etc. in die Zustände einbinden, um flexibel auf verschiedene Bedingungen zu reagieren. Dadurch wäre es sogar möglich, eine einzige globale Zustandsmaschine zu definieren, also nicht für jedes Item (jede Jalousie) selbst. Aber das wird richtig wild, daher lassen wir das mal 😉
- Ergänzen von Zuständen mittels se_use. Hierbei kann auf ein anderes Item oder ein struct referenziert werden, wobei immer auf den Zustand selbst verwiesen werden muss, beispielsweise struct:se_suntrack.rules.suntrack. Problematisch wird es, wenn die struct-Vorlage nicht nur Regeln beinhaltet, sondern auch zusätzliche Items (wie z.B. zum Setzen des Sperrzustands) – diese werden bei se_use nicht übernommen, da eben nur das Regelwerk selbst eingebunden wird und nichts aus höheren Hierarchieebenen.
- Erweitern von structs. Diese Herangehensweise ermöglicht das komplette Einbinden von Vorlagen inkl. Definitionen und Items oberhalb des „rules“ Items. Wichtig sind hierbei aber zwei Dinge. Zum einen muss die Ergänzung VOR dem Einbinden des structs definiert werden, zum anderen muss das Attribut se_stateorder genutzt werden, damit klar ist, an welcher hierarchischen Stelle die Regel eingebunden sein sollte. In unserem Fall wäre das nach release, lock, suspend, also Nr. 4.
Im folgenden Beispiel ergänzen wir die in structs/global_stucts.yaml erstellte Vorlage namens se_suntrack mit zwei Zeilen zur Sonnenrichtung.
blinds:
room_east:
blind_height:
type: num
stateengine:
rules:
suntrack:
se_stateorder: 4
enter:
se_min_sun_azimut: 20
se_max_sun_azimut: 120
struct:
- stateengine.general
- stateengine.state_release
- stateengine.state_lock
- stateengine.state_suspend
- se_suntrack
- se_standard
room_west:
blind_height:
type: num
stateengine:
rules:
suntrack:
se_stateorder: 4
enter:
se_min_sun_azimut: 220
se_max_sun_azimut: 300
struct:
- stateengine.general
- stateengine.state_release
- stateengine.state_lock
- stateengine.state_suspend
- se_suntrack
- se_standard
Auf diese Weise werden sämtliche Bedingungen unseres structs „se_suntrack.rules.suntrack“ durch die zwei zusätzlichen Zeilen ergänzt und somit individualisiert.
Gratulation!
Wir haben nun eine grundlegende Zustandsmaschine für Jalousien erstellt und diese individuell erweitert. Nach dem gleichen Muster lassen sich nun noch viele Ergänzungen angehen. Beispielsweise möchte man vermutlich auch Hysteresen umsetzen. So soll der „Heiß“ Zustand nicht gleich bei 21.9 Grad wieder verlassen werden, sondern erst, wenn die Temperatur längere Zeit unter 22 bleibt. Eventuell möchte man auch noch weitere Bedingungsgruppen definieren, sodass nicht nur die Kombination von Sonnenstand und Temperatur beachtet wird, sondern vielleicht auch die Helligkeit außen und innen, die Raumtemperatur, etc. Dann hätten wir nicht nur einen „enter“-Eintrag, sondern ein „enter_aussentemp“ und „enter_innentemp“ oder ähnlich. Vielleicht möchten wir den Sonnenschutz auch nur in den Sommermonaten aktivieren, dann könnten wir die Bedingungsgruppen mit einer Abfrage zum aktuellen Monat ergänzen und den Zustand nur einnehmen, wenn es zwischen März und September ist.
Natürlich machen gerade für Jalousien auch weitere Zustände Sinn, beispielsweise sollen sie am Abend halb runter fahren und/oder die Lamellen halb öffnen, in der Nacht immer ganz zu fahren, etc. Aber was bedeutet Nacht? Immer wenn es dunkel ist? Sollen die Jalousien auch zufahren, obwohl ich gerade auf der Terrasse sitze? Womöglich nicht – dann müsste man einen weiteren Zustand „SperrMichNichtAus“ definieren, der beispielsweise eingenommen wird, wenn die Türe offen ist und der Bewegungsmelder auf der Terrasse innerhalb der letzten 10 Minuten aktiv war..? Wie ist es bei Wind? Hier ist die Empfehlung natürlich, das Sicherheitsobjekt der KNX Aktoren zu nutzen. Aber diese Funktionen sind meist recht limitiert, sodass man zumindest das Verlassen des Sicherheitsmodus über einen entsprechenden Zustand definieren und ergänzen möchte (wieder Stichwort Hysterese).
The sky is the limit!
Natürlich lassen sich auch viele andere Dinge sinnvoll über State Machines steuern und optimieren. Vieles ist prinzipiell auch über Logiken machbar und im Endeffekt ist das Plugin auch nicht so viel anders als ein Set an Logiken und Regeln. Nur anstelle von Hunderten if/else Abfragen lässt sich hier alles komfortabel über eine Item-Hierarchie anlegen und individualisieren. Lichtszenen können sich am Verhalten der Anwesenden orientieren, Musik-Playlisten werden abhängig von Uhrzeit, Geburtstagen, Weihnachtszeit, etc. individuell geladen, Elektronikgeräte schalten je nach Situation in verschiedene Modi, die Heizung steuert sich abhängig vom Ertrag der PV-Anlage, dem Wetterbericht, der Temperatur, Anwesenheit, und und und. The sky is (not) the limit!
5 Kommentare
onkelandy · 15. April 2020 um 22:10
Hi! Ich verstehe noch nicht genau, worauf du hinaus willst.. Was würdest du mit der rückgemeldeten Laufzeit machen wollen?
Frank Häfele · 15. April 2020 um 21:21
Hi Onkelandy, ist es sinnvoll wenn die Rolladenaktoren beim Betrieb mit der Statemachine ihre Laufzeit zurückmelden?
Würde gerne meine Enocean Aktoren damit nutzen, nur aktuell ist das Rückmelden der aktuellen Laufzeit noch nicht implementiert.
onkelandy · 18. Juli 2019 um 23:14
Bernd, hier kommt deine gewünschte Grafik.. tadaaaaa!
Andreas · 9. November 2018 um 13:20
Bezüglich Deutsch hilft dir der Browser/Google 😉
Was für ne Grafik schwebt dir vor? Ein Ablaufdiagramm? Kann ich im (noch ausstehenden) advanced Beitrag einbinden. Hier wird ja nur ein einziger Zustand evaluiert.
Bernd Meiners · 9. November 2018 um 13:10
Es wäre schön das in deutsch lesen zu können und dazu auch ein paar Grafiken zur Veranschaulichung zu haben. So verliere ich dann doch ein wenig den Überblick. Aber Danke für die Mühe!