############################################## # $Id$ 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; ##################################### sub weblink_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "weblink_Define"; $hash->{AttrList} = "fixedrange plotmode plotsize label ". "title htmlattr plotfunction"; $hash->{SetFn} = "weblink_Set"; $hash->{FW_summaryFn} = "weblink_FwFn"; $hash->{FW_detailFn} = "weblink_FwFn"; $hash->{FW_atPageEnd} = 1; $data{FWEXT}{"/weblinkDetails"}{FUNC} = "weblink_WriteGplot"; } ##################################### 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 ); if(!$link || !$thash{$wltype}) { return "Usage: define weblink [" . join("|",sort keys %thash) . "] "; } $hash->{WLTYPE} = $wltype; $hash->{LINK} = $link; $hash->{STATE} = "initial"; return undef; } sub weblink_Set($@) { my ($hash, @a) = @_; my $me = $hash->{NAME}; return "no set argument specified" if(int(@a) < 2); my %sets = (copyGplotFile=>0); my $cmd = $a[1]; return "Unknown argument $cmd, choose one of ".join(" ",sort keys %sets) if(!defined($sets{$cmd})); return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 2); if($cmd eq "copyGplotFile") { return "type is not fileplot" if($hash->{WLTYPE} ne "fileplot"); my @a = split(":", $hash->{LINK}); my $srcName = "$FW_gplotdir/$a[1].gplot"; $a[1] = $hash->{NAME}; my $dstName = "$FW_gplotdir/$a[1].gplot"; $hash->{LINK} = join(":", @a); return "this is already a unique gplot file" if($srcName eq $dstName); $hash->{DEF} = "$hash->{WLTYPE} $hash->{LINK}"; open(SFH, $srcName) || return "Can't open $srcName: $!"; open(DFH, ">$dstName") || return "Can't open $dstName: $!"; while(my $l = ) { print DFH $l; } close(SFH); close(DFH); } return undef; } ##################################### # FLOORPLAN compat sub FW_showWeblink($$$$) { my ($d,undef,undef,$buttons) = @_; if($buttons !~ m/HASH/) { my %h = (); $buttons = \%h; } FW_pO(weblink_FwFn(undef, $d, "", $buttons)); return $buttons; } ################## sub weblink_FwDetail($@) { my ($d, $text)= @_; return "" if(AttrVal($d, "group", "")); my $alias= AttrVal($d, "alias", $d); my $ret = "
"; $ret .= "$text " if($text); $ret .= FW_pHPlain("detail=$d", $alias) if(!$FW_subdir); $ret .= "
"; return $ret; } sub weblink_FwFn($$$$) { my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. my $hash = $defs{$d}; my $link = $hash->{LINK}; my $wltype = $hash->{WLTYPE}; my $ret = ""; my $attr = AttrVal($d, "htmlattr", ""); if($wltype eq "htmlCode") { $link = AnalyzePerlCommand(undef, $link) if($link =~ m/^{(.*)}$/); $ret = $link; } elsif($wltype eq "link") { $ret = "$d"; # no FW_pH, open extra browser } elsif($wltype eq "image") { $ret = "
" . weblink_FwDetail($d); } elsif($wltype eq "iframe") { $ret = "" . weblink_FwDetail($d); } elsif($wltype eq "cmdList") { my @lines = split(" ", $link); my $row = 0; my $ret = ""; $ret .= ""; $ret .= ""; $ret .= "
"; foreach my $line (@lines) { my @args = split(":", $line, 3); $ret .= ""; $ret .= ""; $ret .= ""; } $ret .= "
\"$args[0]\" $args[1]

