2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 06:39:11 +00:00

weblink fileplot/dbplot converted to SVG device + FHEMWEB cleanup

git-svn-id: https://svn.fhem.de/fhem/trunk@3697 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2013-08-14 15:08:15 +00:00
parent bb808092ad
commit 81e67a9258
7 changed files with 1152 additions and 1019 deletions

View File

@ -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

View File

@ -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/<br>/g;
FW_pO "<div id=\"content\">$motd</div>";
@ -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 "<div id=\"content\">$path: $!</div>";
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 "<div id=\"content\">";
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_pO "<div id=\"content\">$path: $!</div></body></html>";
return 0;
}
my $cnt = join("", reverse <FH>);
close(FH);
$cnt = FW_htmlEscape($cnt);
FW_pO $cnt;
FW_pO $suffix;
} else {
FW_pO "<div id=\"content\">";
FW_pO "<br>";
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 "<table><tr><td>";
FW_pO "<td>";
my $logtype = $defs{$d}{TYPE};
my $wl = "&amp;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 "<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 "cmd=toweblink $d:$type:$file", "Convert to weblink";
FW_pO "</td>";
FW_pO "</td></tr></table>";
FW_pO "</div>";
}
FW_pO "</body></html>";
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 = <FH>) {
$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/<SPEC$spec_count>/$_/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/<OUT>/$tmpfile/g;
$gplot_script =~ s/<IN>/$file/g;
my $ps = AttrVal($wl,"plotsize",$FW_plotsize);
$gplot_script =~ s/<SIZE>/$ps/g;
$gplot_script =~ s/<TL>/$title/g;
my $g_count=1;
if ($label) {
foreach (@g_label) {
$gplot_script =~ s/<L$g_count>/$_/g;
$plot =~ s/<L$g_count>/$_/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 '<svg xmlns="http://www.w3.org/2000/svg">';
FW_pO '<text x="20" y="20">'.$msg.'</text>';
FW_pO '</svg>';
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("", <FH>);
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("", <CFH>);
close(CFH);
} else {
FW_fC("get $d $file INT $f $t " . join(" ", @{$flog}), 1);
($cfg, $plot) = FW_substcfg(1, $wl, $cfg, $plot, $file, "<OuT>");
$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")) . "&amp;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 "&nbsp;&nbsp;".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 .= "<td>" if($td);
if($FW_ss || $FW_tp) {
$ret .= "<a onClick=\"location.href='$FW_ME$FW_subdir?$link'\">$txt</a>";
$ret .= "<a onClick=\"location.href='$FW_ME$FW_subdir$link'\">$txt</a>";
} else {
$ret .= "<a href=\"$FW_ME$FW_subdir?$link\">$txt</a>";
$ret .= "<a href=\"$FW_ME$FW_subdir$link\">$txt</a>";
}
$ret .= "</td>" 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};
}

View File

@ -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",
"<div class=\"dval\">$name</div>", 1, "dval", 1);
}
$ret .= "</tr>";
@ -337,17 +360,118 @@ FileLog_fhemwebFn($$$$)
$ret .= "</table>";
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",
"<div class=\"dval\">Create new SVG plot</div>", 0, "dval", 1);
"<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_pO '<div id="content">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_pO "<div id=\"content\">";
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_pO "<div id=\"content\">$path: $!</div></body></html>";
return 0;
}
my $cnt = join("", reverse <FH>);
close(FH);
$cnt = FW_htmlEscape($cnt);
FW_pO $cnt;
FW_pO $suffix;
} else {
FileLog_loadSVG();
FW_pO "<div id=\"content\">";
FW_pO "<br>";
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}{TYPE};
my $wl = "&amp;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

View File

