From a01e9133d1c4a94e9ab3ba5ee24bf764dd6b549f Mon Sep 17 00:00:00 2001 From: damian-s <> Date: Sat, 7 Mar 2015 17:10:02 +0000 Subject: [PATCH] =?UTF-8?q?98=5FDOIF.pm:=20Indirekte=20Zeitangaben,=20Unte?= =?UTF-8?q?rst=C3=BCtzung=20von=20readingFnAttributes,=20$Id-String=20eing?= =?UTF-8?q?er=C3=BCckt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.fhem.de/fhem/trunk@8166 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/98_DOIF.pm | 154 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 122 insertions(+), 32 deletions(-) diff --git a/fhem/FHEM/98_DOIF.pm b/fhem/FHEM/98_DOIF.pm index 121645127..6ba9f9e2f 100644 --- a/fhem/FHEM/98_DOIF.pm +++ b/fhem/FHEM/98_DOIF.pm @@ -1,20 +1,19 @@ ############################################## -# $Id$ -# -# This file is part of fhem. -# -# Fhem is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# Fhem is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with fhem. If not, see . +# $Id$ +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . # ############################################### @@ -35,7 +34,7 @@ DOIF_Initialize($) $hash->{UndefFn} = "DOIF_Undef"; $hash->{AttrFn} = "DOIF_Attr"; $hash->{NotifyFn} = "DOIF_Notify"; - $hash->{AttrList} = "disable:0,1 loglevel:0,1,2,3,4,5,6 wait do:always,resetwait cmdState state initialize repeatsame waitsame waitdel cmdpause"; + $hash->{AttrList} = "disable:0,1 loglevel:0,1,2,3,4,5,6 wait do:always,resetwait cmdState state initialize repeatsame waitsame waitdel cmdpause ".$readingFnAttributes; } @@ -276,6 +275,8 @@ sub ReplaceAllReadingsDoIf($$$$) $event=1; } elsif ($condition == -2) { $hash->{state}{device} = AddItemDoIf($hash->{state}{device},$device) if ($device ne $hash->{NAME}); + } elsif ($condition == -3) { + $hash->{itimer}{all} = AddItemDoIf($hash->{itimer}{all},$device); } } } else { @@ -374,8 +375,9 @@ DOIF_CheckTimers($$$$$) my $pos; my $time; my $block; + my $result; my ($hash,$timer,$condition,$trigger,$timerarray)=@_; - if ($timer =~ /^(\+)?((\{.*\})|([0-9][0-9](:[0-5][0-9]){1,2}))(\|[0-8]+$|-(\+)?([0-9][0-9](:[0-5][0-9]){1,2})|-(\+)?({.*})|$)(\|[0-8]+$|$)/) { + if ($timer =~ /^(\+)?((\{.*\})|(\[.*\])|([0-9][0-9](:[0-5][0-9]){1,2}))(\|[0-8]+$|-(\+)?(([0-9][0-9](:[0-5][0-9]){1,2})|({.*})|(\[.*\]))|$)(\|[0-8]+$|$)/) { while ($timer ne "") { if ($timer=~ /^\s*\{/) { ($beginning,$time,$err,$timer)=GetBlockDoIf($timer,'[\{\}]'); @@ -386,6 +388,17 @@ DOIF_CheckTimers($$$$$) $days=substr($timer,$pos); $timer=""; } + } elsif ($timer=~ /^\s*\[/) { + ($beginning,$time,$err,$timer)=GetBlockDoIf($timer,'[\[\]]'); + return ($time,$err) if ($err); + $time="[".$time."]"; + ($result,$err)=ReplaceAllReadingsDoIf($hash,$time,-3,0); + return ($time,$err) if ($err); + if ($timer =~ /^\s*\|/g) { + $pos=pos($timer); + $days=substr($timer,$pos); + $timer=""; + } } elsif ($timer =~ /-/g) { $pos=pos($timer)-1; $time=substr($timer,0,$pos); @@ -408,8 +421,10 @@ DOIF_CheckTimers($$$$$) $hash->{timeCond}{$nr}=$condition; $hash->{days}{$nr}=$days if ($days ne ""); ${$timerarray}[$nr]={hash=>$hash,nr=>$nr}; - $err=(DOIF_SetTimer("DOIF_TimerTrigger",\${$timerarray}[$nr])); - return($hash->{time}{$nr},$err) if ($err); + if ($init_done) { + $err=(DOIF_SetTimer("DOIF_TimerTrigger",\${$timerarray}[$nr])); + return($hash->{time}{$nr},$err) if ($err); + } $hash->{timers}{$condition}.=" $nr " if ($trigger); $hash->{timerfunc}{$nr}=\${$timerarray}[$nr]; } @@ -420,6 +435,7 @@ DOIF_CheckTimers($$$$$) } return ($block,""); } + delete ($hash->{helper}{modify}); return("",""); } @@ -673,25 +689,47 @@ DOIF_Notify($$) my ($hash, $dev) = @_; my $pn = $hash->{NAME}; return "" if($attr{$pn} && $attr{$pn}{disable}); - return "" if (!$hash->{devices}{all} and !$hash->{state}{device}) ; return "" if (!$dev->{NAME}); my $device; my $reading; my $internal; my $ret; + my $err; + + if ($dev->{NAME} eq "global" and ((EventDoIf("global","global",deviceEvents($dev, AttrVal("global", "addStateEvent", 0)),"INITIALIZED")) or EventDoIf("global","global",deviceEvents($dev, AttrVal("global", "addStateEvent", 0)),"REREADCFG"))) + { + if ($hash->{helper}{last_timer} > 0){ + for (my $j=0; $j<$hash->{helper}{last_timer};$j++) + { + DOIF_SetTimer("DOIF_TimerTrigger",$hash->{timerfunc}{$j}); + } + } + return undef; + } + + if (($hash->{itimer}{all}) and $hash->{itimer}{all} =~ / $dev->{NAME} /) { + for (my $j=0; $j<$hash->{helper}{last_timer};$j++) + { + if ($hash->{time}{$j} =~ /\[$dev->{NAME}\]|\[$dev->{NAME}:/) { + DOIF_SetTimer("DOIF_TimerTrigger",$hash->{timerfunc}{$j}); + } + } + } + + return "" if (!$hash->{devices}{all} and !$hash->{state}{device}) ; if (($hash->{devices}{all}) and $hash->{devices}{all} =~ / $dev->{NAME} /) { if ($hash->{readings}{all}) { foreach my $item (split(/ /,$hash->{readings}{all})) { ($device,$reading)=(split(":",$item)); - readingsSingleUpdate ($hash, "e_".$dev->{NAME}."_".$reading,$defs{$device}{READINGS}{$reading}{VAL},0) if ($item and $device eq $dev->{NAME} and $defs{$device}{READINGS}{$reading}); + readingsSingleUpdate ($hash, "e_".$dev->{NAME}."_".$reading,$defs{$device}{READINGS}{$reading}{VAL},0) if ($item and $device eq $dev->{NAME} and defined ($defs{$device}{READINGS}{$reading})); } } if ($hash->{internals}{all}) { foreach my $item (split(/ /,$hash->{internals}{all})) { ($device,$internal)=(split(":",$item)); - readingsSingleUpdate ($hash, "e_".$dev->{NAME}."_".$internal,$defs{$device}{$internal},0) if ($item and $device eq $dev->{NAME} and $defs{$device}{$internal}); + readingsSingleUpdate ($hash, "e_".$dev->{NAME}."_".$internal,$defs{$device}{$internal},0) if ($item and $device eq $dev->{NAME} and defined ($defs{$device}{$internal})); } } #my ($seconds, $microseconds) = gettimeofday(); @@ -711,7 +749,7 @@ DOIF_Notify($$) } if (($hash->{state}{device}) and $hash->{state}{device} =~ / $dev->{NAME} / and !$ret) { DOIF_SetState($hash,"","",""); - } + } return undef; } @@ -745,7 +783,25 @@ DOIF_SetTimer($$) $timeStr=substr($timeStr,1); $rel=1; } + if ($timeStr=~ /^\s*\[/) { + my $err; + ($timeStr,$err)=ReplaceAllReadingsDoIf($hash,$timeStr,-3,1); + if ($err) + { + readingsSingleUpdate ($hash,"timer_".($nr+1)."_c".($cond+1),"error: ".$err,0); + RemoveInternalTimer($timer); + $hash->{realtime}{$nr}="00:00:00"; + return $err; + } + } my ($err, $h, $m, $s, $fn) = GetTimeSpec($timeStr); + if ($err) + { + readingsSingleUpdate ($hash,"timer_".($nr+1)."_c".($cond+1),"error: ".$err,0); + RemoveInternalTimer($timer); + $hash->{realtime}{$nr}="00:00:00"; + return $err; + } return $err if($err); my $second = $h*3600+$m*60+$s; #my $now = time(); @@ -859,7 +915,6 @@ CmdDoIf($$) } else { $tail =~ s/\n//g; } - if (defined $hash->{helper}) #def modify { RemoveInternalTimer($hash); @@ -875,6 +930,7 @@ CmdDoIf($$) delete ($hash->{time}); delete ($hash->{timer}); delete ($hash->{timers}); + delete ($hash->{itimer}); delete ($hash->{timeCond}); delete ($hash->{realtime}); delete ($hash->{days}); @@ -1054,7 +1110,7 @@ The commands are always processed from left to right. There is only one command + it can be any FHEM commands and perl commands are executed
+ syntax checking at the time of definition are identified missing brackets
+ status is specified with [<devicename>], readings with [<devicename>:<readingname>] or internals with [<devicename>:&<internal>]
-+ time information on the condition: [HH:MM:SS] or [HH:MM] or [{<perl-function>}]
++ time information on the condition: [HH:MM:SS] or [HH:MM] or [[<devicename>]] or [[<devicename>:<readingname>]] or [{<perl-function>}]
+ time intervals: [<begin>-<end>] for <begin> and <end>, the above time format can be selected.
+ relative times preceded by a plus sign [+<time>] or [+<begin>-+<end>] combined with Perl functions
+ weekday control: [<time>|012345678] or [<begin>-<end>|012345678] (0-6 corresponds to Sunday through Saturday) such as 7 for $we and 8 for !$we
@@ -1097,7 +1153,7 @@ Die Angaben werden immer von links nach rechts abgearbeitet. Es wird immer nur e + Bedingungen werden vom Perl-Interpreter ausgewertet, daher beliebige logische Abfragen möglich
+ Die Perl-Syntax wird um Angaben von Stati, Readings, Internals, Events oder Zeitangaben in eckigen Klammern erweitert, diese führen zur Triggerung des Moduls
+ Stati werden mit [<devicename>], Readings mit [<devicename>:<readingname>], Internals mit [<devicename>:&<internal>] oder Events mit [<devicename>:?<regexp>] angegeben
-+ Zeitangaben in der Bedingung: [HH:MM:SS] oder [HH:MM] oder [{<perl-function>}]
++ Zeitangaben in der Bedingung: [HH:MM:SS] oder [HH:MM] oder [[<devicename>]] oder [[<devicename>:<readingname>]] oder [{<perl-function>}]
+ Zeitintervalle: [<begin>-<end>] für <begin> bzw. <end> kann das obige Zeitformat gewählt werden
+ relative Zeitangaben mit vorangestelltem Pluszeichen [+<time>] oder [+<begin>-+<end>] kombinierbar mit Perl-Funktionen s. o.
+ Wochentagsteuerung: [<time>|012345678] oder [<begin>-<end>|012345678] (0-6 entspricht Sonntag bis Samstag) sowie 7 für $we und 8 für !$we
@@ -1216,10 +1272,36 @@ Einschalten am Freitag ausschalten am Montag:

