diff --git a/fhem/FHEM/98_DOIFtools.pm b/fhem/FHEM/98_DOIFtools.pm new file mode 100644 index 000000000..690261c42 --- /dev/null +++ b/fhem/FHEM/98_DOIFtools.pm @@ -0,0 +1,1011 @@ +############################################# +# $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; +use Time::Local; + +sub DOIFtools_Initialize($); +sub DOIFtools_Set($@); +sub DOIFtools_Get($@); +sub DOIFtools_Undef; +sub DOIFtools_Define($$$); +sub DOIFtools_Attr(@); +sub DOIFtools_Notify($$); +sub DOIFtoolsRg; +sub DOIFtoolsNxTimer; +sub DOIFtoolsNextTimer; +sub DOIFtoolsGetAssocDev; +sub DOIFtoolsCheckDOIF; +sub DOIFtoolsCheckDOIFcoll; +sub DOIFtools_fhemwebFn($$$$); +sub DOIFtools_eM($$$$); +sub DOIFtools_dO ($$$$); +sub DOIFtoolsSetNotifyDev; +sub DOIFtools_logWrapper($); +sub DOIFtoolsCounterReset($); + +my @DOIFtools_we =(); + + +######################### +sub DOIFtools_Initialize($) +{ + my ($hash) = @_; + $hash->{DefFn} = "DOIFtools_Define"; + $hash->{SetFn} = "DOIFtools_Set"; + $hash->{GetFn} = "DOIFtools_Get"; + $hash->{UndefFn} = "DOIFtools_Undef"; + $hash->{AttrFn} = "DOIFtools_Attr"; + $hash->{NotifyFn} = "DOIFtools_Notify"; + + $hash->{FW_detailFn} = "DOIFtools_fhemwebFn"; + + $data{FWEXT}{"/DOIFtools_logWrapper"}{CONTENTFUNC} = "DOIFtools_logWrapper"; + + my $oldAttr = "target_room:noArg target_group:noArg executeDefinition:noArg executeSave:noArg eventMonitorInDOIF:noArg readingsPrefix:noArg"; + $hash->{AttrList} = "DOIFtoolsExecuteDefinition:1,0 DOIFtoolsTargetRoom DOIFtoolsTargetGroup DOIFtoolsExecuteSave:1,0 DOIFtoolsReadingsPrefix DOIFtoolsEventMonitorInDOIF:1,0 DOIFtoolsHideModulShortcuts:1,0 DOIFtoolsMyShortcuts DOIFtoolsMenuEntry:1,0 disabledForIntervals ".$oldAttr; +} + + +sub DOIFtools_dO ($$$$){return "";} +# FW_detailFn for DOIF injecting event monitor +sub DOIFtools_eM($$$$) { + my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. + my $ret = ""; + # Event Monitor + $ret .= "

Event monitor: on  "; + $ret .= "off"; + $ret .= "
"; + + my $a = ""; + if (ReadingsVal($d,".eM","off") eq "on") { + $ret .= ""; + my $filter = $a ? ($a eq "log" ? "global" : $a) : ".*"; + $ret .= "

"; + $ret .= "Events (Filter: $filter) ". + "  FHEM log ". + "". + "  
\n"; + $ret .= "
"; + $ret .= ""; + $ret .= "
"; + } + return $ret; +} +###################### +# Show the content of the log (plain text), or an image and offer a link +# to convert it to an SVG instance +# If text and no reverse required, try to return the data as a stream; +sub DOIFtools_logWrapper($) { + my ($cmd) = @_; + + my $d = $FW_webArgs{dev}; + my $type = $FW_webArgs{type}; + my $file = $FW_webArgs{file}; + my $ret = ""; + + if(!$d || !$type || !$file) { + FW_pO '
DOIFtools_logWrapper: bad arguments
'; + return 0; + } + + if(defined($type) && $type eq "text") { + $defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File + my $path = "$1/$file"; + $path =~ s/%L/$attr{global}{logdir}/g + if($path =~ m/%/ && $attr{global}{logdir}); + $path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path); + + FW_pO "
"; + FW_pO "
" if($FW_ss); + FW_pO "
jump to: the end first listing
"; + my $suffix = "
jump to: the top first listing
".($FW_ss ? "
" : "")."
"; + + my $reverseLogs = AttrVal($FW_wname, "reverseLogs", 0); + if(!$reverseLogs) { + $suffix .= ""; + return FW_returnFileAsStream($path, $suffix, "text/html", 0, 0); + } + + if(!open(FH, $path)) { + FW_pO "
$path: $!
"; + return 0; + } + my $cnt = join("", reverse ); + close(FH); +# $cnt = FW_htmlEscape($cnt); + FW_pO $cnt; + FW_pO $suffix; + return 1; + } + return 0; +} + +sub DOIFtools_fhemwebFn($$$$) { + my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. + my $ret = ""; + # Logfile Liste + if($FW_ss && $pageHash) { + $ret."
". + "$defs{$d}{STATE}
"; + } else { + my $row = 0; + $ret .= sprintf("", + $pageHash ? "" : "block "); + foreach my $f (FW_fileList($defs{$d}{logfile})) { + my $class = (!$pageHash ? (($row++&1)?"odd":"even") : ""); + $ret .= ""; + $ret .= ""; + my $idx = 0; + foreach my $ln (split(",", AttrVal($d, "logtype", "text"))) { + if($FW_ss && $idx++) { + $ret .= "
$f
"; + } + my ($lt, $name) = split(":", $ln); + $name = $lt if(!$name); + $ret .= FW_pH("$FW_ME/DOIFtools_logWrapper&dev=$d&type=$lt&file=$f", + "
$name
", 1, "dval", 1); + } + } + $ret .= "
"; + } + # Event Monitor + $ret .= "

