############################################## # $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 . # ############################################### package main; use strict; use warnings; sub DOIF_cmd ($$$$); sub DOIF_delTimer($) { my ($hash) = @_; RemoveInternalTimer($hash); my $max_timer=keys %{$hash->{timerfunc}}; for (my $i=0; $i<$max_timer;$i++) { RemoveInternalTimer ($hash->{timerfunc}{$i}); } return undef; } sub DOIF_delAll($) { my ($hash) = @_; delete ($hash->{helper}); delete ($hash->{condition}); delete ($hash->{do}); delete ($hash->{devices}); delete ($hash->{time}); delete ($hash->{timer}); delete ($hash->{timers}); delete ($hash->{itimer}); delete ($hash->{timeCond}); delete ($hash->{realtime}); delete ($hash->{days}); delete ($hash->{readings}); delete ($hash->{internals}); delete ($hash->{trigger}); delete ($defs{$hash->{NAME}}{READINGS}); } ######################### sub DOIF_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "DOIF_Define"; $hash->{SetFn} = "DOIF_Set"; $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 timerWithWait ".$readingFnAttributes; } sub GetBlockDoIf ($$) { my ($cmd,$match) = @_; my $count=0; my $first_pos=0; my $last_pos=0; my $err=""; while($cmd =~ /$match/g) { if (substr($cmd,pos($cmd)-1,1) eq substr($match,2,1)) { $count++; $first_pos=pos($cmd) if ($count == 1); } elsif (substr($cmd,pos($cmd)-1,1) eq substr($match,4,1)) { $count--; } if ($count < 0) { $err="right bracket without left bracket"; return ("",substr($cmd,pos($cmd)-1),$err,""); } if ($count == 0) { $last_pos=pos($cmd); last; } } if ($count > 0) { $err="no right bracket"; return ("",substr($cmd,$first_pos-1),$err); } if ($first_pos) { return (substr($cmd,0,$first_pos-1),substr($cmd,$first_pos,$last_pos-$first_pos-1),"",substr($cmd,$last_pos)); } else { return ($cmd,"","",""); } } sub EventDoIf($$$$) { my ($n,$dev,$events,$NotifyExp)=@_; return 0 if ($dev ne $n); return 0 if(!$events); # Some previous notify deleted the array. my $max = int(@{$events}); my $ret = 0; return 1 if ($NotifyExp eq ""); for (my $i = 0; $i < $max; $i++) { my $s = $events->[$i]; $s = "" if(!defined($s)); my $found = ($s =~ m/$NotifyExp/); return 1 if ($found); #if(!$found && AttrVal($n, "eventMap", undef)) { # my @res = ReplaceEventMap($n, [$n,$s], 0); # shift @res; # $s = join(" ", @res); # $found = ("$n:$s" =~ m/^$re$/); } return 0; } sub InternalDoIf($$$) { my ($name,$internal,$regExp)=@_; my $r=""; my $element; $r=$defs{$name}{$internal}; if ($regExp) { $element = ($r =~ /$regExp/) ? $1 : ""; } else { $element=$r; } return($element); } sub ReadingSecDoIf($$) { my ($name,$reading)=@_; my ($seconds, $microseconds) = gettimeofday(); return ($seconds - time_str2num(ReadingsTimestamp($name, $reading, "1970-01-01 01:00:00"))); } sub ReadingValDoIf($$$) { my ($name,$reading,$regExp)=@_; my $r; my $element; $r=$defs{$name}{READINGS}{$reading}{VAL}; $r="" if (!defined($r)); if ($regExp) { $element = ($r =~ /$regExp/) ? $1 : ""; } else { $element=$r; } return($element); } sub EvalAllDoIf($) { my ($tailBlock)= @_; my $eval=""; my $beginning; my $err; my $cmd=""; my $ret=""; while ($tailBlock ne "") { ($beginning,$eval,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\{\}]'); return ($eval,$err) if ($err); if ($eval) { if (substr($eval,0,1) eq "(") { my $ret = eval $eval; return($eval." ",$@) if ($@); $eval=$ret; } else { $eval="{".$eval."}"; } } $cmd.=$beginning.$eval; } return ($cmd,""); } sub ReplaceReadingDoIf($) { my ($element) = @_; my $beginning; my $tailBlock; my $err; my $regExp=""; my ($name,$reading,$format)=split(":",$element); my $internal=""; my $notifyExp=""; if ($name) { #return ($name,"unknown Device") if(!$defs{$name}); if ($reading) { if (substr($reading,0,1) eq "\?") { $notifyExp=substr($reading,1); return("EventDoIf('$name',".'$hash->{helper}{triggerDev},'.'$hash->{helper}{triggerEvents},'."'$notifyExp')","",$name,undef,undef); } $internal = substr($reading,1) if (substr($reading,0,1) eq "\&"); if ($format) { if ($format eq "d") { $regExp = '(-?\d+(\.\d+)?)'; } elsif ($format eq "sec") { return("ReadingSecDoIf('$name','$reading')","",$name,$reading,undef); } elsif (substr($format,0,1) eq '[') { ($beginning,$regExp,$err,$tailBlock)=GetBlockDoIf($format,'[\[\]]'); return ($regExp,$err) if ($err); return ($regExp,"no round brackets in regular expression") if ($regExp !~ /.*\(.*\)/); } else { return($format,"unknown expression format"); } } if ($internal) { return("InternalDoIf('$name','$internal','$regExp')","",$name,undef,$internal); } else { return("ReadingValDoIf('$name','$reading','$regExp')","",$name,$reading,undef); } } else { return("InternalDoIf('$name','STATE','$regExp')","",$name,undef,'STATE'); } } } sub ReplaceReadingEvalDoIf($$) { my ($element,$eval) = @_; my ($block,$err,$device,$reading,$internal)=ReplaceReadingDoIf($element); return ($block,$err) if ($err); if ($eval) { return ($device,"device does not exist: $device") if(!$defs{$device}); return ($block,"reading does not exist: $device:$reading") if (defined ($reading) and !defined($defs{$device}{READINGS}{$reading})); return ($block,"internal does not exist: $device:$internal") if (defined ($internal) and !defined($defs{$device}{$internal})); my $ret = eval $block; return($block." ",$@) if ($@); $block=$ret; } return ($block,"",$device,$reading,$internal); } sub AddItemDoIf($$) { my ($items,$item)=@_; if (!$items) { $items=" $item "; } elsif ($items !~ / $item /) { $items.="$item "; } return $items; } sub ReplaceAllReadingsDoIf($$$$) { my ($hash,$tailBlock,$condition,$eval)= @_; my $block=""; my $beginning; my $err; my $cmd=""; my $ret=""; my $device=""; my @timerarray; my $nr; my $timer=""; my $event=0; my $definition=$tailBlock; my $reading; my $internal; my $trigger=1; while ($tailBlock ne "") { $trigger=1; ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\[\]]'); return ($block,$err) if ($err); if ($block ne "") { if (substr($block,0,1) eq "?") { $block=substr($block,1); $trigger=0; } # if ($block =~ /^[0-9]+$/) { # $block="[".$block."]"; # } elsif if ($block =~ /^\??[a-z0-9._]*[a-z._]+[a-z0-9._]*($|:.+$)/i) { ($block,$err,$device,$reading,$internal)=ReplaceReadingEvalDoIf($block,$eval); return ($block,$err) if ($err); if ($trigger) { if ($condition >= 0) { $hash->{devices}{$condition} = AddItemDoIf($hash->{devices}{$condition},$device); $hash->{devices}{all} = AddItemDoIf($hash->{devices}{all},$device); $hash->{readings}{$condition} = AddItemDoIf($hash->{readings}{$condition},"$device:$reading") if (defined ($reading)); $hash->{internals}{$condition} = AddItemDoIf($hash->{internals}{$condition},"$device:$internal") if (defined ($internal)); $hash->{readings}{all} = AddItemDoIf($hash->{readings}{all},"$device:$reading") if (defined ($reading)); $hash->{internals}{all} = AddItemDoIf($hash->{internals}{all},"$device:$internal") if (defined ($internal)); $hash->{trigger}{all} = AddItemDoIf($hash->{trigger}{all},"$device") if (!defined ($internal) and !defined($reading)); $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); } } } elsif ($condition >= 0) { ($timer,$err)=DOIF_CheckTimers($hash,$block,$condition,$trigger,\@timerarray); return($timer,$err) if ($err); if ($timer) { $block=$timer; $event=1 if ($trigger); } } else { $block="[".$block."]"; } } $cmd.=$beginning.$block; } return ($definition,"no trigger in condition") if ($condition >=0 and $event == 0); return ($cmd,""); } sub ParseCommandsDoIf($$$) { my($hash,$tailBlock,$eval) = @_; my $pn=$hash->{NAME}; my $currentBlock=""; my $beginning=""; my $err=""; my $pos=0; my $last_error=""; my $ifcmd; my $ret; while ($tailBlock ne "") { if ($tailBlock=~ /^\s*\{/) { # perl block ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\{\}]'); return ($currentBlock,$err) if ($err); if ($currentBlock ne "") { ($currentBlock,$err)=ReplaceAllReadingsDoIf($hash,$currentBlock,-1,$eval); return ($currentBlock,$err) if ($err); if ($eval) { ($currentBlock,$err)=EvalAllDoIf($currentBlock); return ($currentBlock,$err) if ($err); } } $currentBlock="{".$currentBlock."}"; } elsif ($tailBlock =~ /^\s*IF/) { my $ifcmd=""; ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); #condition return ($currentBlock,$err) if ($err); $ifcmd.=$beginning."(".$currentBlock.")"; ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); #if case return ($currentBlock,$err) if ($err); $ifcmd.=$beginning."(".$currentBlock.")"; if ($tailBlock =~ /^\s*ELSE/) { ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); #else case return ($currentBlock,$err) if ($err); $ifcmd.=$beginning."(".$currentBlock.")"; } $currentBlock=$ifcmd; } else { if ($tailBlock =~ /^\s*\(/) { # remove bracket ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); return ($currentBlock,$err) if ($err); #$tailBlock=substr($tailBlock,pos($tailBlock)) if ($tailBlock =~ /^\s*,/g); } elsif ($tailBlock =~ /,/g) { $pos=pos($tailBlock)-1; $currentBlock=substr($tailBlock,0,$pos); $tailBlock=substr($tailBlock,$pos+1); } else { $currentBlock=$tailBlock; $tailBlock=""; } if ($currentBlock ne "") { ($currentBlock,$err)=ReplaceAllReadingsDoIf($hash,$currentBlock,-1,$eval); return ($currentBlock,$err) if ($err); if ($eval) { ($currentBlock,$err)=EvalAllDoIf($currentBlock); return ($currentBlock,$err) if ($err); } } } if ($eval) { if ($ret = AnalyzeCommandChain(undef,$currentBlock)) { Log3 $pn,2 , "$pn: $currentBlock: $ret"; $last_error.="$currentBlock: $ret "; } } $tailBlock=substr($tailBlock,pos($tailBlock)) if ($tailBlock =~ /^\s*,/g); } return("",$last_error); } sub DOIF_CheckTimers($$$$$) { my $i=0; my @nrs; my @times; my $nr=0; my $days=""; my $err; my $beginning; my $pos; my $time; my $block; my $result; my ($hash,$timer,$condition,$trigger,$timerarray)=@_; $timer =~ s/\s//g; while ($timer ne "") { if ($timer=~ /^\+\(/) { ($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); } elsif ($timer=~ /^\(/) { ($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); } elsif ($timer=~ /^\{/) { ($beginning,$time,$err,$timer)=GetBlockDoIf($timer,'[\{\}]'); return ($time,$err) if ($err); $time="{".$time."}"; } elsif ($timer=~ m/^\+\[([0-9]+)\]:([0-5][0-9])/g) { $pos=pos($timer); $time=substr($timer,0,$pos); $timer=substr($timer,$pos); } elsif ($timer=~ /^\+\[/) { ($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); } elsif ($timer=~ /^\[/) { ($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); } elsif ($timer =~ /-/g) { $pos=pos($timer)-1; $time=substr($timer,0,$pos); $timer=substr($timer,$pos); } else { ($time,$days)=split(/\|/,$timer); $timer=""; } $times[$i]=$time; $nrs[$i++]=$hash->{helper}{last_timer}++; if ($timer) { if ($timer =~ /\-/g) { $timer=substr($timer,pos($timer)); } elsif ($timer =~ /\|/g) { $days=substr($timer,pos($timer)); $timer=""; } else { return ($timer,"wrong time format"); } } } $days = "" if (!defined ($days)); for (my $j=0; $j<$i;$j++) { $nr=$nrs[$j]; $time=$times[$j]; $time .=":00" if ($time =~ m/^[0-9][0-9]:[0-5][0-9]$/); $hash->{timer}{$nr}=0; $hash->{time}{$nr}=$time; $hash->{timeCond}{$nr}=$condition; $hash->{days}{$nr}=$days if ($days ne ""); ${$timerarray}[$nr]={hash=>$hash,nr=>$nr}; 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]; } if ($i == 2) { $block='DOIF_time($hash,$hash->{realtime}{'.$nrs[0].'},$hash->{realtime}{'.$nrs[1].'},$wday,$hms,"'.$days.'")'; } else { $block='DOIF_time_once($hash,$hash->{timer}{'.$nrs[0].'},$wday,"'.$days.'")'; } return ($block,""); } sub DOIF_time($$$$$$) { my $ret=0; my ($hash,$begin,$end,$wday,$hms,$days)=@_; my $err; ($days,$err)=ReplaceAllReadingsDoIf($hash,$days,-1,1); if ($err) { my $errmsg="error in days: $err"; Log3 ($hash->{NAME},2 , "$hash->{NAME}: $errmsg"); readingsSingleUpdate ($hash, "error", $errmsg,1); return 0; } my $we=DOIF_we($wday); if ($end gt $begin) { if ($hms ge $begin and $hms lt $end) { $ret=1; } } else { if ($hms ge $begin) { $ret=1; } elsif ($hms lt $end) { $wday=1 if ($wday-- == -1); $we=DOIF_we($wday); $ret=1; } } if ($ret == 1) { return 1 if ($days eq "" or $days =~ /$wday/ or ($days =~ /7/ and $we) or ($days =~ /8/ and !$we)); } return 0; } sub DOIF_time_once($$$$) { my ($hash,$flag,$wday,$days)=@_; my $err; ($days,$err)=ReplaceAllReadingsDoIf($hash,$days,-1,1); if ($err) { my $errmsg="error in days: $err"; Log3 ($hash->{NAME},2 , "$hash->{NAME}: $errmsg"); readingsSingleUpdate ($hash, "error", $errmsg,1); return 0; } my $we=DOIF_we($wday); if ($flag) { return 1 if ($days eq "" or $days =~ /$wday/ or ($days =~ /7/ and $we) or ($days =~ /8/ and !$we)); } return 0; } ############################ sub DOIF_SetState($$$$$) { my ($hash,$nr,$subnr,$event,$last_error)=@_; my $pn=$hash->{NAME}; my $cmdNr=""; my $cmd=""; my $err=""; my $attr=AttrVal($hash->{NAME},"cmdState",""); my $state=AttrVal($hash->{NAME},"state",""); my @cmdState=split(/\|/,$attr); return undef if (AttrVal($hash->{NAME},"disable","")); $nr=ReadingsVal($pn,"cmd_nr",0)-1 if (!$event); if ($nr!=-1) { $cmdNr=$nr+1; if (defined $hash->{do}{$nr}{$subnr+1}) { $cmd="cmd_".$cmdNr."_".($subnr+1); } else { if ($attr) { $cmd=$cmdState[$nr] if (defined ($cmdState[$nr])); } else { $cmd="cmd_$cmdNr"; } } } readingsBeginUpdate ($hash); if ($event) { readingsBulkUpdate($hash,"cmd_nr",$cmdNr); if (defined $hash->{do}{$nr}{1}) { readingsBulkUpdate($hash,"cmd_seqnr",$subnr+1) } else { delete ($defs{$hash->{NAME}}{READINGS}{cmd_seqnr}); } readingsBulkUpdate($hash,"cmd_event",$event); if ($last_error) { readingsBulkUpdate($hash,"error",$last_error); } else { delete ($defs{$hash->{NAME}}{READINGS}{error}); } } if ($state and !defined $hash->{do}{$nr}{$subnr+1}) { my $stateblock='\['.$pn.'\]'; $state =~ s/$stateblock/$cmd/g; ($state,$err)=ReplaceAllReadingsDoIf($hash,$state,-1,1); if ($err) { Log3 $pn,2 , "$pn: error in state: $err" if ($err); $state=$err; } else { ($state,$err)=EvalAllDoIf($state); if ($err) { Log3 $pn,2 , "$pn: error in state: $err" if ($err); $state=$err; } } } else { $state=$cmd; } readingsBulkUpdate($hash, "state", $state); readingsEndUpdate ($hash, 1); } sub DOIF_we($) { my ($wday)=@_; my $we = (($wday==0 || $wday==6) ? 1 : 0); if(!$we) { my $h2we = $attr{global}{holiday2we}; $we = 1 if($h2we && $value{$h2we} && $value{$h2we} ne "none"); } return $we; } sub DOIF_CheckCond($$) { my ($hash,$condition) = @_; my $err=""; my ($seconds, $microseconds) = gettimeofday(); my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime($seconds); my $hms = sprintf("%02d:%02d:%02d", $hour, $min, $sec); my $hm = sprintf("%02d:%02d", $hour, $min); my $device; my $reading; my $internal; my $we=DOIF_we($wday); $month++; $year+=1900; if (defined ($hash->{readings}{$condition})) { foreach my $devReading (split(/ /,$hash->{readings}{$condition})) { ($device,$reading)=(split(":",$devReading)); return (0,"reading does not exist: [$device:$reading]") if ($devReading and !defined($defs{$device}{READINGS}{$reading})); } } if (defined ($hash->{internals}{$condition})) { foreach my $devInternal (split(/ /,$hash->{internals}{$condition})) { ($device,$internal)=(split(":",$devInternal)); return (0,"internal does not exist: [$device:$internal]") if ($devInternal and !defined($defs{$device}{$internal})); } } my $ret = eval $hash->{condition}{$condition}; if($@){ $err = "perl error in condition: $hash->{condition}{$condition}: $@"; $ret = 0; } return ($ret,$err); } sub DOIF_cmd ($$$$) { my ($hash,$nr,$subnr,$event)=@_; my $pn = $hash->{NAME}; my $ret; my $cmd; my $err=""; my $repeatnr; my $last_cmd=ReadingsVal($pn,"cmd_nr",0)-1; my @repeatsame=split(/:/,AttrVal($pn,"repeatsame","")); my @cmdpause=split(/:/,AttrVal($pn,"cmdpause","")); my @waitsame=split(/:/,AttrVal($pn,"waitsame","")); my ($seconds, $microseconds) = gettimeofday(); if ($cmdpause[$nr] and $subnr==0) { return undef if ($seconds - time_str2num(ReadingsTimestamp($pn, "state", "1970-01-01 01:00:00")) < $cmdpause[$nr]); } if (AttrVal($pn,"repeatsame","")) { if ($subnr == 0) { if ($repeatsame[$nr]) { $repeatnr=ReadingsVal($pn,"cmd_count",0); if ($last_cmd == $nr) { if ($repeatnr < $repeatsame[$nr]) { $repeatnr++; } else { return undef; } } else { $repeatnr=1; } readingsSingleUpdate ($hash, "cmd_count", $repeatnr,1); } else { return undef if ($last_cmd == $nr and $subnr==0 and (AttrVal($pn,"do","") ne "always" and AttrVal($pn,"do","") ne "resetwait")); delete ($defs{$hash->{NAME}}{READINGS}{cmd_count}); } } } if (AttrVal($pn,"waitsame","")) { if ($subnr == 0) { if ($waitsame[$nr]) { my $cmd_nr="cmd_".($nr+1); if (ReadingsVal($pn,"waitsame","") eq $cmd_nr) { if ($seconds - time_str2num(ReadingsTimestamp($pn, "waitsame", "1970-01-01 01:00:00")) > $waitsame[$nr]) { readingsSingleUpdate ($hash, "waitsame", $cmd_nr,1); return undef; } } else { readingsSingleUpdate ($hash, "waitsame", $cmd_nr,1); return undef; } } delete ($defs{$hash->{NAME}}{READINGS}{waitsame}); } } if ($hash->{do}{$nr}{$subnr}) { $hash->{helper}{cur_cmd_nr}="cmd_".($nr+1)."_".($subnr+1); ($cmd,$err)=ParseCommandsDoIf($hash,$hash->{do}{$nr}{$subnr},1); } DOIF_SetState ($hash,$nr,$subnr,$event,$err); delete $hash->{helper}{cur_cmd_nr}; if (defined $hash->{do}{$nr}{++$subnr}) { my $last_cond=ReadingsVal($pn,"cmd_nr",0)-1; if (DOIF_SetSleepTimer($hash,$last_cond,$nr,$subnr,$event,-1)) { DOIF_cmd ($hash,$nr,$subnr,$event); } } return undef; } sub DOIF_Trigger ($$$) { my ($hash,$device,$timerNr)= @_; my $ret; my $err; my $doelse=0; my $event; my $pn=$hash->{NAME}; my $max_cond=keys %{$hash->{condition}}; my $last_cond=ReadingsVal($pn,"cmd_nr",0)-1; for (my $i=0; $i<$max_cond;$i++) { if ($device eq "") {# timer next if (!defined ($hash->{timers}{$i})); next if ($hash->{timers}{$i} !~ / $timerNr /); $event="timer_".($timerNr+1); } else { #event next if (!defined ($hash->{devices}{$i})); next if ($hash->{devices}{$i} !~ / $device /); $event="$device"; } if (($ret,$err)=DOIF_CheckCond($hash,$i)) { if ($err) { Log3 $hash->{Name},2,"$hash->{NAME}: $err"; readingsSingleUpdate ($hash, "error", $err,1); return undef; } if ($ret) { if (DOIF_SetSleepTimer($hash,$last_cond,$i,0,$device,$timerNr)) { DOIF_cmd ($hash,$i,0,$event); return 1; } else { return undef; } } else { $doelse = 1; } } } if ($doelse) { #DOELSE if (defined ($hash->{do}{$max_cond}{0}) or ($max_cond == 1 and !(AttrVal($pn,"do","") or AttrVal($pn,"repeatsame","")))) { #DOELSE if (DOIF_SetSleepTimer($hash,$last_cond,$max_cond,0,$device,$timerNr)) { DOIF_cmd ($hash,$max_cond,0,$event) ; return 1; } } } return undef; } sub DOIF_Notify($$) { my ($hash, $dev) = @_; my $pn = $hash->{NAME}; return "" if($attr{$pn} && $attr{$pn}{disable}); 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"))) { $hash->{helper}{globalinit}=1; if ($hash->{helper}{last_timer} > 0){ for (my $j=0; $j<$hash->{helper}{last_timer};$j++) { DOIF_SetTimer("DOIF_TimerTrigger",$hash->{timerfunc}{$j}); } } } 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}); return "" if (!$hash->{helper}{globalinit}); return "" if (ReadingsVal($pn,"mode","") eq "disabled"); if (($hash->{devices}{all}) and $hash->{devices}{all} =~ / $dev->{NAME} /) { readingsSingleUpdate ($hash, "Device",$dev->{NAME},0); #my $events = deviceEvents($dev, AttrVal($dev->{NAME}, "addStateEvent", 0)); #readingsSingleUpdate ($hash, "Event","@{$events}",0); 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 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 defined ($defs{$device}{$internal})); } } #my ($seconds, $microseconds) = gettimeofday(); #if ($hash->{helper}{last_event_time}) { # return undef if (($seconds-$hash->{helper}{last_event_time}) < AttrVal($pn,"eventpause",0)); #} #$hash->{helper}{last_event_time}=$seconds; if ($hash->{trigger}{all}) { foreach my $item (split(/ /,$hash->{trigger}{all})) { my $events = deviceEvents($dev, AttrVal($dev->{NAME}, "addStateEvent", 0)); $hash->{helper}{triggerEvents}=$events; $hash->{helper}{triggerDev}=$dev->{NAME}; readingsSingleUpdate ($hash, "e_".$dev->{NAME}."_events","@{$events}",0); } } $ret=DOIF_Trigger($hash,$dev->{NAME},-1); } if (($hash->{state}{device}) and $hash->{state}{device} =~ / $dev->{NAME} / and !$ret) { DOIF_SetState($hash,"",0,"",""); } return undef; } sub DOIF_TimerTrigger ($) { my ($timer)=@_; my $nr=${$timer}->{nr}; my $hash=${$timer}->{hash}; my $pn = $hash->{NAME}; my $ret; # if (!AttrVal($hash->{NAME},"disable","")) { if (ReadingsVal($pn,"mode","") ne "disabled") { $hash->{timer}{$nr}=1; $ret=DOIF_Trigger ($hash,"",$nr); $hash->{timer}{$nr}=0; } DOIF_SetTimer("DOIF_TimerTrigger",$timer) if (!AttrVal($hash->{NAME},"disable","")); return($ret); } sub DOIF_DetTime($) { my ($timeStr) = @_; my $rel=0; my $align; my $hr=0; my $err; my $h=0; my $m=0; my $s=0; my $fn; if (substr($timeStr,0,1) eq "+") { $timeStr=substr($timeStr,1); $rel=1; } my ($now, $microseconds) = gettimeofday(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now); if($timeStr =~ m/^\[([0-9]+)\]:([0-5][0-9])$/) { $hr=$1; $rel=0; $align=$2; } elsif ($timeStr =~ m/^:([0-5][0-9])$/) { $align=$1; } elsif ($timeStr =~ m/^(\-?([0-9]+))$/) { $s=$1; } else { if ($timeStr =~ m/^\$hms$/) { $timeStr = sprintf("%02d:%02d:%02d", $hour, $min, $sec); } elsif ($timeStr =~ m/^\$hm$/) { $timeStr = sprintf("%02d:%02d", $hour, $min); } ($err, $h, $m, $s, $fn) = GetTimeSpec($timeStr); return $err if ($err); } if (defined ($align)) { if ($rel) { if ($align > 0) { $m = (int($min/$align)+1)*$align; if ($m>=60) { $h = $hour+1; $m = 0; } else { $h = $hour; } } $rel=0; } else { $m=$align; if ($hr > 1) { $h = (int($hour/$hr)+1)*$hr; $h = 0 if ($h >=24); } else { if ($m <= $min) { $h = $hour+1; } else { $h = $hour; } } } } my $second = $h*3600+$m*60+$s; if ($second == 0 and $rel) { $err = "null is not allowed on a relative time"; } return ($err, ($rel and !defined ($align)), $second); } sub DOIF_CalcTime($) { my ($block)= @_; my $tailBlock; my $beginning; my $err; my $cmd=""; my $rel=""; my $relGlobal=0; my $reading; my $internal; my $device; my $pos; my $ret; if ($block=~ m/^\+\[([0-9]+)\]:([0-5][0-9])$/) { ($err,$rel,$block)=DOIF_DetTime($block); return ($block,$err,$rel); } elsif ($block =~ /^\+\(/ or $block =~ /^\+\[/) { $relGlobal=1; #$pos=pos($block); $block=substr($block,1); } if ($block =~ /^\(/) { ($beginning,$tailBlock,$err,$tailBlock)=GetBlockDoIf($block,'[\(\)]'); return ($tailBlock,$err) if ($err); } else { if ($block =~ /^\[/) { ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($block,'[\[\]]'); return ($block,$err) if ($err); ($block,$err,$device,$reading,$internal)=ReplaceReadingEvalDoIf($block,1); return ($block,$err) if ($err); } ($err,$rel,$block)=DOIF_DetTime($block); $rel=1 if ($relGlobal); return ($block,$err,$rel); } $tailBlock=$block; while ($tailBlock ne "") { ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\[\]]'); return ($block,$err) if ($err); if ($block ne "") { if ($block =~ /^\??[a-z0-9._]*[a-z._]+[a-z0-9._]*($|:.+$)/i) { ($block,$err,$device,$reading,$internal)=ReplaceReadingEvalDoIf($block,1); return ($block,$err) if ($err); } ($err,$rel,$block)=DOIF_DetTime($block); } $cmd.=$beginning.$block; } $tailBlock=$cmd; $cmd=""; while ($tailBlock ne "") { ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\{\}]'); return ($block,$err) if ($err); if ($block ne "") { $ret = eval $block; return($block." ",$@) if ($@); $block=$ret; ($err,$rel,$block)=DOIF_DetTime($block); } $cmd.=$beginning.$block; } $ret = eval $cmd; return($cmd." ",$@) if ($@); return ($ret,"null is not allowed on a relative time",$relGlobal) if ($ret == 0 and $relGlobal); return ($ret,"",$relGlobal); } sub DOIF_SetTimer($$) { my ($func, $timer) = @_; my $nr=${$timer}->{nr}; my $hash=${$timer}->{hash}; my $timeStr=$hash->{time}{$nr}; my $cond=$hash->{timeCond}{$nr}; my $next_time; my ($second,$err, $rel)=DOIF_CalcTime($timeStr); if ($err) { readingsSingleUpdate ($hash,"timer_".($nr+1)."_c".($cond+1),"error: ".$err,0); RemoveInternalTimer($timer); $hash->{realtime}{$nr}="00:00:00"; return $err; } my ($now, $microseconds) = gettimeofday(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now); my $isdst_now=$isdst; my $sec_today = $hour*3600+$min*60+$sec; my $midnight = $now-$sec_today; if ($rel) { $next_time =$now+$second; } else { $next_time = $midnight+$second; } $next_time+=86400 if ($second <= $sec_today and !$rel); ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($next_time); if ($isdst_now != $isdst) { if ($isdst_now == 1) { $next_time+=3600 if ($isdst == 0); } else { $next_time-=3600 if ($isdst == 1); } } if ($next_time < $now) { readingsSingleUpdate ($hash,"timer_".($nr+1)."_c".($cond+1),"back to the past ist not allowed",0); return("timer_".($nr+1)."_c".($cond+1),"back to the past ist not allowed"); } else { my $next_time_str=strftime("%d.%m.%Y %H:%M:%S",localtime($next_time)); $next_time_str.="\|".$hash->{days}{$nr} if (defined ($hash->{days}{$nr})); readingsSingleUpdate ($hash,"timer_".($nr+1)."_c".($cond+1),$next_time_str,0); $hash->{realtime}{$nr}=strftime("%H:%M:%S",localtime($next_time)); RemoveInternalTimer($timer); InternalTimer($next_time, $func, $timer, 0); } return undef; } sub DOIF_SetSleepTimer($$$$$$) { my ($hash,$last_cond,$nr,$subnr,$device,$timerNr)=@_; my $pn = $hash->{NAME}; if (defined $hash->{helper}{cur_cmd_nr}) { return 0; } my $sleeptimer=$hash->{helper}{sleeptimer}; my @waitdel=split(/:/,AttrVal($pn,"waitdel","")); my @waitdelsubnr=split(/,/,defined $waitdel[$sleeptimer] ? $waitdel[$sleeptimer] : ""); my $err; if ($sleeptimer != -1 and (($sleeptimer != $nr or AttrVal($pn,"do","") eq "resetwait") or ($sleeptimer == $nr and $waitdelsubnr[$subnr]))) { RemoveInternalTimer($hash); #delete ($defs{$hash->{NAME}}{READINGS}{wait_timer}); readingsSingleUpdate ($hash, "wait_timer", "no timer",1); $hash->{helper}{sleeptimer}=-1; $subnr=$hash->{helper}{sleepsubtimer} if ($hash->{helper}{sleepsubtimer}!=-1); return 0 if ($sleeptimer == $nr and $waitdelsubnr[$subnr]); } if ($timerNr >= 0 and !AttrVal($pn,"timerWithWait","")) {#Timer if ($last_cond != $nr or AttrVal($pn,"do","") eq "always" or AttrVal($pn,"repeatsame","")) { return 1; } else { return 0; } } if ($hash->{helper}{sleeptimer} == -1 and ($last_cond != $nr or $subnr > 0 or AttrVal($pn,"do","") eq "always" or AttrVal($pn,"do","") eq "resetwait" or AttrVal($pn,"repeatsame",""))) { my @sleeptimer=split(/:/,AttrVal($pn,"wait","")); my $sleeptime=0; if ($waitdelsubnr[$subnr]) { $sleeptime = $waitdelsubnr[$subnr]; } else { my @sleepsubtimer=split(/,/,defined $sleeptimer[$nr]? $sleeptimer[$nr]: ""); if ($sleepsubtimer[$subnr]) { $sleeptime=$sleepsubtimer[$subnr]; } } ($sleeptime,$err)=ReplaceAllReadingsDoIf($hash,$sleeptime,-1,1); if ($err) { Log3 $pn,2 , "$pn: error in wait: $err" if ($err); $sleeptime=0; } else { my $ret = eval $sleeptime; if ($@) { Log3 ($pn,2 , "$pn: error in wait: $sleeptimer: $@"); $sleeptime=0; } else { $sleeptime=$ret; } } if ($sleeptime) { my ($seconds, $microseconds) = gettimeofday(); my $next_time = $seconds+$sleeptime; $hash->{helper}{sleeptimer}=$nr; $hash->{helper}{sleepsubtimer}=$subnr; $device="timer_".($timerNr+1) if ($timerNr >= 0); $hash->{helper}{sleepdevice}=$device; my $cmd_nr=$nr+1; if (defined $hash->{do}{$nr}{1}) { my $cmd_subnr=$subnr+1; readingsSingleUpdate ($hash,"wait_timer",strftime("%d.%m.%Y %H:%M:%S cmd_$cmd_nr"."_$cmd_subnr $device",localtime($next_time)),1); } else { readingsSingleUpdate ($hash,"wait_timer",strftime("%d.%m.%Y %H:%M:%S cmd_$cmd_nr $device",localtime($next_time)),1); } InternalTimer($next_time, "DOIF_SleepTrigger",$hash, 0); return 0; } else { return 1; } } else { return 0; } } sub DOIF_SleepTrigger ($) { my ($hash)=@_; my $sleeptimer=$hash->{helper}{sleeptimer}; my $sleepsubtimer=$hash->{helper}{sleepsubtimer}; $hash->{helper}{sleeptimer}=-1; $hash->{helper}{sleepsubtimer}=-1; my $pn = $hash->{NAME}; readingsSingleUpdate ($hash, "wait_timer", "no timer",1); # if (!AttrVal($hash->{NAME},"disable","")) { if (ReadingsVal($pn,"mode","") ne "disable") { DOIF_cmd ($hash,$sleeptimer,$sleepsubtimer,$hash->{helper}{sleepdevice}); } return undef; } ############################# sub CmdDoIf($$) { my ($hash, $tail) = @_; my $cond=""; my $err=""; my $if_cmd=""; my $if_cmd_ori=""; my $else_cmd=""; my $else_cmd_ori=""; my $tailBlock; my $eval=""; my $beginning; my $i=0; my $j=0; my $last_do; if (!$tail) { $tail=""; } else { $tail =~ s/(##.*\n)|(##.*$)|\n//g; } # if (defined $hash->{helper}) #def modify if ($init_done) { DOIF_delTimer($hash); DOIF_delAll ($hash); readingsSingleUpdate ($hash,"state","initialized",1); $hash->{helper}{globalinit}=1; } #$hash->{STATE} = 'initialized'; $hash->{helper}{last_timer}=0; $hash->{helper}{sleeptimer}=-1; # if ($init_done) { # $hash->{helper}{globalinit}=1; # } while ($tail ne "") { return($tail, "no left bracket of condition") if ($tail !~ /^ *\(/); #condition ($beginning,$cond,$err,$tail)=GetBlockDoIf($tail,'[\(\)]'); return ($cond,$err) if ($err); ($cond,$err)=ReplaceAllReadingsDoIf($hash,$cond,$i,0); return ($cond,$err) if ($err); return ($tail,"no condition") if ($cond eq ""); $hash->{condition}{$i}=$cond; #DOIF $if_cmd_ori=""; $j=0; while ($tail =~ /^\s*\(/) { ($beginning,$if_cmd_ori,$err,$tail)=GetBlockDoIf($tail,'[\(\)]'); return ($if_cmd_ori,$err) if ($err); ($if_cmd,$err)=ParseCommandsDoIf($hash,$if_cmd_ori,0); return ($if_cmd,$err) if ($err); #return ($tail,"no commands") if ($if_cmd eq ""); $hash->{do}{$i}{$j++}=$if_cmd_ori; } $hash->{do}{$i}{0}=$if_cmd_ori if ($j==0); #do without brackets $last_do=$i; $tail =~ s/^\s*$//g; if (length($tail)) { $tail =~ /^\s*DOELSEIF/g; if (pos($tail)) { $tail=substr($tail,pos($tail)); if (!length($tail)) { return ($tail,"no DOELSEIF block"); } } else { last if ($tail =~ /^\s*DOELSE/); return ($tail,"expected DOELSEIF or DOELSE"); } } $i++; } #DOELSE if (length($tail)) { $tail =~ /^\s*DOELSE/g; if (pos($tail)) { $tail=substr($tail,pos($tail)); } else { return ($tail,"expected DOELSE"); } $j=0; while ($tail =~ /^\s*\(/) { ($beginning,$else_cmd_ori,$err,$tail)=GetBlockDoIf($tail,'[\(\)]'); return ($else_cmd_ori,$err) if ($err); ($else_cmd,$err)=ParseCommandsDoIf($hash,$else_cmd_ori,0); return ($else_cmd,$err) if ($err); $hash->{do}{$last_do+1}{$j++}=$else_cmd_ori; } $hash->{do}{$last_do+1}{0}=$else_cmd_ori if ($j==0); #doelse without brackets } return("","") } sub DOIF_Define($$$) { my ($hash, $def) = @_; my ($name, $type, $cmd) = split(/[\s]+/, $def, 3); return undef if (AttrVal($hash->{NAME},"disable","")); my ($msg,$err)=CmdDoIf($hash,$cmd); if ($err ne "") { $msg=$cmd if (!$msg); my $errmsg="$name $type: $err: $msg"; return $errmsg; } else { return undef; } } ################################# sub DOIF_Attr(@) { my @a = @_; my $hash = $defs{$a[1]}; my $ret=""; if (($a[0] eq "set" and $a[2] eq "disable" and ($a[3] eq "0")) or (($a[0] eq "del" and $a[2] eq "disable"))) { my $cmd = $defs{$hash->{NAME}}{DEF}; my ($msg,$err)=CmdDoIf($hash,$cmd); if ($err ne "") { $msg=$cmd if (!$msg); return ("$err: $msg"); } } elsif($a[0] eq "set" and $a[2] eq "disable" and $a[3] eq "1") { DOIF_delTimer($hash); DOIF_delAll ($hash); readingsSingleUpdate ($hash,"state","deactivated",1); } elsif($a[0] eq "set" && $a[2] eq "state") { delete ($hash->{state}{device}); my ($block,$err)=ReplaceAllReadingsDoIf($hash,$a[3],-2,0); return $err if ($err); } elsif($a[0] eq "set" && $a[2] eq "wait") { RemoveInternalTimer($hash); #delete ($defs{$hash->{NAME}}{READINGS}{wait_timer}); readingsSingleUpdate ($hash, "wait_timer", "no timer",1); $hash->{helper}{sleeptimer}=-1; } elsif($a[0] eq "set" && $a[2] eq "initialize") { readingsSingleUpdate ($hash,"state",$a[3],1); readingsSingleUpdate ($hash,"cmd_nr","0",1); } elsif($a[0] eq "del" && $a[2] eq "repeatsame") { delete ($defs{$hash->{NAME}}{READINGS}{cmd_count}); } elsif($a[0] eq "del" && $a[2] eq "waitsame") { delete ($defs{$hash->{NAME}}{READINGS}{waitsame}); } return undef; } sub DOIF_Undef { my ($hash, $name) = @_; $hash->{DELETED} = 1; DOIF_delTimer($hash); return undef; } sub DOIF_Set($@) { my ($hash, @a) = @_; my $pn = $hash->{NAME}; my $arg = $a[1]; my $value = (defined $a[2]) ? $a[2] : ""; my $ret=""; if ($arg eq "disable" or $arg eq "initialize") { if (AttrVal($hash->{NAME},"disable","")) { return ("modul ist deactivated by disable attribut, delete disable attribut first"); } } if ($arg eq "disable") { readingsBeginUpdate ($hash); readingsBulkUpdate($hash, "state", "disabled"); readingsBulkUpdate($hash, "mode", "disabled"); readingsEndUpdate ($hash, 1); } elsif ($arg eq "initialize" ) { delete ($defs{$hash->{NAME}}{READINGS}{mode}); delete ($defs{$hash->{NAME}}{READINGS}{cmd_nr}); delete ($defs{$hash->{NAME}}{READINGS}{cmd_event}); readingsSingleUpdate($hash, "state","initialize",1); } else { return "$pn: unknown argument $a[1], choose one of disable initialize" } return $ret; } 1; =pod =begin html

DOIF

=end html =begin html_DE

DOIF

=end html_DE =cut