| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703 |
- ##############################################
- # $Id: 92_FileLog.pm 14888 2017-08-13 12:07:12Z rudolfkoenig $
- package main;
- use strict;
- use warnings;
- use IO::File;
- # This block is only needed when FileLog is loaded bevore FHEMWEB
- sub FW_pO(@);
- sub FW_pH(@);
- sub FW_addContent(;$);
- use vars qw($FW_ME); # webname (default is fhem)
- use vars qw($FW_RET); # Returned data (html)
- use vars qw($FW_RETTYPE);
- use vars qw($FW_cmdret); # error msg forwarding from toSVG
- use vars qw($FW_detail); # for redirect after toSVG
- use vars qw($FW_plotmode);# Global plot mode (WEB attribute), used by weblink
- use vars qw($FW_plotsize);# Global plot size (WEB attribute), used by weblink
- use vars qw($FW_ss); # is smallscreen
- use vars qw($FW_wname); # Web instance
- use vars qw(%FW_pos); # scroll position
- use vars qw(%FW_webArgs); # all arguments specified in the GET
- sub FileLog_seekTo($$$$$);
- sub FileLog_dailySwitch($);
- #####################################
- sub
- FileLog_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "FileLog_Define";
- $hash->{SetFn} = "FileLog_Set";
- $hash->{GetFn} = "FileLog_Get";
- $hash->{UndefFn} = "FileLog_Undef";
- #$hash->{DeleteFn} = "FileLog_Delete";
- $hash->{NotifyFn} = "FileLog_Log";
- $hash->{AttrFn} = "FileLog_Attr";
- # logtype is used by the frontend
- no warnings 'qw';
- my @attrList = qw(
- addStateEvent:0,1
- archiveCompress
- archivecmd
- archivedir
- createGluedFile:0,1
- disable:0,1
- disabledForIntervals
- eventOnThreshold
- logtype
- mseclog:1,0
- nrarchive
- reformatFn
- );
- use warnings 'qw';
- $hash->{AttrList} = join(" ", @attrList);
- $hash->{FW_summaryFn} = "FileLog_fhemwebFn";
- $hash->{FW_detailFn} = "FileLog_fhemwebFn";
- $hash->{SVG_sampleDataFn} = "FileLog_sampleDataFn";
- $hash->{SVG_regexpFn} = "FileLog_regexpFn";
- $data{FWEXT}{"/FileLog_toSVG"}{CONTENTFUNC} = "FileLog_toSVG";
- $data{FWEXT}{"/FileLog_logWrapper"}{CONTENTFUNC} = "FileLog_logWrapper";
-
- InternalTimer(time()+0.1, sub() { # Forum #39792
- map { HandleArchiving($defs{$_},1) } devspec2array("TYPE=FileLog");
- FileLog_dailySwitch($hash); # Forum #42415
- }, $hash, 0);
- }
- sub
- FileLog_dailySwitch($)
- {
- my ($hash) = @_;
- map { FileLog_Switch($defs{$_}) } devspec2array("TYPE=FileLog");
- my $t = time();
- my $off = fhemTzOffset($t);
- $t = 86400*(int(($t+$off)/86400)+1)+1-$off; # tomorrow, 1s after midnight
- InternalTimer($t, "FileLog_dailySwitch", $hash, 0);
- }
- #####################################
- sub
- FileLog_Define($@)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- my $fh;
- if(@a == 5 && $a[4] eq "readonly") {
- $hash->{READONLY} = 1;
- pop(@a);
- }
- return "wrong syntax: define <name> FileLog filename regexp [readonly]"
- if(int(@a) != 4);
- return "Bad regexp: starting with *" if($a[3] =~ m/^\*/);
- eval { "Hallo" =~ m/^$a[3]$/ };
- return "Bad regexp: $@" if($@);
- my @t = localtime;
- my $f = ResolveDateWildcards($a[2], @t);
- if(!$hash->{READONLY}) {
- $fh = new IO::File ">>$f";
- return "Can't open $f: $!" if(!defined($fh));
- }
- $hash->{FH} = $fh;
- $hash->{REGEXP} = $a[3];
- $hash->{logfile} = $a[2];
- $hash->{currentlogfile} = $f;
- $hash->{STATE} = "active";
- InternalTimer(0, sub(){ notifyRegexpChanged($hash, $a[3]); }, $hash);
- return undef;
- }
- #####################################
- sub
- FileLog_Undef($$)
- {
- my ($hash, $name) = @_;
- close($hash->{FH}) if($hash->{FH});
- return undef;
- }
- # Unused
- sub
- FileLog_Delete($$)
- {
- my ($hash, $name) = @_;
- return if(!$hash->{currentlogfile});
- unlink($hash->{currentlogfile});
- return undef;
- }
- sub
- FileLog_Switch($)
- {
- my ($log) = @_;
- my $fh = $log->{FH};
- my @t = localtime;
- my $cn = ResolveDateWildcards($log->{logfile}, @t);
- if($cn ne $log->{currentlogfile}) { # New logfile
- $log->{currentlogfile} = $cn;
- return 1 if($log->{READONLY});
- $fh->close() if($fh);
- HandleArchiving($log);
- $fh = new IO::File ">>$cn";
- if(!defined($fh)) {
- Log3 $log, 0, "Can't open $cn";
- return 0;
- }
- $log->{FH} = $fh;
- setReadingsVal($log, "linesInTheFile", 0, TimeNow());
- return 1;
- }
- return 0;
- }
- #####################################
- sub
- FileLog_Log($$)
- {
- # Log is my entry, Dev is the entry of the changed device
- my ($log, $dev) = @_;
- return if($log->{READONLY});
- my $ln = $log->{NAME};
- return if(IsDisabled($ln));
- my $events = deviceEvents($dev, AttrVal($ln, "addStateEvent", 0));
- return if(!$events);
- my $n = $dev->{NAME};
- my $re = $log->{REGEXP};
- my $max = int(@{$events});
- my $tn = $dev->{NTFY_TRIGGERTIME};
- if($log->{mseclog}) {
- my ($seconds, $microseconds) = gettimeofday();
- $tn .= sprintf(".%03d", $microseconds/1000);
- }
- my $ct = $dev->{CHANGETIME};
- my $fh;
- my $switched;
- my $written = 0;
- for (my $i = 0; $i < $max; $i++) {
- my $s = $events->[$i];
- $s = "" if(!defined($s));
- my $t = (($ct && $ct->[$i]) ? $ct->[$i] : $tn);
- if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/ || "$t:$n:$s" =~ m/^$re$/) {
- $t =~ s/ /_/; # Makes it easier to parse with gnuplot
- if(!$switched) {
- FileLog_Switch($log);
- $switched = 1;
- }
- $fh = $log->{FH};
- print $fh "$t $n $s\n";
- $written++;
- }
- }
- return "" if(!$written);
- if($fh) {
- $fh->flush;
- # Skip sync, it costs too much HD strain, esp. on SSD
- # $fh->sync if !($^O eq 'MSWin32'); #not implemented in Windows
- }
- my $owr = ReadingsVal($ln, "linesInTheFile", 0);
- my $eot = AttrVal($ln, "eventOnThreshold", 0);
- if($eot && ($owr+$written) % $eot == 0) {
- readingsSingleUpdate($log, "linesInTheFile", $owr+$written, 1);
- } else {
- setReadingsVal($log, "linesInTheFile", $owr+$written, $tn);
- }
- return "";
- }
- ###################################
- sub
- FileLog_Attr(@)
- {
- my @a = @_;
- my $do = 0;
- if($a[2] eq "mseclog") {
- $defs{$a[1]}{mseclog} = ($a[0] eq "set" && (!defined($a[3]) || $a[3]) );
- return;
- }
- if($a[0] eq "set" && $a[2] eq "disable") {
- $do = (!defined($a[3]) || $a[3]) ? 1 : 2;
- }
- $do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
- return if(!$do);
- $defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
- return undef;
- }
- ###################################
- sub
- FileLog_Set($@)
- {
- my ($hash, @a) = @_;
- my $me = $hash->{NAME};
- return "no set argument specified" if(int(@a) < 2);
- my %sets = (reopen=>0, clear=>0, absorb=>1, addRegexpPart=>2,
- removeRegexpPart=>1);
-
- my $cmd = $a[1];
- if(!defined($sets{$cmd})) {
- my $r = "Unknown argument $cmd, choose one of ".join(" ",sort keys %sets);
- my $fllist = join(",", grep { $me ne $_ } devspec2array("TYPE=FileLog"));
- $r =~ s/absorb/absorb:$fllist/;
- return $r;
- }
- return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 2);
- if(($cmd eq "reopen") or ($cmd eq "clear")) {
- if(!FileLog_Switch($hash)) { # No rename, reopen anyway
- my $fh = $hash->{FH};
- my $cn = $hash->{currentlogfile};
- $fh->close();
- if($cmd eq "clear") {
- $fh = new IO::File(">$cn");
- setReadingsVal($hash, "linesInTheFile", 0, TimeNow());
- } else {
- $fh = new IO::File(">>$cn");
- }
- return "Can't open $cn" if(!defined($fh));
- $hash->{FH} = $fh;
- }
- } elsif($cmd eq "addRegexpPart") {
- my %h;
- my $re = "$a[2]:$a[3]";
- map { $h{$_} = 1 } split(/\|/, $hash->{REGEXP});
- $h{$re} = 1;
- $re = join("|", sort keys %h);
- return "Bad regexp: starting with *" if($re =~ m/^\*/);
- eval { "Hallo" =~ m/^$re$/ };
- return "Bad regexp: $@" if($@);
- $hash->{REGEXP} = $re;
- $hash->{DEF} = $hash->{logfile} ." $re";
- notifyRegexpChanged($hash, $re);
-
- } elsif($cmd eq "removeRegexpPart") {
- my %h;
- map { $h{$_} = 1 } split(/\|/, $hash->{REGEXP});
- return "Cannot remove regexp part: not found" if(!$h{$a[2]});
- return "Cannot remove last regexp part" if(int(keys(%h)) == 1);
- delete $h{$a[2]};
- my $re = join("|", sort keys %h);
- return "Bad regexp: starting with *" if($re =~ m/^\*/);
- eval { "Hallo" =~ m/^$re$/ };
- return "Bad regexp: $@" if($@);
- $hash->{REGEXP} = $re;
- $hash->{DEF} = $hash->{logfile} ." $re";
- notifyRegexpChanged($hash, $re);
- } elsif($cmd eq "absorb") {
- my $victim = $a[2];
- return "need another FileLog as argument."
- if(!$victim ||
- !$defs{$victim} ||
- $defs{$victim}{TYPE} ne "FileLog" ||
- $victim eq $me);
- my $vh = $defs{$victim};
- my $mylogfile = $hash->{currentlogfile};
- return "Cant open the associated files"
- if(!open(FH1, $mylogfile) ||
- !open(FH2, $vh->{currentlogfile}) ||
- !open(FH3, ">$mylogfile.new"));
- my $fh = $hash->{FH};
- $fh->close();
- my $b1 = <FH1>; my $b2 = <FH2>;
- while(defined($b1) && defined($b2)) {
- if($b1 lt $b2) {
- print FH3 $b1; $b1 = <FH1>;
- } else {
- print FH3 $b2; $b2 = <FH2>;
- }
- }
- while($b1 = <FH1>) { print FH3 $b1; }
- while($b2 = <FH2>) { print FH3 $b2; }
- close(FH1); close(FH2); close(FH3);
- rename("$mylogfile.new", $mylogfile);
- $fh = new IO::File(">>$mylogfile");
- $hash->{FH} = $fh;
- $hash->{REGEXP} .= "|".$vh->{REGEXP};
- $hash->{DEF} = $hash->{logfile} . " ". $hash->{REGEXP};
- notifyRegexpChanged($hash, $hash->{REGEXP});
- CommandDelete(undef, $victim);
- }
- return undef;
- }
- sub
- FileLog_loadSVG()
- {
- if(!$modules{SVG}{LOADED} && -f "$attr{global}{modpath}/FHEM/98_SVG.pm") {
- my $ret = CommandReload(undef, "98_SVG");
- Log3 undef, 1, $ret if($ret);
- }
- }
- #########################
- sub
- FileLog_fhemwebFn($$$$)
- {
- my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
- return "<div id=\"$d\" align=\"center\" class=\"FileLog col2\">".
- "$defs{$d}{STATE}</div>" if($FW_ss && $pageHash);
- my $row = 0;
- my $ret = sprintf("<table class=\"FileLog %swide\">",
- $pageHash ? "" : "block ");
- foreach my $f (FW_fileList($defs{$d}{logfile})) {
- my $class = (!$pageHash ? (($row++&1)?"odd":"even") : "");
- $ret .= "<tr class=\"$class\">";
- $ret .= "<td><div class=\"dname\">$f</div></td>";
- my $idx = 0;
- foreach my $ln (split(",", AttrVal($d, "logtype", "text"))) {
- if($FW_ss && $idx++) {
- $ret .= "</tr><tr class=\"".(($row++&1)?"odd":"even")."\"><td>";
- }
- my ($lt, $name) = split(":", $ln);
- $name = $lt if(!$name);
- $ret .= FW_pH("$FW_ME/FileLog_logWrapper&dev=$d&type=$lt&file=$f",
- "<div class=\"dval\">$name</div>", 1, "dval", 1);
- }
- $ret .= "</tr>";
- }
- $ret .= "</table>";
- return $ret if($pageHash);
- # DETAIL only from here on
- my $hash = $defs{$d};
- $ret .= "<br>Regexp parts";
- $ret .= "<br><table class=\"block wide\">";
- my @ra = split(/\|/, $hash->{REGEXP});
- if(@ra > 1) {
- foreach my $r (@ra) {
- $ret .= "<tr class=\"".(($row++&1)?"odd":"even")."\">";
- my $cmd = "cmd.X= set $d removeRegexpPart&val.X=$r"; # =.set: avoid JS
- $ret .= "<td>$r</td>";
- $ret .= FW_pH("$cmd&detail=$d", "removeRegexpPart", 1,undef,1);
- $ret .= "</tr>";
- }
- }
- my @et = devspec2array("TYPE=eventTypes");
- if(!@et) {
- $ret .= FW_pH("$FW_ME/docs/commandref.html#eventTypes",
- "To add a regexp an eventTypes definition is needed",
- 1, undef, 1);
- } else {
- my %dh;
- my $etList = AnalyzeCommand(undef, "get $et[0] list");
- $etList = "" if(!$etList);
- foreach my $l (split("\n", $etList)) {
- my @a = split(/[ \r\n]/, $l);
- $a[1] = "" if(!defined($a[1]));
- $a[1] =~ s/\.\*//g;
- $a[1] =~ s/,.*//g;
- next if(@a < 2);
- $dh{$a[0]}{".*"} = 1;
- $dh{$a[0]}{$a[1].".*"} = 1;
- }
- my $list = "";
- foreach my $dev (sort keys %dh) {
- $list .= " $dev:" . join(",", sort keys %{$dh{$dev}});
- }
- $list =~ s/(['"])/./g;
- $ret .= "<tr class=\"".(($row++&1)?"odd":"even")."\">";
- $ret .= '<td colspan="2">';
- $ret .= FW_detailSelect($d, "set", $list, "addRegexpPart");
- $ret .= "</td></tr>";
- }
- $ret .= "</table>";
- my $newIdx=1;
- while($defs{"SVG_${d}_$newIdx"}) {
- $newIdx++;
- }
- my $name = "SVG_${d}_$newIdx";
- $ret .= FW_pH("cmd=define $name SVG $d:template:CURRENT;".
- "set $name copyGplotFile&detail=$name",
- "<div class=\"dval\">Create SVG plot</div>", 0, "dval", 1);
- return $ret;
- }
- ###################################
- sub
- FileLog_toSVG($)
- {
- my ($arg) = @_;
- FW_digestCgi($arg);
- return("text/html;", "bad url: cannot create SVG def")
- if(!defined($FW_webArgs{arg}));
- my @aa = split(":", $FW_webArgs{arg});
- my $max = 0;
- for my $d (keys %defs) {
- $max = ($1+1) if($d =~ m/^SVG_(\d+)$/ && $1 >= $max);
- }
- $defs{$aa[0]}{currentlogfile} =~ m,([^/]*)$,;
- $aa[2] = "CURRENT" if($1 eq $aa[2]);
- $FW_cmdret = FW_fC("define SVG_$max SVG $aa[0]:$aa[1]:$aa[2]");
- $FW_detail = "SVG_$max" if(!$FW_cmdret);
- return;
- }
- ######################
- # 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
- FileLog_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_addContent(">FileLog_logWrapper: bad arguments</div");
- 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_addContent();
- FW_pO "<div class=\"tiny\">" if($FW_ss);
- FW_pO "<pre class=\"log\">";
- my $suffix = "</pre>".($FW_ss ? "</div>" : "")."</div>";
- my $reverseLogs = AttrVal($FW_wname, "reverseLogs", 0);
- if(!$reverseLogs) {
- $suffix .= "</body></html>";
- return FW_returnFileAsStream($path, $suffix, "text/html", 1, 0);
- }
- if(!open(FH, $path)) {
- FW_addContent(">$path: $!</div></body></html");
- return 0;
- }
- my $cnt = join("", reverse <FH>);
- close(FH);
- $cnt = FW_htmlEscape($cnt);
- FW_pO $cnt;
- FW_pO $suffix;
- return 1;
- } else {
- FileLog_loadSVG();
- FW_pO "<script type='text/javascript' src='$FW_ME/pgm2/svg.js'></script>";
- FW_addContent();
- FW_pO "<br>";
- if(AttrVal($d,"plotmode",$FW_plotmode) ne "gnuplot") {
- FW_pO SVG_zoomLink("$cmd;zoom=-1", "Zoom-in", "zoom in");
- FW_pO SVG_zoomLink("$cmd;zoom=1", "Zoom-out","zoom out");
- FW_pO SVG_zoomLink("$cmd;off=-1", "Prev", "prev");
- FW_pO SVG_zoomLink("$cmd;off=1", "Next", "next");
- }
- FW_pO "<table><tr><td>";
- FW_pO "<td>";
- my $logtype = $defs{$d}{NAME};
- my $wl = "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos);
- my $arg = "$FW_ME/SVG_showLog&dev=$logtype&logdev=$d".
- "&gplotfile=$type&logfile=$file$wl";
- if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") {
- my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize));
- FW_pO "<embed src=\"$arg\" type=\"image/svg+xml\" " .
- "width=\"$w\" height=\"$h\" name=\"$d\"/>\n";
- } else {
- FW_pO "<img src=\"$arg\"/>";
- }
- FW_pO "<br>";
- FW_pH "$FW_ME/FileLog_toSVG&arg=$d:$type:$file", "Create SVG instance";
- FW_pO "</td>";
- FW_pO "</td></tr></table>";
- FW_pO "</div>";
- }
- return 0;
- }
- ###################################
- # We use this function to be able to scroll/zoom in the plots created from the
- # logfile. When outfile is specified, it is used with gnuplot post-processing,
- # when outfile is "-" it is used to create SVG graphics
- #
- # Up till now following functions are impemented:
- # - int (to cut off % from a number, as for the actuator)
- # - delta-h / delta-d to get rain/h and rain/d values from continuous data.
- #
- # It will set the %data values
- # mindate<x>, min<x>, maxdate<x>, max<x>, avg<x>, cnt<x>, currdate<x>,
- # currval<x>, sum<x>
- # for each requested column, beginning with <x> = 1
- sub
- FileLog_Get($@)
- {
- my ($hash, @a) = @_;
-
- return "Usage: get $a[0] <infile> <outfile> <from> <to> [<column_spec>...]\n".
- " where column_spec is <col>:<regexp>:<default>:<fn>\n" .
- " see the FileLogGrep entries in he .gplot files\n" .
- " <infile> is without direcory, - means the current file\n" .
- " <outfile> is a prefix, - means stdout\n"
- if(int(@a) < 4);
- shift @a;
- my $inf = shift @a;
- my $outf = shift @a;
- my $from = shift @a;
- my $to = shift @a; # Now @a contains the list of column_specs
- my $internal;
- my $name = $hash->{NAME};
- if($outf eq "INT") {
- $outf = "-";
- $internal = 1;
- }
- my $reformatFn = AttrVal($name, "reformatFn", "");
- my $tempread;
- if($inf eq "-") {
- # In case now is after midnight, before the first event is logged.
- FileLog_Switch($hash);
- $inf = $hash->{currentlogfile};
- } else {
- my $linf;
- if($inf eq "CURRENT") {
- # Try to guess
- if($from =~ m/^(....)-(..)-(..)/) {
- $linf = $hash->{logfile};
- my ($Y,$m,$d) = ($1,$2,$3);
- sub expandFileWildcards($$$$) {
- my ($f,$Y,$m,$d)=@_;
- return ResolveDateWildcards($f,
- localtime(time_str2num("$Y-$m-$d 00:00:00")));
- };
- $linf=expandFileWildcards($linf,$Y,$m,$d);
- if(AttrVal($name, "createGluedFile", 0)) {
- if($to =~ m/^(....)-(..)-(..)/) {
- my $linf_to = $hash->{logfile};
- my ($Y,$m,$d) = ($1,$2,$3);
- $linf_to=expandFileWildcards($linf_to,$Y,$m,$d);
- if($linf ne $linf_to){ # use to log files
- $tempread=$linf_to.".transit.temp.log";
- if(open(my $temp,'>',$tempread)){
- if(open(my $i,'<',$linf)){
- print $temp join("",<$i>);
- close($i);
- }
- if(open(my $i,'<',$linf_to)){
- print $temp join("",<$i>);
- close($i);
- }
- $linf=$tempread;
- close($temp);
- }
- }
- }
- }
- $linf = $hash->{currentlogfile} if($linf =~ m/%/ || ! -f $linf);
- } else {
- $linf = $hash->{currentlogfile};
- }
- } else {
- $linf = "$1/$inf" if($hash->{currentlogfile} =~ m,^(.*)/[^/]*$,);
- $linf = "" if(!$linf); # Missing log directory
- }
- # Look for the file in the log directory...
- if(!-f $linf) {
- # ... or in the archivelog
- $linf = AttrVal($name, "archivedir",".") ."/". $inf;
- }
- $inf = $linf;
- }
- Log3 $name, 4, "$name get: Input file $inf, from:$from to:$to";
- my $ifh = new IO::File $inf if($inf);
- FileLog_seekTo($inf, $ifh, $hash, $from, $reformatFn) if($ifh);
- # Return the the plain file data, $outf is ignored
- if(!@a) {
- return "" if(!$ifh);
- my $out = "";
- while(my $l = <$ifh>) {
- if($reformatFn) { no strict; $l = &$reformatFn($l); use strict; }
- next if($l lt $from);
- last if($l gt $to);
- $out .= $l;
- }
- return $out;
- }
- #############
- # Digest the input.
- # last1: first delta value after d/h change
- # last2: last delta value recorded (for the very last entry)
- # last3: last delta timestamp (d or h)
- my (@d, @fname);
- my (@min, @max, @sum, @cnt, @lastv, @lastd, @mind, @maxd, @firstv, @firstd);
- for(my $i = 0; $i < int(@a); $i++) {
- my @fld = split(":", $a[$i], 4);
- my %h;
- if($outf ne "-") {
- $fname[$i] = "$outf.$i";
- $h{fh} = new IO::File "> $fname[$i]";
- }
- $h{re} = $fld[1]; # Filter: regexp
- $h{df} = defined($fld[2]) ? $fld[2] : ""; # default value
- $h{fn} = $fld[3]; # function
- $h{didx} = 10 if($fld[3] && $fld[3] eq "delta-d"); # delta idx, substr len
- $h{didx} = 13 if($fld[3] && $fld[3] eq "delta-h");
- if($fld[0] =~ m/"(.*)"/o) {
- $h{col} = $1;
- $h{type} = 0;
- } else {
- $h{col} = $fld[0]-1;
- $h{type} = 1;
- }
- if($h{fn}) {
- $h{type} = 4;
- $h{type} = 2 if($h{didx});
- $h{type} = 3 if($h{fn} eq "int");
- }
- $h{ret} = "";
- $d[$i] = \%h;
- $min[$i] = 999999;
- $max[$i] = -999999;
- $sum[$i] = 0;
- $cnt[$i] = 0;
- $lastv[$i] = 0;
- $lastd[$i] = "undef";
- $firstv[$i] = 0;
- $firstd[$i] = "undef";
- $mind[$i] = "undef";
- $maxd[$i] = "undef";
- }
- my %lastdate;
- my $d; # Used by eval functions
- my ($rescan, $rescanNum, $rescanIdx, @rescanArr);
- $rescan = 0;
- RESCAN:
- for(;;) {
- my $l;
- if($rescan) {
- last if($rescanIdx<1 || !$rescanNum);
- $l = $rescanArr[$rescanIdx--];
- } else {
- $l = <$ifh> if($ifh);
- last if(!$l);
- if($reformatFn) { no strict; $l = &$reformatFn($l); use strict; }
- }
- next if($l lt $from && !$rescan);
- last if($l gt $to);
- my @fld = split("[ \r\n]+", $l); # 40% CPU
- for my $i (0..int(@a)-1) { # Process each req. field
- my $h = $d[$i];
- next if($rescan && $h->{ret});
- my @missingvals;
- next if($h->{re} && $l !~ m/$h->{re}/); # 20% CPU
- my $col = $h->{col};
- my $t = $h->{type};
- my $val = undef;
- my $dte = $fld[0];
- if($t == 0) { # Fixed text
- $val = $col;
- } elsif($t == 1) { # The column
- $val = $fld[$col] if(defined($fld[$col]));
- } elsif($t == 2) { # delta-h or delta-d
- next if($rescan);
- my $hd = $h->{didx}; # TimeStamp-Length
- my $ld = substr($fld[0],0,$hd); # TimeStamp-Part (hour or date)
- if(!defined($h->{last1}) || $h->{last3} ne $ld) {
- if(defined($h->{last1})) {
- my @lda = split("[_:]", $lastdate{$hd});
- my $ts = "12:00:00"; # middle timestamp
- $ts = "$lda[1]:30:00" if($hd == 13);
- my $v = $fld[$col]-$h->{last1};
- # $v = 0 if($v < 0); # Skip negative delta (why?)
- $dte = "$lda[0]_$ts";
- $val = sprintf("%g", $v);
- if($hd == 13) { # Generate missing 0 values / hour
- my @cda = split("[_:]", $ld);
- for(my $mi = $lda[1]+1; $mi < $cda[1]; $mi++) {
- push @missingvals, sprintf("%s_%02d:30:00 0\n", $lda[0], $mi);
- }
- }
- }
- $h->{last1} = $fld[$col];
- $h->{last3} = $ld;
- }
- $h->{last2} = $fld[$col];
- $lastdate{$hd} = $fld[0];
- } elsif($t == 3) { # int function
- $val = $1 if($fld[$col] =~ m/^(\d+).*/o);
- } else { # evaluate
- $cmdFromAnalyze = $h->{fn};
- $val = eval($cmdFromAnalyze);
- $cmdFromAnalyze = undef;
- }
- next if(!defined($val) || $val !~ m/^-?[.\d]+$/o);
- if($val < $min[$i]) {
- $min[$i] = $val;
- $mind[$i] = $dte;
- }
- if($val > $max[$i]) {
- $max[$i] = $val;
- $maxd[$i] = $dte;
- }
- $sum[$i] += $val;
- $cnt[$i]++;
- if($firstd[$i] eq "undef") {
- $firstv[$i] = $val;
- $firstd[$i] = $dte;
- }
- $lastv[$i] = $val;
- $lastd[$i] = $dte;
- map { $cnt[$i]++; $min[$i] = 0 if(0 < $min[$i]); } @missingvals;
- if($outf eq "-") {
- $h->{ret} .= "$dte $val\n";
- map { $h->{ret} .= $_ } @missingvals;
- } else {
- my $fh = $h->{fh}; # cannot use $h->{fh} in print directly
- print $fh "$dte $val\n";
- map { print $fh $_ } @missingvals;
- }
- $h->{count}++;
- $rescanNum--;
- last if(!$rescanNum);
- }
- }
- # If no value found for some of the required columns, then look for the last
- # matching entry outside of the range. Known as the "window left open
- # yesterday" problem
- if(!$rescan && $ifh) {
- $rescanNum = 0;
- map { $rescanNum++ if(!$d[$_]->{count} && $d[$_]->{df} eq "") } (0..$#a);
- if($rescanNum) {
- $rescan=1;
- my $buf;
- my $end = $hash->{pos}{"$inf:$from"};
- my $start = $end - 1024;
- $start = 0 if($start < 0);
- $ifh->seek($start, 0);
- sysread($ifh, $buf, $end-$start);
- @rescanArr = split("\n", $buf);
- $rescanIdx = $#rescanArr;
- goto RESCAN;
- }
- }
- $ifh->close() if($ifh);
- unlink($tempread) if($tempread);
- my $ret = "";
- for(my $i = 0; $i < int(@a); $i++) {
- my $h = $d[$i];
- my $hd = $h->{didx};
- if($hd && $lastdate{$hd}) {
- my $val = defined($h->{last1}) ? $h->{last2}-$h->{last1} : 0;
- $min[$i] = $val if($min[$i] == 999999);
- $max[$i] = $val if($max[$i] == -999999);
- $lastv[$i] = $val if(!$lastv[$i]);
- $sum[$i] = ($sum[$i] ? $sum[$i] + $val : $val);
- $cnt[$i]++;
- my @lda = split("[_:]", $lastdate{$hd});
- my $ts = "12:00:00"; # middle timestamp
- $ts = "$lda[1]:30:00" if($hd == 13);
- my $line = sprintf("%s_%s %0.1f\n", $lda[0],$ts,
- defined($h->{last1}) ? $h->{last2}-$h->{last1} : 0);
- if($outf eq "-") {
- $h->{ret} .= $line;
- } else {
- my $fh = $h->{fh};
- print $fh $line;
- $h->{count}++;
- }
- }
- if($outf eq "-") {
- $h->{ret} .= "$from $h->{df}\n" if(!$h->{ret} && $h->{df} ne "");
- $ret .= $h->{ret} if($h->{ret});
- $ret .= "#$a[$i]\n";
- } else {
- my $fh = $h->{fh};
- if(!$h->{count} && $h->{df} ne "") {
- print $fh "$from $h->{df}\n";
- }
- $fh->close();
- }
- my $j = $i+1;
- $data{"min$j"} = $min[$i];
- $data{"max$j"} = $max[$i];
- $data{"avg$j"} = $cnt[$i] ? sprintf("%0.1f", $sum[$i]/$cnt[$i]) : 0;
- $data{"sum$j"} = $sum[$i];
- $data{"cnt$j"} = $cnt[$i];
- $data{"currval$j"} = $lastv[$i];
- $data{"currdate$j"} = $lastd[$i];
- $data{"firstval$j"} = $firstv[$i];
- $data{"firstdate$j"} = $firstd[$i];
- $data{"mindate$j"} = $mind[$i];
- $data{"maxdate$j"} = $maxd[$i];
- $data{"lastraw$j"} = $h->{last2} if($h->{last2});
- Log3 $name, 4,
- "$name get: line $j, regexp:".$d[$i]->{re}.", col:".$d[$i]->{col}.
- ", output lines:".$data{"cnt$j"};
- }
- if($internal) {
- $internal_data = \$ret;
- return undef;
- }
- return ($outf eq "-") ? $ret : join(" ", @fname);
- }
- ###############
- # this is not elegant. Assume, that current seek pos is after a cr/nl
- sub
- seekBackOneLine($$)
- {
- my ($fh, $pos) = @_;
- my $buf;
- while($pos > 0) { # skip current CR/NL
- $fh->seek(--$pos, 0);
- $fh->read($buf, 1);
- last if($buf ne "\n" && $buf ne "\r");
- }
- $fh->seek($pos, 0);
- while($pos > 0 && $fh->read($buf, 1)) {
- return ++$pos if($buf eq "\n" || $buf eq "\r");
- $fh->seek(--$pos, 0);
- }
- return 0;
- }
- ###################################
- #($1-40587)*86400+$2
- sub
- FileLog_seekTo($$$$$)
- {
- my ($fname, $fh, $hash, $ts, $reformatFn) = @_;
- # If its cached
- if($hash->{pos} && $hash->{pos}{"$fname:$ts"}) {
- $fh->seek($hash->{pos}{"$fname:$ts"}, 0);
- return;
- }
- $fh->seek(0, 2); # Go to the end
- my $upper = $fh->tell;
- my ($lower, $next, $last) = (0, $upper/2, -1);
- for(my $iter=0; $iter<200; $iter++) { # Binary search
- if($next == $last) {
- $fh->seek($next, 0);
- last;
- }
- $fh->seek($next, 0);
- my $data = <$fh>;
- if(!$data) {
- $last = $next;
- last;
- }
- if($reformatFn) { no strict; $data = &$reformatFn($data); use strict; }
- if($data !~ m/^\d\d\d\d-\d\d-\d\d_\d\d:\d\d:\d\d /o) {
- $next = seekBackOneLine($fh, $fh->tell);
- next;
- }
- $last = $next;
- if(!$data || $data lt $ts) {
- ($lower, $next) = ($next, int(($next+$upper)/2));
- } else {
- ($upper, $next) = ($next, int(($lower+$next)/2));
- }
- }
- $last = 0 if($last < 0); # Forum #46512
- $hash->{pos}{"$fname:$ts"} = $last;
- }
- sub
- FileLog_addTics($$)
- {
- my ($in, $p) = @_;
- return if(!$in || $in !~ m/^\((.*)\)$/);
- map { $p->{"\"$2\""}=1 if(m/^ *([^ ]+) ([^ ]+) */); } split(",",$1);
- }
- sub
- FileLog_sampleDataFn($$$$$)
- {
- my ($flName, $flog, $max, $conf, $wName) = @_;
- my $desc = "Input:Column,Regexp,DefaultValue,Function";
- my @htmlArr;
- my $fName = $defs{$flName}{currentlogfile};
- my $reformatFn = AttrVal($flName, "reformatFn", "");
- my $fh = new IO::File $fName;
- if(!$fh) {
- $fName = "<undefined>" if(!defined($fName));
- Log3 $wName, 1, "FileLog get sample data: $fName: $!";
- return ($desc, \@htmlArr, "");
- }
- $fh->seek(0, 2); # Go to the end
- my $sz = $fh->tell;
- $fh->seek($sz > 65536 ? $sz-65536 : 0, 0);
- my $data;
- $data = <$fh> if($sz > 65536); # discard the first/partial line
- my $maxcols = 0;
- my %h;
- while($data = <$fh>) {
- if($reformatFn) { no strict; $data = &$reformatFn($data); use strict; }
- my @cols = split(" ", $data);
- next if(@cols < 3);
- $maxcols = @cols if(@cols > $maxcols);
- $cols[2] = "*" if($cols[2] =~ m/^[-\.\d]+$/);
- $h{"$cols[1].$cols[2]"} = $data;
- $h{"$cols[1].*"} = "" if($cols[2] ne "*");
- }
- $fh->close();
- my $colnums = $maxcols;
- my $colregs = join(",", sort keys %h);
- my $example = join("<br>", grep /.+/,map { $h{$_} } sort keys %h);
- $colnums = join(",", 3..$colnums);
- my %tickh;
- FileLog_addTics($conf->{ytics}, \%tickh);
- FileLog_addTics($conf->{y2tics}, \%tickh);
- $colnums = join(",", sort keys %tickh).",$colnums" if(%tickh);
- for(my $r=0; $r < $max; $r++) {
- my @f = split(":", ($flog->[$r] ? $flog->[$r] : ":::"), 4);
- my $ret = "";
- $f[1] =~ s/\\x(..)/chr(hex($1))/ge; # Convert \x3a to :
- $colregs .= ",$f[1]" if($f[1] && !$h{$f[1]});
- $ret .= SVG_sel("par_${r}_0", $colnums, $f[0], undef, "svgColumn");
- $ret .= SVG_sel("par_${r}_1", $colregs, $f[1], undef, "svgRegexp");
- $ret .= SVG_txt("par_${r}_2", "", $f[2], 2);
- $ret .= SVG_txt("par_${r}_3", "", $f[3],10);
- push @htmlArr, $ret;
- }
- return ($desc, \@htmlArr, $example);
- }
- sub
- FileLog_regexpFn($$)
- {
- my ($name, $filter) = @_;
- $filter = " $filter ";
- $filter =~ s/ [^: ]*:/ /g;
- $filter =~ s/:[^ ]* / /g;
- $filter =~ s/(^ | $)//g;
- $filter =~ s/ /|/g;
- return $filter;
- }
- 1;
- =pod
- =item helper
- =item summary log events to a file
- =item summary_DE schreibt Events in eine Logdatei
- =begin html
- <a name="FileLog"></a>
- <h3>FileLog</h3>
- <ul>
- <br>
- <a name="FileLogdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> FileLog <filename> <regexp> [readonly]</code>
- <br><br>
- Log events to <code><filename></code>. The log format is
- <ul><code><br>
- YYYY-MM-DD_HH:MM:SS <device> <event><br>
- <br></code></ul>
- The regexp will be checked against the device name
- devicename:event or timestamp:devicename:event combination.
- The regexp must match the complete string, not just a part of it.
- <br>
- <code><filename></code> may contain %-wildcards of the
- POSIX strftime function of the underlying OS (see your strftime manual).
- Common used wildcards are:
- <ul>
- <li><code>%d</code> day of month (01..31)</li>
- <li><code>%m</code> month (01..12)</li>
- <li><code>%Y</code> year (1970...)</li>
- <li><code>%w</code> day of week (0..6); 0 represents Sunday</li>
- <li><code>%j</code> day of year (001..366)</li>
- <li><code>%U</code> week number of year with Sunday as first day of week (00..53)</li>
- <li><code>%W</code> week number of year with Monday as first day of week (00..53)</li>
- </ul>
- FHEM also replaces <code>%L</code> by the value of the global logdir attribute.<br>
- Before using <code>%V</code> for ISO 8601 week numbers check if it is
- correctly supported by your system (%V may not be replaced, replaced by an
- empty string or by an incorrect ISO-8601 week number, especially
- at the beginning of the year)
- If you use <code>%V</code> you will also have to use %G
- instead of %Y for the year!<br>
- If readonly is specified, then the file is used only for visualisation, and
- it is not opened for writing.
- Examples:
- <ul>
- <code>define lamplog FileLog %L/lamp.log lamp</code><br>
- <code>define wzlog FileLog ./log/wz-%Y-%U.log
- wz:(measured-temp|actuator).*</code><br>
- With ISO 8601 week numbers, if supported:<br>
- <code>define wzlog FileLog ./log/wz-%G-%V.log
- wz:(measured-temp|actuator).*</code><br>
- </ul>
- <br>
- </ul>
- <a name="FileLogset"></a>
- <b>Set </b>
- <ul>
- <li>reopen
- <ul>
- Reopen a FileLog after making some manual changes to the
- logfile.
- </ul>
- </li>
- <li>clear
- <ul>
- Clears and reopens the logfile.
- </ul>
- </li>
- <li>addRegexpPart <device> <regexp>
- <ul>
- add a regexp part, which is constructed as device:regexp. The parts
- are separated by |. Note: as the regexp parts are resorted, manually
- constructed regexps may become invalid.
- </ul>
- </li>
- <li>removeRegexpPart <re>
- <ul>
- remove a regexp part. Note: as the regexp parts are resorted, manually
- constructed regexps may become invalid.<br>
- The inconsistency in addRegexpPart/removeRegexPart arguments originates
- from the reusage of javascript functions.
- </ul>
- </li>
- <li>absorb secondFileLog
- <ul>
- merge the current and secondFileLog into one file, add the regexp of the
- secondFileLog to the current one, and delete secondFileLog.<br>
- This command is needed to create combined plots (weblinks).<br>
- <b>Notes:</b>
- <ul>
- <li>secondFileLog will be deleted (i.e. the FHEM definition).</li>
- <li>only the current files will be merged.</li>
- <li>weblinks using secondFilelog will become broken, they have to be
- adopted to the new logfile or deleted.</li>
- </ul>
- </ul>
- </li>
- <br>
- </ul>
- <br>
- <a name="FileLogget"></a>
- <b>Get</b>
- <ul>
- <code>get <name> <infile> <outfile> <from>
- <to> <column_spec> </code>
- <br><br>
- Read data from the logfile, used by frontends to plot data without direct
- access to the file.<br>
- <ul>
- <li><infile><br>
- Name of the logfile to open. Special case: "-" is the currently active
- logfile, "CURRENT" opens the file corresponding to the "from"
- parameter.
- </li>
- <li><outfile><br>
- If it is "-", you get the data back on the current connection, else it
- is the prefix for the output file. If more than one file is specified,
- the data is separated by a comment line for "-", else it is written in
- separate files, numerated from 0.
- </li>
- <li><from> <to><br>
- Used to grep the data. The elements should correspond to the
- timeformat or be an initial substring of it.</li>
- <li><column_spec><br>
- For each column_spec return a set of data in a separate file or
- separated by a comment line on the current connection.<br>
- Syntax: <col>:<regexp>:<default>:<fn><br>
- <ul>
- <li><col>
- The column number to return, starting at 1 with the date.
- If the column is enclosed in double quotes, then it is a fix text,
- not a column number.</li>
- <li><regexp>
- If present, return only lines containing the regexp. Case sensitive.
- </li>
- <li><default><br>
- If no values were found and the default value is set, then return
- one line containing the from value and this default. We need this
- feature as gnuplot aborts if a dataset has no value at all.
- </li>
- <li><fn>
- One of the following:
- <ul>
- <li>int<br>
- Extract the integer at the beginning og the string. Used e.g.
- for constructs like 10%</li>
- <li>delta-h or delta-d<br>
- Return the delta of the values for a given hour or a given day.
- Used if the column contains a counter, as is the case for the
- KS300 rain column.</li>
- <li>everything else<br>
- The string is evaluated as a perl expression. @fld is the
- current line splitted by spaces. Note: The string/perl
- expression cannot contain spaces, as the part after the space
- will be considered as the next column_spec.</li>
- </ul></li>
- </ul></li>
- </ul>
- <br><br>
- Example:
- <ul><code><br>
- get outlog out-2008.log - 2008-01-01 2008-01-08 4:IR:int: 9:IR::
- </code></ul>
- <br>
- </ul>
- <a name="FileLogattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#addStateEvent">addStateEvent</a></li><br><br>
- <a name="archivedir"></a>
- <a name="archivecmd"></a>
- <a name="nrarchive"></a>
- <li>archivecmd / archivedir / nrarchive<br>
- When a new FileLog file is opened, the FileLog archiver wil be called.
- This happens only, if the name of the logfile has changed (due to
- time-specific wildcards, see the <a href="#FileLog">FileLog</a>
- section), and there is a new entry to be written into the file.
- <br>
- If the attribute archivecmd is specified, then it will be started as a
- shell command (no enclosing " is needed), and each % in the command
- will be replaced with the name of the old logfile.<br>
- If this attribute is not set, but nrarchive is set, then nrarchive old
- logfiles are kept along the current one while older ones are moved to
- archivedir (or deleted if archivedir is not set).<br>
- Note: "old" means here the first ones in the alphabetically soreted
- list. <br>
- Note: setting these attributes for the global instance will effect the
- <a href="#logfile">FHEM logfile</a> only.
- </li><br>
- <a name="archiveCompress"></a>
- <li>archiveCompress<br>
- If nrarchive, archivedir and archiveCompress is set, then the files
- in the archivedir will be compressed.
- </li><br>
- <a name="createGluedFile"></a>
- <li>createGluedFile<br>
- If set (to 1), and the SVG-Plot requests a time-range wich is stored
- in two files, a temporary file with the content of both files will be
- created, in order to satisfy the request.
- </li><br>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- <br>
- <a name="eventOnThreshold"></a>
- <li>eventOnThreshold<br>
- If set (to a nonzero number), the event linesInTheFile will be
- generated, if the lines in the file is a multiple of the set number.
- Note: the counter is only correct for files created after this
- feature was implemented. A FHEM crash or kill will falsify the counter.
- </li><br>
- <a name="logtype"></a>
- <li>logtype<br>
- Used by the pgm2 webfrontend to offer gnuplot/SVG images made from the
- logs. The string is made up of tokens separated by comma (,), each
- token specifies a different gnuplot program. The token may contain a
- colon (:), the part before the colon defines the name of the program,
- the part after is the string displayed in the web frontend. Currently
- following types of gnuplot programs are implemented:<br>
- <ul>
- <li>fs20<br>
- Plots on as 1 and off as 0. The corresponding filelog definition
- for the device fs20dev is:<br>
- define fslog FileLog log/fs20dev-%Y-%U.log fs20dev
- </li>
- <li>fht<br>
- Plots the measured-temp/desired-temp/actuator lines. The
- corresponding filelog definitions (for the FHT device named
- fht1) looks like:<br>
- <code>define fhtlog1 FileLog log/fht1-%Y-%U.log fht1:.*(temp|actuator).*</code>
- </li>
- <li>temp4rain10<br>
- Plots the temperature and rain (per hour and per day) of a
- ks300. The corresponding filelog definitions (for the KS300
- device named ks300) looks like:<br>
- define ks300log FileLog log/fht1-%Y-%U.log ks300:.*H:.*
- </li>
- <li>hum6wind8<br>
- Plots the humidity and wind values of a
- ks300. The corresponding filelog definition is the same as
- above, both programs evaluate the same log.
- </li>
- <li>text<br>
- Shows the logfile as it is (plain text). Not gnuplot definition
- is needed.
- </li>
- </ul>
- Example:<br>
- attr ks300log1 logtype
- temp4rain10:Temp/Rain,hum6wind8:Hum/Wind,text:Raw-data
- </li><br>
- <li><a href="#mseclog">mseclog</a></li><br>
- <a name="reformatFn"></a>
- <li>reformatFn<br>
- used to convert "foreign" logfiles for the SVG Module, contains the
- name(!) of a function, which will be called with a "raw" line from the
- original file, and has to return a line in "FileLog" format.<br>
- E.g. to visualize the NTP loopstats, set reformatFn to ntpLoopstats, and
- copy the following into your 99_myUtils.pm:
- <pre><code>
- sub
- ntpLoopstats($)
- {
- my ($d) = @_;
- return $d if($d !~ m/^(\d{5}) (\d+)\.(\d{3}) (.*)$/);
- my ($r, $t) = ($4, FmtDateTime(($1-40587)*86400+$2));
- $t =~ s/ /_/;
- return "$t ntpLoopStats $r";
- }</code></pre>
- </li>
- </ul>
- <br>
- </ul>
- =end html
- =begin html_DE
- <a name="FileLog"></a>
- <h3>FileLog</h3>
- <ul>
- <br>
- <a name="FileLogdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> FileLog <filename> <regexp> [readonly]</code>
- <br><br>
- Speichert Ereignisse in einer Log-Datei mit Namen <code><filename></code>. Das Log-Format ist
- <ul><code><br>
- YYYY-MM-DD_HH:MM:SS <device> <event><br>
- <br></code></ul>
- Der Ausdruck unter regexp wird anhand des Gerätenames überprüft und zwar
- devicename:event oder der timestamp:devicename:event-Kombination.
- Der regexp muss mit dem kompletten String übereinstimmen und nicht nur teilweise.
- <br>
- <code><filename></code> können %-wildcards der POSIX
- strftime-Funktion des darunterliegenden OS enthalten (siehe auch strftime
- Beschreibung).
- Allgemein gebräuchliche Wildcards sind:
- <ul>
- <li><code>%d</code> Tag des Monats (01..31)</li>
- <li><code>%m</code> Monat (01..12)</li>
- <li><code>%Y</code> Jahr (1970...)</li>
- <li><code>%w</code> Wochentag (0..6); beginnend mit Sonntag (0)</li>
- <li><code>%j</code> Tag des Jahres (001..366)</li>
- <li><code>%U</code> Wochennummer des Jahres, wobei Wochenbeginn = Sonntag (00..53)</li>
- <li><code>%W</code> Wochennummer des Jahres, wobei Wochenbeginn = Montag (00..53)</li>
- </ul>
- FHEM ersetzt <code>%L</code> mit dem Wert des global logdir Attributes.<br>
- Bevor <code>%V</code> für ISO 8601 Wochennummern verwendet werden,
- muss überprüft werden, ob diese Funktion durch das Brriebssystem
- unterstützt wird (Es kann sein, dass %V nicht umgesetzt wird, durch
- einen Leerstring ersetzt wird oder durch eine falsche ISO-Wochennummer
- dargestellt wird - besonders am Jahresanfang)
- Bei der Verwendung von <code>%V</code> muss gleichzeitig für das Jahr
- ein <code>%G</code> anstelle von <code>%Y</code> benutzt werden.<br>
- Falls man readonly spezifiziert, dann wird die Datei nur zum visualisieren
- verwendet, und nicht zum Schreiben geöffnet.
- <br>
- Beispiele:
- <ul>
- <code>define lamplog FileLog %L/lamp.log lamp</code><br>
- <code>define wzlog FileLog ./log/wz-%Y-%U.log
- wz:(measured-temp|actuator).*</code><br>
- Mit ISO 8601 Wochennummern falls unterstützt:<br>
- <code>define wzlog FileLog ./log/wz-%G-%V.log
- wz:(measured-temp|actuator).*</code><br>
- </ul>
- <br>
- </ul>
- <a name="FileLogset"></a>
- <b>Set </b>
- <ul>
- <li>reopen
- <ul>
- Erneutes Öffnen eines FileLogs nach händischen
- Änderungen in dieser Datei.
- </ul></li>
- <li>clear
- <ul>
- Löschen und erneutes Öffnen eines FileLogs.
- </ul></li>
- <li>addRegexpPart <device> <regexp>
- <ul>
- Fügt ein regexp Teil hinzu, der als device:regexp aufgebaut ist.
- Die Teile werden nach Regexp-Regeln mit | getrennt. Achtung: durch
- hinzufügen können manuell erzeugte Regexps ungültig
- werden.
- </ul></li>
- <li>removeRegexpPart <re>
- <ul>
- Entfernt ein regexp Teil. Die Inkonsistenz von addRegexpPart /
- removeRegexPart-Argumenten hat seinen Ursprung in der Wiederverwendung
- von Javascript-Funktionen.
- </ul></li>
- <li>absorb secondFileLog
- <ul>
- Führt den gegenwärtigen Log und den secondFileLog zu einer
- gemeinsamen Datei zusammen, fügt danach die regexp des
- secondFileLog dem gegenwärtigen Filelog hinzu und löscht dann
- anschließend das secondFileLog.<br>
- Dieses Komanndo wird zur Erzeugung von kombinierten Plots (weblinks)
- benötigt.<br>
- <b>Hinweise:</b>
- <ul>
- <li>secondFileLog wird gelöscht (d.h. die FHEM-Definition und
- die Datei selbst).</li>
- <li>nur das aktuelle File wird zusammengeführt, keine
- archivierten Versionen.</li>
- <li>Weblinks, die das secondFilelog benutzen werden unbrauchbar, sie
- müssen deshalb auf das neue Logfile angepasst oder
- gelöscht werden.</li>
- </ul>
- </ul></li>
- <br>
- </ul>
- <br>
- <a name="FileLogget"></a>
- <b>Get</b>
- <ul>
- <code>get <name> <infile> <outfile> <from>
- <to> <column_spec> </code>
- <br><br>
- Liest Daten aus einem Logfile und wird von einem Frontend benötigt, um
- Daten ohne direkten Zugriff aus der Datei zu lesen.<br>
- <ul>
- <li><infile><br>
- Name des Logfiles, auf das zugegriffen werden soll. Sonderfälle:
- "-" steht für das aktuelle Logfile, und "CURRENT" öffnet die
- zum "from" passende Datei.</li>
- <li><outfile><br>
- Bei einem "-", bekommt man die Daten auf der aktuellen Verbindung
- zurück, anderenfall ist es das Name (eigentlich Prefix, s.u.) des
- Output-Files. Wenn mehr als ein File angesprochen wird, werden die
- einzelnen Dateinamen durch ein "-" getrennt, anderenfalls werden die
- Daten in einzelne Dateien geschrieben, die - beginnend mit 0 -
- durchnummeriert werden.
- </li>
- <li><from> <to><br>
- Bezeichnet den gewünschten Datenbereich. Die beiden Elemente
- müssen ganz oder mit dem Anfang des Zeitformates
- übereinstimmen.</li>
- <li><column_spec><br>
- Jede column_spec sendet die gewünschten Daten entweder in eine
- gesonderte Datei oder über die gegenwärtige Verbindung durch
- "-" getrennt.<br>
- Syntax: <col>:<regexp>:<default>:<fn><br>
- <ul>
- <li><col>
- gibt die Spaltennummer zurück, beginnend mit 1 beim Datum.
- Wenn die Spaltenmummer in doppelten Anführungszeichen steht,
- handelt es sich um einen festen Text und nicht um eine
- Spaltennummer.</li>
- <li><regexp>
- gibt, falls vorhanden, Zeilen mit Inhalten von regexp zurück.
- Groß- und Kleinschreibung beachten. </li>
- <li><default><br>
- Wenn keine Werte gefunden werden, und der Default-Wert
- (Voreinstellung) wurde gesetzt, wird eine Zeile zurückgegeben,
- die den von-Wert (from) und diesen Default-Wert enthält.
- Dieses Leistungsmerkmal ist notwendig, da gnuplot abbricht, wenn
- ein Datensatz keine Daten enthält.
- </li>
- <li><fn>
- Kann folgende Inhalte haben:
- <ul>
- <li>int<br>
- Löst den Integer-Wert zu Beginn eines Strings heraus. Wird
- z.B. bei 10% gebraucht.</li>
- <li>delta-h oder delta-d<br>
- Gibt nur den Unterschied der Werte-Spalte pro
- Stunde oder pro Tag aus. Wird benötigt, wenn die Spalte
- einen Zähler enthält, wie im Falles des KS300 in der
- Spalte für die Regenmenge.</li>
- <li>alles andere<br>
- Dieser String wird als Perl-Ausdruck ausgewertet. @fld enthaelt
- die aktuelle Zeile getrennt durch Leerzeichen. Achtung:
- Dieser String/Perl-Ausdruck darf keine Leerzeichen enthalten.
- </li>
- </ul></li>
- </ul></li>
- </ul>
- <br><br>
- Beispiel:
- <ul><code><br>
- get outlog out-2008.log - 2008-01-01 2008-01-08 4:IR:int: 9:IR::
- </code></ul>
- <br>
- </ul>
- <a name="FileLogattr"></a>
- <b>Attribute</b>
- <ul>
- <li><a href="#addStateEvent">addStateEvent</a></li><br><br>
- <a name="archivedir"></a>
- <a name="archivecmd"></a>
- <a name="nrarchive"></a>
- <li>archivecmd / archivedir / nrarchive<br>
- Wenn eine neue FileLog-Datei geöffnet wird, wird der FileLog
- archiver aufgerufen. Das geschieht aber nur , wenn der Name der Datei
- sich geändert hat(abhängig von den zeitspezifischen
- Wildcards, die weiter oben unter <a href="#FileLogdefine">FileLog
- (define)</a> beschrieben werden) und gleichzeitig ein neuer Datensatz
- in diese Datei geschrieben werden muss. <br>
- Wenn das Attribut archivecmd benutzt wird, startet es als
- shell-Kommando ( eine Einbettung in " ist nicht notwendig), und jedes %
- in diesem Befehl wird durch den Namen des alten Logfiles ersetzt.<br>
- Wenn dieses Attribut nicht gesetzt wird, aber dafür nrarchive,
- werden nrarchive viele Logfiles im aktuellen Verzeichnis gelassen, und
- ältere Dateien in das Archivverzeichnis (archivedir) verschoben
- (oder gelöscht, falls kein archivedir gesetzt wurde).<br>
- Achtung: "ältere Dateien" sind die, die in der alphabetisch
- sortierten Liste oben sind.<br>
-
- Hinweis: Werden diese Attribute als global instance gesetzt, hat das
- auschließlich auf das <a href="#logfile">FHEM logfile</a>
- Auswirkungen. </li><br>
- <a name="archiveCompress"></a>
- <li>archiveCompress<br>
- Falls nrarchive, archivedir und archiveCompress gesetzt ist, dann
- werden die Dateien im archivedir komprimiert abgelegt.
- </li><br>
- <a name="createGluedFile"></a>
- <li>createGluedFile<br>
- Falls gesetzt (1), und im SVG-Plot ein Zeitbereich abgefragt wird, was
- in zwei Logdateien gespeichert ist, dann wird für die Anfrage eine
- temporäre Datei mit dem Inhalt der beiden Dateien erzeugt.
- </li><br>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- <br>
- <a name="eventOnThreshold"></a>
- <li>eventOnThreshold<br>
- Falls es auf eine (nicht Null-) Zahl gesetzt ist, dann wird das
- linesInTheFile Event generiert, falls die Anzahl der Zeilen in der
- Datei ein Mehrfaches der gesetzen Zahl ist. Achtung: der Zähler ist
- nur für solche Dateien korrekt, die nach dem Impementieren dieses
- Features angelegt wurden. Ein Absturz/Abschuß von FHEM
- verfälscht die Zählung.
- </li><br>
- <a name="logtype"></a>
- <li>logtype<br>
- Wird vom SVG Modul benötigt, um daten grafisch aufzubereiten.
- Der String wird aus komma-separierten Tokens
- (,) erzeugt, wobei jeder Token ein eigenes gnuplot-Programm bezeichnet.
- Die Token können Doppelpunkte (:) enthalten. Der Teil vor dem
- Doppelpunkt bezeichnet den Namen des Programms; der Teil nach dem
- Doppelpunkt ist der String, der im Web.Frontend dargestellt werden
- soll. Gegenwärtig sind folgende Typen von gnuplot-Programmen
- implementiert:<br>
- <ul>
- <li>fs20<br>
- Zeichnet on als 1 and off als 0. Die geeignete
- filelog-Definition für das Gerät fs20dev lautet:<br>
- define fslog FileLog log/fs20dev-%Y-%U.log fs20dev
- </li>
- <li>fht<br>
- Zeichnet die Ist-Temperatur/Soll-temperatur/Aktor Kurven. Die
- passende FileLog-Definition (für das FHT-Gerät mit
- Namen fht1)sieht wie folgt aus: <br>
- <code>define fhtlog1 FileLog log/fht1-%Y-%U.log
- fht1:.*(temp|actuator).*</code>
- </li>
- <li>temp4rain10<br>
- Zeichnet eine Kurve aus der Temperatur und dem Niederschlag (pro
- Stunde und pro Tag) eines KS300. Die dazu passende
- FileLog-Definition (für das KS300
- Gerät mit Namen ks300) sieht wie folgt aus:<br>
- define ks300log FileLog log/fht1-%Y-%U.log ks300:.*H:.*
- </li>
- <li>hum6wind8<br>
- Zeichnet eine Kurve aus der Feuchtigkeit und der
- Windgeschwindigkeit eines ks300. Die geeignete
- FileLog-Definition ist identisch mit der vorhergehenden
- Definition. Beide programme erzeugen das gleiche Log.
- </li>
- <li>text<br>
- Zeigt das LogFile in seiner ursprünglichen Form (Nur
- Text).Eine gnuplot-Definition ist nicht notwendig.
- </li>
- </ul>
- Beispiel:<br> attr ks300log1 logtype
- temp4rain10:Temp/Rain,hum6wind8:Hum/Wind,text:Raw-data
- </li><br>
- <li><a href="#mseclog">mseclog</a></li><br>
- <a name="reformatFn"></a>
- <li>reformatFn<br>
- wird verwendet, um "fremde" Dateien für die SVG-Anzeige ins
- FileLog-Format zu konvertieren. Es enthält nur den Namen einer
- Funktion, der mit der ursprünglichen Zeile aufgerufen wird. Z.Bsp.
- um die NTP loopstats Datei zu visualisieren kann man den Wert von
- reformatFn auf ntpLoopstats setzen, und folgende Funktion in
- 99_myUtils.pm definieren:
- <pre><code>
- sub
- ntpLoopstats($)
- {
- my ($d) = @_;
- return $d if($d !~ m/^(\d{5}) (\d+)\.(\d{3}) (.*)$/);
- my ($r, $t) = ($4, FmtDateTime(($1-40587)*86400+$2));
- $t =~ s/ /_/;
- return "$t ntpLoopStats $r";
- }</code></pre>
- </li>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|