| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- ##############################################
- # $Id: 60_allergy.pm 13588 2017-03-03 15:45:05Z moises $$$
- #
- # 60_allergy.pm
- #
- # 2017 Markus Moises < vorname at nachname . de >
- #
- # This module provides allergy forecast data
- #
- # http://forum.fhem.de/index.php/topic,37194.0.html
- #
- #
- ##############################################################################
- #
- # define <name> allergy <zipcode>
- #
- ##############################################################################
- package main;
- use strict;
- use warnings;
- use Time::Local;
- use Encode;
- use XML::Simple;
- use LWP::UserAgent;
- use HTTP::Request;
- use utf8;
- ##############################################################################
- sub allergy_Initialize($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- $hash->{DefFn} = "allergy_Define";
- $hash->{UndefFn} = "allergy_Undefine";
- $hash->{GetFn} = "allergy_Get";
- $hash->{AttrList} = "disable:0,1 ".
- "ignoreList ".
- "updateIgnored:1 ".
- "updateEmpty:1 ".
- "levelsFormat ".
- "weekdaysFormat ".
- $readingFnAttributes;
- }
- sub allergy_Define($$$) {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- my ($found, $dummy);
- return "syntax: define <name> allergy <zipcode>" if(int(@a) != 3 );
- my $name = $hash->{NAME};
- $hash->{helper}{ZIPCODE} = $a[2];
- $hash->{helper}{INTERVAL} = 10800;
- $hash->{ERROR} = 0;
- my $req = eval
- {
- require XML::Simple;
- XML::Simple->import();
- 1;
- };
- if($req)
- {
- InternalTimer( gettimeofday() + 60, "allergy_GetUpdate", $hash, 0);
- if (!defined($attr{$name}{stateFormat}))
- {
- $attr{$name}{stateFormat} = 'fc1_maximum';
- }
- }
- else
- {
- $hash->{STATE} = "XML::Simple is required!";
- $attr{$name}{disable} = "1";
- return undef;
- }
- $hash->{STATE} = "Initialized";
- return undef;
- }
- sub allergy_Undefine($$) {
- my ($hash, $arg) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- fhem("deletereading $name fc.*", 1);
- return undef;
- }
- sub allergy_Get($@) {
- my ($hash, @a) = @_;
- my $command = $a[1];
- my $parameter = $a[2] if(defined($a[2]));
- my $name = $hash->{NAME};
- my $usage = "Unknown argument $command, choose one of data:noArg ";
- return $usage if $command eq '?';
- RemoveInternalTimer($hash);
- if(AttrVal($name, "disable", 0) eq 1) {
- $hash->{STATE} = "disabled";
- return "allergy $name is disabled. Aborting...";
- }
- allergy_GetUpdate($hash);
- return undef;
- }
- sub allergy_GetUpdate($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- if(AttrVal($name, "disable", 0) eq 1) {
- $hash->{STATE} = "disabled";
- Log3 ($name, 2, "allergy $name is disabled, data update cancelled.");
- return undef;
- }
- my $url="http://www.allergie.hexal.de/pollenflug/xml-interface-neu/pollen_de_7tage.php?plz=".$hash->{helper}{ZIPCODE};
- Log3 ($name, 4, "Getting URL $url");
- HttpUtils_NonblockingGet({
- url => $url,
- noshutdown => 1,
- hash => $hash,
- type => 'allergydata',
- callback => \&allergy_Parse,
- });
- return undef;
- }
- sub allergy_Parse($$$)
- {
- my ($param, $err, $data) = @_;
- my $hash = $param->{hash};
- my $name = $hash->{NAME};
- if( $err )
- {
- Log3 $name, 1, "$name: URL error: ".$err;
- my $nextupdate = gettimeofday()+( (900*$hash->{ERROR}) + 90 );
- InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
- $hash->{STATE} = "error" if($hash->{ERROR} > 1);
- $hash->{ERROR} = $hash->{ERROR}+1;
- return undef;
- }
- $hash->{ERROR} = 0;
- Log3 $name, 5, "Received XML data ".$data;
- my $xml = new XML::Simple();
- my $xmldata = $xml->XMLin($data,forcearray => [qw( pollenbelastungen pollen )],keyattr => {pollen => 'name'});
- my @wdays = split(',',AttrVal($hash->{NAME}, "weekdaysFormat", "Sun,Mon,Tue,Wed,Thu,Fri,Sat" ));
- my @levels = split(',',AttrVal($hash->{NAME}, "levelsFormat", "-,low,moderate,high,extreme" ));
- readingsBeginUpdate($hash); # Start update readings
- my $city = $xmldata->{'pollendaten'}->{'ort'};
- readingsBulkUpdate($hash, "city", allergy_utf8clean($city));
- Log3 $name, 4, "Received data for postcode ".$xmldata->{'pollendaten'}->{'plz'};
- foreach my $day (@{$xmldata->{'pollendaten'}{'pollenbelastungen'}})
- {
- my $daycode = $day->{'tag'}+1;
- my @daydata = $day->{'pollen'};
- my $daymax = 0;
- my $pollenkey='';
- my $pollenvalue='';
- my $pollendata=0;
- my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time+($day->{'tag'}*86400));
- readingsBulkUpdate($hash, "fc".$daycode."_day_of_week", $wdays[$wday]);
- foreach my $pollenhash (@daydata)
- {
- while(($pollenkey, $pollenvalue) = each(%$pollenhash))
- {
- $pollenkey = allergy_utf8clean($pollenkey);
- $pollendata = $pollenvalue->{'belastung'};
- 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 )))
- {
- readingsBulkUpdate($hash, "fc".$daycode."_".$pollenkey, $levels[$pollendata]);
- $daymax = $pollendata if($pollendata gt $daymax);
- Log3 $name, 4, "Received pollen level for ".$pollenkey.": day".$daycode." level ".$pollendata;
- }
- else
- {
- fhem( "deletereading $name fc".$daycode."_".$pollenkey, 1 );
- Log3 $name, 5, "Received pollen level for ".$pollenkey.": day".$daycode." level ".$pollendata." (ignored)";
- }
- }
- }
- readingsBulkUpdate($hash, "fc".$daycode."_maximum", $levels[$daymax]);
- }
- readingsEndUpdate($hash, 1);
- $hash->{UPDATED} = FmtDateTime(time());
- my $nextupdate = gettimeofday()+$hash->{helper}{INTERVAL};
- InternalTimer($nextupdate, "allergy_GetUpdate", $hash, 1);
- return undef;
- }
- sub allergy_utf8clean($) {
- my ($string) = @_;
- my $log = "";
- if($string !~ m/^[A-Za-z\d_\.-]+$/)
- {
- $log .= $string."(standard) ";
- $string =~ s/Ä/Ae/g;
- $string =~ s/Ö/Oe/g;
- $string =~ s/Ü/Ue/g;
- $string =~ s/ä/ae/g;
- $string =~ s/ö/oe/g;
- $string =~ s/ü/ue/g;
- $string =~ s/ß/ss/g;
- }
- if($string !~ m/^[A-Za-z\d_\.-]+$/)
- {
- $log .= $string."(single) ";
- $string =~ s/Ä/Ae/g;
- $string =~ s/Ö/Oe/g;
- $string =~ s/Ü/Ue/g;
- $string =~ s/ä/ae/g;
- $string =~ s/ö/oe/g;
- $string =~ s/ü/ue/g;
- $string =~ s/ß/ss/g;
- }
- if($string !~ m/^[A-Za-z\d_\.-]+$/)
- {
- $log .= $string."(double) ";
- $string =~ s/Ä/Ae/g;
- $string =~ s/Ö/Oe/g;
- $string =~ s/Ü/Ue/g;
- $string =~ s/ä/ae/g;
- $string =~ s/ö/oe/g;
- $string =~ s/ü/ue/g;
- $string =~ s/ß/ss/g;
- }
- if($string !~ m/^[A-Za-z\d_\.-]+$/)
- {
- $log .= $string."(unknown)";
- #$string =~ s/[^!-~\s]//g;
- $string =~ s/[^A-Za-z\d_\.-]//g;
- }
- Log3 "utf8clean", 5, "Cleaned $string // $log" if($log ne "");
- return $string;
- }
- ##########################
- 1;
- =pod
- =item device
- =item summary Allergy forecast data for Germany
- =begin html
- <a name="allergy"></a>
- <h3>allergy</h3>
- <ul>
- This modul provides allergy forecast data for Germany.<br/>
- It requires the Perl module XML::Simple to be installed
- <br/><br/>
- <b>Define</b>
- <ul>
- <code>define <name> allergy <zipcode></code>
- <br>
- Example: <code>define allergydata allergy 12345</code>
- <br>
- <li><code>zipcode</code>
- <br>
- German zipcode
- </li><br>
- </ul>
- <br>
- <b>Get</b>
- <ul>
- <li><code>data</code>
- <br>
- Manually trigger data update
- </li><br>
- </ul>
- <br>
- <b>Readings</b>
- <ul>
- <li><code>city</code>
- <br>
- Name of the city the forecast is read for
- </li><br>
- <li><code>fc<i>n</i>_total</code>
- <br>
- Daily maximum levels for all allergens that are not being ignored due to <i>ignoreList</i><br/>
- </li><br>
- <li><code>fc<i>n</i>_day_of_week</code>
- <br>
- Weekday, can be localized through <i>weekdaysFormat</i><br/>
- </li><br>
- <li><code>fc<i>n</i>_<i>allergen</i></code>
- <br>
- Daily levels for all allergens that are not being ignored due to <i>ignoreList</i>
- </li><br>
- </ul>
- <br>
- <b>Attributes</b>
- <ul>
- <li><code>ignoreList</code>
- <br>
- Comma-separated list of allergen names that are to be ignored during updates and for cumulated day levels calculation
- </li><br>
- <li><code>updateEmpty</code>
- <br>
- Also update (and keep) level readings for inactive allergens that are otherwise removed
- </li><br>
- <li><code>updateIgnored</code>
- <br>
- Also update (and keep) level readings for ignored allergens that are otherwise removed
- </li><br>
- <li><code>levelsFormat</code>
- <br>
- Localize levels by adding them comma separated (default: -,low,moderate,high,extreme)
- </li><br>
- <li><code>weekdaysFormat</code>
- <br>
- Localize Weekdays by adding them comma separated (default: Sun,Mon,Tue,Wed,Thu,Fr,Sat)
- </li><br>
- </ul>
- </ul>
- =end html
- =begin html_DE
- <a name="allergy"></a>
- <h3>allergy</h3>
- <ul>
- <br>Dieses Modul prognostiziert Allergie Daten für Deutschland.</br>
- Es erfordert dass das Perlmodul XML:: Simple installiert ist.
- <br/><br/>
- <b>Define</b>
- <ul>
- <code>define <name> allergy <Postleitzahl></code>
- <br>
- Beispiel: <code>define allergydata allergy 12345</code>
- <br><br>
- <li><code>Postleitzahl</code>
- <br>
- Deutsche Postleitzahl
- </li><br>
- </ul>
- <br>
- <b>Get</b>
- <ul>
- <li><code>data</code>
- <br>
- Manuelles Datenupdate
- </li><br>
- </ul>
- <br>
- <b>Readings</b>
- <ul>
- <li><code>city</code>
- <br>
- Name der Stadt, für die Prognosen gelesen werden.
- </li><br>
- <li><code>fc<i>n</i>_total</code>
- <br>
- Täglicher Höchstwerte für alle Allergene, die nicht aufgrund der Ignoreliste <i>(attr ignoreList)</i> ignoriert werden<br/>
- </li><br>
- <li><code>fc<i>n</i>_day_of_week</code>
- <br>
- Wochentag, kann durch <i>weekdaysFormat</i> lokalisiert werden.<br/>
- </li><br>
- <li><code>fc<i>n</i>_<i>allergen</i></code>
- <br>
- Tägliche Werte für alle Allergene, die nicht aufgrund der Ignoreliste <i>(attr ignoreList)</i> ignoriert werden.
- </li><br>
- </ul>
- <br>
- <b>Attribute</b>
- <ul>
- <li><code>ignoreList</code>
- <br>
- Kommagetrennte Liste von Allergen-Namen, die bei der Aktualisierung ignoriert werden sollen.
- <br>
- </li><br>
- <li><code>updateEmpty (Standard: 0|1)</code>
- <br>
- Aktualisierung von Allergenen.
- <code> <br>
- 0 = nur Allergene mit Belastung.
- <br>
- 1 = auch Allergene die keine Belastung haben.
- </code>
- </li><br>
- <li><code>updateIgnored (1)</code>
- <br>
- Aktualisierung von Allergenen, die sonst durch die ignoreList entfernt werden.
- </li><br>
- <li><code>levelsFormat (Standard: -,low,moderate,high,extreme)</code>
- <br>
- Lokalisierte Levels, durch Kommas getrennt.
- </li><br>
- <li><code>weekdaysFormat (Standard: Sun,Mon,Tue,Wed,Thu,Fri,Sat)</code>
- <br>
- Lokalisierte Wochentage, durch Kommas getrennt.
- </li><br>
- </ul>
- </ul>
- =end html_DE
- =cut
|