From 9c804558727d3bd35e0ec196c96b39ed17d97a85 Mon Sep 17 00:00:00 2001 From: Damian <> Date: Sun, 23 Apr 2017 17:59:06 +0000 Subject: [PATCH] 98_DOIF.pm: aggregate function git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@14088 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- FHEM/98_DOIF.pm | 853 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 629 insertions(+), 224 deletions(-) diff --git a/FHEM/98_DOIF.pm b/FHEM/98_DOIF.pm index 7e4b641f8..6d1e8fa81 100644 --- a/FHEM/98_DOIF.pm +++ b/FHEM/98_DOIF.pm @@ -2,7 +2,7 @@ # $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 @@ -11,7 +11,7 @@ # 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 . # @@ -30,7 +30,7 @@ DOIF_delTimer($) my ($hash) = @_; RemoveInternalTimer($hash); foreach my $key (keys %{$hash->{triggertime}}) { - RemoveInternalTimer (\$hash->{triggertime}{$key}); + RemoveInternalTimer (\$hash->{triggertime}{$key}); } } @@ -61,8 +61,7 @@ DOIF_delAll($) foreach my $key (keys %{$defs{$hash->{NAME}}{READINGS}}) { delete $defs{$hash->{NAME}}{READINGS}{$key} if ($key =~ "^(Device|state|error|warning|cmd|e_|timer_|wait_|matched_|last_cmd|mode)"); } - -} +} ######################### sub @@ -78,8 +77,8 @@ DOIF_Initialize($) } - -sub + +sub GetBlockDoIf ($$) { my ($cmd,$match) = @_; @@ -99,7 +98,7 @@ GetBlockDoIf ($$) $err="right bracket without left bracket"; return ("",substr($cmd,pos($cmd)-1),$err,""); } - + if ($count == 0) { $last_pos=pos($cmd); last; @@ -115,39 +114,39 @@ GetBlockDoIf ($$) return ($cmd,"","",""); } } -sub +sub GetCommandDoIf ($$) { my ($separator,$tailBlock) = @_; - my $char; + my $char; my $beginning; my $currentBlock; my $err; my $cmd=""; - while ($tailBlock=~ /^([^$separator^"^\[^\{^\(]*)/g) { + while ($tailBlock=~ /^([^$separator^"^\[^\{^\(]*)/g) { $char=substr($tailBlock,pos($tailBlock),1); - if ($char eq $separator) { + if ($char eq $separator) { $cmd=$cmd.substr($tailBlock,0,pos($tailBlock)); $tailBlock=substr($tailBlock,pos($tailBlock)+1); return($cmd,$tailBlock,""); } elsif ($char eq '{') { ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\{\}]'); return ($currentBlock,$tailBlock,$err) if ($err); - $cmd=$cmd.$beginning."{$currentBlock}"; + $cmd=$cmd.$beginning."{$currentBlock}"; } elsif ($char eq '(') { - ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); + ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); return ($currentBlock,$tailBlock,$err) if ($err); - $cmd=$cmd.$beginning."($currentBlock)"; + $cmd=$cmd.$beginning."($currentBlock)"; } elsif ($char eq '[') { ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\[\]]'); return ($currentBlock,$tailBlock,$err) if ($err); - $cmd=$cmd.$beginning."[$currentBlock]"; + $cmd=$cmd.$beginning."[$currentBlock]"; } elsif ($char eq '"') { if ($tailBlock =~ /(^[^"]*"[^"]*")(.*)/) { $cmd=$cmd.$1; $tailBlock=$2; } - } + } } if ($cmd eq "") { $cmd=$tailBlock; @@ -159,10 +158,10 @@ GetCommandDoIf ($$) sub EvalValueDoIf($$$) { -my ($hash,$attr,$value)=@_; -return "" if (!defined($value) or $value eq ""); -my $err=""; -my $pn=$hash->{NAME}; + my ($hash,$attr,$value)=@_; + return "" if (!defined($value) or $value eq ""); + my $err=""; + my $pn=$hash->{NAME}; $value =~ s/\$SELF/$pn/g; ($value,$err)=ReplaceAllReadingsDoIf($hash,$value,-1,1); if ($err) { @@ -186,9 +185,9 @@ my $pn=$hash->{NAME}; sub EvalCmdStateDoIf($$) { -my ($hash,$state)=@_; -my $err; -my $pn=$hash->{NAME}; + my ($hash,$state)=@_; + my $err; + my $pn=$hash->{NAME}; ($state,$err)=ReplaceAllReadingsDoIf($hash,$state,-1,1); if ($err) { Log3 $pn,4 , "$pn: error in state: $err" if ($err); @@ -200,24 +199,24 @@ my $pn=$hash->{NAME}; $state=$err; } } - return($state) + return($state) } sub SplitDoIf($$) { -my ($separator,$tailBlock)=@_; -my @commands; -my $cmd; -my $err; -if (defined $tailBlock) { - while ($tailBlock ne "") { - ($cmd,$tailBlock,$err)=GetCommandDoIf($separator,$tailBlock); - #return (@commands,$err) if ($err); - push(@commands,$cmd); + my ($separator,$tailBlock)=@_; + my @commands; + my $cmd; + my $err; + if (defined $tailBlock) { + while ($tailBlock ne "") { + ($cmd,$tailBlock,$err)=GetCommandDoIf($separator,$tailBlock); + #return (@commands,$err) if ($err); + push(@commands,$cmd); + } } -} -return(@commands); + return(@commands); } sub @@ -232,7 +231,7 @@ EventCheckDoif($$$$) my $ret = 0; if ($NotifyExp eq "") { return 1 ; - } + } for (my $i = 0; $i < $max; $i++) { $s = $eventa->[$i]; $s = "" if(!defined($s)); @@ -244,11 +243,170 @@ EventCheckDoif($$$$) return 0; } +sub +AggrIntDoIf +{ + my ($hash,$modeType,$device,$reading,$cond,$default)=@_; + my $num=0; + my $value=""; + my $sum=0; + my $average; + my $extrem; + my $name; + my $err; + my $ret; + my $result; + my @devices; + my $group; + my $room; + my $STATE; + my $TYPE; + my $warning=0; + my $mode=substr($modeType,0,1); + my $type; + my $dec; + my $place; + my $number; + + if ($modeType =~ /.(sum|average|max|min)?[:]?(d(\d)?)?/) { + $type = (defined $1)? $1 : ""; + $dec= $2; + $place= $3; + } + + if (defined $default) { + if ($default =~ /^"(.*)"$/) { + $default = $1; + } else { + $default=EvalValueDoIf($hash,"default",$default); + } + } + foreach my $name (($device eq "") ? keys %defs:grep {/$device/} keys %defs) { + next if($attr{$name} && $attr{$name}{ignore}); + $value=""; + $number=""; + if ($reading) { + if (defined $defs{$name}{READINGS}{$reading}) { + $value=$defs{$name}{READINGS}{$reading}{VAL}; + $number = ($value =~ /(-?\d+(\.\d+)?)/ ? $1 : 0); + } else { + next; + } + } + if ($cond) { + if ($cond =~ /^"(.*)"$/) { + if (defined $defs{$name}{READINGS}{$reading}) { + $ret=($value =~ /$1/); + } + } else { + $_=$value; + $STATE=Value($name); + $TYPE=$defs{$name}{TYPE}; + $group=AttrVal($name,"group",""); + $room=AttrVal($name,"room",""); + $lastWarningMsg=""; + $ret = eval $cond; + if ($@) { + $@ =~ s/^(.*) at \(eval.*\)(.*)$/$1,$2/; + if (defined $hash) { + Log3 ($hash->{NAME},3 , "$hash->{NAME}: aggregate function: error in condition: $cond, $@"); + } + return("error in aggregate function: ".$@); + } + if ($lastWarningMsg) { + $warning=1; + $lastWarningMsg =~ s/^(.*) at \(eval.*$/$1/; + Log3 ($hash->{NAME},3 , "$hash->{NAME}: aggregate function: warning in condition: $cond, Device: $name"); + readingsSingleUpdate ($hash, "warning_aggr", "condition: $cond , device: $name, $lastWarningMsg",0); + } + $lastWarningMsg=""; + } + } else { + $ret=1; + } + if ($ret) { + if ($type eq ""){ + $num++; + push (@devices,$name); + } elsif (defined $value) { + if ($type eq "sum" or $type eq "average") { + $num++; + push (@devices,$name); + $sum+=$number; + } elsif ($type eq "max") { + if (!defined $extrem or $number>$extrem) { + $extrem=$number; + @devices=($name); + } + } elsif ($type eq "min") { + if (!defined $extrem or $number<$extrem) { + $extrem=$number; + @devices=($name); + } + } + } + } + } + + delete ($defs{$hash->{NAME}}{READINGS}{warning_aggr}) if ($warning==0); + + if ($type eq "max" or $type eq "min") { + $extrem=0 if (!defined $extrem); + $result=$extrem; + } elsif ($type eq "sum") { + $result= $sum; + } elsif ($type eq "average") { + if ($num>0) { + $result=($sum/$num) + } + } else { + $result=$num; + } + if ($mode eq "#") { + if (defined $dec) { + $result = ($result =~ /(-?\d+(\.\d+)?)/ ? $1 : 0); + $result = round ($result,$place) if (defined $place); + } + if ($num==0 and defined $default) { + return ($default); + } else { + return ($result); + } + } elsif ($mode eq "@") { + if ($num==0 and defined $default) { + @devices =($default); + } + return (sort @devices); + } + return 0; +} + +sub +AggrDoIf +{ + my ($modeType,$device,$reading,$cond,$default)=@_; + return (AggrIntDoIf(undef,$modeType,$device,$reading,$cond,$default)); +} + +sub +AggregateDoIf +{ + my ($hash,$modeType,$device,$reading,$cond,$default)=@_; + my $mode=substr($modeType,0,1); + my $type=substr($modeType,1); + if ($mode eq "#") { + return (AggrIntDoIf($hash,$modeType,$device,$reading,$cond,$default)); + } elsif ($mode eq "@") { + return (join (",",AggrIntDoIf($hash,$modeType,$device,$reading,$cond,$default))); + } + return (""); +} + sub EventDoIf { my ($n,$hash,$NotifyExp,$check,$filter,$output,$default)=@_; - + my $dev=$hash->{helper}{triggerDev}; my $eventa=$hash->{helper}{triggerEvents}; if ($check) { @@ -273,7 +431,7 @@ EventDoIf my $ret = 0; if ($NotifyExp eq "") { return 1 if (!defined $filter); - } + } my $s; my $found; my $element; @@ -292,7 +450,7 @@ EventDoIf Log3 ($hash->{NAME},4 , "$hash->{NAME}: $@"); readingsSingleUpdate ($hash, "error", $@,0); return(undef); - } + } } return ($element); } @@ -315,7 +473,7 @@ EventDoIf sub InternalDoIf -{ +{ my ($hash,$name,$internal,$default,$regExp,$output)=@_; $default=AttrVal($hash->{NAME},'notexist','') if (!defined $default); @@ -359,7 +517,7 @@ sub ReadingValDoIf { my ($hash,$name,$reading,$default,$regExp,$output)=@_; - + $default=AttrVal($hash->{NAME},'notexist','') if (!defined $default); $output='' if (!defined $output); $regExp='' if (!defined $regExp); @@ -371,11 +529,18 @@ ReadingValDoIf my $r; my $element; return ($default) if (!defined $defs{$name}); - return ($default) if (!defined $defs{$name}{READINGS}{$reading}{VAL}); + return ($default) if (!defined $defs{$name}{READINGS}); + return ($default) if (!defined $defs{$name}{READINGS}{$reading}); $r=$defs{$name}{READINGS}{$reading}{VAL}; $r="" if (!defined($r)); if ($regExp) { - "" =~ /()()()()()()()()()/; #reset $1, $2.... + if ($regExp =~ /^d(\d)?/) { + my $round=$1; + $r = ($r =~ /(-?\d+(\.\d+)?)/ ? $1 : 0); + $r = round ($r,$round) if (defined $round); + $regExp="(.*)"; + } + "" =~ /()()()()()()()()()/; #reset $1, $2... $element = ($r =~ /$regExp/) ? $1 : ""; if ($output) { $element= eval $output; @@ -383,7 +548,7 @@ ReadingValDoIf Log3 ($hash->{NAME},4 , "$hash->{NAME}: $@"); readingsSingleUpdate ($hash, "error", $@,0); return(undef); - } + } } } else { $element=$r; @@ -425,6 +590,57 @@ EvalAllDoIf($$) return ($cmd,""); } +sub ReplaceAggregateDoIf($$$) +{ + my ($hash,$block,$condition) = @_; + my $exp; + my $nameExp; + my $notifyExp; + my $match; + my $reading; + my $aggrType; + my $default; + + ($block,$default)=SplitDoIf(",",$block); + + if ($block =~ /^([^"]*)(.*)/) { + $aggrType=$1; + $block=$2; + } + + ($exp,$reading,$match)=SplitDoIf(":",$block); + if ($exp =~ /^"(.*)"/){ + $exp=$1; + if ($exp =~ /([^\:]*):(.*)/) { + $nameExp=$1; + $notifyExp=$2; + } else { + $nameExp=$exp; + } + } + $nameExp="" if (!defined $nameExp); + $notifyExp="" if (!defined $notifyExp); + + if (defined $default) { + $match="" if (!defined $match); + $block="AggregateDoIf(".'$hash'.",'$aggrType','$nameExp','$reading','$match','$default')"; + } elsif (defined $match) { + $block="AggregateDoIf(".'$hash'.",'$aggrType','$nameExp','$reading','$match')"; + } elsif (defined $reading) { + $block="AggregateDoIf(".'$hash'.",'$aggrType','$nameExp','$reading')"; + } else { + $block="AggregateDoIf(".'$hash'.",'$aggrType','$nameExp')"; + } + + if (!($condition >= 0)) { + my $ret = eval $block; + return($block." ",$@) if ($@); + $block=$ret; + } + return ($block,undef); +} + + sub ReplaceEventDoIf($) { my ($block) = @_; @@ -467,11 +683,11 @@ sub ReplaceEventDoIf($) if (defined $filter) { return ($block,"default value must be defined") } else { - $block="EventDoIf('$nameExp',".'$hash,'."'$notifyExp',0)"; + $block="EventDoIf('$nameExp',".'$hash,'."'$notifyExp',0)"; return ($block,undef); - } + } } - $block="EventDoIf('$nameExp',".'$hash,'."'$notifyExp',0,'$filter','$output','$default')"; + $block="EventDoIf('$nameExp',".'$hash,'."'$notifyExp',0,'$filter','$output','$default')"; return ($block,undef); } @@ -490,7 +706,7 @@ sub ReplaceReadingDoIf($) my $default; my $param=""; - + ($exp,$default)=SplitDoIf(",",$element); $default="" if (!defined($default)); @@ -525,16 +741,16 @@ sub ReplaceReadingDoIf($) $regExp=$1; $output=$2; return ($regExp,"no round brackets in regular expression") if ($regExp !~ /.*\(.*\)/); - } elsif ($format =~ /^d[^:]*(?::(.*))?/) { - $regExp = '(-?\d+(\.\d+)?)'; - $output=$1; + } elsif ($format =~ /^(d[^:]*)(?::(.*))?/) { + $regExp =$1; + $output=$2; } else { return($format,"unknown expression format"); - } - } + } + } $output="" if (!defined($output)); - + if ($output) { $param=",'$default','$regExp','$output'"; } elsif ($regExp) { @@ -568,7 +784,7 @@ sub ReplaceReadingEvalDoIf($$$) my $ret = eval $block; return($block." ",$@) if ($@); $block=$ret; - } + } return ($block,"",$device,$reading,$internal); } @@ -583,11 +799,11 @@ sub AddItemDoIf($$) return $items; } -sub AddRegexpTriggerDoIf($$$) +sub AddRegexpTriggerDoIf($$$) { my ($hash,$regexp,$condition)= @_; my $max_regexp=keys %{$hash->{regexp}{$condition}}; - for (my $i=0; $i<$max_regexp;$i++) + for (my $i=0; $i<$max_regexp;$i++) { if ($hash->{regexp}{$condition}{$i} eq $regexp) { return; @@ -595,7 +811,7 @@ sub AddRegexpTriggerDoIf($$$) } $hash->{regexp}{$condition}{$max_regexp}=$regexp; $max_regexp=keys %{$hash->{regexp}{all}}; - for (my $i=0; $i<$max_regexp;$i++) + for (my $i=0; $i<$max_regexp;$i++) { if ($hash->{regexp}{all}{$i} eq $regexp) { return; @@ -604,6 +820,21 @@ sub AddRegexpTriggerDoIf($$$) $hash->{regexp}{all}{$max_regexp}=$regexp; } +sub AddRegexpStateDoIf($$) +{ + my ($hash,$regexp)= @_; + my $max_regexp=keys %{$hash->{state}{STATE}}; + for (my $i=0; $i<$max_regexp;$i++) + { + if ($hash->{state}{STATE}{$i} eq $regexp) { + return; + } + } + $hash->{state}{STATE}{$max_regexp}=$regexp; +} + + + sub ReplaceAllReadingsDoIf($$$$) { my ($hash,$tailBlock,$condition,$eval)= @_; @@ -622,27 +853,39 @@ sub ReplaceAllReadingsDoIf($$$$) my $trigger=1; if (!defined $tailBlock) { return ("",""); - } + } $tailBlock =~ s/\$SELF/$hash->{NAME}/g; while ($tailBlock ne "") { - $trigger=1; ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\[\]]'); return ($block,$err) if ($err); if ($block ne "") { - if ($block =~ /^"([^"]*)"/){ - if ($condition>=0) { - ($block,$err)=ReplaceEventDoIf($block); - return ($block,$err) if ($err); - AddRegexpTriggerDoIf($hash,$1,$condition); - $event=1; - } else { - $block="[".$block."]"; - } + if (substr($block,0,1) eq "?") { + $block=substr($block,1); + $trigger=0; } else { - if (substr($block,0,1) eq "?") { - $block=substr($block,1); - $trigger=0; + $trigger=1; + } + if ($block =~ /^(?:(?:#|@)[^"]*)"([^"]*)"/) { + ($block,$err)=ReplaceAggregateDoIf($hash,$block,$condition); + return ($block,$err) if ($err); + if ($trigger) { + if ($condition >= 0) { + AddRegexpTriggerDoIf($hash,$1,$condition); + $event=1; + } elsif ($condition == -2) { + AddRegexpStateDoIf($hash,$1); + } } + } elsif ($block =~ /^"([^"]*)"/) { + if ($condition>=0) { + ($block,$err)=ReplaceEventDoIf($block); + return ($block,$err) if ($err); + AddRegexpTriggerDoIf($hash,$1,$condition); + $event=1; + } else { + $block="[".$block."]"; + } + } else { $trigger=0 if (substr($block,0,1) eq "\$"); if ($block =~ /^\$?[a-z0-9._]*[a-z._]+[a-z0-9._]*($|:.+$|,.+$)/i) { ($block,$err,$device,$reading,$internal)=ReplaceReadingEvalDoIf($hash,$block,$eval); @@ -658,7 +901,7 @@ sub ReplaceAllReadingsDoIf($$$$) $hash->{readings}{all} = AddItemDoIf($hash->{readings}{all},"$device:$reading") if (defined ($reading) and $trigger); $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)); - + } elsif ($condition == -2) { $hash->{state}{device} = AddItemDoIf($hash->{state}{device},$device); #if ($device ne $hash->{NAME}); } elsif ($condition == -3) { @@ -703,7 +946,7 @@ ParseCommandsDoIf($$$) } while ($tailBlock ne "") { if ($tailBlock=~ /^\s*\{/) { # perl block - ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\{\}]'); + ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\{\}]'); return ($currentBlock,$err) if ($err); if ($currentBlock ne "") { ($currentBlock,$err)=ReplaceAllReadingsDoIf($hash,$currentBlock,-1,$eval); @@ -729,13 +972,13 @@ ParseCommandsDoIf($$$) } $currentBlock=$ifcmd; } else { - if ($tailBlock =~ /^\s*\(/) { # remove bracket - ($beginning,$currentBlock,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\(\)]'); + 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); } else { ($currentBlock,$tailBlock)=GetCommandDoIf(',',$tailBlock); - } + } if ($currentBlock ne "") { ($currentBlock,$err)=ReplaceAllReadingsDoIf($hash,$currentBlock,-1,$eval); return ($currentBlock,$err) if ($err); @@ -768,7 +1011,7 @@ sub DOIF_weekdays($$) for (my $i=0;$i<@days;$i++) { $weekdays =~ s/$days[$i]/$i/; - } + } return($weekdays); } @@ -790,34 +1033,34 @@ DOIF_CheckTimers($$$$) my ($hash,$timer,$condition,$trigger)=@_; $timer =~ s/\s//g; while ($timer ne "") { - if ($timer=~ /^\+\(/) { - ($beginning,$time,$err,$timer)=GetBlockDoIf($timer,'[\(\)]'); + 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,'[\(\)]'); + } 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,'[\{\}]'); + } 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,'[\[\]]'); + } 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,'[\[\]]'); + } elsif ($timer=~ /^\[/) { + ($beginning,$time,$err,$timer)=GetBlockDoIf($timer,'[\[\]]'); return ($time,$err) if ($err); $time="[".$time."]"; ($result,$err)=ReplaceAllReadingsDoIf($hash,$time,-3,0); @@ -841,8 +1084,8 @@ DOIF_CheckTimers($$$$) } else { return ($timer,"wrong time format"); } - } - } + } + } $days = "" if (!defined ($days)); for (my $j=0; $j<$i;$j++) { $nr=$nrs[$j]; @@ -853,14 +1096,14 @@ DOIF_CheckTimers($$$$) $hash->{timeCond}{$nr}=$condition; $hash->{days}{$nr}=$days if ($days ne ""); if ($init_done) { - DOIF_SetTimer($hash,"DOIF_TimerTrigger",$nr); + DOIF_SetTimer($hash,"DOIF_TimerTrigger",$nr); #$err=(DOIF_SetTimer($hash,"DOIF_TimerTrigger",$nr)); #return($hash->{time}{$nr},$err) if ($err); } $hash->{timers}{$condition}.=" $nr " if ($trigger); } if ($i == 2) { - if ($days eq "") { + if ($days eq "") { $block='DOIF_time($hash,'.$nrs[0].','.$nrs[1].',$wday,$hms)'; } else { $block='DOIF_time($hash,'.$nrs[0].','.$nrs[1].',$wday,$hms,"'.$days.'")'; @@ -868,11 +1111,11 @@ DOIF_CheckTimers($$$$) $hash->{interval}{$nrs[0]}=-1; $hash->{interval}{$nrs[1]}=$nrs[0]; } else { - if ($days eq "") { + if ($days eq "") { $block='DOIF_time_once($hash,'.$nrs[0].',$wday)'; } else { $block='DOIF_time_once($hash,'.$nrs[0].',$wday,"'.$days.'")'; - } + } } return ($block,""); } @@ -900,22 +1143,22 @@ DOIF_time my $we=DOIF_we($wday); if ($end gt $begin) { if ($hms ge $begin and $hms lt $end) { - $ret=1; - } + $ret=1; + } } else { if ($hms ge $begin) { $ret=1; } elsif ($hms lt $end) { $wday=6 if ($wday-- == 0); $we=DOIF_we($wday); - $ret=1; - } + $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 @@ -936,8 +1179,8 @@ DOIF_time_once if ($flag) { return 1 if ($days eq "" or $days =~ /$wday/ or ($days =~ /7/ and $we) or ($days =~ /8/ and !$we)); } - return 0; -} + return 0; +} ############################ sub @@ -991,7 +1234,7 @@ DOIF_SetState($$$$$) delete ($defs{$hash->{NAME}}{READINGS}{error}); } } - + # if ($state and !defined $hash->{do}{$nr}{$subnr+1}) { if ($state) { my $stateblock='\['.$pn.'\]'; @@ -1000,7 +1243,7 @@ DOIF_SetState($$$$$) } else { $state=$cmd; } - + readingsBulkUpdate($hash, "state", $state); readingsEndUpdate ($hash, 1); } @@ -1052,7 +1295,7 @@ DOIF_CheckCond($$) # ($dev,$reading)=(split(":",$devReading)); # return (-1,"device does not exist: [$dev:$reading]") if ($devReading and !defined ($defs{$dev})); # return (-1,"reading does not exist: [$dev:$reading]") if ($devReading and !defined($defs{$dev}{READINGS}{$reading}{VAL})); - #} + #} } } if (defined ($hash->{internals}{$condition})) { @@ -1099,7 +1342,7 @@ DOIF_CheckCond($$) } $lastWarningMsg=""; $cmdFromAnalyze = undef; - + return ($ret,$err); } @@ -1115,7 +1358,7 @@ DOIF_cmd ($$$$) my $err=""; my $repeatnr; my $last_cmd=ReadingsVal($pn,"cmd_nr",0)-1; - + my @cmdpause=SplitDoIf(':',AttrVal($pn,"cmdpause","")); my @sleeptimer=SplitDoIf(':',AttrVal($pn,"repeatcmd","")); my ($seconds, $microseconds) = gettimeofday(); @@ -1161,13 +1404,13 @@ DOIF_cmd ($$$$) readingsSingleUpdate ($hash, "waitsame", $cmd_nr,1); return undef; } - } + } delete ($defs{$hash->{NAME}}{READINGS}{waitsame}); } } - if ($hash->{do}{$nr}{$subnr}) { + if ($hash->{do}{$nr}{$subnr}) { $cmd=$hash->{do}{$nr}{$subnr}; - + my $eventa=$hash->{helper}{triggerEvents}; my $events=""; if ($eventa) { @@ -1212,7 +1455,7 @@ sub CheckiTimerDoIf($$$) my $found; return 1 if ($itimer =~ /\[$device(\]|,.+\])/); for (my $j = 0; $j < $max; $j++) { - if ($eventa->[$j] =~ "^(.+): ") { + if ($eventa->[$j] =~ "^(.+): ") { $found = ($itimer =~ /\[$device:$1(\]|:.+\]|,.+\])/); if ($found) { return 1; @@ -1232,7 +1475,7 @@ sub CheckReadingDoIf($$) my $found=0; my $device; my $reading; - + if (!defined $readings) { return 1; } @@ -1254,20 +1497,25 @@ sub CheckReadingDoIf($$) sub CheckRegexpDoIf($$$$) { - my ($hash,$name,$eventa,$condition)=@_; - my $cond=($condition == -1) ? "all" : $condition; - my $max_regexp=keys %{$hash->{regexp}{$cond}}; + my ($hash,$name,$eventa,$cond)=@_; + my $type; + if ($cond eq "STATE") { + $type="state"; + } else { + $type="regexp"; + } + my $max_regexp=keys %{$hash->{$type}{$cond}}; my $c; my $nameExp; my $notifyExp; - - for (my $i=0; $i<$max_regexp;$i++) + + for (my $i=0; $i<$max_regexp;$i++) { - if ($hash->{regexp}{$cond}{$i} =~ /([^\:]*):(.*)/) { + if ($hash->{$type}{$cond}{$i} =~ /([^\:]*):(.*)/) { $nameExp=$1; $notifyExp=$2; } else { - $nameExp=$hash->{regexp}{$cond}{$i}; + $nameExp=$hash->{$type}{$cond}{$i}; } $nameExp="" if (!$nameExp); $notifyExp="" if (!$notifyExp); @@ -1276,9 +1524,9 @@ sub CheckRegexpDoIf($$$$) my $events=""; if ($eventa) { $events=join(",",@{$eventa}); - } + } if ($notifyExp eq "") { - if ($cond ne "all") { + if ($cond ne "all" and $cond ne "STATE") { $c=$cond+1; readingsSingleUpdate ($hash, "matched_event_c".$c."_".($i+1),"$events",0); } @@ -1292,7 +1540,7 @@ sub CheckRegexpDoIf($$$$) $s = "" if(!defined($s)); $found = ($s =~ m/$notifyExp/); if ($found) { - if ($cond ne "all") { + if ($cond ne "all" and $cond ne "STATE") { $c=$cond+1; readingsSingleUpdate ($hash, "matched_event_c".$c."_".($i+1),$s,0); } @@ -1302,7 +1550,7 @@ sub CheckRegexpDoIf($$$$) } } return 0; -} +} sub DOIF_Trigger ($$) @@ -1335,7 +1583,7 @@ DOIF_Trigger ($$) $found=1; $timerNr=$j; last; - } + } } } next if (!$found and AttrVal($pn, "checkall", 0) !~ "1|all|timer"); @@ -1345,7 +1593,7 @@ DOIF_Trigger ($$) $hash->{helper}{triggerDev}=""; $hash->{helper}{event}=$event; } else { #event - if (!CheckRegexpDoIf($hash, $device, $hash->{helper}{triggerEvents}, $i)) { + if (!CheckRegexpDoIf($hash, $device, $hash->{helper}{triggerEvents}, $i)) { if (AttrVal($pn, "checkall", 0) !~ "1|all|event") { next if (!defined ($hash->{devices}{$i})); next if ($hash->{devices}{$i} !~ / $device /); @@ -1406,7 +1654,7 @@ DOIF_Notify($$) my $err; my $eventa; my $eventas; - $eventa = deviceEvents($dev, AttrVal($pn, "addStateEvent", 0)); + $eventa = deviceEvents($dev, AttrVal($pn, "addStateEvent", 0)); $eventas = deviceEvents($dev, 1); if ($dev->{NAME} eq "global" and (EventCheckDoif($dev->{NAME},"global",$eventa,"INITIALIZED") or EventCheckDoif($dev->{NAME},"global",$eventa,"REREADCFG"))) { @@ -1429,23 +1677,23 @@ DOIF_Notify($$) readingsEndUpdate($hash, 0); } } - - + + return "" if (!$hash->{helper}{globalinit}); - return "" if (!$hash->{itimer}{all} and !$hash->{devices}{all} and !$hash->{state}{device} and !$hash->{regexp}{all}); + return "" if (!$hash->{itimer}{all} and !$hash->{devices}{all} and !$hash->{state}{device} and !keys %{$hash->{state}{STATE}} and !keys %{$hash->{regexp}{all}}); if (($hash->{itimer}{all}) and $hash->{itimer}{all} =~ / $dev->{NAME} /) { - for (my $j=0; $j<$hash->{helper}{last_timer};$j++) { + for (my $j=0; $j<$hash->{helper}{last_timer};$j++) { if (CheckiTimerDoIf ($dev->{NAME},$hash->{time}{$j},$eventas)) { DOIF_SetTimer($hash,"DOIF_TimerTrigger",$j); } } } - + return "" if (defined $hash->{helper}{cur_cmd_nr}); return "" if (ReadingsVal($pn,"mode","") eq "disabled"); - - if ((($hash->{devices}{all}) and $hash->{devices}{all} =~ / $dev->{NAME} /) or CheckRegexpDoIf($hash,$dev->{NAME},$eventa,-1)){ + + if ((($hash->{devices}{all}) and $hash->{devices}{all} =~ / $dev->{NAME} /) or CheckRegexpDoIf($hash,$dev->{NAME},$eventa,"all") or CheckRegexpDoIf($hash,$dev->{NAME},$eventa,"STATE")){ $hash->{helper}{cur_cmd_nr}="Trigger $dev->{NAME}" if (AttrVal($hash->{NAME},"selftrigger","") ne "all"); readingsSingleUpdate ($hash, "Device",$dev->{NAME},0); #my $events = deviceEvents($dev, AttrVal($dev->{NAME}, "addStateEvent", 0)); @@ -1453,7 +1701,7 @@ DOIF_Notify($$) if ($hash->{readings}{all}) { foreach my $item (split(/ /,$hash->{readings}{all})) { ($device,$reading)=(split(":",$item)); - if ($item and $device eq $dev->{NAME} and defined ($defs{$device}{READINGS}{$reading})) { + if ($item and $device eq $dev->{NAME} and defined ($defs{$device}{READINGS}{$reading})) { if (!AttrVal($pn, "checkReadingEvent", 0) or CheckReadingDoIf ("$item",$eventas)) { readingsSingleUpdate ($hash, "e_".$dev->{NAME}."_".$reading,$defs{$device}{READINGS}{$reading}{VAL},0); } @@ -1477,7 +1725,7 @@ DOIF_Notify($$) $hash->{helper}{event}=join(",",@{$eventa}); $ret=DOIF_Trigger($hash,$dev->{NAME}); } - if (($hash->{state}{device}) and $hash->{state}{device} =~ / $dev->{NAME} / and !$ret) { + if ((($hash->{state}{device}) and $hash->{state}{device} =~ / $dev->{NAME} / or CheckRegexpDoIf($hash,$dev->{NAME},$eventa,"STATE")) and !$ret) { $hash->{helper}{cur_cmd_nr}="Trigger $dev->{NAME}" if (AttrVal($hash->{NAME},"selftrigger","") ne "all"); $hash->{helper}{triggerEvents}=$eventa; $hash->{helper}{triggerEventsState}=$eventas; @@ -1487,8 +1735,8 @@ DOIF_Notify($$) } delete $hash->{helper}{cur_cmd_nr}; return undef; -} - +} + sub DOIF_TimerTrigger ($) { @@ -1497,7 +1745,7 @@ DOIF_TimerTrigger ($) my $pn = $hash->{NAME}; my $localtime=${$timer}->{localtime}; delete $hash->{triggertime}{$localtime}; - + my $ret; my ($now, $microseconds) = gettimeofday(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now); @@ -1519,7 +1767,7 @@ DOIF_TimerTrigger ($) } } } - $ret=DOIF_Trigger ($hash,"") if (ReadingsVal($pn,"mode","") ne "disabled"); + $ret=DOIF_Trigger ($hash,"") if (ReadingsVal($pn,"mode","") ne "disabled"); for (my $j=0; $j<$hash->{helper}{last_timer};$j++) { $hash->{timer}{$j}=0; if (defined $hash->{localtime}{$j} and $hash->{localtime}{$j} == $localtime) { @@ -1627,11 +1875,11 @@ DOIF_CalcTime($$) $relGlobal=1; #$pos=pos($block); $block=substr($block,1); - } + } if ($block =~ /^\(/) { ($beginning,$tailBlock,$err,$tailBlock)=GetBlockDoIf($block,'[\(\)]'); return ($tailBlock,$err) if ($err); - } else { + } else { if ($block =~ /^\[/) { ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($block,'[\[\]]'); return ($block,$err) if ($err); @@ -1697,24 +1945,24 @@ DOIF_SetTimer($$$) if (!$found) { RemoveInternalTimer(\$hash->{triggertime}{$old_lt}); delete ($hash->{triggertime}{$old_lt}); - } + } } my ($second,$err, $rel)=DOIF_CalcTime($hash,$timeStr); my $timernr=sprintf("timer_%02d_c%02d",($nr+1),($cond+1)); if ($err) - { + { readingsSingleUpdate ($hash,$timernr,"error: ".$err,AttrVal($hash->{NAME},"timerevent","")?1:0); Log3 $hash->{NAME},4 , "$hash->{NAME} ".$timernr." error: ".$err; #RemoveInternalTimer($timer); #$hash->{realtime}{$nr} = "00:00:00" if (!defined $hash->{realtime}{$nr}); return $err; } - + if ($second < 0 and $rel) { readingsSingleUpdate ($hash,$timernr,"time offset: $second, negativ offset is not allowed",AttrVal($hash->{NAME},"timerevent","")?1:0); return($timernr,"time offset: $second, negativ offset is not allowed"); } - + my ($now, $microseconds) = gettimeofday(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now); my $isdst_now=$isdst; @@ -1726,7 +1974,7 @@ DOIF_SetTimer($$$) } else { $next_time = $midnight+$second; } - + if ($second <= $sec_today and !$rel) { $next_time+=86400; ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($next_time); @@ -1738,7 +1986,7 @@ DOIF_SetTimer($$$) } } } - + 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,$timernr,$next_time_str,AttrVal($hash->{NAME},"timerevent","")?1:0); @@ -1748,7 +1996,7 @@ DOIF_SetTimer($$$) $hash->{triggertime}{$next_time}{hash}=$hash; $hash->{triggertime}{$next_time}{localtime}=$next_time; InternalTimer($next_time, $func, \$hash->{triggertime}{$next_time}, 0); - } + } return undef; } @@ -1761,7 +2009,7 @@ DOIF_SetSleepTimer($$$$$$$) my @waitdel=SplitDoIf(':',AttrVal($pn,"waitdel","")); my @waitdelsubnr=SplitDoIf(',',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}); @@ -1770,25 +2018,25 @@ DOIF_SetSleepTimer($$$$$$$) $subnr=$hash->{helper}{sleepsubtimer} if ($hash->{helper}{sleepsubtimer}!=-1 and $sleeptimer == $nr); 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","") + } + 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","") or defined($repeatcmd))) { my $sleeptime=0; if (defined ($repeatcmd)) { $sleeptime=$repeatcmd; } else { my @sleeptimer=SplitDoIf(':',AttrVal($pn,"wait","")); - if ($waitdelsubnr[$subnr]) { + if ($waitdelsubnr[$subnr]) { $sleeptime = $waitdelsubnr[$subnr]; } else { my @sleepsubtimer=SplitDoIf(',',defined $sleeptimer[$nr]? $sleeptimer[$nr]: ""); @@ -1817,7 +2065,7 @@ DOIF_SetSleepTimer($$$$$$$) } elsif (defined($repeatcmd)){ return 0; } else { - return 1; + return 1; } } else { return 0; @@ -1871,7 +2119,7 @@ CmdDoIf($$) $tail =~ s/(##.*\n)|(##.*$)|\n/ /g; $tail =~ s/\$SELF/$hash->{NAME}/g; } - + #def modify if ($init_done) { @@ -1883,19 +2131,19 @@ CmdDoIf($$) readingsEndUpdate($hash, 1); $hash->{helper}{globalinit}=1; } - + $hash->{helper}{last_timer}=0; $hash->{helper}{sleeptimer}=-1; return("","") if ($tail =~ /^ *$/); - + while ($tail ne "") { return($tail, "no left bracket of condition") if ($tail !~ /^ *\(/); #condition ($beginning,$cond,$err,$tail)=GetBlockDoIf($tail,'[\(\)]'); - return ($cond,$err) if ($err); + return ($cond,$err) if ($err); ($cond,$err)=ReplaceAllReadingsDoIf($hash,$cond,$i,0); - return ($cond,$err) if ($err); + return ($cond,$err) if ($err); return ($tail,"no condition") if ($cond eq ""); $hash->{condition}{$i}=$cond; #DOIF @@ -1914,8 +2162,8 @@ CmdDoIf($$) 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 + } + $hash->{do}{$i}{0}=$if_cmd_ori if ($j==0); #do without brackets $last_do=$i; $tail =~ s/^\s*$//g; if (length($tail)) { @@ -1990,11 +2238,11 @@ DOIF_Attr(@) DOIF_delAll ($hash); readingsSingleUpdate ($hash,"state","deactivated",1); } elsif($a[0] eq "set" && $a[2] eq "state") { - delete ($hash->{state}{device}); + delete $hash->{state}; my ($block,$err)=ReplaceAllReadingsDoIf($hash,$a[3],-2,0); return $err if ($err); } elsif($a[0] eq "del" && $a[2] eq "state") { - delete ($hash->{state}{device}); + delete $hash->{state}; } elsif($a[0] eq "set" && $a[2] eq "wait") { RemoveInternalTimer($hash); #delete ($defs{$hash->{NAME}}{READINGS}{wait_timer}); @@ -2110,7 +2358,7 @@ DOIF_Set($@) =pod =item helper -=item summary universal module, it works event- and time-controlled +=item summary universal module, it works event- and time-controlled =item summary_DE universelles Modul, welches ereignis- und zeitgesteuert Anweisungen ausführt =begin html @@ -2208,7 +2456,7 @@ Kombinierte Ereignis- und Zeitsteuerung

