From bd3450cb81850a6e4ab2c3ad3288407f497bf854 Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Tue, 28 Oct 2008 07:28:56 +0000 Subject: [PATCH] Olaf Droegehorn's pgm5. See the README for details git-svn-id: https://svn.fhem.de/fhem/trunk@258 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/webfrontend/pgm5/02_FHEMRENDERER.pm | 586 +++++++++++ fhem/webfrontend/pgm5/README.txt | 71 ++ fhem/webfrontend/pgm5/fhemweb.pl | 1122 ++++++++++++++++++++++ fhem/webfrontend/pgm5/style.css | 44 + 4 files changed, 1823 insertions(+) create mode 100644 fhem/webfrontend/pgm5/02_FHEMRENDERER.pm create mode 100644 fhem/webfrontend/pgm5/README.txt create mode 100644 fhem/webfrontend/pgm5/fhemweb.pl create mode 100644 fhem/webfrontend/pgm5/style.css diff --git a/fhem/webfrontend/pgm5/02_FHEMRENDERER.pm b/fhem/webfrontend/pgm5/02_FHEMRENDERER.pm new file mode 100644 index 000000000..e43bb0c41 --- /dev/null +++ b/fhem/webfrontend/pgm5/02_FHEMRENDERER.pm @@ -0,0 +1,586 @@ +############################################## +package main; + +use strict; +use warnings; + +use IO::Socket; + + +################### +# Config +use vars qw($__ME); +my $FHEMRENDERERdir = "$attr{global}{modpath}/FHEM"; # + +use vars qw(%defs); +use vars qw(%attr); + +# Nothing to config below +######################### + +######################### +# Forward declaration + +sub FHEMRENDERER_getAttr($$); +sub FHEMRENDERER_setAttr($$); +sub FHEMRENDERER_parseXmlList($); +sub FHEMRENDERER_render($); +sub FHEMRENDERER_fatal($); +sub pF($@); +sub pO(@); +#sub FHEMRENDERER_zoomLink($$$$); +sub FHEMRENDERER_calcWeblink($$); + +######################### +# As we are _not_ multithreaded, it is safe to use global variables. +my %__icons; # List of icons +my $__iconsread; # Timestamp of last icondir check +my %__rooms; # hash of all rooms +my %__devs; # hash of all devices ant their attributes +my %__types; # device types, for sorting +my $__room; # currently selected room +my $__detail; # durrently selected device for detail view +my $__title; # Page title +my $__cmdret; # Returned data by the fhem call +my $__scrolledweblinkcount; # Number of scrolled weblinks +my %__pos; # scroll position +my $__RET; # Returned data (html) +my $__RETTYPE; # image/png or the like +my $__SF; # Short for submit form +my $__ti; # Tabindex for all input fields +my @__zoom; # "qday", "day","week","month","year" +my %__zoom; # the same as @__zoom +my $__wname; # instance name +my $__plotmode; # Current plotmode +my $__plotsize; # Size for a plot +my $__timeinterval; # Time-Intervall for Renderer +my $__data; # Filecontent from browser when editing a file +my $__svgloaded; # Do not load the SVG twice +my $__lastxmllist; # last time xmllist was parsed +my $FHEMRENDERER_tmpfile; # TempDir & File for the rendered graphics + +##################################### +sub +FHEMRENDERER_Initialize($) +{ + my ($hash) = @_; + + $hash->{ReadFn} = "FHEMRENDERER_Read"; + $hash->{DefFn} = "FHEMRENDERER_Define"; + $hash->{UndefFn} = "FHEMRENDERER_Undef"; + $hash->{AttrList}= "loglevel:0,1,2,3,4,5,6 plotmode:gnuplot,gnuplot-scroll plotsize refresh tmpfile status"; + $hash->{SetFn} = "FHEMRENDERER_Set"; + $hash->{GetFn} = "FHEMRENDERER_Get"; +} + +##################################### +sub +FHEMRENDERER_Define($$) +{ + my ($hash, $def) = @_; + my ($name, $type, $global) = split("[ \t]+", $def); + return "Usage: define FHEMRENDERER [global]" + if($global && $global ne "global"); + + $hash->{STATE} = "Initialized"; + Log(2, "FHEMRENDERER defined"); + + ############### + # Initialize internal structures + my $n = 0; + @__zoom = ("qday", "day","week","month","year"); + %__zoom = map { $_, $n++ } @__zoom; + $__wname = $hash->{NAME}; + + $__timeinterval = FHEMRENDERER_getAttr("refresh", "00:10:00"); + $__plotmode = FHEMRENDERER_getAttr("plotmode", "gnuplot"); + $__plotsize = FHEMRENDERER_getAttr("plotsize", "800,200"); + $FHEMRENDERER_tmpfile = FHEMRENDERER_getAttr("tmpfile", "/tmp/"); + FHEMRENDERER_setAttr("status", "off"); + + return undef; +} + +##################################### +sub +FHEMRENDERER_Undef($$) +{ + my ($hash, $arg) = @_; + return undef; +} + + +################################### +sub +FHEMRENDERER_Set($@) +{ + my ($hash, @a) = @_; + my $ret = undef; + my $na = int(@a); + + return "no set value specified" if($na < 2 || $na > 3); + +# if($__plotmode eq "SVG" && !$modules{SVG}{LOADED}) { +# my $ret = CommandReload(undef, "98_SVG"); +# Log 0, $ret if($ret); +# } + + if($a[1] eq "on") { + $__timeinterval = FHEMRENDERER_getAttr("refresh", "00:10:00"); + CommandDefine(undef, $hash->{NAME} . "_trigger at +*$__timeinterval get $a[0]"); + FHEMRENDERER_setAttr("status", "on"); + } elsif($a[1] eq "off") { + CommandDelete(undef, $__wname . "_trigger"); + FHEMRENDERER_setAttr("status", "off"); + } + + return $ret; +} + + +##################################### +sub +FHEMRENDERER_Get($@) +{ + my ($hash, @a) = @_; + + my $ret = undef; + my $v; + my $t; + + FHEMRENDERER_parseXmlList(0); + + $__plotmode = FHEMRENDERER_getAttr("plotmode", "gnuplot"); + $__plotsize = FHEMRENDERER_getAttr("plotsize", "800,200"); + $FHEMRENDERER_tmpfile = FHEMRENDERER_getAttr("tmpfile", "/tmp/"); + + if (@a <= 2) { + if (@a == 2) { + my ($p,$v) = split("=",$a[1], 2); + + # Multiline: escape the NL for fhem + $v =~ s/[\r]\n/\\\n/g if($v && $p && $p ne "data"); + Log(2, "P: $p, V: $v"); + + if($p eq "pos") { + %__pos = split(/[=&]/, $v); + } + } + + foreach my $type (sort keys %__types) { + if($type eq "weblink") { + foreach my $d (sort keys %__devs ) { + next if($__devs{$d}{type} ne $type); + + $v = $__devs{$d}{INT}{LINK}{VAL}; + $t = $__devs{$d}{INT}{WLTYPE}{VAL}; + if($t eq "fileplot") { + my @va = split(":", $v, 3); + if(@va != 3 || !$__devs{$va[0]}{INT}{currentlogfile}) { + pO "Broken definition: $v"; + } else { + if($va[2] eq "CURRENT") { + $__devs{$va[0]}{INT}{currentlogfile}{VAL} =~ m,([^/]*)$,; + $va[2] = $1; + } + FHEMRENDERER_render ("undef $d $va[0] $va[1] $va[2]"); + } + } + } + } + } + + } elsif (@a == 4 ) { + FHEMRENDERER_render ("undef $a[3] $a[1] $a[2] $a[3]"); + + } elsif (@a == 5 ) { + my ($p,$v) = split("=",$a[4], 2); + + # Multiline: escape the NL for fhem + $v =~ s/[\r]\n/\\\n/g if($v && $p && $p ne "data"); + Log(2, "P: $p, V: $v"); + + if($p eq "pos") { + %__pos = split(/[=&]/, $v); + FHEMRENDERER_render ("undef $a[3] $a[1] $a[2] $a[3]"); + + } else { + FHEMRENDERER_render ("undef $a[1] $a[2] $a[3] $a[4]"); + } + + } elsif (@a == 6 ) { + my ($p,$v) = split("=",$a[5], 2); + + # Multiline: escape the NL for fhem + $v =~ s/[\r]\n/\\\n/g if($v && $p && $p ne "data"); + Log(2, "P: $p, V: $v"); + + if($p eq "pos") { + %__pos = split(/[=&]/, $v); + FHEMRENDERER_render ("undef $a[1] $a[2] $a[3] $a[4]"); + + } else { + return "\"get FHEMRENDERER\" needs either none, 1(pos) or 3-5 arguments ([file-name] device type logfile [pos=zoom=XX&off=YYY])"; + } + } else { + return "\"get FHEMRENDERER\" needs either none, 1(pos) or 3-5 arguments ([file-name] device type logfile [pos=zoom=XX&off=YYY])"; + } + return $ret; +} + +##################### +# Get the data and parse it. We are parsing XML in a non-scientific way :-) +sub +FHEMRENDERER_parseXmlList($) +{ + my $docmd = shift; + my $name; + + if(!$docmd && $__lastxmllist && (time() - $__lastxmllist) < 2) { + $__room = $__devs{$__detail}{ATTR}{room}{VAL} if($__detail); + return; + } + + $__lastxmllist = time(); + %__rooms = (); + %__devs = (); + %__types = (); + $__title = ""; + + foreach my $l (split("\n", fC("xmllist"))) { + + ####### Device + if($l =~ m/^\t\t<(.*) name="(.*)" state="(.*)" sets="(.*)" attrs="(.*)">/){ + $name = $2; + $__devs{$name}{type} = ($1 eq "HMS" ? "KS300" : $1); + $__devs{$name}{state} = $3; + $__devs{$name}{sets} = $4; + $__devs{$name}{attrs} = $5; + next; + } + ####### INT, ATTR & STATE + if($l =~ m,^\t\t\t<(.*) key="(.*)" value="([^"]*)"(.*)/>,) { + my ($t, $n, $v, $m) = ($1, $2, $3, $4); + $v =~ s,<br>,
,g; + $__devs{$name}{$t}{$n}{VAL} = $v; + if($m) { + $m =~ m/measured="(.*)"/; + $__devs{$name}{$t}{$n}{TIM} = $1; + } + + if($t eq "ATTR" && $n eq "room") { + $__rooms{$v}{$name} = 1; + if($name eq "global") { + $__rooms{$v}{LogFile} = 1; + $__devs{LogFile}{ATTR}{room}{VAL} = $v; + } + } + + if($name eq "global" && $n eq "logfile") { + my $ln = "LogFile"; + $__devs{$ln}{type} = "FileLog"; + $__devs{$ln}{INT}{logfile}{VAL} = $v; + $__devs{$ln}{state} = "active"; + } + } + + } + if(defined($__devs{global}{ATTR}{archivedir})) { + $__devs{LogFile}{ATTR}{archivedir}{VAL} = + $__devs{global}{ATTR}{archivedir}{VAL}; + } + + ################# + #Tag the gadgets without room with "Unsorted" + if(%__rooms) { + foreach my $name (keys %__devs ) { + if(!$__devs{$name}{ATTR}{room}) { + $__devs{$name}{ATTR}{room}{VAL} = "Unsorted"; + $__rooms{Unsorted}{$name} = 1; + } + } + } + + ############### + # Needed for type sorting + foreach my $d (sort keys %__devs ) { + $__types{$__devs{$d}{type}} = 1; + } + $__title = $__devs{global}{ATTR}{title}{VAL} ? + $__devs{global}{ATTR}{title}{VAL} : "First page"; + $__room = $__devs{$__detail}{ATTR}{room}{VAL} if($__detail); +} + + +###################### +# Generate an image from the log via gnuplot +sub +FHEMRENDERER_render($) +{ + my ($cmd) = @_; + my (undef, $wl, $d, $type, $file) = split(" ", $cmd, 5); + + my $gplot_pgm = "$FHEMRENDERERdir/$type.gplot"; + return FHEMRENDERER_fatal("Cannot read $gplot_pgm") if(!-r $gplot_pgm); + FHEMRENDERER_calcWeblink($d,$wl); + + if($__plotmode =~ m/gnuplot/) { + if($__plotmode eq "gnuplot" || !$__devs{$d}{from}) { + + # Looking for the logfile.... + + $__devs{$d}{INT}{logfile}{VAL} =~ m,^(.*)/([^/]*)$,; # Dir and File + my $path = "$1/$file"; + $path = $__devs{$d}{ATTR}{archivedir}{VAL} . "/$file" if(!-f $path); + return FHEMRENDERER_fatal("Cannot read $path") if(!-r $path); + + open(FH, $gplot_pgm) || return FHEMRENDERER_fatal("$gplot_pgm: $!"); + my $gplot_script = join("", ); + close(FH); + + $gplot_script =~ s//$FHEMRENDERER_tmpfile$wl/g; + $gplot_script =~ s//$__plotsize/g; + $gplot_script =~ s//$path/g; + $gplot_script =~ s//$file/g; + + if($__devs{$wl} && $__devs{$wl}{ATTR}{fixedrange}) { + my $fr = $__devs{$wl}{ATTR}{fixedrange}{VAL}; + $fr =~ s/ /\":\"/; + $fr = "set xrange [\"$fr\"]\n"; + $gplot_script =~ s/(set timefmt ".*")/$1\n$fr/; + } + + open(FH, "|gnuplot > /dev/null");# feed it to gnuplot + print FH $gplot_script; + close(FH); + + } elsif($__plotmode eq "gnuplot-scroll") { + + ############################ + # 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); + open(FH, $gplot_pgm) || return FHEMRENDERER_fatal("$gplot_pgm: $!"); + while(my $l = ) { + if($l =~ m/^#FileLog (.*)$/) { + push(@filelog, $1); + } elsif($l =~ "^plot" || $plot) { + $plot .= $l; + } else { + push(@data, $l); + } + } + close(FH); + + my $gplot_script = join("", @data); + $gplot_script =~ s//$FHEMRENDERER_tmpfile$wl/g; + $gplot_script =~ s//$__plotsize/g; + $gplot_script =~ s//$file/g; + + my ($f,$t)=($__devs{$d}{from}, $__devs{$d}{to}); + + my @path = split(" ", fC("get $d $file $FHEMRENDERER_tmpfile$wl $f $t " . + join(" ", @filelog))); + 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); + } + + open(FH, "|gnuplot > /dev/null");# feed it to gnuplot + print FH $gplot_script, $xrange, $plot; + close(FH); + foreach my $p (@path) { + unlink($p); + } + } + + } #elsif($__plotmode eq "SVG") { + +# my (@filelog, @data, $plot); +# open(FH, $gplot_pgm) || return FHEMRENDERER_fatal("$gplot_pgm: $!"); +# while(my $l = ) { +# if($l =~ m/^#FileLog (.*)$/) { +# push(@filelog, $1); +# } elsif($l =~ "^plot" || $plot) { +# $plot .= $l; +# } else { +# push(@data, $l); +# } +# } +# close(FH); +# my ($f,$t)=($__devs{$d}{from}, $__devs{$d}{to}); +# $f = 0 if(!$f); # From the beginning of time... +# $t = 9 if(!$t); # till the end +# +# my $ret = fC("get $d $file INT $f $t " . join(" ", @filelog)); +# SVG_render($file, $__plotsize, $f, $t, \@data, $internal_data, $plot); +# +# open (FH, ">$FHEMRENDERER_tmpfile$wl.svg"); +# print FH $__RET; +# close (FH); +# } +} + + + +################## +sub +FHEMRENDERER_fatal($) +{ + my ($msg) = @_; + pO "$msg"; +} + +################## +# print formatted +sub +pF($@) +{ + my $fmt = shift; + $__RET .= sprintf $fmt, @_; +} + +################## +# print output +sub +pO(@) +{ + $__RET .= shift; +} + +################## +# fhem command +sub +fC($) +{ + my ($cmd) = @_; + #Log 0, "Calling $cmd"; + my $oll = $attr{global}{verbose}; + $attr{global}{verbose} = 0 if($cmd ne "save"); + my $ret = AnalyzeCommand(undef, $cmd); + $attr{global}{verbose} = $oll if($cmd !~ m/attr.*global.*verbose/); + return $ret; +} + +################## +sub +FHEMRENDERER_getAttr($$) +{ + my ($aname, $def) = @_; + if($attr{$__wname} && defined($attr{$__wname}{$aname})) { + return $attr{$__wname}{$aname}; + } else { + CommandAttr (undef, $__wname . " $aname $def"); + } + return $def; +} + +################## +sub +FHEMRENDERER_setAttr($$) +{ + my ($aname, $def) = @_; + CommandAttr (undef, $__wname . " $aname $def"); +} + + +################## +# 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 +FHEMRENDERER_calcWeblink($$) +{ + my ($d,$wl) = @_; + + return if($__plotmode eq "gnuplot"); + my $now = time(); + + my $zoom = $__pos{zoom}; + $zoom = "day" if(!$zoom); + + if(!$d) { + foreach my $d (sort keys %__devs ) { + next if($__devs{$d}{type} ne "weblink"); + next if(!$__room || ($__room ne "all" && !$__rooms{$__room}{$d})); + next if($__devs{$d}{ATTR} && $__devs{$d}{ATTR}{noscroll}); + next if($__devs{$d}{ATTR} && $__devs{$d}{ATTR}{fixedrange}); + $__scrolledweblinkcount++; + } + return; + } + + + return if(!$__devs{$wl}); + return if($__devs{$wl} && $__devs{$wl}{ATTR}{noscroll}); + + if($__devs{$wl} && $__devs{$wl}{ATTR}{fixedrange}) { + my @range = split(" ", $__devs{$wl}{ATTR}{fixedrange}{VAL}); + $__devs{$d}{from} = $range[0]; + $__devs{$d}{to} = $range[1]; + return; + } + + my $off = $__pos{$d}; + $off = 0 if(!$off); + $off += $__pos{off} if($__pos{off}); + + if($zoom eq "qday") { + + my $t = $now + $off*21600; + my @l = localtime($t); + $l[2] = int($l[2]/6)*6; + $__devs{$d}{from} + = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]); + $__devs{$d}{to} + = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]+6); + + } elsif($zoom eq "day") { + + my $t = $now + $off*86400; + my @l = localtime($t); + $__devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]); + $__devs{$d}{to} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]+1); + + } elsif($zoom eq "week") { + + my @l = localtime($now); + my $t = $now - ($l[6]*86400) + ($off*86400)*7; + @l = localtime($t); + $__devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]); + + @l = localtime($t+7*86400); + $__devs{$d}{to} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]); + + + } elsif($zoom eq "month") { + + my @l = localtime($now); + while($off < -12) { + $off += 12; $l[5]--; + } + $l[4] += $off; + $l[4] += 12, $l[5]-- if($l[4] < 0); + $__devs{$d}{from} = sprintf("%04d-%02d", $l[5]+1900, $l[4]+1); + + $l[4]++; + $l[4] = 0, $l[5]++ if($l[4] == 12); + $__devs{$d}{to} = sprintf("%04d-%02d", $l[5]+1900, $l[4]+1); + + } elsif($zoom eq "year") { + + my @l = localtime($now); + $l[5] += $off; + $__devs{$d}{from} = sprintf("%04d", $l[5]+1900); + $__devs{$d}{to} = sprintf("%04d", $l[5]+1901); + + } +} + + +1; \ No newline at end of file diff --git a/fhem/webfrontend/pgm5/README.txt b/fhem/webfrontend/pgm5/README.txt new file mode 100644 index 000000000..2ba20af15 --- /dev/null +++ b/fhem/webfrontend/pgm5/README.txt @@ -0,0 +1,71 @@ +Web frontend 5 (webfrontend/pgm5) (known upto FHEM 4.2 as pgm2): + +This frontend is CGI/CSS based. It has support for rooms, and FHT/KS300 logs. + +This webfrontend is an update of the former know pgm2 (up to 4.2): +It resides in YOUR HTTP server, and doesn't provide an own, like the FHEMWEB module does. + +Why to use this: +1) If you want to stick with your Web-Servers (due to restrictions, + ports or any other reason) +2) If you have a NAS (Network attached storage) and limited CPU-Power. + This frontend can render the graphics in the background (in + intervals) and sends only the rendered graphics to the HTML-Page. +3) If you need the FHEMRENDERER to render the images for other/own + pages. + +How it works: +The WebFrontend works as usual and well known from before. +Main difference: +It creates and uses an instance of FHEMRENDERER (called renderer). +The renderer has plotmode, plotsize, tmpfile, status, refresh as attributes. +With this you can control, how it works (and when in renders: refresh: 00:15:00 means every 15 minutes it will render an update). + +What will be rendered: All FileLogs for which you have set a WebLink will be rendered in the given intervals. + +The GET method of the renderer is also able to render specific graphics only for single use on request. + +Supported methods of renderer: +DEFINE, SET, GET, ATTR +DEFINE: defines the renderer +SET: Set renderer ON/OFF toggels interval based rendering +GET: renders graphics, based on given parameters +ATTR: defines attributes, but all attributes will be set during define + to default values. + +The PGM5 webfrontend does all this for you, but if you want to use the FHEMRENDERER for own things, you can use it directly. + +Installation: +Copy the file fhemweb.pl and *.css to your cgi-bin directory (/home/httpd/cgi-bin), the icons (*.gif) to your httpd icons (/home/httpd/icons), and commandref.html to the html directory (/home/httpd/html) (or also to cgi-bin directory). + +The *.gplot files should be reused from the built-in FHEMWEB and should reside in the installed FHEM directory. Here we don't provide specific *.gplot files as the mechanisms are exactly the same. + +Note: The program looks for icons in the following order: +., , ., + +If you want to have access to plotted logs, then make sure that gnuplot is installed and set the logtype for the FileLog device (see commandref.html and example/04_log). +Copy the file contrib/99_weblink.pm to the installed FHEM directory. + +Copy the file pgm5/02_FHEMRENDERER.pm to the installed FHEM directory. +This gives you a grphic rendering engine (gnuplot & gnuplot-scroll at the moment), which can be configured to renderer images in intervals. + +Call /cgi-bin/fhemweb.pl + +If you want to show only a part of your devices on a single screen +(i.e divide them into separate rooms), then assign each device the +room attribute in the config file: + + attr ks300 room garden + attr ks300-log room garden + +The attribute title of the global device will be used as title on the first +screen, which shows the list of all rooms. Devices in the room +"hidden" will not be shown. Devices without a room attribute go +to the room "misc". + +To configure the absicondir and relicondir correctly, look into the +httpd.conf file (probably /etc/httpd/conf/httpd.conf), and check the +line which looks like: + Alias /icons/ "/home/httpd/icons/" +relicondir will then be /icons, and absicondir /home/httpd/icons. + diff --git a/fhem/webfrontend/pgm5/fhemweb.pl b/fhem/webfrontend/pgm5/fhemweb.pl new file mode 100644 index 000000000..d31d2d780 --- /dev/null +++ b/fhem/webfrontend/pgm5/fhemweb.pl @@ -0,0 +1,1122 @@ +#!/usr/bin/perl + +#Note: use warnings/-w is deadly on some linux devices (e.g.WL500GX) +use strict; +use warnings; +use POSIX; + +use Time::HiRes qw(gettimeofday); + + +use CGI; +use IO::Socket; + +################### +# Config +my $addr = "localhost:7072"; # FHZ server +my $absicondir = "/home/httpd/icons"; # Copy your icons here +my $relicondir = "/icons"; +my $gnuplotdir = "/usr/local/FHEM"; # the .gplot filees live here (should be the FHEM dir, as FHEMRENDERER needs them there) +my $fhemwebdir = "/home/httpd/cgi-bin"; # the fhemweb.pl & style.css files live here +my $faq = "/home/httpd/cgi-bin/faq.html"; +my $howto = "/home/httpd/cgi-bin/HOWTO.html"; +my $doc = "/home/httpd/cgi-bin/commandref.html"; +my $tmpfile = "/tmp/pgm5-"; # the Images will be rendered there with beginning of name +my $configfile = "/etc/fhem.conf"; # the fhem.conf file is that +my $plotmode = "gnuplot"; # Current plotmode +my $plotsize = "800,200"; # Size for a plot +my $renderer = "pgm5_renderer"; # Name of suitable renderer +my $rendrefresh= "00:15:00"; # Refresh Interval for the Renderer + + +# Nothing to config below +######################### + +######################### +# Forward declaration +sub checkDirs(); +sub digestCgi(); +sub doDetail($); +sub fhemcmd($); +sub fileList($); +sub makeTable($$$$$$$$); +sub parseXmlList($); +sub showRoom(); +sub showArchive($); +sub showLog($); +sub showLogWrapper($); +sub roomOverview($); +sub style($$); +sub fatal($); +sub zoomLink($$$$); +sub calcWeblink($$); +sub makeEdit($$$$); + + +######################### +# Global variables; +my $me = $ENV{SCRIPT_NAME}; + +my %icons; # List of icons +my $iconsread; # Timestamp of last icondir check +my %rooms; # hash of all rooms +my %devs; # hash of all devices ant their attributes +my %types; # device types, for sorting +my $room; # currently selected room +my $detail; # durrently selected device for detail view +my $title; # Page title +my $cmdret; # Returned data by the fhem call +my $scrolledweblinkcount; # Number of scrolled weblinks +my %pos; # scroll position +my $RET; # Returned data (html) +my $RETTYPE; # image/png or the like +my $SF; # Short for submit form +my $ti; # Tabindex for all input fields +my @zoom; # "qday", "day","week","month","year" +my %zoom; # the same as @zoom +my $wname; # Web instance name +my $data; # Filecontent from browser when editing a file +my $lastxmllist; # last time xmllist was parsed + +my ($lt, $ltstr); + +############### +# Initialize internal structures +my $n = 0; +@zoom = ("qday", "day","week","month","year"); +%zoom = map { $_, $n++ } @zoom; + +open(FH, "$fhemwebdir/style.css") || fatal("$fhemwebdir/style.css: $!"); # Read in the template Stylesheet file +my $css = join("", ); +close(FH); + +$me = "" if(!$me); +my $q = new CGI; + + +################## +# Lets go: +my ($cmd,$debug) = digestCgi(); + +my $docmd = 0; +$docmd = 1 if($cmd && + $cmd !~ /^showlog/ && + $cmd !~ /^toweblink/ && + $cmd !~ /^showarchive/ && + $cmd !~ /^style / && + $cmd !~ /^edit/); + +$cmdret = fhemcmd($cmd) if($docmd); + +parseXmlList($docmd); + +if($cmd =~ m/^showlog /) { + showLog($cmd); + exit (0); +} + + +if($cmd =~ m/^toweblink (.*)$/) { + my @aa = split(":", $1); + my $max = 0; + for my $d (keys %devs) { + $max = ($1+1) if($d =~ m/^wl_(\d+)$/ && $1 >= $max); + } + $devs{$aa[0]}{INT}{currentlogfile}{VAL} =~ m,([^/]*)$,; + $aa[2] = "CURRENT" if($1 eq $aa[2]); + $cmdret = fhemcmd("define wl_$max weblink fileplot $aa[0]:$aa[1]:$aa[2]"); + if(!$cmdret) { + $detail = "wl_$max"; + parseXmlList($docmd); + } +} + +print $q->header; +print $q->start_html(-name=>$title, -title=>$title, -style=>{ -code=>$css }); + +if($cmdret) { + $detail = ""; + $room = ""; + $cmdret =~ s//>/g; + print "
\n"; + print "
$cmdret
\n"; + print "
\n"; +} + +roomOverview($cmd); + +style($cmd,undef) if($cmd =~ m/^style /); + +doDetail($detail) if($detail); +showRoom() if($room && !$detail); +showLogWrapper($cmd) if($cmd =~ /^showlogwrapper/); +showArchive($cmd) if($cmd =~ m/^showarchive/); +print $q->end_html; +exit(0); + + +################### +sub +fhemcmd($) +{ + my $p = shift; + + my $server = IO::Socket::INET->new(PeerAddr => $addr); + if(!$server) { + print $q->h3("Can't connect to the server on $addr"); + print $q->end_html; + return 0; + } + syswrite($server, "$p; quit\n"); + my ($lst, $buf) = ("", ""); + while(sysread($server, $buf, 2048) > 0) { + $lst .= $buf; + } + close($server); + return $lst; +} + +########################### +# Digest CGI parameters +sub +digestCgi() +{ + my (%arg, %val, %dev); + my ($cmd, $debug, $c) = ("","",""); + + foreach my $p ($q->param) { + my $v = $q->param($p); + $debug .= "$p : $v
\n"; + + if($p eq "detail") { $detail = $v; } + if($p eq "room") { $room = $v; } + if($p eq "cmd") { $cmd = $v; delete($q->{$p}); } + if($p =~ m/^arg\.(.*)$/) { $arg{$1} = $v; } + if($p =~ m/^val\.(.*)$/) { $val{$1} = $v; } + if($p =~ m/^dev\.(.*)$/) { $dev{$1} = $v; } + if($p =~ m/^cmd\.(.*)$/) { $cmd = $v; $c= $1; delete($q->{$p}); } + if($p eq "pos") { %pos = split(/[=]/, $v); } + if($p eq "data") { $data = $v; } + + + } + $cmd.=" $dev{$c}" if($dev{$c}); + $cmd.=" $arg{$c}" if($arg{$c}); + $cmd.=" $val{$c}" if($val{$c}); + return ($cmd, $debug); +} + +##################### +# Get the data and parse it. We are parsing XML in a non-scientific way :-) +sub +parseXmlList($) +{ + my $docmd = shift; + my $name; + + if(!$docmd && $lastxmllist && (time() - $lastxmllist) < 2) { + $room = $devs{$detail}{ATTR}{room}{VAL} if($detail); + return; + } + + $lastxmllist = time(); + %rooms = (); + %devs = (); + %types = (); + $title = ""; + + foreach my $l (split("\n", fhemcmd("xmllist"))) { + + ####### Device + if($l =~ m/^\t\t<(.*) name="(.*)" state="(.*)" sets="(.*)" attrs="(.*)">/){ + $name = $2; + $devs{$name}{type} = ($1 eq "HMS" ? "KS300" : $1); + $devs{$name}{state} = $3; + $devs{$name}{sets} = $4; + $devs{$name}{attrs} = $5; + next; + } + ####### INT, ATTR & STATE + if($l =~ m,^\t\t\t<(.*) key="(.*)" value="([^"]*)"(.*)/>,) { + my ($t, $n, $v, $m) = ($1, $2, $3, $4); + #### NEW ###### + $v =~ s,<br>,
,g; + $devs{$name}{$t}{$n}{VAL} = $v; + if($m) { + $m =~ m/measured="(.*)"/; + $devs{$name}{$t}{$n}{TIM} = $1; + } + + if($t eq "ATTR" && $n eq "room") { + $rooms{$v}{$name} = 1; + if($name eq "global") { + $rooms{$v}{LogFile} = 1; + $devs{LogFile}{ATTR}{room}{VAL} = $v; + } + } + + if($name eq "global" && $n eq "logfile") { + my $ln = "LogFile"; + $devs{$ln}{type} = "FileLog"; + $devs{$ln}{INT}{logfile}{VAL} = $v; + $devs{$ln}{state} = "active"; + } + } + + } + if(defined($devs{global}{ATTR}{archivedir})) { + $devs{LogFile}{ATTR}{archivedir}{VAL} = + $devs{global}{ATTR}{archivedir}{VAL}; + } + + ################# + #Tag the gadgets without room with "Unsorted" + if(%rooms) { + foreach my $name (keys %devs ) { + if(!$devs{$name}{ATTR}{room}) { + $devs{$name}{ATTR}{room}{VAL} = "Unsorted"; + $rooms{Unsorted}{$name} = 1; + } + } + } + + ############### + # Needed for type sorting + foreach my $d (sort keys %devs ) { + $types{$devs{$d}{type}} = 1; + } + $title = $devs{global}{ATTR}{title}{VAL} ? + $devs{global}{ATTR}{title}{VAL} : "Home Management"; + $room = $devs{$detail}{ATTR}{room}{VAL} if($detail); +} + +############################## +sub +makeTable($$$$$$$$) +{ + my($d,$t,$header,$hash,$clist,$ccmd,$makelink,$cmd) = (@_); + + return if(!$hash && !$clist); + + $t = "EM" if($t =~ m/^EM.*$/); # EMWZ,EMEM,etc. + print " \n"; + + # Header + print " "; + foreach my $h (split(",", $header)) { + print ""; + } + print "\n"; + if($clist) { + print "\n"; + my @al = map { s/[:;].*//;$_ } split(" ", $clist); + print ""; + print ""; + print ""; + print $q->hidden("dev.$ccmd$d", $d); + print "", $row?"odd":"even"); + $row = ($row+1)%2; + if($makelink && $doc) { + print ""; + } else { + print ""; + } + + if($v eq "DEF") { + makeEdit($d, $t, "modify", $hash->{$v}{VAL}); + } else { + print ""; + } + + print "" if($hash->{$v}{TIM}); + print "" + if($cmd); + + print "\n"; + } + print "
$h
" . $q->popup_menu(-name=>"arg.$ccmd$d", -value=>\@al) . "" . $q->textfield(-name=>"val.$ccmd$d", -size=>6) . "" . $q->submit(-name=>"cmd.$ccmd$d", -value=>$ccmd) . "
\n"; + } + + my $row = 1; + foreach my $v (sort keys %{$hash}) { + printf("
$v$v$hash->{$v}{VAL}$hash->{$v}{TIM}$cmd
\n"; + print "
\n"; + +} + +############################## +sub +showArchive($) +{ + my ($arg) = @_; + my (undef, $d) = split(" ", $arg); + + my $fn = $devs{$d}{INT}{logfile}{VAL}; + if($fn =~ m,^(.+)/([^/]+)$,) { + $fn = $2; + } + $fn = $devs{$d}{ATTR}{archivedir}{VAL} . "/" . $fn; + my $t = $devs{$d}{type}; + + print "
\n"; + print "
\n"; + print "", $row?"odd":"even"); + $row = ($row+1)%2; + if(!defined($l)) { + print(""); + } else { + foreach my $ln (split(",", $l->{VAL})) { + my ($lt, $name) = split(":", $ln); + $name = $lt if(!$name); + print(""); + } + } + print ""; + } + + print "
\n"; + + my $row = 0; + my $l = $devs{$d}{ATTR}{logtype}; + foreach my $f (fileList($fn)) { + printf("
$ftext$name
\n"; + print "
\n"; + print "
\n"; +} + + +############################## +sub +doDetail($) +{ + my ($d) = @_; + + print $q->start_form; + print $q->hidden("detail", $d); + + $room = $devs{$d}{ATTR}{room}{VAL} if($devs{$d}{ATTR}{room}); + + my $t = $devs{$d}{type}; + + print "
\n"; + print "
\n"; + print "Delete $d\n"; + + my $pgm = "Javascript:" . + "s=document.getElementById('edit').style;". + "if(s.display=='none') s.display='block'; else s.display='none';". + "s=document.getElementById('disp').style;". + "if(s.display=='none') s.display='block'; else s.display='none';"; + print "Modify $d"; + + + print "
\n"; + makeTable($d, $t, "State,Value,Measured", + $devs{$d}{STATE}, $devs{$d}{sets}, "set", 0, undef); + makeTable($d, $t, "Internal,Value", + $devs{$d}{INT}, "", undef, 0, undef); + makeTable($d, $t, "Attribute,Value,Action", + $devs{$d}{ATTR}, $devs{$d}{attrs}, "attr", 1, + $d eq "global" ? "" : "delattr"); + print "
\n"; + print "
\n"; + + print $q->end_form; +} + +############## +# Room overview +sub +roomOverview($) +{ + my ($cmd) = @_; + print $q->start_form; + + print "
\n"; + + print "
"; + + print "Cmd: "; + print $q->textfield(-name=>"cmd", -size=>30); + + $scrolledweblinkcount = 0; + if($room) { + print $q->hidden(-name=>"room", -value=>"$room"); + if(!$detail) { # Global navigation buttons for weblink >= 2 + calcWeblink(undef,undef); + if($scrolledweblinkcount) { + print ""; + print "  "; + zoomLink("zoom=-1", "Zoom-in.png", "zoom in", 0); + zoomLink("zoom=1", "Zoom-out.png","zoom out", 0); + zoomLink("off=-1", "Prev.png", "prev", 0); + zoomLink("off=1", "Next.png", "next", 0); + } + } + } + print "
"; + print "
\n"; + + print "
\n"; + print " \n"; + print " \n"; + print "
\n"; # Need for "right" compatibility + print " \n"; + $room = "" if(!$room); + foreach my $r (sort keys %rooms) { + next if($r eq "hidden"); + printf(" ", $r eq $room ? " class=\"sel\"" : ""); + print "\n"; + } + + printf(" ", "all" eq $room ? " class=\"sel\"" : ""); + print ""; + print " \n"; + + print "
$r"; + print "
All together
\n"; + print "
\n"; + print " \n"; + print " \n"; + print " \n"; + print " \n"; + my $sel = ($cmd =~ m/examples/) ? " class=\"sel\"" : ""; + print " \n"; + $sel = ($cmd =~ m/list/) ? " class=\"sel\"" : ""; + print " \n"; + print "
Howto
FAQ
Details
Examples
Edit files
\n"; + print "
\n"; + print "
\n"; + print $q->end_form; +} + +################# +# Read in the icons +sub +checkDirs() +{ + return if($iconsread && (time() - $iconsread) < 5); + %icons = (); + + if(opendir(DH, $absicondir)) { + while(my $l = readdir(DH)) { + next if($l =~ m/^\./); + my $x = $l; + $x =~ s/\.[^.]+$//; # Cut .gif/.jpg + $icons{$x} = $l; + } + closedir(DH); + } + $iconsread = time(); +} + +######################## +# Generate the html output: i.e present the data +sub +showRoom() +{ + checkDirs(); + my $havelookedforrenderer; + + print $q->start_form; + print "
\n"; + print " \n
\n"; # Need for equal width of subtables + + foreach my $type (sort keys %types) { + + ################# + # Filter the devices in the room + if($room && $room ne "all") { + my $havedev; + foreach my $d (sort keys %devs ) { + next if($devs{$d}{type} ne $type); + next if(!$rooms{$room}{$d}); + $havedev = 1; + last; + } + next if(!$havedev); + } + + my $rf = ($room ? "&room=$room" : ""); + + + ############################ + # Print the table headers + my $t = $type; + $t = "EM" if($t =~ m/^EM.*$/); + print " \n"; + + if($type eq "FS20") { + print " "; + print ""; + print "\n"; + } + if($type eq "FHT") { + print " "; + print ""; + print "\n"; + } + + my $hstart = " \n"; + print $hstart . "Logs" . $hend if($type eq "FileLog"); + print $hstart . "HMS/KS300", $row?"odd":"even"); + $row = ($row+1)%2; + + ##################### + # Check if the icon exists + + my $v = $devs{$d}{state}; + + if($type eq "FS20") { + + my $v = $devs{$d}{state}; + my $iv = $v; + my $iname = ""; + + if(defined($devs{$d}) && + defined($devs{$d}{ATTR}{showtime})) { + $v = $devs{$d}{STATE}{state}{TIM}; + } elsif($iv) { + $iv =~ s/ .*//; # Want to be able to have icons for "on-for-timer xxx" + $iname = $icons{"$type"} if($icons{"$type"}); + $iname = $icons{"$type.$iv"} if($icons{"$type.$iv"}); + $iname = $icons{"$d"} if($icons{"$d"}); + $iname = $icons{"$d.$iv"} if($icons{"$d.$iv"}); + } + $v = "" if(!defined($v)); + + print ""; + if($iname) { + print ""; + } else { + print ""; + } + if($devs{$d}{sets}) { + print ""; + print ""; + } + + } elsif($type eq "FHT") { + + $v = $devs{$d}{STATE}{"measured-temp"}{VAL}; + $v = "" if(!defined($v)); + + $v =~ s/ .*//; + print ""; + print ""; + + $v = sprintf("%2.1f", int(2*$v)/2) if($v =~ m/[0-9.-]/); + my @tv = map { ($_.".0", $_+0.5) } (16..26); + $v = int($v*20)/$v if($v =~ m/^[0-9].$/); + print $q->hidden("arg.$d", "desired-temp"); + print $q->hidden("dev.$d", $d); + print ""; + + } elsif($type eq "FileLog") { + print "\n"; + if($devs{$d}{ATTR}{archivedir}) { + print(""); + } + my $l = $devs{$d}{ATTR}{logtype}; + if(!defined($l)) { + my %h = ("VAL" => "text"); + $l = \%h; + } + + foreach my $f (fileList($devs{$d}{INT}{logfile}{VAL})) { + printf(" ", $row?"odd":"even"); + $row = ($row+1)%2; + foreach my $ln (split(",", $l->{VAL})) { + my ($lt, $name) = split(":", $ln); + $name = $lt if(!$name); + print(""); + } + print ""; + } + + } elsif($type eq "weblink" && $room ne "all") { + $v = $devs{$d}{INT}{LINK}{VAL}; + $t = $devs{$d}{INT}{WLTYPE}{VAL}; + if($t eq "link") { + print "\n"; + } elsif($t eq "fileplot") { + my @va = split(":", $v, 3); + if(@va != 3 || !$devs{$va[0]}{INT}{currentlogfile}) { + print(""); + } else { + if($va[2] eq "CURRENT") { + $devs{$va[0]}{INT}{currentlogfile}{VAL} =~ m,([^/]*)$,; + $va[2] = $1; + } + + ################### + # Search for fitting renderer + if (!$havelookedforrenderer) { + my $haverend; + foreach my $rend (sort keys %devs ) { + next if($rend ne $renderer); + $haverend = 1; + last; + } + $havelookedforrenderer = 1; + if (!$haverend) { + fhemcmd ("define $renderer FHEMRENDERER"); + fhemcmd ("attr $renderer plotmode $plotmode"); + fhemcmd ("attr $renderer plotsize $plotsize"); + fhemcmd ("attr $renderer refresh $rendrefresh"); + fhemcmd ("attr $renderer tmpfile $tmpfile"); + fhemcmd ("set $renderer on"); + fhemcmd ("get $renderer"); + } + } + print ""; + print ""; + } + } + + } else { + print "\n"; + } + print " \n"; + } + print "
FS20 dev.StateSet to
FHT dev.MeasuredSet to
"; + my $hend = "
Readings" . $hend if($type eq "KS300"); + print $hstart . "Scheduled commands (at)" . $hend if($type eq "at"); + print $hstart . "Triggers (notify)" . $hend if($type eq "notify"); + print $hstart . "Global variables" . $hend if($type eq "_internal_"); + + my $row=1; + foreach my $d (sort keys %devs ) { + + next if($devs{$d}{type} ne $type); + next if($room && $room ne "all" && !$rooms{$room}{$d}); + + printf("
$d$vonoff$d$v°" . + $q->popup_menu(-name=>"val.$d", -values=>\@tv, -default=>$v) . + $q->submit(-name=>"cmd.$d", -value=>"set") . "$d$varchive
$f$name
$dBroken definition: $v"; + + my $wl = "&pos=" . join("=", map {"$_=$pos{$_}"} keys %pos); + + my $arg="$me?cmd=showlog $d $va[0] $va[1] $va[2]$wl"; + if($plotmode eq "SVG") { + my ($w, $h) = split(",", $plotsize); + print "\n"; + } else { + print "\n"; + } + + print ""; + print "$d
$d$v
\n"; + print "
\n"; # Empty line + } + print "
\n"; + print "
\n"; + print $q->end_form; +} + +################# +sub +fileList($) +{ + my ($fname) = @_; + $fname =~ m,^(.*)/([^/]*)$,; # Split into dir and file + my ($dir,$re) = ($1, $2); + return if(!$re); + $re =~ s/%./\.*/g; + my @ret; + return @ret if(!opendir(DH, $dir)); + while(my $f = readdir(DH)) { + next if($f !~ m,^$re$,); + push(@ret, $f); + } + closedir(DH); + return sort @ret; +} + +###################### +sub +showLogWrapper($) +{ + my ($cmd) = @_; + my (undef, $d, $type, $file) = split(" ", $cmd, 4); + my $havelookedforrenderer; + + if($type eq "text") { + $devs{$d}{INT}{logfile}{VAL} =~ m,^(.*)/([^/]*)$,; # Split into dir and file + my $path = "$1/$file"; + $path = $devs{$d}{ATTR}{archivedir}{VAL} . "/$file" if(!-f $path); + + open(FH, $path) || fatal("$path: $!"); + my $cnt = join("", ); + close(FH); + $cnt =~ s//>/g; + + print "
\n"; + print "
$cnt
\n"; + print "
\n"; + + } else { + + ################### + # Search for fitting renderer + if (!$havelookedforrenderer) { + my $havedev; + foreach my $d (sort keys %devs ) { + next if($d ne $renderer); + $havedev = 1; + last; + } + $havelookedforrenderer = 1; + if (!$havedev) { + fhemcmd ("define $renderer FHEMRENDERER"); + fhemcmd ("attr $renderer plotmode $plotmode"); + fhemcmd ("attr $renderer plotsize $plotsize"); + fhemcmd ("attr $renderer refresh $rendrefresh"); + fhemcmd ("attr $renderer tmpfile $tmpfile"); + fhemcmd ("set $renderer on"); + fhemcmd ("get $renderer"); + } + } + print "
\n"; + print "\n"; + print "
\n"; + + print ""; + print "
"; + my $arg = "$me?cmd=showlog undef $d $type $file"; + if($plotmode eq "SVG") { + my ($w, $h) = split(",", $plotsize); + print "\n"; + } else { + print "\n"; + } + + print "
Convert to weblink
\n"; + print "\n"; + print "
\n"; + } +} + +###################### +sub +showLog($) +{ + my ($cmd) = @_; + my (undef, $wl, $d, $type, $file) = split(" ", $cmd, 5); + + my $arguments = "pos=" . join("&", map {"$_=$pos{$_}"} keys %pos); + + if (($wl eq "undef") || ($pos{off}) || ($pos{zoom})) { + if ($wl eq "undef") { + fhemcmd ("get $renderer $d $type $file $arguments"); + } else { + if (!$arguments) { + fhemcmd ("get $renderer $wl $d $type $file"); + } else { + fhemcmd ("get $renderer $wl $d $type $file $arguments"); + } + } + } + + print $q->header(-type=>"image/png"); + + if ($wl eq "undef") { + open (FH, "$tmpfile$file.png"); # read in the result and send it + print join("", ); + close(FH); + unlink ("$tmpfile$file.png"); + } else { + open(FH, "$tmpfile$wl.png"); # read in the result and send it + print join("", ); + close(FH); + } + + exit(0); +} + +################## +sub +fatal($) +{ + my ($msg) = @_; + print $q->header; + print $q->start_html(); + print($msg); + print $q->end_html; + exit(0); +} + +################## +# Multiline (for some types of widgets) editor with submit +sub +makeEdit($$$$) +{ + my ($name, $type, $cmd, $val) = @_; + + print ""; + print "
"; + my $eval = $val; + $eval =~ s,
,\n,g; + + if($type eq "at" || $type eq "notify") { + print ""; + } else { + print ""; + } + $ti++; + print "
" . $q->submit(-name=>"cmd.${cmd}$name", -value=>"$cmd $name"); + + print "
"; + $eval = "
$eval
" if($eval =~ m/\n/); + print "
$eval
"; + print ""; +} + +################## +# Generate the zoom and scroll images with links if appropriate +sub +zoomLink($$$$) +{ + my ($cmd, $img, $alt, $br) = @_; + + my ($d,$off) = split("=", $cmd, 2); + + return if($plotmode eq "gnuplot"); # No scrolling + return if($devs{$d} && $devs{$d}{ATTR}{fixedrange}); + return if($devs{$d} && $devs{$d}{ATTR}{noscroll}); + + my $val = $pos{$d}; + + $cmd = "room=$room&pos="; + if($d eq "zoom") { + + $val = "day" if(!$val); + $val = $zoom{$val}; + return if(!defined($val) || $val+$off < 0 || $val+$off >= int(@zoom) ); + $val = $zoom[$val+$off]; + return if(!$val); + + # Approximation of the next offset. + my $w_off = $pos{off}; + $w_off = 0 if(!$w_off); + if($val eq "qday") { + $w_off = $w_off*4; + } 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=$pos{zoom}; + $zoom = 0 if(!$zoom); + $cmd .= "zoom=$zoom=off=$off"; + + } + + print ""; + print "\"$alt\""; + print "
" if($br); +} + +################## +# 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 +calcWeblink($$) +{ + my ($d,$wl) = @_; + + return if($plotmode eq "gnuplot"); + my $now = time(); + + my $zoom = $pos{zoom}; + $zoom = "day" if(!$zoom); + + if(!$d) { + foreach my $d (sort keys %devs ) { + next if($devs{$d}{type} ne "weblink"); + next if(!$room || ($room ne "all" && !$rooms{$room}{$d})); + next if($devs{$d}{ATTR} && $devs{$d}{ATTR}{noscroll}); + next if($devs{$d}{ATTR} && $devs{$d}{ATTR}{fixedrange}); + $scrolledweblinkcount++; + } + return; + } + + return if(!$devs{$wl}); + return if($devs{$wl} && $devs{$wl}{ATTR}{noscroll}); + + if($devs{$wl} && $devs{$wl}{ATTR}{fixedrange}) { + my @range = split(" ", $devs{$wl}{ATTR}{fixedrange}{VAL}); + $devs{$d}{from} = $range[0]; + $devs{$d}{to} = $range[1]; + return; + } + + my $off = $pos{$d}; + $off = 0 if(!$off); + $off += $pos{off} if($pos{off}); + + if($zoom eq "qday") { + + my $t = $now + $off*21600; + my @l = localtime($t); + $l[2] = int($l[2]/6)*6; + $devs{$d}{from} + = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]); + $devs{$d}{to} + = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]+6); + + } elsif($zoom eq "day") { + + my $t = $now + $off*86400; + my @l = localtime($t); + $devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]); + $devs{$d}{to} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]+1); + + } elsif($zoom eq "week") { + + my @l = localtime($now); + my $t = $now - ($l[6]*86400) + ($off*86400)*7; + @l = localtime($t); + $devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]); + + @l = localtime($t+7*86400); + $devs{$d}{to} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]); + + + } elsif($zoom eq "month") { + + my @l = localtime($now); + while($off < -12) { + $off += 12; $l[5]--; + } + $l[4] += $off; + $l[4] += 12, $l[5]-- if($l[4] < 0); + $devs{$d}{from} = sprintf("%04d-%02d", $l[5]+1900, $l[4]+1); + + $l[4]++; + $l[4] = 0, $l[5]++ if($l[4] == 12); + $devs{$d}{to} = sprintf("%04d-%02d", $l[5]+1900, $l[4]+1); + + } elsif($zoom eq "year") { + + my @l = localtime($now); + $l[5] += $off; + $devs{$d}{from} = sprintf("%04d", $l[5]+1900); + $devs{$d}{to} = sprintf("%04d", $l[5]+1901); + + } +} + +################## +# List/Edit/Save css and gnuplot files +sub +style($$) +{ + my ($cmd, $msg) = @_; + my @a = split(" ", $cmd); + + if($a[1] eq "list") { + + my @fl; + push(@fl, "fhem.cfg"); + push(@fl, "
"); + push(@fl, fileList("$fhemwebdir/.*.css")); + push(@fl, "
"); + push(@fl, fileList("$gnuplotdir/.*.gplot")); + push(@fl, "
"); + push(@fl, fileList("$fhemwebdir/.*html")); + + print "
\n"; + print "
\n"; + print " $msg