Event monitor: on  "; + $ret .= "off  "; + $ret .= "Shortcuts: " if (!AttrVal($d,"DOIFtoolsHideModulShortcuts",0) or AttrVal($d,"DOIFtoolsMyShortcuts","")); + if (!AttrVal($d,"DOIFtoolsHideModulShortcuts",0)) { + $ret .= "reload DOIFtools  " if(ReadingsVal($d,".debug","")); + $ret .= "update check  "; + $ret .= "update  " if(!ReadingsVal($d,".debug","")); + $ret .= "update  " if(ReadingsVal($d,".debug","")); + $ret .= "shutdown restart  "; + $ret .= "fheminfo send  "; + } + if (AttrVal($d,"DOIFtoolsMyShortcuts","")) { + my @sc = split(",",AttrVal($d,"DOIFtoolsMyShortcuts","")); + for (my $i = 0; $i < @sc; $i+=2) { + if ($sc[$i] =~ m/^\#\#(.*)/) { + $ret .= "$1  "; + } else { + $ret .= "$sc[$i]  " if($sc[$i] and $sc[$i+1]); + } + } + } + $ret .= "
"; + my $a = ""; + if (ReadingsVal($d,".eM","off") eq "on") { + $ret .= ""; + my $filter = $a ? ($a eq "log" ? "global" : $a) : ".*"; + $ret .= "

