#!/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/pgm6-"; # 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 = "320,200"; # Size for a plot
my $renderer = "pgm6_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;
##################
# iPhone Anpassungen:
$me = "" if(!$me);
my $q = new CGI;
$ti = 1;
##################
# Lets go:
my ($cmd,$debug) = digestCgi();
my $docmd = 0;
$docmd = 1 if($cmd &&
$cmd !~ /^showlog/ &&
$cmd !~ /^toweblink/ &&
$cmd !~ /^showarchive/ &&
$cmd !~ /^style / &&
$cmd !~ /^edit/);
if($docmd) {
$cmdret = fhemcmd($cmd);
exit (0);
}
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, -meta=> {'viewport'=>'width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;'}, -style =>{ -type=>'text/css', -media=>'screen', -src=>'./icons/iui.css'}, -script=>{ -type=>"application/x-javascript", -src=>"./icons/iui.js"});
if ($cmd =~ m/^style /) {
style($cmd,undef);
} elsif ($detail) {
doDetail($detail);
} elsif ($room && !$detail) {
showRoom();
} elsif ($cmd =~ /^showlogwrapper/) {
showLogWrapper($cmd);
} elsif ($cmd =~ m/^showarchive/) {
showArchive($cmd);
} else {
roomOverview($cmd);
}
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} : "FHEM - Control";
$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 "$h | ";
}
print "
\n";
if($clist) {
print "\n";
my @al = map { s/[:;].*//;$_ } split(" ", $clist);
print "" . $q->popup_menu(-name=>"arg.$ccmd$d", -value=>\@al) . " | ";
print "" . $q->textfield(-name=>"val.$ccmd$d", -size=>6) . " | ";
print "" . $q->submit(-name=>"cmd.$ccmd$d", -value=>$ccmd) . " | ";
print $q->hidden("dev.$ccmd$d", $d);
print "\n";
}
my $row = 1;
foreach my $v (sort keys %{$hash}) {
printf(" |
", $row?"odd":"even");
$row = ($row+1)%2;
if($makelink && $doc) {
print "$v | ";
} else {
print "$v | ";
}
if($v eq "DEF") {
makeEdit($d, $t, "modify", $hash->{$v}{VAL});
} else {
print "$hash->{$v}{VAL} | ";
}
print "$hash->{$v}{TIM} | " if($hash->{$v}{TIM});
print "$cmd | "
if($cmd);
print "
\n";
}
print "
\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 "\n";
my $row = 0;
my $l = $devs{$d}{ATTR}{logtype};
foreach my $f (fileList($fn)) {
printf(" | $f | ", $row?"odd":"even");
$row = ($row+1)%2;
if(!defined($l)) {
print("text | ");
} else {
foreach my $ln (split(",", $l->{VAL})) {
my ($lt, $name) = split(":", $ln);
$name = $lt if(!$name);
print("$name | ");
}
}
print " ";
}
print " \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"";
print "";
#########
#Alte Kommando-Zeile
# print "Cmd: ";
# print $q->textfield(-name=>"cmd", -size=>30);
print " - Rooms:
";
$room = "" if(!$room);
foreach my $r (sort keys %rooms) {
next if($r eq "hidden");
print " - $r
";
}
print " - All together
";
print " - Help/Configuration:
";
print " - Howto
";
print " - FAQ
";
print " - Details
";
print " - Examples
";
print " - Edit files
";
print "
";
}
#################
# 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( -id => $room, -title => $room, -class => 'panel', selected => 'true', action => $me.'?room='.$room);
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.*$/);
if($type eq "FS20") {
print " FS20
";
}
if($type eq "FHT") {
#print " FHT dev. | Measured | ";
print " FHT
";
}
print " Logs
" if($type eq "FileLog");
print " HMS/KS300 Readings
" if($type eq "KS300");
print " Scheduled commands (at)
" if($type eq "at");
print " Triggers (notify)
" if($type eq "notify");
print " Global variables
" if($type eq "_internal_");
print"
---|