Wecker mit FHEM DOIF-Modul und Steuerung über TabletUI

Endlich habe ich auch mal wieder ein wenig Zeit gefunden, meine neuesten FHEM Erkenntnisse in einem kleinen Artikel zusammen zu fassen. Schon seit einiger Zeit wollte ich mich mal dem DOIF-Modul widmen. Dieses Modul wurde zunächst entwickelt, um notify-, watchdog- und at-Befehle an einer Stelle zusammen zu fassen. Das Modul  DOIF dient also dazu bei Ereignissen oder zu bestimmten Zeiten definierte Befehle ausführt. Hierbei können zusätzlich umfangreiche Bedingungen definiert werden, die zutreffen müssen, damit der entsprechende Befehl ausgeführt wird. Also z.B. „Schalte um 18:00 das Licht ein aber nur, wenn es dunkel ist“.

Mittlerweile ist das DOIF-Modul ziemlich komplex geworden und  ich habe einige Zeit gebraucht, die sehr gute und umfangreiche Dokumentation zu lesen und zu verstehen. Bis auf einige Spezialfunktionalitäten habe ich die Funktion hoffentlich nun soweit durchdrungen, dass ich euch nachfolgend keinen Quatsch erzähle ;-).

FTUI Weckersteuerung mittels DOIF Umsetzung

Im Rahmen der Optimierung meiner Haussteuerung und der Bereinigung einiger Altlasten (damit ich demnächst auch mein config mal bereit stellen kann) habe ich mich zunächst entschieden, meine Rolladensteuerung mit DOIF umzusetzen. Hier war der Ansatz, die Fensterkontakte der Terrassentür so zu integrieren, dass die Rolladen nur bei geschlossenem Fenster runter fahren. Da hierbei auch mein selbst entwickelter Wecker ins Spiel kommt und ich hierfür auch schon länger eine andere Variante ausprobieren wollte, habe ich zunächst einen neuen Wecker mit dem DOIF-Modul umgesetzt. Diese Umsetzung zeige ich euch nun Schritt für Schritt und hoffe, dass ihr auch ein wenig mehr zu DOIF kennen lernt.

Ausgangsbasis und Vorbereitungen

Da die Umsetzung eines DOIF schon mal recht umfangreich werden kann, habe ich den Tipp aus dem Wiki zum Thema DOIF-Fehlersuche beherzigt und meiner FHEM Weboberfläche einen optimierten Editor verpasst. Dies erfolgt durch Setzen des Attributs „JavaScripts“ im Web-Device wie folgt (Eingabe über das Device):

attr JavaScripts codemirror/fhem_codemirror.js

In diesem Editor kann man nun sehr einfach Zeilen einrücken, Kommentare mit „##“ einleiten und Zeilenumbrüche umsetzen. Damit wird langer Code deutlich lesbarer.

FHEM Web Editor mit Codemirror nutzen

 

Nun überlegen wir uns zunächst, wie der Wecker funktionieren soll. Ähnlich, wie mein bisheriger Wecker sollte eine Weckzeit für jeden Wochentag definiert werden können. Die aktuell eingestellten Zeiten pro Wochentag sollen natürlich angezeigt werden. Dann sollte es eine Funktion geben, mit der der Wecker deaktiviert werden kann. Weiterhin soll es eine Reset-Funktion geben, mit der alle Weckzeiten mit Standardwerten vorbelegt werden können. Schließlich soll der Wecker über die TabletUI Oberfläche bedient werden können. Wie bisher wird zum eigentlichen wecken das Residents-Modul verwendet. Durch Setzen von „awoken“ werden dann die weiteren Aktionen, wie bisher gesteuert.

Wir haben also grundsätzlich folgende Bedingungen und Zustände, bei denen etwas geschaltet werden soll:

  1. Wenn der Wecker „aktiv“ ist und die für den „Wochentag“ eingestellte „Weckzeit“ erreicht ist, soll „gr_Bewohner“ auf „awoken“ gesetzt werden
  2. Wenn ein Schalter „Reset“ gedrückt wird, dann sollen die „Weckzeiten“ auf „Standardwerte“ gesetzt werden.

