98_DOIF.pm: aggregate function

git-svn-id: https://svn.fhem.de/fhem/trunk@14088 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Damian 2017-04-23 17:59:06 +00:00
parent 2492d61fea
commit 77115d1cea

View File

@ -61,7 +61,6 @@ DOIF_delAll($)
foreach my $key (keys %{$defs{$hash->{NAME}}{READINGS}}) { 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)"); delete $defs{$hash->{NAME}}{READINGS}{$key} if ($key =~ "^(Device|state|error|warning|cmd|e_|timer_|wait_|matched_|last_cmd|mode)");
} }
} }
######################### #########################
@ -159,10 +158,10 @@ GetCommandDoIf ($$)
sub EvalValueDoIf($$$) sub EvalValueDoIf($$$)
{ {
my ($hash,$attr,$value)=@_; my ($hash,$attr,$value)=@_;
return "" if (!defined($value) or $value eq ""); return "" if (!defined($value) or $value eq "");
my $err=""; my $err="";
my $pn=$hash->{NAME}; my $pn=$hash->{NAME};
$value =~ s/\$SELF/$pn/g; $value =~ s/\$SELF/$pn/g;
($value,$err)=ReplaceAllReadingsDoIf($hash,$value,-1,1); ($value,$err)=ReplaceAllReadingsDoIf($hash,$value,-1,1);
if ($err) { if ($err) {
@ -186,9 +185,9 @@ my $pn=$hash->{NAME};
sub EvalCmdStateDoIf($$) sub EvalCmdStateDoIf($$)
{ {
my ($hash,$state)=@_; my ($hash,$state)=@_;
my $err; my $err;
my $pn=$hash->{NAME}; my $pn=$hash->{NAME};
($state,$err)=ReplaceAllReadingsDoIf($hash,$state,-1,1); ($state,$err)=ReplaceAllReadingsDoIf($hash,$state,-1,1);
if ($err) { if ($err) {
Log3 $pn,4 , "$pn: error in state: $err" if ($err); Log3 $pn,4 , "$pn: error in state: $err" if ($err);
@ -206,18 +205,18 @@ my $pn=$hash->{NAME};
sub sub
SplitDoIf($$) SplitDoIf($$)
{ {
my ($separator,$tailBlock)=@_; my ($separator,$tailBlock)=@_;
my @commands; my @commands;
my $cmd; my $cmd;
my $err; my $err;
if (defined $tailBlock) { if (defined $tailBlock) {
while ($tailBlock ne "") { while ($tailBlock ne "") {
($cmd,$tailBlock,$err)=GetCommandDoIf($separator,$tailBlock); ($cmd,$tailBlock,$err)=GetCommandDoIf($separator,$tailBlock);
#return (@commands,$err) if ($err); #return (@commands,$err) if ($err);
push(@commands,$cmd); push(@commands,$cmd);
} }
} }
return(@commands); return(@commands);
} }
sub sub
@ -244,6 +243,165 @@ EventCheckDoif($$$$)
return 0; 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 sub
EventDoIf EventDoIf
{ {
@ -371,11 +529,18 @@ ReadingValDoIf
my $r; my $r;
my $element; my $element;
return ($default) if (!defined $defs{$name}); 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=$defs{$name}{READINGS}{$reading}{VAL};
$r="" if (!defined($r)); $r="" if (!defined($r));
if ($regExp) { 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 : ""; $element = ($r =~ /$regExp/) ? $1 : "";
if ($output) { if ($output) {
$element= eval $output; $element= eval $output;
@ -425,6 +590,57 @@ EvalAllDoIf($$)
return ($cmd,""); 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($) sub ReplaceEventDoIf($)
{ {
my ($block) = @_; my ($block) = @_;
@ -525,9 +741,9 @@ sub ReplaceReadingDoIf($)
$regExp=$1; $regExp=$1;
$output=$2; $output=$2;
return ($regExp,"no round brackets in regular expression") if ($regExp !~ /.*\(.*\)/); return ($regExp,"no round brackets in regular expression") if ($regExp !~ /.*\(.*\)/);
} elsif ($format =~ /^d[^:]*(?::(.*))?/) { } elsif ($format =~ /^(d[^:]*)(?::(.*))?/) {
$regExp = '(-?\d+(\.\d+)?)'; $regExp =$1;
$output=$1; $output=$2;
} }
else { else {
return($format,"unknown expression format"); return($format,"unknown expression format");
@ -604,6 +820,21 @@ sub AddRegexpTriggerDoIf($$$)
$hash->{regexp}{all}{$max_regexp}=$regexp; $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($$$$) sub ReplaceAllReadingsDoIf($$$$)
{ {
my ($hash,$tailBlock,$condition,$eval)= @_; my ($hash,$tailBlock,$condition,$eval)= @_;
@ -625,11 +856,27 @@ sub ReplaceAllReadingsDoIf($$$$)
} }
$tailBlock =~ s/\$SELF/$hash->{NAME}/g; $tailBlock =~ s/\$SELF/$hash->{NAME}/g;
while ($tailBlock ne "") { while ($tailBlock ne "") {
$trigger=1;
($beginning,$block,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\[\]]'); ($beginning,$block,$err,$tailBlock)=GetBlockDoIf($tailBlock,'[\[\]]');
return ($block,$err) if ($err); return ($block,$err) if ($err);
if ($block ne "") { if ($block ne "") {
if ($block =~ /^"([^"]*)"/){ if (substr($block,0,1) eq "?") {
$block=substr($block,1);
$trigger=0;
} else {
$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) { if ($condition>=0) {
($block,$err)=ReplaceEventDoIf($block); ($block,$err)=ReplaceEventDoIf($block);
return ($block,$err) if ($err); return ($block,$err) if ($err);
@ -639,10 +886,6 @@ sub ReplaceAllReadingsDoIf($$$$)
$block="[".$block."]"; $block="[".$block."]";
} }
} else { } else {
if (substr($block,0,1) eq "?") {
$block=substr($block,1);
$trigger=0;
}
$trigger=0 if (substr($block,0,1) eq "\$"); $trigger=0 if (substr($block,0,1) eq "\$");
if ($block =~ /^\$?[a-z0-9._]*[a-z._]+[a-z0-9._]*($|:.+$|,.+$)/i) { if ($block =~ /^\$?[a-z0-9._]*[a-z._]+[a-z0-9._]*($|:.+$|,.+$)/i) {
($block,$err,$device,$reading,$internal)=ReplaceReadingEvalDoIf($hash,$block,$eval); ($block,$err,$device,$reading,$internal)=ReplaceReadingEvalDoIf($hash,$block,$eval);
@ -1254,20 +1497,25 @@ sub CheckReadingDoIf($$)
sub CheckRegexpDoIf($$$$) sub CheckRegexpDoIf($$$$)
{ {
my ($hash,$name,$eventa,$condition)=@_; my ($hash,$name,$eventa,$cond)=@_;
my $cond=($condition == -1) ? "all" : $condition; my $type;
my $max_regexp=keys %{$hash->{regexp}{$cond}}; if ($cond eq "STATE") {
$type="state";
} else {
$type="regexp";
}
my $max_regexp=keys %{$hash->{$type}{$cond}};
my $c; my $c;
my $nameExp; my $nameExp;
my $notifyExp; 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; $nameExp=$1;
$notifyExp=$2; $notifyExp=$2;
} else { } else {
$nameExp=$hash->{regexp}{$cond}{$i}; $nameExp=$hash->{$type}{$cond}{$i};
} }
$nameExp="" if (!$nameExp); $nameExp="" if (!$nameExp);
$notifyExp="" if (!$notifyExp); $notifyExp="" if (!$notifyExp);
@ -1278,7 +1526,7 @@ sub CheckRegexpDoIf($$$$)
$events=join(",",@{$eventa}); $events=join(",",@{$eventa});
} }
if ($notifyExp eq "") { if ($notifyExp eq "") {
if ($cond ne "all") { if ($cond ne "all" and $cond ne "STATE") {
$c=$cond+1; $c=$cond+1;
readingsSingleUpdate ($hash, "matched_event_c".$c."_".($i+1),"$events",0); readingsSingleUpdate ($hash, "matched_event_c".$c."_".($i+1),"$events",0);
} }
@ -1292,7 +1540,7 @@ sub CheckRegexpDoIf($$$$)
$s = "" if(!defined($s)); $s = "" if(!defined($s));
$found = ($s =~ m/$notifyExp/); $found = ($s =~ m/$notifyExp/);
if ($found) { if ($found) {
if ($cond ne "all") { if ($cond ne "all" and $cond ne "STATE") {
$c=$cond+1; $c=$cond+1;
readingsSingleUpdate ($hash, "matched_event_c".$c."_".($i+1),$s,0); readingsSingleUpdate ($hash, "matched_event_c".$c."_".($i+1),$s,0);
} }
@ -1432,7 +1680,7 @@ DOIF_Notify($$)
return "" if (!$hash->{helper}{globalinit}); 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} /) { 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++) {
@ -1445,7 +1693,7 @@ DOIF_Notify($$)
return "" if (defined $hash->{helper}{cur_cmd_nr}); return "" if (defined $hash->{helper}{cur_cmd_nr});
return "" if (ReadingsVal($pn,"mode","") eq "disabled"); 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"); $hash->{helper}{cur_cmd_nr}="Trigger $dev->{NAME}" if (AttrVal($hash->{NAME},"selftrigger","") ne "all");
readingsSingleUpdate ($hash, "Device",$dev->{NAME},0); readingsSingleUpdate ($hash, "Device",$dev->{NAME},0);
#my $events = deviceEvents($dev, AttrVal($dev->{NAME}, "addStateEvent", 0)); #my $events = deviceEvents($dev, AttrVal($dev->{NAME}, "addStateEvent", 0));
@ -1477,7 +1725,7 @@ DOIF_Notify($$)
$hash->{helper}{event}=join(",",@{$eventa}); $hash->{helper}{event}=join(",",@{$eventa});
$ret=DOIF_Trigger($hash,$dev->{NAME}); $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}{cur_cmd_nr}="Trigger $dev->{NAME}" if (AttrVal($hash->{NAME},"selftrigger","") ne "all");
$hash->{helper}{triggerEvents}=$eventa; $hash->{helper}{triggerEvents}=$eventa;
$hash->{helper}{triggerEventsState}=$eventas; $hash->{helper}{triggerEventsState}=$eventas;
@ -1990,11 +2238,11 @@ DOIF_Attr(@)
DOIF_delAll ($hash); DOIF_delAll ($hash);
readingsSingleUpdate ($hash,"state","deactivated",1); readingsSingleUpdate ($hash,"state","deactivated",1);
} elsif($a[0] eq "set" && $a[2] eq "state") { } 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); my ($block,$err)=ReplaceAllReadingsDoIf($hash,$a[3],-2,0);
return $err if ($err); return $err if ($err);
} elsif($a[0] eq "del" && $a[2] eq "state") { } 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") { } elsif($a[0] eq "set" && $a[2] eq "wait") {
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
#delete ($defs{$hash->{NAME}}{READINGS}{wait_timer}); #delete ($defs{$hash->{NAME}}{READINGS}{wait_timer});
@ -2220,6 +2468,8 @@ Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgel
<a href="#DOIF_Teilausdruecke_abfragen">Teilausdrücke abfragen</a><br> <a href="#DOIF_Teilausdruecke_abfragen">Teilausdrücke abfragen</a><br>
<a href="#DOIF_Ereignissteuerung_ueber_Auswertung_von_Events">Ereignissteuerung über Auswertung von Events</a><br> <a href="#DOIF_Ereignissteuerung_ueber_Auswertung_von_Events">Ereignissteuerung über Auswertung von Events</a><br>
<a href="#DOIF_Angaben_im_Ausfuehrungsteil">Angaben im Ausführungsteil</a><br> <a href="#DOIF_Angaben_im_Ausfuehrungsteil">Angaben im Ausführungsteil</a><br>
<a href="#DOIF_Filtern_nach_Zahlen">Filtern nach Ausdrücken mit Ausgabeformatierung</a><br>
<a href="#DOIF_aggregation">Aggregieren von Werten</a><br>
<a href="#DOIF_Zeitsteuerung">Zeitsteuerung</a><br> <a href="#DOIF_Zeitsteuerung">Zeitsteuerung</a><br>
<a href="#DOIF_Relative_Zeitangaben">Relative Zeitangaben</a><br> <a href="#DOIF_Relative_Zeitangaben">Relative Zeitangaben</a><br>
<a href="#DOIF_Zeitangaben_nach_Zeitraster_ausgerichtet">Zeitangaben nach Zeitraster ausgerichtet</a><br> <a href="#DOIF_Zeitangaben_nach_Zeitraster_ausgerichtet">Zeitangaben nach Zeitraster ausgerichtet</a><br>
@ -2234,7 +2484,6 @@ Eine ausführliche Erläuterung der obigen Anwendungsbeispiele kann hier nachgel
<a href="#DOIF_Nutzung_von_Readings_Status_oder_Internals_im_Ausfuehrungsteil">Nutzung von Readings, Status oder Internals im Ausführungsteil</a><br> <a href="#DOIF_Nutzung_von_Readings_Status_oder_Internals_im_Ausfuehrungsteil">Nutzung von Readings, Status oder Internals im Ausführungsteil</a><br>
<a href="#DOIF_Berechnungen_im_Ausfuehrungsteil">Berechnungen im Ausführungsteil</a><br> <a href="#DOIF_Berechnungen_im_Ausfuehrungsteil">Berechnungen im Ausführungsteil</a><br>
<a href="#DOIF_notexist">Ersatzwert für nicht existierende Readings oder Status</a><br> <a href="#DOIF_notexist">Ersatzwert für nicht existierende Readings oder Status</a><br>
<a href="#DOIF_Filtern_nach_Zahlen">Filtern nach Ausdrücken mit Ausgabeformatierung</a><br>
<a href="#DOIF_wait">Verzögerungen</a><br> <a href="#DOIF_wait">Verzögerungen</a><br>
<a href="#DOIF_timerWithWait">Verzögerungen von Timern</a><br> <a href="#DOIF_timerWithWait">Verzögerungen von Timern</a><br>
<a href="#DOIF_do_resetwait">Zurücksetzen des Waittimers für das gleiche Kommando</a><br> <a href="#DOIF_do_resetwait">Zurücksetzen des Waittimers für das gleiche Kommando</a><br>
@ -2490,12 +2739,57 @@ Syntax:<br>
<br> <br>
Regex-Filter- und Output-Parameter sind optional. Der Default-Wert ist verpflichtend.<br> Regex-Filter- und Output-Parameter sind optional. Der Default-Wert ist verpflichtend.<br>
<br> <br>
Die Angaben zum Filter und Output funktionieren, wie die beim Reading-Filter. Siehe: <a href="#DOIF_Filtern_nach_Zahlen">Filtern nach Ausdrücken mit Ausgabeformatierung</a><br><br> Die Angaben zum Filter und Output funktionieren, wie die beim Reading-Filter. Siehe: <a href="#DOIF_Filtern_nach_Zahlen">Filtern nach Ausdrücken mit Ausgabeformatierung</a><br>
<br> <br>
Wenn kein Filter, wie obigen Beispiel, angegeben wird, so wird intern folgende Regex vorbelegt: "[^\:]*: (.*)" Damit wird der Wert hinter der Readingangabe genommen. 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, 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.<br> ohne auf Readings zurückgreifen zu müssen.<br>
<br> <br>
<a name="DOIF_Filtern_nach_Zahlen"></a>
<b>Filtern nach Ausdrücken mit Ausgabeformatierung</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br>
Syntax: <code>[&lt;device&gt;:&lt;reading&gt;|&lt;internal&gt;:d&lt;number&gt|"&lt;regex&gt;":&lt;output&gt;]</code><br>
<br>
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.<br>
&lt;Regex&gt;- 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.<br>
&lt;Output&gt; - 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.<br>
<br>
Beispiele:<br>
<br>
Es soll aus einem Reading, das z. B. ein Prozentzeichen beinhaltet, nur der Zahlenwert für den Vergleich genutzt werden:<br>
<br>
<code>define di_heating DOIF ([adjusting:actuator:d] &lt; 10) (set heating off) DOELSE (set heating on)</code><br>
<br>
Alternativen für die Nutzung der Syntax am Beispiel des Filterns nach Zahlen:<br>
<br>
<code>[mydevice:myreading:d]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)"]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)":$1]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)":"$1"]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)":sprintf("%s":$1)]</code><br>
<br>
Es soll die Zahl aus einem Reading auf 3 Nachkommastellen formatiert werden:<br>
<br>
<code>[mydevice:myreading:d3]</code><br>
<br>
Es soll aus einem Text eine Zahl herausgefiltert werden und anschließend gerundet auf zwei Nachkommastellen mit der Einheit °C ausgeben werden:<br>
<br>
<code>... (set mydummy [mydevice:myreading:d2:"$1 °C"])</code><br>
<br>
Es sollen aus einem Reading der Form "HH:MM:SS" die Stunden, Minuten und Sekunden separieret werden:<br>
<br>
<code>[mydevice:myreading:"(\d\d):(\d\d):(\d\d)":"hours: $1, minutes $2, seconds: $3"]</code><br>
<br>
Der Inhalt des Dummys Alarm soll in einem Text eingebunden werden:<br>
<br>
<code>[alarm:state:"(.*)":"state of alarm is $1"]</code><br>
<br>
Die Definition von regulären Ausdrücken mit Nutzung der Perl-Variablen $1, $2 usw. kann in der Perldokumentation nachgeschlagen werden.<br>
<br>
<a name="DOIF_Angaben_im_Ausfuehrungsteil"></a> <a name="DOIF_Angaben_im_Ausfuehrungsteil"></a>
<b>Angaben im Ausführungsteil</b>:&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br> <b>Angaben im Ausführungsteil</b>:&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br> <br>
@ -2511,6 +2805,161 @@ Perlcode kann im DEF-Editor wie gewohnt programmiert werden: <code>...{my $name=
<br> <br>
FHEM-Befehle lassen sich mit Perl-Befehlen kombinieren: <code>... ({system ("wmail Peter is at home")}, set lamp on)</code><br> FHEM-Befehle lassen sich mit Perl-Befehlen kombinieren: <code>... ({system ("wmail Peter is at home")}, set lamp on)</code><br>
<br> <br>
<a name="DOIF_aggregation"></a><br>
<b>Aggregieren von Werten</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br>
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.<br>
<br>
Syntax:<br>
<br>
<code>[&lt;function&gt;:"&lt;regex device&gt;:&lt;regex event&gt;":&lt;reading&gt;:&lt;condition&gt;,&lt;default&gt;]</code><br>
<br>
&lt;function&gt;:<br>
<br>
<b>#</b> Anzahl der betroffenen Devices, der folgende Doppelpunkt kann weggelassen werden<br>
<b>@</b> kommagetrennte Liste Devices, der folgende Doppelpunkt kann weggelassen werden<br>
<b>#sum</b> Summe <br>
<b>#max</b> höchster Wert<br>
<b>#min</b> niedrigster Wert<br>
<b>#average</b> Durchschnitt<br>
<b>@max</b> Device des höchsten Wertes<br>
<b>@min</b> Device de niedrigsten Wertes<br>
<br>
&lt;function&gt;:d&lt;number&gt optional kann das Ergebnis auf Nachkommastellen gerundet werden, dazu wird an die Funktion getrennt durch einen Doppelpunkt der Buchstabe "d" gefolgt von einer Ziffer (&lt;number&gt) angegeben. Die Ziffer gibt die Anzahl der Nachkommastellen an.<br>
<br>
"&lt;regex Device&gt;:&lt;regex Event&gt;" spezifiziert sowohl die betroffenen Devices, als auch den Ereignistrigger, die Syntax entspricht der DOIF-Syntax für Ereignistrigger.<br>
Die Angabe &lt;regex Event&gt; ist im Ausführungsteil nicht sinnvoll und sollte weggelassen werden.<br>
<br>
&lt;reading&gt; Reading, welches überprüft werden soll<br>
<br>
&lt;condition&gt; Aggregations-Bedingung, $_ ist der Platzhalter für den aktuellen Wert des internen Schleifendurchlaufs, Angaben in Anführungszeichen der Art "&lt;value&gt;" entsprechen $_ =~ "&lt;value&gt;" , hier sind alle Perloperatoren möglich.<br>
<br>
&lt;default&gt; Default-Wert, falls kein Device gefunden wird, entspricht der Syntax des Default-Wertes bei Readingangaben<br>
<br>
&lt;reading&gt;, &lt;condition&gt;, &lt;default&gt; sind optional<br>
<br>
<u>Syntax-Beispiele im Ausführungteil</u><br>
<br>
Anzahl der Devices, die mit "window" beginnen:<br>
<br>
<code>[#"^window"]</code><br>
<br>
Liste der Devices, die mit "window" beginnen:<br>
<br>
<code>[@"^window"]</code><br>
<br>
Liste der Devices, die mit "windows" beginnen und ein Reading "myreading" beinhalten:<br>
<br>
<code>[@"^window":myreading]</code><br>
<br>
Liste der Devices, die mit "windows" beginnen und im Status das Wort "open" vorkommt:<br>
<br>
<code>[@"^window":state:"open"]</code><br>
<br>
entspricht:<br>
<br>
<code>[@"^window":state:$_ =~ "open"]</code> siehe Aggregationsbedingung.<br>
<br>
In der Aggregationsbedingung <condition> können alle in FHEM definierten Perlfunktionen genutzt werden. Folgende Variablen sind vorbelegt und können ebenfalls benutzt werden:<br>
<br>
<b>$_</b> Inhalt des angegebenen Readings (s.o.)<br>
<b>$number</b> Nach Zahl gefilteres Reading<br>
<b>$name</b> Name des Devices<br>
<b>$TYPE</b> Devices-Typ<br>
<b>$STATE</b> Status des Devices (nicht das Reading state)<br>
<b>$room</b> Raum des Devices<br>
<b>$group</b> Gruppe des Devices<br>
<br>
<u>Beispiele für Definition der Aggregationsbedingung &lt;condition&gt;:</u><br>
<br>
Liste der Devices, die mit "rooms" enden und im Reading "temperature" einen Wert größer 20 haben:<br>
<br>
<code>[@"rooms$":temperature:$_ > 20]</code><br>
<br>
Liste der Devices im Raum "livingroom", die mit "rooms" enden und im Reading "temperature" einen Wert größer 20 haben:<br>
<br>
<code>[@"rooms$":temperature:$_ > 20 and $room eq "livingroom"]</code><br>
<br>
Liste der Devices in der Gruppe "windows", die mit "rooms" enden, deren Status (nicht state-Reading) "on" ist:<br>
<br>
<code>[@"rooms$"::$STATE eq "on" and $group eq "windows"]</code><br>
<br>
Liste der Devices, deren state-Reading "on" ist und das Attribut disable nicht auf "1" gesetzt ist:<br>
<br>
<code>[@"":state:$_ eq "on" and AttrVal($name,"disable","") ne "1"]</code><br>
<br>
<br>
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.<br>
<br>
Anzahl der Devices, die mit "window" beginnen. Getriggert wird, wenn eine Eventzeile beginnend mit "window" und dem Wort "open" vorkommt:<br>
<br>
<code>[#"^window:open"]</code><br>
<br>
<u>Anwendungsbeispiele</u><br>
<br>
Statusanzeige: Offene Fenster:<br>
<br>
<code>define di_window DOIF<br>
<br>
attr di_window state Offene Fenster: [@"^window:open":state:"open","keine"]</code><br>
<br>
Statusanzeige: Alle Devices, deren Batterie nicht ok ist:<br>
<br>
<code>define di_battery DOIF<br>
<br>
attr di_battery state [@":battery":battery:$_ ne "ok","alle OK"]</code><br>
<br>
Statusanzeige: Durchschnittstemperatur aller Temperatursensoren in der Gruppe "rooms":<br>
<br>
<code>define di_average_temp DOIF<br>
<br>
attr di_average_temp state [#average:d2:":temperature":temperature:$group eq "rooms"]</code><br>
<br>
Fenster Status/Meldung:<br>
<br>
<code>define di_Fenster DOIF (["^Window:open"]) <br>
(push "Fenster $DEVICE wurde geöffnet. Es sind folgende Fenster offen: [@"^Window":state:"open"]")<br>
DOELSEIF ([#"^Window:closed":state:"open"] == 0)<br>
(push "alle Fenster geschlossen")</code><br>
<br>
attr di_Fenster do always<br>
attr di_Fenster cmdState $DEVICE zuletzt geöffnet|alle geschlossen</code><br>
<br>
Raumtemperatur-Überwachung:<br>
<br>
<code>define di_temp DOIF (([08:00] or [20:00]) and [?#"^Rooms":temperature: $_ < 20] != 0)<br>
(push "In folgenden Zimmern ist zu kalt [@"^Rooms":temperature:$_ < 20,"keine"]")<br>
DOELSE<br>
(push "alle Zimmmer sind warm")<br>
<br>
attr di_temp do always<br>
attr di_Raumtemp state In folgenden Zimmern ist zu kalt: [@"^Rooms":temperature:$_ < 20,"keine"])</code><br>
<br>
Es soll beim Öffnen eines Fensters eine Meldung über alle geöffneten Fenster erfolgen:<br>
<br>
<code>define di_Fenster DOIF (["^Window:open"]) (push "Folgende Fenster: [@"^Window:state:"open"] sind geöffnet")</code><br>
<br>
attr di_Fenster do always<br>
<br>
Wenn im Wohnzimmer eine Lampe ausgeschaltet wird, sollen alle anderen Lampen im Wohnzimmer ebenfalls ausgeschaltet werden, die noch an sind:<br>
<br>
<code>define di_lamp DOIF (["^lamp_livingroom: off"]) (set [@"^lamp_livingroom":state:"on","defaultdummy"] off)<br>
<br>
attr di_lamp DOIF do always</code><br>
<br>
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.<br>
<br>
Für reine Perlangaben gibt es eine entsprechende Perlfunktion namens <code>AggrDoIf(&lt;function&gt;,&lt;regex Device&gt;,&lt;reading&gt;,&lt;condition&gt;,&lt;default&gt;)</code> diese liefert bei der Angabe @ ein Array statt einer Stringliste, dadurch lässt sie sich gut bei foreach-Schleifen verwenden.<br>
<br>
<u>Beispiele</u><br>
<br>
<code>define di_Fenster DOIF (["^Window:open"]) {foreach (AggrDoIf('@','^windows','state','"open"')) {Log3 "di_Fenster",3,"Das Fenster $_ ist noch offen"}}</code><br>
<br>
<code>define di_Temperature DOIF (["^room:temperature"]) {foreach (AggrDoIf('@','^room','temperature','$_ < 15')) {Log3 "di_Temperatur",3,"im Zimmer $_ ist zu kalt"}}</code><br>
<br>
<a name="DOIF_Zeitsteuerung"></a> <a name="DOIF_Zeitsteuerung"></a>
<b>Zeitsteuerung</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br> <b>Zeitsteuerung</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br> <br>
@ -2833,51 +3282,6 @@ so lässt sich das über das Attribut <code>notexist</code> bewerkstelligen. Ein
<br> <br>
Syntax: <code>attr &lt;DOIF-module&gt; notexist "&lt;default value&gt;"</code> <br> Syntax: <code>attr &lt;DOIF-module&gt; notexist "&lt;default value&gt;"</code> <br>
<br> <br>
<a name="DOIF_Filtern_nach_Zahlen"></a>
<b>Filtern nach Ausdrücken mit Ausgabeformatierung</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br>
Syntax: <code>[&lt;Device&gt;:&lt;Reading&gt;|&lt;Internal&gt;:d|"&lt;Regex&gt;":&lt;Output&gt;]</code><br>
<br>
d - Der Buchstabe "d" ist ein Synonym für das Filtern nach Dezimalzahlen, es entspricht intern dem regulären Ausdruck "(-?\d+(\.\d+)?)"<br>
&lt;Regex&gt;- 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.<br>
&lt;Output&gt; - 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.<br>
<br>
Beispiele:<br>
<br>
Es soll aus einem Reading, das z. B. ein Prozentzeichen beinhaltet, nur der Zahlenwert für den Vergleich genutzt werden:<br>
<br>
<code>define di_heating DOIF ([adjusting:actuator:d] &lt; 10) (set heating off) DOELSE (set heating on)</code><br>
<br>
Alternativen für die Nutzung der Syntax am Beispiel des Filterns nach Zahlen:<br>
<br>
<code>[mydevice:myreading:d]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)"]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)":$1]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)":"$1"]</code><br>
entspricht:<br>
<code>[mydevice:myreading:"(-?\d+(\.\d+)?)":sprintf("%s":$1)]</code><br>
<br>
Es soll aus einem Text eine Zahl herausgefiltert werden und anschließend die Einheit °C angehängt werden:<br>
<br>
<code>... (set mydummy [mydevice:myreading:d:"$1 °C"])</code><br>
<br>
Es soll die Zahl aus einem Reading auf 2 Nachkommastellen formatiert werden:<br>
<br>
<code>[mydevice:myreading:d:sprintf("%.2f",$1)]</code><br>
<br>
Es sollen aus einem Reading der Form "HH:MM:SS" die Stunden, Minuten und Sekunden separieret werden:<br>
<br>
<code>[mydevice:myreading:"(\d\d):(\d\d):(\d\d)":"hours: $1, minutes $2, seconds: $3"]</code><br>
<br>
Der Inhalt des Dummys Alarm soll in einem Text eingebunden werden:<br>
<br>
<code>[alarm:state:"(.*)":"state of alarm is $1"]</code><br>
<br>
Die Definition von regulären Ausdrücken mit Nutzung der Perl-Variablen $1, $2 usw. kann in der Perldokumentation nachgeschlagen werden.<br>
<br>
<a name="DOIF_wait"></a> <a name="DOIF_wait"></a>
<b>Verzögerungen</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br> <b>Verzögerungen</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br> <br>
@ -3294,6 +3698,7 @@ attr di_lamp setList on off<br>
<code>set di_lamp on</code> entspricht hier <code>set di_lamp cmd_1</code> und <code>set di_lamp off set di_lamp cmd_2</code><br> <code>set di_lamp on</code> entspricht hier <code>set di_lamp cmd_1</code> und <code>set di_lamp off set di_lamp cmd_2</code><br>
Zusätzlich führt die Definition von <code>setList</code> zur Ausführung von <code>set di_lamp on/off</code> durch das Anlicken des Lampensymbols wie im vorherigen Beispiel.<br> Zusätzlich führt die Definition von <code>setList</code> zur Ausführung von <code>set di_lamp on/off</code> durch das Anlicken des Lampensymbols wie im vorherigen Beispiel.<br>
<br> <br>
<br>
<a name="DOIF_Weitere_Anwendungsbeispiele"></a> <a name="DOIF_Weitere_Anwendungsbeispiele"></a>
<b>Weitere Anwendungsbeispiele</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br> <b>Weitere Anwendungsbeispiele</b>&nbsp;&nbsp;&nbsp;<a href="#DOIF_Inhaltsuebersicht">back</a><br>
<br> <br>