define di_lamp DOIF ([06:00-09:00] and [sensor:brightness] < 40) (set lamp on) DOELSE (set lamp off)

-Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgelesen werden: +Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgelesen werden: Erste Schritte mit DOIF


@@ -2220,6 +2468,8 @@ Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgel Teilausdrücke abfragen
Ereignissteuerung über Auswertung von Events
Angaben im Ausführungsteil
+ Filtern nach Ausdrücken mit Ausgabeformatierung
+ Aggregieren von Werten
Zeitsteuerung
Relative Zeitangaben
Zeitangaben nach Zeitraster ausgerichtet
@@ -2234,7 +2484,6 @@ Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgel Nutzung von Readings, Status oder Internals im Ausführungsteil
Berechnungen im Ausführungsteil
Ersatzwert für nicht existierende Readings oder Status
- Filtern nach Ausdrücken mit Ausgabeformatierung
Verzögerungen
Verzögerungen von Timern
Zurücksetzen des Waittimers für das gleiche Kommando
@@ -2253,7 +2502,7 @@ Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgel Reine Statusanzeige ohne Ausführung von Befehlen
Anpassung des Status mit Hilfe des Attributes state
Vorbelegung des Status mit Initialisierung nach dem Neustart mit dem Attribut initialize
- Deaktivieren des Moduls
+ Deaktivieren des Moduls
Bedingungslose Ausführen von Befehlszweigen
Initialisieren des Moduls
Weitere Anwendungsbeispiele
@@ -2345,7 +2594,7 @@ Das Modul wird getriggert, sobald das angegebene Device hier "remotecontrol" ein Ausgewertet wird hier der Zustand des Status von remotecontrol nicht das Event selbst. Die Ausführung erfolgt standardmäßig einmalig nur nach Zustandswechsel des Moduls. Das bedeutet, dass ein mehrmaliges Drücken der Fernbedienung auf "on" nur einmal "set garage on" ausführt. Die nächste mögliche Ausführung ist "set garage off", wenn Fernbedienung "off" liefert. -Wünscht man eine Ausführung des gleichen Befehls mehrfach nacheinander bei jedem Trigger, unabhängig davon welchen Zustand das DOIF-Modul hat, +Wünscht man eine Ausführung des gleichen Befehls mehrfach nacheinander bei jedem Trigger, unabhängig davon welchen Zustand das DOIF-Modul hat, weil z. B. Garage nicht nur über die Fernbedienung geschaltet wird, dann muss man das per "do always"-Attribut angeben:

@@ -2432,7 +2681,7 @@ Rollladen auf Lüften stellen

attr di_air do always

-Hier werden alle Fensterkontakte, die mit dem Device-Namen "window_contact_" beginnen auf "open" oder "tilted" im Event überwacht +Hier werden alle Fensterkontakte, die mit dem Device-Namen "window_contact_" beginnen auf "open" oder "tilted" im Event überwacht und der entsprechende Rollladen mit der gleichen Endung auf Lüften per set shutters_<postfix> 10 gestellt. In diesem Beispiel wird die Möglichkeit genutzt bei FHEM-Befehlen Perlcode innerhalb der Klammern {(...)} einzufügen. Siehe Berechnungen im Ausführungsteil

@@ -2462,7 +2711,7 @@ Batteriewarnung per E-Mail verschicken
    ({DebianMail('yourname@gmail.com', 'FHEM - battery warning from device: $DEVICE')}, setreading $SELF B_$DEVICE low)
DOELSEIF ([":battery: ok"] and [?$SELF:B_$DEVICE] ne "ok")
    (setreading $SELF B_$DEVICE ok)
-
+
attr di_battery do always

Eine aktuelle Übersicht aller Batterie-Status entsteht gleichzeitig in den Readings des di_battery-DOIF-Moduls.
@@ -2490,12 +2739,57 @@ Syntax:

Regex-Filter- und Output-Parameter sind optional. Der Default-Wert ist verpflichtend.

-Die Angaben zum Filter und Output funktionieren, wie die beim Reading-Filter. Siehe: Filtern nach Ausdrücken mit Ausgabeformatierung

+Die Angaben zum Filter und Output funktionieren, wie die beim Reading-Filter. Siehe: Filtern nach Ausdrücken mit Ausgabeformatierung

Wenn kein Filter, wie obigen Beispiel, angegeben wird, so wird intern folgende Regex vorbelegt: "[^\:]*: (.*)" Damit wird der Wert hinter der Readingangabe genommen. Durch eigene Regex-Filter-Angaben kann man beliebige Teile des Events herausfiltern, ggf. über Output formatieren und in der Bedingung entsprechend auswerten, ohne auf Readings zurückgreifen zu müssen.

+ +Filtern nach Ausdrücken mit Ausgabeformatierung   back
+
+Syntax: [<device>:<reading>|<internal>:d<number>|"<regex>":<output>]
+
+d - Der Buchstabe "d" ist ein Synonym für das Filtern nach Dezimalzahlen, es entspricht intern dem regulären Ausdruck "(-?\d+(\.\d+)?)". Ebenfalls lässt sich eine Dezimalzahl auf eine bestimmte Anzahl von Nachkommastellen runden. Dazu wird an das "d" eine Ziffer angehängt. Mit der Angabe d0 wird die Zahl auf ganze Zahlen gerundet.
+<Regex>- Der reguläre Ausdruck muss in Anführungszeichen angegeben werden. Dabei werden Perl-Mechanismen zu regulären Ausdrücken mit Speicherung der Ergebnisse in Variablen $1, $2 usw. genutzt.
+<Output> - ist ein optionaler Parameter, hier können die in den Variablen $1, $2, usw. aus der Regex-Suche gespeicherten Informationen für die Aufbereitung genutzt werden. Sie werden in Anführungszeichen bei Texten oder in Perlfunktionen angegeben. Wird kein Output-Parameter angegeben, so wird automatisch $1 genutzt.
+
+Beispiele:
+
+Es soll aus einem Reading, das z. B. ein Prozentzeichen beinhaltet, nur der Zahlenwert für den Vergleich genutzt werden:
+
+define di_heating DOIF ([adjusting:actuator:d] < 10) (set heating off) DOELSE (set heating on)
+
+Alternativen für die Nutzung der Syntax am Beispiel des Filterns nach Zahlen:
+
+[mydevice:myreading:d]
+entspricht:
+[mydevice:myreading:"(-?\d+(\.\d+)?)"]
+entspricht:
+[mydevice:myreading:"(-?\d+(\.\d+)?)":$1]
+entspricht:
+[mydevice:myreading:"(-?\d+(\.\d+)?)":"$1"]
+entspricht:
+[mydevice:myreading:"(-?\d+(\.\d+)?)":sprintf("%s":$1)]
+
+Es soll die Zahl aus einem Reading auf 3 Nachkommastellen formatiert werden:
+
+[mydevice:myreading:d3]
+
+Es soll aus einem Text eine Zahl herausgefiltert werden und anschließend gerundet auf zwei Nachkommastellen mit der Einheit °C ausgeben werden:
+
+... (set mydummy [mydevice:myreading:d2:"$1 °C"])
+
+Es sollen aus einem Reading der Form "HH:MM:SS" die Stunden, Minuten und Sekunden separieret werden:
+
+[mydevice:myreading:"(\d\d):(\d\d):(\d\d)":"hours: $1, minutes $2, seconds: $3"]
+
+Der Inhalt des Dummys Alarm soll in einem Text eingebunden werden:
+
+[alarm:state:"(.*)":"state of alarm is $1"]
+
+Die Definition von regulären Ausdrücken mit Nutzung der Perl-Variablen $1, $2 usw. kann in der Perldokumentation nachgeschlagen werden.
+
Angaben im Ausführungsteil:   back