Für das DOIF-Modul bedeutet dies somit, dass wir 2 Bedingungen und 2 Aktionen definieren müssen. Jeweils jede Bedingung und jede Aktion wir mit Klammern umschlossen, so dass ein erster Entwurf eines DOIF wie folgt aussehen könnte:

DOIF (Bedingung1-> W_Aktiv == "on" and Weckzeitxx ist eingetreten )
(Aktion1 -> set gr_Bewohner awoken) DOELSEIF
(Bedingung2 -> "W_Reset" wurde auf "on" gesetzt)
(Aktion2 -> set Weckzeitenxx mit Standardwertxx)

Eine DOIF-Anweisung wird immer von links nach rechts abgearbeitet. Sobald die erste Bedingung ausgewertet wird, die „TRUE“ ergibt, wird die entsprechende Aktion ausgeführt und die Bearbeitung beendet. Die ganzen Besonderheiten und zusätzlichen Features solltet ihr unbedingt in der umfangreichen Doku und den vielen Beispielen nachlesen. Hier habe ich auch viele Dinge gelernt, die ich für die Umsetzung des Weckers benötigt habe.

FHEM mit Einsatz von Codemirror – Code übersichtlich darstellen

Definition des DOIF für unseren Wecker

Nachdem wir nun oben einen ersten Entwurf unseres Wecker-Codes definiert haben, geht es jetzt an die konkrete Umsetzung mittels DOIF-Definition. Zu den oben aufgeführten Grundinformationen sei noch erwähnt, dass das DOIF-Modul mittlerweile auch als „Dummy“-Ersatz genutzt werden kann. Hierdurch entfallen weitere Definitionen, die auch ich bei meiner bisherigen Weckerumsetzung genutzt habe und man den gesamten notwendigen Coder an einer Stelle zentral zur Verfügung.

Inititales Modul anlegen

Beginnen wir nun zuerst damit ein Basismodul – ohne weiteren Code anzulegen. Damit ist es dann zum Beispiel auch möglich, bereits erste Attribute zu setzen, die einem die weitere Definition des Codes etwas vereinfachen. Dieses Modul könnte zum Beispiel wie folgt angelegt werden (Eingabe in der Befehlszeile der FHEM Weboberfläche):

define di_Wecker DOIF ([0])

Nun definieren wir zunächst die folgenden Attribute :

attr di_Wecker readingList W_Aktiv W_Reset W_Montag W_Dienstag W_Mittwoch W_Donnerstag W_Freitag W_Samstag W_Sonntag
attr di_Wecker setList W_Aktiv:on,off W_Reset:on W_Montag:time W_Dienstag:time W_Mittwoch:time W_Donnerstag:time W_Freitag:time W_Samstag:time W_Sonntag:time
attr di_Wecker notexist "09:00"

Die Attribute „readingList“ und „setList“ entsprechen den Definitionen eines FHEM Dummy-Moduls. Mit „readingList“ werden alle benutzer definierten Readings angelegt, die wir benötigen. Hierbei ist zu beachten, dass solche Readings immer mit einem Großbuchstaben und einem Unterstrich beginnen sollten. Nur so ist sichergestellt, dass es keine Kollision mit Readings des Moduls gibt.

Das Attribut „setList“ dient dazu, die Liste der möglichen set Kommandos zu definieren. Hierbei können auch die widgets wie „time“ oder „slider“ angegeben werden.

Wir haben nun also für jeden Wochentag ein Reading für die Weckzeit definiert (W_Montag usw.) sowie ein Reading für den Status „aktiviert“ und ein Reading zur Ermittlung, wann ein Reset ausgeführt wurde.

