59_PROPLANTA.pm 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. ####################################################################################################
  2. # $Id: 59_PROPLANTA.pm 11752 2016-07-06 16:27:06Z grompo $
  3. #
  4. # 59_PROPLANTA.pm
  5. #
  6. # (c) 2014 Torsten Poitzsch
  7. # (c) 2014-2016 tupol http://forum.fhem.de/index.php?action=profile;u=5432
  8. #
  9. # Weather forecast values for 12 days are captured from www.proplanta.de
  10. # inspired by 23_KOSTALPIKO.pm
  11. #
  12. # Copyright notice
  13. #
  14. # This script is free software; you can redistribute it and/or modify
  15. # it under the terms of the GNU General Public License as published by
  16. # the Free Software Foundation; either version 2 of the License, or
  17. # (at your option) any later version.
  18. #
  19. # The GNU General Public License can be found at
  20. # http://www.gnu.org/copyleft/gpl.html.
  21. # A copy is found in the text file GPL.txt and important notices to the license
  22. # from the author is found in LICENSE.txt distributed with these scripts.
  23. #
  24. # This script is distributed in the hope that it will be useful,
  25. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. # GNU General Public License for more details.
  28. #
  29. # This copyright notice MUST APPEAR in all copies of the script!
  30. #
  31. ####################################################################################################
  32. ###############################################
  33. # parser for the weather data
  34. package MyProplantaParser;
  35. use base qw(HTML::Parser);
  36. use Time::HiRes qw(usleep);
  37. our @texte = ();
  38. my $lookupTag = "span|b";
  39. my $curTag = "";
  40. my $curReadingName = "";
  41. my $curRowID = "";
  42. my $curCol = 0;
  43. our $startDay = 0;
  44. my $curTextPos = 0;
  45. my $curReadingType = 0;
  46. # 1 = span|b Text, 2 = readingName, 3 = Tag-Type
  47. # Tag-Types:
  48. # 1 = Number Col 3
  49. # 2 = Number Col 2-5
  50. # 3 = Number Col 2|4|6|8
  51. # 4 = Intensity-Text Col 2-5
  52. # 5 = Time Col 2-5
  53. # 6 = Time Col 3
  54. # 7 = alternative text of image Col 2-5 (weather state)
  55. # 8 = MinMaxNummer Col 3
  56. # 9 = Date Col 2-5 / bold
  57. # 10 = alternative text of Col 3 (Wind direction)
  58. # 11 = alternative text of image Col 3 (current weather state)
  59. my @knownNoneIDs = ( ["Temperatur", "temperature", 1]
  60. ,["relative Feuchte", "humidity", 1]
  61. ,["Sichtweite", "visibility", 1]
  62. ,["Wetterzustand", "weather", 11]
  63. ,["Windrichtung", "windDir", 10]
  64. ,["Windgeschwindigkeit", "wind", 1]
  65. ,["Luftdruck", "pressure", 1]
  66. ,["Taupunkt", "dewPoint", 1]
  67. ,["Uhrzeit", "obsTime", 6]
  68. ,["Höhe der", "cloudBase", 8]
  69. );
  70. # 1 = Tag-ID, 2 = readingName, 3 = Tag-Type (see above)
  71. my @knownIDs = (
  72. ["DATUM", "date", 9]
  73. ,["TMAX", "tempMax", 2]
  74. ,["TMIN", "tempMin", 2]
  75. ,["NW", "chOfRainDay", 2]
  76. ,["NW_Nacht", "chOfRainNight", 2]
  77. ,["BF", "frost", 4]
  78. ,["VERDUNST", "evapor", 4]
  79. ,["TAUBILDUNG", "dew", 4]
  80. ,["SD", "sun", 2]
  81. ,["UV", "uv", 2]
  82. ,["GS", "rad", 3]
  83. ,["WETTER_ID", "weather", 7]
  84. ,["WETTER_ID_MORGENS", "weatherMorning", 7]
  85. ,["WETTER_ID_TAGSUEBER", "weatherDay", 7]
  86. ,["WETTER_ID_ABENDS", "weatherEvening", 7]
  87. ,["WETTER_ID_NACHT", "weatherNight", 7]
  88. ,["T_0", "temp00", 2]
  89. ,["T_3", "temp03", 2]
  90. ,["T_6", "temp06", 2]
  91. ,["T_9", "temp09", 2]
  92. ,["T_12", "temp12", 2]
  93. ,["T_15", "temp15", 2]
  94. ,["T_18", "temp18", 2]
  95. ,["T_21", "temp21", 2]
  96. ,["NW_0", "chOfRain00", 2]
  97. ,["NW_3", "chOfRain03", 2]
  98. ,["NW_6", "chOfRain06", 2]
  99. ,["NW_9", "chOfRain09", 2]
  100. ,["NW_12", "chOfRain12", 2]
  101. ,["NW_15", "chOfRain15", 2]
  102. ,["NW_18", "chOfRain18", 2]
  103. ,["NW_21", "chOfRain21", 2]
  104. ,["NS_0", "rain00", 2]
  105. ,["NS_3", "rain03", 2]
  106. ,["NS_6", "rain06", 2]
  107. ,["NS_9", "rain09", 2]
  108. ,["NS_12", "rain12", 2]
  109. ,["NS_15", "rain15", 2]
  110. ,["NS_18", "rain18", 2]
  111. ,["NS_21", "rain21", 2]
  112. ,["NS_24H", "rain", 2]
  113. ,["BD_0", "cloud00", 2]
  114. ,["BD_3", "cloud03", 2]
  115. ,["BD_6", "cloud06", 2]
  116. ,["BD_9", "cloud09", 2]
  117. ,["BD_12", "cloud12", 2]
  118. ,["BD_15", "cloud15", 2]
  119. ,["BD_18", "cloud18", 2]
  120. ,["BD_21", "cloud21", 2]
  121. ,["MA", "moonRise", 5]
  122. ,["MU", "moonSet", 5]
  123. );
  124. my %intensity = ( "keine" => 0
  125. ,"nein" => 0
  126. ,"gering" => 1
  127. ,"leicht" => 1
  128. ,"ja" => 1
  129. ,"mäßig" => 2
  130. ,"stark" => 3
  131. );
  132. my %winddir = ( "Nord" => 0
  133. ,"Nord-Nordost" => 23
  134. ,"Nordost" => 45
  135. ,"Ost-Nordost" => 68
  136. ,"Ost" => 90
  137. ,"Ost-Südost" => 113
  138. ,"Südost" => 135
  139. ,"Süd-Südost" => 158
  140. ,"Süd" => 180
  141. ,"Süd-Südwest" => 203
  142. ,"Südwest" => 225
  143. ,"West-Südwest" => 248
  144. ,"West" => 270
  145. ,"West-Nordwest" => 203
  146. ,"Nordwest" => 225
  147. ,"Nord-Nordwest" => 248
  148. );
  149. ##############################################
  150. sub get_wday($)
  151. {
  152. my ($date) = @_;
  153. my @wday_txt = qw(So Mo Di Mi Do Fr Sa);
  154. my @th=localtime $date;
  155. return $wday_txt [$th[6]];
  156. }
  157. ##############################################
  158. # here HTML::text/start/end are overridden
  159. sub text
  160. {
  161. my ( $self, $text ) = @_;
  162. my $found = 0;
  163. my $readingName;
  164. # Wait 1ms to reduce CPU load and hence blocking of FHEM by it (workaround until a better solution is available)
  165. usleep (1000);
  166. if ( $curTag =~ $lookupTag ) {
  167. $curTextPos++;
  168. $text =~ s/^\s+//; # trim string
  169. $text =~ s/\s+$//;
  170. $text =~ s/0/0/g; # replace 0
  171. $text =~ s/1/1/g; # replace 1
  172. $text =~ s/2/2/g; # replace 2
  173. $text =~ s/3/3/g; # replace 3
  174. $text =~ s/4/4/g; # replace 4
  175. $text =~ s/5/5/g; # replace 5
  176. $text =~ s/6/6/g; # replace 6
  177. $text =~ s/7/7/g; # replace 7
  178. $text =~ s/8/8/g; # replace 8
  179. $text =~ s/9/9/g; # replace 9
  180. # Tag-Type 0 = Check for readings without tag-ID (current readings)
  181. if ($curReadingType == 0) {
  182. if ($startDay == 0 && $curCol == 1 && $curTextPos == 1)
  183. {
  184. foreach my $r (@knownNoneIDs)
  185. {
  186. if ( $$r[0] eq $text )
  187. {
  188. $curReadingName = $$r[1];
  189. $curReadingType = $$r[2];
  190. last;
  191. }
  192. }
  193. }
  194. }
  195. # Tag-Type 1 = Number Col 3
  196. elsif ($curReadingType == 1) {
  197. if ( $curCol == 3 )
  198. {
  199. $readingName = $curReadingName;
  200. if ( $text =~ m/([-,\+]?\d+[,\.]?\d*)/ )
  201. {
  202. $text = $1;
  203. $text =~ tr/,/./; # komma durch punkt ersetzen
  204. }
  205. push( @texte, $readingName."|".$text );
  206. $curReadingType = 0;
  207. }
  208. }
  209. # Tag-Type 2 = Number Col 2-5
  210. elsif ($curReadingType == 2) {
  211. if ( 1 < $curCol && $curCol <= 5 )
  212. {
  213. $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName;
  214. if ( $text =~ m/([-+]?\d+[,.]?\d*)/ )
  215. {
  216. $text = $1;
  217. $text =~ tr/,/./; # komma durch punkt ersetzen
  218. }
  219. push( @texte, $readingName."|".$text );
  220. }
  221. }
  222. # Tag-Type 3 = Number Col 2|4|6|8
  223. elsif ($curReadingType == 3) {
  224. if ( 2 <= $curCol && $curCol <= 5 )
  225. {
  226. if ( $curTextPos % 2 == 1 )
  227. {
  228. $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName;
  229. $text =~ tr/,/./; # komma durch punkt ersetzen
  230. push( @texte, $readingName."|".$text );
  231. }
  232. }
  233. }
  234. # Tag-Type 4 = Intensity-Text Col 2-5
  235. elsif ($curReadingType == 4) {
  236. if ( 2 <= $curCol && $curCol <= 5 )
  237. {
  238. $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName;
  239. $text = $intensity{$text} if defined $intensity{$text};
  240. push( @texte, $readingName . "|" . $text );
  241. }
  242. }
  243. # Tag-Type 5 = Time Col 2-5
  244. elsif ($curReadingType == 5) {
  245. if ( 2 <= $curCol && $curCol <= 5 )
  246. {
  247. $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName;
  248. if ( $text =~ m/([012-]?[-0-9][.:][-0-5][-0-9])/ )
  249. {
  250. $text = $1;
  251. $text =~ tr/./:/; # Punkt durch Doppelpunkt ersetzen
  252. }
  253. push( @texte, $readingName."|".$text );
  254. }
  255. }
  256. # Tag-Type 6 = Time Col 3
  257. elsif ($curReadingType == 6) {
  258. if ( $curCol == 3 )
  259. {
  260. $readingName = $curReadingName;
  261. if ( $text =~ m/([012-]?[-0-9][.:][-0-5][-0-9])/ )
  262. {
  263. $text = $1;
  264. $text =~ tr/./:/; # Punkt durch Doppelpunkt ersetzen
  265. }
  266. push( @texte, $readingName."|".$text );
  267. }
  268. }
  269. # Tag-Type 8 = MinMaxNumber Col 3
  270. elsif ($curReadingType == 8) {
  271. if ( $curCol == 3 )
  272. {
  273. $readingName = $curReadingName;
  274. if ( $text =~ m/(\d+)\s*-\s*(\d+)/ )
  275. {
  276. push( @texte, $readingName."Min|".$1 );
  277. push( @texte, $readingName."Max|".$2 );
  278. }
  279. else
  280. {
  281. push( @texte, $readingName."Min|-" );
  282. push( @texte, $readingName."Max|-" );
  283. }
  284. }
  285. }
  286. # Tag-Type 9 = Date Col 2-5
  287. elsif ($curReadingType == 9 && $curTag eq "b") {
  288. if ( 1 < $curCol && $curCol <= 5 ) {
  289. $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName;
  290. push( @texte, $readingName."|".$text );
  291. }
  292. }
  293. }
  294. }
  295. ##############################################
  296. sub start
  297. {
  298. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  299. $curTag = $tagname;
  300. if ( $tagname eq "tr" ) {
  301. $curReadingType = 0;
  302. $curCol = 0;
  303. $curTextPos = 0;
  304. if ( defined( $attr->{id} ) ) {
  305. foreach my $r (@knownIDs) {
  306. if ( $$r[0] eq $attr->{id} ) {
  307. $curReadingName = $$r[1];
  308. $curReadingType = $$r[2];
  309. last;
  310. }
  311. }
  312. }
  313. }
  314. elsif ($tagname eq "td") {
  315. $curCol++;
  316. $curTextPos = 0;
  317. }
  318. #wetterstate and icon - process immediately
  319. elsif ($tagname eq "img"
  320. && ( $curReadingType == 7 && 2 <= $curCol && $curCol <= 5
  321. || $curReadingType == 11 && $curCol == 3) ) {
  322. # process alternative text
  323. $readingName = $curReadingName;
  324. $readingName = "fc" . ($startDay+$curCol-2) . "_" . $readingName if $curReadingType == 7;
  325. $text = $attr->{alt};
  326. $text =~ s/Wetterzustand: //;
  327. $text =~ s/ö/oe/;
  328. $text =~ s/ä/ae/;
  329. $text =~ s/ü/ue/;
  330. $text =~ s/ß/ss/;
  331. push( @texte, $readingName . "|" . $text );
  332. # Image URL
  333. push( @texte, $readingName."Icon" . "|" . $attr->{src} );
  334. }
  335. #wind direction - process immediately
  336. elsif ($tagname eq "img" && $curReadingType == 10 && $curCol == 3 ) {
  337. # process alternative text
  338. $readingName = $curReadingName;
  339. $text = $attr->{alt};
  340. $text =~ s/Windrichtung: //;
  341. $text = $winddir{$text} if defined $winddir{$text};
  342. # $text =~ s/ö/oe/;
  343. # $text =~ s/ä/ae/;
  344. # $text =~ s/ü/ue/;
  345. # $text =~ s/ß/ss/;
  346. push( @texte, $readingName . "|" . $text );
  347. # Image URL
  348. push( @texte, $readingName."Icon" . "|" . $attr->{src} );
  349. }
  350. }
  351. ##############################################
  352. sub end
  353. {
  354. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  355. $curTag = "";
  356. if ( $tagname eq "tr" ) {
  357. $curReadingType = 0
  358. };
  359. }
  360. ##############################################
  361. package main;
  362. use strict;
  363. use feature qw/say switch/;
  364. use warnings;
  365. my $missingModul;
  366. eval "use LWP::UserAgent;1" or $missingModul .= "LWP::UserAgent ";
  367. eval "use HTTP::Request;1" or $missingModul .= "HTTP::Request ";
  368. eval "use HTML::Parser;1" or $missingModul .= "HTML::Parser ";
  369. eval "use MIME::Base64;1" or $missingModul .= "MIME::Base64 ";
  370. require 'Blocking.pm';
  371. require 'HttpUtils.pm';
  372. use vars qw($readingFnAttributes);
  373. use vars qw(%defs);
  374. my $MODUL = "PROPLANTA";
  375. # my %url_template_1 =( "de" => "http://www.proplanta.de/Wetter/LOKALERORT-Wetter.html"
  376. # , "at" => "http://www.proplanta.de/Agrarwetter-Oesterreich/LOKALERORT/"
  377. # , "ch" => "http://www.proplanta.de/Agrarwetter-Schweiz/LOKALERORT/"
  378. # , "fr" => "http://www.proplanta.de/Agrarwetter-Frankreich/LOKALERORT.html"
  379. # , "it" => "http://www.proplanta.de/Agrarwetter-Italien/LOKALERORT.html"
  380. # );
  381. my %url_template = ( "de" => "http://www.proplanta.de/Wetter/profi-wetter.php?SITEID=60&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter&wT="
  382. , "at" => "http://www.proplanta.de/Wetter-Oesterreich/profi-wetter-at.php?SITEID=70&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter&wT="
  383. , "ch" => "http://www.proplanta.de/Wetter-Schweiz/profi-wetter-ch.php?SITEID=80&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter&wT="
  384. , "fr" => "http://www.proplanta.de/Wetter-Frankreich/profi-wetter-fr.php?SITEID=50&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter-Frankreich&wT="
  385. , "it" => "http://www.proplanta.de/Wetter-Italien/profi-wetter-it.php?SITEID=40&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter-Italien&wT="
  386. );
  387. ########################################
  388. sub PROPLANTA_Log($$$)
  389. {
  390. my ( $hash, $loglevel, $text ) = @_;
  391. my $xline = ( caller(0) )[2];
  392. my $xsubroutine = ( caller(1) )[3];
  393. my $sub = ( split( ':', $xsubroutine ) )[2];
  394. $sub =~ s/PROPLANTA_//;
  395. my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : $hash;
  396. Log3 $instName, $loglevel, "$MODUL $instName: $sub.$xline " . $text;
  397. }
  398. ###################################
  399. sub PROPLANTA_Initialize($)
  400. {
  401. my ($hash) = @_;
  402. $hash->{DefFn} = "PROPLANTA_Define";
  403. $hash->{UndefFn} = "PROPLANTA_Undef";
  404. $hash->{SetFn} = "PROPLANTA_Set";
  405. $hash->{AttrList} = "INTERVAL URL disable:0,1 forecastDays:4,7,11,14 " . $readingFnAttributes;
  406. }
  407. ###################################
  408. sub PROPLANTA_Define($$)
  409. {
  410. my ( $hash, $def ) = @_;
  411. my $name = $hash->{NAME};
  412. my $lang = "";
  413. my @a = split( "[ \t][ \t]*", $def );
  414. return "Error: Perl moduls ".$missingModul."are missing on this system"
  415. if $missingModul;
  416. return "Wrong syntax: use define <name> PROPLANTA [City] [CountryCode]" if int(@a) > 4;
  417. $lang = "de" if int(@a) == 3;
  418. $lang = lc( $a[3] ) if int(@a) == 4;
  419. if ( $lang ne "")
  420. { # {my $test="http://www.proplanta.de/Wetter/LOKALERORT-Wetter.html";; $test =~ s/LOKALERORT/München/g;; return $test;;}
  421. return "Wrong country code '$lang': use " . join(" | ", keys( %url_template ) ) unless defined( $url_template{$lang} );
  422. my $URL = $url_template{$lang};
  423. my $ort_encode= $a[2];
  424. # change Umlaute from UTF8 in Percent-encode
  425. $ort_encode =~ s/Ä|Ä/%C4/g;
  426. $ort_encode =~ s/Ö|Ö/%D6/g;
  427. $ort_encode =~ s/Ü|Ü/%DC/g;
  428. $ort_encode =~ s/ß|ß/%DF/g;
  429. $ort_encode =~ s/ä|ä/%E4/g;
  430. $ort_encode =~ s/ö|ö/%F6/g;
  431. $ort_encode =~ s/ü|ü/%FC/g;
  432. $URL =~ s/LOKALERORT/$ort_encode/g;
  433. $hash->{URL} = $URL;
  434. # $URL = $url_template_2{$lang};
  435. # $URL =~ s/LOKALERORT/$ort/g;
  436. # $hash->{URL2} = $URL;
  437. }
  438. $hash->{STATE} = "Initializing";
  439. $hash->{fhem}{LOCAL} = 0;
  440. $hash->{INTERVAL} = 3600;
  441. $hash->{fhem}{modulVersion} = '$Date: 2016-07-06 18:27:06 +0200 (Wed, 06 Jul 2016) $';
  442. RemoveInternalTimer($hash);
  443. #Get first data after 32 seconds
  444. InternalTimer( gettimeofday() + 32, "PROPLANTA_Start", $hash, 0 );
  445. return undef;
  446. }
  447. #####################################
  448. sub PROPLANTA_Undef($$)
  449. {
  450. my ( $hash, $arg ) = @_;
  451. RemoveInternalTimer( $hash );
  452. BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
  453. return undef;
  454. }
  455. #####################################
  456. sub PROPLANTA_Set($@)
  457. {
  458. my ($hash, $name, $cmd, @val) = @_;
  459. # my $reUINT = '^([\\+]?\\d+)$';
  460. my $usage = "Unknown argument $cmd, choose one of update:noArg ";
  461. return $usage unless defined $cmd;
  462. if ( $cmd eq "?" ) {
  463. return $usage;
  464. }
  465. elsif ( $cmd eq "update" ) {
  466. Log3 $name, 3, "PROPLANTA: set $name $cmd ".join(" ", @val);
  467. $hash->{fhem}{LOCAL} = 1;
  468. PROPLANTA_Start($hash);
  469. $hash->{fhem}{LOCAL} = 0;
  470. }
  471. else {
  472. return $usage;
  473. }
  474. return;
  475. }
  476. #####################################
  477. # acquires the html page
  478. sub PROPLANTA_HtmlAcquire($$)
  479. {
  480. my ($hash, $URL) = @_;
  481. my $name = $hash->{NAME};
  482. return unless (defined($hash->{NAME}));
  483. PROPLANTA_Log $hash, 4, "Start capturing of $URL";
  484. my $err_log = "";
  485. my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, protocols_allowed => ['http'], timeout => 10
  486. , agent => "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" );
  487. my $request = HTTP::Request->new( GET => $URL );
  488. my $response = $agent->request($request);
  489. $err_log = "Can't get $URL -- " . $response->status_line
  490. unless $response->is_success;
  491. if ( $err_log ne "" )
  492. {
  493. readingsSingleUpdate($hash, "lastConnection", $response->status_line, 1);
  494. PROPLANTA_Log $hash, 1, "Error: $err_log";
  495. return "Error|Error " . $response->status_line;
  496. }
  497. PROPLANTA_Log $hash, 4, length($response->content)." characters captured";
  498. return $response->content;
  499. }
  500. #####################################
  501. sub PROPLANTA_Start($)
  502. {
  503. my ($hash) = @_;
  504. my $name = $hash->{NAME};
  505. return unless (defined($hash->{NAME}));
  506. $hash->{INTERVAL} = AttrVal( $name, "INTERVAL", $hash->{INTERVAL} );
  507. readingsSingleUpdate($hash, "state", "disabled", 1) if AttrVal($name, "disable", 0 ) == 1;
  508. if($hash->{INTERVAL} > 0) {
  509. # reset timer if interval is defined
  510. RemoveInternalTimer( $hash );
  511. InternalTimer(gettimeofday() + $hash->{INTERVAL}, "PROPLANTA_Start", $hash, 1 );
  512. return undef if AttrVal($name, "disable", 0 ) == 1 && !$hash->{fhem}{LOCAL};
  513. }
  514. if ( AttrVal( $name, 'URL', '') eq '' && not defined( $hash->{URL} ) )
  515. {
  516. PROPLANTA_Log $hash, 3, "missing URL";
  517. return;
  518. }
  519. # "Set update"-action will kill a running update child process
  520. if (defined ($hash->{helper}{RUNNING_PID}) && $hash->{fhem}{LOCAL})
  521. {
  522. BlockingKill($hash->{helper}{RUNNING_PID});
  523. delete( $hash->{helper}{RUNNING_PID} );
  524. PROPLANTA_Log $hash, 4, "Killing old forked process";
  525. }
  526. unless (defined ($hash->{helper}{RUNNING_PID}))
  527. {
  528. $hash->{helper}{RUNNING_PID} =
  529. BlockingCall(
  530. "PROPLANTA_Run", # callback worker task
  531. $name, # name of the device
  532. "PROPLANTA_Done", # callback result method
  533. 120, # timeout seconds
  534. "PROPLANTA_Aborted", # callback for abortion
  535. $hash ); # parameter for abortion
  536. PROPLANTA_Log $hash, 4, "Start forked process to capture html";
  537. }
  538. else
  539. {
  540. PROPLANTA_Log $hash, 1, "Could not start forked process, old process still running";
  541. }
  542. }
  543. #####################################
  544. sub PROPLANTA_Run($)
  545. {
  546. eval "setpriority( 0, 0, 10);"; #work-around for old perl
  547. my ($name) = @_;
  548. my $ptext=$name;
  549. my $URL;
  550. my $response;
  551. return unless ( defined($name) );
  552. my $hash = $defs{$name};
  553. return unless (defined($hash->{NAME}));
  554. my $readingStartTime = time();
  555. my $fcDays = AttrVal( $name, 'forecastDays', 14 );
  556. my $parser = MyProplantaParser->new;
  557. # get date from Attribut URL only
  558. my $attrURL = AttrVal( $name, 'URL', "" );
  559. if ($attrURL ne "") {
  560. $response = PROPLANTA_HtmlAcquire($hash,$attrURL);
  561. if ($response =~ /^Error\|/) {
  562. $ptext .= "|".$response;
  563. }
  564. else {
  565. PROPLANTA_Log $hash, 4, "Start HTML parsing of captured page";
  566. $parser->report_tags(qw(tr td span b img));
  567. @MyProplantaParser::texte = ();
  568. $MyProplantaParser::startDay = 0;
  569. # parsing the complete html-page-response, needs some time
  570. $parser->parse($response);
  571. }
  572. }
  573. # Get data from location specified in define
  574. else {
  575. $URL = $hash->{URL};
  576. my @URL_days = (0, 4, 7, 11);
  577. foreach (@URL_days) {
  578. last unless $_ < $fcDays;
  579. $response = PROPLANTA_HtmlAcquire($hash,$URL . $_);
  580. $MyProplantaParser::startDay = $_;
  581. if ($response !~ /^Error\|/) {
  582. PROPLANTA_Log $hash, 4, "Start HTML parsing of captured page";
  583. $parser->parse($response);
  584. }
  585. }
  586. PROPLANTA_Log $hash, 4, "Found terms: " . @MyProplantaParser::texte;
  587. # pack the results in a single string
  588. if (@MyProplantaParser::texte > 0) {
  589. $ptext .= "|". join('|', @MyProplantaParser::texte);
  590. }
  591. PROPLANTA_Log $hash, 5, "Parsed string: " . $ptext;
  592. }
  593. $ptext .= "|durationFetchReadings|";
  594. $ptext .= sprintf "%.2f", time() - $readingStartTime;
  595. return $ptext;
  596. }
  597. #####################################
  598. # asyncronous callback by blocking
  599. sub PROPLANTA_Done($)
  600. {
  601. my ($string) = @_;
  602. return unless ( defined($string) );
  603. # all term are separated by "|" , the first is the name of the instance
  604. my ( $name, %values ) = split( "\\|", $string );
  605. my $hash = $defs{$name};
  606. return unless ( defined($hash->{NAME}) );
  607. PROPLANTA_Log $hash, 4, "Forked process successfully finished";
  608. # delete the marker for RUNNING_PID process
  609. delete( $hash->{helper}{RUNNING_PID} );
  610. # Wetterdaten speichern
  611. readingsBeginUpdate($hash);
  612. if ( defined $values{Error} )
  613. {
  614. readingsBulkUpdate( $hash, "lastConnection", $values{Error} );
  615. }
  616. else
  617. {
  618. my $x = 0;
  619. my $fcDays = AttrVal( $name, 'forecastDays', 14 );
  620. while (my ($rName, $rValue) = each(%values) )
  621. {
  622. if ($fcDays < 14 && $rName =~ /^fc(\d+)_/)
  623. {
  624. next unless $1 < $fcDays;
  625. }
  626. readingsBulkUpdate( $hash, $rName, $rValue );
  627. PROPLANTA_Log $hash, 5, "reading:$rName value:$rValue";
  628. }
  629. if (keys %values > 0)
  630. {
  631. my $newState;
  632. if (defined $values{fc0_tempMin} && defined $values{fc0_tempMax})
  633. {
  634. $newState = "Tmin: " . $values{fc0_tempMin} . " Tmax: " . $values{fc0_tempMax};
  635. # Achtung! Nach Mitternacht fehlen für 1 h die aktuellen Werte
  636. $newState .= " T: " . $values{temperature} . " H: " . $values{humidity} . " W: " . $values{wind} . " P: " . $values{pressure}
  637. if defined $values{temperature} && defined $values{humidity} && defined $values{wind} && defined $values{pressure};
  638. }
  639. else
  640. {
  641. $newState = "Error: Could not capture all data. Please check URL or city name.";
  642. }
  643. readingsBulkUpdate($hash, "state", $newState);
  644. readingsBulkUpdate( $hash, "lastConnection", keys( %values )." values captured in ".$values{durationFetchReadings}." s" );
  645. PROPLANTA_Log $hash, 4, keys( %values )." values captured";
  646. }
  647. else
  648. {
  649. readingsBulkUpdate( $hash, "lastConnection", "no data found" );
  650. PROPLANTA_Log $hash, 1, "No data found. Check city name or URL.";
  651. }
  652. }
  653. readingsEndUpdate( $hash, 1 );
  654. }
  655. #####################################
  656. sub PROPLANTA_Aborted($)
  657. {
  658. my ($hash) = @_;
  659. delete( $hash->{helper}{RUNNING_PID} );
  660. PROPLANTA_Log $hash, 4, "Forked process timed out";
  661. }
  662. ##### noch nicht fertig ###########
  663. sub #####################################
  664. PROPLANTA_Html($)
  665. {
  666. my ($d) = @_;
  667. $d = "<none>" if(!$d);
  668. return "$d is not a PROPLANTA instance<br>"
  669. if(!$defs{$d} || $defs{$d}{TYPE} ne "PROPLANTA");
  670. my $uselocal= 0; #AttrVal($d,"localicons",0);
  671. my $isday;
  672. if ( exists &isday) {
  673. $isday = isday();
  674. }
  675. else {
  676. $isday = 1; #($hour>6 && $hour<19);
  677. }
  678. my $ret = "<table border=0><thead align=center>";
  679. $ret .= sprintf '<tr><th colspan=9 align=left>%s</th></tr>', $defs{$d}{DEF};
  680. $ret .= '<tr><th>Tag</th><th>morgens</th><th>tagsueber</th><th>abends</th><th>nachts</th><th>min</th><th>max</th><th>Regen tags</th><th>Frost</th></tr></thead>';
  681. $ret .= "<tbody align=center>";
  682. # define MyForecast weblink htmlCode { PROPLANTA_Html("ProPlanta_Wetter") }
  683. for(my $i=0; $i<=2; $i++) {
  684. $ret .= sprintf('<tr><td>%s</td><td>%s<br><img src="%s"></td><td>%s<br><img src="%s"></td><td>%s<br><img src="%s"></td><td>%s<br><img src="%s"></td><td>%s&deg;C</td><td>%s&deg;C</td><td>%s %%</td><td>%s</td></tr>',
  685. ReadingsVal($d, "fc".$i."_date", ""),
  686. ReadingsVal($d, "fc".$i."_weatherMorning", ""), ReadingsVal($d, "fc".$i."_weatherMorningIcon", ""),
  687. ReadingsVal($d, "fc".$i."_weatherDay", ""), ReadingsVal($d, "fc".$i."_weatherDayIcon", ""),
  688. ReadingsVal($d, "fc".$i."_weatherEvening", ""), ReadingsVal($d, "fc".$i."_weatherEveningIcon", ""),
  689. ReadingsVal($d, "fc".$i."_weatherNight", ""), ReadingsVal($d, "fc".$i."_weatherNightIcon", ""),
  690. ReadingsVal($d, "fc".$i."_tempMin", ""), ReadingsVal($d, "fc".$i."_tempMax", ""),
  691. ReadingsVal($d, "fc".$i."_chOfRainDay", ""),
  692. ReadingsVal($d, "fc".$i."_frost", "") ? "ja" : "nein"
  693. );
  694. }
  695. # for(my $i=0; $i<=4; $i++) {
  696. # $ret .= sprintf('<tr><td>%s</td><td>%s: %s<br>min %s °C max %s °C<br>wind: %s km/h %s<br>precip: %s mm</td></tr>',
  697. # WWOIconIMGTag(ReadingsVal($d, "fc${i}_weatherDayIcon", ""),$uselocal,$isday),
  698. # ReadingsVal($d, "fc${i}_date", ""),
  699. # ReadingsVal($d, "fc${i}_weatherDay", ""),
  700. # ReadingsVal($d, "fc${i}_tempMinC", ""), ReadingsVal($d, "fc${i}_tempMaxC", ""),
  701. # }
  702. $ret .= "</tbody></table>";
  703. return $ret;
  704. }
  705. #####################################
  706. 1;
  707. =pod
  708. =begin html
  709. <a name="PROPLANTA"></a>
  710. <h3>PROPLANTA</h3>
  711. <div>
  712. <ul>
  713. The module extracts weather data from <a href="http://www.proplanta.de">www.proplanta.de</a>.
  714. <br>
  715. The website provides a forecast for 12 days, for the first 7 days in a 3-hours-interval.
  716. <br>
  717. This modul causes a high CPU load. It is recommended to reduce the number of captured forecast days.
  718. <br>
  719. It uses the perl moduls HTTP::Request, LWP::UserAgent and HTML::Parse.
  720. <br/><br/>
  721. <a name="PROPLANTAdefine"></a>
  722. <b>Define</b>
  723. <ul>
  724. <br>
  725. <code>define &lt;name&gt; PROPLANTA [City] [CountryCode]</code>
  726. <br>
  727. Example:
  728. <br>
  729. <code>define wetter PROPLANTA Bern ch</code>
  730. <br>
  731. <code>define wetter PROPLANTA Wittingen+(Niedersachsen)</code>
  732. <br>&nbsp;
  733. <li><code>[City]</code>
  734. <br>
  735. Optional. The city must be selectable on <a href="http://www.proplanta.de">www.proplanta.de</a>.
  736. <br>
  737. Please pay attention to the <b>Capital</b> letters in the city names.
  738. Spaces within the name are replaced by a + (plus).
  739. </li><br>
  740. <li><code>[CountryCode]</code>
  741. <br>
  742. Optional. Possible values: de (default), at, ch, fr, it
  743. </li><br>
  744. The function <code>PROPLANTA_Html</code> creates a HTML code for a 3 day weather forecast.
  745. <br>
  746. Example:
  747. <br>
  748. <code>define HTMLForecast weblink htmlCode { PROPLANTA_Html("ProPlanta_Wetter") }</code>
  749. <br/><br/>
  750. </ul>
  751. <br>
  752. <a name="PROPLANTAset"></a>
  753. <b>Set</b>
  754. <ul>
  755. <br>
  756. <li><code>set &lt;name&gt; update</code>
  757. <br>
  758. The weather data are immediately polled from the website.
  759. </li><br>
  760. </ul>
  761. <br>
  762. <a name="PROPLANTAattr"></a>
  763. <b>Attributes</b>
  764. <ul>
  765. <br>
  766. <li><code>forecastDays &lt;4-14&gt;</code>
  767. <br>
  768. Number of days for which the forecast data shall be fetched. Default is 14 days (incl. today).
  769. </li><br>
  770. <li><code>INTERVAL &lt;seconds&gt;</code>
  771. <br>
  772. Poll interval for weather data in seconds (default 3600 = 1 hour)
  773. </li><br>
  774. <li><code>URL &lt;internet address&gt;</code>
  775. <br>
  776. URL to extract information from. Overwrites the values in the 'define' term.
  777. </li><br>
  778. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  779. </ul>
  780. <br>
  781. <a name="PROPLANTAreading"></a>
  782. <b>Forecast readings</b>
  783. <ul>
  784. <br>
  785. <li><b>fc</b><i>0|1|2|3|...|13</i><b>_...</b> - forecast values for <i>today|tommorrow|in 2|3|...|13 days</i></li>
  786. <li><b>fc</b><i>0</i><b>_...<i>00|03|06|09|12|15|18|21</i></b> - forecast values for <i>today</i> at <i>00|03|06|09|12|15|18|21</i> o'clock</li>
  787. <li><b>fc</b><i>0</i><b>_chOfRain</b><i>Day|Night</i> - chance of rain <i>today</i> by <i>day|night</i> in %</li>
  788. <li><b>fc</b><i>0</i><b>_chOfRain</b><i>15</i> - chance of rain <i>today</i> at <i>15:00</i> in %</li>
  789. <li><b>fc</b><i>0</i><b>_cloud</b><i>15</i> - cloud coverage <i>today</i> at <i>15:00</i> in %</li>
  790. <li><b>fc</b><i>0</i><b>_dew</b> - dew formation <i>today</i> (0=none, 1=small, 2=medium, 3=strong)</li>
  791. <li><b>fc</b><i>0</i><b>_evapor</b> - evaporation <i>today</i> (0=none, 1=small, 2=medium, 3=strong)</li>
  792. <li><b>fc</b><i>0</i><b>_frost</b> - ground frost <i>today</i> (0=no, 1=yes)</li>
  793. <li><b>fc</b><i>0</i><b>_moon</b><i>Rise|Set</i> - moon <i>rise|set today</i></li>
  794. <li><b>fc</b><i>0</i><b>_rad</b> - global radiation <i>today</i></li>
  795. <li><b>fc</b><i>0</i><b>_rain</b><i>15</i> - amount of rainfall <i>today</i> at <i>15:00</i> o'clock in mm</li>
  796. <li><b>fc</b><i>0</i><b>_sun</b> - relative sun shine duration <i>today</i> in % (between sun rise and set)</li>
  797. <li><b>fc</b><i>0</i><b>_temp</b><i>Min|Max</i> - <i>minimal|maximal</i> temperature <i>today</i> in &deg;C</li>
  798. <li><b>fc</b><i>0</i><b>_temp</b><i>15</i> - temperatur <i>today</i> at <i>15:00</i> o'clock in &deg;C</li>
  799. <li><b>fc</b><i>0</i><b>_uv</b> - UV-Index <i>today</i></li>
  800. <li><b>fc</b><i>0</i><b>_weather</b><i>Morning|Day|Evening|Night</i> - weather situation <i>today morning|during day|in the evening|during night</i></li>
  801. <li><b>fc</b><i>0</i><b>_weather</b><i>Day</i><b>Icon</b> - icon of weather situation <i>today</i> by <i>day</i></li>
  802. <li>etc.</li>
  803. </ul>
  804. <br>
  805. </ul>
  806. </div>
  807. =end html
  808. =begin html_DE
  809. <a name="PROPLANTA"></a>
  810. <h3>PROPLANTA</h3>
  811. <div>
  812. <ul>
  813. <a name="PROPLANTAdefine"></a>
  814. Das Modul extrahiert Wetterdaten von der Website <a href="http://www.proplanta.de">www.proplanta.de</a>.
  815. <br/>
  816. Es stellt eine Vorhersage f&uuml;r 12 Tage zur Verf&uuml;gung - w&auml;hrend der ersten 7 Tage im 3-Stunden-Intervall.
  817. <br>
  818. Dieses Modul erzeugt eine hohe CPU-Last. Es wird deshalb empfohlen, die auszulesenden Vorhersagetage zu reduzieren.
  819. <br>
  820. <i>Es nutzt die Perl-Module HTTP::Request, LWP::UserAgent und HTML::Parse</i>.
  821. <br>
  822. F&uuml;r detailierte Anleitungen bitte die <a href="http://www.fhemwiki.de/wiki/PROPLANTA"><b>FHEM-Wiki</b></a> konsultieren und erg&auml;nzen.
  823. <br/><br/>
  824. <b>Define</b>
  825. <ul>
  826. <br>
  827. <code>define &lt;Name&gt; PROPLANTA [Stadt] [L&auml;ndercode]</code>
  828. <br>
  829. Beispiel:
  830. <br>
  831. <code>define wetter PROPLANTA Bern ch</code>
  832. <br>
  833. <code>define wetter PROPLANTA Wittingen+(Niedersachsen)</code>
  834. <br>&nbsp;
  835. <li><code>[Stadt]</code>
  836. <br>
  837. Optional. Die Stadt muss auf <a href="http://www.proplanta.de">www.proplanta.de</a> ausw&auml;hlbar sein.
  838. <br>
  839. Wichtig!! Auf die <b>gro&szlig;en</b> Anfangsbuchstaben achten.
  840. Leerzeichen im Stadtnamen werden durch ein + (Plus) ersetzt.
  841. </li><br>
  842. <li><code>[L&auml;ndercode]</code>
  843. <br>
  844. Optional. M&ouml;gliche Werte: de (Standard), at, ch, fr, it
  845. </li><br>
  846. &Uuml;ber die Funktion <code>PROPLANTA_Html</code> wird ein HTML-Code f&uuml;r eine 3-Tages-Vorhersage erzeugt.
  847. <br>
  848. Beispiel:
  849. <br>
  850. <code>define Vorschau weblink htmlCode {PROPLANTA_Html("Wetter")}</code>
  851. <br/><br/>
  852. </ul>
  853. <br>
  854. <a name="PROPLANTAset"></a>
  855. <b>Set</b>
  856. <ul>
  857. <br>
  858. <li><code>set &lt;name&gt; update</code>
  859. <br>
  860. Startet sofort ein neues Auslesen der Wetterdaten.
  861. </li><br>
  862. </ul>
  863. <a name="PROPLANTAattr"></a>
  864. <b>Attribute</b>
  865. <ul>
  866. <br>
  867. <li><code>forecastDays &lt;4-14&gt;</code>
  868. <br>
  869. Anzahl Tage, f&uuml;r die die Vorhersage ausgelesen werden soll. Standard ist 14 Tage (inkl. heute).
  870. </li><br>
  871. <li><code>INTERVAL &lt;Abfrageinterval&gt;</code>
  872. <br>
  873. Abfrageinterval in Sekunden (Standard 3600 = 1 Stunde)
  874. </li><br>
  875. <li><code>URL &lt;Internetadresse&gt;</code>
  876. <br>
  877. Internetadresse, von der die Daten ausgelesen werden (&uuml;berschreibt die Werte im 'define'-Term)
  878. </li><br>
  879. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  880. </ul>
  881. <br><br>
  882. <a name="PROPLANTAreading"></a>
  883. <b>Vorhersagewerte</b>
  884. <ul>
  885. <br>
  886. <li><b>fc</b><i>0|1|2|3...|13</i><b>_...</b> - Vorhersagewerte f&uumlr <i>heute|morgen|&uuml;bermorgen|in 3|...|13 Tagen</i></li>
  887. <li><b>fc</b><i>0</i><b>_...<i>00|03|06|09|12|15|18|21</i></b> - Vorhersagewerte f&uumlr <i>heute</i> um <i>00|03|06|09|12|15|18|21</i> Uhr</li>
  888. <li><b>fc</b><i>0</i><b>_chOfRain</b><i>Day|Night</i> - <i>heutiges</i> Niederschlagsrisiko <i>tags&uuml;ber|nachts</i> in %</li>
  889. <li><b>fc</b><i>1</i><b>_chOfRain</b><i>15</i> - <i>morgiges</i> Niederschlagsrisiko um <i>15</i>:00 Uhr in %</li>
  890. <li><b>fc</b><i>2</i><b>_cloud</b><i>15</i> - Wolkenbedeckungsgrad <i>&uuml;bermorgen</i> um <i>15</i>:00 Uhr in %</li>
  891. <li><b>fc</b><i>0</i><b>_dew</b> - Taubildung <i>heute</i> (0=keine, 1=leicht, 2=m&auml;&szlig;ig, 3=stark)</li>
  892. <li><b>fc</b><i>0</i><b>_evapor</b> - Verdunstung <i>heute</i> (0=keine, 1=gering, 2=m&auml;&szlig;ig, 3=stark)</li>
  893. <li><b>fc</b><i>0</i><b>_frost</b> - Bodenfrost <i>heute</i> (0=nein, 1=ja)</li>
  894. <li><b>fc</b><i>1</i><b>_moon</b><i>Rise|Set</i> - Mond<i>auf|unter</i>gang <i>morgen</i></li>
  895. <li><b>fc</b><i>0</i><b>_rad</b> - Globalstrahlung <i>heute</i></li>
  896. <li><b>fc</b><i>0</i><b>_rain</b><i>15</i> - Niederschlagsmenge <i>heute</i> um <i>15</i>:00 Uhr in mm</li>
  897. <li><b>fc</b><i>0</i><b>_sun</b> - relative Sonnenscheindauer <i>heute</i> in % (zwischen Sonnenauf- und -untergang)</li>
  898. <li><b>fc</b><i>0</i><b>_temp</b><i>Min|Max</i> - <i>Minimal|Maximal</i>temperatur <i>heute</i> in &deg;C</li>
  899. <li><b>fc</b><i>0</i><b>_temp</b><i>15</i> - Temperatur <i>heute</i> um <i>15</i>:00 Uhr in &deg;C</li>
  900. <li><b>fc</b><i>0</i><b>_uv</b> - UV-Index <i>heute</i></li>
  901. <li><b>fc</b><i>0</i><b>_weather</b><i>Morning|Day|Evening|Night</i> - Wetterzustand <i>heute morgen|tags&uuml;ber|abends|nachts</i></li>
  902. <li><b>fc</b><i>0</i><b>_weather</b><i>Day</i><b>Icon</b> - Icon Wetterzustand <i>heute tags&uuml;ber</i></li>
  903. <li>etc.</li>
  904. </ul>
  905. <br>
  906. <b>Aktuelle Werte</b>
  907. <ul>
  908. <br>
  909. <li><b>cloudBase</b><i>Min|Max</i> - H&ouml;he der <i>minimalen|maximalen</i> Wolkenuntergrenze in m</li>
  910. <li><b>dewPoint</b> - Taupunkt in &deg;C</li>
  911. <li><b>humidity</b> - relative Feuchtigkeit in %</li>
  912. <li><b>obs_time</b> - Uhrzeit der Wetterbeobachtung</li>
  913. <li><b>pressure</b> - Luftdruck in hPa</li>
  914. <li><b>temperature</b> - Temperature in &deg;C</li>
  915. <li><b>visibility</b> - Sichtweite in km</li>
  916. <li><b>weather</b> - Wetterzustand</li>
  917. <li><b>weatherIcon</b> - Icon Wetterzustand</li>
  918. <li><b>wind</b> - Windgeschwindigkeit in km/h</li>
  919. <li><b>windDir</b> - Windrichtung in &deg;</li>
  920. </ul>
  921. <br><br>
  922. </ul>
  923. </div>
  924. =end html_DE
  925. =cut