# $Id$ =for comment ############################################## # # 55_InfoPanel.pm written by betateilchen # # forked from 02_RSS.pm by Dr. Boris Neubert # ############################################## # # This module uses vTicker, # a jQuery plug-in that adds scrolling vertical tickers # # vTicker is published under MIT and BSD licenses # # Copyright (c) 2009–2011 # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # ############################################## # # 2015-02-06 - 7892 - module moved from ./contrib to ./FHEM # 2015-02-08 - 7921 - changed: additional modules are optional # 2015-02-10 - 7931 - added: direct link to circle,ellipse,rect # 2015-02-11 - 7933 - added: item counter # 2015-02-11 - 7944 - added: items push/pop # 2015-02-12 - 7947 - changed: turtle added to new elements # 2015-02-14 - 7967 - added: item ticker # changed: img syntax # 2015-02-15 - 7989 - added: item embed # 7995 - added: xcondition (can be overridden by set) # set ovClear,ovDisable,ovEnable # get counter, layout, overrides # - removed: trashcan due to performance issues # - added: break condition for includes # 2015-02-24 - 8092 - added: longpoll support (experimental) # 2015-02-25 - 8095 - changed: iframe handling for secret div # 2015-03-07 - 8168 - fixed: handling for bg img (Bugzilla #8) # 2015-03-22 - 8268 - added: attribute showTime # 2015-03-24 - 8281 - added: BACK as special link to return # to $FW_httpheader{Referer} # changed: limit refresh to at least 60 secs # to prevent performance issues # 2015-03-29 - 8334 - changed: commandref updated # 2015-09-25 - - changed: support ReadingsVal() in ticker # with \n in text # 2016-09-04 - 12114 - added: movecalculated # # 2018-05-06 - 16695 - changed: check plotName exists # 2018-05-28 - $Rev$ - changed: remove misleading link in commandref # ############################################## =cut package main; use strict; use warnings; #use Data::Dumper; use feature qw/switch/; use vars qw(%data); use HttpUtils; my @valid_valign = qw(auto baseline middle center hanging); my @valid_halign = qw(start middle end); my $useImgTools = 1; no if $] >= 5.017011, warnings => 'experimental'; sub btIP_Define; sub btIP_Undef; sub btIP_Set; sub btIP_Get; sub btIP_Notify; sub btIP_readLayout; sub btIP_itemArea; sub btIP_itemButton; sub btIP_itemCircle; sub btIP_itemCounter; sub btIP_itemDate; sub btIP_itemEllipse; sub btIP_itemGroup; sub btIP_itemImg; sub _btIP_imgData; sub _btIP_imgRescale; sub btIP_itemLine; sub btIP_itemLongpoll; sub btIP_itemPlot; sub btIP_itemRect; sub btIP_itemSeconds; sub btIP_itemText; sub btIP_itemTextBox; sub btIP_itemTicker; sub btIP_itemTime; sub btIP_changeColor; sub btIP_color; sub btIP_FileRead; sub btIP_findTarget; sub btIP_xy; sub btIP_ReturnSVG; sub btIP_evalLayout; sub btIP_addExtension; sub btIP_CGI; sub btIP_splitRequest; sub btIP_returnHTML; sub btIP_HTMLHead; sub btIP_getScript; sub btIP_HTMLTail; sub btIP_Overview; sub btIP_getURL; ###################################### sub InfoPanel_Initialize { my ($hash) = @_; ## no critic eval "use MIME::Base64" ; $useImgTools = 0 if($@); Log3(undef,4,"InfoPanel: MIME::Base64 missing.") unless $useImgTools; eval "use Image::Info qw(image_info dim)"; $useImgTools = 0 if($@); Log3(undef,4,"InfoPanel: Image::Info missing.") unless $useImgTools; ## use critic $hash->{DefFn} = "btIP_Define"; $hash->{UndefFn} = "btIP_Undef"; $hash->{SetFn} = "btIP_Set"; $hash->{GetFn} = "btIP_Get"; $hash->{NotifyFn} = "btIP_Notify"; $hash->{AttrList} = "autoreread:1,0 useViewPort:1,0 bgcolor refresh size "; $hash->{AttrList} .= "mobileApp:none,apple,other "; $hash->{AttrList} .= "title noscript showTime:1,0 "; $hash->{AttrList} .= " bgcenter:1,0 bgdir bgopacity tmin" if $useImgTools; return; } sub btIP_Define { my ($hash, $def) = @_; my @a = split("[ \t]+", $def); return "Usage: define InfoPanel filename" if(int(@a) != 3); my $name= $a[0]; my $filename= $a[2]; $hash->{NOTIFYDEV} = 'global'; $hash->{fhem}{div} = ''; $hash->{LAYOUTFILE} = $filename; btIP_addExtension("btIP_CGI","btip","InfoPanel"); btIP_readLayout($hash); readingsSingleUpdate($hash,'state','defined',1); return; } sub btIP_Undef { my ($hash, $arg) = @_; # check if last device my $url = '/btip'; delete $data{FWEXT}{$url} if int(devspec2array('TYPE=InfoPanel')) == 1; return; } sub btIP_Set { my ($hash, @a) = @_; my $name = $a[0]; my $usage= "Unknown argument, choose one of reread:noArg ovClear ovEnable ovDisable"; my $ret = undef; given ($a[1]) { when ("ovClear") { if ($a[2] eq "all") { delete $defs{$name}{fhem}{override}; } else { delete $defs{$name}{fhem}{override}{$a[2]}; } } when ("ovDisable") { $defs{$name}{fhem}{override}{$a[2]} = 0; } when ("ovEnable") { $defs{$name}{fhem}{override}{$a[2]} = 1; } when ("reread") { btIP_readLayout($hash); } default { $ret = $usage; } } return $ret; } sub btIP_Get { my ($hash, @a) = @_; my $name = $a[0]; my $usage= "Unknown argument, choose one of reread:noArg counter:noArg layout:noArg overrides:noArg"; my $ret = undef; given ($a[1]) { when ("counter") { $ret = $defs{$name}{fhem}{counter}; } when ("layout") { $ret = $defs{$name}{fhem}{layout}; } when ("overrides") { last if(!defined($defs{$name}{fhem}{override})); foreach my $key ( keys %{$defs{$name}{fhem}{override}} ) { $ret .= "$key => $defs{$name}{fhem}{override}{$key} \n"; } } default { $ret = $usage; } } return $ret; } sub btIP_Notify { my ($hash,$dev) = @_; return unless AttrVal($hash->{NAME},'autoreload',1); return if($dev->{NAME} ne "global"); return if(!grep(m/^FILEWRITE $hash->{LAYOUTFILE}$/, @{$dev->{CHANGED}})); Log3(undef, 4, "InfoPanel: $hash->{NAME} reread layout after edit."); undef = btIP_readLayout($hash); return; } sub btIP_readLayout { my ($hash)= @_; my $filename= $hash->{LAYOUTFILE}; my $name= $hash->{NAME}; my $level = 0; my ($err, @layoutfile) = FileRead($filename); if($err) { # Log 1, "InfoPanel $name: $err"; # $hash->{fhem}{layout} = "text ERROR 50 50 \"Error on reading layout!\""; Log 1, "InfoPanel $name: $err"; $hash->{fhem}{layout} = "text ERROR 50 50 \"Error on reading layout!\""; my ($e,@layout) = FileRead('./FHEM/template.layout'); unless ($e){ FileWrite($filename,@layout); $hash->{fhem}{layout} = "text ERROR 50 50 \"Please edit layoutfile now.\""; } } else { $hash->{fhem}{layout} = join("\n", @layoutfile); while($hash->{fhem}{layout} =~ m/\@include/ && $level < 1000) { $level++; my (@layout2,@include); foreach my $ll (@layoutfile) { if($ll !~ m/^\@include/) { push(@layout2,$ll); } elsif ($ll =~ m/^\@include/) { my ($cmd, $def) = split("[ \t]+", $ll, 2); ($err,@include) = FileRead($def) if($def); splice(@layout2,-1,0,@include) unless $err; } } @layoutfile = @layout2; @layout2 = undef; $hash->{fhem}{layout} = join("\n",@layoutfile); } while($hash->{fhem}{layout} =~ m/\@finclude/ && $level < 1000) { $level++; my (@layout2,@include); foreach my $ll (@layoutfile) { if($ll !~ m/^\@finclude/) { push(@layout2,$ll); } else { my ($cmd, $def) = split("[ \t]+", $ll, 2); @include = split("\n",AnalyzePerlCommand(undef,$def)); splice(@layout2,-1,0,@include); } } @layoutfile = @layout2; @layout2 = undef; $hash->{fhem}{layout} = join("\n",@layoutfile); } $hash->{fhem}{layout} = "text ERROR 50 50 \"Loop detected in includes!\"" if ($level >= 1000); $hash->{fhem}{layout} =~ s/\n\n/\n/g; } return; } ################## # # Layout evaluation # ##### Items sub btIP_itemArea { my ($id,$x1,$y1,$x2,$y2,$link,$target,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my $oldrgb = $params{rgb}; $params{rgb} = '00000000'; my $output = btIP_itemRect($id,$x1,$y1,$x2,$y2,0,0,1,0,$link,$target,%params); $params{rgb} = $oldrgb; return $output; } sub btIP_itemButton { my ($id,$x1,$y1,$x2,$y2,$rx,$ry,$link,$text,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my $width = $x2 - $x1; my $height = $y2 - $y1; my $oldrgb = $params{rgb}; $params{rgb} = $params{boxcolor}; my $output = btIP_itemRect($id,$x1,$y1,$x2,$y2,$rx,$ry,1,0,$link,undef,%params); $params{rgb} = $oldrgb; my ($oldhalign,$oldvalign) = ($params{thalign},$params{tvalign}); ($params{thalign},$params{tvalign}) = ("middle","middle"); my $textoutput .= btIP_itemText("${id}_text",($x1+$x2)/2,($y1+$y2)/2,$text,%params); ($params{thalign},$params{tvalign}) = ($oldhalign,$oldvalign); $output =~ s/<\/a>/$textoutput<\/a>/; return $output; } sub btIP_itemCircle { my ($id,$x,$y,$r,$filled,$stroked,$link,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my $target; ($link,$target) = btIP_findTarget($link); my $output = ""; $output .= "\n" if($link && length($link)); $output .= " 0 || $stroked > 0) { $output .= "style=\""; if($filled > 0) { my ($r,$g,$b,$a) = btIP_color($params{rgb}); $output .= "fill:rgb($r,$g,$b); fill-opacity:$a; "; } if($stroked > 0) { my ($r,$g,$b,$a) = btIP_color($params{rgb}); $output .= "stroke:rgb($r,$g,$b); stroke-width:$stroked; "; $output .= "fill:none; " if ($filled == 0); } $output .= "\" "; } else { $output .= "style=\"fill:none; stroke-width:0; \" "; } $output .= "/>\n"; $output .= "\n" if($link && length($link)); return $output; } sub btIP_itemCounter { my ($id,$x,$y,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; return btIP_itemText($id,$x,$y,$params{counter},%params); } sub btIP_itemDate { my ($id,$x,$y,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return btIP_itemText($id,$x,$y,sprintf("%02d.%02d.%04d", $mday, $mon+1, $year+1900),%params); } sub btIP_itemEllipse { my ($id,$x,$y,$rx,$ry,$filled,$stroked,$link,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my $target; ($link,$target) = btIP_findTarget($link); my $output = ""; $output .= "\n" if($link && length($link)); $output .= " 0 || $stroked > 0) { $output .= "style=\""; if($filled > 0) { my ($r,$g,$b,$a) = btIP_color($params{rgb}); $output .= "fill:rgb($r,$g,$b); fill-opacity:$a; "; } if($stroked > 0) { my ($r,$g,$b,$a) = btIP_color($params{rgb}); $output .= "stroke:rgb($r,$g,$b); stroke-width:$stroked; "; $output .= "fill:none; " if ($filled == 0); } $output .= "\" "; } else { $output .= "style=\"fill:none; stroke-width:0; \" "; } $output .= "/>\n"; $output .= "\n" if($link && length($link)); return $output; } sub btIP_itemEmbed { my ($id,$x,$y,$width,$height,$arg,%params) = @_; my $embed = "
\n". "$arg\n". "
\n"; return $embed; } sub btIP_itemGroup { my($id,$type,$x,$y,%params) = @_; return "\n" if $type eq 'close'; $id = ($id eq '-') ? createUniqueId() : $id; return "\n" if $type eq 'open'; } sub btIP_itemImg { return unless $useImgTools; my ($id,$x,$y,$scale,$link,$srctype,$arg,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; return unless(defined($arg)); return if($arg eq ""); my ($counter,$data,$info,$width,$height,$mimetype,$output); if($srctype eq 'file') { ($counter,$data) = btIP_FileRead($arg); return unless $counter; } elsif ($srctype eq "url" || $srctype eq "urlq") { if($srctype eq "url") { $data= GetFileFromURL($arg,3,undef,1); } else { $data= GetFileFromURLQuiet($arg,3,undef,1); } } elsif ($srctype eq 'data') { $data = $arg; } else { Log3(undef,4,"InfoPanel: unknown sourcetype $srctype for image tag"); return ""; } ($width,$height,$mimetype,undef) = _btIP_imgData($data,1); if($mimetype eq 'image/svg+xml') { if($data !~ m/viewBox/) { $data =~ s/width=/viewBox="0 0 $width $height"\n\twidth=/; } ($width,$height) = _btIP_imgRescale($width,$height,$scale); $data =~ s/width=".*"/width="$width"/; $data =~ s/height=".*"/height="$height"/; $scale = 1; (undef,undef,undef,$data) = _btIP_imgData($data,$scale); } else { ($width,$height,$mimetype,$data) = _btIP_imgData($data,$scale); } my $target; ($link,$target) = btIP_findTarget($link); $output = "\n"; $output .= "\n" if($link && length($link)); $output .= "\n"; $output .= "\n" if($link && length($link)); return ($output,$width,$height); } sub _btIP_imgData { my ($arg,$scale) = @_; my $info = image_info(\$arg); my $width = $info->{width}; my $height = $info->{height}; $width =~ s/px//; $height =~ s/px//; $width =~ s/pt//; $height =~ s/pt//; ($width,$height)= _btIP_imgRescale($width,$height,$scale); my $mimetype = $info->{file_media_type}; if($FW_userAgent =~ m/Trident/ && $mimetype =~ m/svg/) { $arg =~ s/width=".*"//g; $arg =~ s/height=".*"//g; } my $data = "data:$mimetype;base64,".encode_base64($arg); return ($width,$height,$mimetype,$data); } sub _btIP_imgRescale { my ($width,$height,$scale) = @_; if ($scale =~ s/([whWH])([\d]*)/$2/) { $scale = (uc($1) eq "W") ? $scale/$width : $scale/$height; } $width = int($scale*$width); $height = int($scale*$height); return ($width,$height); } sub btIP_itemLine { my ($id,$x1,$y1,$x2,$y2,$th,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; $th //= 1; my ($r,$g,$b,$a) = btIP_color($params{rgb}); return "\n"; } sub btIP_itemLongpoll { my ($id,$x,$y,$text,%params)= @_; my ($iconName,undef,undef) = FW_dev2image($id); my $iconURL; $iconURL = FW_IconURL($iconName) if defined($iconName); my $color = substr($params{rgb},0,6); my $opacity = hex(substr($params{rgb},6,2))/255; my $output = "
\n"; $output .= "$text\n" if defined($text); $output .= "\n" unless defined($text); $output .= "
\n"; $defs{$params{name}}{fhem}{div} .= $output; return ""; } sub btIP_itemPlot { my ($id,$x,$y,$scale,$inline,$arg) = @_; my (@plotName) = split(";",$arg); return ("\n",undef,undef) unless defined($defs{$plotName[0]}); $id = ($id eq '-') ? createUniqueId() : $id; my (@webs,$width,$height,$newWidth,$newHeight,$output,$mimetype,$svgdata); @webs=devspec2array("TYPE=FHEMWEB"); foreach(@webs) { if(!InternalVal($_,'TEMPORARY',undef)) { $FW_wname=InternalVal($_,'NAME',''); last; } } if(!$useImgTools) { $scale = 1; $inline = 0; } ($width,$height) = split(",", AttrVal($plotName[0],"plotsize","800,160")); ($newWidth,$newHeight) = _btIP_imgRescale($width,$height,$scale); if($inline == 1) { # embed base64 data $FW_RET = undef; $FW_webArgs{dev} = $plotName[0]; $FW_webArgs{logdev} = InternalVal($plotName[0], "LOGDEVICE", ""); $FW_webArgs{gplotfile} = InternalVal($plotName[0], "GPLOTFILE", ""); $FW_webArgs{logfile} = InternalVal($plotName[0], "LOGFILE", "CURRENT"); $FW_pos{zoom} = $plotName[1] if(length($plotName[1])); $FW_pos{off} = $plotName[2] if(length($plotName[2])); $FW_plotsize = "$newWidth,$newHeight"; ($mimetype, $svgdata) = SVG_showLog("unused"); $svgdata =~ s/<\/svg>/<\/svg>/; (undef,undef,undef,$svgdata) = _btIP_imgData($svgdata,1); $output = "\n"; $output .= "\n"; } else { # embed link to plot my $url; $url = "$FW_ME/SVG_showLog?dev=". $plotName[0]. "&logdev=". InternalVal($plotName[0], "LOGDEVICE", ""). "&gplotfile=". InternalVal($plotName[0], "GPLOTFILE", ""). "&logfile=". InternalVal($plotName[0], "LOGFILE", "CURRENT"). "&plotsize=". "$newWidth,$newHeight"; $url .= "&pos="; $url .= "zoom=". "$plotName[1];" if(length($plotName[1])); $url .= "off=". $plotName[2] if(length($plotName[2])); $output = "\n"; $output .= "\n"; } return ($output,$newWidth,$newHeight); } sub btIP_itemRect { my ($id,$x1,$y1,$x2,$y2,$rx,$ry,$filled,$stroked,$link,$target,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; $target //= ""; ($link,$target) = btIP_findTarget($link) unless ($target ne ""); my $width = $x2 - $x1; my $height = $y2 - $y1; $filled //= 0; $stroked //= 0; my $output = ""; $output .= "\n" if($link && length($link)); $output .= " 0 || $stroked > 0) { $output .= "style=\""; if($filled > 0) { my ($r,$g,$b,$a) = btIP_color($params{rgb}); $output .= "fill:rgb($r,$g,$b); fill-opacity:$a; "; } if($stroked > 0) { my ($r,$g,$b,$a) = btIP_color($params{rgb}); $output .= "stroke:rgb($r,$g,$b); stroke-width:$stroked; "; $output .= "fill:none; " if ($filled == 0); } $output .= "\" "; } else { $output .= "style=\"fill:none; stroke-width:0; \" "; } $output .= "/>\n"; $output .= "\n" if($link && length($link)); return $output; } sub btIP_itemSeconds { my ($id,$x,$y,$format,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); if ($format eq "colon") { return btIP_itemText($id,$x,$y,sprintf(":%02d", $sec),%params); } else { return btIP_itemText($id,$x,$y,sprintf("%02d", $sec),%params); } } sub btIP_itemText { my ($id,$x,$y,$text,%params)= @_; return unless(defined($text)); $id = ($id eq '-') ? createUniqueId() : $id; my ($r,$g,$b,$a) = btIP_color($params{rgb}); my $output = "\n". "$text\n". "\n"; return $output; } sub btIP_itemTextBox { my ($id,$x,$y,$boxwidth,$boxheight,$text,$link,%params)= @_; return unless(defined($text)); $id = ($id eq '-') ? createUniqueId() : $id; my $color = substr($params{rgb},0,6); $link =~ s/"//g; my $target; ($link,$target) = btIP_findTarget($link); my ($d,$output); if(defined($params{boxcolor})) { my $orgcolor = $params{rgb}; $params{rgb} = $params{boxcolor}; my $bx1 = $x - $params{padding}; my $by1 = $y - $params{padding}; my $bx2 = $x + $boxwidth + $params{padding}; my $by2 = $y + $boxheight + $params{padding}; $output .= btIP_itemRect("box_$id",$bx1,$by1,$bx2,$by2,1,1,1,0,undef,undef,%params); $params{rgb} = $orgcolor; } else { $output = ""; } $d = "
\n". "\n"; $d .= "\n" if($link && length($link)); $d .= "

\n$text\n

\n"; $d .= "
" if($link && length($link)); $d .= "
\n"; $defs{$params{name}}{fhem}{div} .= $d; return $output; } sub btIP_itemTicker { my ($id,$x,$y,$width,$items,$speed,$arg,%params) = @_; $id = ($id eq '-') ? createUniqueId() : $id; my $pause = 2 * $speed; my $color = substr($params{rgb},0,6); $arg =~ s/\\n/\n/g; # support ReadingsVal() with \n in text my @a = split("\n",$arg); my $liTemplate = '
  • %s
  • '."\n"; my $ticker = "
    \n". "\n". "
    \n
      \n"; foreach (@a) {$ticker .= sprintf($liTemplate,$_)}; $ticker .= "
    \n
    \n
    \n"; return $ticker; } sub btIP_itemTime { my ($id,$x,$y,%params)= @_; $id = ($id eq '-') ? createUniqueId() : $id; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return btIP_itemText($id,$x,$y,sprintf("%02d:%02d", $hour, $min),%params); } ##### Helper sub btIP_changeColor { my($file,$oldcolor,$newcolor) = @_; Log3(undef,4,"InfoPanel: read file $file for changeColor"); my ($counter,$data) = btIP_FileRead($file); return unless $counter; if($newcolor =~ /[[:xdigit:]]{6}/) { Log3(undef,4,"InfoPanel: changing color from $oldcolor to $newcolor"); $data =~ s/fill="#$oldcolor"/fill="#$newcolor"/g; $data =~ s/fill:#$oldcolor/fill:#$newcolor/g; } else { Log3(undef,4,"InfoPanel: invalid rgb value for changeColor!"); } return $data; } sub btIP_color { my ($rgb)= @_; my $alpha = 1; my @d= split("", $rgb); if(length($rgb) == 8) { $alpha = hex("$d[6]$d[7]"); $alpha = $alpha/255; } return (hex("$d[0]$d[1]"),hex("$d[2]$d[3]"),hex("$d[4]$d[5]"),$alpha); } sub btIP_FileRead { my ($file) = @_; my ($data,$counter); Log3(undef,4,"InfoPanel: looking for img $file"); if(configDBUsed()){ Log3(undef,4,"InfoPanel: reading from configDB"); ($data,$counter) = _cfgDB_Fileexport($file,1); Log3(undef,4,"InfoPanel: file not found in database") unless $counter; } if(!$counter) { Log3(undef,4,"InfoPanel: reading from filesystem"); my $length = -s "$file"; open(my $GRAFIK, "<", $file) or die("File not found $!"); binmode($GRAFIK); $counter = read($GRAFIK, $data, $length); close($GRAFIK); Log3(undef,4,"InfoPanel: file not found in filesystem") unless $counter; } return "" unless $counter; Log3(undef,4,"InfoPanel: file found."); return ($counter,$data); } sub btIP_findTarget { my ($link) = shift; return unless length($link); my $target = 'secret'; $target = '_top' if $link =~ s/^-//; $target = '_blank' if $link =~ s/^\+//; $link = $FW_httpheader{Referer} if $link eq 'BACK'; return ($link,$target); } sub btIP_xy { my ($x,$y,%params)= @_; $x = $params{width} if ($x eq 'max'); $y = $params{height} if ($y eq 'max'); $x = $params{xx} if ($x eq 'x'); $y = $params{yy} if ($y eq 'y'); if((-1 < $x) && ($x < 1)) { $x *= $params{width}; } if((-1 < $y) && ($y < 1)) { $y *= $params{height}; } return($x,$y); } ################## # # create SVG content # sub btIP_returnSVG { my ($name)= @_; # # increase counter # if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{counter})) { $defs{$name}{fhem}{counter}++; } else { $defs{$name}{fhem}{counter}= 1; } my ($width,$height)= split(/x/, AttrVal($name,"size","800x600")); my $bgcolor = AnalyzePerlCommand(undef,AttrVal($name,'bgcolor','"000000"')); $bgcolor = substr($bgcolor,0,6); my $output = ""; our $svg = ""; eval { $svg = "\n= $tmin) { $defs{$name}{fhem}{t}= $t1; $bgnr++; } if(opendir(BGDIR, $bgdir)){ my @bgfiles= grep {$_ !~ /^\./} readdir(BGDIR); closedir(BGDIR); if($#bgfiles>=0) { if($bgnr > $#bgfiles) { $bgnr= 0; } $defs{$name}{fhem}{bgnr}= $bgnr; my $bgfile = $bgdir . "/" . $bgfiles[$bgnr]; my $info = image_info($bgfile); my $bgwidth = $info->{width}; my $bgheight = $info->{height}; my ($u,$v) = ($bgwidth/$width, $bgheight/$height); my ($w,$h); if($u>$v) { $w= $width; $h= $bgheight/$u; } else { $h= $height; $w= $bgwidth/$v; } my $scale = ($u>$v) ? 1/$u : 1/$v; my ($bgx,$bgy) = (0,0); $bgx = ($width - $w)/2 if AttrVal($name,'bgcenter',1); $bgy = ($height - $h)/2 if AttrVal($name,'bgcenter',1); ($output,undef,undef) = btIP_itemImg('bgImage',$bgx,$bgy,$scale,undef,'file',$bgfile,undef); my $opacity = AttrVal($name,'bgopacity',1); $output =~ s/\n"; $svg .= "$output\n"; $svg = btIP_evalLayout($svg, $name, $defs{$name}{fhem}{layout}); readingsSingleUpdate($defs{$name},'state',localtime(),1) if(AttrVal($name,'showTime',1)); }; #warn $@ if $@; if($@) { my $msg= $@; chomp $msg; Log3($name, 2, $msg); } $svg .= "\nSorry, your browser does not support inline SVG.\n\n"; return $svg; } sub btIP_evalLayout { my ($svg,$name,$layout)= @_; my ($width,$height)= split(/x/, AttrVal($name,"size","800x600")); my @layout= split("\n", $layout); my $pstackcount = 0; my %pstack; my %params; $params{name} = $name; $params{counter} = $defs{$name}{fhem}{counter}; $params{xx} = 0; $params{yy} = 0; $params{groupx} = 0; $params{groupy} = 0; $params{width} = $width; $params{height} = $height; $params{rgb} = 'FFFFFF'; $params{condition} = 1; $params{boxcolor} = undef; $params{tbalign} = 'left'; $params{padding} = 0; $params{font} = 'Arial'; $params{pt} = 12; $params{fontstyle} = 'initial'; $params{fontweight} = 'normal'; $params{textdecoration} = 'none'; $params{thalign} = 'start'; $params{tvalign} = 'auto'; $defs{$name}{fhem}{div} = undef; my ($id,$x,$y,$x1,$y1,$x2,$y2,$radius,$rx,$ry); my ($scale,$inline,$boxwidth,$boxheight,$boxcolor); my ($speed,$bgcolor,$fgcolor); my ($text,$link,$target,$imgtype,$srctype,$arg,$format,$filled,$stroked); my $cont= ""; foreach my $line (@layout) { # kill trailing newline chomp $line; # kill comments and blank lines $line=~ s/\#.*$//; $line=~ s/\@.*$//; $line=~ s/\s+$//; $line= $cont . $line; if($line=~ s/\\$//) { $cont= $line; undef $line; } next unless($line); $cont= ""; # Debug "$name: evaluating >$line<"; # split line into command and definition my ($cmd, $def)= split("[ \t]+", $line, 2); # Debug "CMD= \"$cmd\", DEF= \"$def\""; # separate condition handling if($cmd =~ m/condition/) { if($cmd =~ m/^xcond/) { ($id,$arg) = split("[ \t]+", $def, 2); $params{condition} = AnalyzePerlCommand(undef,$arg); my $override = $defs{$name}{fhem}{override}{$id}; $override //= $params{condition}; if($params{condition}) { $params{condition} = AnalyzePerlCommand(undef,$arg) && $override; } else { $params{condition} = AnalyzePerlCommand(undef,$arg) || $override; } } else { $params{condition} = AnalyzePerlCommand(undef,$def); } next; } next unless($params{condition}); # Debug "before command $line: x= " . $params{xx} . ", y= " . $params{yy}; eval { given($cmd) { when("area") { ($id,$x1,$y1,$x2,$y2,$link,$target)= split("[ \t]+", $def, 7); $target //= ""; ($x1,$y1)= btIP_xy($x1,$y1,%params); ($x2,$y2)= btIP_xy($x2,$y2,%params); $link = AnalyzePerlCommand(undef,$link); $params{xx} = $x1; $params{yy} = $y2; $svg .= btIP_itemArea($id,$x1,$y1,$x2,$y2,$link,$target,%params); } when("boxcolor"){ $def = "\"$def\"" if(length($def) == 6 && $def =~ /[[:xdigit:]]{6}/); $params{boxcolor} = AnalyzePerlCommand(undef, $def); } when("button") { ($id,$x1,$y1,$x2,$y2,$rx,$ry,$link,$text)= split("[ \t]+", $def, 9); ($x1,$y1)= btIP_xy($x1,$y1,%params); ($x2,$y2)= btIP_xy($x2,$y2,%params); ($rx,$ry)= btIP_xy($rx,$ry,%params); $params{xx} = $x1; $params{yy} = $y2; $link = AnalyzePerlCommand(undef,$link); $link = (length($link)) ? $link : "-$params{name}.html"; $text = AnalyzePerlCommand(undef,$text); $svg .= btIP_itemButton($id,$x1,$y1,$x2,$y2,$rx,$ry,$link,$text,%params); } when("buttonpanel"){ $defs{$params{name}}{fhem}{div} .= "
    ". "
    \n"; } when("circle") { ($id,$x1,$y1,$radius,$filled,$stroked,$link)= split("[ \t]+", $def, 7); ($x1,$y1)= btIP_xy($x1,$y1,%params); $params{xx} = $x1; $params{yy} = $y1+$radius; $filled //= 0; $stroked //= 0; $link //= ""; $link = AnalyzePerlCommand(undef,$link); $svg .= btIP_itemCircle($id,$x1,$y1,$radius,$filled,$stroked,$link,%params); } when("counter") { ($id,$x,$y)= split("[ \t]+", $def, 3); ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $svg .= btIP_itemCounter($id,$x,$y,%params); } when("date") { ($id,$x,$y)= split("[ \t]+", $def, 3); ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $svg .= btIP_itemDate($id,$x,$y,%params); } when("ellipse") { ($id,$x1,$y1,$rx,$ry,$filled,$stroked,$link)= split("[ \t]+", $def, 8); ($x1,$y1) = btIP_xy($x1,$y1,%params); ($rx,$ry) = btIP_xy($rx,$ry,%params); $params{xx} = $x1; $params{yy} = $y1+$ry; $filled //= 0; $stroked //= 0; $link //= ""; $link = AnalyzePerlCommand(undef,$link); $svg .= btIP_itemEllipse($id,$x1,$y1,$rx,$ry,$filled,$stroked,$link,%params); } when("embed") { ($id,$x,$y,$width,$height,$arg)= split("[ \t]+", $def, 6); ($x,$y)= btIP_xy($x,$y,%params); ($width,$height)= btIP_xy($width,$height,%params); $params{xx} = $x; $params{yy} = $y; $arg = AnalyzePerlCommand(undef,$arg); $defs{$name}{fhem}{div} .= btIP_itemEmbed($id,$x,$y,$width,$height,$arg,%params); } when("font") { $params{font} = $def; } when("group") { ($id,$text,$x,$y) = split("[ \t]+", $def, 4); $x //= $params{xx}; $y //= $params{yy}; ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; if($text eq 'open') { $params{groupx} = $x; $params{groupy} = $y; } else { $params{groupx} = 0; $params{groupy} = 0; } $svg .= btIP_itemGroup($id,$text,$x,$y,%params); } when("img") { ($id,$x,$y,$scale,$link,$srctype,$arg) = split("[ \t]+", $def,7); ($x,$y) = btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $arg = AnalyzePerlCommand(undef,$arg); $link = AnalyzePerlCommand(undef,$link); my($output,$width,$height)= btIP_itemImg($id,$x,$y,$scale,$link,$srctype,$arg,%params); $svg .= $output; $params{xx} = $x; $params{yy} = $y+$height; } when("line") { ($id,$x1,$y1,$x2,$y2,$format) = split("[ \t]+", $def, 6); ($x1,$y1) = btIP_xy($x1,$y1,%params); ($x2,$y2) = btIP_xy($x2,$y2,%params); $format //= 1; $svg .= btIP_itemLine($id,$x1,$y1,$x2,$y2,$format,%params); } when("longpoll") { ($id,$x,$y,$text)= split("[ \t]+", $def, 4); $text //= undef; $text = AnalyzePerlCommand(undef,$text) if defined($text); ($x,$y)= btIP_xy($x,$y,%params); $x += $params{groupx}; $y += $params{groupy}; $params{xx} = $x; $params{yy} = $y; $svg .= btIP_itemLongpoll($id,$x,$y,$text,%params); } when("movecalculated") { my ($tox,$toy)= split('[ \t]+', $def, 2); $params{xx} = AnalyzePerlCommand(undef,$tox); $params{yy} = AnalyzePerlCommand(undef,$toy); } when("moveby") { my ($byx,$byy) = split('[ \t]+', $def, 2); my ($x,$y)= btIP_xy($byx,$byy,%params); $params{xx} += $x; $params{yy} += $y; } when("moveto") { my ($tox,$toy)= split('[ \t]+', $def, 2); my ($x,$y)= btIP_xy($tox,$toy,%params); $params{xx} = $x; $params{yy} = $y; } when("padding") { $params{padding}= AnalyzePerlCommand(undef,$def); } when("plain") { $svg .= AnalyzePerlCommand(undef,$def); } when("plot") { ($id,$x,$y,$scale,$inline,$arg)= split("[ \t]+", $def,6); ($x,$y)= btIP_xy($x,$y,%params); $arg = AnalyzePerlCommand(undef, $arg); my($output,$width,$height)= btIP_itemPlot($id,$x,$y,$scale,$inline,$arg,%params); $svg .= $output; $params{xx} = $x; $params{yy} = $y+$height; } when("pop") { return unless $pstackcount; foreach my $key ( keys %{$pstack{$pstackcount}} ) { # Debug "pop key: $key, value: $pstack{$pstackcount}{$key}"; $params{$key} = $pstack{$pstackcount}{$key}; } delete $pstack{$pstackcount}; $pstackcount--; } when("pt") { $def = AnalyzePerlCommand(undef, $def); if($def =~ m/^[+-]/) { $params{pt} += $def; } else { $params{pt} = $def; } $params{pt} = 6 if($params{pt} < 0); } when("push") { $pstackcount++; foreach my $key ( keys %params ) { # Debug "push key: $key, value: $params{$key}"; $pstack{$pstackcount}{$key} = $params{$key}; } } when("rect") { ($id,$x1,$y1,$x2,$y2,$rx,$ry,$filled,$stroked,$link)= split("[ \t]+", $def, 10); ($x1,$y1)= btIP_xy($x1,$y1,%params); ($x2,$y2)= btIP_xy($x2,$y2,%params); ($rx,$ry) = btIP_xy($rx,$ry,%params); $params{xx} = $x1; $params{yy} = $y2; $filled //= 0; # set 0 as default (not filled) $stroked //= 0; # set 0 as default (not stroked) $link //= ""; $link = AnalyzePerlCommand(undef,$link); $svg .= btIP_itemRect($id,$x1,$y1,$x2,$y2,$rx,$ry,$filled,$stroked,$link,undef,%params); } when("rgb"){ $def = "\"$def\"" if(length($def) == 6 && $def =~ /[[:xdigit:]]{6}/); $params{rgb} = AnalyzePerlCommand(undef, $def); } when("seconds") { ($id,$x,$y,$format) = split("[ \+]", $def,4); ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $svg .= btIP_itemSeconds($id,$x,$y,$format,%params); } when("text") { ($id,$x,$y,$text)= split("[ \t]+", $def, 4); ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $text= AnalyzePerlCommand(undef, $text); $svg .= btIP_itemText($id,$x,$y,$text,%params); } when("lptext") { $svg .= "\n\n\n"; Log3($name, 2, "InfoPanel $name: command 'lptext' no longer supported."); } when("textbox") { ($id,$x,$y,$boxwidth,$boxheight,$link,$text)= split("[ \t]+", $def, 7); ($x,$y)= btIP_xy($x,$y,%params); $text = AnalyzePerlCommand(undef, $text); $text =~ s/\n//g; $link = AnalyzePerlCommand(undef, $link); $svg .= btIP_itemTextBox($id,$x,$y,$boxwidth,$boxheight,$text,$link,%params); $params{xx} = $x; $params{yy} = $y + $boxheight; } when("textboxalign") { $params{tbalign} = $def; } when("textdesign") { my @args = split(/,/,$def); my @deco = qw(underline overline line-through); #text-decoration my @style = qw(italic oblique); #font-style my @weight = qw(bold); #font-weight $params{fontstyle} = "initial"; $params{fontweight} = "initial"; $params{textdecoration} = "none"; foreach my $s (@args) { if($s ne 'clear') { $params{fontstyle} = "$s " if($s ~~ @style); $params{fontweight} = "$s " if($s ~~ @weight); $params{textdecoration} = "$s " if($s ~~ @deco); } } } when("ticker") { ($id,$x,$y,$width,$format,$speed,$arg)= split("[ \t]+", $def, 7); ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $arg = AnalyzePerlCommand(undef,$arg); $defs{$name}{fhem}{div} .= btIP_itemTicker($id,$x,$y,$width,$format,$speed,$arg,%params); } when("time") { ($id,$x,$y)= split("[ \t]+", $def, 3); ($x,$y)= btIP_xy($x,$y,%params); $params{xx} = $x; $params{yy} = $y; $svg .= btIP_itemTime($id,$x,$y,%params); } when("trash") { $svg .= "\n\n\n"; Log3($name, 2, "InfoPanel $name: command 'trash' no longer supported."); } when("thalign"){ my $d = AnalyzePerlCommand(undef, $def); if($d ~~ @valid_halign) { $params{thalign}= $d; } else { Log3($name, 2, "InfoPanel $name: Illegal horizontal alignment $d"); } } when("tvalign"){ my $d = AnalyzePerlCommand(undef, $def); if($d ~~ @valid_valign) { $params{tvalign}= $d; } else { Log3($name, 2, "InfoPanel $name: Illegal vertical alignment $d"); } } default { Log3($name, 2, "InfoPanel $name: Illegal command $cmd in layout definition."); } # default } # given } # eval #Debug "after command $line: x= " . $params{xx} . ", y= " . $params{yy}; } # foreach return $svg; } ################## # # here we answer any request to http://host:port/fhem/btip # sub btIP_addExtension { my ($func,$link,$friendlyname)= @_; my $url = "/" . $link; $data{FWEXT}{$url}{FUNC} = $func; $data{FWEXT}{$url}{LINK} = "+$link"; $data{FWEXT}{$url}{NAME} = $friendlyname; $data{FWEXT}{$url}{FORKABLE} = 0; $data{FWEXT}{jquery}{SCRIPT} = "/pgm2/jquery.min.js" unless $data{FWEXT}{jquery}{SCRIPT}; $data{FWEXT}{jqueryvticker}{SCRIPT} = "/pgm2/jquery.vticker.min.js" unless $data{FWEXT}{jqueryvticker}{SCRIPT}; } sub btIP_CGI{ my ($request) = @_; my ($name,$ext)= btIP_splitRequest($request); if(defined($name)) { if($ext eq "") { return("text/plain; charset=utf-8", "Illegal extension."); } if(!defined($defs{$name})) { return("text/plain; charset=utf-8", "Unknown InfoPanel device: $name"); } if($ext eq "png") { return btIP_returnPNG($name); } if($ext eq "info" || $ext eq "html") { return btIP_returnHTML($name); } } else { return btIP_Overview(); } } sub btIP_splitRequest { my ($request) = @_; if($request =~ /^.*\/btip$/) { # http://localhost:8083/fhem/btip return (undef,undef); # name, ext } else { my $call= $request; $call =~ s/^.*\/btip\/([^\/]*)$/$1/; my $name= $call; $name =~ s/^(.*)\.(png|svg|info|html)$/$1/; my $ext= $call; $ext =~ s/^$name\.(.*)$/$1/; return ($name,$ext); } } #################### # # HTML Stuff # sub btIP_returnHTML { my ($name) = @_; my $refresh = AttrVal($name, 'refresh', 60); $refresh = ($refresh && $refresh < 59) ? 60 : $refresh; my $title = AttrVal($name, 'title', $name); my $viewport= ""; $viewport = AttrVal($name,"useViewPort",1) ? $viewport : ""; my $webApp = ""; $webApp = "" if (AttrVal($name,'mobileApp','none') eq 'apple'); $webApp = "" if (AttrVal($name,'mobileApp','none') eq 'other'); my $gen = 'generated="'.(time()-1).'"'; my $code = btIP_HTMLHead($name,$title,$viewport,$webApp,$refresh); my $csrf= ($FW_CSRF ? "fwcsrf='$defs{$FW_wname}{CSRFTOKEN}'" : ""); $code .= "\n". "
    \n". btIP_returnSVG($name)."\n
    \n"; $code .= $defs{$name}{fhem}{div} if($defs{$name}{fhem}{div}); $code .= "\n".btIP_HTMLTail(); return ("text/html; charset=utf-8", $code); } sub btIP_HTMLHead { my ($name,$title,$viewport,$webApp,$refresh) = @_; my $doctype = ' '."\n". ''."\n"; my $xmlns = ""; my $r = (defined($refresh) && $refresh) ? "" : ""; my $scripts = btIP_getScript($name); my $meta = ""."\n"; $meta .= ""; my $code = "$doctype\n\n\n$title\n$meta\n$r\n$viewport\n$webApp\n$scripts\n"; return $code; } sub btIP_getScript { my ($name) = shift; return "" if AttrVal($name,'noscript',0); my $scripts= ""; my $jsTemplate = ''; if(defined($data{FWEXT})) { foreach my $k (sort keys %{$data{FWEXT}}) { my $h = $data{FWEXT}{$k}; next if($h !~ m/HASH/ || !$h->{SCRIPT}); my $script = $h->{SCRIPT}; $script = ($script =~ m,^/,) ? "$FW_ME$script" : "$FW_ME/pgm2/$script" unless ($script =~ m,^http,); $scripts .= sprintf($jsTemplate, $script); } } # $scripts .= sprintf($jsTemplate,"/fhem/pgm2/cordova-2.3.0.js"); # $scripts .= sprintf($jsTemplate,"/fhem/pgm2/webviewcontrol.js"); $scripts .= sprintf($jsTemplate,"/fhem/pgm2/fhemweb.js"); $scripts =~ s/script>/script>\n/g; return $scripts; } sub btIP_HTMLTail { return ""; } sub btIP_Overview { my ($name, $url); my $html= btIP_HTMLHead(undef, "InfoPanel Overview", undef, undef) . "\n"; foreach my $def (sort keys %defs) { if($defs{$def}{TYPE} eq "InfoPanel") { $name= $defs{$def}{NAME}; $url= btIP_getURL(); $html.= "$name
    \n
      "; $html.= "HTML
      \n"; $html.= "
    \n

    \n"; } } $html.="\n" . btIP_HTMLTail(); return ("text/html; charset=utf-8", $html); } sub btIP_getURL { my $proto = (AttrVal($FW_wname, 'HTTPS', 0) == 1) ? 'https' : 'http'; return $proto."://$FW_httpheader{Host}$FW_ME"; } 1; # =pod =item helper =item summary create a simple status display =item summary_DE erzeugt ein einfaches Statusdisplay =begin html

    InfoPanel

      InfoPanel is an extension to FHEMWEB. You must install FHEMWEB to use InfoPanel.


      Prerequesits

      • InfoPanel is an extension to FHEMWEB. You must install FHEMWEB to use InfoPanel.

      • Module uses following additional Perl modules:

          MIME::Base64 Image::Info

        If not already installed in your environment, please install them using appropriate commands from your environment.

        Package installation in debian environments: apt-get install libmime-base64-perl libimage-info-perl

      • You can use this module without the two additional perl modules, but in this case, you have to accept some limitations:

        • layout tag img can not be used
        • layout tag plot can only handle scale = 1 and inline = 0


      Define

        define <name> InfoPanel <layoutFileName>

        Example:

          define myInfoPanel InfoPanel ./FHEM/panel.layout


      Set-Commands

      • set <name> reread

          Rereads the layout definition from the file.

          Important:
            Layout will be reread automatically if edited via fhem's "Edit files" function.
            Autoread can be disabled via attribute.

      • set <name> ovEnable <xconditionName>

          set an override "1" to named xcondition

      • set <name> ovDisable <xconditionName>

          set an override "0" to named xcondition

      • set <name> ovClear <xconditionName>|all;

          delete an existing overrides to named xcondition. "all" will clear all overrides.


      Get-Commands

      • get <name> counter

          return value from internal counter

      • get <name> layout

          return complete layout definition

      • get <name> overrides

          return list of defined overrides




      Attributes

      • autoreread - disables automatic layout reread after edit if set to 1
      • refresh - time (in seconds) after which the HTML page will be reloaded automatically.
        Any values below 60 seconds will not become valid.
      • showTime - disables generation timestamp in state if set to 0
      • size - The dimensions of the picture in the format <width>x<height>
      • useViewPort - add viewport meta tag to fit mobile displays
      • mobileApp - add support for mobile fullscreen experience
      • title - webpage title to be shown in Browser

      • bgcenter - background images will not be centered if attribute set to 0. Default: show centered
      • bgcolor - defines the background color, use html-hexcodes to specify color, eg 00FF00 for green background. Default color is black. You can use bgcolor=none to disable use of any background color
      • bgdir - directory containing background images
      • bgopacity - set opacity for background image, values 0...1.0
      • tmin - background picture will be shown at least tmin seconds, no matter how frequently the RSS feed consumer accesses the page.

      • Important: bgcolor and bgdir will be evaluated by { <perl special> } use quotes for absolute values!


      Generated Readings/Events:

      • state - show time and date of last layout evaluation


      Layout definition

        All parameters in curly brackets can be evaluated by { <perl special> }

      • area <id> <x1> <y1> <x2> <y2> <{link}>

          create a responsive area which will call a link when clicked.

          id = element id
          x1,y1 = upper left corner
          x2,y2 = lower right corner
          link = url to be called


      • boxcolor <{rgba}>

          define an rgb color code to be used for filling button and textbox


      • button <id> <x1> <y1> <x2> <y2> <r1> <r2> <link> <text>

          create a responsive colored button which will call a link when clicked.

          id = element id
          x1,y1 = upper left corner
          x2,y2 = lower right corner
          r1,r2 = radius for rounded corners
          link = url to be called
          text = text that will be written onto the button

          button will be filled with color defined by "boxcolor"
          text color will be read from "rgb" value


      • buttonpanel

          needed once in your layout file if you intend to use buttons in the same layout.


      • circle <id> <x> <y> <r> [<fill>] [<stroke-width>] [<link>]

          create a circle

          id = element id
          x,y = center coordinates of circle
          r = radius
          fill = circle will be filled with "rgb" color if set to 1. Default = 0
          stroke-width = defines stroke width to draw around the circle. Default = 0
          link = URL to be linked to item


      • counter <id> <x> <y>

          print internal counter

          id = element id
          x,y = position


      • date <id> <x> <y>

          print date

          id = element id
          x,y = position


      • embed <id> <x> <y> <width> <height> <{object}>

          embed any object

          id = element id
          x,y = position
          width,height = containers's dimension
          object = object to embed


      • ellipse <id> <x> <y> <r1> <r2> [<fill>] [<stroke-width>] [<link>]

          create an ellipse

          id = element id
          x,y = center coordinates of ellipse
          r1,r2 = radius
          fill = ellipse will be filled with "rgb" color if set to 1. Default = 0
          stroke-width = defines stroke width to draw around the ellipse. Default = 0
          link = URL to be linked to item


      • font <font-family>

          define font family used for text elements (text, date, time, seconds ...)

          Example: font arial


      • group <id> open <x> <y>
        group - close
          (id will not be evaluated, just give any value)

          group items

          open|close = define start and end of group
          x,y = upper left corner as reference for all grouped items, will be inherited to all elements.

          Examples:
          group - open 150 150
          rect ...
          img ...
          group - close


      • img <id> <x> <y> <scale> <link> <sourceType> <{dataSource}>s

          embed an image into InfoPanel

          id = element id
          x,y = upper left corner
          scale = scale to be used for resizing; may be factor or defined by width or height
          link = URL to be linked to item, use "" if not needed
          sourceType = file | url | data
          dataSource = where to read data from, depends on sourceType


      • line <id> <x1> <y1> <x2> <y2> [<stroke>]

          draw a line

          id = element id
          x1,y1 = coordinates (start)
          x2,y2 = coordinates (end)
          stroke = stroke width for line; if omitted, default = 0


      • moveby <x> <y>

          move most recently x- and y-coordinates by given steps


      • movecalculated <{perlSpecial x}> <{perlSpecial y}>

          calculate x- and y-coordinates by perlSpecials


      • moveto <x> <y>

          move x- and y-coordinates to the given positon


      • padding <width>

          border width (in pixel) to be used in textboxes


      • plot <id> <x> <y> <scale> <inline> <{plotName}>

          embed an SVG plot into InfoPanel

          id = element id
          x,y = upper left corner
          scale = scale to be used for resizing; may be factor or defined by width or height
          inline = embed plot as data (inline=1) or as link (inline=0)
          plotName = name of desired SVG device from your fhem installation


      • pop

          fetch last parameter set from stack and set it actice


      • pt <[+-]font-size>

          define font size used for text elements (text, date, time, seconds ...)
          can be given as absolute or relative value.

          Examples:
          pt 12
          pt +3
          pt -2


      • push

          push active parameter set onto stack


      • rect <id> <x1> <y1> <x2> <y2> <r1> <r2> [<fill>] [<stroke-width>] [<link>]

          create a rectangle

          id = element id
          x1,y1 = upper left corner
          x2,y2 = lower right corner
          r1,r2 = radius for rounded corners
          fill = rectangle will be filled with "rgb" color if set to 1. Default = 0
          stroke-width = defines stroke width to draw around the rectangle. Default = 0
          link = URL to be linked to item


      • rgb <{rgb[a]}>

          define rgba value (hex digits!) used for text, lines, circles, ellipses

          r = red value
          g = green value
          b = blue value
          a = alpha value used for opacity; optional


      • seconds <id> <x> <y> [<format>]

          print seconds

          id = element id
          x,y = position
          format = seconds will be preceeded by ':' if set to 'colon'; optional


      • text <id> <x> <y> <{text}>

          print text

          id = element id
          x,y = position
          text = text content to be printed


      • textbox <id> <x> <y> <boxWidth> <boxHeight> <{link}> <{text}>

          create a textbox to print text with auto wrapping

          id = element id
          x,y = upper left corner
          boxWidth,boxHeight = dimensions of textbox
          link = url to be used when clicked; use "" if not needed
          text = text to be printed in textbox

          Important: textboxes are not responsive via area tag. Use optional link parameter in textbox tag


      • textboxalign <align>

          define horizontal alignment for text inside textboxes

          valid values: left center right justify


      • textdesign <align>

          define comma-separated list for text design and decoration

          valid values: underline overline line-through bold italic oblique clear

          Examples:
          textdesign underline
          textdesign bold,italic,underline


          Important: "clear" resets all to default values!


      • thalign <align>

          define horizontal alignment for text output

          valid values: start middle end


      • ticker <id> <x> <y> <width> <items> <speed> <{data}>

          create a vertical ticker

          id = element id
          x,y = position
          width = width
          items = number of items to be displayed simultanously
          speed = scroll speed
          data = list of text items, separated by \n


      • time <id> <x> <y>

          print time

          id = element id
          x,y = position


      • tvalign <align>

          define vertical alignment for text output

          valid values: auto baseline middle center hanging



      Author's notes

      • Have fun!

    =end html =cut