define di_light DOIF ([22:00|5]) (set light on) DOELSEIF ([10:00|1]) (set light off)

-Schalten bei Sonnenaufgang und Sonnenuntergang:
+Schalten mit Zeitfunktionen, hier: bei Sonnenaufgang und Sonnenuntergang:

define di_light DOIF ([{sunrise_abs()}-{sunset(1800,"17:00","21:00")}])(set outdoorlight off) DOELSE (set outdoorlight on)

+Schalten mit indirekten Zeitangaben
+
+Statt fester Zeitangaben können auch Stati, Readings oder Internals angegeben werden. Diese müssen eine Zeitangabe im Format HH:MM oder HH:MM:SS beinhalten.
+
+Anwendungsbeispiel
+
+define time dummy
+set time 08:00
+define di_time DOIF ([[time]])(set lamp on)

+
+oder Angaben mit Zeitintervallen
+
+define begin dummy
+set begin 08:00
+
+define end dummy
+set end 10:00
+
+define di_time DOIF ([[begin]-[end]])(set radio on) DOELSE (set radio off)

+
+Bei einer Änderung des angebenen Status oder Readings wird die geänderte Zeit sofort im Modul aktualisiert.
+
+Die Endzeit soll abhängig von der Beginnzeit mit Hilfe einer eignen Perl-Funktion, hier: OffTime(), bestimmt werden. begin und end sind Dummys, wie oben definiert:
+
+define di_time DOIF ([[begin]-[end]]) (set lamp on, set end_time {(OffTime("[begin]"))}) DOELSE (set lamp off)
+
Kombination von Ereignis- und Zeitsteuerung mit logischen Abfragen