Zusätzlich wurde das Attribut „notexist“ mit „09:00″ gesetzt.Dieser Wert wird nun im ganzen DOIF-Modul überall dort genutzt, wo ein Default-Wert eingetragen werden muss. Normalerweise müssten wir in den Bedingen für die Weckzeit (Bsp: [[$SELF:W_Dienstag,“09:00“]) immer einen Defaultwert setzen, da bei der initialen Definition noch kein Wert für „W_Dienstag“ vorhanden ist. Hier würde es eine Fehlermeldung geben, wenn man den Default weg lässt. Mit „notexist“ kann ich das umgehen, wobei ein im Code angegebener Default-Wert immer die höhere Prio hat.

DOIF Wecker Basiscode mit Basisattributen

Code für Wecker Bedingungen und Aktionen erfassen

Nun rufen wir den Device Editor auf und geben nachfolgenden Code ein. Damit wird abgefragt, ob „W_Aktiv“ auf „on“ steht – der Wecker also aktiv ist – und ob eine der Weckzeiten eingetreten ist (alle Zeiten mit oder verknüpft und in einer zusätzlichen Klammer zusammen gefasst). Ereignisse und Zeiten müssen immer in eckigen Klammern angegeben werden. Will man eine Zeit (im Format HH:MM, HH:MM:SS, Zahl) aus einem Reading, einem Status usw. auslesen – also indirekt ermitteln – muss um das entsprechende Reading eine zusätzliche Klammer eingegeben werden. Im nachfolgenden Code wird  „$SELF“ durch den Namen des Moduls ersetzt. Man hätte statt dessen auch [[di_Wecker:W_Montag]] schreiben können. Die Zeit wird also aus dem Device „di_Wecker“ und dem Reading „W_Montag“ ermittelt.

##1 - Schaltzeiten werden geschaltet, wenn Wecker_Disable == on
([?$SELF:W_Aktiv, "off"] eq "on"
and ([[$SELF:W_Montag]|1]
or [[$SELF:W_Dienstag]|2]
or [[$SELF:W_Mittwoch]|3]
or [[$SELF:W_Donnerstag]|4]
or [[$SELF:W_Freitag]|5]
or [[$SELF:W_Samstag]|6]
or [[$SELF:W_Sonntag]|0]))
({Log3 undef, 3, "Wecker wurde über DOIF ausgeführt"})

Als Befehl habe ich für Testzwecke zunächst einen Log-Aufruf eingefügt. Solche Befehle oder auch selbst geschriebene Funktionen müssen immer mit „{}“ umschlossen werden. Im finalen Code wird hier einfach „set gr_Bewohner awoken“ in meinem Fall eingetragen. Hier müsstet ihr euren „Weckaufruf“ entsprechend eintragen.

Eine Besonderheit ist noch in der ersten Abfrage ?[$SELF:W_Aktiv, „off“] enthalten. Das Fragezeichen sorgt dafür, dass das Modul nicht getriggert wird, sondern der Wert nur gelesen wird. Nach meinem Verständnis wäre das in unserem Code nicht zwingend notwendig. Wenn ich es richtig verstanden habe, wäre es dann sinnvoll, wenn es einen DOELSE -Zweig gibt. Würde man den Schalter für „W_Aktiv“ ändern, dann wir die DOIF-Auswertung angestoßen. Die erste Bedingung wäre dann „FALSE“ und er würde in den DOELSE-Zweig springen. Wenn das nicht gewollt ist, muß mit dem Fragezeichen dafür gesorgt werden, dass das Modul nicht getriggert wird.

DOIF Wecker kompletter Code im Codemirror Editor

Die letzte Besonderheit sind die Ergänzungen „|1“ hinter der Weckzeit. Ähnlich wie beim Weekday-Timer werden damit die Wochentage angegeben (Start mit 0 für Sonntag, 7 = Wochenende und 8 = Arbeitstag). Damit wird sicher gestellt, dass die definierte Weckzeit auch wirklich nur an dem gewünschten Tag gültig ist. Ohne diese Angabe würde der Wecker an einem Tag bis zu 7-mal auslösen.

Nun kommt noch der weitere Code für die Reset-Steuerung dazu. Hier wird auf ein Ereignis reagiert. Dies erkennt man an den Anführungszeichen innerhalb der eckigen Klammern. Sobald das Ereignis „on“ für das Reading „di_Wecker:W_Reset“ eintrifft werden die set-Kommandos ausgeführt.

##2 - Resetfunktion setzt Uhrzeiten auf Defaultwerte
DOELSEIF (["$SELF:W_Reset: on"])
( set $SELF W_Montag 07:20;
set $SELF W_Dienstag 06:40;
set $SELF W_Mittwoch 07:20;
set $SELF W_Donnerstag 07:20;
set $SELF W_Freitag 06:40;
set $SELF W_Samstag 09:00;
set $SELF W_Sonntag 09:00)

Zum Schluß noch do always und selftrigger einbauen

Der eigentliche Code ist nun vollständig. Zwei Dinge fehlen jedoch noch. Ein DOIF sollte möglichst immer so definiert werden, dass mindestens zwei Zustände bzw. Aktionen ausgeführt werden. Also z.B. „Wenn es dunkel ist Lampe ein sonst Lampe aus“. Dies geht im Falle des Weckers jedoch nicht. Nun haben wir aber das Problem, dass DOIF ein Kommando nur einmal ausführt. Wenn also einmal eine Weckzeit ausgeführt wurde und damit Kommando 1 ausgeführt wurde, wird dieses Kommando nicht noch einmal ausgeführt. Der Wecker würde nur einmal funktionieren – oder man führt ein Reset aus.

Hier hilft das Attribut „do“ mit dem Wert „always“. Dies sorgt dafür, dass der DOIF-Code immer wieder neu ausgeführt wird. Also setzen wir noch das Attribut:

attr di_Wecker do always

Ein weiteres Problem tritt auf, wenn wir die Resetfunktion ausführen. Im DOIF ist zwar die Änderung des entsprechenden Readings erkennbar und auch die Weckzeiten Readings ändern sich. Jedoch werden keine neuen Timer ermittelt. Dies liegt daran, dass das DOIF-Modul mögliche Endlosschleifen verhindern will und sich nicht selbst triggert. Damit die Timer also auch geändert werden, muß noch das Attribut selftrigger gesetzt werden. Für unser Modul können wir es auf „all“ setzen.

attr di_Wecker selftrigger all

Damit ist die Definition nun komplett und wir können uns der Steuerung über das TabletUI zuwenden.

DOIF Welcker über TabletUI steuern

Für die Definition der Wecker-Steuerung innerhalb des TabletUI muß ich wahrscheinlich nicht mehr soviel erzählen. Hier habe ich eine Tabelle genutzt, um die jeweiligen Zeiten darzustellen. Zur Auswahl der Uhrzeiten verwende ich das widget „datetimepicker“. Den Reset-Knopf habe ich mittels „push“ umgesetzt und für Aktivierungsschalter habe ich einen Switch mit Toggle-Icons genutzt.

Weckersteuerung mittels FTUI und Datetimpicker

Der entsprechende Code sieht dann wie folgt aus:

<li data-row="1" data-col="5" data-sizex="2" data-sizey="2">
<header>Weckzeiten</header>
<table class="large left-align left-space">
   <tr><td>Montag:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Montag"
    data-set="W_Montag"
    data-format="H:i"
    data-step="10"
    data-datepicker="false" >
   </div></td></tr>
   <tr><td>Dienstag:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Dienstag"
    data-set="W_Dienstag"
    data-format="H:i"
    data-step="10"
    data-datepicker="false">
   </div></td></tr>
   <tr><td>Mittwoch:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Mittwoch"
    data-set="W_Mittwoch"
    data-format="H:i"
    data-step="10"
    data-datepicker="false">
   </div></td></tr>
   <tr><td>Donnerstag:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Donnerstag"
    data-set="W_Donnerstag"
    data-format="H:i"
    data-step="10"
    data-datepicker="false">
   </div></td></tr>
   <tr><td>Freitag:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Freitag"
    data-set="W_Freitag"
    data-format="H:i"
    data-step="10"
    data-datepicker="false">
   </div></td></tr>
   <tr><td>Samstag:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Samstag"
    data-set="W_Samstag"
    data-format="H:i"
    data-step="10"
    data-datepicker="false">
   </div></td></tr>
   <tr><td>Sonntag:</td>
   <td><div
    data-type="datetimepicker"
    data-device="di_Wecker"
    data-get="W_Sonntag"
    data-set="W_Sonntag"
    data-format="H:i"
    data-step="10"
    data-datepicker="false">
   </div></td></tr>
 </table>
 
 <div data-type="label" class="inline">Reset</div>
 <div data-type="push" 
    data-device="di_Wecker" 
    data-set="W_Reset"
    data-set-on="on" 
    class="inline small"></div>
 
 <div data-type="label" class="inline">Aktiv</div>
 <div data-type="switch" 
    data-device="di_Wecker" 
    data-set="W_Aktiv"
    data-get="W_Aktiv"
    data-states='["on","off"]'
    data-icons='["fa-toggle-on","fa-toggle-off"]'
    data-colors='["green","red"]'
    data-background-icons='["fa-blank","fa-blank"]'
    data-background-colors='["#2A2A2A","#2A2A2A"]'
    data-on-background-color="grey"
    data-off-background-color="grey"
    data-set-on="on"
    data-set-off="off"
    data-get-on="on"
    data-get-off="off"
    class="inline"></div>
</li>

Nun ist die Beschreibung doch mal deutlich umfangreicher geworden. Trotzdem habe ich euch hoffentlich damit die Verwendung von DOIF ein wenig näher gebracht und Anregungen für eigene Umsetzungen gegeben. Sobald meine Rolladensteuerung mit dem DOIF-Modul funktioniert, gibt es natürlich auch wieder eine entsprechende Erläuterung.

3 comments

  1. Hallo und vielen Dank für die tolle Anleitung. Habe nach deinen Vorgaben alles nachgemacht und wie oft beim nachmachen nicht alles verstanden.
    Wie kann ich jetzt eine Funktion auslösen wenn die Weckzeit erreicht ist? Genau betrachtet möchte ich etwas schalten wenn der Wecker Aktiv ist und die Weckzeit erreicht ist. Mit dem Deaktivieren des Weckers soll auch die Funktion beendet sein.

    Danke und Lg

  2. Das Auslösen einer Funktion bei erreichen der Weckzeit passiert an der Stelle, wo in der Beschreibung im Code die Funktion „Log“ aktuell eingetragen ist. Diese muss man austauschen gegen die gewünschte Aktion (z.B. „set Licht an“ oder „set Rollade hoch“. Wenn man hier ein Funktion aufrufen möchte, die man selbst geschrieben hat, dann ruft man diese so auf: „{meineFunktion()}“ (ohne Anführungszeichen 😉 ).

    Das Deaktivieren des Weckers ist eine manuelle Funktion, die z.B. mittels Button oder einem Schalter ausgelöst werden kann. Dies dient eigentlich dazu, den Wecker außer Betrieb zu nehmen – also keine Weckzeiten auszulösen. Man könnte natürlich mit einem notify oder auch wieder einem neu zu definierenden DOIF auf das Deaktivieren reagieren. Wenn du aber eine Funktion benötigst wie z.B. „bei Weckzeit Rollade hoch und bei weiterer Zeit Rollade runter“ dann musst du im Prinzip nochmals entsprechende Ende_Zeiten definieren. Diese könntes du dann wieder genauso setzen.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Why ask?

Allnet Flat 728x090
x

Check Also

FHEM Tablet UI – Testumgebung als Schutz vor umfangreichen Updates

Aktuell bin ich dabei, meine Tablet UI Umgebung zu optimieren und die Dateien für eine ...

Durch die weitere Nutzung der Seite stimmen Sie der Verwendung von Cookies zu. Mehr Infos

Die Cookie-Einstellungen auf dieser Website sind auf "Cookies zulassen" eingestellt, um Ihnen das beste Surferlebnis zu ermöglichen. Wenn Sie diese Website ohne Änderung Ihrer Cookie-Einstellungen verwenden oder auf "Akzeptieren" klicken, erklären Sie sich damit einverstanden.

Schließen