60_allergy.pm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. ##############################################
  2. # $Id: 60_allergy.pm 16669 2018-04-28 21:42:29Z moises $$$
  3. #
  4. # 60_allergy.pm
  5. #
  6. # 2017 Markus Moises < vorname at nachname . de >
  7. #
  8. # This module provides allergy forecast data
  9. #
  10. # http://forum.fhem.de/index.php/topic,37194.0.html
  11. #
  12. #
  13. ##############################################################################
  14. #
  15. # define <name> allergy <zipcode>
  16. #
  17. ##############################################################################
  18. package main;
  19. use strict;
  20. use warnings;
  21. use Time::Local;
  22. use Encode;
  23. use XML::Simple;
  24. use LWP::UserAgent;
  25. use HTTP::Request;
  26. use utf8;
  27. my %pollen_types = ( 0 => "Unknown",
  28. 1 => "Ahorn",
  29. 2 => "Ambrosia",
  30. 3 => "Beifuss",
  31. 4 => "Birke",
  32. 5 => "Brennnessel",
  33. 6 => "Buche",
  34. 7 => "Eiche",
  35. 8 => "Erle",
  36. 9 => "Esche",
  37. 10 => "Fichte",
  38. 11 => "Flieder",
  39. 12 => "Gaensefuss",
  40. 13 => "Gerste",
  41. 14 => "Graeser",
  42. 15 => "Hafer",
  43. 16 => "Hasel",
  44. 17 => "Holunder",
  45. 18 => "Hopfen",
  46. 19 => "Kiefer",
  47. 20 => "Linde",
  48. 21 => "Loewenzahn",
  49. 22 => "Mais",
  50. 23 => "Nessel",
  51. 24 => "Pappel",
  52. 25 => "Platane",
  53. 26 => "Raps",
  54. 27 => "Roggen",
  55. 28 => "Rotbuche",
  56. 29 => "Spitzwegerich",
  57. 30 => "Tanne",
  58. 31 => "Ulme",
  59. 32 => "Weide",
  60. 33 => "Weizen", );
  61. ##############################################################################
  62. sub allergy_Initialize($) {
  63. my ($hash) = @_;
  64. my $name = $hash->{NAME};
  65. $hash->{DefFn} = "allergy_Define";
  66. $hash->{UndefFn} = "allergy_Undefine";
  67. $hash->{GetFn} = "allergy_Get";
  68. $hash->{AttrFn} = "allergy_Attr";
  69. $hash->{AttrList} = "disable:0,1 ".
  70. "ignoreList ".
  71. "updateIgnored:1,0 ".
  72. "updateEmpty:1,0 ".
  73. "levelsFormat ".
  74. "weekdaysFormat ".
  75. "extended5Day:1,0 ".
  76. $readingFnAttributes;
  77. }
  78. sub allergy_Define($$$) {
  79. my ($hash, $def) = @_;
  80. my @a = split("[ \t][ \t]*", $def);
  81. my ($found, $dummy);
  82. return "syntax: define <name> allergy <zipcode>" if(int(@a) != 3 );
  83. my $name = $hash->{NAME};
  84. $hash->{helper}{ZIPCODE} = $a[2];
  85. $hash->{helper}{INTERVAL} = 10800;
  86. $hash->{ERROR} = 0;
  87. my $req = eval
  88. {
  89. require XML::Simple;
  90. XML::Simple->import();
  91. require JSON;
  92. JSON->import();
  93. 1;
  94. };
  95. if($req)
  96. {
  97. InternalTimer( gettimeofday() + 60, "allergy_GetUpdate", $hash, 0);
  98. if (!defined($attr{$name}{stateFormat}))
  99. {
  100. $attr{$name}{stateFormat} = 'fc1_maximum';
  101. }
  102. }
  103. else
  104. {
  105. $hash->{STATE} = "XML::Simple and JSON is required!";
  106. $attr{$name}{disable} = "1";
  107. return undef;
  108. }
  109. $hash->{STATE} = "Initialized";
  110. return undef;
  111. }
  112. sub allergy_Undefine($$) {
  113. my ($hash, $arg) = @_;
  114. my $name = $hash->{NAME};
  115. RemoveInternalTimer($hash);
  116. #fhem("deletereading $name fc.*", 1);
  117. return undef;
  118. }
  119. sub allergy_Get($@) {
  120. my ($hash, @a) = @_;
  121. my $command = $a[1];
  122. my $parameter = $a[2] if(defined($a[2]));
  123. my $name = $hash->{NAME};
  124. my $usage = "Unknown argument $command, choose one of data:noArg ";
  125. return $usage if $command eq '?';
  126. RemoveInternalTimer($hash);
  127. if(AttrVal($name, "disable", 0) eq 1) {
  128. $hash->{STATE} = "disabled";
  129. return "allergy $name is disabled. Aborting...";
  130. }
  131. allergy_GetUpdate($hash);
  132. return undef;
  133. }
  134. sub allergy_GetUpdate($) {
  135. my ($hash) = @_;
  136. my $name = $hash->{NAME};
  137. if(AttrVal($name, "disable", 0) eq 1) {
  138. $hash->{STATE} = "disabled";
  139. Log3 ($name, 2, "allergy $name is disabled, data update cancelled.");
  140. return undef;
  141. }
  142. my $url="http://www.allergie.hexal.de/pollenflug/xml-interface-neu/pollen_de_7tage.php?plz=".$hash->{helper}{ZIPCODE};
  143. if(AttrVal($name, "extended5Day", 0) eq 1) {
  144. $url="https://pollenwarner-live.herokuapp.com/pollen/".$hash->{helper}{ZIPCODE};
  145. Log3 ($name, 4, "Getting URL $url");
  146. HttpUtils_NonblockingGet({
  147. url => $url,
  148. noshutdown => 1,
  149. hash => $hash,
  150. type => 'allergydata',
  151. callback => \&allergy_ParseExtended,
  152. });
  153. return undef;
  154. }
  155. Log3 ($name, 4, "Getting URL $url");
  156. HttpUtils_NonblockingGet({
  157. url => $url,
  158. noshutdown => 1,
  159. hash => $hash,
  160. type => 'allergydata',
  161. callback => \&allergy_Parse,
  162. });
  163. return undef;
  164. }
  165. sub allergy_Parse($$$)
  166. {
  167. my ($param, $err, $data) = @_;
  168. my $hash = $param->{hash};
  169. my $name = $hash->{NAME};
  170. if( $err )
  171. {
  172. Log3 $name, 1, "$name: URL error (".($hash->{ERROR}+1)."): ".$err;
  173. my $nextupdate = gettimeofday()+( (900*$hash->{ERROR}) + 90 );
  174. InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
  175. $hash->{STATE} = "error" if($hash->{ERROR} > 1);
  176. $hash->{ERROR} = $hash->{ERROR}+1;
  177. return undef;
  178. }
  179. $hash->{ERROR} = 0;
  180. Log3 $name, 5, "Received XML data ".$data;
  181. my $xml = new XML::Simple();
  182. #my $xmldata = $xml->XMLin($data,forcearray => [qw( pollenbelastungen pollen )],keyattr => {pollen => 'name'});
  183. my $xmldata = eval { $xml->XMLin($data,forcearray => [qw( pollenbelastungen pollen )],keyattr => {pollen => 'name'}) };
  184. if($@)
  185. {
  186. Log3 $name, 2, "$name: XML error ".$@;
  187. my $nextupdate = gettimeofday()+$hash->{helper}{INTERVAL};
  188. InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
  189. return undef;
  190. }
  191. my @wdays = split(',',AttrVal($hash->{NAME}, "weekdaysFormat", "Sun,Mon,Tue,Wed,Thu,Fri,Sat" ));
  192. my @levels = split(',',AttrVal($hash->{NAME}, "levelsFormat", "-,low,moderate,high,extreme" ));
  193. readingsBeginUpdate($hash); # Start update readings
  194. my $city = $xmldata->{'pollendaten'}->{'ort'};
  195. readingsBulkUpdate($hash, "city", allergy_utf8clean($city));
  196. Log3 $name, 4, "Received data for postcode ".$xmldata->{'pollendaten'}->{'plz'};
  197. foreach my $day (@{$xmldata->{'pollendaten'}{'pollenbelastungen'}})
  198. {
  199. my $daycode = $day->{'tag'}+1;
  200. my @daydata = $day->{'pollen'};
  201. my $daymax = 0;
  202. my $pollenkey='';
  203. my $pollenvalue='';
  204. my $pollendata=0;
  205. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time+($day->{'tag'}*86400));
  206. readingsBulkUpdate($hash, "fc".$daycode."_day_of_week", $wdays[$wday]);
  207. foreach my $pollenhash (@daydata)
  208. {
  209. while(($pollenkey, $pollenvalue) = each(%$pollenhash))
  210. {
  211. $pollenkey = allergy_utf8clean($pollenkey);
  212. $pollendata = $pollenvalue->{'belastung'};
  213. if (( AttrVal($hash->{NAME}, "updateEmpty", 0 ) gt 0 or $pollendata gt 0) and ( AttrVal($hash->{NAME}, "updateIgnored", 0 ) gt 0 or ( index(AttrVal($hash->{NAME}, "ignoreList", ""), $pollenkey ) == -1 )))
  214. {
  215. readingsBulkUpdate($hash, "fc".$daycode."_".$pollenkey, $levels[$pollendata]);
  216. $daymax = $pollendata if($pollendata gt $daymax);
  217. Log3 $name, 4, "Received pollen level for ".$pollenkey.": day".$daycode." level ".$pollendata;
  218. }
  219. else
  220. {
  221. fhem( "deletereading $name fc".$daycode."_".$pollenkey, 1 );
  222. Log3 $name, 5, "Received pollen level for ".$pollenkey.": day".$daycode." level ".$pollendata." (ignored)";
  223. }
  224. }
  225. }
  226. readingsBulkUpdate($hash, "fc".$daycode."_maximum", $levels[$daymax]);
  227. }
  228. readingsEndUpdate($hash, 1);
  229. $hash->{UPDATED} = FmtDateTime(time());
  230. my $nextupdate = gettimeofday()+$hash->{helper}{INTERVAL};
  231. InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
  232. return undef;
  233. }
  234. sub allergy_ParseExtended($$$)
  235. {
  236. my ($param, $err, $data) = @_;
  237. my $hash = $param->{hash};
  238. my $name = $hash->{NAME};
  239. if( $err )
  240. {
  241. Log3 $name, 1, "$name: URL error (".($hash->{ERROR}+1)."): ".$err;
  242. my $nextupdate = gettimeofday()+( (900*$hash->{ERROR}) + 90 );
  243. InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
  244. $hash->{STATE} = "error" if($hash->{ERROR} > 1);
  245. $hash->{ERROR} = $hash->{ERROR}+1;
  246. return undef;
  247. }
  248. $hash->{ERROR} = 0;
  249. Log3 $name, 5, "Received data ".$data;
  250. my $json = eval { JSON::decode_json($data) };
  251. if($@)
  252. {
  253. Log3 $name, 2, "$name: JSON error ".$@;
  254. my $nextupdate = gettimeofday()+$hash->{helper}{INTERVAL};
  255. InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
  256. return undef;
  257. }
  258. Log3 $name, 5, "$name: parse json\n".Dumper($json);
  259. my @wdays = split(',',AttrVal($hash->{NAME}, "weekdaysFormat", "Sun,Mon,Tue,Wed,Thu,Fri,Sat" ));
  260. my @levels = split(',',AttrVal($hash->{NAME}, "levelsFormat", "-,low,moderate,high,extreme" ));
  261. readingsBeginUpdate($hash); # Start update readings
  262. my $city = $json->{region};
  263. readingsBulkUpdate($hash, "city", allergy_utf8clean($city)) if($json->{region});
  264. my $day = $json->{date};
  265. readingsBulkUpdate($hash, "date", $day) if($json->{date});
  266. Log3 $name, 4, "Received data for postcode ".$json->{region};
  267. my @daymax;
  268. return undef if(!defined($json->{polls}));
  269. #Log3 $name, 1, "found polls ".ref($json->{polls});
  270. foreach my $pollenid ( keys %{$json->{polls}}) {
  271. my $pollenid = $json->{polls}->{$pollenid}->{id};
  272. #Log3 $name, 1, "polls step ".$pollenid;
  273. my $pollenkey = 'Unknown';
  274. $pollenkey = $pollen_types{$pollenid} if( defined($pollen_types{$pollenid}) );
  275. return undef if(!defined($json->{polls}->{$pollenid}->{forecast}));
  276. #Log3 $name, 1, "forecast ";
  277. return undef if(ref($json->{polls}->{$pollenid}->{forecast}) ne "ARRAY");
  278. #my @forecast = $json->{polls}->{$pollenid}->{forecast};
  279. my $daycode = 0;
  280. while(defined($json->{polls}->{$pollenid}->{forecast}[$daycode])) {
  281. my $pollendata = int($json->{polls}->{$pollenid}->{forecast}[$daycode]);
  282. #Log3 $name, 1, "forecast array".ref($pollendata);
  283. if (( AttrVal($hash->{NAME}, "updateEmpty", 0 ) gt 0 or $pollendata gt 0) and ( AttrVal($hash->{NAME}, "updateIgnored", 0 ) gt 0 or ( index(AttrVal($hash->{NAME}, "ignoreList", ""), $pollenkey ) == -1 )))
  284. {
  285. readingsBulkUpdate($hash, "fc".($daycode+1)."_".$pollenkey, $levels[$pollendata]);
  286. $daymax[$daycode] = $pollendata if(!defined($daymax[$daycode]) || $pollendata gt $daymax[$daycode]);
  287. Log3 $name, 4, "Received pollen level for ".$pollenkey.": day".($daycode+1)." level ".$pollendata;
  288. }
  289. else
  290. {
  291. fhem( "deletereading $name fc".($daycode+1)."_".$pollenkey, 1 );
  292. Log3 $name, 5, "Received pollen level for ".$pollenkey.": day".($daycode+1)." level ".$pollendata." (ignored)";
  293. }
  294. $daymax[$daycode] = 0 if(!defined($daymax[$daycode]));
  295. $daycode++;
  296. }
  297. }
  298. my $daycode = 0;
  299. while(defined($daymax[$daycode])) {
  300. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time+($daycode*86400));
  301. readingsBulkUpdate($hash, "fc".($daycode+1)."_day_of_week", $wdays[$wday]);
  302. readingsBulkUpdate($hash, "fc".($daycode+1)."_maximum", $levels[$daymax[$daycode]]);
  303. $daycode++;
  304. }
  305. readingsEndUpdate($hash, 1);
  306. $hash->{UPDATED} = FmtDateTime(time());
  307. my $nextupdate = gettimeofday()+$hash->{helper}{INTERVAL};
  308. InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
  309. return undef;
  310. }
  311. sub allergy_Attr($$$)
  312. {
  313. my ($cmd, $name, $attrName, $attrVal) = @_;
  314. my $orig = $attrVal;
  315. if( $attrName eq "disable" ) {
  316. my $hash = $defs{$name};
  317. RemoveInternalTimer($hash);
  318. if( $cmd eq "set" && $attrVal ne "0" ) {
  319. $attrVal = 1;
  320. } else {
  321. $attr{$name}{$attrName} = 0;
  322. allergy_GetUpdate($hash);
  323. }
  324. }
  325. elsif ($attrName eq "extended5Day") {
  326. fhem("deletereading $name fc.*", 1);
  327. fhem("deletereading $name date", 1);
  328. my $hash = $defs{$name};
  329. allergy_GetUpdate($hash);
  330. }
  331. if( $cmd eq "set" ) {
  332. if( !defined($orig) || $orig ne $attrVal ) {
  333. $attr{$name}{$attrName} = $attrVal;
  334. return $attrName ." set to ". $attrVal;
  335. }
  336. }
  337. return;
  338. }
  339. sub allergy_utf8clean($) {
  340. my ($string) = @_;
  341. my $log = "";
  342. if($string !~ m/^[A-Za-z\d_\.-]+$/)
  343. {
  344. $log .= $string."(standard) ";
  345. $string =~ s/Ä/Ae/g;
  346. $string =~ s/Ö/Oe/g;
  347. $string =~ s/Ü/Ue/g;
  348. $string =~ s/ä/ae/g;
  349. $string =~ s/ö/oe/g;
  350. $string =~ s/ü/ue/g;
  351. $string =~ s/ß/ss/g;
  352. }
  353. if($string !~ m/^[A-Za-z\d_\.-]+$/)
  354. {
  355. $log .= $string."(single) ";
  356. $string =~ s/Ä/Ae/g;
  357. $string =~ s/Ö/Oe/g;
  358. $string =~ s/Ü/Ue/g;
  359. $string =~ s/ä/ae/g;
  360. $string =~ s/ö/oe/g;
  361. $string =~ s/ü/ue/g;
  362. $string =~ s/ß/ss/g;
  363. }
  364. if($string !~ m/^[A-Za-z\d_\.-]+$/)
  365. {
  366. $log .= $string."(double) ";
  367. $string =~ s/Ä/Ae/g;
  368. $string =~ s/Ö/Oe/g;
  369. $string =~ s/Ü/Ue/g;
  370. $string =~ s/ä/ae/g;
  371. $string =~ s/ö/oe/g;
  372. $string =~ s/ü/ue/g;
  373. $string =~ s/ß/ss/g;
  374. }
  375. if($string !~ m/^[A-Za-z\d_\.-]+$/)
  376. {
  377. $log .= $string."(unknown)";
  378. #$string =~ s/[^!-~\s]//g;
  379. $string =~ s/[^A-Za-z\d_\.-]//g;
  380. }
  381. Log3 "utf8clean", 5, "Cleaned $string // $log" if($log ne "");
  382. return $string;
  383. }
  384. ##########################
  385. 1;
  386. =pod
  387. =item device
  388. =item summary Allergy forecast data for Germany
  389. =begin html
  390. <a name="allergy"></a>
  391. <h3>allergy</h3>
  392. <ul>
  393. This modul provides allergy forecast data for Germany.<br/>
  394. It requires the Perl module XML::Simple to be installed
  395. <br/><br/>
  396. <b>Define</b>
  397. <ul>
  398. <code>define &lt;name&gt; allergy &lt;zipcode&gt;</code>
  399. <br>
  400. Example: <code>define allergydata allergy 12345</code>
  401. <br>&nbsp;
  402. <li><code>zipcode</code>
  403. <br>
  404. German zipcode
  405. </li><br>
  406. </ul>
  407. <br>
  408. <b>Get</b>
  409. <ul>
  410. <li><code>data</code>
  411. <br>
  412. Manually trigger data update
  413. </li><br>
  414. </ul>
  415. <br>
  416. <b>Readings</b>
  417. <ul>
  418. <li><code>city</code>
  419. <br>
  420. Name of the city the forecast is read for
  421. </li><br>
  422. <li><code>fc<i>n</i>_total</code>
  423. <br>
  424. Daily maximum levels for all allergens that are not being ignored due to <i>ignoreList</i><br/>
  425. </li><br>
  426. <li><code>fc<i>n</i>_day_of_week</code>
  427. <br>
  428. Weekday, can be localized through <i>weekdaysFormat</i><br/>
  429. </li><br>
  430. <li><code>fc<i>n</i>_<i>allergen</i></code>
  431. <br>
  432. Daily levels for all allergens that are not being ignored due to <i>ignoreList</i>
  433. </li><br>
  434. </ul>
  435. <br>
  436. <b>Attributes</b>
  437. <ul>
  438. <li><code>ignoreList</code>
  439. <br>
  440. Comma-separated list of allergen names that are to be ignored during updates and for cumulated day levels calculation
  441. </li><br>
  442. <li><code>updateEmpty</code>
  443. <br>
  444. Also update (and keep) level readings for inactive allergens that are otherwise removed
  445. </li><br>
  446. <li><code>updateIgnored</code>
  447. <br>
  448. Also update (and keep) level readings for ignored allergens that are otherwise removed
  449. </li><br>
  450. <li><code>levelsFormat</code>
  451. <br>
  452. Localize levels by adding them comma separated (default: -,low,moderate,high,extreme)
  453. </li><br>
  454. <li><code>weekdaysFormat</code>
  455. <br>
  456. Localize Weekdays by adding them comma separated (default: Sun,Mon,Tue,Wed,Thu,Fr,Sat)
  457. </li><br>
  458. </ul>
  459. </ul>
  460. =end html
  461. =begin html_DE
  462. <a name="allergy"></a>
  463. <h3>allergy</h3>
  464. <ul>
  465. <br>Dieses Modul prognostiziert Allergie Daten für Deutschland.</br>
  466. Es erfordert dass das Perlmodul XML:: Simple installiert ist.
  467. <br/><br/>
  468. <b>Define</b>
  469. <ul>
  470. <code>define &lt;name&gt; allergy &lt;Postleitzahl&gt;</code>
  471. <br>
  472. Beispiel: <code>define allergydata allergy 12345</code>
  473. <br><br>
  474. <li><code>Postleitzahl</code>
  475. <br>
  476. Deutsche Postleitzahl
  477. </li><br>
  478. </ul>
  479. <br>
  480. <b>Get</b>
  481. <ul>
  482. <li><code>data</code>
  483. <br>
  484. Manuelles Datenupdate
  485. </li><br>
  486. </ul>
  487. <br>
  488. <b>Readings</b>
  489. <ul>
  490. <li><code>city</code>
  491. <br>
  492. Name der Stadt, für die Prognosen gelesen werden.
  493. </li><br>
  494. <li><code>fc<i>n</i>_total</code>
  495. <br>
  496. Täglicher Höchstwerte für alle Allergene, die nicht aufgrund der Ignoreliste <i>(attr ignoreList)</i> ignoriert werden<br/>
  497. </li><br>
  498. <li><code>fc<i>n</i>_day_of_week</code>
  499. <br>
  500. Wochentag, kann durch <i>weekdaysFormat</i> lokalisiert werden.<br/>
  501. </li><br>
  502. <li><code>fc<i>n</i>_<i>allergen</i></code>
  503. <br>
  504. Tägliche Werte für alle Allergene, die nicht aufgrund der Ignoreliste <i>(attr ignoreList)</i> ignoriert werden.
  505. </li><br>
  506. </ul>
  507. <br>
  508. <b>Attribute</b>
  509. <ul>
  510. <li><code>ignoreList</code>
  511. <br>
  512. Kommagetrennte Liste von Allergen-Namen, die bei der Aktualisierung ignoriert werden sollen.
  513. <br>
  514. </li><br>
  515. <li><code>updateEmpty (Standard: 0|1)</code>
  516. <br>
  517. Aktualisierung von Allergenen.
  518. <code> <br>
  519. 0 = nur Allergene mit Belastung.
  520. <br>
  521. 1 = auch Allergene die keine Belastung haben.
  522. </code>
  523. </li><br>
  524. <li><code>updateIgnored (1)</code>
  525. <br>
  526. Aktualisierung von Allergenen, die sonst durch die ignoreList entfernt werden.
  527. </li><br>
  528. <li><code>extended5Days (1)</code>
  529. <br>
  530. Alternative Datenquelle mit 5 Tagen Vorhersage für mehr Allergene
  531. </li><br>
  532. <li><code>levelsFormat (Standard: -,low,moderate,high,extreme)</code>
  533. <br>
  534. Lokalisierte Levels, durch Kommas getrennt.
  535. </li><br>
  536. <li><code>weekdaysFormat (Standard: Sun,Mon,Tue,Wed,Thu,Fri,Sat)</code>
  537. <br>
  538. Lokalisierte Wochentage, durch Kommas getrennt.
  539. </li><br>
  540. </ul>
  541. </ul>
  542. =end html_DE
  543. =cut