Anwendungsbeispiel: Lampe soll ab 6:00 Uhr angehen, wenn es dunkel ist und wieder ausgehen, wenn es hell wird, spätestens aber um 9:00 Uhr:
@@ -1234,9 +1316,9 @@ Schalten bei Sonnenaufgang und Sonnenuntergang:

Angaben in eckigen Klammern, die mit einem Fragezeichen beginnen, führen zu keiner Triggerung des Moduls, sie dienen lediglich der Abfrage.

-Anwendungsbeispiel: Licht soll zwischen 06:00 und 10:00 angehen, getriggert wird nur durch den Bewegungssensor nicht um 06:00 bzw. 10:00 Uhr
+Anwendungsbeispiel: Licht soll zwischen 06:00 und 10:00 angehen, getriggert wird nur durch den Taster nicht um 06:00 bzw. 10:00 Uhr

-define di_motion DOIF ([?06:00-10:00] and [BM] eq "motion")(set lamp on-for-timer 600)
+define di_motion DOIF ([?06:00-10:00] and [button])(set lamp on-for-timer 600)
attr di_motion do always


Nutzung von Readings, Stati oder Internals im Ausführungsteil
@@ -1280,11 +1362,19 @@ attr di_washer wait 300


Eine erneute Benachrichtigung wird erst wieder ausgelöst, wenn zwischendurch der Verbrauch über 2 Watt angestiegen war.