@ -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 <name> SVG <logdevice>:<gnuplot-file>:<logfile>";
}
$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 = <SFH>) {
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 ? "" : "<br>");
$ret .= "$text " if($text);
$ret .= FW_pHPlain("detail=$d", $alias) if(!$FW_subdir);
$ret .= "<br>";
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 .= "<br>";
}
my $arg="$FW_ME/SVG_showLog?dev=$d".
"&amp;logdev=$hash->{LOGDEVICE}".
"&amp;gplotfile=$hash->{GPLOTFILE}".
"&amp;logfile=$hash->{LOGFILE}".
"&amp;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 .= "<div class=\"SVGplot\">";
$ret .= "<embed src=\"$arg\" type=\"image/svg+xml\" " .
"width=\"$w\" height=\"$h\" name=\"$d\"/>\n";
$ret .= "</div>";
} else {
$ret .= "<img src=\"$arg\"/>";
}
if(!$pageHash) {
if($FW_plotmode eq "SVG") {
$ret .= SVG_PEdit($FW_wname,$d,$room,$pageHash) . "<br>";
}
} else {
$ret .= SVG_FwDetail($d, "", 1) if(!$FW_hiddenroom{detail});
}
return $ret;
}
sub
SVG_cb($$$)
{
my ($v,$t,$c) = @_;
$c = ($c ? " checked" : "");
return "<td>$t&nbsp;<input type=\"checkbox\" name=\"$v\" value=\"$v\"$c></td>";
}
sub
SVG_txt($$$$)
{
my ($v,$t,$c,$sz) = @_;
$c = "" if(!defined($c));
$c =~ s/"/\&quot;/g;
return "$t&nbsp;<input type=\"text\" name=\"$v\" size=\"$sz\" ".
"value=\"$c\"/>";
}
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("<br>", 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 .= "<br><form autocomplete=\"off\" action=\"$FW_ME/SVG_WriteGplot\">";
$ret .= FW_hidden("detail", $d);
$ret .= FW_hidden("gplotName", $gp);
$ret .= "<table class=\"block wide plotEditor\">";
$ret .= "<tr class=\"even\">";
$ret .= "<td>Plot title</td>";
$ret .= "<td>".SVG_txt("title", "", $conf{title}, 32)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"odd\">";
$ret .= "<td>Y-Axis label</td>";
$conf{ylabel} =~ s/"//g if($conf{ylabel});
$ret .= "<td>".SVG_txt("ylabel", "left", $conf{ylabel}, 16)."</td>";
$conf{y2label} =~ s/"//g if($conf{y2label});
$ret .= "<td>".SVG_txt("y2label","right", $conf{y2label}, 16)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"even\">";
$ret .= "<td>Grid aligned</td>";
$ret .= SVG_cb("gridy", "left", $conf{hasygrid});
$ret .= SVG_cb("gridy2","right",$conf{hasy2grid});
$ret .= "</tr>";
$ret .= "<tr class=\"odd\">";
$ret .= "<td>Range as [min:max]</td>";
$ret .= "<td>".SVG_txt("yrange", "left", $conf{yrange}, 16)."</td>";
$ret .= "<td>".SVG_txt("y2range", "right", $conf{y2range}, 16)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"even\">";
$ret .= "<td>Tics as (\"Txt\" val, ...)</td>";
$ret .= "<td>".SVG_txt("ytics", "left", $conf{ytics}, 16)."</td>";
$ret .= "<td>".SVG_txt("y2tics","right", $conf{y2tics}, 16)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"odd\"><td>Diagramm label</td>";
$ret .= "<td>Input:Column,Regexp,DefaultValue,Function</td>";
$ret .=" <td>Y-Axis,Plot-Type,Style,Width</td></tr>";
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 .= "<tr class=\"".(($r&1)?"odd":"even")."\"><td>";
$ret .= SVG_txt("title_${r}", "", !$conf{lTitle}[$r]&&$r<($max-1) ?
"notitle" : $conf{lTitle}[$r], 12);
$ret .= "</td><td>";
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 .= "</td><td>";
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 .= "</td></tr>";
}
$ret .= "<tr class=\"".(($r++&1)?"odd":"even")."\"><td colspan=\"3\">";
$ret .= "Example lines for input:<br>$coldata</td></tr>";
$ret .= "<tr class=\"".(($r++&1)?"odd":"even")."\"><td colspan=\"3\">";
$ret .= FW_submit("submit", "Write .gplot file")."</td></tr>";
$ret .= "</table></form>";
}
##################
# 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")) . "&amp;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 "&nbsp;&nbsp;".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 <SIZE> crop\n";
print FH "set output '<OUT>.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, "\"<IN>\" 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 = <FH>) {
$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/<SPEC$spec_count>/$_/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/<OUT>/$tmpfile/g;
$gplot_script =~ s/<IN>/$file/g;
my $ps = AttrVal($wl,"plotsize",$FW_plotsize);
$gplot_script =~ s/<SIZE>/$ps/g;
$gplot_script =~ s/<TL>/$title/g;
my $g_count=1;
if ($label) {
foreach (@g_label) {
$gplot_script =~ s/<L$g_count>/$_/g;
$plot =~ s/<L$g_count>/$_/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 '<svg xmlns="http://www.w3.org/2000/svg">';
FW_pO '<text x="20" y="20">'.$msg.'</text>';
FW_pO '</svg>';
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("", <FH>);
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("", <CFH>);
close(CFH);
} else {
FW_fC("get $d $file INT $f $t " . join(" ", @{$flog}), 1);
($cfg, $plot) = SVG_substcfg(1, $wl, $cfg, $plot, $file, "<OuT>");
$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
<a name="SVG"></a>
<h3>SVG</h3>
<ul>
<a name="SVGlinkdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; SVG
&lt;logDevice&gt;:&lt;gplotfile&gt;:&lt;logfile&gt;</code>
<br><br>
This is the Plotting/Charting device of FHEMWEB
Examples:
<ul>
<code>define MyPlot SVG inlog:temp4hum4:CURRENT</code><br>
</ul>
<br>
Notes:
<ul>
<li>Normally you won't define an SVG device manually, as
FHEMWEB makes it easy for you, just plot a logfile (see <a
href="#logtype">logtype</a>) and click on "Create SVG instance".
Specifying CURRENT as a logfilename will always access the current
logfile, even if its name changes regularly.</li>
<li>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.</li>
</ul>
</ul>
<a name="SVGset"></a>
<b>Set</b>
<ul>
<li>copyGplotFile<br>
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.
</li>
</ul><br>
<a name="SVGget"></a>
<b>Get</b> <ul>N/A</ul><br>
<a name="SVGattr"></a>
<b>Attributes</b>
<ul>
<li><a href="#fixedrange">fixedrange</a></li>
<li><a href="#plotsize">plotsize</a></li>
<li><a href="#plotmode">plotmode</a></li>
<a name="label"></a>
<li>label<br>
Double-Colon separated list of values. The values will be used to replace
&lt;L#&gt; type of strings in the .gplot file, with # beginning at 1
(&lt;L1&gt;, &lt;L2&gt;, etc.). Each value will be evaluated as a perl
expression, so you have access e.g. to the Value functions.<br><br>
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:<br>
<ul>
<li>Fixed text for the right and left axis:<br>
<ul>
<li>Fhem config:<br>
attr wl_1 label "Temperature"::"Humidity"</li>
<li>.gplot file entry:<br>
set ylabel &lt;L1&gt;<br>
set y2label &lt;L2&gt;</li>
</ul></li>
<li>Title with maximum and current values of the 1st curve (FileLog)
<ul>
<li>Fhem config:<br>
attr wl_1 label "Max $data{max1}, Current $data{currval1}"</li>
<li>.gplot file entry:<br>
set title &lt;L1&gt;<br></li>
</ul></li>
</ul>
</li>
<a name="title"></a>
<li>title<br>
A special form of label (see above), which replaces the string &lt;TL&gt;
in the .gplot file. It defaults to the filename of the logfile.
</li>
<a name="plotfunction"></a>
<li>plotfunction<br>
Space value separated list of values. The value will be used to replace
&lt;SPEC#&gt; type of strings in the .gplot file, with # beginning at 1
(&lt;SPEC1&gt;, &lt;SPEC2&gt;, etc.) in the #FileLog or #DbLog directive.
With this attribute you can use the same .gplot file for multiple devices
with the same logdevice.
<ul><b>Example:</b><br>
<li>#FileLog <SPEC1><br>
with: attr <SVGdevice> plotfunction "4:IR\x3a:0:"<br>
instead of<br>
#FileLog 4:IR\x3a:0:
</li>
<li>#DbLog <SPEC1><br>
with: attr <SVGdevice> plotfunction
"Garage_Raumtemp:temperature::"<br> instead of<br>
#DbLog Garage_Raumtemp:temperature::
</li>
</ul>
</li>
</ul>
<br>
<a name="plotEditor"></a>
<b>Plot-Editor</b>
<ul>
This editor is visible on the detail screen of the SVG instance.
Most features are obvious here, up to some exceptions:
<li>if you want to omit the title for a Diagram label, enter notitle in the
input field.</li>
<li>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.<br>
Example:
<ul>
Enter in the Tics field: ("On" 1, "Off" 0)<br>
Write .gplot file<br>
Select "1" from the column dropdown (note the double quote!) for the
regexp switch.on, and "0" for the regexp switch.off.<br>
Write .gplot file again<br>
</ul></li>
</ul>
<br>
</ul>
=end html
=cut

View File

@ -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 <name> weblink [" .
join("|",sort keys %thash) . "] <arg>";
}
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 = <SFH>) {
print DFH $l;
}
close(SFH); close(DFH);
}
return undef;
}
#####################################
# FLOORPLAN compat
sub
@ -155,8 +124,6 @@ weblink_FwFn($$$$)
$ret .= "</table></td></tr>";
$ret .= "</table><br>";
return $ret;
} elsif($wltype eq "readings") {
my @params = split(" ", $link);
@ -232,295 +199,11 @@ weblink_FwFn($$$$)
$ret .= "</table>";
$ret .= "</br>";
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 .= "<br>";
}
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 = "&amp;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 .= "<div class=\"SVGplot\">";
$ret .= "<embed src=\"$arg\" type=\"image/svg+xml\" " .
"width=\"$w\" height=\"$h\" name=\"$d\"/>\n";
$ret .= "</div>";
} else {
$ret .= "<img src=\"$arg\"/>";
}
if(!$pageHash) {
$ret .= wl_PEdit($FW_wname,$d,$room,$pageHash)
if($wltype eq "fileplot" && $FW_plotmode eq "SVG");
$ret .= "<br>";
} else {
$ret .= weblink_FwDetail($d, "", 1) if(!$FW_hiddenroom{detail});
}
}
}
return $ret;
}
sub
wl_cb($$$)
{
my ($v,$t,$c) = @_;
$c = ($c ? " checked" : "");
return "<td>$t&nbsp;<input type=\"checkbox\" name=\"$v\" value=\"$v\"$c></td>";
}
sub
wl_txt($$$$)
{
my ($v,$t,$c,$sz) = @_;
$c = "" if(!defined($c));
$c =~ s/"/\&quot;/g;
return "$t&nbsp;<input type=\"text\" name=\"$v\" size=\"$sz\" ".
"value=\"$c\"/>";
}
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("<br>", 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 .= "<br><form autocomplete=\"off\" action=\"$FW_ME/weblinkDetails\">";
$ret .= FW_hidden("detail", $d);
$ret .= FW_hidden("gplotName", $gp);
$ret .= "<table class=\"block wide plotEditor\">";
$ret .= "<tr class=\"even\">";
$ret .= "<td>Plot title</td>";
$ret .= "<td>".wl_txt("title", "", $conf{title}, 32)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"odd\">";
$ret .= "<td>Y-Axis label</td>";
$conf{ylabel} =~ s/"//g if($conf{ylabel});
$ret .= "<td>".wl_txt("ylabel", "left", $conf{ylabel}, 16)."</td>";
$conf{y2label} =~ s/"//g if($conf{y2label});
$ret .= "<td>".wl_txt("y2label","right", $conf{y2label}, 16)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"even\">";
$ret .= "<td>Grid aligned</td>";
$ret .= wl_cb("gridy", "left", $conf{hasygrid});
$ret .= wl_cb("gridy2","right",$conf{hasy2grid});
$ret .= "</tr>";
$ret .= "<tr class=\"odd\">";
$ret .= "<td>Range as [min:max]</td>";
$ret .= "<td>".wl_txt("yrange", "left", $conf{yrange}, 16)."</td>";
$ret .= "<td>".wl_txt("y2range", "right", $conf{y2range}, 16)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"even\">";
$ret .= "<td>Tics as (\"Txt\" val, ...)</td>";
$ret .= "<td>".wl_txt("ytics", "left", $conf{ytics}, 16)."</td>";
$ret .= "<td>".wl_txt("y2tics","right", $conf{y2tics}, 16)."</td>";
$ret .= "</tr>";
$ret .= "<tr class=\"odd\"><td>Diagramm label</td>";
$ret .= "<td>Input:Column,Regexp,DefaultValue,Function</td>";
$ret .=" <td>Y-Axis,Plot-Type,Style,Width</td></tr>";
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 .= "<tr class=\"".(($r&1)?"odd":"even")."\"><td>";
$ret .= wl_txt("title_${r}", "", !$conf{lTitle}[$r]&&$r<($max-1) ?
"notitle" : $conf{lTitle}[$r], 12);
$ret .= "</td><td>";
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 .= "</td><td>";
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 .= "</td></tr>";
}
$ret .= "<tr class=\"".(($r++&1)?"odd":"even")."\"><td colspan=\"3\">";
$ret .= "Example lines for input:<br>$coldata</td></tr>";
$ret .= "<tr class=\"".(($r++&1)?"odd":"even")."\"><td colspan=\"3\">";
$ret .= FW_submit("submit", "Write .gplot file")."</td></tr>";
$ret .= "</table></form>";
}
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 <SIZE> crop\n";
print FH "set output '<OUT>.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, "\"<IN>\" 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($)
<a name="weblinkdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; weblink [link|fileplot|dbplot|image|iframe|htmlCode|cmdList|readings]
<code>define &lt;name&gt; weblink [link|image|iframe|htmlCode|cmdList|readings]
&lt;argument&gt;</code>
<br><br>
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:
<ul>
<code>define homepage weblink link http://www.fhem.de</code><br>
@ -546,8 +227,6 @@ weblink_WriteGplot($)
<code>define interactive_webcam weblink iframe http://w.x.y.z/webcam.cgi</code><br>
<code>define hr weblink htmlCode &lt;hr&gt</code><br>
<code>define w_Frlink weblink htmlCode { WeatherAsHtml("w_Frankfurt") }</code><br>
<code>define MyPlot weblink fileplot &lt;logdevice&gt;:&lt;gnuplot-file&gt;:&lt;logfile&gt;</code><br>
<code>define MyPlot weblink dbplot &lt;logdevice&gt;:&lt;gnuplot-file&gt;</code><br>
<code>define systemCommands weblink cmdList pair:Pair:set+cul2+hmPairForSec+60 restart:Restart:shutdown+restart update:UpdateCheck:update+check</code><br>
<code>define wl_SystemStatus weblink readings sysstat *nostate *notime {{ 'load' => 'Systemauslastung', 'temperature' => 'Systemtemperatur in &amp;deg;;C'}}</code><br>
<code>define wlHeizung weblink readings t1:temperature t2:temperature t3:temperature *notime {{ 't1.temperature' => 'Vorlauf', 't2.temperature' => 'R&amp;uuml;;cklauf', 't3.temperature' => 'Zirkulation'}}</code>
@ -556,15 +235,8 @@ weblink_WriteGplot($)
Notes:
<ul>
<li>Normally you won't have to define fileplot weblinks manually, as
FHEMWEB makes it easy for you, just plot a logfile (see
<a href="#logtype">logtype</a>) 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).</li>
<li>For cmdList &lt;argument&gt; consist of a list of space separated icon:label:cmd triples.</li>
<li>For redings &lt;argument&gt; consist of one or more &lt;device&gt;[:regex] pairs,
<li>For readings &lt;argument&gt; consist of one or more &lt;device&gt;[: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.
<ul>
@ -583,16 +255,7 @@ weblink_WriteGplot($)
</ul>
<a name="weblinkset"></a>
<b>Set</b>
<ul>
<li>copyGplotFile<br>
Only applicable to fileplot type weblinks.<br>
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.
</li>
</ul><br>
<b>Set</b> <ul>N/A</ul><br>
<a name="weblinkget"></a>
<b>Get</b> <ul>N/A</ul><br>
@ -610,91 +273,9 @@ weblink_WriteGplot($)
attr yw weblink htmlattr width="480" height="560"<br>
</code>
</ul></li>
<li><a href="#fixedrange">fixedrange</a></li>
<li><a href="#plotsize">plotsize</a></li>
<li><a href="#plotmode">plotmode</a></li>
<a name="label"></a>
<li>label<br>
Double-Colon separated list of values. The values will be used to replace
&lt;L#&gt; type of strings in the .gplot file, with # beginning at 1
(&lt;L1&gt;, &lt;L2&gt;, etc.). Each value will be evaluated as a perl
expression, so you have access e.g. to the Value functions.<br><br>
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:<br>
<ul>
<li>Fixed text for the right and left axis:<br>
<ul>
<li>Fhem config:<br>
attr wl_1 label "Temperature"::"Humidity"</li>
<li>.gplot file entry:<br>
set ylabel &lt;L1&gt;<br>
set y2label &lt;L2&gt;</li>
</ul></li>
<li>Title with maximum and current values of the 1st curve (FileLog)
<ul>
<li>Fhem config:<br>
attr wl_1 label "Max $data{max1}, Current $data{currval1}"</li>
<li>.gplot file entry:<br>
set title &lt;L1&gt;<br></li>
</ul></li>
</ul>
</li>
<a name="title"></a>
<li>title<br>
A special form of label (see above), which replaces the string &lt;TL&gt;
in the .gplot file. It defaults to the filename of the logfile.
</li>
<a name="plotfunction"></a>
<li>plotfunction<br>
Space value separated list of values. The value will be used to replace
&lt;SPEC#&gt; type of strings in the .gplot file, with # beginning at 1
(&lt;SPEC1&gt;, &lt;SPEC2&gt;, etc.) in the #FileLog or #DbLog directive.
With this attribute you can use the same .gplot file for multiple devices
with the same logdevice.
<ul><b>Example:</b><br>
<li>#FileLog <SPEC1><br>
with: attr <weblinkdevice> plotfunction "4:IR\x3a:0:"<br>
instead of<br>
#FileLog 4:IR\x3a:0:
</li>
<li>#DbLog <SPEC1><br>
with: attr <weblinkdevice> plotfunction
"Garage_Raumtemp:temperature::"<br> instead of<br>
#DbLog Garage_Raumtemp:temperature::
</li>
</ul>
</li>
</ul>
<br>
<a name="weblinkEditor"></a>
<b>Plot-Editor specialities</b>
<ul>
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:
<li>if you want to omit the title for a Diagram label, enter notitle in the
input field.</li>
<li>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.<br>
Example:
<ul>
Enter in the Tics field: ("On" 1, "Off" 0)<br>
Write .gplot file<br>
Select "1" from the column dropdown (note the double quote!) for the
regexp switch.on, and "0" for the regexp switch.off.<br>
Write .gplot file again<br>
</ul></li>
</ul>
<br>
</ul>
=end html

View File

@ -104,6 +104,7 @@
<a href="#sequence">sequence</a> &nbsp;
<a href="#speedtest">speedtest</a> &nbsp;
<a href="#structure">structure</a> &nbsp;
<a href="#SVG">SVG</a> &nbsp;
<a href="#telnet">telnet</a> &nbsp;
<a href="#Twilight">Twilight</a> &nbsp;
<a href="#THRESHOLD">THRESHOLD</a> &nbsp;

View File

@ -102,6 +102,7 @@
<a href="#sequence">sequence</a> &nbsp;
<a href="#speedtest">speedtest</a> &nbsp;
<a href="#structure">structure</a> &nbsp;
<a href="#SVG">SVG</a> &nbsp;
<a href="#telnet">telnet</a> &nbsp;
<a href="#Twilight">Twilight</a> &nbsp;
<a href="#THRESHOLD">THRESHOLD</a> &nbsp;