diff --git a/fhem/FHEM/98_DOIF.pm b/fhem/FHEM/98_DOIF.pm index 08bff6137..da160ebdc 100644 --- a/fhem/FHEM/98_DOIF.pm +++ b/fhem/FHEM/98_DOIF.pm @@ -2821,13 +2821,12 @@ DOIF_SleepTrigger ($) sub DOIF_set_Exec { my ($hash,$timername,$seconds,$subname,$param)=@_; - $param="" if (!defined $param); my $current = gettimeofday(); my $next_time = $current+$seconds; $hash->{ptimer}{$timername}{time}=$next_time; $hash->{ptimer}{$timername}{name}=$timername; $hash->{ptimer}{$timername}{subname}=$subname; - $hash->{ptimer}{$timername}{param}=$param; + $hash->{ptimer}{$timername}{param}=$param if (defined $param); $hash->{ptimer}{$timername}{hash}=$hash; RemoveInternalTimer(\$hash->{ptimer}{$timername}); if ($seconds > 0) { @@ -2869,8 +2868,12 @@ sub DOIF_ExecTimer my $timername=${$timer}->{name}; my $name=$hash->{NAME}; my $subname=${$timer}->{subname}; - my $param=${$timer}->{param}; - eval ("$subname(\"$param\")"); + my $param=${$timer}->{param} if (defined ${$timer}->{param}); + if (!defined ($param)) { + eval ("$subname"); + } else { + eval ('$subname("$param")'); + } if ($@) { Log3 ($defs{$name}{NAME},1 , "$name error in $subname: $@"); readingsSingleUpdate ($hash, "error", "in $subname: $@",0); @@ -3453,15 +3456,16 @@ DOIF (ausgeprochen: du if, übersetzt: tue wenn) ist ein universelles Modul mit Mit diesem Modul ist es möglich, einfache wie auch komplexere Automatisierungsvorgänge zu definieren oder in Perl zu programmieren. Ereignisse, Zeittrigger, Readings oder Status werden durch DOIF-spezifische Angaben in eckigen Klammern angegeben. Sie führen zur Triggerung des Moduls und damit zur Auswertung und Ausführung der definierten Anweisungen.

-Das Modul verfügt über zwei Modi: FHEM-Modus und [NEU] Perl-Modus. Der Modus eines definierten DOIF-Devices wird automatisch aufgrund der Definition vom Modul erkannt +Das Modul verfügt über zwei Modi: FHEM-Modus und Perl-Modus. Der Modus eines definierten DOIF-Devices wird automatisch aufgrund der Definition vom Modul erkannt (FHEM-Modus beginnt mit einer runden Klammer auf). +Der Perl-Modus kommt weitgehend ohne Attribute aus, er ist aufgrund seiner Flexibilität, +der Möglichkeit strukturiert zu programmieren und seiner hohen Performance insb. bei umfangreichen Automatisierungsaufgaben dem FHEM-Modus vorzuziehen. Hier geht´s zum Perl-Modus. Beide Modi sind innerhalb eines DOIF-Devices nicht miteinander kombinierbar. Im Folgendem wird der FHEM-Modus beschrieben.

Syntax FHEM-Modus:

    define <name> DOIF (<Bedingung>) (<Befehle>) DOELSEIF (<Bedingung>) (<Befehle>) DOELSEIF ... DOELSE (<Befehle>)

-Im FHEM-Modus lassen sich Automatisierungsabläufe ohne Perlkenntnisse definieren. Die Angaben werden immer von links nach rechts abgearbeitet. Logische Abfragen werden in DOIF/DOELSEIF-Bedingungen vornehmlich mit Hilfe von and/or-Operatoren erstellt. Zu beachten ist, dass nur die Bedingungen überprüft werden, die zum ausgelösten Event das dazughörige Device bzw. die dazugehörige Triggerzeit beinhalten. @@ -5539,35 +5543,79 @@ Hier passiert das nicht mehr, da die ursprünglichen Zustände cmd_1 und cmd_2 j Perl Modus

-Im Perl-Modus lassen sich insb. komplexere Abläufe innerhalb eines DOIF-Devices in Perl programmieren. -Der Anwender hat mehr Einfluss auf den Ablauf der Steuerung als im FHEM-Modus. Einfache Perlkenntnise werden in diesem Modus vorausgesetzt.
-
-Die Steuerungsabläufe werden im Gegensatz zum FHEM-Modus nicht durch Attibute beinflusst, daher werden im Perl-Modus selten Attribute benötigt. -Die Auswahl der DOIF-spezifischen Attribute im Perl-Modus ist auf einige sinnvolle beschränkt.
-
-Der Status des Moduls wird nicht vom Modul gesetzt, er kann vom Anwender mit Hilfe der Funktion set_Reading verändert werden, siehe spezifische Perl-Funktionen im Perl-Modus. -DOIF-spezifische Operanden-Angaben in eckigen Klammern entsprechen vollständig den Angaben im FHEM-Modus des Moduls. Sie können an beliebiger Stelle im Perlcode angegeben werden, wo auch Perl-Funktionen angegeben werden können.
+Der Perl-Modus ist sowohl für einfache, als auch für komplexere Automatisierungsabläufe geeignet. Der Anwender hat mehr Einfluss auf den Ablauf der Steuerung als im FHEM-Modus. +Die Abläufe lassen sich, wie in höheren Programmiersprachen üblich, strukturiert programmieren. Zum Zeitpunkt der Definition werden alle DOIF-spezifischen Angaben in Perl übersetzt, zum Zeitpunkt der Ausführung wird nur noch Perl ausgeführt, damit wird maximale Performance gewährleistet.

Syntax Perl-Modus:

-
    define <name> DOIF <Blockname> {<Perl mit DOIF-Syntax in eckigen Klammern>} <Blockname> {<Perl mit DOIF-Syntax in eckigen Klammern>} ...

+
    define <name> DOIF <Blockname> {<Perlcode mit Ereignis-/Zeittriggern in eckigen Klammern>}

-Ein Perlblock wird ausgeführt, wenn dieser, bedingt durch DOIF-spezifischen Angaben in eckigen Klammern innerhalb des Blocks, getriggert wird. -Es wird die vollständige Perl-Syntax unterstützt. Es können beliebig viele Perlblöcke definiert werden. Der Name eines Blocks ist optional. Wird ein Perlblock mit dem Namen "init" benannt, so wird er ausgeführt, nachdem das FHEM-System hochgefahren wurde. Er bietet sich insb. an, um Instatnzvariablen des Moduls vorzubelegen.
-Ein besonderer Perlblock ist der Block namens "subs". Dieser Block wird nur zum Definitionszeitpunkt ausgeführt. In diesem Block sollten vornehmlich Perlfunktionen definiert werden, die innerhalb des DOIFs genutzt werden. Um eine möglichst hohe Kompatibilität zu Perl sicherzustellen, wird keine DOIF-Syntax in eckigen Klammern unterstützt, insb. gibt es keine Trigger, die den Block ausführen können.
+Ein Perlblock wird ausgeführt, wenn dieser bedingt durch Ereignis- und Zeittrigger in eckigen Klammern innerhalb des Blocks, getriggert wird. +Es wird die vollständige Perl-Syntax unterstützt. Es können beliebig viele Perlblöcke innerhalb eines DOIF-Devices definiert werden. Sie werden unabhängig voneinander durch passende Trigger ausgeführt. Der Name eines Blocks ist optional.

-FHEM-Befehle werden durch den Aufruf der Perlfunktion fhem"..." ausgeführt. Im Gegensatz zum FHEM-Modus können im Perl-Modus mehrere Blöcke unabhängig voneinander, ausgelöst durch einen Ereignis- oder Zeit-Trigger, ausgeführt werden. So kann die Funktionalität mehrer DOIF-Module im FHEM-Modus innerhalb eines DOIF-Moduls im Perl-Moduls realisiert werden.
+Der Status des Moduls wird nicht vom Modul gesetzt, er kann vom Anwender mit Hilfe der Funktion set_Reading verändert werden, siehe spezifische Perl-Funktionen im Perl-Modus. +FHEM-Befehle werden durch den Aufruf der Perlfunktion fhem"..." ausgeführt.

-Im Perl-Modus gibt es keinen wait-Timer, stattdessen kann der Benutzer mit der Funktion set_Timer beliebig viele eigene Timer definieren, die unabhängig voneinander gesetzt und ausgewertet werden können, siehe Spezifische Perl-Funktionen im Perl-Modus.
+Der Benutzer kann mit der Funktion set_Timer/set_Exec beliebig viele eigene Timer definieren, die unabhängig voneinander gesetzt und ausgewertet werden können, siehe Spezifische Perl-Funktionen im Perl-Modus.

-Zum Zeitpunkt der Definition werden alle DOIF-spezifischen Angaben in Perl übersetzt, zum Zeitpunkt der Ausführung wird nur noch Perl ausgeführt, damit wird maximale Performance gewährleistet.
+Definitionen im FHEM-Modus der Form:

+DOIF (<Bedingung mit Trigger>) (<FHEM-Befehle>) DOELSE (<FHEM-Befehle>)
+
+lassen sich wie folgt in Perl-Modus übertragen:
+
+DOIF {if (<Bedingung mit Trigger>) {fhem"<FHEM-Befehle>"} else {fhem"<FHEM-Befehle>"}}
+
+Die Bedingungen des FHEM-Modus können ohne Änderungen in Perl-Modus übernommen werden können.
+
+Im Perl-Modus können beliebig viele Blöcke definiert werden, die unabhängig von einander durch einen Trigger ausgewertet und zur Ausführung führen können:
+
+DOIF
+{ if (<Bedingung mit Trigger>) ... }
+{ if (<Bedingung mit Trigger>) ... }
+...

+
+Im Perlmodus sind beliebige Hierarchietiefen möglich:
+
+DOIF
+{ if (<Bedingung>) {
+    if (<Bedingung>) {
+      if (...
+        ...
+      }
+    }
+  }
+}

+
+Bemerkung: Innerhalb eines DOIF-Blocks muss mindestens ein Trigger in irgendeiner Bedingung definiert werden, damit der gesamte Block beim passenden Trigger ausgewertet wird.
+
+Eigene Funktionen
+
+Ein besonderer Perlblock ist der Block namens "subs". In diesem Block werden Perlfunktionen definiert werden, die innerhalb des DOIFs genutzt werden. +Um eine möglichst hohe Kompatibilität zu Perl sicherzustellen, wird keine DOIF-Syntax in eckigen Klammern unterstützt, insb. gibt es keine Trigger, die den Block ausführen können.
+
+Beispiel:
+
+DOIF +subs { ## Definition von Perlfunktionen lamp_on und lamp_off
+  sub lamp_on {
+     fhem"set lamp on";
+     set_Reading("state","on",1);
+  }
+  sub lamp_off {
+     fhem"set lamp off";
+     set_Reading("state","off",1);
+  }
+}
+{if ([06:00]) {lamp_on()  # Um 06:00 Uhr wird die Funktion lamp_on aufgerufen }
+{if ([08:00]) {lamp_off() # Um 08:00 Uhr wird die Funktion lamp_off aufgerufen }
+

Einfache Anwendungsbeispiele (vgl. Anwendungsbeispiele im FHEM-Modus):

    define di_rc_tv DOIF {if ([remotecontol:"on"]) {fhem"set tv on"} else {fhem"set tv off"}}

    -define di_clock_radio DOIF {if ([06:30|Mo Di Mi] or [08:30|Do Fr Sa So]) {fhem"set radio on"} elsif ([08:00|Mo Di Mi] or [09:30|Do Fr Sa So]) {fhem"set radio off"}}
    +define di_clock_radio DOIF {if ([06:30|Mo Di Mi] or [08:30|Do Fr Sa So]) {fhem"set radio on"}} {if ([08:00|Mo Di Mi] or [09:30|Do Fr Sa So]) {fhem"set radio off"}}

    define di_lamp DOIF {if ([06:00-09:00] and [sensor:brightness] < 40) {fhem"set lamp:FILTER=STATE!=on on"} else {fhem"set lamp:FILTER=STATE!=off off"}}

    @@ -5587,33 +5635,50 @@ Timer holen: get_Timer(<TimerEvent>), Returnwert: 0, w
    Laufenden Timer löschen: del_Timer(<TimerEvent>)

    -Funktionstimer
    +Beispiel: Das Event "hello" 30 Sekunden verzögert auslösen:

    -Timer setzen: set_Exec(<timerName>, <seconds>, <function>, <parameter>), mit <timerName>: beliebige Angabe, sie spezifiziert eindeutig einen Timer, -welcher nach Ablauf die angegebene Perl-Funktion <function> mit optionalen Parameter <parameter> aufruft. Die Perlfunkion muss eindeutig sein und in FHEM zuvor deklariert worden sein. +set_Time("event",30,"hello");
    +
    +Ausführungstimer
    +
    +Timer setzen: set_Exec(<timerName>, <seconds>, <perlCode>, <parameter>), mit <timerName>: beliebige Angabe, sie spezifiziert eindeutig einen Timer, +welcher nach Ablauf den angegebenen Perlcode <perlCode> aufruft. Falls als Perlcode eine Perlfunktion angegeben wird, kann optional ein Übergabeparameter <parameter> angegeben werden. Die Perlfunkion muss eindeutig sein und in FHEM zuvor deklariert worden sein. Wird set_Exec mit dem gleichen <timerName> vor seinem Ablauf erneut aufgerufen, so wird der laufender Timer gelöscht und neugesetzt.

    Timer holen: get_Exec(<timerName>), Returnwert: 0, wenn Timer abgelaufen oder nicht gesetzt ist, sonst Anzahl der Sekunden bis zum Ablauf des Timers

    Laufenden Timer löschen: del_Exec(<timerName>)

    +Beispiel: Lampe verzögert um 30 Sekunden ausschalten:
    +
    +set_Exec("aus",30,'fhem"set lamp off"');

    Ein beliebiges FHEM-Event absetzen: set_Event(<Event>)

    Reading schreiben: set_Reading(<readingName>,<content>,<trigger>), mit <trigger>: 0 ohne Trigger, 1 mit Trigger

    -Es können alle in FHEM vorhanden Funktionen genutzt werden. Größere Perlblöcke sollten in eigene Funktionen im subs-Block ausgelagert werden. -Der Anwender hat die Möglichkeit Instanzvariablen beginnen mit $_ zu nutzen. Sie müssen nicht deklariert werden. Deren Gültigkeitsbereich ist ein definiertes DOIF-Device. Wenn sie nicht vorbelegt werden, gelten sie als nicht definiert. Das lässt sich abfragen mit:
    +init-Block
    +
    +Wird ein Perlblock mit dem Namen "init" benannt, so wird er ausgeführt, nachdem das FHEM-System hochgefahren wurde. Er bietet sich insb. an, um Instanzvariablen des Moduls vorzubelegen.
    +
    +Instanzvariablen
    +
    +Instanzvariablen sind Devicevariablen, die global innerhalb eines DOIF-Devices genutzt werden können. Sie beginnen mit $_ und müssen nicht deklariert werden. Wenn sie nicht vorbelegt werden, gelten sie als nicht definiert. Das lässt sich abfragen mit:
    +
    if (defined $_...) ...

    -Instanzvariablen überleben nicht den Neustart, sie können jedoch im init-Block aus Readings vorbelegt werden.
    +Instanzvariablen überleben nicht den Neustart, sie können jedoch z.B. im init-Block, der beim Systemstart ausgewertet wird, aus Readings vorbelegt werden.

    Bsp. Vorbelgung einer Instanzvariablen beim Systemstart mit dem Status des Moduls:
    +
    init {$_status=ReadingsVal("$SELF","state",0)}
    +
    alternativ
    +
    init {$_status=[?$SELF:state]}

    Instanzvariablen lassen sich indizieren, z. B.:
    +
    my $i=0;
    $_betrag{$i}=100;


    @@ -5653,23 +5718,29 @@ Für unterschiedliche blockierende Funktionen ist jeweils ein eigener Name (<
    Weitere Anwendungsbeispiele:

    + +Treppenhauslicht mit Bewegungsmelder
    +
    +define di_light DOIF {
    +  if (["FS:motion"]) {                        # bei Bewegung
    +    fhem"set lamp on" if ([?lamp] ne "on");   # Lampe einschalten, wenn sie nicht an ist
    +    set_Exec("off",30,'fhem"set lamp off"');  # Timer namens "off" für das Ausschalten der Lampe auf 30 Sekunden setzen bzw. verlängern
    +  }
    +}
    +
    +
    Einknopf-Fernbedienung

    Anforderung: Wenn eine Taste innerhalb von zwei Sekunden zwei mal betätig wird, soll der Rollladen nach oben, bei einem Tastendruck nach unten.

    -define di_shutter DOIF {                              # Block zur Auswertung des Tastendruckes
    -  if (["FS:^on$"] and get_Timer("Timer_shutter")==0){ # wenn Taste betätigt wird und kein Timer läuft
    -    set_Timer("Timer_shutter",2);                     # Timer für zwei Sekunden setzen
    -  } else {                                            # wenn Timer läuft, d.h. ein weitere Tastendruck innerhalb von zwei Sekunden
    -    del_Timer("Timer_shutter")                        # Timer löschen
    -    fhem"set shutter up";                             # Rollladen hoch
    -  }
    -}
    -{                                                     # Block für die Bearbeitung des Timerevents
    -  if ([$SELF:"Timer_shutter"]){                       # wenn nach zwei Sekunden Timer abläuft, d.h. nur ein Tastendruck
    -    fhem"set shutter down";                           # Rollladen runter
    +define di_shutter DOIF {
    +  if (["FS:^on$"] and !get_Exec("shutter")){          # wenn Taste betätigt wird und kein Timer läuft
    +    set_Exec("shutter",2,'fhem"set shutter down"');   # Timer zum shutter down auf zwei Sekunden setzen
    +  } else {                                            # wenn Timer läuft, d.h. ein weitere Tastendruck innerhalb von zwei Sekunden
    +    del_Timer("shutter");                             # Timer löschen
    +    fhem"set shutter up";                             # Rollladen hoch
      }
    }
    @@ -5679,43 +5750,12 @@ define di_shutter DOIF {         &n Im folgenden Beispiel wird die Nutzung von Instanzvariablen demonstriert.

    -define di_count DOIF {                              # Block zur Auswertung des Ereignisses
    -  if (["FS:on"] and get_Timer("Timer_counter")==0){ # wenn Ereignis (hier "FS:on") eintritt und kein Timer läuft
    -    $_count=1;                                      # setze count-Variable auf 1
    -    set_Timer("Timer_counter",3600);                # setze Timer auf eine Stunde
    -  } else {
    -    $_count++;                                      # wenn Timer bereits läuft zähle Ereignis
    -  }
    -}
    -{                                                   # Block für die Auswertung nach Ablauf des Timers
    -  if ([$SELF:"Timer_counter"]) {                    # wenn Timer nach einer Stunde abläuft
    -    if ($_count > 10) {
    -      Log 3,"count: $_count action";                # protokolliere im Log die Anzahl der Ereignisse, wenn sie über 10 ist
    -    }
    -  }
    -}
    -

    -
    - -Treppenhauslicht mit Bewegungsmelder
    -

    -define di_light DOIF
    -subs {                                             # Block "subs" zur Definition eigener Perl-Funktionen, hier ist nur Perl erlaubt ohne DOIF-Syntax
    -  sub ein {                                        # Perlfunktion "ein" zum Einschalten wird definiert
    -    if (ReadingsVal ("lamp","state","") ne "on") {
    -      fhem"set lamp on";
    -      set_Reading ("state","on",1);
    -    }
    -  }
    -  sub aus {                                        # Perlfunkton "aus" zum Ausschalten wird definiert
    -    fhem"set lamp off";
    -    set_Reading ("state","off",1);
    -  }
    -}
    -bewegung {                                         # Block namens "bewegung" reagiert auf Bewegung von FS
    -  if (["FS:motion"]) {
    -    ein();                                         # Perlfunktion "ein" wird ausgeführt
    -    set_Exec("light_off",10,"aus");                # Timer namens "light_off"für das Ausschalten über Perlfunktion "aus" wird gesetzt bzw. verlängert
    +define di_count DOIF {
    +  if (["FS:on"] and !get_Exec("counter")) {                                        # wenn Ereignis (hier "FS:on") eintritt und kein Timer läuft
    +    $_count=1;                                                                     # setze count-Variable auf 1
    +    set_Exec("counter",3600,'Log (3,"count: $_count action") if ($_count > 10)');  # setze Timer auf eine Stunde zum Protokollieren der Anzahl der Ereignisse, wenn sie über 10 ist
    +  } else {
    +    $_count++;                                                                     # wenn Timer bereits läuft zähle Ereignis
      }
    }