"; + $ret .= "Events (Filter: $filter) ". + "  FHEM log ". + "". + "  
\n"; + $ret .= "
"; + $ret .= ""; + $ret .= "
"; + } + return $ret; +} +sub DOIFtools_Notify($$) { + my ($hash, $source) = @_; + my $pn = $hash->{NAME}; + my $sn = $source->{NAME}; + my $events = deviceEvents($source,1); + return if( !$events ); + # \@DOIFtools_we aktualisieren + if ($sn eq AttrVal("global","holiday2we","")) { + my $we; + my $val; + my $a; + my $b; + for (my $i = 0; $i < 8; $i++) { + $DOIFtools_we[$i] = 0; + $val = CommandGet(undef,"get $sn days $i"); + if($val) { + ($a, $b) = ReplaceEventMap($sn, [$sn, $val], 0); + $DOIFtools_we[$i] = 1 if($b ne "none"); + } + } + } + my $ldi = ReadingsVal($pn,"specialLog","") ? ReadingsVal($pn,"doif_to_log","") : ""; + foreach my $event (@{$events}) { + $event = "" if(!defined($event)); + # add list to DOIFtoolsLog + if ($ldi and $ldi =~ "$sn" and $event =~ m/(^cmd: \d+(\.\d+)?|^wait_timer: \d\d.*)/) { + $hash->{helper}{counter}{0}++; + my $trig = "{helper}{counter}{0}\">"; + $trig .= "\[$hash->{helper}{counter}{0}\] +++++ Listing $sn:$1 +++++\n"; + my $prev = $hash->{helper}{counter}{0} - 1; + my $next = $hash->{helper}{counter}{0} + 1; + $trig .= $prev ? "jump to: prev  next Listing
" : "jump to: next Listing
"; + $trig .= "DOIF-Version: ".ReadingsVal($pn,"DOIF_version","n/a")."
"; + $trig .= CommandList(undef,$sn); + foreach my $itm (keys %defs) { + $trig =~ s,([\[\" ])$itm([\"\:\] ]),$1$itm$2,g; + } + CommandTrigger(undef,"$hash->{TYPE}Log $trig"); + } + # DOIFtools DEF addition + if ($sn eq "global" and $event =~ "MODIFIED|INITIALIZED|DEFINED|DELETED|RENAMED|UNDEFINED") { + my @doifList = devspec2array("TYPE=DOIF"); + $hash->{DEF} = "associated DOIF: ".join(" ",sort @doifList); + readingsSingleUpdate($hash,"DOIF_version",fhem("version 98_DOIF.pm noheader",1),0); + } + # get DOIF version and FHEM revision + if ($sn eq "global" and $event =~ "INITIALIZED|MODIFIED $pn") { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"DOIF_version",fhem("version 98_DOIF.pm noheader",1)); + readingsBulkUpdate($hash,"FHEM_revision",fhem("version revision noheader",1)); + readingsEndUpdate($hash,0); + $defs{$pn}{VERSION} = fhem("version 98_DOIFtools.pm noheader",1); + DOIFtoolsSetNotifyDev($hash,1,1); + #set new attributes and delete old ones + CommandAttr(undef,"$pn DOIFtoolsExecuteDefinition ".AttrVal($pn,"executeDefinition","")) if (AttrVal($pn,"executeDefinition","")); + CommandDeleteAttr(undef,"$pn executeDefinition") if (AttrVal($pn,"executeDefinition","")); + CommandAttr(undef,"$pn DOIFtoolsExecuteSave ".AttrVal($pn,"executeSave","")) if (AttrVal($pn,"executeSave","")); + CommandDeleteAttr(undef,"$pn executeSave") if (AttrVal($pn,"executeSave","")); + CommandAttr(undef,"$pn DOIFtoolsTargetRoom ".AttrVal($pn,"target_room","")) if (AttrVal($pn,"target_room","")); + CommandDeleteAttr(undef,"$pn target_room") if (AttrVal($pn,"target_room","")); + CommandAttr(undef,"$pn DOIFtoolsTargetGroup ".AttrVal($pn,"target_group","")) if (AttrVal($pn,"target_group","")); + CommandDeleteAttr(undef,"$pn target_group") if (AttrVal($pn,"target_group","")); + CommandAttr(undef,"$pn DOIFtoolsReadingsPrefix ".AttrVal($pn,"readingsPrefix","")) if (AttrVal($pn,"readingsPrefix","")); + CommandDeleteAttr(undef,"$pn readingsPrefix") if (AttrVal($pn,"readingsPrefix","")); + CommandAttr(undef,"$pn DOIFtoolsEventMonitorInDOIF ".AttrVal($pn,"eventMonitorInDOIF","")) if (AttrVal($pn,"eventMonitorInDOIF","")); + CommandDeleteAttr(undef,"$pn eventMonitorInDOIF") if (AttrVal($pn,"eventMonitorInDOIF","")); + CommandSave(undef,undef); + } + # Event monitor in DOIF + if ($modules{DOIF}{LOADED} and !defined $modules{DOIF}->{FW_detailFn} and $sn eq "global" and $event =~ "INITIALIZED" and AttrVal($pn,"DOIFtoolsEventMonitorInDOIF","")) { + $modules{DOIF}->{FW_detailFn} = "DOIFtools_eM" if (!defined $modules{DOIF}->{FW_detailFn}); + readingsSingleUpdate($hash,".DOIFdO",$modules{DOIF}->{FW_deviceOverview},0); + $modules{DOIF}->{FW_deviceOverview} = 1; + } + # Statistics event recording + if (ReadingsVal($pn,"doStatistics","disabled") eq "enabled" and !IsDisabled($pn) and $sn ne "global" and ReadingsVal($pn,"statisticHours",0) <= ReadingsVal($pn,"recording_target_duration",0)) { + readingsSingleUpdate($hash,"stat_$sn",ReadingsVal($pn,"stat_$sn",0)+1,0); + } + } + #statistics time counter updating + if (ReadingsVal($pn,"doStatistics","disabled") eq "enabled" and !IsDisabled($pn) and $sn ne "global") { + if (ReadingsVal($pn,"statisticHours",0) <= ReadingsVal($pn,"recording_target_duration",0) or ReadingsVal($pn,"recording_target_duration",0) == 0) { + my $t = gettimeofday(); + my $te = ReadingsVal($pn,".te",gettimeofday()) + $t - ReadingsVal($pn,".t0",gettimeofday()); + my $tH = int($te*100/3600 +.5)/100; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,".te",$te); + readingsBulkUpdate($hash,".t0",$t); + readingsBulkUpdate($hash,"statisticHours",sprintf("%.2f",$tH)); + readingsEndUpdate($hash,0); + } else { + DOIFtoolsSetNotifyDev($hash,1,0); + readingsSingleUpdate($hash,"doStatistics","disabled",0); + } + } + return undef; +} +sub DOIFtoolsRg +{ + my ($hash,$arg) = @_; + my $pn = $hash->{NAME}; + my $pnRg= "rg_$arg"; + my $ret = ""; + my @ret; + my $defRg = ""; + my @defRg; + my $cL = ""; + my @rL = split(/ /,AttrVal($arg,"readingList","")); + for (my $i=0; $i<@rL; $i++) { + $defRg .= ",<$rL[$i]>,$rL[$i]"; + $cL .= "\"$rL[$i]\"=>\"$rL[$i]:\","; + } + push @defRg, "$pnRg readingsGroup $arg:+STATE$defRg"; + my $rooms = AttrVal($pn,"DOIFtoolsTargetRoom","") ? AttrVal($pn,"DOIFtoolsTargetRoom","") : AttrVal($arg,"room",""); + push @defRg, "$pnRg room $rooms" if($rooms); + my $groups = AttrVal($pn,"DOIFtoolsTargetGroup","") ? AttrVal($pn,"DOIFtoolsTargetGroup","") : AttrVal($arg,"group",""); + push @defRg, "$pnRg group $groups" if($groups); + push @defRg, "$pnRg commands {$cL}" if ($cL); + push @defRg, "$pnRg noheading 1"; + $defRg = "defmod $defRg[0]\rattr ".join("\rattr ",@defRg[1..@defRg-1]); + if (AttrVal($pn,"DOIFtoolsExecuteDefinition","")) { + $ret = CommandDefMod(undef,$defRg[0]); + push @ret, $ret if ($ret); + for (my $i = 1; $i < @defRg; $i++) { + $ret = CommandAttr(undef,$defRg[$i]); + push @ret, $ret if ($ret); + } + if (@ret) { + $ret = join("\n", @ret); + return $ret; + } else { + $ret = "Created device $pnRg.\n"; + $ret .= CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave","")); + return $ret; + } + } else { + $defRg =~ s//>/g; + return $defRg; + } +} +# calculate real date in userReadings +sub DOIFtoolsNextTimer { + my ($timer_str) = @_; + $timer_str =~ /(\d\d).(\d\d).(\d\d\d\d) (\d\d):(\d\d):(\d\d)\|([0-8]+)/; + my $tdays = $7; + return "$1.$2.$3 $4:$5:$6" if (length($7)==0); + my $timer = timelocal($6,$5,$4,$1,$2-1,$3); + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($timer); + my $ilook = 0; + my $we; + for (my $iday = $wday; $iday < 7; $iday++) { + $we = (($iday==0 || $iday==6) ? 1 : 0); + if(!$we) { + $we = $DOIFtools_we[$ilook + 1]; + } + if ($tdays =~ /$iday/ or ($tdays =~ /7/ and $we) or ($tdays =~ /8/ and !$we)) { + return strftime("%d.%m.%Y %H:%M:%S",localtime($timer + $ilook * 86400)); + } + $ilook++; + } + for (my $iday = 0; $iday < $wday; $iday++) { + $we = (($iday==0 || $iday==6) ? 1 : 0); + if(!$we) { + $we = $DOIFtools_we[$ilook + 1]; + } + if ($tdays =~ /$iday/ or ($tdays =~ /7/ and $we) or ($tdays =~ /8/ and !$we)) { + return strftime("%d.%m.%Y %H:%M:%S",localtime($timer + $ilook * 86400)); + } + $ilook++; + } +} + +sub DOIFtoolsNxTimer { + my ($hash,$arg) = @_; + my $pn = $hash->{NAME}; + my $tn= $arg; + my $thash = $defs{$arg}; + my $ret = ""; + my @ret; + foreach my $key (keys %{$thash->{READINGS}}) { + if ($key =~ m/^timer_\d\d_c\d\d/ && $thash->{READINGS}{$key}{VAL} =~ m/.*\|[0-8]+/) { + $ret = AttrVal($pn,"DOIFtoolsReadingsPrefix","N_")."$key:$key.* \{DOIFtoolsNextTimer(ReadingsVal(\"$tn\",\"$key\",\"none\"))\}"; + push @ret, $ret if ($ret); + } + } + if (@ret) { + $ret = join(",", @ret); + if (!AttrVal($tn,"userReadings","")) { + CommandAttr(undef,"$tn userReadings $ret"); + $ret = "Created userReadings for $tn.\n"; + $ret .= CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave","")); + return $ret; + } else { + $ret = "A userReadings Attribute already exists, adding is not implemented, try it manually.\r\r $ret\r"; + return $ret; + } + } + return join("\n", @ret); +} + +sub DOIFtoolsGetAssocDev { + my ($hash,$arg) = @_; + my $pn = $hash->{NAME}; + my $tn= $arg; + my $thash = $defs{$arg}; + my $ret = ""; + my @ret = (); + push @ret ,$arg; + $ret .= $thash->{devices}{all} if ($thash->{devices}{all}); + $ret =~ s/^\s|\s$//; + push @ret, split(/ /,$ret); + push @ret, getPawList($tn); + return @ret; +} + +sub DOIFtoolsCheckDOIFcoll { + my ($hash,$tn) = @_; + my $ret = ""; + my $tail = $defs{$tn}{DEF}; + if (!$tail) { + $tail=""; + } else { + $tail =~ s/(##.*\n)|(##.*$)|\n/ /g; + } + return("") if ($tail =~ /^ *$/); + $ret .= $tn if ($tail =~ m/(DOELSEIF )/ and !($tail =~ m/(DOELSE )/) and AttrVal($tn,"do","") !~ "always"); + return $ret; +} + +sub DOIFtoolsCheckDOIF { + my ($hash,$tn) = @_; + my $ret = ""; + my $tail = $defs{$tn}{DEF}; + if (!$tail) { + $tail=""; + } else { + $tail =~ s/(##.*\n)|(##.*$)|\n/ /g; + } + return("") if ($tail =~ /^ *$/); + $ret .= "
  • replace DOIF name with \$SELF (utilization of events)
  • \n" if ($tail =~ m/[\[|\?]($tn)/); + $ret .= "
  • replace ReadingsVal(...) with [name:reading] (controlling by events)
  • \n" if ($tail =~ m/(ReadingsVal)/); + $ret .= "
  • replace ReadingsNum(...) with [name:reading:d] (filtering numbers)
  • \n" if ($tail =~ m/(ReadingsNum)/); + $ret .= "
  • replace InternalVal(...) with [name:&internal] (controlling by events)
  • \n" if ($tail =~ m/(InternalVal)/); + $ret .= "
  • replace $1...\")} with $2... (plain FHEM command)
  • \n" if ($tail =~ m/(\{\s*fhem.*?\"\s*(set|get))/); + $ret .= "
  • replace {system \"<shell command>\"} with \"\<shell command>\" (plain FHEM shell command, non blocking)
  • \n" if ($tail =~ m/(\{\s*system.*?\})/); + $ret .= "
  • sleep is not recommended in DOIF, use attribute wait for (delay)
  • \n" if ($tail =~ m/(sleep\s\d+\.?\d+\s*[;|,]?)/); + $ret .= "
  • replace [name:?regex] by [name:\"regex\"] (avoid old syntax)
  • \n" if ($tail =~ m/(\[.*?[^"]?:[^"]?\?.*?\])/); + + $ret .= "
  • the first command after DOELSE seems to be a condition indicated by $2, check it.
  • \n" if ($tail =~ m/(DOELSE .*?\]\s*?(\!\S|\=\~|\!\~|and|or|xor|not|\|\||\&\&|\=\=|\!\=|ne|eq|lt|gt|le|ge)\s*?).*?\)/); + my @wait = SplitDoIf(":",AttrVal($tn,"wait","")); + my @sub0 = (); + my @tmp = (); + if (@wait and !AttrVal($tn,"timerWithWait","")) { + for (my $i = 0; $i < @wait; $i++) { + ($sub0[$i],@tmp) = SplitDoIf(",",$wait[$i]); + $sub0[$i] =~ s/\s// if($sub0[$i]); + } + foreach my $key (sort keys %{$defs{$tn}{timeCond}}) { + if ($defs{$tn}{timeCond}{$key} and $sub0[$defs{$tn}{timeCond}{$key}]) { + $ret .= "
  • Timer in condition and wait timer for commands in the same DOIF branch.
    If you observe unexpected behaviour, try attribute timerWithWait (delay of Timer)
  • \n"; + last; + } + } + } + $ret = $ret ? "$tn\n " : ""; + return $ret; +} + +# param: $hash, doif_to_log, statisticsTypes as 1 or 0 +sub DOIFtoolsSetNotifyDev { + my ($hash,@a) = @_; + my $pn = $hash->{NAME}; + $hash->{NOTIFYDEV} = "global"; + $hash->{NOTIFYDEV} .= ",$attr{global}{holiday2we}" if ($attr{global}{holiday2we}); + $hash->{NOTIFYDEV} .= ",".ReadingsVal($pn,"doif_to_log","") if ($a[0] and ReadingsVal($pn,"doif_to_log","") and ReadingsVal($pn,"specialLog",0)); + $hash->{NOTIFYDEV} .= ",TYPE=".ReadingsVal($pn,"statisticsTYPEs","") if ($a[1] and ReadingsVal($pn,"statisticsTYPEs","") and ReadingsVal($pn,"doStatistics","deleted") eq "enabled"); + return undef; +} +sub DOIFtoolsCounterReset($) { + my ($pn) = @_; + RemoveInternalTimer($pn,"DOIFtoolsCounterReset"); + $defs{$pn}->{helper}{counter}{0} = 0; + my $nt = gettimeofday(); + my @lt = localtime($nt); + $nt -= ($lt[2]*3600+$lt[1]*60+$lt[0]); # Midnight + $nt += 86400 + 3; # Tomorrow + InternalTimer($nt, "DOIFtoolsCounterReset", $pn, 0); + return undef; +} +################################# +sub DOIFtools_Define($$$) +{ + my ($hash, $def) = @_; + my ($pn, $type, $cmd) = split(/[\s]+/, $def, 3); + my @Liste = devspec2array("TYPE=DOIFtools"); + if (@Liste > 1) { + CommandDelete(undef,$pn); + CommandSave(undef,undef); + return "Only one instance of DOIFtools is allowed per FHEM installation. Delete the old one first."; + } + $hash->{STATE} = "initialized"; + $hash->{logfile} = AttrVal("global","logdir","./log/")."$hash->{TYPE}Log-%Y-%j.log"; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"sourceAttribute","readingList") unless ReadingsVal($pn,"sourceAttribute",""); + readingsBulkUpdate($hash,"doStatistics","disabled") unless ReadingsVal($pn,"doStatistics",""); + readingsBulkUpdate($hash,".eM", ReadingsVal($pn,".eM","off")); + readingsEndUpdate($hash, 0); + DOIFtoolsCounterReset($pn); + return undef; +} + +sub DOIFtools_Attr(@) +{ + my @a = @_; + my $cmd = $a[0]; + my $pn = $a[1]; + my $attr = $a[2]; + my $value = (defined $a[3]) ? $a[3] : ""; + my $hash = $defs{$pn}; + my $ret=""; + if ($attr eq "DOIFtoolsEventMonitorInDOIF") { + if (!defined $modules{DOIF}->{FW_detailFn} and $cmd eq "set" and $value) { + $modules{DOIF}->{FW_detailFn} = "DOIFtools_eM"; + readingsSingleUpdate($hash,".DOIFdO",$modules{DOIF}->{FW_deviceOverview},0); + $modules{DOIF}->{FW_deviceOverview} = "DOIFtools_dO"; + } elsif ($modules{DOIF}->{FW_detailFn} eq "DOIFtools_eM" and ($cmd eq "del" or !$value)) { + delete $modules{DOIF}->{FW_detailFn}; + $modules{DOIF}->{FW_deviceOverview} = ReadingsVal($pn,"DOIFtools_dO",""); + } + } elsif ($attr eq "DOIFtoolsMenuEntry") { + if ($cmd eq "set" and $value) { + if (!(AttrVal($FW_wname, "menuEntries","") =~ m/(DOIFtools\,\/fhem\?detail\=DOIFtools\,)/)) { + CommandAttr(undef, "$FW_wname menuEntries DOIFtools,/fhem?detail=DOIFtools,".AttrVal($FW_wname, "menuEntries","")); + CommandSave(undef, undef); + } + } elsif ($cmd eq "del" or !$value) { + if (AttrVal($FW_wname, "menuEntries","") =~ m/(DOIFtools\,\/fhem\?detail\=DOIFtools\,)/) { + my $me = AttrVal($FW_wname, "menuEntries",""); + $me =~ s/DOIFtools\,\/fhem\?detail\=DOIFtools\,//; + CommandAttr(undef, "$FW_wname menuEntries $me"); + CommandSave(undef, undef); + } + + } + } + return undef; +} + +sub DOIFtools_Undef +{ + my ($hash, $pn) = @_; + $hash->{DELETED} = 1; + if (devspec2array("TYPE=DOIFtools") <=1 and defined($modules{DOIF}->{FW_detailFn}) and $modules{DOIF}->{FW_detailFn} eq "DOIFtools_eM") { + delete $modules{DOIF}->{FW_detailFn}; + $modules{DOIF}->{FW_deviceOverview} = ReadingsVal($pn,"DOIFtools_dO",""); + } + RemoveInternalTimer($pn,"DOIFtoolsCounterReset"); + return undef; +} + +sub DOIFtools_Set($@) +{ + my ($hash, @a) = @_; + my $pn = $hash->{NAME}; + my $arg = $a[1]; + my $value = (defined $a[2]) ? $a[2] : ""; + my $ret = ""; + my @ret = (); + my @doifList = devspec2array("TYPE=DOIF"); + my @ntL =(); + my $dL = join(",",sort @doifList); + + my %types = (); + foreach my $d (keys %defs ) { + next if(IsIgnored($d)); + my $t = $defs{$d}{TYPE}; + $types{$t} = ""; + } + my $tL = join(",",sort keys %types); + + if ($arg eq "sourceAttribute") { + readingsSingleUpdate($hash,"sourceAttribute",$value,0); + return $ret; + } elsif ($arg eq "targetDOIF") { + readingsSingleUpdate($hash,"targetDOIF",$value,0); + } elsif ($arg eq "deleteReadingsInTargetDOIF") { + if ($value) { + my @i = split(",",$value); + foreach my $i (@i) { + $ret = CommandDeleteReading(undef,ReadingsVal($pn,"targetDOIF","")." $i"); + push @ret, $ret if($ret); + } + $ret = join("\n", @ret); + Log3 $pn, 3, $ret if($ret); + return $ret; + } else { + return "no reading selected."; + } + } elsif ($arg eq "doStatistics") { + if ($value eq "deleted") { + DOIFtoolsSetNotifyDev($hash,1,0); + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"doStatistics","disabled"); + readingsBulkUpdate($hash,"statisticHours","0.00"); + readingsBulkUpdate($hash,".t0",gettimeofday()); + readingsBulkUpdate($hash,".te",0); + readingsEndUpdate($hash,0); + foreach my $key (keys %{$defs{$pn}->{READINGS}}) { + delete $defs{$pn}->{READINGS}{$key} if ($key =~ "^stat_"); + } + } elsif ($value eq "disabled") { + readingsSingleUpdate($hash,"doStatistics","disabled",0); + DOIFtoolsSetNotifyDev($hash,1,0); + } elsif ($value eq "enabled") { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"doStatistics","enabled"); + readingsBulkUpdate($hash,".t0",gettimeofday()); + readingsEndUpdate($hash,0); + DOIFtoolsSetNotifyDev($hash,1,1); + } + } elsif ($arg eq "statisticsTYPEs") { + $value =~ s/\,/|/g; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"statisticsTYPEs",$value); + readingsBulkUpdate($hash,"doStatistics","disabled"); + readingsBulkUpdate($hash,".te",0); + readingsBulkUpdate($hash,".t0",gettimeofday()); + readingsBulkUpdate($hash,"statisticHours","0.00"); + readingsEndUpdate($hash,0); + foreach my $key (keys %{$defs{$pn}->{READINGS}}) { + delete $defs{$pn}->{READINGS}{$key} if ($key =~ "^stat_"); + } + DOIFtoolsSetNotifyDev($hash,1,0); + } elsif ($arg eq "recording_target_duration") { + $value =~ m/(\d+)/; + readingsSingleUpdate($hash,"recording_target_duration",$1 ? $1 : 0,0); + } elsif ($arg eq "specialLog") { + $hash->{helper}{counter}{1} = 0; + $hash->{helper}{counter}{2} = 0; + if ($value) { + readingsSingleUpdate($hash,"specialLog",1,0); + DOIFtoolsSetNotifyDev($hash,1,1); + } else { + readingsSingleUpdate($hash,"specialLog",0,0); + DOIFtoolsSetNotifyDev($hash,0,1); + } + } else { + if (ReadingsVal($pn,"targetDOIF","")) { + my $tn = ReadingsVal($pn,"targetDOIF",""); + my @rL = (); + foreach my $key (keys %{$defs{$tn}->{READINGS}}) { + push @rL, $key if ($key !~ "^(Device|state|error|cmd|e_|timer_|wait_|matched_|last_cmd|mode)"); + } + my $rL = join(",",@rL); + return "unknown argument $arg for $pn, choose one of statisticsTYPEs:multiple-strict,.*,$tL doStatistics:disabled,enabled,deleted sourceAttribute:readingList targetDOIF:$dL deleteReadingsInTargetDOIF:multiple-strict,$rL recording_target_duration:0,1,6,12,24,168 specialLog:0,1 "; + } else { + return "unknown argument $arg for $pn, choose one of statisticsTYPEs:multiple-strict,.*,$tL doStatistics:disabled,enabled,deleted sourceAttribute:readingList targetDOIF:$dL recording_target_duration:0,1,6,12,24,168 specialLog:0,1 "; + } + } +return $ret; +} + +sub DOIFtools_Get($@) +{ + my ($hash, @a) = @_; + my $pn = $hash->{NAME}; + my $arg = $a[1]; + my $value = (defined $a[2]) ? $a[2] : ""; + my $ret=""; + my @ret=(); + my @doifList = devspec2array("TYPE=DOIF"); + my @ntL =(); + my $dL = join(",",sort @doifList); + + foreach my $i (@doifList) { + foreach my $key (keys %{$defs{$i}{READINGS}}) { + if ($key =~ m/^timer_\d\d_c\d\d/ && $defs{$i}{READINGS}{$key}{VAL} =~ m/.*\|[0-8]+/) { + push @ntL, $i; + last; + } + } + } + my $ntL = join(",",@ntL); + + my %types = (); + foreach my $d (keys %defs ) { + next if(IsIgnored($d)); + my $t = $defs{$d}{TYPE}; + $types{$t} = ""; + } + + if ($arg eq "readingsGroup_for") { + foreach my $i (split(",",$value)) { + push @ret, DOIFtoolsRg($hash,$i); + } + $ret .= join("\n",@ret); + $ret = "Definition for a simple readingsGroup prepared for import with \"Raw definition\":\r--->\r$ret\r<---\r\r"; + Log3 $pn, 3, $ret if($ret); + return $ret; + } elsif ($arg eq "DOIF_to_Log") { + my @regex = (); + my $regex = ""; + my $pnLog = "$hash->{TYPE}Log"; + push @regex, $pnLog; + readingsSingleUpdate($hash,"doif_to_log",$value,0); + return unless($value); + + foreach my $i (split(",",$value)) { + push @regex, DOIFtoolsGetAssocDev($hash,$i); + } + @regex = keys %{{ map { $_ => 1 } @regex}}; + $regex = join("|",@regex).":.*"; + if (AttrVal($pn,"DOIFtoolsExecuteDefinition","")) { + push @ret, "Create device $pnLog.\n"; + $ret = CommandDefMod(undef,"$pnLog FileLog ".AttrVal("global","logdir","./log/")."$pnLog-%Y-%j.log $regex"); + push @ret, $ret if($ret); + $ret = CommandAttr(undef,"$pnLog mseclog ".AttrVal($pnLog,"mseclog","1")); + push @ret, $ret if($ret); + $ret = CommandAttr(undef,"$pnLog nrarchive ".AttrVal($pnLog,"nrarchive","3")); + push @ret, $ret if($ret); + $ret = CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave","")); + push @ret, $ret if($ret); + $ret = join("\n", @ret); + Log3 $pn, 3, $ret if($ret); + return $ret; + } else { + $ret = "Definition for a FileLog prepared for import with \"Raw definition\":\r--->\r"; + $ret .= "defmod $pnLog FileLog ".AttrVal("global","logdir","./log/")."$pnLog-%Y-%j.log $regex\r"; + $ret .= "attr $pnLog mseclog 1\r<---\r\r"; + return $ret; + } + } elsif ($arg eq "userReading_nextTimer_for") { + foreach my $i (split(",",$value)) { + push @ret, DOIFtoolsNxTimer($hash,$i); + } + $ret .= join("\n",@ret); + Log3 $pn, 3, $ret if($ret); + return $ret; + } elsif ($arg eq "statisticsReport") { + # event statistics + my $evtsum = 0; + my $typsum = 0; + my $allattr = ""; + $ret = "".sprintf("%-17s","TYPE").sprintf("%-25s","Name").sprintf("%-12s","Anzahl").sprintf("%-8s","Rate").sprintf("%-12s","Begrenzung")."\n"; + $ret .= sprintf("%-17s","").sprintf("%-25s","").sprintf("%-12s","Events").sprintf("%-8s","1/h").sprintf("%-12s","event-on...")."\n"; + $ret .= sprintf("-"x76)."\n"; + my $te = ReadingsVal($pn,".te",0)/3600; + my $i = 0; + my $t = 0; + foreach my $typ (sort keys %types) { + $typsum = 0; + $t=0; + foreach my $key (sort keys %{$defs{$pn}->{READINGS}}) { + if ($key =~ m/^stat_(.*)/ and $defs{$1}->{TYPE} eq $typ) { + $evtsum += $hash->{READINGS}{$key}{VAL}; + $typsum += $hash->{READINGS}{$key}{VAL}; + $allattr = " ".join(" ",keys %{$attr{$1}}); + $ret .= sprintf("%-17s",$typ).sprintf("%-25s",$1).sprintf("%-12s",$hash->{READINGS}{$key}{VAL}).sprintf("%-8s",$te ? int($hash->{READINGS}{$key}{VAL}/$te + 0.5) : "").sprintf("%-12s",($allattr =~ " event-on") ? "ja" : "nein")."\n"; + $i++; + $t++; + } + } + if ($t) { + $ret .= sprintf("%52s","="x10).sprintf("%2s"," ").sprintf("="x6)."\n"; + $ret .= sprintf("%42s","Summe: ").sprintf("%-10s",$typsum).sprintf("%2s","∅:").sprintf("%-8s",$te ? int($typsum/$te + 0.5) : "")."\n"; + $ret .= sprintf("%43s","Geräte: ").sprintf("%-10s",$t)."\n"; + $ret .= sprintf("%43s","Events/Gerät: ").sprintf("%-10s",int($typsum/$t + 0.5))."\n"; + $ret .= "
    ".sprintf("-"x71)."
    "; + } + } + $ret .= sprintf("%52s","="x10).sprintf("%2s"," ").sprintf("="x6)."\n"; + $ret .= sprintf("%42s","Summe: ").sprintf("%-10s",$evtsum).sprintf("%2s","∅:").sprintf("%-8s",$te ? int($evtsum/$te + 0.5) : "")."\n"; + $ret .= sprintf("%42s","Dauer: ").sprintf("%d:%02d",int($te),int(($te-int($te))*60+.5))."\n"; + $ret .= sprintf("%43s","Geräte: ").sprintf("%-10s",$i)."\n"; + $ret .= sprintf("%43s","Events/Gerät: ").sprintf("%-10s",int($evtsum/$i + 0.5))."\n\n" if ($i); + fhem("count",1) =~ m/(\d+)/; + $ret .= sprintf("%43s","Geräte total: ").sprintf("%-10s","$1\n\n"); + $ret .= "
    ".sprintf("-"x71)."
    "; + # attibute statistics + $ret .= "".sprintf("%-30s","gesetzte Attribute in DOIF").sprintf("%-12s","Anzahl")."\n"; + $ret .= sprintf("-"x42)."\n"; + my %da = (); + foreach my $di (@doifList) { + foreach my $dia (keys %{$attr{$di}}) { + if ($modules{DOIF}{AttrList} =~ m/(^|\s)$dia(:|\s)/) { + if ($dia =~ "do|selftrigger|checkall") { + $dia = "* $dia ".AttrVal($di,$dia,""); + $da{$dia} = ($da{$dia} ? $da{$dia} : 0) + 1; + } else { + $dia = "* $dia"; + $da{$dia} = ($da{$dia} ? $da{$dia} : 0) + 1; + } + } else { + $da{$dia} = ($da{$dia} ? $da{$dia} : 0) + 1; + } + } + } + foreach $i (sort keys %da) { + $ret .= sprintf("%-28s","$i").sprintf("%-12s","$da{$i}")."\n"; + } + } elsif ($arg eq "checkDOIF") { + my @coll = (); + my $coll = ""; + foreach my $di (@doifList) { + $coll = DOIFtoolsCheckDOIFcoll($hash,$di); + push @coll, $coll if($coll); + } + $ret .= join(" ",@coll); + $ret .= "\n \n" if (@coll); + foreach my $di (@doifList) { + $ret .= DOIFtoolsCheckDOIF($hash,$di); + } + + $ret = $ret ? "Found recommendation for:\n\n$ret" : "No recommendation found."; + return $ret; + + } elsif ($arg eq "runningTimerInDOIF") { + my $erg =""; + foreach my $di (@doifList) { + push @ret, sprintf("%-28s","$di").sprintf("%-40s",ReadingsVal($di,"wait_timer",""))."\n" if (ReadingsVal($di,"wait_timer","no timer") ne "no timer"); + } + $ret .= join("",@ret); + $ret = $ret ? "Found running wait_timer for:\n\n$ret" : "No running wait_timer found."; + return $ret; + + } else { + return "unknown argument $arg for $pn, choose one of checkDOIF:noArg statisticsReport:noArg readingsGroup_for:multiple-strict,$dL DOIF_to_Log:multiple-strict,$dL userReading_nextTimer_for:multiple-strict,$ntL runningTimerInDOIF:noArg "; + } + + return $ret; +} + + +1; + +=pod +=item helper +=item summary tools to support DOIF +=item summary_DE Werkzeuge zur Unterstützung von DOIF +=begin html + + +

    DOIFtools

    + +=end html +=begin html_DE + + +

    DOIFtools

    + +=end html_DE +=cut