02_RSS.pm 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. #
  2. #
  3. # 02_RSS.pm
  4. # written by Dr. Boris Neubert 2012-03-24
  5. # e-mail: omega at online dot de
  6. #
  7. ##############################################
  8. # $Id: 02_RSS.pm 14405 2017-05-28 17:30:51Z neubert $
  9. package main;
  10. use strict;
  11. use warnings;
  12. use GD;
  13. use feature qw/switch/;
  14. use vars qw(%data);
  15. use HttpUtils;
  16. #require "98_SVG.pm"; # enable use of plotAsPng()
  17. sub plotAsPng(@); # forward declaration will be enough
  18. # to ensure correct function
  19. # and will avoid reloading 98_SVG.pm
  20. # during fhem startup/rereadcfg
  21. my @cmd_halign= qw(halign thalign ihalign);
  22. my @cmd_valign= qw(valign tvalign ivalign);
  23. my @valid_valign = qw(top center base bottom);
  24. my @valid_halign = qw(left center right justified);
  25. # we can
  26. # use vars qw(%FW_types); # device types,
  27. # use vars qw($FW_RET); # Returned data (html)
  28. # use vars qw($FW_wname); # Web instance
  29. # use vars qw($FW_subdir); # Sub-path in URL for extensions, e.g. 95_FLOORPLAN
  30. # use vars qw(%FW_pos); # scroll position
  31. # use vars qw($FW_cname); # Current connection name
  32. # http://blogs.perl.org/users/mike_b/2013/06/a-little-nicer-way-to-use-smartmatch-on-perl-518.html
  33. #no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  34. no if $] >= 5.017011, warnings => 'experimental';
  35. #########################
  36. sub
  37. RSS_addExtension($$$) {
  38. my ($func,$link,$friendlyname)= @_;
  39. my $url = "/" . $link;
  40. $data{FWEXT}{$url}{FUNC} = $func;
  41. $data{FWEXT}{$url}{LINK} = "+$link";
  42. $data{FWEXT}{$url}{NAME} = $friendlyname;
  43. $data{FWEXT}{$url}{SCRIPT} = "RSS.js";
  44. $data{FWEXT}{$url}{FORKABLE} = 0;
  45. }
  46. ##################
  47. sub
  48. RSS_Initialize($) {
  49. my ($hash) = @_;
  50. $hash->{DefFn} = "RSS_Define";
  51. $hash->{UndefFn} = "RSS_Undefine";
  52. #$hash->{AttrFn} = "RSS_Attr";
  53. $hash->{AttrList} = "size itemtitle bg bgcolor tmin refresh areas autoreread:1,0 viewport noscroll urlOverride";
  54. $hash->{SetFn} = "RSS_Set";
  55. $hash->{NotifyFn} = "RSS_Notify";
  56. return undef;
  57. }
  58. ##################
  59. sub
  60. RSS_readLayout($) {
  61. my ($hash)= @_;
  62. my $filename= $hash->{fhem}{filename};
  63. my $name= $hash->{NAME};
  64. my ($err, @layoutfile) = FileRead($filename);
  65. if($err) {
  66. Log 1, "RSS $name: $err";
  67. $hash->{fhem}{layout}= ("text 0.1 0.1 'Error: $err'");
  68. } else {
  69. $hash->{fhem}{layout}= join("\n", @layoutfile);
  70. $hash->{fhem}{layout} =~ s/\n\n/\n/g;
  71. }
  72. return;
  73. }
  74. ##################
  75. sub
  76. RSS_Define($$) {
  77. my ($hash, $def) = @_;
  78. my @a = split("[ \t]+", $def);
  79. return "Usage: define <name> RSS jpg|png hostname filename" if(int(@a) != 5);
  80. my $name= $a[0];
  81. my $style= $a[2];
  82. my $hostname= $a[3];
  83. my $filename= $a[4];
  84. $hash->{fhem}{style}= $style;
  85. $hash->{fhem}{hostname}= $hostname;
  86. $hash->{fhem}{filename}= $filename;
  87. $hash->{LAYOUTFILE} = $filename;
  88. $hash->{NOTIFYDEV} = 'global';
  89. RSS_addExtension("RSS_CGI","rss","RSS");
  90. eval "use GD::Text::Align";
  91. $hash->{fhem}{useTextAlign} = ($@ ? 0 : 1 );
  92. if(!($hash->{fhem}{useTextAlign})) {
  93. Log3 $hash, 2, "$name: Cannot use text alignment: $@";
  94. }
  95. eval "use GD::Text::Wrap";
  96. $hash->{fhem}{useTextWrap} = ($@ ? 0 : 1 );
  97. if(!($hash->{fhem}{useTextWrap})) {
  98. Log3 $hash, 2, "$name: Cannot use text wrapping: $@";
  99. }
  100. RSS_readLayout($hash);
  101. $hash->{STATE} = 'defined'; #$name;
  102. return undef;
  103. }
  104. sub RSS_Undefine($$) {
  105. my ($hash, $arg) = @_;
  106. # check if last device
  107. my $url = '/rss';
  108. $data{FWEXT}{$url} = undef if int(devspec2array('TYPE=RSS')) == 1;
  109. return undef;
  110. }
  111. sub RSS_Notify {
  112. my ($hash,$dev) = @_;
  113. my $name= $hash->{NAME};
  114. return unless AttrVal($name, 'autoreread',1);
  115. return if($dev->{NAME} ne "global");
  116. return if(!grep(m/^FILEWRITE $hash->{LAYOUTFILE}$/, @{$dev->{CHANGED}}));
  117. Log3(undef, 4, "RSS: $name reread layout after edit.");
  118. RSS_readLayout($hash);
  119. return undef;
  120. }
  121. ##################
  122. sub
  123. RSS_Set() {
  124. my ($hash, @a) = @_;
  125. my $name = $a[0];
  126. # usage check
  127. my $usage= "Unknown argument, choose one of rereadcfg:noArg";
  128. if((@a == 2) && ($a[1] eq "rereadcfg")) {
  129. RSS_readLayout($hash);
  130. return undef;
  131. } else {
  132. return $usage;
  133. }
  134. }
  135. ####################
  136. #
  137. sub
  138. RSS_getURL($) {
  139. my ($name)= @_;
  140. my $url= AttrVal($name, 'urlOverride', '');
  141. return $url if($url ne "");
  142. my $hostname= $defs{$name}{fhem}{hostname};
  143. # http://hostname:8083/fhem
  144. my $proto = (AttrVal($FW_wname, 'HTTPS', 0) == 1) ? 'https' : 'http';
  145. return $proto."://$hostname:" . $defs{$FW_wname}{PORT} . $FW_ME;
  146. }
  147. # ##################
  148. # sub
  149. # RSS_Attr(@)
  150. # {
  151. # my @a = @_;
  152. # my $attr= $a[2];
  153. #
  154. # if($a[0] eq "set") { # set attribute
  155. # if($attr eq "bgdir") {
  156. # }
  157. # }
  158. # elsif($a[0] eq "del") { # delete attribute
  159. # if($attr eq "bgdir") {
  160. # }
  161. # }
  162. #
  163. # return undef;
  164. #
  165. # }
  166. ##################
  167. # list all RSS devices
  168. sub
  169. RSS_Overview {
  170. my ($name, $url);
  171. my $html= RSS_HTMLHead("RSS Overview", undef) . "<body>\n";
  172. foreach my $def (sort keys %defs) {
  173. if($defs{$def}{TYPE} eq "RSS") {
  174. $name= $defs{$def}{NAME};
  175. $url= RSS_getURL($name);
  176. $html.= "$name<br>\n<ul>";
  177. $html.= "<a href='$url/rss/$name.rss' target='_blank' >RSS</a><br>\n";
  178. $html.= "<a href='$url/rss/$name.html' target='_blank' >HTML</a><br>\n";
  179. $html.= "<a href='$url/rss/$name.png' target='_blank' >Portable Network Graphics</a><br>\n";
  180. $html.= "<a href='$url/rss/$name.jpg' target='_blank' >JPEG Graphics</a><br>\n";
  181. $html.= "</ul>\n<p>\n";
  182. }
  183. }
  184. $html.="</body>\n" . RSS_HTMLTail();
  185. return ("text/html; charset=utf-8", $html);
  186. }
  187. ##################
  188. sub
  189. RSS_splitRequest($) {
  190. # http://hostname:8083/fhem/rss
  191. # http://hostname:8083/fhem/rss/myDeviceName.rss
  192. # http://hostname:8083/fhem/rss/myDeviceName.jpg?t=47110.815
  193. # |--------- url ----------| |---name --| ext |--query--|
  194. my ($request) = @_;
  195. # http://hostname:8083/fhem/rss/myDeviceName.rss
  196. # http://hostname:8083/fhem/rss/myDeviceName.jpg
  197. # http://hostname:8083/fhem/rss/myDeviceName.png
  198. # http://hostname:8083/fhem/rss/myDeviceName.html
  199. use constant REGEXP => '^.*\/rss\/([^\/]*)\.(jpg|png|rss|html)(\?(.*))?$';
  200. if($request =~ REGEXP) {
  201. return ($1,$2,$4);
  202. } else {
  203. #main::Debug "not matched";
  204. return(undef,undef,undef);
  205. }
  206. }
  207. ##################
  208. sub
  209. RSS_returnRSS($) {
  210. my ($name) = @_;
  211. my $url= RSS_getURL($name);
  212. my $type = $defs{$name}{fhem}{style};
  213. my $mime = ($type eq 'png')? 'image/png' : 'image/jpeg';
  214. my $now = time();
  215. my $itemTitle = AttrVal($name, "itemtitle", "");
  216. my $titleTag = ($itemTitle ne '')? '<title>'.$itemTitle.'</title>' : '';
  217. my $code = "<rss version='2.0' xmlns:media='http://search.yahoo.com/mrss/'><channel><title>$name</title><ttl>1</ttl><item>$titleTag<media:content url='$url/rss/$name.$type' type='$mime'/><guid isPermaLink='false'>item_$now</guid></item></channel></rss>";
  218. return ("application/xml; charset=utf-8", $code);
  219. }
  220. ##################
  221. sub
  222. RSS_getScript() {
  223. my $jsTemplate = '<script type="text/javascript" src="%s"></script>';
  224. my $scripts= "";
  225. if(defined($data{FWEXT})) {
  226. foreach my $k (sort keys %{$data{FWEXT}}) {
  227. my $h = $data{FWEXT}{$k};
  228. next if($h !~ m/HASH/ || !$h->{SCRIPT});
  229. my $script = $h->{SCRIPT};
  230. $script = ($script =~ m,^/,) ? "$FW_ME$script" : "$FW_ME/pgm2/$script";
  231. $scripts .= sprintf($jsTemplate, $script) . "\n";
  232. }
  233. }
  234. return $scripts;
  235. }
  236. sub
  237. RSS_HTMLHead($$) {
  238. my ($name,$refresh) = @_;
  239. my ($width,$height)= split(/x/, AttrVal($name,"size","800x600"));
  240. my $viewportContent= AttrVal($name, "viewport", "");
  241. my $doctype= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
  242. my $xmlns= 'xmlns="http://www.w3.org/1999/xhtml"';
  243. my $scripts= RSS_getScript();
  244. my $viewport= $viewportContent eq "" ? "" : "<meta name=\"viewport\" content=\"$viewportContent\"/>\n";
  245. my $code= "$doctype\n<html $xmlns>\n<head>\n<title>$name</title>\n$viewport$scripts</head>\n";
  246. }
  247. sub
  248. RSS_HTMLTail() {
  249. return "</html>";
  250. }
  251. sub
  252. RSS_returnHTML($) {
  253. my ($name) = @_;
  254. my $url= RSS_getURL($name);
  255. my $type = $defs{$name}{fhem}{style};
  256. my $img= "$url/rss/$name.$type";
  257. my $refresh= AttrVal($name, 'refresh', 60);
  258. my $noscroll= AttrVal($name, 'noscroll', 0);
  259. my $overflow= $noscroll ? " style=\"overflow:hidden\"" : "";
  260. my $areas= AttrVal($name, 'areas', "");
  261. my $embed= $defs{$name}{".embed"};
  262. my $r= "";
  263. if(defined($refresh) && ($refresh> 0)) {
  264. my $handler= "\"setTimeout(function(){reloadImage(\'img0\')},$refresh*1000);\"";
  265. $r= " onload=$handler onerror=$handler";
  266. }
  267. my $code= RSS_HTMLHead($name, $refresh) .
  268. "<body topmargin=\"0\" leftmargin=\"0\" margin=\"0\" padding=\"0\"$overflow>\n" .
  269. "<div id=\"rss_$name\" style=\"z-index:1;\" >\n" .
  270. "<img id=\"img0\" src=\"$img\" usemap=\"#map\"$r/>\n" .
  271. "<map name=\"map\" id=\"map\">\n$areas\n</map>\n" .
  272. "</div>\n" .
  273. "$embed\n" .
  274. "</body>\n" .
  275. RSS_HTMLTail();
  276. return ("text/html; charset=utf-8", $code);
  277. }
  278. ##################
  279. # Library
  280. ##################
  281. sub
  282. RSS_xy {
  283. my ($S,$x,$y,%params)= @_;
  284. $x = $params{x} if($x eq 'x');
  285. $y = $params{y} if($y eq 'y');
  286. if((-1 < $x) && ($x < 1)) { $x*= $S->width; }
  287. if((-1 < $y) && ($y < 1)) { $y*= $S->height; }
  288. return($x,$y);
  289. }
  290. sub
  291. RSS_color {
  292. my ($S,$rgb)= @_;
  293. my $alpha = 0;
  294. my @d= split("", $rgb);
  295. if(length($rgb) == 8) {
  296. $alpha = hex("$d[6]$d[7]");
  297. $alpha = ($alpha < 127) ? $alpha : 127;
  298. }
  299. return $S->colorAllocateAlpha(hex("$d[0]$d[1]"),hex("$d[2]$d[3]"),hex("$d[4]$d[5]"),$alpha);
  300. }
  301. sub
  302. RSS_itemText {
  303. my ($S,$x,$y,$text,%params)= @_;
  304. return unless(defined($text));
  305. if($params{useTextAlign}) {
  306. my $align = GD::Text::Align->new($S,
  307. color => RSS_color($S, $params{rgb}),
  308. valign => $params{tvalign},
  309. halign => $params{thalign},
  310. );
  311. $align->set_font($params{font}, $params{pt});
  312. $align->set_text($text);
  313. $align->draw($x, $y, 0);
  314. } else {
  315. $S->stringFT(RSS_color($S,$params{rgb}),$params{font},$params{pt},0,$x,$y,$text);
  316. }
  317. }
  318. sub
  319. RSS_itemTextBox {
  320. my ($S,$x,$y,$boxwidth,$bgcolor,$text,%params)= @_;
  321. return unless(defined($text));
  322. if($params{useTextWrap}) {
  323. if((0 < $boxwidth) && ($boxwidth < 1)) { $boxwidth*= $S->width; }
  324. my $wrapbox = GD::Text::Wrap->new($S,
  325. color => RSS_color($S, $params{rgb}),
  326. line_space => $params{linespace},
  327. text => $text,
  328. );
  329. $wrapbox->set_font($params{font}, $params{pt});
  330. $wrapbox->set(align => $params{thalign}, width => $boxwidth);
  331. my ($left, $top, $right, $bottom);
  332. ($left, $top, $right, $bottom) = $wrapbox->get_bounds($x,$y);
  333. $S->filledRectangle($left,$top,$right,$bottom,RSS_color($S,$bgcolor)) if(defined($bgcolor));
  334. ($left, $top, $right, $bottom) = $wrapbox->draw($x, $y);
  335. return $bottom;
  336. } else {
  337. RSS_itemText($S,$x,$y,$text,%params);
  338. return $y;
  339. }
  340. }
  341. sub
  342. RSS_itemTime {
  343. my ($S,$x,$y,%params)= @_;
  344. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  345. RSS_itemText($S,$x,$y,sprintf("%02d:%02d", $hour, $min),%params);
  346. }
  347. sub
  348. RSS_itemSeconds {
  349. my ($S,$x,$y,$format,%params)= @_;
  350. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  351. if ($format eq "colon")
  352. {
  353. RSS_itemText($S,$x,$y,sprintf(":%02d", $sec),%params);
  354. }
  355. else
  356. {
  357. RSS_itemText($S,$x,$y,sprintf("%02d", $sec),%params);
  358. }
  359. }
  360. sub
  361. RSS_itemDate {
  362. my ($S,$x,$y,%params)= @_;
  363. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  364. RSS_itemText($S,$x,$y,sprintf("%02d.%02d.%04d", $mday, $mon+1, $year+1900),%params);
  365. }
  366. sub
  367. RSS_itemImg {
  368. my ($S,$x,$y,$scale,$imgtype,$srctype,$arg,%params)= @_;
  369. return unless(defined($arg));
  370. return if($arg eq "");
  371. my $I;
  372. if($srctype eq "url" || $srctype eq "urlq") {
  373. my $data;
  374. if($srctype eq "url") {
  375. $data= GetFileFromURL($arg,3,undef,1);
  376. } else {
  377. $data= GetFileFromURLQuiet($arg,3,undef,1);
  378. }
  379. if($imgtype eq "gif") {
  380. $I= GD::Image->newFromGifData($data);
  381. } elsif($imgtype eq "png") {
  382. $I= GD::Image->newFromPngData($data);
  383. } elsif($imgtype eq "jpeg") {
  384. $I= GD::Image->newFromJpegData($data);
  385. } else {
  386. return;
  387. }
  388. } elsif($srctype eq "file") {
  389. if($imgtype eq "gif") {
  390. $I= GD::Image->newFromGif($arg);
  391. } elsif($imgtype eq "png") {
  392. $I= GD::Image->newFromPng($arg);
  393. } elsif($imgtype eq "jpeg") {
  394. $I= GD::Image->newFromJpeg($arg);
  395. } else {
  396. return;
  397. }
  398. } elsif($srctype eq "data") {
  399. if($imgtype eq "gif") {
  400. $I= GD::Image->newFromGifData($arg);
  401. } elsif($imgtype eq "png") {
  402. $I= GD::Image->newFromPngData($arg);
  403. } elsif($imgtype eq "jpeg") {
  404. $I= GD::Image->newFromJpegData($arg);
  405. } else {
  406. return;
  407. }
  408. } else {
  409. return;
  410. }
  411. eval {
  412. my ($width,$height)= $I->getBounds();
  413. if ($scale =~ s/([wh])([\d]*)/$2/) { # get the digit from width/hight to pixel entry
  414. #Debug "RSS scale $scale (1: $1 / 2: $2)contais px after Digit - width: $width / height: $height";
  415. if ($1 eq "w") {
  416. $scale=$scale/$width;
  417. } else {
  418. $scale=$scale/$height;
  419. }
  420. }
  421. my ($swidth,$sheight)= (int($scale*$width), int($scale*$height));
  422. given ($params{ihalign}) {
  423. when('center') { $x -= $swidth/2; }
  424. when('right') { $x -= $swidth; }
  425. default { } # nothing to do
  426. }
  427. given ($params{ivalign}) {
  428. when('center') { $y -= $sheight/2; }
  429. when('base') { $y -= $sheight; }
  430. when('bottom') { $y -= $sheight; }
  431. default { } # nothing to do
  432. }
  433. #Debug "RSS placing $arg ($swidth x $sheight) at ($x,$y)";
  434. $S->copyResampled($I,$x,$y,0,0,$swidth,$sheight,$width,$height);
  435. };
  436. if($@) {
  437. Log3 undef, 2, "RSS: cannot create image $srctype $imgtype '$arg': $@";
  438. }
  439. }
  440. sub
  441. RSS_itemLine {
  442. my ($S,$x1,$y1,$x2,$y2,$th,%params)= @_;
  443. $S->setThickness($th);
  444. $S->line($x1,$y1,$x2,$y2,RSS_color($S,$params{rgb}));
  445. }
  446. sub
  447. RSS_itemRect {
  448. my ($S,$x1,$y1,$x2,$y2,$filled,%params)= @_;
  449. $x2 = $x1 + $x2 if ($x2 =~ /^\+/);
  450. $y2 = $y1 + $y2 if ($y2 =~ /^\+/);
  451. if($filled) {
  452. $S->filledRectangle($x1,$y1,$x2,$y2,RSS_color($S,$params{rgb}));
  453. } else {
  454. $S->rectangle($x1,$y1,$x2,$y2,RSS_color($S,$params{rgb}));
  455. }
  456. }
  457. ##################
  458. sub
  459. RSS_evalLayout($$@) {
  460. my ($S,$name,$layout)= @_;
  461. my @layout= split("\n", $layout);
  462. my %pstack;
  463. my $pstackcount = 0;
  464. my %params;
  465. $params{font}= "Arial";
  466. $params{pt}= 12;
  467. $params{rgb}= "ffffff";
  468. $params{halign} = 'left';
  469. $params{valign} = 'base';
  470. $params{condition} = 1;
  471. # we need two pairs of align parameters
  472. # due to different default values for text and img
  473. $params{useTextAlign}= $defs{$name}{fhem}{useTextAlign};
  474. $params{useTextWrap}= $defs{$name}{fhem}{useTextWrap};
  475. $params{ihalign} = 'left';
  476. $params{ivalign} = 'top';
  477. $params{thalign} = 'left';
  478. $params{tvalign} = 'base';
  479. $params{linespace} = 0;
  480. $params{x}= 0;
  481. $params{y}= 0;
  482. $defs{$name}{".embed"}= "";
  483. my ($x,$y,$z,$x1,$y1,$x2,$y2,$scale,$bgcolor,$boxwidth,$text,$imgtype,$srctype,$arg,$format);
  484. my $cont= "";
  485. foreach my $line (@layout) {
  486. # kill trailing newline
  487. chomp $line;
  488. # kill comments and blank lines
  489. $line=~ s/\#.*$//;
  490. $line=~ s/\s+$//;
  491. $line= $cont . $line;
  492. if($line=~ s/\\$//) { $cont= $line; undef $line; }
  493. next unless($line);
  494. $cont= "";
  495. #Debug "$name: evaluating >$line<";
  496. # split line into command and definition
  497. my ($cmd, $def)= split("[ \t]+", $line, 2);
  498. #Debug "CMD= \"$cmd\", DEF= \"$def\"";
  499. # separate condition handling
  500. if($cmd eq 'condition') {
  501. $params{condition} = AnalyzePerlCommand(undef, $def);
  502. next;
  503. }
  504. next unless($params{condition});
  505. #Debug "before command $line: x= " . $params{x} . ", y= " . $params{y};
  506. eval {
  507. if($cmd eq "rgb") {
  508. $def= "\"$def\"" if(length($def) == 6 && $def =~ /[[:xdigit:]]{6}/);
  509. $params{rgb}= AnalyzePerlCommand(undef, $def);
  510. } elsif($cmd eq "font") {
  511. $params{font}= $def;
  512. } elsif($cmd eq "pt") {
  513. $def= AnalyzePerlCommand(undef, $def);
  514. if($def =~ m/^[+-]/) {
  515. $params{pt} += $def;
  516. } else {
  517. $params{pt} = $def;
  518. }
  519. $params{pt} = 6 if($params{pt} < 0);
  520. } elsif($cmd eq "moveto") {
  521. my ($tox,$toy)= split('[ \t]+', $def, 2);
  522. my ($x,$y)= RSS_xy($S, $tox,$toy,%params);
  523. $params{x} = $x;
  524. $params{y} = $y;
  525. } elsif($cmd eq "moveby") {
  526. my ($byx,$byy)= split('[ \t]+', $def, 2);
  527. my ($x,$y)= RSS_xy($S, $byx,$byy,%params);
  528. $params{x} += $x;
  529. $params{y} += $y;
  530. } elsif($cmd ~~ @cmd_halign) {
  531. my $d = AnalyzePerlCommand(undef, $def);
  532. if($d ~~ @valid_halign) {
  533. $params{ihalign}= $d unless($cmd eq "thalign");
  534. $params{thalign}= $d unless($cmd eq "ihalign");
  535. } else {
  536. Log3 $name, 2, "$name: Illegal horizontal alignment $d";
  537. }
  538. } elsif($cmd ~~ @cmd_valign) {
  539. my $d = AnalyzePerlCommand(undef, $def);
  540. if( $d ~~ @valid_valign) {
  541. $params{ivalign}= $d unless($cmd eq "tvalign");
  542. $params{tvalign}= $d unless($cmd eq "ivalign");
  543. } else {
  544. Log3 $name, 2, "$name: Illegal vertical alignment $d";
  545. }
  546. } elsif($cmd eq "linespace") {
  547. $params{linespace}= $def;
  548. } elsif($cmd eq "text") {
  549. ($x,$y,$text)= split("[ \t]+", $def, 3);
  550. ($x,$y)= RSS_xy($S, $x,$y,%params);
  551. $params{x} = $x;
  552. $params{y} = $y;
  553. my $txt= AnalyzePerlCommand(undef, $text);
  554. #Debug "$name: ($x,$y) $txt";
  555. RSS_itemText($S,$x,$y,$txt,%params);
  556. } elsif($cmd eq "textbox") {
  557. ($x,$y,$boxwidth,$text)= split("[ \t]+", $def, 4);
  558. ($x,$y)= RSS_xy($S, $x,$y,%params);
  559. my $txt= AnalyzePerlCommand(undef, $text);
  560. $y= RSS_itemTextBox($S,$x,$y,$boxwidth,undef,$txt,%params);
  561. $params{x} = $x;
  562. $params{y} = $y;
  563. } elsif($cmd eq "textboxf") {
  564. ($x,$y,$boxwidth,$bgcolor,$text)= split("[ \t]+", $def, 5);
  565. ($x,$y)= RSS_xy($S, $x,$y,%params);
  566. $bgcolor = ($bgcolor ne "") ? AnalyzePerlCommand(undef,$bgcolor) : undef;
  567. my $txt= AnalyzePerlCommand(undef, $text);
  568. $y= RSS_itemTextBox($S,$x,$y,$boxwidth,$bgcolor,$txt,%params);
  569. $params{x} = $x;
  570. $params{y} = $y;
  571. } elsif($cmd eq "line") {
  572. ($x1,$y1,$x2,$y2,$format)= split("[ \t]+", $def, 5);
  573. ($x1,$y1)= RSS_xy($S, $x1,$y1,%params);
  574. ($x2,$y2)= RSS_xy($S, $x2,$y2,%params);
  575. $format //= 1; # set format to 1 as default thickness for the line
  576. RSS_itemLine($S,$x1,$y1,$x2,$y2, $format,%params);
  577. } elsif($cmd eq "rect") {
  578. ($x1,$y1,$x2,$y2,$format)= split("[ \t]+", $def, 5);
  579. ($x1,$y1)= RSS_xy($S, $x1,$y1,%params);
  580. ($x2,$y2)= RSS_xy($S, $x2,$y2,%params);
  581. $format //= 0; # set format to 0 as default (not filled)
  582. RSS_itemRect($S,$x1,$y1,$x2,$y2, $format,%params);
  583. } elsif($cmd eq "time") {
  584. ($x,$y)= split("[ \t]+", $def, 2);
  585. ($x,$y)= RSS_xy($S, $x,$y,%params);
  586. $params{x} = $x;
  587. $params{y} = $y;
  588. RSS_itemTime($S,$x,$y,%params);
  589. } elsif($cmd eq "seconds") {
  590. ($x,$y,$format) = split("[ \+]", $def,3);
  591. ($x,$y)= RSS_xy($S, $x,$y,%params);
  592. $params{x} = $x;
  593. $params{y} = $y;
  594. RSS_itemSeconds($S,$x,$y,$format,%params);
  595. } elsif($cmd eq "date") {
  596. ($x,$y)= split("[ \t]+", $def, 2);
  597. ($x,$y)= RSS_xy($S, $x,$y,%params);
  598. $params{x} = $x;
  599. $params{y} = $y;
  600. RSS_itemDate($S,$x,$y,%params);
  601. } elsif($cmd eq "img") {
  602. ($x,$y,$scale,$imgtype,$srctype,$arg)= split("[ \t]+", $def,6);
  603. ($x,$y)= RSS_xy($S, $x,$y,%params);
  604. $params{x} = $x;
  605. $params{y} = $y;
  606. my $arg= AnalyzePerlCommand(undef, $arg);
  607. RSS_itemImg($S,$x,$y,$scale,$imgtype,$srctype,$arg,%params);
  608. } elsif($cmd eq "push") {
  609. $pstackcount++;
  610. while ( my ($key, $value) = each(%params) ) {
  611. $pstack{$pstackcount}{$key} = $value;
  612. }
  613. } elsif($cmd eq "pop") {
  614. return unless $pstackcount;
  615. while ( my ($key, $value) = each(%{$pstack{$pstackcount}}) ) {
  616. $params{$key} = $value;
  617. }
  618. delete $pstack{$pstackcount};
  619. $pstackcount--;
  620. } elsif($cmd eq "embed") {
  621. ($x,$y,$z,$format,$text,$arg) = split("[ \t]+", $def,6);
  622. ($x,$y)= RSS_xy($S, $x,$y,%params);
  623. my $arg= AnalyzePerlCommand(undef, $arg);
  624. $defs{$name}{".embed"} .= "<div id=\"$text\" style=\"position:".$format."; left:".$x."px; top:".$y."px; z-index:$z;\">\n";
  625. $defs{$name}{".embed"} .= $arg."\n";
  626. $defs{$name}{".embed"} .= "</div>\n";
  627. #main::Debug "SET EMBED=" . $defs{$name}{".embed"};
  628. } else {
  629. Log3 $name, 2, "$name: Illegal command $cmd in layout definition.";
  630. }
  631. #Debug "after command $line: x= " . $params{x} . ", y= " . $params{y};
  632. };
  633. if($@) {
  634. my $msg= "$name: Error from line \'$line\' in layout definition: $@";
  635. chomp $msg;
  636. Log3 $name, 2, $msg;
  637. }
  638. }
  639. }
  640. ##################
  641. sub
  642. RSS_returnIMG($$) {
  643. my ($name,$type)= @_;
  644. my ($width,$height)= split(/x/, AttrVal($name,"size","800x600"));
  645. #
  646. # increase counter
  647. #
  648. if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{counter})) {
  649. $defs{$name}{fhem}{counter}++;
  650. } else {
  651. $defs{$name}{fhem}{counter}= 1;
  652. }
  653. # true color
  654. GD::Image->trueColor(1);
  655. #
  656. # create the image
  657. #
  658. our $S;
  659. # let's create a blank image, we will need it in most cases.
  660. $S= GD::Image->newTrueColor($width,$height);
  661. my $bgcolor = AttrVal($name,'bgcolor','000000'); #default bg color = black
  662. $bgcolor = RSS_color($S, $bgcolor);
  663. # $S->colorAllocate(0,0,0); # other colors seem not to work (issue with GD)
  664. $S->fill(0,0,$bgcolor);
  665. # wrap to make problems with GD non-lethal
  666. eval {
  667. #
  668. # set the background
  669. #
  670. # check if background directory is set
  671. my $reason= "?"; # remember reason for undefined image
  672. my $bgdir= AttrVal($name,"bg","undef");
  673. if(defined($bgdir)){
  674. my $bgnr; # item number
  675. if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{bgnr})) {
  676. $bgnr= $defs{$name}{fhem}{bgnr};
  677. } else {
  678. $bgnr= 0;
  679. }
  680. # check if at least tmin seconds have passed
  681. my $t0= 0;
  682. my $tmin= AttrVal($name,"tmin",0);
  683. if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{t})) {
  684. $t0= $defs{$name}{fhem}{t};
  685. }
  686. my $t1= time();
  687. if($t1-$t0>= $tmin) {
  688. $defs{$name}{fhem}{t}= $t1;
  689. $bgnr++;
  690. }
  691. # detect pictures
  692. if(opendir(BGDIR, $bgdir)){
  693. my @bgfiles= grep {$_ !~ /^\./} readdir(BGDIR);
  694. #foreach my $f (@bgfiles) {
  695. # Debug sprintf("File \"%s\"\n", $f);
  696. #}
  697. closedir(BGDIR);
  698. # get item number
  699. if($#bgfiles>=0) {
  700. if($bgnr > $#bgfiles) { $bgnr= 0; }
  701. $defs{$name}{fhem}{bgnr}= $bgnr;
  702. my $bgfile= $bgdir . "/" . $bgfiles[$bgnr];
  703. my $filetype =(split(/\./,$bgfile))[-1];
  704. my $bg;
  705. $bg= newFromGif GD::Image($bgfile) if $filetype =~ m/^gif$/i;
  706. $bg= newFromJpeg GD::Image($bgfile) if $filetype =~ m/^jpe?g$/i;
  707. $bg= newFromPng GD::Image($bgfile) if $filetype =~ m/^png$/i;
  708. if(defined($bg)) {
  709. my ($bgwidth,$bgheight)= $bg->getBounds();
  710. if($bgwidth != $width or $bgheight != $height) {
  711. # we need to resize
  712. my ($w,$h);
  713. my ($u,$v)= ($bgwidth/$width, $bgheight/$height);
  714. if($u>$v) {
  715. $w= $width;
  716. $h= $bgheight/$u;
  717. } else {
  718. $h= $height;
  719. $w= $bgwidth/$v;
  720. }
  721. $S->copyResized($bg,($width-$w)/2,($height-$h)/2,0,0,$w,$h,$bgwidth,$bgheight);
  722. } else {
  723. # size is as required
  724. # kill the predefined image and take the original
  725. undef $S;
  726. $S= $bg;
  727. }
  728. } else {
  729. undef $S;
  730. $reason= "Something was wrong with background image \"$bgfile\".";
  731. }
  732. }
  733. }
  734. }
  735. #
  736. # evaluate layout
  737. #
  738. if(defined($S)) {
  739. RSS_evalLayout($S, $name, $defs{$name}{fhem}{layout});
  740. } else {
  741. Log3 $name, 2, "$name: Could not create image. $reason";
  742. $S= GD::Image->newTrueColor($width,$height); # return empty image
  743. }
  744. $defs{$name}{STATE} = localtime();
  745. }; #warn $@ if $@;
  746. if($@) {
  747. my $msg= $@;
  748. chomp $msg;
  749. Log3 $name, 2, $msg;
  750. }
  751. #
  752. # return image
  753. #
  754. return ("image/jpeg; charset=utf-8", $S->jpeg) if($type eq 'jpg');
  755. return ("image/png; charset=utf-8", $S->png) if($type eq 'png');
  756. }
  757. ##################
  758. #
  759. # here we answer any request to http://host:port/fhem/rss and below
  760. sub
  761. RSS_CGI(){
  762. my ($request) = @_; # /rss or /rss/name.rss or /rss/name.jpg or /rss/name.png
  763. my ($name,$ext,$query)= RSS_splitRequest($request); # name, ext (rss, jpg, png, html), query
  764. # query is unused
  765. #main::Debug "Request: $request";
  766. #main::Debug " Name : $name";
  767. #main::Debug " Ext : $ext";
  768. #main::Debug " Query : $query";
  769. if(defined($name)) {
  770. if($ext eq "") {
  771. return("text/plain; charset=utf-8", "Illegal extension.");
  772. }
  773. if(!defined($defs{$name})) {
  774. return("text/plain; charset=utf-8", "Unknown RSS device: $name");
  775. }
  776. if($ext eq "jpg") {
  777. return RSS_returnIMG($name,'jpg');
  778. } elsif($ext eq "png") {
  779. return RSS_returnIMG($name,'png');
  780. } elsif($ext eq "rss") {
  781. return RSS_returnRSS($name);
  782. } elsif($ext eq "html") {
  783. return RSS_returnHTML($name);
  784. }
  785. } else {
  786. return RSS_Overview();
  787. }
  788. }
  789. sub
  790. plotFromUrl(@)
  791. {
  792. my (@plotName) = @_;
  793. my @webs;
  794. @webs=devspec2array("TYPE=FHEMWEB");
  795. foreach(@webs) {
  796. if(!InternalVal($_,'TEMPORARY',undef)) {
  797. $FW_wname=InternalVal($_,'NAME','');
  798. last;
  799. }
  800. }
  801. my ($w, $h) = split(",", AttrVal($plotName[0],"plotsize","800,160"));
  802. my $url;
  803. $url = "<embed src=\"".
  804. "$FW_ME/SVG_showLog?dev=". $plotName[0].
  805. "&amp;logdev=". InternalVal($plotName[0], "LOGDEVICE", "").
  806. "&amp;gplotfile=". InternalVal($plotName[0], "GPLOTFILE", "").
  807. "&amp;logfile=". InternalVal($plotName[0], "LOGFILE", "CURRENT");
  808. $url .= "&amp;pos=". $plotName[1] if $plotName[1];
  809. $url .= "&amp;zoom=". $plotName[2] if $plotName[2];
  810. $url .= "\"";
  811. $url .= " type=\"image/svg+xml\"";
  812. $url .= " width=\"$w\"";
  813. $url .= " height=\"$h\"";
  814. $url .= " id=\"".$plotName[0]."\" >";
  815. return $url;
  816. }
  817. #
  818. 1;
  819. =pod
  820. =item helper
  821. =item summary Provides a freely configurable RSS feed and HTML page.
  822. =item summary_DE Stellt frei konfigurierbaren RSS-Feed und HTML-Seite bereit.
  823. =begin html
  824. <a name="RSS"></a>
  825. <h3>RSS</h3>
  826. <ul>
  827. Provides a freely configurable RSS feed and HTML page.<p>
  828. The media RSS feed delivers status pictures either in JPEG or PNG format.
  829. This media RSS feed can be used to feed a status display to a
  830. network-enabled photo frame.<p>
  831. In addition, a periodically refreshing HTML page is generated that shows the picture
  832. with an optional HTML image map.<p>
  833. You need to have the perl module <code>GD</code> installed. This module is most likely not
  834. available for small systems like Fritz!Box.<p>
  835. RSS is an extension to <a href="#FHEMWEB">FHEMWEB</a>. You must install FHEMWEB to use RSS.<p>
  836. Beginners might find the <a href="http://forum.fhem.de/index.php/topic,22520.0.html">RSS Workshop</a> useful.<p>
  837. A note on colors: Colors are specified as 6- or 8-digit hex numbers,
  838. every 2 digits determining the red, green and blue color components as in HTML
  839. color codes. The optional 7th and 8th digit code the alpha channel (transparency from
  840. 00 to 7F). Examples: <code>FF0000</code> for red, <code>C0C0C0</code> for light
  841. gray, <code>1C1C1C40</code> for semi-transparent gray.<p>
  842. <a name="RSSdefine"></a>
  843. <b>Define</b>
  844. <ul>
  845. <code>define &lt;name&gt; RSS jpg|png &lt;hostname&gt; &lt;filename&gt;</code><br><br>
  846. Defines the RSS feed. <code>jpg</code> and <code>png</code> are fixed literals to select output format.
  847. <code>&lt;hostname&gt;</code> is the hostname of the fhem server as
  848. seen from the consumer of the RSS feed. <code>&lt;filename&gt;</code> is the
  849. name of the file that contains the <a href="RSSlayout">layout definition</a>.<p>
  850. Examples
  851. <ul>
  852. <code>define FrameRSS RSS jpg host.example.org /etc/fhem/layout</code><br>
  853. <code>define MyRSS RSS png 192.168.1.222 /var/fhem/conf/layout.txt</code><br>
  854. </ul>
  855. <br>
  856. The RSS feeds are at
  857. <ul>
  858. <code>http://host.example.org:8083/fhem/rss/FrameRSS.rss</code><br>
  859. <code>http://192.168.1.222:8083/fhem/rss/MyRSS.rss</code><br>
  860. </ul>
  861. <br>
  862. The pictures are at
  863. <ul>
  864. <code>http://host.example.org:8083/fhem/rss/FrameRSS.jpg</code><br>
  865. <code>http://192.168.1.222:8083/fhem/rss/MyRSS.png</code><br>
  866. </ul>
  867. <br>
  868. The HTML pages are at
  869. <ul>
  870. <code>http://host.example.org:8083/fhem/rss/FrameRSS.html</code><br>
  871. <code>http://192.168.1.222:8083/fhem/rss/MyRSS.html</code><br>
  872. </ul>
  873. <br>
  874. </ul>
  875. <a name="RSSset"></a>
  876. <b>Set</b>
  877. <ul>
  878. <code>set &lt;name&gt; rereadcfg</code>
  879. <br><br>
  880. Rereads the <a href="RSSlayout">layout definition</a> from the file. Useful to enable
  881. changes in the layout on-the-fly.
  882. <br><br>
  883. </ul>
  884. <a name="RSSattr"></a>
  885. <b>Attributes</b>
  886. <br><br>
  887. <ul>
  888. <li>autoreread<br>If set to 1, layout is automatically reread when layout file has been changed
  889. by FHEMWEB file editor.</li><br>
  890. <li>size<br>The dimensions of the picture in the format
  891. <code>&lt;width&gt;x&lt;height&gt;</code>.</li><br>
  892. <li>bg<br>The directory that contains the background pictures (must be in JPEG, GIF or PNG format, file
  893. format is guessed from file name extension).</li><br>
  894. <li>bgcolor &lt;color&gt;<br>Sets the background color. </li><br>
  895. <li>tmin<br>The background picture is shown at least <code>tmin</code> seconds,
  896. no matter how frequently the RSS feed consumer accesses the page.</li><br>
  897. <li>refresh &lt;interval&gt;<br>Time in seconds after which the HTML page is automatically reloaded. Defaults to 60.
  898. Set &lt;interval&gt; to 0 to disable reloading.</li><br>
  899. <li>viewport<br>Adds a viewport directive to the HTML header.<br>
  900. Example: <code>attr FrameRSS viewport width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0</code><br>
  901. This scales the image to fit to the browser's viewport and inhibits zooming in or out on tablets.
  902. </li><br>
  903. <li>noscroll</br>Suppresses scrollbars in browsers, if set to 1.
  904. </li><br>
  905. <li>areas<br>HTML code that goes into the image map.<br>
  906. Example: <code>attr FrameRSS areas &lt;area shape="rect" coords="0,0,200,200" href="http://fhem.de"/&gt;&lt;area shape="rect" coords="600,400,799,599" href="http://has:8083/fhem" target="_top"/&gt;</code>
  907. </li><br>
  908. <li>itemtitle</br>Adds a title tag to the RSS item that contains the specified text.
  909. </li><br>
  910. <li>urlOverride<br>Overrides the URL in the generated feed.
  911. If you specify
  912. <code>attr &lt;name&gt; http://otherhost.example.org:8083/foo/bar</code>, the
  913. JPEG picture that is at
  914. <code>http://host.example.org:8083/fhem/rss/FrameRSS.jpg</code>
  915. will be referenced as
  916. <code>http://otherhost.example.org:8083/foo/bar/rss/FrameRSS.jpg</code>. This is useful when
  917. your FHEM URLs are rewritten, e.g. if FHEM is accessed by a Reverse Proxy.</li>
  918. <br>
  919. </ul>
  920. <br><br>
  921. <b>Usage information</b>
  922. <br><br>
  923. <ul>
  924. If a least one RSS feed is defined, the menu entry <code>RSS</code> appears in the FHEMWEB
  925. side menu. If you click it you get a list of all defined RSS feeds. The URL of any such is
  926. RSS feed is <code>http://hostname:port/fhem/rss/name.rss</code> with <code>hostname</code> and
  927. <code>name</code> from the RSS feed's <a href="RSSdefine">definition</a> and the <code>port</code>
  928. (usually 8083) and literal <code>/fhem</code> from the underlying <a href="#FHEMWEB">FHEMWEB</a>
  929. definition.<p>
  930. Example:
  931. <ul><code>http://host.example.org:8083/fhem/rss/FrameRSS.rss</code></ul><p>
  932. The media RSS feed points to a dynamically generated picture. The URL of the picture
  933. belonging to the RSS can be found by replacing the extension ".rss" in feed's URL by ".jpg" or ".png"
  934. depending on defined output format,<p>
  935. Example:
  936. <ul><code>http://host.example.org:8083/fhem/rss/FrameRSS.jpg</code></ul><p>
  937. <ul><code>http://192.168.100.200:8083/fhem/rss/FrameRSS.png</code></ul><p>
  938. To render the picture the current, or, if <code>tmin</code> seconds have elapsed, the next
  939. JPEG picture from the directory <code>bg</code> is chosen and scaled to the dimensions given
  940. in <code>size</code>. The background is black if no usable JPEG picture can be found. Next the
  941. script in the <a href="RSSlayout">layout definition</a> is used to superimpose items on
  942. the background.<p>
  943. You can directly access the URL of the picture in your browser. Reload the page to see
  944. how it works.<p>
  945. The media RSS feed advertises to refresh after 1 minute (ttl). Some photo frames ignore it and
  946. use their preset refresh rate. Go for a photo frame with an adjustable refresh rate (e.g
  947. every 5 seconds) if you have the choice!<p>
  948. This is how the fhem config part might look like:<p>
  949. <code>
  950. define ui FHEMWEB 8083 global<br><br>
  951. define FrameRSS RSS jpg host.example.org /etc/fhem/layout<br>
  952. attr FrameRSS size 800x600<br>
  953. attr FrameRSS bg /usr/share/pictures<br>
  954. attr FrameRSS tmin 10<br>
  955. </code>
  956. </ul>
  957. <a name="RSSlayout"></a>
  958. <b>Layout definition</b>
  959. <br><br>
  960. <ul>
  961. The layout definition is a script for placing items on the background. It is read top-down.
  962. It consists of layout control commands and items placement commands. Layout control
  963. commands define the appearance of subsequent items. Item placement commands actually
  964. render items.<p>
  965. Everything after a # is treated as a comment and ignored. You can fold long lines by
  966. putting a \ at the end.<p>
  967. <i>General notes</i><br>
  968. <ol>
  969. <li>Use double quotes to quote literal text if perl specials are allowed.</li>
  970. <li>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 <code>apt-get install libgd-text-perl</code>.</li>
  971. </ol>
  972. <p>
  973. <i>Notes on coordinates</i><br>
  974. <ol>
  975. <li>(0,0) is the upper left corner.</li>
  976. <li>Coordinates equal or greater than 1 are considered to be absolute pixels, coordinates between 0 and 1 are considered to
  977. be relative to the total width or height of the picture.</li>
  978. <li>Literal <code>x</code> and <code>y</code> evaluate to the most recently used x- and y-coordinate. See also moveto and moveby below.</li>
  979. <!--<li>You can use <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> for x and for y.</li>-->
  980. </ol>
  981. <p>
  982. <i>Layout control commands</i><p>
  983. <ul>
  984. <li>moveto &lt;x&gt; &lt;y&gt;<br>Moves most recently used x- and y-coordinate to the given absolute or relative position.</li><br>
  985. <li>moveby &lt;x&gt; &lt;y&gt;<br>Moves most recently used x- and y-coordinate by the given absolute or relative amounts.</li><br>
  986. <li>font "&lt;font&gt;"<br>Sets the font. &lt;font&gt; is the name of a TrueType font (e.g.
  987. <code>Arial</code>) or the full path to a TrueType font
  988. (e.g. <code>/usr/share/fonts/truetype/arial.ttf</code>),
  989. whatever works on your system.</li><br>
  990. <a name="rss_rgb"></a>
  991. <li>rgb "&lt;color&gt;"<br>Sets the color. You can use
  992. <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> for &lt;color&gt.</li><br>
  993. <li>pt &lt;pt&gt;<br>Sets the font size in points. A + or - sign in front of the the number given
  994. for &lt;pt&gt signifies a change of the font size relative to the current size. Otherwise the absolute
  995. size is set. You can use
  996. <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> for &lt;pt&gt.</li><br>
  997. <li>thalign|ihalign|halign "left"|"center"|"right"<br>Sets the horizontal alignment of text, image or both. Defaults to left-aligned. You can use
  998. <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> instead of the literal alignment control word.</li><br>
  999. <li>tvalign|ivalign|valign "top"|"center"|"base"|"bottom"<br>Sets the vertical alignment of text, image or both. Defaults to base-aligned for text and
  1000. top-aligned for image. You can use
  1001. <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> instead of the literal alignment control word.</li><br>
  1002. <li>linespace &lt;space&gt;<br>Sets the line spacing in pixels for text boxes (see textbox item below).</li><br>
  1003. <li>condition &lt;condition&gt;<br>Subsequent layout control and item placement commands except for another condition command are ignored if and only if &lt;condition&gt; evaluates to false.</li><br>
  1004. <li>push<br>The current parameter set (position, color, font name and size, text alignment and line spacing) is
  1005. put (saved) on top of a stack.</li><br>
  1006. <li>pop<br>The most recently saved (pushed) current parameter set is pulled from the top of the stack and restored.</li><br>
  1007. </ul>
  1008. <i>Item placement commands</i><p>
  1009. <ul>
  1010. <li>text &lt;x&gt; &lt;y&gt; &lt;text&gt;<br>Renders the text &lt;text&gt; at the
  1011. position (&lt;x&gt;, &lt;y&gt;) using the current font, font size and color.
  1012. You can use
  1013. <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> for &lt;text&gt; to fully
  1014. access device readings and do some programming on the fly. See below for examples.</li><br>
  1015. <li>textbox &lt;x&gt; &lt;y&gt; &lt;boxwidth&gt; &lt;text&gt;<br>Same as before but text is rendered
  1016. in a box of horizontal width &lt;boxwidth&gt;.</li><br>
  1017. <li>textboxf &lt;x&gt; &lt;y&gt; &lt;boxwidth&gt; &lt;bgcolor&gt; &lt;text&gt;<br>Same as before but
  1018. the textbox will be filled with the given background color &lt;bgcolor&gt; before drawing the text.
  1019. &lt;bgcolor&gt; can be used with <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> to evalute <a href="#rss_rgb">RGB</a> value.</li><br>
  1020. <li>time &lt;x&gt; &lt;y&gt;<br>Renders the current time in HH:MM format.</li><br>
  1021. <li>seconds &lt;x&gt; &lt;y&gt; &lt;format&gt<br>Renders the curent seconds. Maybe useful for a RSS Clock.</li><br>
  1022. <li>date &lt;x&gt; &lt;y&gt;<br>Renders the current date in DD.MM.YYYY format.</li><br>
  1023. <li>line &lt;x1&gt; &lt;y1&gt; &lt;x2&gt; &lt;y2&gt; [&lt;thickness&gt;]<br>Draws a line from position (&lt;x1&gt;, &lt;y1&gt;) to position (&lt;x2&gt;, &lt;y2&gt;) with optional thickness (default=1).</li><br>
  1024. <li>rect &lt;x1&gt; &lt;y1&gt; &lt;x2&gt; &lt;y2&gt; [&lt;filled&gt;]<br>Draws a rectangle with corners at positions (&lt;x1&gt;, &lt;y1&gt;) and (&lt;x2&gt;, &lt;y2&gt;), which is filled if the &lt;filled&gt; parameter is set and not zero.<br>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.</li><br>
  1025. <li>img &lt;x&gt; &lt;y&gt; &lt;['w' or 'h']s&gt; &lt;imgtype&gt; &lt;srctype&gt; &lt;arg&gt; <br>Renders a picture at the
  1026. position (&lt;x&gt;, &lt;y&gt;). The &lt;imgtype&gt; is one of <code>gif</code>, <code>jpeg</code>, <code>png</code>.
  1027. The picture is scaled by the factor &lt;s&gt; (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 &lt;srctype&gt; is <code>file</code>, the picture
  1028. is loaded from the filename &lt;arg&gt;, if &lt;srctype&gt; is <code>url</code> or <code>urlq</code>, the picture
  1029. is loaded from the URL &lt;arg&gt; (with or without logging the URL), if &lt;srctype&gt; is <code>data</code>, the picture
  1030. is piped in from data &lt;arg&gt;. You can use
  1031. <code>{ <a href="#perl">&lt;perl special&gt;</a> }</code> for &lt;arg&gt. See below for example.<br>
  1032. <b>Warning</b>: do not load the image from URL that is served by fhem as it leads to a deadlock.</li><br>
  1033. <li>embed &lt;x&gt; &lt;y&gt; &lt;z&gt; &lt;position&gt; &lt;id&gt; &lt;element&gt;<br>
  1034. For HTML output: embeds a <code>div</code> element into the HTML page at (&lt;x&gt;,&lt;y&gt;) with z-order &lt;z&gt; and positioning &lt;position&gt; (use <code>absolute</code>). &lt;id&gt; is the <code>id</code> attribute of the
  1035. <code>div</code> element and &lt;element&gt; is its content.<br>
  1036. <b>Note:</b> There are several issues with different browsers when using this.</li><br>
  1037. </ul>
  1038. <i>Example</i><p>
  1039. This is how a layout definition might look like:<p>
  1040. <code>
  1041. font /usr/share/fonts/truetype/arial.ttf # must be a TrueType font<br>
  1042. rgb "c0c0c0" # HTML color notation, RGB<br>
  1043. pt 48 # font size in points<br>
  1044. time 0.10 0.90<br>
  1045. pt 24<br>
  1046. text 0.10 0.95 { ReadingsVal("MyWeather","temperature","?"). "C" }<br>
  1047. moveby 0 -25<br>
  1048. text x y "Another text"<br>
  1049. img 20 530 0.5 png file { "/usr/share/fhem/www/images/weather/" . ReadingsVal("MyWeather","icon","") . ".png" }<br>
  1050. embed 0 0 2 absolute plot1 { plotFromUrl('mySVG') }
  1051. embed 10 200 2 absolute iframe1 "&lt;iframe width=\"420\" height=\"315\" src=\"//www.youtube.com/embed/9HShl_ufOFI\" frameborder=\"0\" allowfullscreen&gt;&lt;/iframe&gt;"
  1052. </code>
  1053. <p>
  1054. <i>Special uses</i><p>
  1055. You can display <a href="#SVG">SVG</a> plots with the aid of the helper function <code>plotAsPng(&lt;name&gt;[,&lt;zoom&gt;[,&lt;offset&gt;]])</code> (in 98_SVG.pm). Examples:<p>
  1056. <code>
  1057. img 20 30 0.6 png data { plotAsPng("mySVGPlot") }<BR>
  1058. img 20 30 0.6 png data { plotAsPng("mySVGPlot","qday",-1) }
  1059. </code>
  1060. <p>
  1061. This requires the perl module Image::LibRSVG and librsvg. Debian-based systems can install these with <code>apt-get install libimage-librsvg-perl</code>.<p>
  1062. For HTML output, you can use <code>plotFromURL(&lt;name&gt;[,&lt;zoom&gt;[,&lt;offset&gt;]])</code> instead.
  1063. </ul>
  1064. </ul>
  1065. =end html
  1066. =cut