@@ -2511,6 +2805,161 @@ Perlcode kann im DEF-Editor wie gewohnt programmiert werden: ...{my $name=
FHEM-Befehle lassen sich mit Perl-Befehlen kombinieren: ... ({system ("wmail Peter is at home")}, set lamp on)

+
+Aggregieren von Werten   back
+
+Mit Hilfe der Aggregationsfunktion können mehrere gleichnamige Readings im System ausgewertet werden, die einem bestimmten Kriterium entsprechen. Sie wird in eckigen Klammern durch ein # (aggregierter Wert) oder @ (Liste der passeden Devices) eingeleitet. +Es kann bestimmt werden: die Anzahl der Readings bzw. Devices, Durchschnittswert, Summe, höchster Wert, niedrigster Wert oder eine Liste der dazugehörigen Devices. +Die Aggregationsfunktion kann in einer DOIF-Bedingungen, im Ausführungsteil oder mit Hilfe des state-Attributs im Status angegeben werden. In der Bedingung und im Status reagiert sie auf Ereignistrigger. Das lässt sich durch ein vorangestelltes Fragezeichen unterbinden. +Die Angabe des Readings kann weggelassen werden, dann wird lediglich nach entsprechenden Devices gesucht.
+
+Syntax:
+
+[<function>:"<regex device>:<regex event>":<reading>:<condition>,<default>]
+
+<function>:
+
+# Anzahl der betroffenen Devices, der folgende Doppelpunkt kann weggelassen werden
+@ kommagetrennte Liste Devices, der folgende Doppelpunkt kann weggelassen werden
+#sum Summe
+#max höchster Wert
+#min niedrigster Wert
+#average Durchschnitt
+@max Device des höchsten Wertes
+@min Device de niedrigsten Wertes
+
+<function>:d<number> optional kann das Ergebnis auf Nachkommastellen gerundet werden, dazu wird an die Funktion getrennt durch einen Doppelpunkt der Buchstabe "d" gefolgt von einer Ziffer (<number>) angegeben. Die Ziffer gibt die Anzahl der Nachkommastellen an.
+
+"<regex Device>:<regex Event>" spezifiziert sowohl die betroffenen Devices, als auch den Ereignistrigger, die Syntax entspricht der DOIF-Syntax für Ereignistrigger.
+Die Angabe <regex Event> ist im Ausführungsteil nicht sinnvoll und sollte weggelassen werden.
+
+<reading> Reading, welches überprüft werden soll
+
+<condition> Aggregations-Bedingung, $_ ist der Platzhalter für den aktuellen Wert des internen Schleifendurchlaufs, Angaben in Anführungszeichen der Art "<value>" entsprechen $_ =~ "<value>" , hier sind alle Perloperatoren möglich.
+
+<default> Default-Wert, falls kein Device gefunden wird, entspricht der Syntax des Default-Wertes bei Readingangaben
+
+<reading>, <condition>, <default> sind optional
+
+Syntax-Beispiele im Ausführungteil
+
+Anzahl der Devices, die mit "window" beginnen:
+
+[#"^window"]
+
+Liste der Devices, die mit "window" beginnen:
+
+[@"^window"]
+
+Liste der Devices, die mit "windows" beginnen und ein Reading "myreading" beinhalten:
+
+[@"^window":myreading]
+
+Liste der Devices, die mit "windows" beginnen und im Status das Wort "open" vorkommt:
+
+[@"^window":state:"open"]
+
+entspricht:
+
+[@"^window":state:$_ =~ "open"] siehe Aggregationsbedingung.
+
+In der Aggregationsbedingung können alle in FHEM definierten Perlfunktionen genutzt werden. Folgende Variablen sind vorbelegt und können ebenfalls benutzt werden:
+
+$_ Inhalt des angegebenen Readings (s.o.)
+$number Nach Zahl gefilteres Reading
+$name Name des Devices
+$TYPE Devices-Typ
+$STATE Status des Devices (nicht das Reading state)
+$room Raum des Devices
+$group Gruppe des Devices
+
+Beispiele für Definition der Aggregationsbedingung <condition>:
+
+Liste der Devices, die mit "rooms" enden und im Reading "temperature" einen Wert größer 20 haben:
+
+[@"rooms$":temperature:$_ > 20]
+
+Liste der Devices im Raum "livingroom", die mit "rooms" enden und im Reading "temperature" einen Wert größer 20 haben:
+
+[@"rooms$":temperature:$_ > 20 and $room eq "livingroom"]
+
+Liste der Devices in der Gruppe "windows", die mit "rooms" enden, deren Status (nicht state-Reading) "on" ist:
+
+[@"rooms$"::$STATE eq "on" and $group eq "windows"]
+
+Liste der Devices, deren state-Reading "on" ist und das Attribut disable nicht auf "1" gesetzt ist:
+
+[@"":state:$_ eq "on" and AttrVal($name,"disable","") ne "1"]
+
+
+Aggregationsangaben in der DOIF-Bedingung reagieren zusätzlich auf Ereignistrigger, hier sollte die regex-Angabe für das Device um eine regex-Angabe für das zu triggernde Event erweitert werden.
+
+Anzahl der Devices, die mit "window" beginnen. Getriggert wird, wenn eine Eventzeile beginnend mit "window" und dem Wort "open" vorkommt:
+
+[#"^window:open"]
+
+Anwendungsbeispiele
+
+Statusanzeige: Offene Fenster:
+
+define di_window DOIF
+
+attr di_window state Offene Fenster: [@"^window:open":state:"open","keine"]

+
+Statusanzeige: Alle Devices, deren Batterie nicht ok ist:
+
+define di_battery DOIF
+
+attr di_battery state [@":battery":battery:$_ ne "ok","alle OK"]

+
+Statusanzeige: Durchschnittstemperatur aller Temperatursensoren in der Gruppe "rooms":
+
+define di_average_temp DOIF
+
+attr di_average_temp state [#average:d2:":temperature":temperature:$group eq "rooms"]

+
+Fenster Status/Meldung:
+
+define di_Fenster DOIF (["^Window:open"])
+(push "Fenster $DEVICE wurde geöffnet. Es sind folgende Fenster offen: [@"^Window":state:"open"]")
+DOELSEIF ([#"^Window:closed":state:"open"] == 0)
+(push "alle Fenster geschlossen")

+
+attr di_Fenster do always
+attr di_Fenster cmdState $DEVICE zuletzt geöffnet|alle geschlossen

+
+Raumtemperatur-Überwachung:
+
+define di_temp DOIF (([08:00] or [20:00]) and [?#"^Rooms":temperature: $_ < 20] != 0)
+ (push "In folgenden Zimmern ist zu kalt [@"^Rooms":temperature:$_ < 20,"keine"]")
+DOELSE
+ (push "alle Zimmmer sind warm")
+
+attr di_temp do always
+attr di_Raumtemp state In folgenden Zimmern ist zu kalt: [@"^Rooms":temperature:$_ < 20,"keine"])

+
+Es soll beim Öffnen eines Fensters eine Meldung über alle geöffneten Fenster erfolgen:
+
+define di_Fenster DOIF (["^Window:open"]) (push "Folgende Fenster: [@"^Window:state:"open"] sind geöffnet")
+
+attr di_Fenster do always
+
+Wenn im Wohnzimmer eine Lampe ausgeschaltet wird, sollen alle anderen Lampen im Wohnzimmer ebenfalls ausgeschaltet werden, die noch an sind:
+
+define di_lamp DOIF (["^lamp_livingroom: off"]) (set [@"^lamp_livingroom":state:"on","defaultdummy"] off)
+
+attr di_lamp DOIF do always

+
+Mit der Angabe des Default-Wertes "defaultdummy", wird verhindert, dass der set-Befehl eine Fehlermeldung liefert, wenn die Device-Liste leer ist. Der angegebene Default-Dummy muss zuvor definiert werden.
+
+Für reine Perlangaben gibt es eine entsprechende Perlfunktion namens AggrDoIf(<function>,<regex Device>,<reading>,<condition>,<default>) diese liefert bei der Angabe @ ein Array statt einer Stringliste, dadurch lässt sie sich gut bei foreach-Schleifen verwenden.
+
+Beispiele
+
+define di_Fenster DOIF (["^Window:open"]) {foreach (AggrDoIf('@','^windows','state','"open"')) {Log3 "di_Fenster",3,"Das Fenster $_ ist noch offen"}}
+
+define di_Temperature DOIF (["^room:temperature"]) {foreach (AggrDoIf('@','^room','temperature','$_ < 15')) {Log3 "di_Temperatur",3,"im Zimmer $_ ist zu kalt"}}
+
Zeitsteuerung   back

@@ -2705,7 +3154,7 @@ Indirekte Zeitangaben können auch als Übergabeparameter für Zeitfunktionen, w
Bei einer Änderung des angegebenen Status oder Readings wird die geänderte Zeit sofort im Modul aktualisiert.

-Angabe eines Readings als Zeitangabe. Beispiel: Schalten anhand eines Twilight-Readings:
+Angabe eines Readings als Zeitangabe. Beispiel: Schalten anhand eines Twilight-Readings:

define di_time DOIF ([[myTwilight:ss_weather]])(set lamp on)

@@ -2738,7 +3187,7 @@ Lampe wird nach Sonnenuntergang zwischen 900 und 1500 (900+600) Sekunden zufäll DOELSEIF ([([23:00]+int(rand(600)))])
    (set lamp off)

-Zeitberechnung können ebenfalls in Zeitintervallen genutzt werden.
+Zeitberechnung können ebenfalls in Zeitintervallen genutzt werden.

Licht soll eine Stunde vor gegebener Zeit eingeschaltet werden und eine Stunde danach wieder ausgehen:

@@ -2759,7 +3208,7 @@ DOELSE
    (set lampe off)

-Ein Änderung des Dummys Fixtime z. B. durch "set Fixtime ...", führt zur sofortiger Neuberechnung der Timer im DOIF-Modul.
+Ein Änderung des Dummys Fixtime z. B. durch "set Fixtime ...", führt zur sofortiger Neuberechnung der Timer im DOIF-Modul.

Für die Zeitberechnung wird der Perlinterpreter benutzt, daher sind für die Berechnung der Zeit keine Grenzen gesetzt.

@@ -2833,51 +3282,6 @@ so lässt sich das über das Attribut notexist bewerkstelligen. Ein
Syntax: attr <DOIF-module> notexist "<default value>"

- -Filtern nach Ausdrücken mit Ausgabeformatierung   back
-
-Syntax: [<Device>:<Reading>|<Internal>:d|"<Regex>":<Output>]
-
-d - Der Buchstabe "d" ist ein Synonym für das Filtern nach Dezimalzahlen, es entspricht intern dem regulären Ausdruck "(-?\d+(\.\d+)?)"
-<Regex>- Der reguläre Ausdruck muss in Anführungszeichen angegeben werden. Dabei werden Perl-Mechanismen zu regulären Ausdrücken mit Speicherung der Ergebnisse in Variablen $1, $2 usw. genutzt.
-<Output> - ist ein optionaler Parameter, hier können die in den Variablen $1, $2, usw. aus der Regex-Suche gespeicherten Informationen für die Aufbereitung genutzt werden. Sie werden in Anführungszeichen bei Texten oder in Perlfunktionen angegeben. Wird kein Output-Parameter angegeben, so wird automatisch $1 genutzt.
-
-Beispiele:
-
-Es soll aus einem Reading, das z. B. ein Prozentzeichen beinhaltet, nur der Zahlenwert für den Vergleich genutzt werden:
-
-define di_heating DOIF ([adjusting:actuator:d] < 10) (set heating off) DOELSE (set heating on)
-
-Alternativen für die Nutzung der Syntax am Beispiel des Filterns nach Zahlen:
-
-[mydevice:myreading:d]
-entspricht:
-[mydevice:myreading:"(-?\d+(\.\d+)?)"]
-entspricht:
-[mydevice:myreading:"(-?\d+(\.\d+)?)":$1]
-entspricht:
-[mydevice:myreading:"(-?\d+(\.\d+)?)":"$1"]
-entspricht:
-[mydevice:myreading:"(-?\d+(\.\d+)?)":sprintf("%s":$1)]
-
-Es soll aus einem Text eine Zahl herausgefiltert werden und anschließend die Einheit °C angehängt werden:
-
-... (set mydummy [mydevice:myreading:d:"$1 °C"])
-
-Es soll die Zahl aus einem Reading auf 2 Nachkommastellen formatiert werden:
-
-[mydevice:myreading:d:sprintf("%.2f",$1)]
-
-Es sollen aus einem Reading der Form "HH:MM:SS" die Stunden, Minuten und Sekunden separieret werden:
-
-[mydevice:myreading:"(\d\d):(\d\d):(\d\d)":"hours: $1, minutes $2, seconds: $3"]
-
-Der Inhalt des Dummys Alarm soll in einem Text eingebunden werden:
-
-[alarm:state:"(.*)":"state of alarm is $1"]
-
-Die Definition von regulären Ausdrücken mit Nutzung der Perl-Variablen $1, $2 usw. kann in der Perldokumentation nachgeschlagen werden.
-
Verzögerungen   back

@@ -2892,7 +3296,7 @@ Beispiel: Bei einer Befehlssequenz, hier: (set lamp1 on, set lamp2 on)(set lamp1 on)(set lamp2 on). Nun kann mit dem wait-Attribut nicht nur für den Beginn der Sequenz, sondern für jeden Klammerblock eine Verzögerung, getrennt mit Komma, definieren werden, hier also: wait 0,1. Damit wird lamp1 sofort, lamp2 eine Sekunde danach geschaltet. Die Verzögerungszeit bezieht sich immer auf den vorherigen Befehl.
-
+
Beispieldefinition bei mehreren DO-Blöcken mit Befehlssequenzen:

DOIF (Bedingung1)
@@ -3251,7 +3655,7 @@ Das ist insb. dann sinnvoll, wenn das System ohne Sicherung der Konfiguration (u Deaktivieren des Moduls   back

Ein DOIF-Modul kann mit Hilfe des Attributes disable, deaktiviert werden. Dabei werden alle Timer und Readings des Moduls gelöscht. -Soll das Modul nur vorübergehend deaktiviert werden, so kann das durch set <DOIF-modul> disable geschehen. +Soll das Modul nur vorübergehend deaktiviert werden, so kann das durch set <DOIF-modul> disable geschehen. Hierbei bleiben alle Timer aktiv, sie werden aktualisiert - das Modul bleibt im Takt, allerding werden keine Befehle ausgeführt. Das Modul braucht mehr Rechenzeit, als wenn es komplett über das Attribut deaktiviert wird. In beiden Fällen bleibt der Zustand nach dem Neustart erhalten, das Modul bleibt deaktiviert.

@@ -3294,6 +3698,7 @@ attr di_lamp setList on off
set di_lamp on entspricht hier set di_lamp cmd_1 und set di_lamp off set di_lamp cmd_2
Zusätzlich führt die Definition von setList zur Ausführung von set di_lamp on/off durch das Anlicken des Lampensymbols wie im vorherigen Beispiel.

+
Weitere Anwendungsbeispiele   back