"; return $ret; } elsif($wltype eq "fileplot" || $wltype eq "dbplot" ) { # plots navigation buttons if((!$pageHash || !$pageHash->{buttons}) && ($wltype eq "fileplot" || $wltype eq "dbplot") && AttrVal($d, "fixedrange", "x") !~ m/^[ 0-9:-]*$/) { $ret .= FW_zoomLink("zoom=-1", "Zoom-in", "zoom in"); $ret .= FW_zoomLink("zoom=1", "Zoom-out","zoom out"); $ret .= FW_zoomLink("off=-1", "Prev", "prev"); $ret .= FW_zoomLink("off=1", "Next", "next"); $pageHash->{buttons} = 1 if($pageHash); $ret .= "
"; } my @va = split(":", $link, 3); if($wltype eq "fileplot" && (@va != 3 || !$defs{$va[0]} || !$defs{$va[0]}{currentlogfile})) { $ret .= weblink_FwDetail($d, "Broken definition "); } elsif ($wltype eq "dbplot" && (@va != 2 || !$defs{$va[0]})) { $ret .= weblink_FwDetail($d, "Broken definition "); } else { if ($wltype eq "dbplot") { $va[2] = "-"; } my $wl = "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos); my $arg="$FW_ME?cmd=showlog $d $va[0] $va[1] $va[2]$wl"; if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); $ret .= "\n"; } else { $ret .= ""; } if(!$pageHash) { $ret .= wl_PEdit($FW_wname,$d,$room,$pageHash) if($wltype eq "fileplot" && $FW_plotmode eq "SVG"); $ret .= "
"; } else { $ret .= weblink_FwDetail($d) if(!$FW_hiddenroom{detail}); } } } return $ret; } sub wl_cb($$$) { my ($v,$t,$c) = @_; $c = ($c ? " checked" : ""); return "$t "; } sub wl_txt($$$$) { my ($v,$t,$c,$sz) = @_; $c = "" if(!defined($c)); $c =~ s/"/\"/g; return "$t "; } sub wl_sel($$$@) { my ($v,$l,$c,$fnData) = @_; my @al = split(",",$l); $c =~ s/\\x3a/:/g if($c); return FW_select($v,$v,\@al,$c, "set", $fnData); } sub wl_getRegFromFile($) { my ($fName) = @_; my $fh = new IO::File $fName; if(!$fh) { Log 1, "$fName: $!"; return (3, "NoFile", "NoFile"); } $fh->seek(0, 2); # Go to the end my $sz = $fh->tell; $fh->seek($sz > 65536 ? $sz-65536 : 0, 0); my $data; $data = <$fh> if($sz > 65536); # discard the first/partial line my $maxcols = 0; my %h; while($data = <$fh>) { my @cols = split(" ", $data); next if(@cols < 3); $maxcols = @cols if(@cols > $maxcols); $cols[2] = "*" if($cols[2] =~ m/^[-\.\d]+$/); $h{"$cols[1].$cols[2]"} = $data; $h{"$cols[1].*"} = "" if($cols[2] ne "*"); } $fh->close(); return ($maxcols+1, join(",", sort keys %h), join("
", grep /.+/,map { $h{$_} } sort keys %h)), close(FH); } sub wl_addTics($$) { my ($in, $p) = @_; return if(!$in || $in !~ m/^\((.*)\)$/); map { $p->{"\"$2\""}=1 if(m/^ *([^ ]+) ([^ ]+) */); } split(",",$1); } ############################ # gnuplot file "editor" sub wl_PEdit($$$$) { my ($FW_wname,$d,$room,$pageHash) = @_; my @a = split(":", $defs{$d}{LINK}); my $gp = "$FW_gplotdir/$a[1].gplot"; my $file = $defs{$a[0]}{currentlogfile}; my ($err, $cfg, $plot, $flog) = FW_readgplotfile($d, $gp, $file); my %conf = SVG_digestConf($cfg, $plot); my $ret .= "
"; $ret .= FW_hidden("detail", $d); $ret .= FW_hidden("gplotName", $gp); $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $conf{ylabel} =~ s/"//g if($conf{ylabel}); $ret .= ""; $conf{y2label} =~ s/"//g if($conf{y2label}); $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= wl_cb("gridy", "left", $conf{hasygrid}); $ret .= wl_cb("gridy2","right",$conf{hasy2grid}); $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .=" "; my ($colnums, $colregs, $coldata) = wl_getRegFromFile($file); $colnums = join(",", 3..$colnums); my %tickh; wl_addTics($conf{ytics}, \%tickh); wl_addTics($conf{y2tics}, \%tickh); $colnums = join(",", sort keys %tickh).",$colnums" if(%tickh); my $max = @{$conf{lType}}+1; $max = 8 if($max > 8); my $r = 0; for($r=0; $r < $max; $r++) { $ret .= ""; } $ret .= ""; $ret .= ""; $ret .= "
Plot title".wl_txt("title", "", $conf{title}, 32)."
Y-Axis label".wl_txt("ylabel", "left", $conf{ylabel}, 16)."".wl_txt("y2label","right", $conf{y2label}, 16)."
Grid aligned
Range as [min:max]".wl_txt("yrange", "left", $conf{yrange}, 16)."".wl_txt("y2range", "right", $conf{y2range}, 16)."
Tics as (\"Txt\" val, ...)".wl_txt("ytics", "left", $conf{ytics}, 16)."".wl_txt("y2tics","right", $conf{y2tics}, 16)."
Diagramm labelInput:Column,Regexp,DefaultValue,FunctionY-Axis,Plot-Type,Style,Width
"; $ret .= wl_txt("title_${r}", "", !$conf{lTitle}[$r]&&$r<($max-1) ? "notitle" : $conf{lTitle}[$r], 12); $ret .= ""; my @f = split(":", ($flog->[$r] ? $flog->[$r] : ":::"), 4); $ret .= wl_sel("cl_${r}", $colnums, $f[0]); $ret .= wl_sel("re_${r}", $colregs, $f[1]); $ret .= wl_txt("df_${r}", "", $f[2], 1); $ret .= wl_txt("fn_${r}", "", $f[3], 6); $ret .= ""; my $v = $conf{lAxis}[$r]; $ret .= wl_sel("axes_${r}", "left,right", ($v && $v eq "x1y1") ? "left" : "right"); $ret .= wl_sel("type_${r}", "lines,points,steps,fsteps,histeps,bars", $conf{lType}[$r]); my $ls = $conf{lStyle}[$r]; if($ls) { $ls =~ s/class=//g; $ls =~ s/"//g; } $ret .= wl_sel("style_${r}", "l0,l1,l2,l3,l4,l5,l6,l7,l8,". "l0fill,l1fill,l2fill,l3fill,l4fill,l5fill,l6fill", $ls); my $lw = $conf{lWidth}[$r]; if($lw) { $lw =~ s/.*stroke-width://g; $lw =~ s/"//g; } $ret .= wl_sel("width_${r}", "0.2,0.5,1,1.5,2,3,4", ($lw ? $lw : 1)); $ret .= "
"; $ret .= "Example lines for input:
$coldata
"; $ret .= FW_submit("submit", "Write .gplot file")."
"; } sub weblink_WriteGplot($) { my ($arg) = @_; FW_digestCgi($arg); my $hasTl; for(my $i=0; $i <= 8; $i++) { $hasTl = 1 if($FW_webArgs{"title_$i"}); } return (undef, "continue") if(!$hasTl); my $fName = $FW_webArgs{gplotName}; return (undef, "continue") if(!$fName); if(!open(FH, ">$fName")) { Log 1, "weblink_WriteGplot: Can't write $fName"; return (undef, "continue"); } print FH "# Created by FHEMWEB, ".TimeNow()."\n"; print FH "set terminal png transparent size crop\n"; print FH "set output '.png'\n"; print FH "set xdata time\n"; print FH "set timefmt \"%Y-%m-%d_%H:%M:%S\"\n"; print FH "set xlabel \" \"\n"; print FH "set title '$FW_webArgs{title}'\n"; print FH "set ytics ".$FW_webArgs{ytics}."\n"; print FH "set y2tics ".$FW_webArgs{y2tics}."\n"; print FH "set grid".($FW_webArgs{gridy} ? " ytics" :""). ($FW_webArgs{gridy2} ? " y2tics":"")."\n"; print FH "set ylabel \"$FW_webArgs{ylabel}\"\n"; print FH "set y2label \"$FW_webArgs{y2label}\"\n"; print FH "set yrange $FW_webArgs{yrange}\n" if($FW_webArgs{yrange}); print FH "set y2range $FW_webArgs{y2range}\n" if($FW_webArgs{y2range}); print FH "\n"; my @plot; for(my $i=0; $i <= 8; $i++) { next if(!$FW_webArgs{"title_$i"}); my $re = $FW_webArgs{"re_$i"}; $re = "" if(!defined($re)); $re =~ s/:/\\x3a/g; print FH "#FileLog ". $FW_webArgs{"cl_$i"} .":$re:". $FW_webArgs{"df_$i"} .":". $FW_webArgs{"fn_$i"} ."\n"; push @plot, "\"\" using 1:2 axes ". ($FW_webArgs{"axes_$i"} eq "right" ? "x1y2" : "x1y1"). ($FW_webArgs{"title_$i"} eq "notitle" ? " notitle" : " title '".$FW_webArgs{"title_$i"} ."'"). " ls " .$FW_webArgs{"style_$i"} . " lw " .$FW_webArgs{"width_$i"} . " with " .$FW_webArgs{"type_$i"}; } print FH "\n"; print FH "plot ".join(",\\\n ", @plot)."\n"; close(FH); #foreach my $k (sort keys %FW_webArgs) { # Log 1, "$k: $FW_webArgs{$k}"; #} return (undef, "continue"); } 1; =pod =begin html

weblink

    Define
      define <name> weblink [link|fileplot|dbplot|image|iframe|htmlCode|cmdList] <argument>

      This is a placeholder used with webpgm2 to be able to integrate links into it, and to be able to put more than one gnuplot/SVG picture on one page. It has no set or get methods. Examples:
        define homepage weblink link http://www.fhem.de
        define webcam_picture weblink image http://w.x.y.z/current.jpg
        define interactive_webcam weblink iframe http://w.x.y.z/webcam.cgi
        define hr weblink htmlCode <hr>
        define w_Frlink weblink htmlCode { WeatherAsHtml("w_Frankfurt") }
        define MyPlot weblink fileplot <logdevice>:<gnuplot-file>:<logfile>
        define MyPlot weblink dbplot <logdevice>:<gnuplot-file>
        define systemCommands weblink cmdList pair:Pair:set+cul2+hmPairForSec+60 restart:Restart:shutdown+restart update:UpdateCheck:update+check

      Notes:
      • Normally you won't have to define fileplot weblinks manually, as FHEMWEB makes it easy for you, just plot a logfile (see logtype) and convert it to weblink. Now you can group these weblinks by putting them into rooms. If you convert the current logfile to a weblink, it will always refer to the current file, even if its name changes regularly (and not the one you originally specified).
      • For cmdList <argument> consist of a list of space separated icon:label:cmd triples.
    Set
    • copyGplotFile
      Only applicable to fileplot type weblinks.
      Copy the currently specified gplot file to a new file, which is named after the weblink (existing files will be overwritten), in order to be able to modify it locally without the problem of being overwritten by update. The weblink definition will be updated.

    Get
      N/A

    Attributes
    • htmlattr
      HTML attributes to be used for link, image and iframe type of links. E.g.:
        define yw weblink wl_im1 iframe http://weather.yahooapis.com/forecastrss?w=650272&u=c
        attr yw weblink htmlattr width="480" height="560"
    • fixedrange
    • plotsize
    • plotmode
    • label
      Double-Colon separated list of values. The values will be used to replace <L#> type of strings in the .gplot file, with # beginning at 1 (<L1>, <L2>, etc.). Each value will be evaluated as a perl expression, so you have access e.g. to the Value functions.

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

    Plot-Editor specialities
      If the weblink type is set to fileplot, a weblink editor is displayed in the detail view of the weblink. Most features are obvious here, up to some exceptions:
    • if you want to omit the title for a Diagram label, enter notitle in the input field.
    • if you want to specify a fixed value (not taken from a column) if a string found (e.g. 1 of the FS20 switch is on 0 if it off), then you have to specify the Tics first, and write the .gplot file, before you can select this value from the dropdown.
      Example:
        Enter in the Tics field: ("On" 1, "Off" 0)
        Write .gplot file
        Select "1" from the column dropdown (note the double quote!) for the regexp switch.on, and "0" for the regexp switch.off.
        Write .gplot file again

=end html =cut