From d7464cf370d318253407037d6dc11e39a03cde52 Mon Sep 17 00:00:00 2001 From: neubert Date: Sun, 22 Apr 2018 15:29:55 +0000 Subject: [PATCH] 02_RSS: label and goto in layout definition (forum #86844) git-svn-id: https://svn.fhem.de/fhem/trunk@16645 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/02_RSS.pm | 1822 ++++++++++++++++++++++++------------------- 2 files changed, 1018 insertions(+), 805 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index b0e21ca9e..92b73b684 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 02_RSS: label and goto in layout definition (forum #86844) - change: 59_Weather: reverted changes in HTML routine (forum #87187) - feature: 59_Weather: italian translations and other (forum #87080) - feature: 49_SSCam: V3.9.1, PTZ control panel added, please consider diff --git a/fhem/FHEM/02_RSS.pm b/fhem/FHEM/02_RSS.pm index 25c016249..02fcef7e8 100644 --- a/fhem/FHEM/02_RSS.pm +++ b/fhem/FHEM/02_RSS.pm @@ -14,18 +14,19 @@ use GD; use feature qw/switch/; use vars qw(%data); use HttpUtils; -#require "98_SVG.pm"; # enable use of plotAsPng() -sub plotAsPng(@); # forward declaration will be enough - # to ensure correct function - # and will avoid reloading 98_SVG.pm - # during fhem startup/rereadcfg -my @cmd_halign= qw(halign thalign ihalign); -my @cmd_valign= qw(valign tvalign ivalign); +#require "98_SVG.pm"; # enable use of plotAsPng() +sub plotAsPng(@); # forward declaration will be enough + # to ensure correct function + # and will avoid reloading 98_SVG.pm + # during fhem startup/rereadcfg + +my @cmd_halign = qw(halign thalign ihalign); +my @cmd_valign = qw(valign tvalign ivalign); my @valid_valign = qw(top center base bottom); my @valid_halign = qw(left center right justified); -# we can +# we can # use vars qw(%FW_types); # device types, # use vars qw($FW_RET); # Returned data (html) # use vars qw($FW_wname); # Web instance @@ -38,142 +39,144 @@ my @valid_halign = qw(left center right justified); no if $] >= 5.017011, warnings => 'experimental'; ######################### -sub -RSS_addExtension($$$) { - my ($func,$link,$friendlyname)= @_; - +sub RSS_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}{SCRIPT} = "RSS.js"; + $data{FWEXT}{$url}{FUNC} = $func; + $data{FWEXT}{$url}{LINK} = "+$link"; + $data{FWEXT}{$url}{NAME} = $friendlyname; + $data{FWEXT}{$url}{SCRIPT} = "RSS.js"; $data{FWEXT}{$url}{FORKABLE} = 0; } ################## -sub -RSS_Initialize($) { +sub RSS_Initialize($) { my ($hash) = @_; - $hash->{DefFn} = "RSS_Define"; - $hash->{UndefFn} = "RSS_Undefine"; + $hash->{DefFn} = "RSS_Define"; + $hash->{UndefFn} = "RSS_Undefine"; + #$hash->{AttrFn} = "RSS_Attr"; - $hash->{AttrList} = "size itemtitle bg bgcolor tmin refresh areas autoreread:1,0 viewport noscroll urlOverride"; + $hash->{AttrList} = +"size itemtitle bg bgcolor tmin refresh areas autoreread:1,0 viewport noscroll urlOverride"; $hash->{SetFn} = "RSS_Set"; $hash->{NotifyFn} = "RSS_Notify"; return undef; - } - -################## -sub -RSS_readLayout($) { - - my ($hash)= @_; - my $filename= $hash->{fhem}{filename}; - my $name= $hash->{NAME}; - - my ($err, @layoutfile) = FileRead($filename); - if($err) { - Log 1, "RSS $name: $err"; - $hash->{fhem}{layout}= ("text 0.1 0.1 'Error: $err'"); - } else { - $hash->{fhem}{layout}= join("\n", @layoutfile); - $hash->{fhem}{layout} =~ s/\n\n/\n/g; - } - return; } - + ################## -sub -RSS_Define($$) { +sub RSS_readLayout($) { - my ($hash, $def) = @_; + my ($hash) = @_; + my $filename = $hash->{fhem}{filename}; + my $name = $hash->{NAME}; - my @a = split("[ \t]+", $def); + my ( $err, @layoutfile ) = FileRead($filename); + if ($err) { + Log 1, "RSS $name: $err"; + $hash->{fhem}{layout} = ("text 0.1 0.1 'Error: $err'"); + } + else { + $hash->{fhem}{layout} = join( "\n", @layoutfile ); + $hash->{fhem}{layout} =~ s/\n\n/\n/g; + } + return; +} - return "Usage: define RSS jpg|png hostname filename" if(int(@a) != 5); - my $name= $a[0]; - my $style= $a[2]; - my $hostname= $a[3]; - my $filename= $a[4]; +################## +sub RSS_Define($$) { - $hash->{fhem}{style}= $style; - $hash->{fhem}{hostname}= $hostname; - $hash->{fhem}{filename}= $filename; - $hash->{LAYOUTFILE} = $filename; - $hash->{NOTIFYDEV} = 'global'; + my ( $hash, $def ) = @_; - RSS_addExtension("RSS_CGI","rss","RSS"); + my @a = split( "[ \t]+", $def ); - eval "use GD::Text::Align"; - $hash->{fhem}{useTextAlign} = ($@ ? 0 : 1 ); - if(!($hash->{fhem}{useTextAlign})) { - Log3 $hash, 2, "$name: Cannot use text alignment: $@"; - } - - eval "use GD::Text::Wrap"; - $hash->{fhem}{useTextWrap} = ($@ ? 0 : 1 ); - if(!($hash->{fhem}{useTextWrap})) { - Log3 $hash, 2, "$name: Cannot use text wrapping: $@"; - } - - RSS_readLayout($hash); - - $hash->{STATE} = 'defined'; #$name; - return undef; + return "Usage: define RSS jpg|png hostname filename" + if ( int(@a) != 5 ); + my $name = $a[0]; + my $style = $a[2]; + my $hostname = $a[3]; + my $filename = $a[4]; + + $hash->{fhem}{style} = $style; + $hash->{fhem}{hostname} = $hostname; + $hash->{fhem}{filename} = $filename; + $hash->{LAYOUTFILE} = $filename; + $hash->{NOTIFYDEV} = 'global'; + + RSS_addExtension( "RSS_CGI", "rss", "RSS" ); + + eval "use GD::Text::Align"; + $hash->{fhem}{useTextAlign} = ( $@ ? 0 : 1 ); + if ( !( $hash->{fhem}{useTextAlign} ) ) { + Log3 $hash, 2, "$name: Cannot use text alignment: $@"; + } + + eval "use GD::Text::Wrap"; + $hash->{fhem}{useTextWrap} = ( $@ ? 0 : 1 ); + if ( !( $hash->{fhem}{useTextWrap} ) ) { + Log3 $hash, 2, "$name: Cannot use text wrapping: $@"; + } + + RSS_readLayout($hash); + + $hash->{STATE} = 'defined'; #$name; + return undef; } sub RSS_Undefine($$) { - my ($hash, $arg) = @_; + my ( $hash, $arg ) = @_; + # check if last device my $url = '/rss'; - $data{FWEXT}{$url} = undef if int(devspec2array('TYPE=RSS')) == 1; - return undef; + $data{FWEXT}{$url} = undef if int( devspec2array('TYPE=RSS') ) == 1; + return undef; } sub RSS_Notify { - my ($hash,$dev) = @_; + my ( $hash, $dev ) = @_; - my $name= $hash->{NAME}; - return unless AttrVal($name, 'autoreread',1); - return if($dev->{NAME} ne "global"); - return if(!grep(m/^FILEWRITE $hash->{LAYOUTFILE}$/, @{$dev->{CHANGED}})); + my $name = $hash->{NAME}; + return unless AttrVal( $name, 'autoreread', 1 ); + return if ( $dev->{NAME} ne "global" ); + return + if ( !grep( m/^FILEWRITE $hash->{LAYOUTFILE}$/, @{ $dev->{CHANGED} } ) ); - Log3(undef, 4, "RSS: $name reread layout after edit."); - RSS_readLayout($hash); - return undef; + Log3( undef, 4, "RSS: $name reread layout after edit." ); + RSS_readLayout($hash); + return undef; } ################## -sub -RSS_Set() { +sub RSS_Set() { - my ($hash, @a) = @_; - my $name = $a[0]; + my ( $hash, @a ) = @_; + my $name = $a[0]; - # usage check - my $usage= "Unknown argument, choose one of rereadcfg:noArg"; - if((@a == 2) && ($a[1] eq "rereadcfg")) { - RSS_readLayout($hash); - return undef; - } else { - return $usage; - } + # usage check + my $usage = "Unknown argument, choose one of rereadcfg:noArg"; + if ( ( @a == 2 ) && ( $a[1] eq "rereadcfg" ) ) { + RSS_readLayout($hash); + return undef; + } + else { + return $usage; + } } #################### -# -sub -RSS_getURL($) { - my ($name)= @_; +# +sub RSS_getURL($) { + my ($name) = @_; - my $url= AttrVal($name, 'urlOverride', ''); - return $url if($url ne ""); - - my $hostname= $defs{$name}{fhem}{hostname}; - # http://hostname:8083/fhem - my $proto = (AttrVal($FW_wname, 'HTTPS', 0) == 1) ? 'https' : 'http'; - return $proto."://$hostname:" . $defs{$FW_wname}{PORT} . $FW_ME; + my $url = AttrVal( $name, 'urlOverride', '' ); + return $url if ( $url ne "" ); + + my $hostname = $defs{$name}{fhem}{hostname}; + + # http://hostname:8083/fhem + my $proto = ( AttrVal( $FW_wname, 'HTTPS', 0 ) == 1 ) ? 'https' : 'http'; + return $proto . "://$hostname:" . $defs{$FW_wname}{PORT} . $FW_ME; } # ################## @@ -182,7 +185,7 @@ RSS_getURL($) { # { # my @a = @_; # my $attr= $a[2]; -# +# # if($a[0] eq "set") { # set attribute # if($attr eq "bgdir") { # } @@ -191,744 +194,941 @@ RSS_getURL($) { # if($attr eq "bgdir") { # } # } -# +# # return undef; -# +# # } ################## # list all RSS devices -sub -RSS_Overview { +sub RSS_Overview { - my ($name, $url); - my $html= RSS_HTMLHead("RSS Overview", undef) . "\n"; - foreach my $def (sort keys %defs) { - if($defs{$def}{TYPE} eq "RSS") { - $name= $defs{$def}{NAME}; - $url= RSS_getURL($name); - $html.= "$name
\n\n

\n"; + my ( $name, $url ); + my $html = RSS_HTMLHead( "RSS Overview", undef ) . "\n"; + foreach my $def ( sort keys %defs ) { + if ( $defs{$def}{TYPE} eq "RSS" ) { + $name = $defs{$def}{NAME}; + $url = RSS_getURL($name); + $html .= "$name
\n

\n

\n"; } - } - $html.="\n" . RSS_HTMLTail(); - - return ("text/html; charset=utf-8", $html); -} - -################## -sub -RSS_splitRequest($) { - - # http://hostname:8083/fhem/rss - # http://hostname:8083/fhem/rss/myDeviceName.rss - # http://hostname:8083/fhem/rss/myDeviceName.jpg?t=47110.815 - # |--------- url ----------| |---name --| ext |--query--| - - my ($request) = @_; - - # http://hostname:8083/fhem/rss/myDeviceName.rss - # http://hostname:8083/fhem/rss/myDeviceName.jpg - # http://hostname:8083/fhem/rss/myDeviceName.png - # http://hostname:8083/fhem/rss/myDeviceName.html - use constant REGEXP => '^.*\/rss\/([^\/]*)\.(jpg|png|rss|html)(\?(.*))?$'; - if($request =~ REGEXP) { - return ($1,$2,$4); - } else { - #main::Debug "not matched"; - return(undef,undef,undef); - } -} - -################## -sub -RSS_returnRSS($) { - my ($name) = @_; - - my $url= RSS_getURL($name); - my $type = $defs{$name}{fhem}{style}; - my $mime = ($type eq 'png')? 'image/png' : 'image/jpeg'; - my $now = time(); - my $itemTitle = AttrVal($name, "itemtitle", ""); - my $titleTag = ($itemTitle ne '')? ''.$itemTitle.'' : ''; - my $code = "$name1$titleTagitem_$now"; - - return ("application/xml; charset=utf-8", $code); -} - -################## -sub -RSS_getScript() { - - my $jsTemplate = ''; - my $scripts= ""; - 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"; - $scripts .= sprintf($jsTemplate, $script) . "\n"; } - } - return $scripts; + $html .= "\n" . RSS_HTMLTail(); + + return ( "text/html; charset=utf-8", $html ); } -sub -RSS_HTMLHead($$) { - my ($name,$refresh) = @_; - - my ($width,$height)= split(/x/, AttrVal($name,"size","800x600")); - my $viewportContent= AttrVal($name, "viewport", ""); - - - my $doctype= ''; - my $xmlns= 'xmlns="http://www.w3.org/1999/xhtml"'; - my $scripts= RSS_getScript(); - my $viewport= $viewportContent eq "" ? "" : "\n"; - my $code= "$doctype\n\n\n$name\n$viewport$scripts\n"; +################## +sub RSS_splitRequest($) { + + # http://hostname:8083/fhem/rss + # http://hostname:8083/fhem/rss/myDeviceName.rss + # http://hostname:8083/fhem/rss/myDeviceName.jpg?t=47110.815 + # |--------- url ----------| |---name --| ext |--query--| + + my ($request) = @_; + + # http://hostname:8083/fhem/rss/myDeviceName.rss + # http://hostname:8083/fhem/rss/myDeviceName.jpg + # http://hostname:8083/fhem/rss/myDeviceName.png + # http://hostname:8083/fhem/rss/myDeviceName.html + use constant REGEXP => '^.*\/rss\/([^\/]*)\.(jpg|png|rss|html)(\?(.*))?$'; + if ( $request =~ REGEXP ) { + return ( $1, $2, $4 ); + } + else { + #main::Debug "not matched"; + return ( undef, undef, undef ); + } } +################## +sub RSS_returnRSS($) { + my ($name) = @_; -sub -RSS_HTMLTail() { - return ""; + my $url = RSS_getURL($name); + my $type = $defs{$name}{fhem}{style}; + my $mime = ( $type eq 'png' ) ? 'image/png' : 'image/jpeg'; + my $now = time(); + my $itemTitle = AttrVal( $name, "itemtitle", "" ); + my $titleTag = + ( $itemTitle ne '' ) ? '' . $itemTitle . '' : ''; + my $code = +"$name1$titleTagitem_$now"; + + return ( "application/xml; charset=utf-8", $code ); } -sub -RSS_returnHTML($) { - my ($name) = @_; +################## +sub RSS_getScript() { - my $url= RSS_getURL($name); - my $type = $defs{$name}{fhem}{style}; - my $img= "$url/rss/$name.$type"; - my $refresh= AttrVal($name, 'refresh', 60); - my $noscroll= AttrVal($name, 'noscroll', 0); - my $overflow= $noscroll ? " style=\"overflow:hidden\"" : ""; - my $areas= AttrVal($name, 'areas', ""); - my $embed= $defs{$name}{".embed"}; - my $r= ""; - if(defined($refresh) && ($refresh> 0)) { - my $handler= "\"setTimeout(function(){reloadImage(\'img0\')},$refresh*1000);\""; - $r= " onload=$handler onerror=$handler"; - } - my $code= RSS_HTMLHead($name, $refresh) . - "\n" . - "

\n" . - "\n" . - "\n$areas\n\n" . - "
\n" . - "$embed\n" . - "\n" . - RSS_HTMLTail(); - return ("text/html; charset=utf-8", $code); + my $jsTemplate = ''; + my $scripts = ""; + 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"; + $scripts .= sprintf( $jsTemplate, $script ) . "\n"; + } + } + return $scripts; +} + +sub RSS_HTMLHead($$) { + my ( $name, $refresh ) = @_; + + my ( $width, $height ) = split( /x/, AttrVal( $name, "size", "800x600" ) ); + my $viewportContent = AttrVal( $name, "viewport", "" ); + + my $doctype = +''; + my $xmlns = 'xmlns="http://www.w3.org/1999/xhtml"'; + my $scripts = RSS_getScript(); + my $viewport = + $viewportContent eq "" + ? "" + : "\n"; + my $code = +"$doctype\n\n\n$name\n$viewport$scripts\n"; +} + +sub RSS_HTMLTail() { + return ""; +} + +sub RSS_returnHTML($) { + my ($name) = @_; + + my $url = RSS_getURL($name); + my $type = $defs{$name}{fhem}{style}; + my $img = "$url/rss/$name.$type"; + my $refresh = AttrVal( $name, 'refresh', 60 ); + my $noscroll = AttrVal( $name, 'noscroll', 0 ); + my $overflow = $noscroll ? " style=\"overflow:hidden\"" : ""; + my $areas = AttrVal( $name, 'areas', "" ); + my $embed = $defs{$name}{".embed"}; + my $r = ""; + if ( defined($refresh) && ( $refresh > 0 ) ) { + my $handler = + "\"setTimeout(function(){reloadImage(\'img0\')},$refresh*1000);\""; + $r = " onload=$handler onerror=$handler"; + } + my $code = + RSS_HTMLHead( $name, $refresh ) + . "\n" + . "
\n" + . "\n" + . "\n$areas\n\n" + . "
\n" + . "$embed\n" + . "\n" + . RSS_HTMLTail(); + return ( "text/html; charset=utf-8", $code ); } ################## # Library ################## -sub -RSS_xy { - my ($S,$x,$y,%params)= @_; - - $x = $params{x} if($x eq 'x'); - $y = $params{y} if($y eq 'y'); - - if((-1 < $x) && ($x < 1)) { $x*= $S->width; } - if((-1 < $y) && ($y < 1)) { $y*= $S->height; } - - return($x,$y); +sub RSS_xy { + my ( $S, $x, $y, %params ) = @_; + + $x = $params{x} if ( $x eq 'x' ); + $y = $params{y} if ( $y eq 'y' ); + + if ( ( -1 < $x ) && ( $x < 1 ) ) { $x *= $S->width; } + if ( ( -1 < $y ) && ( $y < 1 ) ) { $y *= $S->height; } + + return ( $x, $y ); } -sub -RSS_color { - my ($S,$rgb)= @_; - my $alpha = 0; - my @d= split("", $rgb); - if(length($rgb) == 8) { - $alpha = hex("$d[6]$d[7]"); - $alpha = ($alpha < 127) ? $alpha : 127; - } - return $S->colorAllocateAlpha(hex("$d[0]$d[1]"),hex("$d[2]$d[3]"),hex("$d[4]$d[5]"),$alpha); +sub RSS_color { + my ( $S, $rgb ) = @_; + my $alpha = 0; + my @d = split( "", $rgb ); + if ( length($rgb) == 8 ) { + $alpha = hex("$d[6]$d[7]"); + $alpha = ( $alpha < 127 ) ? $alpha : 127; + } + return $S->colorAllocateAlpha( hex("$d[0]$d[1]"), hex("$d[2]$d[3]"), + hex("$d[4]$d[5]"), $alpha ); } -sub -RSS_itemText { - my ($S,$x,$y,$text,%params)= @_; - return unless(defined($text)); +sub RSS_itemText { + my ( $S, $x, $y, $text, %params ) = @_; + return unless ( defined($text) ); - if($params{useTextAlign}) { - my $align = GD::Text::Align->new($S, - color => RSS_color($S, $params{rgb}), - valign => $params{tvalign}, - halign => $params{thalign}, - ); - $align->set_font($params{font}, $params{pt}); - $align->set_text($text); - $align->draw($x, $y, 0); - } else { - $S->stringFT(RSS_color($S,$params{rgb}),$params{font},$params{pt},0,$x,$y,$text); - } + if ( $params{useTextAlign} ) { + my $align = GD::Text::Align->new( + $S, + color => RSS_color( $S, $params{rgb} ), + valign => $params{tvalign}, + halign => $params{thalign}, + ); + $align->set_font( $params{font}, $params{pt} ); + $align->set_text($text); + $align->draw( $x, $y, 0 ); + } + else { + $S->stringFT( RSS_color( $S, $params{rgb} ), + $params{font}, $params{pt}, 0, $x, $y, $text ); + } } -sub -RSS_itemTextBox { - my ($S,$x,$y,$boxwidth,$bgcolor,$text,%params)= @_; - return unless(defined($text)); - - if($params{useTextWrap}) { - if((0 < $boxwidth) && ($boxwidth < 1)) { $boxwidth*= $S->width; } - my $wrapbox = GD::Text::Wrap->new($S, - color => RSS_color($S, $params{rgb}), - line_space => $params{linespace}, - text => $text, - ); - $wrapbox->set_font($params{font}, $params{pt}); - $wrapbox->set(align => $params{thalign}, width => $boxwidth); - my ($left, $top, $right, $bottom); - ($left, $top, $right, $bottom) = $wrapbox->get_bounds($x,$y); - $S->filledRectangle($left,$top,$right,$bottom,RSS_color($S,$bgcolor)) if(defined($bgcolor)); - ($left, $top, $right, $bottom) = $wrapbox->draw($x, $y); - return $bottom; - } else { - RSS_itemText($S,$x,$y,$text,%params); - return $y; +sub RSS_itemTextBox { + my ( $S, $x, $y, $boxwidth, $bgcolor, $text, %params ) = @_; + return unless ( defined($text) ); + + if ( $params{useTextWrap} ) { + if ( ( 0 < $boxwidth ) && ( $boxwidth < 1 ) ) { + $boxwidth *= $S->width; } + my $wrapbox = GD::Text::Wrap->new( + $S, + color => RSS_color( $S, $params{rgb} ), + line_space => $params{linespace}, + text => $text, + ); + $wrapbox->set_font( $params{font}, $params{pt} ); + $wrapbox->set( align => $params{thalign}, width => $boxwidth ); + my ( $left, $top, $right, $bottom ); + ( $left, $top, $right, $bottom ) = $wrapbox->get_bounds( $x, $y ); + $S->filledRectangle( $left, $top, $right, $bottom, + RSS_color( $S, $bgcolor ) ) + if ( defined($bgcolor) ); + ( $left, $top, $right, $bottom ) = $wrapbox->draw( $x, $y ); + return $bottom; + } + else { + RSS_itemText( $S, $x, $y, $text, %params ); + return $y; + } } -sub -RSS_itemTime { - my ($S,$x,$y,%params)= @_; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); - RSS_itemText($S,$x,$y,sprintf("%02d:%02d", $hour, $min),%params); +sub RSS_itemTime { + my ( $S, $x, $y, %params ) = @_; + my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = + localtime(time); + RSS_itemText( $S, $x, $y, sprintf( "%02d:%02d", $hour, $min ), %params ); } -sub -RSS_itemSeconds { - my ($S,$x,$y,$format,%params)= @_; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); - if ($format eq "colon") - { - RSS_itemText($S,$x,$y,sprintf(":%02d", $sec),%params); - } - else - { - RSS_itemText($S,$x,$y,sprintf("%02d", $sec),%params); - } +sub RSS_itemSeconds { + my ( $S, $x, $y, $format, %params ) = @_; + my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = + localtime(time); + if ( $format eq "colon" ) { + RSS_itemText( $S, $x, $y, sprintf( ":%02d", $sec ), %params ); + } + else { + RSS_itemText( $S, $x, $y, sprintf( "%02d", $sec ), %params ); + } } -sub -RSS_itemDate { - my ($S,$x,$y,%params)= @_; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); - RSS_itemText($S,$x,$y,sprintf("%02d.%02d.%04d", $mday, $mon+1, $year+1900),%params); + +sub RSS_itemDate { + my ( $S, $x, $y, %params ) = @_; + my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = + localtime(time); + RSS_itemText( $S, $x, $y, + sprintf( "%02d.%02d.%04d", $mday, $mon + 1, $year + 1900 ), %params ); } -sub -RSS_itemImg { - my ($S,$x,$y,$scale,$imgtype,$srctype,$arg,%params)= @_; - return unless(defined($arg)); - return if($arg eq ""); - my $I; - if($srctype eq "url" || $srctype eq "urlq") { - my $data; - if($srctype eq "url") { - $data= GetFileFromURL($arg,3,undef,1); - } else { - $data= GetFileFromURLQuiet($arg,3,undef,1); +sub RSS_itemImg { + my ( $S, $x, $y, $scale, $imgtype, $srctype, $arg, %params ) = @_; + return unless ( defined($arg) ); + return if ( $arg eq "" ); + my $I; + if ( $srctype eq "url" || $srctype eq "urlq" ) { + my $data; + if ( $srctype eq "url" ) { + $data = GetFileFromURL( $arg, 3, undef, 1 ); + } + else { + $data = GetFileFromURLQuiet( $arg, 3, undef, 1 ); + } + if ( $imgtype eq "gif" ) { + $I = GD::Image->newFromGifData($data); + } + elsif ( $imgtype eq "png" ) { + $I = GD::Image->newFromPngData($data); + } + elsif ( $imgtype eq "jpeg" ) { + $I = GD::Image->newFromJpegData($data); + } + else { + return; + } } - if($imgtype eq "gif") { - $I= GD::Image->newFromGifData($data); - } elsif($imgtype eq "png") { - $I= GD::Image->newFromPngData($data); - } elsif($imgtype eq "jpeg") { - $I= GD::Image->newFromJpegData($data); - } else { - return; + elsif ( $srctype eq "file" ) { + if ( $imgtype eq "gif" ) { + $I = GD::Image->newFromGif($arg); + } + elsif ( $imgtype eq "png" ) { + $I = GD::Image->newFromPng($arg); + } + elsif ( $imgtype eq "jpeg" ) { + $I = GD::Image->newFromJpeg($arg); + } + else { + return; + } } - } elsif($srctype eq "file") { - if($imgtype eq "gif") { - $I= GD::Image->newFromGif($arg); - } elsif($imgtype eq "png") { - $I= GD::Image->newFromPng($arg); - } elsif($imgtype eq "jpeg") { - $I= GD::Image->newFromJpeg($arg); - } else { - return; + elsif ( $srctype eq "data" ) { + if ( $imgtype eq "gif" ) { + $I = GD::Image->newFromGifData($arg); + } + elsif ( $imgtype eq "png" ) { + $I = GD::Image->newFromPngData($arg); + } + elsif ( $imgtype eq "jpeg" ) { + $I = GD::Image->newFromJpegData($arg); + } + else { + return; + } } - } elsif($srctype eq "data") { - if($imgtype eq "gif") { - $I= GD::Image->newFromGifData($arg); - } elsif($imgtype eq "png") { - $I= GD::Image->newFromPngData($arg); - } elsif($imgtype eq "jpeg") { - $I= GD::Image->newFromJpegData($arg); - } else { - return; + else { + return; } - } else { - return; - } - eval { - my ($width,$height)= $I->getBounds(); - if ($scale =~ s/([wh])([\d]*)/$2/) { # get the digit from width/hight to pixel entry - #Debug "RSS scale $scale (1: $1 / 2: $2)contais px after Digit - width: $width / height: $height"; - if ($1 eq "w") { - $scale=$scale/$width; - } else { - $scale=$scale/$height; - } - } - my ($swidth,$sheight)= (int($scale*$width), int($scale*$height)); + eval { + my ( $width, $height ) = $I->getBounds(); + if ( $scale =~ s/([wh])([\d]*)/$2/ ) + { # get the digit from width/hight to pixel entry + #Debug "RSS scale $scale (1: $1 / 2: $2)contais px after Digit - width: $width / height: $height"; + if ( $1 eq "w" ) { + $scale = $scale / $width; + } + else { + $scale = $scale / $height; + } + } + my ( $swidth, $sheight ) = + ( int( $scale * $width ), int( $scale * $height ) ); - given ($params{ihalign}) { - when('center') { $x -= $swidth/2; } - when('right') { $x -= $swidth; } - default { } # nothing to do - } - given ($params{ivalign}) { - when('center') { $y -= $sheight/2; } - when('base') { $y -= $sheight; } - when('bottom') { $y -= $sheight; } - default { } # nothing to do - } + given ( $params{ihalign} ) { + when ('center') { $x -= $swidth / 2; } + when ('right') { $x -= $swidth; } + default { + } # nothing to do + } + given ( $params{ivalign} ) { + when ('center') { $y -= $sheight / 2; } + when ('base') { $y -= $sheight; } + when ('bottom') { $y -= $sheight; } + default { + } # nothing to do + } - #Debug "RSS placing $arg ($swidth x $sheight) at ($x,$y)"; - $S->copyResampled($I,$x,$y,0,0,$swidth,$sheight,$width,$height); - }; - if($@) { - Log3 undef, 2, "RSS: cannot create image $srctype $imgtype '$arg': $@"; - } -} - -sub -RSS_itemLine { - my ($S,$x1,$y1,$x2,$y2,$th,%params)= @_; - $S->setThickness($th); - $S->line($x1,$y1,$x2,$y2,RSS_color($S,$params{rgb})); + #Debug "RSS placing $arg ($swidth x $sheight) at ($x,$y)"; + $S->copyResampled( $I, $x, $y, 0, 0, $swidth, $sheight, $width, + $height ); + }; + if ($@) { + Log3 undef, 2, "RSS: cannot create image $srctype $imgtype '$arg': $@"; + } } -sub -RSS_itemRect { - my ($S,$x1,$y1,$x2,$y2,$filled,%params)= @_; - $x2 = $x1 + $x2 if ($x2 =~ /^\+/); - $y2 = $y1 + $y2 if ($y2 =~ /^\+/); - if($filled) { - $S->filledRectangle($x1,$y1,$x2,$y2,RSS_color($S,$params{rgb})); - } else { - $S->rectangle($x1,$y1,$x2,$y2,RSS_color($S,$params{rgb})); - } +sub RSS_itemLine { + my ( $S, $x1, $y1, $x2, $y2, $th, %params ) = @_; + $S->setThickness($th); + $S->line( $x1, $y1, $x2, $y2, RSS_color( $S, $params{rgb} ) ); +} + +sub RSS_itemRect { + my ( $S, $x1, $y1, $x2, $y2, $filled, %params ) = @_; + $x2 = $x1 + $x2 if ( $x2 =~ /^\+/ ); + $y2 = $y1 + $y2 if ( $y2 =~ /^\+/ ); + if ($filled) { + $S->filledRectangle( $x1, $y1, $x2, $y2, + RSS_color( $S, $params{rgb} ) ); + } + else { + $S->rectangle( $x1, $y1, $x2, $y2, RSS_color( $S, $params{rgb} ) ); + } } ################## -sub -RSS_evalLayout($$@) { - my ($S,$name,$layout)= @_; - my @layout= split("\n", $layout); +sub RSS_cleanLayout($) { + my ($input) = @_; + my @output; - my %pstack; - my $pstackcount = 0; - my %params; - $params{font}= "Arial"; - $params{pt}= 12; - $params{rgb}= "ffffff"; - $params{halign} = 'left'; - $params{valign} = 'base'; - $params{condition} = 1; - # we need two pairs of align parameters - # due to different default values for text and img - $params{useTextAlign}= $defs{$name}{fhem}{useTextAlign}; - $params{useTextWrap}= $defs{$name}{fhem}{useTextWrap}; - $params{ihalign} = 'left'; - $params{ivalign} = 'top'; - $params{thalign} = 'left'; - $params{tvalign} = 'base'; - $params{linespace} = 0; - $params{x}= 0; - $params{y}= 0; - - $defs{$name}{".embed"}= ""; - + my $cont = ""; + foreach my $line ( split( "\n", $input ) ) { - my ($x,$y,$z,$x1,$y1,$x2,$y2,$scale,$bgcolor,$boxwidth,$text,$imgtype,$srctype,$arg,$format); + # kill trailing newline + chomp $line; - my $cont= ""; - foreach my $line (@layout) { - # kill trailing newline - chomp $line; - # kill comments and blank lines - $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 eq 'condition') { - $params{condition} = AnalyzePerlCommand(undef, $def); + # kill comments and blank lines + $line =~ s/\#.*$//; + $line =~ s/\s+$//; + $line = $cont . $line; + if ( $line =~ s/\\$// ) { $cont = $line; undef $line; } + next unless ($line); + $cont = ""; + push @output, $line; + } + return @output; +} + +sub RSS_analyzePerl($) { + my ($expr) = @_; + return AnalyzePerlCommand( undef, $expr, 1 ); +} + +sub RSS_evalLayout($$@) { + my ( $S, $name, $layout ) = @_; + + my @layout = RSS_cleanLayout($layout); + my $lineNr; + my %labels; + my %count; + + # first pass + # collect labels + for ( $lineNr = 0 ; $lineNr <= $#layout ; $lineNr++ ) { + if ( $layout[$lineNr] =~ /^label\s+(.+)$/ ) { + my $label = $1; + if ( exists( $labels{$label} ) ) { + Log3 $name, 2, "$name: label $label redefined."; + } + else { + $labels{$label} = $lineNr; + $count{ '$' . $label } = 0; + #Debug "defined label $label"; + } + } + } + EvalSpecials( undef, %count ); + + # second pass + # create actual layout + + my %pstack; + my $pstackcount = 0; + my %params; + $params{font} = "Arial"; + $params{pt} = 12; + $params{rgb} = "ffffff"; + $params{halign} = 'left'; + $params{valign} = 'base'; + $params{condition} = 1; + + # we need two pairs of align parameters + # due to different default values for text and img + $params{useTextAlign} = $defs{$name}{fhem}{useTextAlign}; + $params{useTextWrap} = $defs{$name}{fhem}{useTextWrap}; + $params{ihalign} = 'left'; + $params{ivalign} = 'top'; + $params{thalign} = 'left'; + $params{tvalign} = 'base'; + $params{linespace} = 0; + $params{x} = 0; + $params{y} = 0; + + $defs{$name}{".embed"} = ""; + + my ( + $x, $y, $z, $x1, $y1, + $x2, $y2, $scale, $bgcolor, $boxwidth, + $text, $imgtype, $srctype, $arg, $format + ); + + for ( $lineNr = 0 ; $lineNr <= $#layout ; $lineNr++ ) { + my $line = $layout[$lineNr]; + + #Debug "Line $lineNr: $line"; + + #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 eq 'condition' ) { + $params{condition} = RSS_analyzePerl($def); + #Debug "condition $def evaluated to " . $params{condition}; next; - } - next unless($params{condition}); - - #Debug "before command $line: x= " . $params{x} . ", y= " . $params{y}; + } + next unless ( $params{condition} ); - eval { - if($cmd eq "rgb") { - $def= "\"$def\"" if(length($def) == 6 && $def =~ /[[:xdigit:]]{6}/); - $params{rgb}= AnalyzePerlCommand(undef, $def); - } elsif($cmd eq "font") { - $params{font}= $def; - } elsif($cmd eq "pt") { - $def= AnalyzePerlCommand(undef, $def); - if($def =~ m/^[+-]/) { - $params{pt} += $def; - } else { - $params{pt} = $def; - } - $params{pt} = 6 if($params{pt} < 0); - } elsif($cmd eq "moveto") { - my ($tox,$toy)= split('[ \t]+', $def, 2); - my ($x,$y)= RSS_xy($S, $tox,$toy,%params); - $params{x} = $x; - $params{y} = $y; - } elsif($cmd eq "moveby") { - my ($byx,$byy)= split('[ \t]+', $def, 2); - my ($x,$y)= RSS_xy($S, $byx,$byy,%params); - $params{x} += $x; - $params{y} += $y; - } elsif($cmd ~~ @cmd_halign) { - my $d = AnalyzePerlCommand(undef, $def); - if($d ~~ @valid_halign) { - $params{ihalign}= $d unless($cmd eq "thalign"); - $params{thalign}= $d unless($cmd eq "ihalign"); - } else { - Log3 $name, 2, "$name: Illegal horizontal alignment $d"; - } - } elsif($cmd ~~ @cmd_valign) { - my $d = AnalyzePerlCommand(undef, $def); - if( $d ~~ @valid_valign) { - $params{ivalign}= $d unless($cmd eq "tvalign"); - $params{tvalign}= $d unless($cmd eq "ivalign"); - } else { - Log3 $name, 2, "$name: Illegal vertical alignment $d"; - } - } elsif($cmd eq "linespace") { - $params{linespace}= $def; - } elsif($cmd eq "text") { - ($x,$y,$text)= split("[ \t]+", $def, 3); - ($x,$y)= RSS_xy($S, $x,$y,%params); - $params{x} = $x; - $params{y} = $y; - my $txt= AnalyzePerlCommand(undef, $text); - #Debug "$name: ($x,$y) $txt"; - RSS_itemText($S,$x,$y,$txt,%params); - } elsif($cmd eq "textbox") { - ($x,$y,$boxwidth,$text)= split("[ \t]+", $def, 4); - ($x,$y)= RSS_xy($S, $x,$y,%params); - my $txt= AnalyzePerlCommand(undef, $text); - $y= RSS_itemTextBox($S,$x,$y,$boxwidth,undef,$txt,%params); - $params{x} = $x; - $params{y} = $y; - } elsif($cmd eq "textboxf") { - ($x,$y,$boxwidth,$bgcolor,$text)= split("[ \t]+", $def, 5); - ($x,$y)= RSS_xy($S, $x,$y,%params); - $bgcolor = ($bgcolor ne "") ? AnalyzePerlCommand(undef,$bgcolor) : undef; - my $txt= AnalyzePerlCommand(undef, $text); - $y= RSS_itemTextBox($S,$x,$y,$boxwidth,$bgcolor,$txt,%params); - $params{x} = $x; - $params{y} = $y; - } elsif($cmd eq "line") { - ($x1,$y1,$x2,$y2,$format)= split("[ \t]+", $def, 5); - ($x1,$y1)= RSS_xy($S, $x1,$y1,%params); - ($x2,$y2)= RSS_xy($S, $x2,$y2,%params); - $format //= 1; # set format to 1 as default thickness for the line - RSS_itemLine($S,$x1,$y1,$x2,$y2, $format,%params); - } elsif($cmd eq "rect") { - ($x1,$y1,$x2,$y2,$format)= split("[ \t]+", $def, 5); - ($x1,$y1)= RSS_xy($S, $x1,$y1,%params); - ($x2,$y2)= RSS_xy($S, $x2,$y2,%params); - $format //= 0; # set format to 0 as default (not filled) - RSS_itemRect($S,$x1,$y1,$x2,$y2, $format,%params); - } elsif($cmd eq "time") { - ($x,$y)= split("[ \t]+", $def, 2); - ($x,$y)= RSS_xy($S, $x,$y,%params); - $params{x} = $x; - $params{y} = $y; - RSS_itemTime($S,$x,$y,%params); - } elsif($cmd eq "seconds") { - ($x,$y,$format) = split("[ \+]", $def,3); - ($x,$y)= RSS_xy($S, $x,$y,%params); - $params{x} = $x; - $params{y} = $y; - RSS_itemSeconds($S,$x,$y,$format,%params); - } elsif($cmd eq "date") { - ($x,$y)= split("[ \t]+", $def, 2); - ($x,$y)= RSS_xy($S, $x,$y,%params); - $params{x} = $x; - $params{y} = $y; - RSS_itemDate($S,$x,$y,%params); - } elsif($cmd eq "img") { - ($x,$y,$scale,$imgtype,$srctype,$arg)= split("[ \t]+", $def,6); - ($x,$y)= RSS_xy($S, $x,$y,%params); - $params{x} = $x; - $params{y} = $y; - my $arg= AnalyzePerlCommand(undef, $arg); - RSS_itemImg($S,$x,$y,$scale,$imgtype,$srctype,$arg,%params); - } elsif($cmd eq "push") { - $pstackcount++; - while ( my ($key, $value) = each(%params) ) { - $pstack{$pstackcount}{$key} = $value; - } - } elsif($cmd eq "pop") { - return unless $pstackcount; - while ( my ($key, $value) = each(%{$pstack{$pstackcount}}) ) { - $params{$key} = $value; - } - delete $pstack{$pstackcount}; - $pstackcount--; - } elsif($cmd eq "embed") { - ($x,$y,$z,$format,$text,$arg) = split("[ \t]+", $def,6); - ($x,$y)= RSS_xy($S, $x,$y,%params); - my $arg= AnalyzePerlCommand(undef, $arg); - $defs{$name}{".embed"} .= "
\n"; - $defs{$name}{".embed"} .= $arg."\n"; - $defs{$name}{".embed"} .= "
\n"; - #main::Debug "SET EMBED=" . $defs{$name}{".embed"}; - } else { - Log3 $name, 2, "$name: Illegal command $cmd in layout definition."; - } - - #Debug "after command $line: x= " . $params{x} . ", y= " . $params{y}; - }; - if($@) { - my $msg= "$name: Error from line \'$line\' in layout definition: $@"; - chomp $msg; - Log3 $name, 2, $msg; - } - } + #Debug "before command $line: x= " . $params{x} . ", y= " . $params{y}; + + eval { + if ( $cmd eq "rgb" ) { + $def = "\"$def\"" + if ( length($def) == 6 && $def =~ /[[:xdigit:]]{6}/ ); + $params{rgb} = RSS_analyzePerl($def); + } + elsif ( $cmd eq "font" ) { + $params{font} = $def; + } + elsif ( $cmd eq "pt" ) { + $def = RSS_analyzePerl($def); + if ( $def =~ m/^[+-]/ ) { + $params{pt} += $def; + } + else { + $params{pt} = $def; + } + $params{pt} = 6 if ( $params{pt} < 0 ); + } + elsif ( $cmd eq "moveto" ) { + my ( $tox, $toy ) = split( '[ \t]+', $def, 2 ); + my ( $x, $y ) = RSS_xy( $S, $tox, $toy, %params ); + $params{x} = $x; + $params{y} = $y; + } + elsif ( $cmd eq "moveby" ) { + my ( $byx, $byy ) = split( '[ \t]+', $def, 2 ); + my ( $x, $y ) = RSS_xy( $S, $byx, $byy, %params ); + $params{x} += $x; + $params{y} += $y; + } + elsif ( $cmd ~~ @cmd_halign ) { + my $d = RSS_analyzePerl($def); + if ( $d ~~ @valid_halign ) { + $params{ihalign} = $d unless ( $cmd eq "thalign" ); + $params{thalign} = $d unless ( $cmd eq "ihalign" ); + } + else { + Log3 $name, 2, "$name: Illegal horizontal alignment $d"; + } + } + elsif ( $cmd ~~ @cmd_valign ) { + my $d = RSS_analyzePerl($def); + if ( $d ~~ @valid_valign ) { + $params{ivalign} = $d unless ( $cmd eq "tvalign" ); + $params{tvalign} = $d unless ( $cmd eq "ivalign" ); + } + else { + Log3 $name, 2, "$name: Illegal vertical alignment $d"; + } + } + elsif ( $cmd eq "linespace" ) { + $params{linespace} = $def; + } + elsif ( $cmd eq "text" ) { + ( $x, $y, $text ) = split( "[ \t]+", $def, 3 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + $params{x} = $x; + $params{y} = $y; + my $txt = RSS_analyzePerl($text); + + #Debug "$name: ($x,$y) $txt"; + RSS_itemText( $S, $x, $y, $txt, %params ); + } + elsif ( $cmd eq "textbox" ) { + ( $x, $y, $boxwidth, $text ) = split( "[ \t]+", $def, 4 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + my $txt = RSS_analyzePerl($text); + $y = RSS_itemTextBox( $S, $x, $y, $boxwidth, undef, $txt, + %params ); + $params{x} = $x; + $params{y} = $y; + } + elsif ( $cmd eq "textboxf" ) { + ( $x, $y, $boxwidth, $bgcolor, $text ) = + split( "[ \t]+", $def, 5 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + $bgcolor = + ( $bgcolor ne "" ) + ? RSS_analyzePerl($bgcolor) + : undef; + my $txt = RSS_analyzePerl($text); + $y = RSS_itemTextBox( $S, $x, $y, $boxwidth, $bgcolor, $txt, + %params ); + $params{x} = $x; + $params{y} = $y; + } + elsif ( $cmd eq "line" ) { + ( $x1, $y1, $x2, $y2, $format ) = + split( "[ \t]+", $def, 5 ); + ( $x1, $y1 ) = RSS_xy( $S, $x1, $y1, %params ); + ( $x2, $y2 ) = RSS_xy( $S, $x2, $y2, %params ); + $format //= + 1; # set format to 1 as default thickness for the line + RSS_itemLine( $S, $x1, $y1, $x2, $y2, $format, %params ); + } + elsif ( $cmd eq "rect" ) { + ( $x1, $y1, $x2, $y2, $format ) = + split( "[ \t]+", $def, 5 ); + ( $x1, $y1 ) = RSS_xy( $S, $x1, $y1, %params ); + ( $x2, $y2 ) = RSS_xy( $S, $x2, $y2, %params ); + $format //= 0; # set format to 0 as default (not filled) + RSS_itemRect( $S, $x1, $y1, $x2, $y2, $format, %params ); + } + elsif ( $cmd eq "time" ) { + ( $x, $y ) = split( "[ \t]+", $def, 2 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + $params{x} = $x; + $params{y} = $y; + RSS_itemTime( $S, $x, $y, %params ); + } + elsif ( $cmd eq "seconds" ) { + ( $x, $y, $format ) = split( "[ \+]", $def, 3 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + $params{x} = $x; + $params{y} = $y; + RSS_itemSeconds( $S, $x, $y, $format, %params ); + } + elsif ( $cmd eq "date" ) { + ( $x, $y ) = split( "[ \t]+", $def, 2 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + $params{x} = $x; + $params{y} = $y; + RSS_itemDate( $S, $x, $y, %params ); + } + elsif ( $cmd eq "img" ) { + ( $x, $y, $scale, $imgtype, $srctype, $arg ) = + split( "[ \t]+", $def, 6 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + $params{x} = $x; + $params{y} = $y; + my $arg = RSS_analyzePerl($arg); + RSS_itemImg( $S, $x, $y, $scale, $imgtype, $srctype, $arg, + %params ); + } + elsif ( $cmd eq "push" ) { + $pstackcount++; + while ( my ( $key, $value ) = each(%params) ) { + $pstack{$pstackcount}{$key} = $value; + } + } + elsif ( $cmd eq "pop" ) { + return unless $pstackcount; + while ( my ( $key, $value ) = + each( %{ $pstack{$pstackcount} } ) ) + { + $params{$key} = $value; + } + delete $pstack{$pstackcount}; + $pstackcount--; + } + elsif ( $cmd eq "embed" ) { + ( $x, $y, $z, $format, $text, $arg ) = + split( "[ \t]+", $def, 6 ); + ( $x, $y ) = RSS_xy( $S, $x, $y, %params ); + my $arg = RSS_analyzePerl($arg); + $defs{$name}{".embed"} .= + "
\n"; + $defs{$name}{".embed"} .= $arg . "\n"; + $defs{$name}{".embed"} .= "
\n"; + + #main::Debug "SET EMBED=" . $defs{$name}{".embed"}; + } + elsif ( $cmd eq "label" ) { + + #Debug "encountered label $def"; + $count{ '$' . $def }++; + if ( $count{ '$' . $def } > 99 ) { + Log3 $name, 2, "$name: exceeded hit count for label $def"; + last; + } + #Debug "label $def hit count " . $count{ '$' . $def }; + EvalSpecials(undef, %count); + } + elsif ( $cmd eq "goto" ) { + my ( $label, $if, $condition ) = split( "[ \t]+", $def, 3 ); + if ( defined($if) + and ( ( $if ne "if" ) or ( !defined($condition) ) ) ) + { + Log3 $name, 2, + "$name: syntax error in goto command \"$line\"."; + next; + } + if ( exists $labels{$label} ) { + if ( !defined($if) + or RSS_analyzePerl($condition) ) + { + $lineNr = $labels{$label} - 1; + } + } + else { + Log3 $name, 2, + "$name: Undefined label $label in goto command."; + } + } + else { + Log3 $name, 2, + "$name: Illegal command $cmd in layout definition."; + } + + #Debug "after command $line: x= " . $params{x} . ", y= " . $params{y}; + }; + if ($@) { + my $msg = + "$name: Error from line \'$line\' in layout definition: $@"; + chomp $msg; + Log3 $name, 2, $msg; + } + } } ################## -sub -RSS_returnIMG($$) { - my ($name,$type)= @_; +sub RSS_returnIMG($$) { + my ( $name, $type ) = @_; - my ($width,$height)= split(/x/, AttrVal($name,"size","800x600")); + my ( $width, $height ) = + split( /x/, AttrVal( $name, "size", "800x600" ) ); - # - # increase counter - # - if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{counter})) { - $defs{$name}{fhem}{counter}++; - } else { - $defs{$name}{fhem}{counter}= 1; - } - - # true color - GD::Image->trueColor(1); - - # - # create the image - # - our $S; - # let's create a blank image, we will need it in most cases. - $S= GD::Image->newTrueColor($width,$height); - my $bgcolor = AttrVal($name,'bgcolor','000000'); #default bg color = black - $bgcolor = RSS_color($S, $bgcolor); - # $S->colorAllocate(0,0,0); # other colors seem not to work (issue with GD) - $S->fill(0,0,$bgcolor); - # wrap to make problems with GD non-lethal - - eval { # - # set the background + # increase counter # - # check if background directory is set - my $reason= "?"; # remember reason for undefined image - my $bgdir= AttrVal($name,"bg","undef"); - if(defined($bgdir)){ - my $bgnr; # item number - if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{bgnr})) { - $bgnr= $defs{$name}{fhem}{bgnr}; - } else { - $bgnr= 0; - } - # check if at least tmin seconds have passed - my $t0= 0; - my $tmin= AttrVal($name,"tmin",0); - if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{t})) { - $t0= $defs{$name}{fhem}{t}; - } - my $t1= time(); - if($t1-$t0>= $tmin) { - $defs{$name}{fhem}{t}= $t1; - $bgnr++; - } - # detect pictures - if(opendir(BGDIR, $bgdir)){ - my @bgfiles= grep {$_ !~ /^\./} readdir(BGDIR); - - #foreach my $f (@bgfiles) { - # Debug sprintf("File \"%s\"\n", $f); - #} - closedir(BGDIR); - # get item number - if($#bgfiles>=0) { - if($bgnr > $#bgfiles) { $bgnr= 0; } - $defs{$name}{fhem}{bgnr}= $bgnr; - my $bgfile= $bgdir . "/" . $bgfiles[$bgnr]; - my $filetype =(split(/\./,$bgfile))[-1]; - my $bg; - $bg= newFromGif GD::Image($bgfile) if $filetype =~ m/^gif$/i; - $bg= newFromJpeg GD::Image($bgfile) if $filetype =~ m/^jpe?g$/i; - $bg= newFromPng GD::Image($bgfile) if $filetype =~ m/^png$/i; - if(defined($bg)) { - my ($bgwidth,$bgheight)= $bg->getBounds(); - if($bgwidth != $width or $bgheight != $height) { - # we need to resize - my ($w,$h); - my ($u,$v)= ($bgwidth/$width, $bgheight/$height); - if($u>$v) { - $w= $width; - $h= $bgheight/$u; - } else { - $h= $height; - $w= $bgwidth/$v; - } - $S->copyResized($bg,($width-$w)/2,($height-$h)/2,0,0,$w,$h,$bgwidth,$bgheight); - } else { - # size is as required - # kill the predefined image and take the original - undef $S; - $S= $bg; - } - } else { - undef $S; - $reason= "Something was wrong with background image \"$bgfile\"."; - } - } - } - } - # - # evaluate layout - # - if(defined($S)) { - RSS_evalLayout($S, $name, $defs{$name}{fhem}{layout}); - } else { - Log3 $name, 2, "$name: Could not create image. $reason"; - $S= GD::Image->newTrueColor($width,$height); # return empty image + if ( defined( $defs{$name}{fhem} ) + && defined( $defs{$name}{fhem}{counter} ) ) + { + $defs{$name}{fhem}{counter}++; + } + else { + $defs{$name}{fhem}{counter} = 1; } - $defs{$name}{STATE} = localtime(); - - }; #warn $@ if $@; - if($@) { - my $msg= $@; - chomp $msg; - Log3 $name, 2, $msg; - } - - # - # return image - # - return ("image/jpeg; charset=utf-8", $S->jpeg) if($type eq 'jpg'); - return ("image/png; charset=utf-8", $S->png) if($type eq 'png'); + # true color + GD::Image->trueColor(1); + + # + # create the image + # + our $S; + + # let's create a blank image, we will need it in most cases. + $S = GD::Image->newTrueColor( $width, $height ); + my $bgcolor = + AttrVal( $name, 'bgcolor', '000000' ); #default bg color = black + $bgcolor = RSS_color( $S, $bgcolor ); + + # $S->colorAllocate(0,0,0); # other colors seem not to work (issue with GD) + $S->fill( 0, 0, $bgcolor ); + + # wrap to make problems with GD non-lethal + + eval { + # + # set the background + # + # check if background directory is set + my $reason = "?"; # remember reason for undefined image + my $bgdir = AttrVal( $name, "bg", "undef" ); + if ( defined($bgdir) ) { + my $bgnr; # item number + if ( defined( $defs{$name}{fhem} ) + && defined( $defs{$name}{fhem}{bgnr} ) ) + { + $bgnr = $defs{$name}{fhem}{bgnr}; + } + else { + $bgnr = 0; + } + + # check if at least tmin seconds have passed + my $t0 = 0; + my $tmin = AttrVal( $name, "tmin", 0 ); + if ( defined( $defs{$name}{fhem} ) + && defined( $defs{$name}{fhem}{t} ) ) + { + $t0 = $defs{$name}{fhem}{t}; + } + my $t1 = time(); + if ( $t1 - $t0 >= $tmin ) { + $defs{$name}{fhem}{t} = $t1; + $bgnr++; + } + + # detect pictures + if ( opendir( BGDIR, $bgdir ) ) { + my @bgfiles = grep { $_ !~ /^\./ } readdir(BGDIR); + + #foreach my $f (@bgfiles) { + # Debug sprintf("File \"%s\"\n", $f); + #} + closedir(BGDIR); + + # get item number + if ( $#bgfiles >= 0 ) { + if ( $bgnr > $#bgfiles ) { $bgnr = 0; } + $defs{$name}{fhem}{bgnr} = $bgnr; + my $bgfile = $bgdir . "/" . $bgfiles[$bgnr]; + my $filetype = ( split( /\./, $bgfile ) )[-1]; + my $bg; + $bg = newFromGif GD::Image($bgfile) + if $filetype =~ m/^gif$/i; + $bg = newFromJpeg GD::Image($bgfile) + if $filetype =~ m/^jpe?g$/i; + $bg = newFromPng GD::Image($bgfile) + if $filetype =~ m/^png$/i; + + if ( defined($bg) ) { + my ( $bgwidth, $bgheight ) = $bg->getBounds(); + if ( $bgwidth != $width or $bgheight != $height ) { + + # we need to resize + my ( $w, $h ); + my ( $u, $v ) = + ( $bgwidth / $width, $bgheight / $height ); + if ( $u > $v ) { + $w = $width; + $h = $bgheight / $u; + } + else { + $h = $height; + $w = $bgwidth / $v; + } + $S->copyResized( + $bg, + ( $width - $w ) / 2, + ( $height - $h ) / 2, + 0, 0, $w, $h, $bgwidth, $bgheight + ); + } + else { + # size is as required + # kill the predefined image and take the original + undef $S; + $S = $bg; + } + } + else { + undef $S; + $reason = +"Something was wrong with background image \"$bgfile\"."; + } + } + } + } + # + # evaluate layout + # + if ( defined($S) ) { + RSS_evalLayout( $S, $name, $defs{$name}{fhem}{layout} ); + } + else { + Log3 $name, 2, "$name: Could not create image. $reason"; + $S = + GD::Image->newTrueColor( $width, $height ); # return empty image + } + $defs{$name}{STATE} = localtime(); + + }; #warn $@ if $@; + if ($@) { + my $msg = $@; + chomp $msg; + Log3 $name, 2, $msg; + } + + # + # return image + # + return ( "image/jpeg; charset=utf-8", $S->jpeg ) if ( $type eq 'jpg' ); + return ( "image/png; charset=utf-8", $S->png ) if ( $type eq 'png' ); } - + ################## # # here we answer any request to http://host:port/fhem/rss and below -sub -RSS_CGI(){ +sub RSS_CGI() { - my ($request) = @_; # /rss or /rss/name.rss or /rss/name.jpg or /rss/name.png - - my ($name,$ext,$query)= RSS_splitRequest($request); # name, ext (rss, jpg, png, html), query - # query is unused - - #main::Debug "Request: $request"; - #main::Debug " Name : $name"; - #main::Debug " Ext : $ext"; - #main::Debug " Query : $query"; + my ($request) = + @_; # /rss or /rss/name.rss or /rss/name.jpg or /rss/name.png - if(defined($name)) { - if($ext eq "") { - return("text/plain; charset=utf-8", "Illegal extension."); + my ( $name, $ext, $query ) = + RSS_splitRequest($request); # name, ext (rss, jpg, png, html), query + # query is unused + + #main::Debug "Request: $request"; + #main::Debug " Name : $name"; + #main::Debug " Ext : $ext"; + #main::Debug " Query : $query"; + + 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 RSS device: $name" ); + } + + if ( $ext eq "jpg" ) { + return RSS_returnIMG( $name, 'jpg' ); + } + elsif ( $ext eq "png" ) { + return RSS_returnIMG( $name, 'png' ); + } + elsif ( $ext eq "rss" ) { + return RSS_returnRSS($name); + } + elsif ( $ext eq "html" ) { + return RSS_returnHTML($name); + } } - if(!defined($defs{$name})) { - return("text/plain; charset=utf-8", "Unknown RSS device: $name"); + else { + return RSS_Overview(); } - - if($ext eq "jpg") { - return RSS_returnIMG($name,'jpg'); - } elsif($ext eq "png") { - return RSS_returnIMG($name,'png'); - } elsif($ext eq "rss") { - return RSS_returnRSS($name); - } elsif($ext eq "html") { - return RSS_returnHTML($name); - } - } else { - return RSS_Overview(); - } } -sub -plotFromUrl(@) -{ - my (@plotName) = @_; - my @webs; - - @webs=devspec2array("TYPE=FHEMWEB"); - foreach(@webs) { - if(!InternalVal($_,'TEMPORARY',undef)) { - $FW_wname=InternalVal($_,'NAME',''); - last; +sub plotFromUrl(@) { + my (@plotName) = @_; + my @webs; + + @webs = devspec2array("TYPE=FHEMWEB"); + foreach (@webs) { + if ( !InternalVal( $_, 'TEMPORARY', undef ) ) { + $FW_wname = InternalVal( $_, 'NAME', '' ); + last; + } } - } - my ($w, $h) = split(",", AttrVal($plotName[0],"plotsize","800,160")); + my ( $w, $h ) = + split( ",", AttrVal( $plotName[0], "plotsize", "800,160" ) ); - my $url; - $url = ""; - return $url; + my $url; + $url = + ""; + return $url; } - # 1; - - - =pod =item helper =item summary Provides a freely configurable RSS feed and HTML page. -=item summary_DE Stellt frei konfigurierbaren RSS-Feed und HTML-Seite bereit. +=item summary_DE Stellt frei konfigurierbaren RSS-Feed und HTML-Seite bereit. =begin html @@ -936,24 +1136,24 @@ plotFromUrl(@)
    Provides a freely configurable RSS feed and HTML page.

    - The media RSS feed delivers status pictures either in JPEG or PNG format. - - This media RSS feed can be used to feed a status display to a + The media RSS feed delivers status pictures either in JPEG or PNG format. + + This media RSS feed can be used to feed a status display to a network-enabled photo frame.

    - + In addition, a periodically refreshing HTML page is generated that shows the picture with an optional HTML image map.

    You need to have the perl module GD installed. This module is most likely not available for small systems like Fritz!Box.

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

    - + Beginners might find the RSS Workshop useful.

    - - A note on colors: Colors are specified as 6- or 8-digit hex numbers, - every 2 digits determining the red, green and blue color components as in HTML - color codes. The optional 7th and 8th digit code the alpha channel (transparency from - 00 to 7F). Examples: FF0000 for red, C0C0C0 for light + + A note on colors: Colors are specified as 6- or 8-digit hex numbers, + every 2 digits determining the red, green and blue color components as in HTML + color codes. The optional 7th and 8th digit code the alpha channel (transparency from + 00 to 7F). Examples: FF0000 for red, C0C0C0 for light gray, 1C1C1C40 for semi-transparent gray.

    @@ -979,21 +1179,21 @@ plotFromUrl(@) http://192.168.1.222:8083/fhem/rss/MyRSS.rss


