diff --git a/fhem/CHANGED b/fhem/CHANGED index f915852e2..86d44b86b 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII - SVN + - change: weblink fileplot/dbplot converted to SVG device (+FHEMWEB cleanup) - change: VIERA: Added support for get dropdown - change: EnOcean: Manufacturer Specific Applications (EEP A5-3F-7F), Shutter: readings position and anglePos are updated automatically, if command diff --git a/fhem/FHEM/01_FHEMWEB.pm b/fhem/FHEM/01_FHEMWEB.pm index 266ff0880..f18f81992 100755 --- a/fhem/FHEM/01_FHEMWEB.pm +++ b/fhem/FHEM/01_FHEMWEB.pm @@ -13,7 +13,6 @@ sub FW_IconURL($); sub FW_iconName($); sub FW_iconPath($); sub FW_answerCall($); -sub FW_calcWeblink($$); sub FW_dev2image($;$); sub FW_devState($$@); sub FW_digestCgi($); @@ -36,26 +35,20 @@ sub FW_returnFileAsStream($$$$$); sub FW_roomOverview($); sub FW_select($$$$$@); sub FW_serveSpecial($$$$); -sub FW_showLog($); sub FW_showRoom(); sub FW_style($$); sub FW_submit($$@); -sub FW_substcfg($$$$$$); sub FW_textfield($$$); sub FW_textfieldv($$$$); sub FW_updateHashes(); -sub FW_zoomLink($$$); -use vars qw($FW_dir); # base directory for web server: the first available - # from $modpath/www, $modpath/FHEM -use vars qw($FW_icondir); # icon base directory for web server: the first - # available from $FW_dir/icons, $FW_dir -use vars qw($FW_cssdir); # css directory for web server: the first available - # from $FW_dir/css, $FW_dir -use vars qw($FW_gplotdir);# gplot directory for web server: the first - # available from $FW_dir/gplot,$FW_dir +use vars qw($FW_dir); # base directory for web server +use vars qw($FW_icondir); # icon base directory +use vars qw($FW_cssdir); # css directory +use vars qw($FW_gplotdir);# gplot directory use vars qw($MW_dir); # moddir (./FHEM), needed by edit Files in new # structure + use vars qw($FW_ME); # webname (default is fhem), used by 97_GROUP/weblink use vars qw($FW_ss); # is smallscreen, needed by 97_GROUP/95_VIEW use vars qw($FW_tp); # is touchpad (iPad / etc) @@ -64,6 +57,7 @@ use vars qw($FW_sp); # stylesheetPrefix # global variables, also used by 97_GROUP/95_VIEW/95_FLOORPLAN use vars qw(%FW_types); # device types, use vars qw($FW_RET); # Returned data (html) +use vars qw($FW_RETTYPE); # image/png or the like use vars qw($FW_wname); # Web instance use vars qw($FW_subdir); # Sub-path in URL, used by FLOORPLAN/weblink use vars qw(%FW_pos); # scroll position @@ -73,6 +67,9 @@ 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_webArgs); # all arguments specified in the GET use vars qw(@FW_fhemwebjs);# List of fhemweb*js scripts to load +use vars qw($FW_detail); # currently selected device for detail view +use vars qw($FW_cmdret); # Returned data by the fhem call +use vars qw($FW_room); # currently selected room my $FW_zlib_checked; my $FW_use_zlib = 1; @@ -84,14 +81,10 @@ my $FW_formmethod = "post"; # Note: for delivering SVG plots we fork my @FW_httpheader; # HTTP header, line by line my @FW_enc; # Accepted encodings (browser header) -my $FW_cmdret; # Returned data by the fhem call my $FW_data; # Filecontent from browser when editing a file -my $FW_detail; # currently selected device for detail view -my %FW_devs; # hash of from/to entries per device my %FW_icons; # List of icons my @FW_iconDirs; # Directory search order for icons my $FW_RETTYPE; # image/png or the like -my $FW_room; # currently selected room my %FW_rooms; # hash of all rooms my %FW_types; # device types, for sorting my %FW_hiddengroup;# hash of hidden groups @@ -214,7 +207,6 @@ FW_Read($) $FW_cname = $name; $FW_subdir = ""; - my $ll = GetLogLevel($FW_wname,4); my $c = $hash->{CD}; if(!$FW_zlib_checked) { $FW_zlib_checked = 1; @@ -223,18 +215,15 @@ FW_Read($) eval { require Compress::Zlib; }; if($@) { $FW_use_zlib = 0; - Log 1, $@; - Log 1, "$FW_wname: Can't load Compress::Zlib, deactivating compression"; + Log3 $FW_wname, 1, $@; + Log3 $FW_wname, 1, + "$FW_wname: Can't load Compress::Zlib, deactivating compression"; $attr{$FW_wname}{fwcompress} = 0; } } } - # This is a hack... Dont want to do it each time after a fork. - if(!$modules{SVG}{LOADED} && -f "$attr{global}{modpath}/FHEM/98_SVG.pm") { - my $ret = CommandReload(undef, "98_SVG"); - Log 1, $ret if($ret); - } + # Data from HTTP Client my $buf; @@ -242,7 +231,7 @@ FW_Read($) if(!defined($ret) || $ret <= 0) { CommandDelete(undef, $name); - Log($ll, "Connection closed for $name"); + Log3 $FW_wname, 4, "Connection closed for $name"; return; } @@ -282,12 +271,12 @@ FW_Read($) if($secret && $basicAuth =~ m/^{.*}$/ || $headerOptions[0]) { eval "use MIME::Base64"; if($@) { - Log 1, $@; + Log3 $FW_wname, 1, $@; } else { my ($user, $password) = split(":", decode_base64($secret)); $pwok = eval $basicAuth; - Log 1, "basicAuth expression: $@" if($@); + Log3 $FW_wname, 1, "basicAuth expression: $@" if($@); } } if($headerOptions[0]) { @@ -321,7 +310,7 @@ FW_Read($) $hash->{LASTACCESS} = $now; $arg = "" if(!defined($arg)); - Log $ll, "HTTP $name GET $arg"; + Log3 $FW_wname, 4, "HTTP $name GET $arg"; my $pid; if(AttrVal($FW_wname, "plotfork", undef)) { # Process SVG rendering as a parallel process @@ -344,7 +333,7 @@ FW_Read($) my $length = length($FW_RET); my $expires = ($cacheable? ("Expires: ".localtime($now+900)." GMT\r\n") : ""); - Log $ll, "$arg / RL: $length / $FW_RETTYPE / $compressed / $expires"; + Log3 $FW_wname, 4, "$arg / RL:$length / $FW_RETTYPE / $compressed / $expires"; print $c "HTTP/1.1 200 OK\r\n", "Content-Length: $length\r\n", $expires, $compressed, $FW_headercors, @@ -423,7 +412,7 @@ FW_answerCall($) } else { my $c = $me->{CD}; - Log 4, "$FW_wname: redirecting $arg to $FW_ME"; + Log3 $FW_wname, 4, "$FW_wname: redirecting $arg to $FW_ME"; print $c "HTTP/1.1 302 Found\r\n", "Content-Length: 0\r\n", $FW_headercors, "Location: $FW_ME\r\n\r\n"; @@ -453,8 +442,6 @@ FW_answerCall($) my $docmd = 0; $docmd = 1 if($cmd && $cmd !~ /^showlog/ && - $cmd !~ /^logwrapper/ && - $cmd !~ /^toweblink/ && $cmd !~ /^style / && $cmd !~ /^edit/); @@ -474,28 +461,31 @@ FW_answerCall($) } ############################## - # Axels FHEMWEB modules... + # FHEMWEB extensions (FLOORPLOAN, SVG_WriteGplot, etc) + my $FW_contentFunc; if(defined($data{FWEXT})) { foreach my $k (sort keys %{$data{FWEXT}}) { my $h = $data{FWEXT}{$k}; - next if($arg !~ m/^$k/ || $h !~ m/HASH/ || !$h->{FUNC}); + next if($arg !~ m/^$k/); + $FW_contentFunc = $h->{CONTENTFUNC}; + next if($h !~ m/HASH/ || !$h->{FUNC}); + #Returns undef as FW_RETTYPE if it already sent a HTTP header no strict "refs"; - #Returns undef if it already sent a HTTP header - my $localType; - ($localType, $FW_RET) = &{$h->{FUNC}}($arg); + ($FW_RETTYPE, $FW_RET) = &{$h->{FUNC}}($arg); use strict "refs"; - if($FW_RET && $FW_RET eq "continue") { # Continue displaying the data - $FW_RET=""; - last; - } - $FW_RETTYPE = $localType; return defined($FW_RETTYPE) ? 0 : -1; } } #Now execute the command - $FW_cmdret = $docmd ? FW_fC($cmd, $cmddev) : ""; + $FW_cmdret = ""; + if($docmd) { + $FW_cmdret = FW_fC($cmd, $cmddev); + if($cmd =~ m/^define +([^ ]+) /) { # "redirect" after define to details + $FW_detail = $1; + } + } # Redirect after a command, to clean the browser URL window if($docmd && !$FW_cmdret && AttrVal($FW_wname, "redirectCmds", 1)) { @@ -511,25 +501,6 @@ FW_answerCall($) } FW_updateHashes(); - if($cmd =~ m/^showlog /) { - FW_showLog($cmd); - return 0; - } - - if($cmd =~ m/^toweblink (.*)$/) { - my @aa = split(":", $1); - my $max = 0; - for my $d (keys %defs) { - $max = ($1+1) if($d =~ m/^wl_(\d+)$/ && $1 >= $max); - } - $defs{$aa[0]}{currentlogfile} =~ m,([^/]*)$,; - $aa[2] = "CURRENT" if($1 eq $aa[2]); - $FW_cmdret = FW_fC("define wl_$max weblink fileplot $aa[0]:$aa[1]:$aa[2]"); - if(!$FW_cmdret) { - $FW_detail = "wl_$max"; - FW_updateHashes(); - } - } my $t = AttrVal("global", "title", "Home, Sweet Home"); @@ -605,11 +576,19 @@ FW_answerCall($) } FW_roomOverview($cmd); + if($FW_contentFunc) { + no strict "refs"; + my $ret = &{$FW_contentFunc}($arg); + use strict "refs"; + return $ret if($ret); + } + if($cmd =~ m/^style /) { FW_style($cmd,undef); } - elsif($cmd =~ /^logwrapper/) { return FW_logWrapper($cmd); } elsif($FW_detail) { FW_doDetail($FW_detail); } elsif($FW_room) { FW_showRoom(); } - elsif(!$FW_cmdret && AttrVal("global", "motd", "none") ne "none") { + elsif(!$FW_cmdret && + !$FW_contentFunc && + AttrVal("global", "motd", "none") ne "none") { my $motd = AttrVal("global","motd",undef); $motd =~ s/\n/
/g; FW_pO "
$motd
"; @@ -671,6 +650,7 @@ FW_digestCgi($) } ##################### +# create FW_rooms && FW_types sub FW_updateHashes() { @@ -991,7 +971,8 @@ FW_roomOverview($) if($defs{$lfn}) { # Add the current Logfile to the list if defined my @l = FW_fileList($defs{$lfn}{logfile}); my $fn = pop @l; - splice @list, 4,0, ("Logfile","$FW_ME?cmd=logwrapper%20$lfn%20text%20$fn"); + splice @list, 4,0, ("Logfile", + "$FW_ME/FileLog_logWrapper?dev=$lfn&type=text&file=$fn"); } my @me = split(",", AttrVal($FW_wname, "menuEntries", "")); @@ -1243,7 +1224,7 @@ FW_returnFileAsStream($$$$$) } if(!open(FH, $path)) { - Log 2, "FHEMWEB $FW_wname $path: $!"; + Log3 $FW_wname, 2, "FHEMWEB $FW_wname $path: $!"; FW_pO "
$path: $!
"; return 0; } @@ -1279,329 +1260,6 @@ FW_returnFileAsStream($$$$$) return -1; } -###################### -# Show the content of the log (plain text), or an image and offer a link -# to convert it to a weblink -# If text and no reverse required, try to return the data as a stream; -sub -FW_logWrapper($) -{ - my ($cmd) = @_; - my (undef, $d, $type, $file) = split(" ", $cmd, 4); - 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 "
";
-    my $suffix = "
".($FW_ss ? "
" : "")."
"; - - my $reverseLogs = AttrVal($FW_wname, "reverseLogs", 0); - if(!$reverseLogs) { - $suffix .= ""; - return FW_returnFileAsStream($path, $suffix, "text/html", 1, 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; - - } else { - FW_pO "
"; - FW_pO "
"; - FW_pO FW_zoomLink("cmd=$cmd;zoom=-1", "Zoom-in", "zoom in"); - FW_pO FW_zoomLink("cmd=$cmd;zoom=1", "Zoom-out","zoom out"); - FW_pO FW_zoomLink("cmd=$cmd;off=-1", "Prev", "prev"); - FW_pO FW_zoomLink("cmd=$cmd;off=1", "Next", "next"); - FW_pO ""; - FW_pO "
"; - FW_pO ""; - my $logtype = $defs{$d}{TYPE}; - my $wl = "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos); - my $arg = "$FW_ME?cmd=showlog $logtype $d $type $file$wl"; - if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { - my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); - FW_pO "\n"; - - } else { - FW_pO ""; - } - - FW_pO "
"; - FW_pH "cmd=toweblink $d:$type:$file", "Convert to weblink"; - FW_pO "
"; - FW_pO "
"; - - } - FW_pO ""; - return 0; -} - -sub -FW_readgplotfile($$$) -{ - my ($wl, $gplot_pgm, $file) = @_; - - ############################ - # Read in the template gnuplot file. Digest the #FileLog lines. Replace - # the plot directive with our own, as we offer a file for each line - my (@filelog, @data, $plot); - - my $wltype = ""; - $wltype = $defs{$wl}{WLTYPE} if($defs{$wl} && $defs{$wl}{WLTYPE}); - - open(FH, $gplot_pgm) || return (FW_fatal("$gplot_pgm: $!"), undef); - while(my $l = ) { - $l =~ s/\r//g; - my $plotfn = undef; - if($l =~ m/^#FileLog (.*)$/ && - ($wltype eq "fileplot" || $wl eq "FileLog")) { - $plotfn = $1; - } elsif ($l =~ m/^#DbLog (.*)$/ && - ($wltype eq "dbplot" || $wl eq "DbLog")) { - $plotfn = $1; - } elsif($l =~ "^plot" || $plot) { - $plot .= $l; - } else { - push(@data, $l); - } - - if($plotfn) { - my $specval = AttrVal($wl, "plotfunction", undef); - if ($specval) { - my @spec = split(" ",$specval); - my $spec_count=1; - foreach (@spec) { - $plotfn =~ s//$_/g; - $spec_count++; - } - } - push(@filelog, $plotfn); - } - } - close(FH); - - return (undef, \@data, $plot, \@filelog); -} - -sub -FW_substcfg($$$$$$) -{ - my ($splitret, $wl, $cfg, $plot, $file, $tmpfile) = @_; - - # interpret title and label as a perl command and make - # to all internal values e.g. $value. - - my $oll = $attr{global}{verbose}; - $attr{global}{verbose} = 0; # Else the filenames will be Log'ged - - if($file eq "CURRENT") { - my @a = split(":", $defs{$wl}{LINK}); - $file = $defs{$a[0]}{currentlogfile}; - $file =~ s+.*/++; - } - my $fileesc = $file; - $fileesc =~ s/\\/\\\\/g; # For Windows, by MarkusRR - my $title = AttrVal($wl, "title", "\"$fileesc\""); - - $title = AnalyzeCommand($FW_chash, "{ $title }"); - my $label = AttrVal($wl, "label", undef); - my @g_label; - if ($label) { - @g_label = split("::",$label); - foreach (@g_label) { - $_ = AnalyzeCommand($FW_chash, "{ $_ }"); - } - } - $attr{global}{verbose} = $oll; - - my $gplot_script = join("", @{$cfg}); - $gplot_script .= $plot if(!$splitret); - - $gplot_script =~ s//$tmpfile/g; - $gplot_script =~ s//$file/g; - - my $ps = AttrVal($wl,"plotsize",$FW_plotsize); - $gplot_script =~ s//$ps/g; - - $gplot_script =~ s//$title/g; - my $g_count=1; - if ($label) { - foreach (@g_label) { - $gplot_script =~ s//$_/g; - $plot =~ s//$_/g; - $g_count++; - } - } - - $plot =~ s/\r//g; # For our windows friends... - $gplot_script =~ s/\r//g; - - if($splitret == 1) { - my @ret = split("\n", $gplot_script); - return (\@ret, $plot); - } else { - return $gplot_script; - } -} - - -###################### -# Generate an image from the log via gnuplot or SVG -sub -FW_showLog($) -{ - my ($cmd) = @_; - my (undef, $wl, $d, $type, $file) = split(" ", $cmd, 5); - - my $pm = AttrVal($wl,"plotmode",$FW_plotmode); - - my $gplot_pgm = "$FW_gplotdir/$type.gplot"; - - if(!-r $gplot_pgm) { - my $msg = "Cannot read $gplot_pgm"; - Log 1, $msg; - - if($pm =~ m/SVG/) { # FW_fatal for SVG: - $FW_RETTYPE = "image/svg+xml"; - FW_pO ''; - FW_pO ''.$msg.''; - FW_pO ''; - return; - - } else { - return FW_fatal($msg); - - } - } - FW_calcWeblink($d,$wl); - - if($pm =~ m/gnuplot/) { - - my $tmpfile = "/tmp/file.$$"; - my $errfile = "/tmp/gnuplot.err"; - - if($pm eq "gnuplot" || !$FW_devs{$d}{from}) { - - # Looking for the logfile.... - $defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File - my $path = "$1/$file"; - $path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path); - return FW_fatal("Cannot read $path") if(!-r $path); - - my ($err, $cfg, $plot, undef) = FW_readgplotfile($wl, $gplot_pgm, $file); - return $err if($err); - my $gplot_script = FW_substcfg(0, $wl, $cfg, $plot, $file,$tmpfile); - - my $fr = AttrVal($wl, "fixedrange", undef); - if($fr) { - $fr =~ s/ /\":\"/; - $fr = "set xrange [\"$fr\"]\n"; - $gplot_script =~ s/(set timefmt ".*")/$1\n$fr/; - } - - open(FH, "|gnuplot >> $errfile 2>&1");# feed it to gnuplot - print FH $gplot_script; - close(FH); - - } elsif($pm eq "gnuplot-scroll") { - - - my ($err, $cfg, $plot, $flog) = FW_readgplotfile($wl, $gplot_pgm, $file); - return $err if($err); - - - # Read the data from the filelog - my ($f,$t)=($FW_devs{$d}{from}, $FW_devs{$d}{to}); - my $oll = $attr{global}{verbose}; - $attr{global}{verbose} = 0; # Else the filenames will be Log'ged - my @path = split(" ", FW_fC("get $d $file $tmpfile $f $t " . - join(" ", @{$flog}))); - $attr{global}{verbose} = $oll; - - - # replace the path with the temporary filenames of the filelog output - my $i = 0; - $plot =~ s/\".*?using 1:[^ ]+ /"\"$path[$i++]\" using 1:2 "/gse; - my $xrange = "set xrange [\"$f\":\"$t\"]\n"; - foreach my $p (@path) { # If the file is empty, write a 0 line - next if(!-z $p); - open(FH, ">$p"); - print FH "$f 0\n"; - close(FH); - } - - my $gplot_script = FW_substcfg(0, $wl, $cfg, $plot, $file, $tmpfile); - - open(FH, "|gnuplot >> $errfile 2>&1");# feed it to gnuplot - print FH $gplot_script, $xrange, $plot; - close(FH); - foreach my $p (@path) { - unlink($p); - } - } - $FW_RETTYPE = "image/png"; - open(FH, "$tmpfile.png"); # read in the result and send it - binmode (FH); # necessary for Windows - FW_pO join("", ); - close(FH); - unlink("$tmpfile.png"); - - } elsif($pm eq "SVG") { - - my ($err, $cfg, $plot, $flog) = FW_readgplotfile($wl, $gplot_pgm, $file); - return $err if($err); - - my ($f,$t)=($FW_devs{$d}{from}, $FW_devs{$d}{to}); - $f = 0 if(!$f); # From the beginning of time... - $t = 9 if(!$t); # till the end - - my $ret; - if(!$modules{SVG}{LOADED}) { - $ret = CommandReload(undef, "98_SVG"); - Log 1, $ret if($ret); - } - Log 5, "plotcommand: get $d $file INT $f $t " . join(" ", @{$flog}); - - $FW_RETTYPE = "image/svg+xml"; - - (my $cachedate = TimeNow()) =~ s/ /_/g; - my $SVGcache = (AttrVal($FW_wname, "SVGcache", undef) && $t lt $cachedate); - my $cDir = "$FW_dir/SVGcache"; - my $cName = "$cDir/$wl-$f-$t.svg"; - if($SVGcache && open(CFH, $cName)) { - FW_pO join("", ); - close(CFH); - - } else { - FW_fC("get $d $file INT $f $t " . join(" ", @{$flog}), 1); - ($cfg, $plot) = FW_substcfg(1, $wl, $cfg, $plot, $file, ""); - $ret = SVG_render($wl, $f, $t, $cfg, - $internal_data, $plot, $FW_wname, $FW_cssdir, $flog); - FW_pO $ret; - if($SVGcache) { - mkdir($cDir) if(! -d $cDir); - if(open(CFH, ">$cName")) { - print CFH $ret; - close(CFH); - } - } - } - - } - -} ################## sub @@ -1667,176 +1325,6 @@ FW_submit($$@) return $s; } -################## -# Generate the zoom and scroll images with links if appropriate -sub -FW_zoomLink($$$) -{ - my ($cmd, $img, $alt) = @_; - - my $prf; - $cmd =~ m/^(.*);([^;]*)$/; - ($prf, $cmd) = ($1, $2) if($2); - my ($d,$off) = split("=", $cmd, 2); - - my $val = $FW_pos{$d}; - $cmd = ($FW_detail ? "detail=$FW_detail": - ($prf ? $prf : "room=$FW_room")) . "&pos="; - - if($d eq "zoom") { - - my $n = 0; - my @FW_zoom = ("hour","qday","day","week","month","year"); - my %FW_zoom = map { $_, $n++ } @FW_zoom; - - $val = "day" if(!$val); - $val = $FW_zoom{$val}; - return "" if(!defined($val) || $val+$off < 0 || $val+$off >= int(@FW_zoom)); - $val = $FW_zoom[$val+$off]; - return "" if(!$val); - - # Approximation of the next offset. - my $w_off = $FW_pos{off}; - $w_off = 0 if(!$w_off); - - if ($val eq "hour") { - $w_off = $w_off*6; - } elsif($val eq "qday") { - $w_off = ($off < 0) ? $w_off*4 : int($w_off/6); - } elsif($val eq "day") { - $w_off = ($off < 0) ? $w_off*7 : int($w_off/4); - } elsif($val eq "week") { - $w_off = ($off < 0) ? $w_off*4 : int($w_off/7); - } elsif($val eq "month") { - $w_off = ($off < 0) ? $w_off*12: int($w_off/4); - } elsif($val eq "year") { - $w_off = int($w_off/12); - } - $cmd .= "zoom=$val;off=$w_off"; - - } else { - - return "" if((!$val && $off > 0) || ($val && $val+$off > 0)); # no future - $off=($val ? $val+$off : $off); - my $zoom=$FW_pos{zoom}; - $zoom = 0 if(!$zoom); - $cmd .= "zoom=$zoom;off=$off"; - - } - - return "  ".FW_pHPlain("$cmd", FW_makeImage($img, $alt)); -} - -################## -# Calculate either the number of scrollable weblinks (for $d = undef) or -# for the device the valid from and to dates for the given zoom and offset -sub -FW_calcWeblink($$) -{ - my ($d,$wl) = @_; - - my $pm = AttrVal($d,"plotmode",$FW_plotmode); - return if($pm eq "gnuplot"); - - my $frx; - if($defs{$wl}) { - my $fr = AttrVal($wl, "fixedrange", undef); - if($fr) { - #klaus fixed range day, week, month or year - if($fr eq "day" || $fr eq "week" || $fr eq "month" || $fr eq "year" ) { - $frx=$fr; - - } else { - my @range = split(" ", $fr); - my @t = localtime; - $FW_devs{$d}{from} = ResolveDateWildcards($range[0], @t); - $FW_devs{$d}{to} = ResolveDateWildcards($range[1], @t); - return; - } - } - } - - my $off = $FW_pos{$d}; - $off = 0 if(!$off); - $off += $FW_pos{off} if($FW_pos{off}); - - my $now = time(); - my $zoom = $FW_pos{zoom}; - $zoom = "day" if(!$zoom); - $zoom = $frx if ($frx); #for fixedrange {day|week|...} klaus - - - if($zoom eq "hour") { - my $t = $now + $off*3600; - my @l = localtime($t); - $FW_devs{$d}{from} - = sprintf("%04d-%02d-%02d_%02d:00:00",$l[5]+1900,$l[4]+1,$l[3],$l[2]); - @l = localtime($t+3600); - $FW_devs{$d}{to} - = sprintf("%04d-%02d-%02d_%02d:00:01",$l[5]+1900,$l[4]+1,$l[3],$l[2]); - - } elsif($zoom eq "qday") { - my $t = $now + $off*21600; - my @l = localtime($t); - $l[2] = int($l[2]/6)*6; - $FW_devs{$d}{from} = - sprintf("%04d-%02d-%02d_%02d:00:00",$l[5]+1900,$l[4]+1,$l[3],$l[2]); - @l = localtime($t+21600); - $l[2] = int($l[2]/6)*6; - $FW_devs{$d}{to} = - sprintf("%04d-%02d-%02d_%02d:00:01",$l[5]+1900,$l[4]+1,$l[3],$l[2]); - - } elsif($zoom eq "day") { - my $t = $now + $off*86400; - my @l = localtime($t); - $FW_devs{$d}{from} = - sprintf("%04d-%02d-%02d_00:00:00",$l[5]+1900,$l[4]+1,$l[3]); - @l = localtime($t+86400); - $FW_devs{$d}{to} = - sprintf("%04d-%02d-%02d_00:00:01",$l[5]+1900,$l[4]+1,$l[3]); - - } elsif($zoom eq "week") { - my @l = localtime($now); - my $start = (AttrVal($FW_wname, "endPlotToday", undef) ? 6 : $l[6]); - my $t = $now - ($start*86400) + ($off*86400)*7; - @l = localtime($t); - $FW_devs{$d}{from} = - sprintf("%04d-%02d-%02d_00:00:00",$l[5]+1900,$l[4]+1,$l[3]); - @l = localtime($t+7*86400); - $FW_devs{$d}{to} = - sprintf("%04d-%02d-%02d_00:00:01",$l[5]+1900,$l[4]+1,$l[3]); - - } elsif($zoom eq "month") { - my ($endDay, @l); - if(AttrVal($FW_wname, "endPlotToday", undef)) { - @l = localtime($now+86400); - $endDay = $l[3]; - $off--; - } else { - @l = localtime($now); - $endDay = 1; - } - while($off < -12) { # Correct the year - $off += 12; $l[5]--; - } - $l[4] += $off; - $l[4] += 12, $l[5]-- if($l[4] < 0); - $FW_devs{$d}{from} = - sprintf("%04d-%02d-%02d_00:00:00", $l[5]+1900, $l[4]+1,$endDay); - $l[4]++; - $l[4] = 0, $l[5]++ if($l[4] == 12); - $FW_devs{$d}{to} = - sprintf("%04d-%02d-%02d_00:00:01", $l[5]+1900, $l[4]+1,$endDay); - - } elsif($zoom eq "year") { - my @l = localtime($now); - $l[5] += $off; - $FW_devs{$d}{from} = sprintf("%04d-01-01_00:00:00", $l[5]+1900); - $FW_devs{$d}{to} = sprintf("%04d-01-01_00:00:01", $l[5]+1901); - - } -} - ################## sub FW_displayFileList($@) @@ -2077,12 +1565,13 @@ FW_pHPlain(@) { my ($link, $txt, $td) = @_; + $link = "?$link" if($link !~ m+^/+); my $ret = ""; $ret .= "" if($td); if($FW_ss || $FW_tp) { - $ret .= "$txt"; + $ret .= "$txt"; } else { - $ret .= "$txt"; + $ret .= "$txt"; } $ret .= "" if($td); return $ret; @@ -2301,7 +1790,7 @@ FW_dev2image($;$) my $devStateIcon = AttrVal($name, "devStateIcon", undef); if(defined($devStateIcon) && $devStateIcon =~ m/^{.*}$/) { my ($html, $link) = eval $devStateIcon; - Log 1, "devStateIcon $name: $@" if($@); + Log3 $FW_wname, 1, "devStateIcon $name: $@" if($@); return ($html, $link, 1) if(defined($html) && $html =~ m/^<.*>$/s); $devStateIcon = $html; } @@ -2632,7 +2121,7 @@ FW_closeOldClients() next if(!$defs{$dev}{TYPE} || $defs{$dev}{TYPE} ne "FHEMWEB" || !$defs{$dev}{LASTACCESS} || $defs{$dev}{inform} || ($now - $defs{$dev}{LASTACCESS}) < 60); - Log 4, "Closing connection $dev"; + Log3 $FW_wname, 4, "Closing connection $dev"; FW_Undef($defs{$dev}, ""); delete $defs{$dev}; } diff --git a/fhem/FHEM/92_FileLog.pm b/fhem/FHEM/92_FileLog.pm index 026723813..2a9756d68 100755 --- a/fhem/FHEM/92_FileLog.pm +++ b/fhem/FHEM/92_FileLog.pm @@ -5,9 +5,21 @@ package main; use strict; use warnings; use IO::File; -#use Devel::Size qw(size total_size); -use vars qw($FW_ss); # is smallscreen + +# This block is only needed when FileLog is loaded bevore FHEMWEB +sub FW_pO(@); +sub FW_pH(@); use vars qw($FW_ME); # webname (default is fhem), needed by 97_GROUP +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 seekTo($$$$); @@ -30,6 +42,8 @@ FileLog_Initialize($) $hash->{FW_summaryFn} = "FileLog_fhemwebFn"; $hash->{FW_detailFn} = "FileLog_fhemwebFn"; + $data{FWEXT}{"/FileLog_toSVG"}{CONTENTFUNC} = "FileLog_toSVG"; + $data{FWEXT}{"/FileLog_logWrapper"}{CONTENTFUNC} = "FileLog_logWrapper"; } @@ -254,6 +268,15 @@ FileLog_Set($@) return undef; } +sub +FileLog_loadSVG() +{ + if(!$modules{SVG}{LOADED} && -f "$attr{global}{modpath}/FHEM/98_SVG.pm") { + my $ret = CommandReload(undef, "98_SVG"); + Log 1, $ret if($ret); + } +} + ######################### sub FileLog_fhemwebFn($$$$) @@ -277,7 +300,7 @@ FileLog_fhemwebFn($$$$) } my ($lt, $name) = split(":", $ln); $name = $lt if(!$name); - $ret .= FW_pH("cmd=logwrapper $d $lt $f", + $ret .= FW_pH("$FW_ME/FileLog_logWrapper&dev=$d&type=$lt&file=$f", "
$name
", 1, "dval", 1); } $ret .= ""; @@ -337,17 +360,118 @@ FileLog_fhemwebFn($$$$) $ret .= ""; my $newIdx=1; - while($defs{"wl_${d}_$newIdx"}) { + while($defs{"SVG_${d}_$newIdx"}) { $newIdx++; } - my $name = "wl_${d}_$newIdx"; - $ret .= FW_pH("cmd=define $name weblink fileplot $d:template:CURRENT;". + my $name = "SVG_${d}_$newIdx"; + $ret .= FW_pH("cmd=define $name SVG $d:template:CURRENT;". "set $name copyGplotFile&detail=$name", - "
Create new SVG plot
", 0, "dval", 1); + "
Create SVG plot
", 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_pO '
FileLog_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 "
";
+    my $suffix = "
".($FW_ss ? "
" : "")."
"; + + my $reverseLogs = AttrVal($FW_wname, "reverseLogs", 0); + if(!$reverseLogs) { + $suffix .= ""; + return FW_returnFileAsStream($path, $suffix, "text/html", 1, 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; + + } else { + FileLog_loadSVG(); + FW_pO "
"; + FW_pO "
"; + 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 ""; + FW_pO "
"; + FW_pO ""; + my $logtype = $defs{$d}{TYPE}; + 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 "\n"; + + } else { + FW_pO ""; + } + + FW_pO "
"; + FW_pH "$FW_ME/FileLog_toSVG&arg=$d:$type:$file", "Create SVG instance"; + FW_pO "
"; + FW_pO "
"; + + } + return 0; +} + ################################### # We use this function to be able to scroll/zoom in the plots created from the diff --git a/fhem/FHEM/98_SVG.pm b/fhem/FHEM/98_SVG.pm index 602d71d90..7d50a7c22 100755 --- a/fhem/FHEM/98_SVG.pm +++ b/fhem/FHEM/98_SVG.pm @@ -7,24 +7,819 @@ use warnings; use POSIX; #use Devel::Size qw(size total_size); +# This block is only needed when SVG is loaded bevore FHEMWEB +sub FW_pO(@); +use vars qw($FW_ME); # webname (default is fhem), needed by 97_GROUP +use vars qw($FW_RET); # Returned data (html) +use vars qw($FW_RETTYPE); # image/png or the like +use vars qw($FW_cssdir); # css directory +use vars qw($FW_detail); # currently selected device for detail view +use vars qw($FW_dir); # base directory for web server +use vars qw($FW_gplotdir);# gplot directory for web server: the first +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_room); # currently selected room +use vars qw($FW_subdir); # Sub-path in URL, used by FLOORPLAN/weblink +use vars qw($FW_wname); # Web instance +use vars qw(%FW_hiddenroom); # hash of hidden rooms, used by weblink +use vars qw(%FW_pos); # scroll position +use vars qw(%FW_webArgs); # all arguments specified in the GET - -sub SVG_render($$$$$$$$$); -sub SVG_time_to_sec($); -sub SVG_fmtTime($$); -sub SVG_time_align($$); -sub SVG_doround($$$); -sub SVG_pO($); my $SVG_RET; # Returned data (SVG) - +sub SVG_calcWeblink($$); +sub SVG_doround($$$); +sub SVG_fmtTime($$); +sub SVG_pO($); +sub SVG_readgplotfile($$$); +sub SVG_render($$$$$$$$$); +sub SVG_showLog($); +sub SVG_substcfg($$$$$$); +sub SVG_time_align($$); +sub SVG_time_to_sec($); my ($SVG_lt, $SVG_ltstr); +my %SVG_devs; # hash of from/to entries per device ##################################### sub SVG_Initialize($) { my ($hash) = @_; + + $hash->{DefFn} = "SVG_Define"; + $hash->{AttrList} = "fixedrange plotsize label title plotfunction"; + $hash->{SetFn} = "SVG_Set"; + $hash->{FW_summaryFn} = "SVG_FwFn"; + $hash->{FW_detailFn} = "SVG_FwFn"; + $hash->{FW_atPageEnd} = 1; + $data{FWEXT}{"/SVG_WriteGplot"}{CONTENTFUNC} = "SVG_WriteGplot"; + $data{FWEXT}{"/SVG_showLog"}{FUNC} = "SVG_showLog"; +} + +##################################### +sub +SVG_Define($$) +{ + my ($hash, $def) = @_; + my ($name, $type, $arg) = split("[ \t]+", $def, 3); + + if(!$arg || $arg !~ m/^(.*):(.*):(.*)$/) { + return "Usage: define SVG ::"; + } + + $hash->{LOGDEVICE} = $1; + $hash->{GPLOTFILE} = $2; + $hash->{LOGFILE} = $3; + $hash->{STATE} = "initialized"; + + return undef; +} + +################## +sub +SVG_Set($@) +{ + my ($hash, @a) = @_; + my $me = $hash->{NAME}; + return "no set argument specified" if(int(@a) < 2); + + my $cmd = $a[1]; + return "Unknown argument $cmd, choose one of copyGplotFile:noArg" + if($cmd ne "copyGplotFile"); + + my $srcName = "$FW_gplotdir/$hash->{GPLOTFILE}.gplot"; + $hash->{GPLOTFILE} = $hash->{NAME}; + my $dstName = "$FW_gplotdir/$hash->{GPLOTFILE}.gplot"; + return "this is already a unique gplot file" if($srcName eq $dstName); + $hash->{DEF} = $hash->{LOGDEVICE} . ":". + $hash->{GPLOTFILE} . ":". + $hash->{LOGFILE}; + + open(SFH, $srcName) || return "Can't open $srcName: $!"; + open(DFH, ">$dstName") || return "Can't open $dstName: $!"; + while(my $l = ) { + print DFH $l; + } + close(SFH); close(DFH); + + return undef; +} + +################## +sub +SVG_FwDetail($@) +{ + my ($d, $text, $nobr)= @_; + return "" if(AttrVal($d, "group", "")); + my $alias= AttrVal($d, "alias", $d); + + my $ret = ($nobr ? "" : "
"); + $ret .= "$text " if($text); + $ret .= FW_pHPlain("detail=$d", $alias) if(!$FW_subdir); + $ret .= "
"; + return $ret; +} + +################## +sub +SVG_FwFn($$$$) +{ + my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. + my $hash = $defs{$d}; + my $ld = $defs{$hash->{LOGDEVICE}}; + my $ret = ""; + + # plots navigation buttons + if((!$pageHash || !$pageHash->{buttons}) && + AttrVal($d, "fixedrange", "x") !~ m/^[ 0-9:-]*$/) { + + $ret .= SVG_zoomLink("zoom=-1", "Zoom-in", "zoom in"); + $ret .= SVG_zoomLink("zoom=1", "Zoom-out","zoom out"); + $ret .= SVG_zoomLink("off=-1", "Prev", "prev"); + $ret .= SVG_zoomLink("off=1", "Next", "next"); + $pageHash->{buttons} = 1 if($pageHash); + $ret .= "
"; + } + + my $arg="$FW_ME/SVG_showLog?dev=$d". + "&logdev=$hash->{LOGDEVICE}". + "&gplotfile=$hash->{GPLOTFILE}". + "&logfile=$hash->{LOGFILE}". + "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos); + + if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { + my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); + $ret .= "
"; + $ret .= "\n"; + $ret .= "
"; + + } else { + $ret .= ""; + } + + if(!$pageHash) { + if($FW_plotmode eq "SVG") { + $ret .= SVG_PEdit($FW_wname,$d,$room,$pageHash) . "
"; + } + + } else { + $ret .= SVG_FwDetail($d, "", 1) if(!$FW_hiddenroom{detail}); + + } + + return $ret; +} + +sub +SVG_cb($$$) +{ + my ($v,$t,$c) = @_; + $c = ($c ? " checked" : ""); + return "$t "; +} + +sub +SVG_txt($$$$) +{ + my ($v,$t,$c,$sz) = @_; + $c = "" if(!defined($c)); + $c =~ s/"/\"/g; + return "$t "; +} + +sub +SVG_sel($$$@) +{ + my ($v,$l,$c,$fnData) = @_; + my @al = split(",",$l); + $c =~ s/\\x3a/:/g if($c); + return FW_select($v,$v,\@al,$c, "set", $fnData); +} + +sub +SVG_getRegFromFile($) +{ + my ($fName) = @_; + my $fh = new IO::File $fName; + if(!$fh) { + Log 1, "$fName: $!"; + return (3, "NoFile", "NoFile"); + } + $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>) { + 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(); + return ($maxcols+1, + join(",", sort keys %h), + join("
", grep /.+/,map { $h{$_} } sort keys %h)), + close(FH); +} + +sub +SVG_addTics($$) +{ + my ($in, $p) = @_; + return if(!$in || $in !~ m/^\((.*)\)$/); + map { $p->{"\"$2\""}=1 if(m/^ *([^ ]+) ([^ ]+) */); } split(",",$1); +} + +############################ +# gnuplot file "editor" +sub +SVG_PEdit($$$$) +{ + my ($FW_wname,$d,$room,$pageHash) = @_; + my $gp = "$FW_gplotdir/$defs{$d}{GPLOTFILE}.gplot"; + my $file = $defs{$defs{$d}{LOGDEVICE}}{currentlogfile}; + + my ($err, $cfg, $plot, $flog) = SVG_readgplotfile($d, $gp, $file); + my %conf = SVG_digestConf($cfg, $plot); + + my $ret .= "
"; + $ret .= FW_hidden("detail", $d); + $ret .= FW_hidden("gplotName", $gp); + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $conf{ylabel} =~ s/"//g if($conf{ylabel}); + $ret .= ""; + $conf{y2label} =~ s/"//g if($conf{y2label}); + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= SVG_cb("gridy", "left", $conf{hasygrid}); + $ret .= SVG_cb("gridy2","right",$conf{hasy2grid}); + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + $ret .= ""; + + $ret .= ""; + $ret .= ""; + $ret .=" "; + + my ($colnums, $colregs, $coldata) = SVG_getRegFromFile($file); + $colnums = join(",", 3..$colnums); + + my %tickh; + SVG_addTics($conf{ytics}, \%tickh); + SVG_addTics($conf{y2tics}, \%tickh); + $colnums = join(",", sort keys %tickh).",$colnums" if(%tickh); + + my $max = @{$conf{lType}}+1; + $max = 8 if($max > 8); + my $r = 0; + for($r=0; $r < $max; $r++) { + $ret .= ""; + } + $ret .= ""; + + $ret .= ""; + + $ret .= "
Plot title".SVG_txt("title", "", $conf{title}, 32)."
Y-Axis label".SVG_txt("ylabel", "left", $conf{ylabel}, 16)."".SVG_txt("y2label","right", $conf{y2label}, 16)."
Grid aligned
Range as [min:max]".SVG_txt("yrange", "left", $conf{yrange}, 16)."".SVG_txt("y2range", "right", $conf{y2range}, 16)."
Tics as (\"Txt\" val, ...)".SVG_txt("ytics", "left", $conf{ytics}, 16)."".SVG_txt("y2tics","right", $conf{y2tics}, 16)."
Diagramm labelInput:Column,Regexp,DefaultValue,FunctionY-Axis,Plot-Type,Style,Width
"; + $ret .= SVG_txt("title_${r}", "", !$conf{lTitle}[$r]&&$r<($max-1) ? + "notitle" : $conf{lTitle}[$r], 12); + $ret .= ""; + my @f = split(":", ($flog->[$r] ? $flog->[$r] : ":::"), 4); + $ret .= SVG_sel("cl_${r}", $colnums, $f[0]); + $ret .= SVG_sel("re_${r}", $colregs, $f[1]); + $ret .= SVG_txt("df_${r}", "", $f[2], 1); + $ret .= SVG_txt("fn_${r}", "", $f[3], 6); + + $ret .= ""; + my $v = $conf{lAxis}[$r]; + $ret .= SVG_sel("axes_${r}", "left,right", + ($v && $v eq "x1y1") ? "left" : "right"); + $ret .= SVG_sel("type_${r}", "lines,points,steps,fsteps,histeps,bars", + $conf{lType}[$r]); + my $ls = $conf{lStyle}[$r]; + if($ls) { + $ls =~ s/class=//g; + $ls =~ s/"//g; + } + $ret .= SVG_sel("style_${r}", "l0,l1,l2,l3,l4,l5,l6,l7,l8,". + "l0fill,l1fill,l2fill,l3fill,l4fill,l5fill,l6fill", $ls); + my $lw = $conf{lWidth}[$r]; + if($lw) { + $lw =~ s/.*stroke-width://g; + $lw =~ s/"//g; + } + $ret .= SVG_sel("width_${r}", "0.2,0.5,1,1.5,2,3,4", ($lw ? $lw : 1)); + $ret .= "
"; + $ret .= "Example lines for input:
$coldata
"; + $ret .= FW_submit("submit", "Write .gplot file")."
"; +} + +################## +# Generate the zoom and scroll images with links if appropriate +sub +SVG_zoomLink($$$) +{ + my ($cmd, $img, $alt) = @_; + + my $prf; + $cmd =~ m/^(.*);([^;]*)$/; + if($2) { + ($prf, $cmd) = ($1, $2); + $prf =~ s/&pos=.*//; + } + my ($d,$off) = split("=", $cmd, 2); + + my $val = $FW_pos{$d}; + $cmd = ($FW_detail ? "detail=$FW_detail": + ($prf ? $prf : "room=$FW_room")) . "&pos="; + if($d eq "zoom") { + + my $n = 0; + my @FW_zoom = ("hour","qday","day","week","month","year"); + my %FW_zoom = map { $_, $n++ } @FW_zoom; + + $val = "day" if(!$val); + $val = $FW_zoom{$val}; + return "" if(!defined($val) || $val+$off < 0 || $val+$off >= int(@FW_zoom)); + $val = $FW_zoom[$val+$off]; + return "" if(!$val); + + # Approximation of the next offset. + my $w_off = $FW_pos{off}; + $w_off = 0 if(!$w_off); + + if ($val eq "hour") { + $w_off = $w_off*6; + } elsif($val eq "qday") { + $w_off = ($off < 0) ? $w_off*4 : int($w_off/6); + } elsif($val eq "day") { + $w_off = ($off < 0) ? $w_off*7 : int($w_off/4); + } elsif($val eq "week") { + $w_off = ($off < 0) ? $w_off*4 : int($w_off/7); + } elsif($val eq "month") { + $w_off = ($off < 0) ? $w_off*12: int($w_off/4); + } elsif($val eq "year") { + $w_off = int($w_off/12); + } + $cmd .= "zoom=$val;off=$w_off"; + + } else { + + return "" if((!$val && $off > 0) || ($val && $val+$off > 0)); # no future + $off=($val ? $val+$off : $off); + my $zoom=$FW_pos{zoom}; + $zoom = 0 if(!$zoom); + $cmd .= "zoom=$zoom;off=$off"; + + } + + return "  ".FW_pHPlain("$cmd", FW_makeImage($img, $alt)); +} + + +sub +SVG_WriteGplot($) +{ + my ($arg) = @_; + FW_digestCgi($arg); + + if(!defined($FW_webArgs{cl_0})) { + FW_pO "missing data in logfile: won't write incomplete .gplot definition"; + return 0; + } + + my $hasTl; + for(my $i=0; $i <= 8; $i++) { + $hasTl = 1 if($FW_webArgs{"title_$i"}); + } + return 0 if(!$hasTl); + + my $fName = $FW_webArgs{gplotName}; + return if(!$fName); + if(!open(FH, ">$fName")) { + FW_pO "SVG_WriteGplot: Can't write $fName"; + return 0; + } + print FH "# Created by FHEMWEB, ".TimeNow()."\n"; + print FH "set terminal png transparent size crop\n"; + print FH "set output '.png'\n"; + print FH "set xdata time\n"; + print FH "set timefmt \"%Y-%m-%d_%H:%M:%S\"\n"; + print FH "set xlabel \" \"\n"; + print FH "set title '$FW_webArgs{title}'\n"; + print FH "set ytics ".$FW_webArgs{ytics}."\n"; + print FH "set y2tics ".$FW_webArgs{y2tics}."\n"; + print FH "set grid".($FW_webArgs{gridy} ? " ytics" :""). + ($FW_webArgs{gridy2} ? " y2tics":"")."\n"; + print FH "set ylabel \"$FW_webArgs{ylabel}\"\n"; + print FH "set y2label \"$FW_webArgs{y2label}\"\n"; + print FH "set yrange $FW_webArgs{yrange}\n" if($FW_webArgs{yrange}); + print FH "set y2range $FW_webArgs{y2range}\n" if($FW_webArgs{y2range}); + print FH "\n"; + + my @plot; + for(my $i=0; $i <= 8; $i++) { + next if(!$FW_webArgs{"title_$i"}); + my $re = $FW_webArgs{"re_$i"}; + $re = "" if(!defined($re)); + $re =~ s/:/\\x3a/g; + print FH "#FileLog ". $FW_webArgs{"cl_$i"} .":$re:". + $FW_webArgs{"df_$i"} .":". + $FW_webArgs{"fn_$i"} ."\n"; + push @plot, "\"\" using 1:2 axes ". + ($FW_webArgs{"axes_$i"} eq "right" ? "x1y2" : "x1y1"). + ($FW_webArgs{"title_$i"} eq "notitle" ? " notitle" : + " title '".$FW_webArgs{"title_$i"} ."'"). + " ls " .$FW_webArgs{"style_$i"} . + " lw " .$FW_webArgs{"width_$i"} . + " with " .$FW_webArgs{"type_$i"}; + } + print FH "\n"; + print FH "plot ".join(",\\\n ", @plot)."\n"; + close(FH); + + return 0; +} + +sub +SVG_readgplotfile($$$) +{ + my ($wl, $gplot_pgm, $file) = @_; + + ############################ + # Read in the template gnuplot file. Digest the #FileLog lines. Replace + # the plot directive with our own, as we offer a file for each line + my (@filelog, @data, $plot); + + my $ldType = $wl; + $ldType = $defs{$defs{$wl}{LOGDEVICE}}{TYPE} + if($defs{$wl}{LOGDEVICE} && $defs{$defs{$wl}{LOGDEVICE}}); + + open(FH, $gplot_pgm) || return (FW_fatal("$gplot_pgm: $!"), undef); + while(my $l = ) { + $l =~ s/\r//g; + my $plotfn = undef; + if($l =~ m/^#$ldType (.*)$/) { + $plotfn = $1; + } elsif($l =~ "^plot" || $plot) { + $plot .= $l; + } else { + push(@data, $l); + } + + if($plotfn) { + my $specval = AttrVal($wl, "plotfunction", undef); + if ($specval) { + my @spec = split(" ",$specval); + my $spec_count=1; + foreach (@spec) { + $plotfn =~ s//$_/g; + $spec_count++; + } + } + push(@filelog, $plotfn); + } + } + close(FH); + + return (undef, \@data, $plot, \@filelog); +} + +sub +SVG_substcfg($$$$$$) +{ + my ($splitret, $wl, $cfg, $plot, $file, $tmpfile) = @_; + + # interpret title and label as a perl command and make + # to all internal values e.g. $value. + + my $oll = $attr{global}{verbose}; + $attr{global}{verbose} = 0; # Else the filenames will be Log'ged + + if($file eq "CURRENT") { + $file = $defs{$defs{$wl}{LOGDEVICE}}{currentlogfile}; + $file =~ s+.*/++; + } + my $fileesc = $file; + $fileesc =~ s/\\/\\\\/g; # For Windows, by MarkusRR + my $title = AttrVal($wl, "title", "\"$fileesc\""); + + $title = AnalyzeCommand(undef, "{ $title }"); + my $label = AttrVal($wl, "label", undef); + my @g_label; + if ($label) { + @g_label = split("::",$label); + foreach (@g_label) { + $_ = AnalyzeCommand(undef, "{ $_ }"); + } + } + $attr{global}{verbose} = $oll; + + my $gplot_script = join("", @{$cfg}); + $gplot_script .= $plot if(!$splitret); + + $gplot_script =~ s//$tmpfile/g; + $gplot_script =~ s//$file/g; + + my $ps = AttrVal($wl,"plotsize",$FW_plotsize); + $gplot_script =~ s//$ps/g; + + $gplot_script =~ s//$title/g; + my $g_count=1; + if ($label) { + foreach (@g_label) { + $gplot_script =~ s//$_/g; + $plot =~ s//$_/g; + $g_count++; + } + } + + $plot =~ s/\r//g; # For our windows friends... + $gplot_script =~ s/\r//g; + + if($splitret == 1) { + my @ret = split("\n", $gplot_script); + return (\@ret, $plot); + } else { + return $gplot_script; + } +} + +################## +# Calculate either the number of scrollable weblinks (for $d = undef) or +# for the device the valid from and to dates for the given zoom and offset +sub +SVG_calcWeblink($$) +{ + my ($d,$wl) = @_; + + my $pm = AttrVal($d,"plotmode",$FW_plotmode); + return if($pm eq "gnuplot"); + + my $frx; + if($defs{$wl}) { + my $fr = AttrVal($wl, "fixedrange", undef); + if($fr) { + #klaus fixed range day, week, month or year + if($fr eq "day" || $fr eq "week" || $fr eq "month" || $fr eq "year" ) { + $frx=$fr; + + } else { + my @range = split(" ", $fr); + my @t = localtime; + $SVG_devs{$d}{from} = ResolveDateWildcards($range[0], @t); + $SVG_devs{$d}{to} = ResolveDateWildcards($range[1], @t); + return; + } + } + } + + my $off = $FW_pos{$d}; + $off = 0 if(!$off); + $off += $FW_pos{off} if($FW_pos{off}); + + my $now = time(); + my $zoom = $FW_pos{zoom}; + $zoom = "day" if(!$zoom); + $zoom = $frx if ($frx); #for fixedrange {day|week|...} klaus + + + if($zoom eq "hour") { + my $t = $now + $off*3600; + my @l = localtime($t); + $SVG_devs{$d}{from} + = sprintf("%04d-%02d-%02d_%02d:00:00",$l[5]+1900,$l[4]+1,$l[3],$l[2]); + @l = localtime($t+3600); + $SVG_devs{$d}{to} + = sprintf("%04d-%02d-%02d_%02d:00:01",$l[5]+1900,$l[4]+1,$l[3],$l[2]); + + } elsif($zoom eq "qday") { + my $t = $now + $off*21600; + my @l = localtime($t); + $l[2] = int($l[2]/6)*6; + $SVG_devs{$d}{from} = + sprintf("%04d-%02d-%02d_%02d:00:00",$l[5]+1900,$l[4]+1,$l[3],$l[2]); + @l = localtime($t+21600); + $l[2] = int($l[2]/6)*6; + $SVG_devs{$d}{to} = + sprintf("%04d-%02d-%02d_%02d:00:01",$l[5]+1900,$l[4]+1,$l[3],$l[2]); + + } elsif($zoom eq "day") { + my $t = $now + $off*86400; + my @l = localtime($t); + $SVG_devs{$d}{from} = + sprintf("%04d-%02d-%02d_00:00:00",$l[5]+1900,$l[4]+1,$l[3]); + @l = localtime($t+86400); + $SVG_devs{$d}{to} = + sprintf("%04d-%02d-%02d_00:00:01",$l[5]+1900,$l[4]+1,$l[3]); + + } elsif($zoom eq "week") { + my @l = localtime($now); + my $start = (AttrVal($FW_wname, "endPlotToday", undef) ? 6 : $l[6]); + my $t = $now - ($start*86400) + ($off*86400)*7; + @l = localtime($t); + $SVG_devs{$d}{from} = + sprintf("%04d-%02d-%02d_00:00:00",$l[5]+1900,$l[4]+1,$l[3]); + @l = localtime($t+7*86400); + $SVG_devs{$d}{to} = + sprintf("%04d-%02d-%02d_00:00:01",$l[5]+1900,$l[4]+1,$l[3]); + + } elsif($zoom eq "month") { + my ($endDay, @l); + if(AttrVal($FW_wname, "endPlotToday", undef)) { + @l = localtime($now+86400); + $endDay = $l[3]; + $off--; + } else { + @l = localtime($now); + $endDay = 1; + } + while($off < -12) { # Correct the year + $off += 12; $l[5]--; + } + $l[4] += $off; + $l[4] += 12, $l[5]-- if($l[4] < 0); + $SVG_devs{$d}{from} = + sprintf("%04d-%02d-%02d_00:00:00", $l[5]+1900, $l[4]+1,$endDay); + $l[4]++; + $l[4] = 0, $l[5]++ if($l[4] == 12); + $SVG_devs{$d}{to} = + sprintf("%04d-%02d-%02d_00:00:01", $l[5]+1900, $l[4]+1,$endDay); + + } elsif($zoom eq "year") { + my @l = localtime($now); + $l[5] += $off; + $SVG_devs{$d}{from} = sprintf("%04d-01-01_00:00:00", $l[5]+1900); + $SVG_devs{$d}{to} = sprintf("%04d-01-01_00:00:01", $l[5]+1901); + + } +} + + +###################### +# Generate an image from the log via gnuplot or SVG +sub +SVG_showLog($) +{ + my ($cmd) = @_; + my $wl = $FW_webArgs{dev}; + my $d = $FW_webArgs{logdev}; + my $type = $FW_webArgs{gplotfile}; + my $file = $FW_webArgs{logfile}; + my $pm = AttrVal($wl,"plotmode",$FW_plotmode); + + my $gplot_pgm = "$FW_gplotdir/$type.gplot"; + + if(!-r $gplot_pgm) { + my $msg = "Cannot read $gplot_pgm"; + Log3 $FW_wname, 1, $msg; + + if($pm =~ m/SVG/) { # FW_fatal for SVG: + $FW_RETTYPE = "image/svg+xml"; + FW_pO ''; + FW_pO ''.$msg.''; + FW_pO ''; + return ($FW_RETTYPE, $FW_RET); + + } else { + return ($FW_RETTYPE, $msg); + + } + } + SVG_calcWeblink($d,$wl); + + if($pm =~ m/gnuplot/) { + + my $tmpfile = "/tmp/file.$$"; + my $errfile = "/tmp/gnuplot.err"; + + if($pm eq "gnuplot" || !$SVG_devs{$d}{from}) { + # Looking for the logfile.... + $defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File + my $path = "$1/$file"; + $path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path); + return ($FW_RETTYPE, "Cannot read $path") if(!-r $path); + + my ($err, $cfg, $plot, undef) = SVG_readgplotfile($wl, $gplot_pgm, $file); + return ($FW_RETTYPE, $err) if($err); + my $gplot_script = SVG_substcfg(0, $wl, $cfg, $plot, $file,$tmpfile); + + my $fr = AttrVal($wl, "fixedrange", undef); + if($fr) { + $fr =~ s/ /\":\"/; + $fr = "set xrange [\"$fr\"]\n"; + $gplot_script =~ s/(set timefmt ".*")/$1\n$fr/; + } + + open(FH, "|gnuplot >> $errfile 2>&1");# feed it to gnuplot + print FH $gplot_script; + close(FH); + + } elsif($pm eq "gnuplot-scroll") { + my ($err, $cfg, $plot, $flog) = SVG_readgplotfile($wl, $gplot_pgm, $file); + return ($FW_RETTYPE, $err) if($err); + + # Read the data from the filelog + my ($f,$t)=($SVG_devs{$d}{from}, $SVG_devs{$d}{to}); + my $oll = $attr{global}{verbose}; + $attr{global}{verbose} = 0; # Else the filenames will be Log'ged + my @path = split(" ", FW_fC("get $d $file $tmpfile $f $t " . + join(" ", @{$flog}))); + $attr{global}{verbose} = $oll; + + # replace the path with the temporary filenames of the filelog output + my $i = 0; + $plot =~ s/\".*?using 1:[^ ]+ /"\"$path[$i++]\" using 1:2 "/gse; + my $xrange = "set xrange [\"$f\":\"$t\"]\n"; + foreach my $p (@path) { # If the file is empty, write a 0 line + next if(!-z $p); + open(FH, ">$p"); + print FH "$f 0\n"; + close(FH); + } + + my $gplot_script = SVG_substcfg(0, $wl, $cfg, $plot, $file, $tmpfile); + + open(FH, "|gnuplot >> $errfile 2>&1");# feed it to gnuplot + print FH $gplot_script, $xrange, $plot; + close(FH); + foreach my $p (@path) { + unlink($p); + } + } + $FW_RETTYPE = "image/png"; + open(FH, "$tmpfile.png"); # read in the result and send it + binmode (FH); # necessary for Windows + FW_pO join("", ); + close(FH); + unlink("$tmpfile.png"); + + } elsif($pm eq "SVG") { + my ($err, $cfg, $plot, $flog) = SVG_readgplotfile($wl, $gplot_pgm, $file); + return ($FW_RETTYPE, $err) if($err); + + my ($f,$t)=($SVG_devs{$d}{from}, $SVG_devs{$d}{to}); + $f = 0 if(!$f); # From the beginning of time... + $t = 9 if(!$t); # till the end + + my $ret; + if(!$modules{SVG}{LOADED}) { + $ret = CommandReload(undef, "98_SVG"); + Log3 $FW_wname, 1, $ret if($ret); + } + Log3 $FW_wname, 5, + "plotcommand: get $d $file INT $f $t " . join(" ", @{$flog}); + + $FW_RETTYPE = "image/svg+xml"; + + (my $cachedate = TimeNow()) =~ s/ /_/g; + my $SVGcache = (AttrVal($FW_wname, "SVGcache", undef) && $t lt $cachedate); + my $cDir = "$FW_dir/SVGcache"; + my $cName = "$cDir/$wl-$f-$t.svg"; + if($SVGcache && open(CFH, $cName)) { + FW_pO join("", ); + close(CFH); + + } else { + FW_fC("get $d $file INT $f $t " . join(" ", @{$flog}), 1); + ($cfg, $plot) = SVG_substcfg(1, $wl, $cfg, $plot, $file, ""); + $ret = SVG_render($wl, $f, $t, $cfg, + $internal_data, $plot, $FW_wname, $FW_cssdir, $flog); + FW_pO $ret; + if($SVGcache) { + mkdir($cDir) if(! -d $cDir); + if(open(CFH, ">$cName")) { + print CFH $ret; + close(CFH); + } + } + } + + } + return ($FW_RETTYPE, $FW_RET); + } @@ -680,3 +1475,144 @@ SVG_pO($) } 1; + +=pod +=begin html + + +

SVG

+
    + + Define +
      + define <name> SVG + <logDevice>:<gplotfile>:<logfile> +

      + This is the Plotting/Charting device of FHEMWEB + Examples: +
        + define MyPlot SVG inlog:temp4hum4:CURRENT
        +
      +
      + + Notes: +
        +
      • Normally you won't define an SVG device manually, as + FHEMWEB makes it easy for you, just plot a logfile (see logtype) and click on "Create SVG instance". + Specifying CURRENT as a logfilename will always access the current + logfile, even if its name changes regularly.
      • +
      • For historic reasons this module uses a Gnuplot file description + to store different attributes. Some special commands (beginning with + #FileLog or #DbLog) are used additionally, and not all gnuplot + attribtues are implemented.
      • +
      +
    + + + Set +
      +
    • copyGplotFile
      + Copy the currently specified gplot file to a new file, which is named + after the SVG device, existing files will be overwritten. + This operation is needed in order to use the plot editor (see below) + without affecting other SVG instances using the same gplot file. + Creating the SVG instance from the FileLog detail menu will also + create a unique gplot file, in this case this operation is not needed. +
    • +

    + + + Get
      N/A

    + + + Attributes +
      +
    • fixedrange
    • +
    • plotsize
    • +
    • plotmode
    • + +
    • label
      + Double-Colon separated list of values. The values will be used to replace + <L#> type of strings in the .gplot file, with # beginning at 1 + (<L1>, <L2>, etc.). Each value will be evaluated as a perl + expression, so you have access e.g. to the Value functions.

      + + If the plotmode is gnuplot-scroll or SVG, you can also use the min, max, + avg, cnt, sum, currval (last value) and currdate (last date) values of + the individual curves, by accessing the corresponding values from the + data hash, see the example below:
      + +
        +
      • Fixed text for the right and left axis:
        +
          +
        • Fhem config:
          + attr wl_1 label "Temperature"::"Humidity"
        • +
        • .gplot file entry:
          + set ylabel <L1>
          + set y2label <L2>
        • +
      • +
      • Title with maximum and current values of the 1st curve (FileLog) +
          +
        • Fhem config:
          + attr wl_1 label "Max $data{max1}, Current $data{currval1}"
        • +
        • .gplot file entry:
          + set title <L1>
        • +
      • +
      +
    • + + +
    • title
      + A special form of label (see above), which replaces the string <TL> + in the .gplot file. It defaults to the filename of the logfile. +
    • + + +
    • plotfunction
      + Space value separated list of values. The value will be used to replace + <SPEC#> type of strings in the .gplot file, with # beginning at 1 + (<SPEC1>, <SPEC2>, etc.) in the #FileLog or #DbLog directive. + With this attribute you can use the same .gplot file for multiple devices + with the same logdevice. +
        Example:
        +
      • #FileLog
        + with: attr plotfunction "4:IR\x3a:0:"
        + instead of
        + #FileLog 4:IR\x3a:0: +
      • +
      • #DbLog
        + with: attr plotfunction + "Garage_Raumtemp:temperature::"
        instead of
        + #DbLog Garage_Raumtemp:temperature:: +
      • +
      +
    • +
    +
    + + + Plot-Editor +
      + This editor is visible on the detail screen of the SVG instance. + Most features are obvious here, up to some exceptions: +
    • if you want to omit the title for a Diagram label, enter notitle in the + input field.
    • +
    • if you want to specify a fixed value (not taken from a column) if a + string found (e.g. 1 of the FS20 switch is on 0 if it off), then you have + to specify the Tics first, and write the .gplot file, before you can + select this value from the dropdown.
      + Example: +
        + Enter in the Tics field: ("On" 1, "Off" 0)
        + Write .gplot file
        + Select "1" from the column dropdown (note the double quote!) for the + regexp switch.on, and "0" for the regexp switch.off.
        + Write .gplot file again
        +
    • +
    +
    +
+ +=end html +=cut diff --git a/fhem/FHEM/98_weblink.pm b/fhem/FHEM/98_weblink.pm index 1505c1a13..07d8f297f 100755 --- a/fhem/FHEM/98_weblink.pm +++ b/fhem/FHEM/98_weblink.pm @@ -5,14 +5,6 @@ package main; use strict; use warnings; use vars qw($FW_subdir); # Sub-path in URL for extensions, e.g. 95_FLOORPLAN -use vars qw($FW_ME); # webname (default is fhem), needed by 97_GROUP -use vars qw(%FW_hiddenroom); # hash of hidden rooms, used by weblink -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_pos); # scroll position -use vars qw($FW_gplotdir);# gplot directory for web server: the first -use vars qw(%FW_webArgs); # all arguments specified in the GET - use IO::File; ##################################### @@ -22,9 +14,7 @@ weblink_Initialize($) my ($hash) = @_; $hash->{DefFn} = "weblink_Define"; - $hash->{AttrList} = "fixedrange plotmode plotsize label ". - "title htmlattr plotfunction"; - $hash->{SetFn} = "weblink_Set"; + $hash->{AttrList} = "htmlattr"; $hash->{FW_summaryFn} = "weblink_FwFn"; $hash->{FW_detailFn} = "weblink_FwFn"; $hash->{FW_atPageEnd} = 1; @@ -37,52 +27,31 @@ sub weblink_Define($$) { my ($hash, $def) = @_; - my ($type, $name, $wltype, $link) = split("[ \t]+", $def, 4); - my %thash = ( link=>1, fileplot=>1, dbplot=>1, image=>1, iframe=>1, htmlCode=>1, cmdList=>1, readings=>1 ); + my ($name, $type, $wltype, $link) = split("[ \t]+", $def, 4); + my %thash = ( link=>1, image=>1, iframe=>1, htmlCode=>1, + cmdList=>1, readings=>1, + fileplot=>1, dbplot=>1); if(!$link || !$thash{$wltype}) { return "Usage: define weblink [" . join("|",sort keys %thash) . "] "; } + + if($wltype eq "fileplot" || $wltype eq "dbplot") { + Log 1, "Converting weblink $name ($wltype) to SVG"; + my $newm = LoadModule("SVG"); + return "Cannot load module SVG" if($newm eq "UNDEFINED"); + $hash->{TYPE} = "SVG"; + $hash->{DEF} = $link; + return CallFn($name, "DefFn", $hash, "$name $type $link"); + } + $hash->{WLTYPE} = $wltype; $hash->{LINK} = $link; - $hash->{STATE} = "initial"; + $hash->{STATE} = "initialized"; return undef; } -sub -weblink_Set($@) -{ - my ($hash, @a) = @_; - my $me = $hash->{NAME}; - return "no set argument specified" if(int(@a) < 2); - my %sets = (copyGplotFile=>0); - - my $cmd = $a[1]; - return "Unknown argument $cmd, choose one of ".join(" ",sort keys %sets) - if(!defined($sets{$cmd})); - return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 2); - - if($cmd eq "copyGplotFile") { - return "type is not fileplot" if($hash->{WLTYPE} ne "fileplot"); - my @a = split(":", $hash->{LINK}); - my $srcName = "$FW_gplotdir/$a[1].gplot"; - $a[1] = $hash->{NAME}; - my $dstName = "$FW_gplotdir/$a[1].gplot"; - $hash->{LINK} = join(":", @a); - return "this is already a unique gplot file" if($srcName eq $dstName); - $hash->{DEF} = "$hash->{WLTYPE} $hash->{LINK}"; - open(SFH, $srcName) || return "Can't open $srcName: $!"; - open(DFH, ">$dstName") || return "Can't open $dstName: $!"; - while(my $l = ) { - print DFH $l; - } - close(SFH); close(DFH); - } - return undef; -} - - ##################################### # FLOORPLAN compat sub @@ -155,8 +124,6 @@ weblink_FwFn($$$$) $ret .= ""; $ret .= "
"; - return $ret; - } elsif($wltype eq "readings") { my @params = split(" ", $link); @@ -232,295 +199,11 @@ weblink_FwFn($$$$) $ret .= ""; $ret .= "
"; - return $ret; - - } elsif($wltype eq "fileplot" || $wltype eq "dbplot" ) { - - # plots navigation buttons - if((!$pageHash || !$pageHash->{buttons}) && - ($wltype eq "fileplot" || $wltype eq "dbplot") && - AttrVal($d, "fixedrange", "x") !~ m/^[ 0-9:-]*$/) { - - $ret .= FW_zoomLink("zoom=-1", "Zoom-in", "zoom in"); - $ret .= FW_zoomLink("zoom=1", "Zoom-out","zoom out"); - $ret .= FW_zoomLink("off=-1", "Prev", "prev"); - $ret .= FW_zoomLink("off=1", "Next", "next"); - $pageHash->{buttons} = 1 if($pageHash); - $ret .= "
"; - } - - my @va = split(":", $link, 3); - if($wltype eq "fileplot" && - (@va != 3 || !$defs{$va[0]} || !$defs{$va[0]}{currentlogfile})) { - $ret .= weblink_FwDetail($d, "Broken definition "); - - } elsif ($wltype eq "dbplot" && (@va != 2 || !$defs{$va[0]})) { - $ret .= weblink_FwDetail($d, "Broken definition "); - - } else { - if ($wltype eq "dbplot") { - $va[2] = "-"; - } - - my $wl = "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos); - - my $arg="$FW_ME?cmd=showlog $d $va[0] $va[1] $va[2]$wl"; - if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { - my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); - $ret .= "
"; - $ret .= "\n"; - $ret .= "
"; - - } else { - $ret .= ""; - } - - if(!$pageHash) { - $ret .= wl_PEdit($FW_wname,$d,$room,$pageHash) - if($wltype eq "fileplot" && $FW_plotmode eq "SVG"); - $ret .= "
"; - - } else { - $ret .= weblink_FwDetail($d, "", 1) if(!$FW_hiddenroom{detail}); - - } - - } } + return $ret; } -sub -wl_cb($$$) -{ - my ($v,$t,$c) = @_; - $c = ($c ? " checked" : ""); - return "$t "; -} - -sub -wl_txt($$$$) -{ - my ($v,$t,$c,$sz) = @_; - $c = "" if(!defined($c)); - $c =~ s/"/\"/g; - return "$t "; -} - -sub -wl_sel($$$@) -{ - my ($v,$l,$c,$fnData) = @_; - my @al = split(",",$l); - $c =~ s/\\x3a/:/g if($c); - return FW_select($v,$v,\@al,$c, "set", $fnData); -} - -sub -wl_getRegFromFile($) -{ - my ($fName) = @_; - my $fh = new IO::File $fName; - if(!$fh) { - Log 1, "$fName: $!"; - return (3, "NoFile", "NoFile"); - } - $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>) { - 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(); - return ($maxcols+1, - join(",", sort keys %h), - join("
", grep /.+/,map { $h{$_} } sort keys %h)), - close(FH); -} - -sub -wl_addTics($$) -{ - my ($in, $p) = @_; - return if(!$in || $in !~ m/^\((.*)\)$/); - map { $p->{"\"$2\""}=1 if(m/^ *([^ ]+) ([^ ]+) */); } split(",",$1); -} - -############################ -# gnuplot file "editor" -sub -wl_PEdit($$$$) -{ - my ($FW_wname,$d,$room,$pageHash) = @_; - my @a = split(":", $defs{$d}{LINK}); - my $gp = "$FW_gplotdir/$a[1].gplot"; - my $file = $defs{$a[0]}{currentlogfile}; - - my ($err, $cfg, $plot, $flog) = FW_readgplotfile($d, $gp, $file); - my %conf = SVG_digestConf($cfg, $plot); - - my $ret .= "
"; - $ret .= FW_hidden("detail", $d); - $ret .= FW_hidden("gplotName", $gp); - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $conf{ylabel} =~ s/"//g if($conf{ylabel}); - $ret .= ""; - $conf{y2label} =~ s/"//g if($conf{y2label}); - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= wl_cb("gridy", "left", $conf{hasygrid}); - $ret .= wl_cb("gridy2","right",$conf{hasy2grid}); - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - $ret .= ""; - - $ret .= ""; - $ret .= ""; - $ret .=" "; - - my ($colnums, $colregs, $coldata) = wl_getRegFromFile($file); - $colnums = join(",", 3..$colnums); - - my %tickh; - wl_addTics($conf{ytics}, \%tickh); - wl_addTics($conf{y2tics}, \%tickh); - $colnums = join(",", sort keys %tickh).",$colnums" if(%tickh); - - my $max = @{$conf{lType}}+1; - $max = 8 if($max > 8); - my $r = 0; - for($r=0; $r < $max; $r++) { - $ret .= ""; - } - $ret .= ""; - - $ret .= ""; - - $ret .= "
Plot title".wl_txt("title", "", $conf{title}, 32)."
Y-Axis label".wl_txt("ylabel", "left", $conf{ylabel}, 16)."".wl_txt("y2label","right", $conf{y2label}, 16)."
Grid aligned
Range as [min:max]".wl_txt("yrange", "left", $conf{yrange}, 16)."".wl_txt("y2range", "right", $conf{y2range}, 16)."
Tics as (\"Txt\" val, ...)".wl_txt("ytics", "left", $conf{ytics}, 16)."".wl_txt("y2tics","right", $conf{y2tics}, 16)."
Diagramm labelInput:Column,Regexp,DefaultValue,FunctionY-Axis,Plot-Type,Style,Width
"; - $ret .= wl_txt("title_${r}", "", !$conf{lTitle}[$r]&&$r<($max-1) ? - "notitle" : $conf{lTitle}[$r], 12); - $ret .= ""; - my @f = split(":", ($flog->[$r] ? $flog->[$r] : ":::"), 4); - $ret .= wl_sel("cl_${r}", $colnums, $f[0]); - $ret .= wl_sel("re_${r}", $colregs, $f[1]); - $ret .= wl_txt("df_${r}", "", $f[2], 1); - $ret .= wl_txt("fn_${r}", "", $f[3], 6); - - $ret .= ""; - my $v = $conf{lAxis}[$r]; - $ret .= wl_sel("axes_${r}", "left,right", - ($v && $v eq "x1y1") ? "left" : "right"); - $ret .= wl_sel("type_${r}", "lines,points,steps,fsteps,histeps,bars", - $conf{lType}[$r]); - my $ls = $conf{lStyle}[$r]; - if($ls) { - $ls =~ s/class=//g; - $ls =~ s/"//g; - } - $ret .= wl_sel("style_${r}", "l0,l1,l2,l3,l4,l5,l6,l7,l8,". - "l0fill,l1fill,l2fill,l3fill,l4fill,l5fill,l6fill", $ls); - my $lw = $conf{lWidth}[$r]; - if($lw) { - $lw =~ s/.*stroke-width://g; - $lw =~ s/"//g; - } - $ret .= wl_sel("width_${r}", "0.2,0.5,1,1.5,2,3,4", ($lw ? $lw : 1)); - $ret .= "
"; - $ret .= "Example lines for input:
$coldata
"; - $ret .= FW_submit("submit", "Write .gplot file")."
"; -} - -sub -weblink_WriteGplot($) -{ - my ($arg) = @_; - FW_digestCgi($arg); - - if(!defined($FW_webArgs{cl_0})) { - return("text/html;", - "missing data in logfile: won't write incomplete .gplot definition"); - } - - my $hasTl; - for(my $i=0; $i <= 8; $i++) { - $hasTl = 1 if($FW_webArgs{"title_$i"}); - } - return (undef, "continue") if(!$hasTl); - - my $fName = $FW_webArgs{gplotName}; - return (undef, "continue") if(!$fName); - if(!open(FH, ">$fName")) { - Log 1, "weblink_WriteGplot: Can't write $fName"; - return (undef, "continue"); - } - print FH "# Created by FHEMWEB, ".TimeNow()."\n"; - print FH "set terminal png transparent size crop\n"; - print FH "set output '.png'\n"; - print FH "set xdata time\n"; - print FH "set timefmt \"%Y-%m-%d_%H:%M:%S\"\n"; - print FH "set xlabel \" \"\n"; - print FH "set title '$FW_webArgs{title}'\n"; - print FH "set ytics ".$FW_webArgs{ytics}."\n"; - print FH "set y2tics ".$FW_webArgs{y2tics}."\n"; - print FH "set grid".($FW_webArgs{gridy} ? " ytics" :""). - ($FW_webArgs{gridy2} ? " y2tics":"")."\n"; - print FH "set ylabel \"$FW_webArgs{ylabel}\"\n"; - print FH "set y2label \"$FW_webArgs{y2label}\"\n"; - print FH "set yrange $FW_webArgs{yrange}\n" if($FW_webArgs{yrange}); - print FH "set y2range $FW_webArgs{y2range}\n" if($FW_webArgs{y2range}); - print FH "\n"; - - my @plot; - for(my $i=0; $i <= 8; $i++) { - next if(!$FW_webArgs{"title_$i"}); - my $re = $FW_webArgs{"re_$i"}; - $re = "" if(!defined($re)); - $re =~ s/:/\\x3a/g; - print FH "#FileLog ". $FW_webArgs{"cl_$i"} .":$re:". - $FW_webArgs{"df_$i"} .":". - $FW_webArgs{"fn_$i"} ."\n"; - push @plot, "\"\" using 1:2 axes ". - ($FW_webArgs{"axes_$i"} eq "right" ? "x1y2" : "x1y1"). - ($FW_webArgs{"title_$i"} eq "notitle" ? " notitle" : - " title '".$FW_webArgs{"title_$i"} ."'"). - " ls " .$FW_webArgs{"style_$i"} . - " lw " .$FW_webArgs{"width_$i"} . - " with " .$FW_webArgs{"type_$i"}; - } - print FH "\n"; - print FH "plot ".join(",\\\n ", @plot)."\n"; - close(FH); - - return (undef, "continue"); -} - 1; =pod @@ -532,13 +215,11 @@ weblink_WriteGplot($) Define
    - define <name> weblink [link|fileplot|dbplot|image|iframe|htmlCode|cmdList|readings] + define <name> weblink [link|image|iframe|htmlCode|cmdList|readings] <argument>

    - This is a placeholder used with webpgm2 to be able to integrate links - into it, and to be able to put more than one gnuplot/SVG picture on one - page. It has no set or get methods. - + This is a placeholder device used with FHEMWEB to be able to add user + defined links. Examples:
      define homepage weblink link http://www.fhem.de
      @@ -546,8 +227,6 @@ weblink_WriteGplot($) define interactive_webcam weblink iframe http://w.x.y.z/webcam.cgi
      define hr weblink htmlCode <hr>
      define w_Frlink weblink htmlCode { WeatherAsHtml("w_Frankfurt") }
      - define MyPlot weblink fileplot <logdevice>:<gnuplot-file>:<logfile>
      - define MyPlot weblink dbplot <logdevice>:<gnuplot-file>
      define systemCommands weblink cmdList pair:Pair:set+cul2+hmPairForSec+60 restart:Restart:shutdown+restart update:UpdateCheck:update+check
      define wl_SystemStatus weblink readings sysstat *nostate *notime {{ 'load' => 'Systemauslastung', 'temperature' => 'Systemtemperatur in &deg;;C'}}
      define wlHeizung weblink readings t1:temperature t2:temperature t3:temperature *notime {{ 't1.temperature' => 'Vorlauf', 't2.temperature' => 'R&uuml;;cklauf', 't3.temperature' => 'Zirkulation'}} @@ -556,15 +235,8 @@ weblink_WriteGplot($) Notes:
        -
      • Normally you won't have to define fileplot weblinks manually, as - FHEMWEB makes it easy for you, just plot a logfile (see - logtype) and convert it to weblink. Now you - can group these weblinks by putting them into rooms. If you convert - the current logfile to a weblink, it will always refer to the current - file, even if its name changes regularly (and not the one you - originally specified).
      • For cmdList <argument> consist of a list of space separated icon:label:cmd triples.
      • -
      • For redings <argument> consist of one or more <device>[:regex] pairs, +
      • For readings <argument> consist of one or more <device>[:regex] pairs, zero or more of the modifiers *noheading, *notime and *nostate and as the last argument an optional {} expression that should return a perl hash to map reading names to display names.
          @@ -583,16 +255,7 @@ weblink_WriteGplot($)
        - Set -
          -
        • copyGplotFile
          - Only applicable to fileplot type weblinks.
          - Copy the currently specified gplot file to a new file, which is named - after the weblink (existing files will be overwritten), in order to be - able to modify it locally without the problem of being overwritten by - update. The weblink definition will be updated. -
        • -

        + Set
          N/A

        Get
          N/A

        @@ -610,91 +273,9 @@ weblink_WriteGplot($) attr yw weblink htmlattr width="480" height="560"
      -
    • fixedrange
    • -
    • plotsize
    • -
    • plotmode
    • - -
    • label
      - Double-Colon separated list of values. The values will be used to replace - <L#> type of strings in the .gplot file, with # beginning at 1 - (<L1>, <L2>, etc.). Each value will be evaluated as a perl - expression, so you have access e.g. to the Value functions.

      - - If the plotmode is gnuplot-scroll or SVG, you can also use the min, max, - avg, cnt, sum, currval (last value) and currdate (last date) values of - the individual curves, by accessing the corresponding values from the - data hash, see the example below:
      - -
        -
      • Fixed text for the right and left axis:
        -
          -
        • Fhem config:
          - attr wl_1 label "Temperature"::"Humidity"
        • -
        • .gplot file entry:
          - set ylabel <L1>
          - set y2label <L2>
        • -
      • -
      • Title with maximum and current values of the 1st curve (FileLog) -
          -
        • Fhem config:
          - attr wl_1 label "Max $data{max1}, Current $data{currval1}"
        • -
        • .gplot file entry:
          - set title <L1>
        • -
      • -
      -
    • - - -
    • title
      - A special form of label (see above), which replaces the string <TL> - in the .gplot file. It defaults to the filename of the logfile. -
    • - - -
    • plotfunction
      - Space value separated list of values. The value will be used to replace - <SPEC#> type of strings in the .gplot file, with # beginning at 1 - (<SPEC1>, <SPEC2>, etc.) in the #FileLog or #DbLog directive. - With this attribute you can use the same .gplot file for multiple devices - with the same logdevice. -
        Example:
        -
      • #FileLog
        - with: attr plotfunction "4:IR\x3a:0:"
        - instead of
        - #FileLog 4:IR\x3a:0: -
      • -
      • #DbLog
        - with: attr plotfunction - "Garage_Raumtemp:temperature::"
        instead of
        - #DbLog Garage_Raumtemp:temperature:: -
      • -
      -

    - - Plot-Editor specialities -
      - If the weblink type is set to fileplot, a weblink editor is displayed in - the detail view of the weblink. Most features are obvious here, up to some - exceptions: -
    • if you want to omit the title for a Diagram label, enter notitle in the - input field.
    • -
    • if you want to specify a fixed value (not taken from a column) if a - string found (e.g. 1 of the FS20 switch is on 0 if it off), then you have - to specify the Tics first, and write the .gplot file, before you can - select this value from the dropdown.
      - Example: -
        - Enter in the Tics field: ("On" 1, "Off" 0)
        - Write .gplot file
        - Select "1" from the column dropdown (note the double quote!) for the - regexp switch.on, and "0" for the regexp switch.off.
        - Write .gplot file again
        -
    • -
    -
=end html diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html index 47f24af2f..aee2cb857 100644 --- a/fhem/docs/commandref_frame.html +++ b/fhem/docs/commandref_frame.html @@ -104,6 +104,7 @@ sequence   speedtest   structure   + SVG   telnet   Twilight   THRESHOLD   diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html index e57a511bc..e879dd4a6 100644 --- a/fhem/docs/commandref_frame_DE.html +++ b/fhem/docs/commandref_frame_DE.html @@ -102,6 +102,7 @@ sequence   speedtest   structure   + SVG   telnet   Twilight   THRESHOLD