-Rolladen um 20 Minuten zeitverzögert bei Sonne runter- bzw. hochfahren (wenn der Zustand der Sonne wechselt, wird die Verzögerungszeit zurückgesetzt):
+Anwendungsbeispiel: Rolladen um 20 Minuten zeitverzögert bei Sonne runter- bzw. hochfahren (wenn der Zustand der Sonne wechselt, wird die Verzögerungszeit zurückgesetzt):

define di_shutters DOIF ([Sun] eq "on") (set shutters down) DOELSE (set shutters up)
attr di_shutters wait 1200:1200


+Anwendungsbeispiel: Rolladen nach Sonnenuntergang mit Zufallsverzögerung von 10 bis 20 Minuten herunterfahren:
+
+define di_shutters DOIF ([{sunset_abs()}]) (set shutters down, attr di_shutters wait {(600+int(rand(600)))})
+attr di_shutters do always
+attr di_shutters wait 600

+
+Hier wird der wait-timer per wait-Attribut für die nächste Ausführung per Zufall vorbereitet.
+
Anwendungsbeispiel: Beschattungssteuerung abhängig von der Temperatur. Der Rollladen soll runter von 11:00 Uhr bis Sonnenuntergang, wenn die Temperatur über 26 Grad ist. Temperaturschwankungen um 26 Grad werden mit Hilfe des wait-Attributes durch eine 15 minutige Verzögerung ausgeglichen.

define di_shutters DOIF ([sensor:temperature] > 26 and [11:00-{sunset_abs()}] (set shutters down) DOELSE (set shutters up)
@@ -1458,7 +1548,7 @@ Die Nachbildung eines on-for-timers lässt sich mit zwei DOIF´s und einem Dummy
define switch_d dummy

-define di_switch DOIF ([detector] eq "motion") (set switch_d on, set switch_d off)
+define di_switch DOIF ([detector:?motion]) (set switch_d on, set switch_d off)
attr di_switch do always

define di_light DOIF ([switch_d] eq "on") (set light on) DOELSE (set light off)