- - The pictures are at + + The pictures are at
    http://host.example.org:8083/fhem/rss/FrameRSS.jpg
    http://192.168.1.222:8083/fhem/rss/MyRSS.png

- - The HTML pages are at + + The HTML pages are at
    http://host.example.org:8083/fhem/rss/FrameRSS.html
    http://192.168.1.222:8083/fhem/rss/MyRSS.html

- + @@ -1010,11 +1210,11 @@ plotFromUrl(@) Attributes

    -
  • autoreread
    If set to 1, layout is automatically reread when layout file has been changed +
  • autoreread
    If set to 1, layout is automatically reread when layout file has been changed by FHEMWEB file editor.

  • size
    The dimensions of the picture in the format <width>x<height>.

  • -
  • bg
    The directory that contains the background pictures (must be in JPEG, GIF or PNG format, file +
  • bg
    The directory that contains the background pictures (must be in JPEG, GIF or PNG format, file format is guessed from file name extension).

  • bgcolor <color>
    Sets the background color.

  • tmin
    The background picture is shown at least tmin seconds, @@ -1032,11 +1232,11 @@ plotFromUrl(@)

  • itemtitle
    Adds a title tag to the RSS item that contains the specified text.

  • -
  • urlOverride
    Overrides the URL in the generated feed. +
  • urlOverride
    Overrides the URL in the generated feed. If you specify - attr <name> http://otherhost.example.org:8083/foo/bar, the + attr <name> http://otherhost.example.org:8083/foo/bar, the JPEG picture that is at - http://host.example.org:8083/fhem/rss/FrameRSS.jpg + http://host.example.org:8083/fhem/rss/FrameRSS.jpg will be referenced as http://otherhost.example.org:8083/foo/bar/rss/FrameRSS.jpg. This is useful when your FHEM URLs are rewritten, e.g. if FHEM is accessed by a Reverse Proxy.
  • @@ -1102,9 +1302,9 @@ plotFromUrl(@) Everything after a # is treated as a comment and ignored. You can fold long lines by putting a \ at the end.

    - General notes
    + General notes

      -
    1. Use double quotes to quote literal text if perl specials are allowed.
    2. +
    3. Use double quotes to quote literal text if perl specials are allowed.
    4. Text alignment requires the Perl module GD::Text::Align to be installed. Text wrapping (in text boxes) require GD::Text::Wrap to be installed. Debian-based systems can install both with apt-get install libgd-text-perl.

    @@ -1117,15 +1317,15 @@ plotFromUrl(@)

    - - + + Layout control commands

    - +

    • moveto <x> <y>
      Moves most recently used x- and y-coordinate to the given absolute or relative position.

    • moveby <x> <y>
      Moves most recently used x- and y-coordinate by the given absolute or relative amounts.

    • - +
    • font "<font>"
      Sets the font. <font> is the name of a TrueType font (e.g. Arial) or the full path to a TrueType font (e.g. /usr/share/fonts/truetype/arial.ttf), @@ -1135,27 +1335,39 @@ plotFromUrl(@)
    • rgb "<color>"
      Sets the color. You can use { <perl special> } for <color>.

    • -
    • pt <pt>
      Sets the font size in points. A + or - sign in front of the the number given +
    • pt <pt>
      Sets the font size in points. A + or - sign in front of the the number given for <pt> signifies a change of the font size relative to the current size. Otherwise the absolute size is set. You can use { <perl special> } for <pt>.

    • - +
    • thalign|ihalign|halign "left"|"center"|"right"
      Sets the horizontal alignment of text, image or both. Defaults to left-aligned. You can use { <perl special> } instead of the literal alignment control word.

    • - +
    • tvalign|ivalign|valign "top"|"center"|"base"|"bottom"
      Sets the vertical alignment of text, image or both. Defaults to base-aligned for text and top-aligned for image. You can use { <perl special> } instead of the literal alignment control word.

    • - +
    • linespace <space>
      Sets the line spacing in pixels for text boxes (see textbox item below).

    • - +
    • condition <condition>
      Subsequent layout control and item placement commands except for another condition command are ignored if and only if <condition> evaluates to false.

    • - +
    • push
      The current parameter set (position, color, font name and size, text alignment and line spacing) is put (saved) on top of a stack.

    • - +
    • pop
      The most recently saved (pushed) current parameter set is pulled from the top of the stack and restored.

    • - + +
    • label <lbl>
      Define a label for this line – i.e. name this line <lbl>. This label can be used to jump back or forward to this line by the <goto> command.
      + A counter is implicitly defined for every label. From <perl specials> it can be accessed with {$<lbl>}. + Example: the hit count for label foo is in $foo. + The counter starts with zero and is increased with every hit of the label. +

    • + +
    • goto <lbl>
      goto <lbl> if { <perl special> }
      + Jump back or forward to the line labelled <lbl>. The condition is + optional. Example: goto foo if { $foo < 5 }. This can be used to create loops. + Note: if the goto jumps forward to the label for the first time, the hit count is 0; + if the goto jumps backward to the label for the first time, the hit count is already 1.

    • +
    Item placement commands

    @@ -1166,22 +1378,22 @@ plotFromUrl(@) { <perl special> } for <text> to fully access device readings and do some programming on the fly. See below for examples.
    -

  • textbox <x> <y> <boxwidth> <text>
    Same as before but text is rendered +
  • textbox <x> <y> <boxwidth> <text>
    Same as before but text is rendered in a box of horizontal width <boxwidth>.

  • - -
  • textboxf <x> <y> <boxwidth> <bgcolor> <text>
    Same as before but - the textbox will be filled with the given background color <bgcolor> before drawing the text. + +
  • textboxf <x> <y> <boxwidth> <bgcolor> <text>
    Same as before but + the textbox will be filled with the given background color <bgcolor> before drawing the text. <bgcolor> can be used with { <perl special> } to evalute RGB value.

  • - +
  • time <x> <y>
    Renders the current time in HH:MM format.

  • - +
  • seconds <x> <y> <format>
    Renders the curent seconds. Maybe useful for a RSS Clock.

  • - +
  • date <x> <y>
    Renders the current date in DD.MM.YYYY format.

  • - +
  • line <x1> <y1> <x2> <y2> [<thickness>]
    Draws a line from position (<x1>, <y1>) to position (<x2>, <y2>) with optional thickness (default=1).

  • rect <x1> <y1> <x2> <y2> [<filled>]
    Draws a rectangle with corners at positions (<x1>, <y1>) and (<x2>, <y2>), which is filled if the <filled> parameter is set and not zero.
    If x2 or y2 is preceeded with a + (plus sign) then the coordinate is relative to x1 or y1, or in other words, it is the width-1 or height-1 of the rectangle, respectively.

  • - +
  • img <x> <y> <['w' or 'h']s> <imgtype> <srctype> <arg>
    Renders a picture at the position (<x>, <y>). The <imgtype> is one of gif, jpeg, png. The picture is scaled by the factor <s> (a decimal value). If 'w' or 'h' is in front of scale-value the value is used to set width or height to the value in pixel. If <srctype> is file, the picture @@ -1195,7 +1407,7 @@ plotFromUrl(@) For HTML output: embeds a div element into the HTML page at (<x>,<y>) with z-order <z> and positioning <position> (use absolute). <id> is the id attribute of the div element and <element> is its content.
    Note: There are several issues with different browsers when using this.

  • - +
Example

@@ -1214,9 +1426,9 @@ plotFromUrl(@) embed 10 200 2 absolute iframe1 "<iframe width=\"420\" height=\"315\" src=\"//www.youtube.com/embed/9HShl_ufOFI\" frameborder=\"0\" allowfullscreen></iframe>"

- + Special uses

- + You can display SVG plots with the aid of the helper function plotAsPng(<name>[,<zoom>[,<offset>]]) (in 98_SVG.pm). Examples:

img 20 30 0.6 png data { plotAsPng("mySVGPlot") }
@@ -1224,7 +1436,7 @@ plotFromUrl(@)

This requires the perl module Image::LibRSVG and librsvg. Debian-based systems can install these with apt-get install libimage-librsvg-perl.

- + For HTML output, you can use plotFromURL(<name>[,<zoom>[,<offset>]]) instead.