\n" if($msg); + print " \n"; + my $row = 0; + foreach my $file (@fl) { + print ""; + print ""; + $row = ($row+1)%2; + } + print "
$file
\n"; + print "
\n"; + print "
\n"; + + } elsif($a[1] eq "examples") { + + my @fl = fileList("$fhemwebdir/example.*"); + print "
\n"; + print "
\n"; + print " $msg

\n" if($msg); + print " \n"; + my $row = 0; + foreach my $file (@fl) { + print ""; + print ""; + $row = ($row+1)%2; + } + print "
$file
\n"; + print "
\n"; + print "
\n"; + + } elsif($a[1] eq "edit") { + + $a[2] =~ s,/,,g; # little bit of security + my $f = ($a[2] eq "fhem.cfg" ? $configfile : + "$fhemwebdir/$a[2]"); + if(!open(FH, $f)) { + print "$f: $!"; + return; + } + my $data = join("", ); + close(FH); + + print "
\n"; + print "
"; + + print $q->submit(-name=>"save", -value=>"Save $f") . "

"; + + print $q->hidden("cmd", "style save $a[2]"); + print ""; + print "
"; + print "
\n"; + + } elsif($a[1] eq "save") { + + $a[2] =~ s,/,,g; # little bit of security + my $f = ($a[2] eq "fhem.cfg" ? $configfile : + "$fhemwebdir/$a[2]"); + if(!open(FH, ">$f")) { + print "$f: $!"; + return; + } + print FH $data; + close(FH); + style("style list", "Saved file $f"); + $f = ($a[2] eq "fhem.cfg" ? $configfile : $a[2]); + + fhemcmd("rereadcfg") if($a[2] eq "fhem.cfg"); + } + +} + +1; + diff --git a/fhem/webfrontend/pgm5/style.css b/fhem/webfrontend/pgm5/style.css new file mode 100644 index 000000000..d53486667 --- /dev/null +++ b/fhem/webfrontend/pgm5/style.css @@ -0,0 +1,44 @@ +body { color: black; background: #FFFFD7; } + +table { -moz-border-radius:8px; } + +table.room { border:thin solid; width: 100%; background: #D7FFFF; } +table.room tr.sel { background: #A0FFFF; } + +table.at { border:thin solid; width: 100%; background: #FFFFC0; } +table.at tr.odd { background: #FFFFD7; } + +table.notify { border:thin solid; width: 100%; background: #D7D7A0; } +table.notify tr.odd { background: #FFFFC0; } + +table.FileLog { border:thin solid; width: 100%; background: #FFC0C0; } +table.FileLog tr.odd { background: #FFD7D7; } + +table._internal_ { border:thin solid; width: 100%; background: #C0C0C0; } +table._internal_ tr.odd { background: #D7D7D7; } + +table.FS20 { border:thin solid; width: 100%; background: #C0FFFF; } +table.FS20 tr.odd { background: #D7FFFF; } + +table.FHT { border:thin solid; width: 100%; background: #FFC0C0; } +table.FHT tr.odd { background: #FFD7D7; } + +table.KS300 { border:thin solid; width: 100%; background: #C0FFC0; } +table.KS300 tr.odd { background: #A7FFA7; } + +table.FHZ { border:thin solid; width: 100%; background: #C0C0C0; } +table.FHZ tr.odd { background: #D7D7D7; } + +table.EM { border:thin solid; width: 100%; background: #E0E0E0; } +table.EM tr.odd { background: #F0F0F0; } + +table.FHEMWEB { border:thin solid; width: 100%; background: #E0E0E0; } +table.FHEMWEB tr.odd { background: #F0F0F0; } + +table.FHEMRENDERER { border:thin solid; width: 100%; background: #E0E0E0; } +table.FHEMRENDERER tr.odd { background: #F0F0F0; } + +#hdr { position:absolute; top:10px; left:10px; } +#left { position:absolute; top:50px; left:10px; width:130px; } +#right { position:absolute; top:50px; left:160px; + right:10px; bottom:10px; overflow:auto; }