#################################################################################################### # $Id$ # # 59_PROPLANTA.pm # # (c) 2014 Torsten Poitzsch # (c) 2014-2016 tupol http://forum.fhem.de/index.php?action=profile;u=5432 # # Weather forecast values for 12 days are captured from www.proplanta.de # inspired by 23_KOSTALPIKO.pm # # Copyright notice # # This script is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html. # A copy is found in the text file GPL.txt and important notices to the license # from the author is found in LICENSE.txt distributed with these scripts. # # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # This copyright notice MUST APPEAR in all copies of the script! # #################################################################################################### ############################################### # parser for the weather data package MyProplantaParser; use base qw(HTML::Parser); use Time::HiRes qw(usleep); our @texte = (); my $lookupTag = "span|b"; my $curTag = ""; my $curReadingName = ""; my $curRowID = ""; my $curCol = 0; our $startDay = 0; my $curTextPos = 0; my $curReadingType = 0; # 1 = span|b Text, 2 = readingName, 3 = Tag-Type # Tag-Types: # 1 = Number Col 3 # 2 = Number Col 2-5 # 3 = Number Col 2|4|6|8 # 4 = Intensity-Text Col 2-5 # 5 = Time Col 2-5 # 6 = Time Col 3 # 7 = alternative text of image Col 2-5 (weather state) # 8 = MinMaxNummer Col 3 (cloud base) # 9 = Date Col 2-5 / bold # 10 = alternative text of Col 3 (wind direction) # 11 = alternative text of image Col 3 (current weather state) # 12 = Text Col 3 my @knownNoneIDs = ( ["Temperatur", "temperature", 1] ,["relative Feuchte", "humidity", 1] ,["Sichtweite", "visibility", 1] ,["Wetterzustand", "weather", 11] ,["Windrichtung", "windDir", 10] ,["Windgeschwindigkeit", "wind", 1] ,["Luftdruck", "pressure", 1] ,["Taupunkt", "dewPoint", 1] ,["Uhrzeit", "obsTime", 6] ,["Höhe der", "cloudBase", 8] ,["Höhe der", "cloudBase", 8] ); # 1 = Tag-ID, 2 = readingName, 3 = Tag-Type (see above) my @knownIDs = ( ["DATUM", "date", 9] ,["TMAX", "tempMax", 2] ,["TMIN", "tempMin", 2] ,["NW", "chOfRainDay", 2] ,["NW_Nacht", "chOfRainNight", 2] ,["BF", "frost", 4] ,["VERDUNST", "evapor", 4] ,["TAUBILDUNG", "dew", 4] ,["SD", "sun", 2] ,["UV", "uv", 2] ,["GS", "rad", 3] ,["WETTER_ID", "weather", 7] ,["WETTER_ID_MORGENS", "weatherMorning", 7] ,["WETTER_ID_TAGSUEBER", "weatherDay", 7] ,["WETTER_ID_ABENDS", "weatherEvening", 7] ,["WETTER_ID_NACHT", "weatherNight", 7] ,["WETTER_ID_0", "weather00", 7] ,["WETTER_ID_3", "weather03", 7] ,["WETTER_ID_6", "weather06", 7] ,["WETTER_ID_9", "weather09", 7] ,["WETTER_ID_12", "weather12", 7] ,["WETTER_ID_15", "weather15", 7] ,["WETTER_ID_18", "weather18", 7] ,["WETTER_ID_21", "weather21", 7] ,["T_0", "temp00", 2] ,["T_3", "temp03", 2] ,["T_6", "temp06", 2] ,["T_9", "temp09", 2] ,["T_12", "temp12", 2] ,["T_15", "temp15", 2] ,["T_18", "temp18", 2] ,["T_21", "temp21", 2] ,["NW_0", "chOfRain00", 2] ,["NW_3", "chOfRain03", 2] ,["NW_6", "chOfRain06", 2] ,["NW_9", "chOfRain09", 2] ,["NW_12", "chOfRain12", 2] ,["NW_15", "chOfRain15", 2] ,["NW_18", "chOfRain18", 2] ,["NW_21", "chOfRain21", 2] ,["NS_0", "rain00", 2] ,["NS_3", "rain03", 2] ,["NS_6", "rain06", 2] ,["NS_9", "rain09", 2] ,["NS_12", "rain12", 2] ,["NS_15", "rain15", 2] ,["NS_18", "rain18", 2] ,["NS_21", "rain21", 2] ,["NS_24H", "rain", 2] ,["BD_0", "cloud00", 2] ,["BD_3", "cloud03", 2] ,["BD_6", "cloud06", 2] ,["BD_9", "cloud09", 2] ,["BD_12", "cloud12", 2] ,["BD_15", "cloud15", 2] ,["BD_18", "cloud18", 2] ,["BD_21", "cloud21", 2] ,["SA", "sunRise", 5] ,["SU", "sunSet", 5] ,["MA", "moonRise", 5] ,["MU", "moonSet", 5] ,["WGRAD_0", "windDir00", 2] ,["WGRAD_3", "windDir03", 2] ,["WGRAD_6", "windDir06", 2] ,["WGRAD_9", "windDir09", 2] ,["WGRAD_12", "windDir12", 2] ,["WGRAD_15", "windDir15", 2] ,["WGRAD_18", "windDir18", 2] ,["WGRAD_21", "windDir21", 2] ,["WGESCHW_0", "wind00", 2] ,["WGESCHW_3", "wind03", 2] ,["WGESCHW_6", "wind06", 2] ,["WGESCHW_9", "wind09", 2] ,["WGESCHW_12", "wind12", 2] ,["WGESCHW_15", "wind15", 2] ,["WGESCHW_18", "wind18", 2] ,["WGESCHW_21", "wind21", 2] ,["BGESCHW_0", "gust00", 2] ,["BGESCHW_3", "gust03", 2] ,["BGESCHW_6", "gust06", 2] ,["BGESCHW_9", "gust09", 2] ,["BGESCHW_12", "gust12", 2] ,["BGESCHW_15", "gust15", 2] ,["BGESCHW_18", "gust18", 2] ,["BGESCHW_21", "gust21", 2] ); my %intensity = ( "keine" => 0 ,"nein" => 0 ,"gering" => 1 ,"leicht" => 1 ,"ja" => 1 ,"mäßig" => 2 ,"mäßig" => 2 ,"stark" => 3 ); my %winddir = ( "Nord" => 0 ,"Nord-Nordost" => 23 ,"Nordost" => 45 ,"Ost-Nordost" => 68 ,"Ost" => 90 ,"Ost-Südost" => 113 ,"Ost-Südost" => 113 ,"Südost" => 135 ,"Südost" => 135 ,"Süd-Südost" => 158 ,"Süd-Südost" => 158 ,"Süd" => 180 ,"Süd" => 180 ,"Süd-Südwest" => 203 ,"Süd-Südwest" => 203 ,"Südwest" => 225 ,"Südwest" => 225 ,"West-Südwest" => 248 ,"West-Südwest" => 248 ,"West" => 270 ,"West-Nordwest" => 303 ,"Nordwest" => 325 ,"Nord-Nordwest" => 348 ); ############################################## sub get_wday($) { my ($date) = @_; my @wday_txt = qw(So Mo Di Mi Do Fr Sa); my @th=localtime $date; return $wday_txt [$th[6]]; } ############################################## # here HTML::text/start/end are overridden sub text { my ( $self, $text ) = @_; my $found = 0; my $readingName; # Wait 1ms to reduce CPU load and hence blocking of FHEM by it (workaround until a better solution is available) usleep (1000); if ( $curTag =~ $lookupTag ) { $curTextPos++; $text =~ s/^\s+//; # trim string $text =~ s/\s+$//; $text =~ s/0/0/g; # replace 0 $text =~ s/1/1/g; # replace 1 $text =~ s/2/2/g; # replace 2 $text =~ s/3/3/g; # replace 3 $text =~ s/4/4/g; # replace 4 $text =~ s/5/5/g; # replace 5 $text =~ s/6/6/g; # replace 6 $text =~ s/7/7/g; # replace 7 $text =~ s/8/8/g; # replace 8 $text =~ s/9/9/g; # replace 9 # Tag-Type 0 = Check for readings without tag-ID (current readings) if ($curReadingType == 0) { if ($startDay == 0 && $curCol == 1 && $curTextPos == 1) { foreach my $r (@knownNoneIDs) { if ( $$r[0] eq $text ) { $curReadingName = $$r[1]; $curReadingType = $$r[2]; last; } } } } # Tag-Type 1 = Number Col 3 elsif ($curReadingType == 1) { if ( $curCol == 3 ) { $readingName = $curReadingName; if ( $text =~ m/([-,\+]?\d+[,\.]?\d*)/ ) { $text = $1; $text =~ tr/,/./; # komma durch punkt ersetzen } push( @texte, $readingName."|".$text ); $curReadingType = 0; } } # Tag-Type 2 = Number Col 2-5 elsif ($curReadingType == 2) { if ( 1 < $curCol && $curCol <= 5 ) { $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName; if ( $text =~ m/([-+]?\d+[,.]?\d*)/ ) { $text = $1; $text =~ tr/,/./; # komma durch punkt ersetzen } push( @texte, $readingName."|".$text ); } } # Tag-Type 3 = Number Col 2|4|6|8 elsif ($curReadingType == 3) { if ( 2 <= $curCol && $curCol <= 5 ) { if ( $curTextPos % 2 == 1 ) { $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName; $text =~ tr/,/./; # komma durch punkt ersetzen push( @texte, $readingName."|".$text ); } } } # Tag-Type 4 = Intensity-Text Col 2-5 elsif ($curReadingType == 4) { if ( 2 <= $curCol && $curCol <= 5 ) { $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName; $text = $intensity{$text} if defined $intensity{$text}; push( @texte, $readingName . "|" . $text ); } } # Tag-Type 5 = Time Col 2-5 elsif ($curReadingType == 5) { if ( 2 <= $curCol && $curCol <= 5 ) { $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName; if ( $text =~ m/([012-]?[-0-9][.:][-0-5][-0-9])/ ) { $text = $1; $text =~ tr/./:/; # Punkt durch Doppelpunkt ersetzen } push( @texte, $readingName."|".$text ); } } # Tag-Type 6 = Time Col 3 elsif ($curReadingType == 6) { if ( $curCol == 3 ) { $readingName = $curReadingName; if ( $text =~ m/([012-]?[-0-9][.:][-0-5][-0-9])/ ) { $text = $1; $text =~ tr/./:/; # Punkt durch Doppelpunkt ersetzen } push( @texte, $readingName."|".$text ); } } # Tag-Type 8 = MinMaxNumber Col 3 elsif ($curReadingType == 8) { if ( $curCol == 3 ) { $readingName = $curReadingName; if ( $text =~ m/(\d+)\s*-\s*(\d+)/ ) { push( @texte, $readingName."Min|".$1 ); push( @texte, $readingName."Max|".$2 ); } else { push( @texte, $readingName."Min|-" ); push( @texte, $readingName."Max|-" ); } } } # Tag-Type 9 = Date Col 2-5 elsif ($curReadingType == 9 && $curTag eq "b") { if ( 1 < $curCol && $curCol <= 5 ) { $readingName = "fc".($startDay+$curCol-2)."_".$curReadingName; push( @texte, $readingName."|".$text ); } } # Tag-Type 12 = Text Col 3 elsif ($curReadingType == 12) { if ( $curCol == 3 ) { $readingName = $curReadingName; push( @texte, $readingName."|".$text ); $curReadingType = 0; } } } } ############################################## sub start { my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_; $curTag = $tagname; if ( $tagname eq "tr" ) { $curReadingType = 0; $curCol = 0; $curTextPos = 0; if ( defined( $attr->{id} ) ) { foreach my $r (@knownIDs) { if ( $$r[0] eq $attr->{id} ) { $curReadingName = $$r[1]; $curReadingType = $$r[2]; last; } } } } elsif ($tagname eq "td") { $curCol++; $curTextPos = 0; } #wetterstate and icon - process immediately elsif ($tagname eq "img" && ( $curReadingType == 7 && 2 <= $curCol && $curCol <= 5 || $curReadingType == 11 && $curCol == 3) ) { # process alternative text $readingName = $curReadingName; $readingName = "fc" . ($startDay+$curCol-2) . "_" . $readingName if $curReadingType == 7; $text = $attr->{alt}; $text =~ s/Wetterzustand: //; # $text =~ s/ö/oe/; # $text =~ s/ä/ae/; # $text =~ s/ü/ue/; # $text =~ s/ß/ss/; push( @texte, $readingName . "|" . $text ); # Image URL push( @texte, $readingName."Icon" . "|" . $attr->{src} ); } #wind direction - process immediately elsif ($tagname eq "img" && $curReadingType == 10 && $curCol == 3 ) { # process alternative text $readingName = $curReadingName; $text = $attr->{alt}; $text =~ s/Windrichtung: //; $text = $winddir{$text} if defined $winddir{$text}; # $text =~ s/ö/oe/; # $text =~ s/ä/ae/; # $text =~ s/ü/ue/; # $text =~ s/ß/ss/; push( @texte, $readingName . "|" . $text ); # Image URL push( @texte, $readingName."Icon" . "|" . $attr->{src} ); } } ############################################## sub end { my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_; $curTag = ""; if ( $tagname eq "tr" ) { $curReadingType = 0 }; } ############################################## package main; use strict; use feature qw/say switch/; use Encode qw/from_to/; use warnings; my $missingModul; eval "use LWP::UserAgent;1" or $missingModul .= "LWP::UserAgent "; eval "use HTTP::Request;1" or $missingModul .= "HTTP::Request "; eval "use HTML::Parser;1" or $missingModul .= "HTML::Parser "; eval "use MIME::Base64;1" or $missingModul .= "MIME::Base64 "; require 'Blocking.pm'; require 'HttpUtils.pm'; use vars qw($readingFnAttributes); use vars qw(%defs); my $MODUL = "PROPLANTA"; # my %url_template_1 =( "de" => "http://www.proplanta.de/Wetter/LOKALERORT-Wetter.html" # , "at" => "http://www.proplanta.de/Agrarwetter-Oesterreich/LOKALERORT/" # , "ch" => "http://www.proplanta.de/Agrarwetter-Schweiz/LOKALERORT/" # , "fr" => "http://www.proplanta.de/Agrarwetter-Frankreich/LOKALERORT.html" # , "it" => "http://www.proplanta.de/Agrarwetter-Italien/LOKALERORT.html" # ); my %url_template = ( "de" => "https://www.proplanta.de/Wetter/profi-wetter.php?SITEID=60&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter&wT=" , "at" => "https://www.proplanta.de/Wetter-Oesterreich/profi-wetter-at.php?SITEID=70&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter&wT=" , "ch" => "https://www.proplanta.de/Wetter-Schweiz/profi-wetter-ch.php?SITEID=80&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter&wT=" , "fr" => "https://www.proplanta.de/Wetter-Frankreich/profi-wetter-fr.php?SITEID=50&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter-Frankreich&wT=" , "it" => "https://www.proplanta.de/Wetter-Italien/profi-wetter-it.php?SITEID=40&PLZ=LOKALERORT&STADT=LOKALERORT&WETTERaufrufen=stadt&Wtp=&SUCHE=Wetter-Italien&wT=" ); ######################################## sub PROPLANTA_Log($$$) { my ( $hash, $loglevel, $text ) = @_; my $xline = ( caller(0) )[2]; my $xsubroutine = ( caller(1) )[3]; my $sub = ( split( ':', $xsubroutine ) )[2]; $sub =~ s/PROPLANTA_//; my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : $hash; Log3 $instName, $loglevel, "$MODUL $instName: $sub.$xline " . $text; } ################################### sub PROPLANTA_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "PROPLANTA_Define"; $hash->{UndefFn} = "PROPLANTA_Undef"; $hash->{SetFn} = "PROPLANTA_Set"; $hash->{AttrList} = "INTERVAL URL disable:0,1 forecastDays:4,7,11,14 " . $readingFnAttributes; } ################################### sub PROPLANTA_Define($$) { my ( $hash, $def ) = @_; my $name = $hash->{NAME}; my $lang = ""; my @a = split( "[ \t][ \t]*", $def ); return "Error: Perl moduls ".$missingModul."are missing on this system" if $missingModul; return "Wrong syntax: use define PROPLANTA [City] [CountryCode]" if int(@a) > 4; $lang = "de" if int(@a) == 3; $lang = lc( $a[3] ) if int(@a) == 4; if ( $lang ne "") { # {my $test="http://www.proplanta.de/Wetter/LOKALERORT-Wetter.html";; $test =~ s/LOKALERORT/München/g;; return $test;;} return "Wrong country code '$lang': use " . join(" | ", keys( %url_template ) ) unless defined( $url_template{$lang} ); my $URL = $url_template{$lang}; my $ort_encode= $a[2]; # change Umlaute from UTF8 in Percent-encode $ort_encode =~ s/Ä|Ä/%C4/g; $ort_encode =~ s/Ö|Ö/%D6/g; $ort_encode =~ s/Ü|Ü/%DC/g; $ort_encode =~ s/ß|ß/%DF/g; $ort_encode =~ s/ä|ä/%E4/g; $ort_encode =~ s/ö|ö/%F6/g; $ort_encode =~ s/ü|ü/%FC/g; $URL =~ s/LOKALERORT/$ort_encode/g; $hash->{URL} = $URL; # $URL = $url_template_2{$lang}; # $URL =~ s/LOKALERORT/$ort/g; # $hash->{URL2} = $URL; } $hash->{STATE} = "Initializing"; $hash->{fhem}{LOCAL} = 0; $hash->{INTERVAL} = 3600; $hash->{fhem}{modulVersion} = '$Date$'; RemoveInternalTimer($hash); #Get first data after 32 seconds InternalTimer( gettimeofday() + 32, "PROPLANTA_Start", $hash, 0 ); return undef; } ##################################### sub PROPLANTA_Undef($$) { my ( $hash, $arg ) = @_; RemoveInternalTimer( $hash ); BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) ); return undef; } ##################################### sub PROPLANTA_Set($@) { my ($hash, $name, $cmd, @val) = @_; # my $reUINT = '^([\\+]?\\d+)$'; my $usage = "Unknown argument $cmd, choose one of update:noArg "; return $usage unless defined $cmd; if ( $cmd eq "?" ) { return $usage; } elsif ( $cmd eq "update" ) { Log3 $name, 3, "PROPLANTA: set $name $cmd ".join(" ", @val); $hash->{fhem}{LOCAL} = 1; PROPLANTA_Start($hash); $hash->{fhem}{LOCAL} = 0; } else { return $usage; } return; } ##################################### # acquires the html page sub PROPLANTA_HtmlAcquire($$) { my ($hash, $URL) = @_; my $name = $hash->{NAME}; return unless (defined($hash->{NAME})); PROPLANTA_Log $hash, 4, "Start capturing of $URL"; #$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=0; my $err_log = ""; my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, protocols_allowed => ['http', 'https'], timeout => 10 , agent => "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" ); my $request = HTTP::Request->new( GET => $URL ); my $response = $agent->request($request); $err_log = "Can't get $URL -- " . $response->status_line unless $response->is_success; if ( $err_log ne "" ) { readingsSingleUpdate($hash, "lastConnection", $response->status_line, 1); PROPLANTA_Log $hash, 1, "Error: $err_log"; return "Error|Error " . $response->status_line; } PROPLANTA_Log $hash, 4, length($response->content)." characters captured"; my $returnString = $response->content; from_to($returnString,"iso-8859-1","utf8", Encode::FB_QUIET); return $returnString; } ##################################### sub PROPLANTA_Start($) { my ($hash) = @_; my $name = $hash->{NAME}; return unless (defined($hash->{NAME})); $hash->{INTERVAL} = AttrVal( $name, "INTERVAL", $hash->{INTERVAL} ); readingsSingleUpdate($hash, "state", "disabled", 1) if AttrVal($name, "disable", 0 ) == 1; if($hash->{INTERVAL} > 0) { # reset timer if interval is defined RemoveInternalTimer( $hash ); InternalTimer(gettimeofday() + $hash->{INTERVAL}, "PROPLANTA_Start", $hash, 1 ); return undef if AttrVal($name, "disable", 0 ) == 1 && !$hash->{fhem}{LOCAL}; } if ( AttrVal( $name, 'URL', '') eq '' && not defined( $hash->{URL} ) ) { PROPLANTA_Log $hash, 3, "missing URL"; return; } # "Set update"-action will kill a running update child process if (defined ($hash->{helper}{RUNNING_PID}) && $hash->{fhem}{LOCAL}) { BlockingKill($hash->{helper}{RUNNING_PID}); delete( $hash->{helper}{RUNNING_PID} ); PROPLANTA_Log $hash, 4, "Killing old forked process"; } unless (defined ($hash->{helper}{RUNNING_PID})) { $hash->{helper}{RUNNING_PID} = BlockingCall( "PROPLANTA_Run", # callback worker task $name, # name of the device "PROPLANTA_Done", # callback result method 120, # timeout seconds "PROPLANTA_Aborted", # callback for abortion $hash ); # parameter for abortion PROPLANTA_Log $hash, 4, "Start forked process to capture html"; } else { PROPLANTA_Log $hash, 1, "Could not start forked process, old process still running"; } } ##################################### sub PROPLANTA_Run($) { eval "setpriority( 0, 0, 10);"; #work-around for old perl my ($name) = @_; my $ptext=$name; my $URL; my $response; return unless ( defined($name) ); my $hash = $defs{$name}; return unless (defined($hash->{NAME})); my $readingStartTime = time(); my $fcDays = AttrVal( $name, 'forecastDays', 14 ); my $parser = MyProplantaParser->new; # get date from Attribut URL only my $attrURL = AttrVal( $name, 'URL', "" ); if ($attrURL ne "") { $response = PROPLANTA_HtmlAcquire($hash,$attrURL); if ($response =~ /^Error\|/) { $ptext .= "|".$response; } else { PROPLANTA_Log $hash, 4, "Start HTML parsing of captured page"; $parser->report_tags(qw(tr td span b img)); @MyProplantaParser::texte = (); $MyProplantaParser::startDay = 0; # parsing the complete html-page-response, needs some time $parser->parse($response); } } # Get data from location specified in define else { $URL = $hash->{URL}; my @URL_days = (0, 4, 7, 11); foreach (@URL_days) { last unless $_ < $fcDays; $response = PROPLANTA_HtmlAcquire($hash,$URL . $_); $MyProplantaParser::startDay = $_; if ($response !~ /^Error\|/) { PROPLANTA_Log $hash, 4, "Start HTML parsing of captured page"; $parser->parse($response); } } PROPLANTA_Log $hash, 4, "Found terms: " . @MyProplantaParser::texte; # pack the results in a single string if (@MyProplantaParser::texte > 0) { $ptext .= "|". join('|', @MyProplantaParser::texte); } PROPLANTA_Log $hash, 5, "Parsed string: " . $ptext; } $ptext .= "|durationFetchReadings|"; $ptext .= sprintf "%.2f", time() - $readingStartTime; return $ptext; } ##################################### # asyncronous callback by blocking sub PROPLANTA_Done($) { my ($string) = @_; return unless ( defined($string) ); # all term are separated by "|" , the first is the name of the instance my ( $name, %values ) = split( "\\|", $string ); my $hash = $defs{$name}; return unless ( defined($hash->{NAME}) ); PROPLANTA_Log $hash, 4, "Forked process successfully finished"; # delete the marker for RUNNING_PID process delete( $hash->{helper}{RUNNING_PID} ); # Wetterdaten speichern readingsBeginUpdate($hash); if ( defined $values{Error} ) { readingsBulkUpdate( $hash, "lastConnection", $values{Error} ); } else { my $x = 0; my $fcDays = AttrVal( $name, 'forecastDays', 14 ); while (my ($rName, $rValue) = each(%values) ) { if ($fcDays < 14 && $rName =~ /^fc(\d+)_/) { next unless $1 < $fcDays; } readingsBulkUpdate( $hash, $rName, $rValue ); PROPLANTA_Log $hash, 5, "reading:$rName value:$rValue"; } if (keys %values > 0) { my $newState; if (defined $values{fc0_tempMin} && defined $values{fc0_tempMax}) { $newState = "Tmin: " . $values{fc0_tempMin} . " Tmax: " . $values{fc0_tempMax}; # Achtung! Nach Mitternacht fehlen für 1 h die aktuellen Werte $newState .= " T: " . $values{temperature} . " H: " . $values{humidity} . " W: " . $values{wind} . " P: " . $values{pressure} if defined $values{temperature} && defined $values{humidity} && defined $values{wind} && defined $values{pressure}; } else { $newState = "Error: Could not capture all data. Please check URL or city name."; } readingsBulkUpdate($hash, "state", $newState); readingsBulkUpdate( $hash, "lastConnection", keys( %values )." values captured in ".$values{durationFetchReadings}." s" ); PROPLANTA_Log $hash, 4, keys( %values )." values captured"; } else { readingsBulkUpdate( $hash, "lastConnection", "no data found" ); PROPLANTA_Log $hash, 1, "No data found. Check city name or URL."; } } readingsEndUpdate( $hash, 1 ); } ##################################### sub PROPLANTA_Aborted($) { my ($hash) = @_; delete( $hash->{helper}{RUNNING_PID} ); PROPLANTA_Log $hash, 4, "Forked process timed out"; } ##### noch nicht fertig ########### sub ##################################### PROPLANTA_Html(@) { my ($d,$days) = @_; $d = "" if(!$d); $days = 3 unless defined $days; return "$d is not a PROPLANTA instance
" if(!$defs{$d} || $defs{$d}{TYPE} ne "PROPLANTA"); my $uselocal= 0; #AttrVal($d,"localicons",0); my $isday; if ( exists &isday) { $isday = isday(); } else { $isday = 1; #($hour>6 && $hour<19); } my $ret = ""; $ret .= sprintf '', $defs{$d}{DEF}; $ret .= ''; $ret .= ""; # define MyForecast weblink htmlCode { PROPLANTA_Html("ProPlanta_Wetter") } for(my $i=0; $i<$days; $i++) { $ret .= sprintf('', ReadingsVal($d, "fc".$i."_date", ""), ReadingsVal($d, "fc".$i."_weatherMorning", ""), ReadingsVal($d, "fc".$i."_weatherMorningIcon", ""), ReadingsVal($d, "fc".$i."_weatherDay", ""), ReadingsVal($d, "fc".$i."_weatherDayIcon", ""), ReadingsVal($d, "fc".$i."_weatherEvening", ""), ReadingsVal($d, "fc".$i."_weatherEveningIcon", ""), ReadingsVal($d, "fc".$i."_weatherNight", ""), ReadingsVal($d, "fc".$i."_weatherNightIcon", ""), ReadingsVal($d, "fc".$i."_tempMin", ""), ReadingsVal($d, "fc".$i."_tempMax", ""), ReadingsVal($d, "fc".$i."_chOfRainDay", ""), ReadingsVal($d, "fc".$i."_frost", "") ? "ja" : "nein" ); } # for(my $i=0; $i<=4; $i++) { # $ret .= sprintf('', # WWOIconIMGTag(ReadingsVal($d, "fc${i}_weatherDayIcon", ""),$uselocal,$isday), # ReadingsVal($d, "fc${i}_date", ""), # ReadingsVal($d, "fc${i}_weatherDay", ""), # ReadingsVal($d, "fc${i}_tempMinC", ""), ReadingsVal($d, "fc${i}_tempMaxC", ""), # } $ret .= "
%s
TagmorgenstagsueberabendsnachtsminmaxRegen tagsFrost
%s%s
%s
%s
%s
%s°C%s°C%s %%%s
%s%s: %s
min %s °C max %s °C
wind: %s km/h %s
precip: %s mm
"; return $ret; } sub ##################################### PROPLANTA_Html_Landscape(@) { my ($d,$days) = @_; $d = "" if(!$d); $days = 3 unless defined $days; return "$d is not a PROPLANTA instance
" if(!$defs{$d} || $defs{$d}{TYPE} ne "PROPLANTA"); my $uselocal= 0; #AttrVal($d,"localicons",0); my $isday; if ( exists &isday) { $isday = isday(); } else { $isday = 1; #($hour>6 && $hour<19); } my $ret = ""; $ret .= ""; $ret .= sprintf '', $defs{$d}{DEF}; $ret .= ''; for(my $i=0; $i<$days; $i++) { if ($i==0) { $ret .= '' : ''; }; $ret .= ''; for(my $i=0; $i<$days; $i++) { $ret .= ''; }; $ret .= ''; for(my $i=0; $i<$days; $i++) { $ret .= ''; }; $ret .= ''; for(my $i=0; $i<$days; $i++) { $ret .= ''; }; $ret .= "
%s
Tagheute'; } elsif ($i==1) { $ret .= 'morgen'; } else { $ret .= ''.substr(ReadingsVal($d, "fc".$i."_date", ""),0,5); } $ret .= ReadingsVal($d, "fc".$i."_frost", "") ? ' (Frost)
Temp'.ReadingsVal($d, "fc".$i."_tempMin", "").'-'.ReadingsVal($d, "fc".$i."_tempMax", "").'℃
Regen'.ReadingsVal($d, "fc".$i."_chOfRainDay", "").'%
Wetter |
"; return $ret; } ##################################### 1; =pod =item device =item summary extracts weather data from www.proplanta.de =item summary_DE Extrahiert Wetterdaten von www.proplanta.de =begin html

PROPLANTA

    The module extracts weather data from www.proplanta.de.
    The website provides a forecast for 12 days, for the first 7 days in a 3-hours-interval.
    This module causes a high CPU load. It is recommended to reduce the number of captured forecast days.
    It uses the perl modules HTTP::Request, LWP::UserAgent and HTML::Parse.

    Define

      define <name> PROPLANTA [City] [CountryCode]
      Example:
      define wetter PROPLANTA Bern ch
      define wetter PROPLANTA Wittingen+(Niedersachsen)
       
    • [City]
      Optional. The city must be selectable on www.proplanta.de.
      Please pay attention to the Capital letters in the city names. Spaces within the name are replaced by a + (plus).

    • [CountryCode]
      Optional. Possible values: de (default), at, ch, fr, it

    • The function PROPLANTA_Html and PROPLANTA_Html_Landscape creates a HTML code for a weather forecast for the given days (default is 3).
      Example:
      define HTMLForecast weblink htmlCode { PROPLANTA_Html("ProPlanta_Wetter"[, days])}


    Set

    • set <name> update
      The weather data are immediately polled from the website.


    Attributes

    • forecastDays <4-14>
      Number of days for which the forecast data shall be fetched. Default is 14 days (incl. today).

    • INTERVAL <seconds>
      Poll interval for weather data in seconds (default 3600 = 1 hour)

    • URL <internet address>
      URL to extract information from. Overwrites the values in the 'define' term.

    • readingFnAttributes

    Forecast readings

    • fc0|1|2|3|...|13_... - forecast values for today|tommorrow|in 2|3|...|13 days
    • fc0_...00|03|06|09|12|15|18|21 - forecast values for today at 00|03|06|09|12|15|18|21 o'clock
    • fc0_chOfRainDay|Night - chance of rain today by day|night in %
    • fc0_chOfRain15 - chance of rain today at 15:00 in %
    • fc0_cloud15 - cloud coverage today at 15:00 in %
    • fc0_dew - dew formation today (0=none, 1=small, 2=medium, 3=strong)
    • fc0_evapor - evaporation today (0=none, 1=small, 2=medium, 3=strong)
    • fc0_frost - ground frost today (0=no, 1=yes)
    • fc0_gust15 - maximal wind gusts today at 15:00 o'clock in km/h
    • fc0_moonRise|Set - moon rise|set today
    • fc0_rad - global radiation today
    • fc0_rain15 - amount of rainfall today at 15:00 o'clock in mm
    • fc0_sun - relative sun shine duration today in % (between sun rise and set)
    • fc0_tempMin|Max - minimal|maximal temperature today in °C
    • fc0_temp15 - temperatur today at 15:00 o'clock in °C
    • fc0_uv - UV-Index today
    • fc0_weatherMorning|Day|Evening|Night - weather situation today morning|during day|in the evening|during night
    • fc0_weatherDayIcon - icon of weather situation today by day
    • fc0_wind15 - wind speed today at 15:00 Uhr in km/h
    • fc0_windDir15 - wind direction today at 15:00 Uhr in °
    • etc.

=end html =begin html_DE

PROPLANTA

    Das Modul extrahiert Wetterdaten von der Website www.proplanta.de.
    Es stellt eine Vorhersage für 12 Tage zur Verfügung - während der ersten 7 Tage im 3-Stunden-Intervall.
    Dieses Modul erzeugt eine hohe CPU-Last. Es wird deshalb empfohlen, die auszulesenden Vorhersagetage zu reduzieren.
    Es nutzt die Perl-Module HTTP::Request, LWP::UserAgent und HTML::Parse.
    Für detaillierte Anleitungen bitte die FHEM-Wiki konsultieren und ergänzen.

    Define

      define <Name> PROPLANTA [Stadt] [Ländercode]
      Beispiel:
      define wetter PROPLANTA Bern ch
      define wetter PROPLANTA Wittingen+(Niedersachsen)
       
    • [Stadt]
      Optional. Die Stadt muss auf www.proplanta.de auswählbar sein.
      Wichtig!! Auf die großen Anfangsbuchstaben achten. Leerzeichen im Stadtnamen werden durch ein + (Plus) ersetzt.

    • [Ländercode]
      Optional. Mögliche Werte: de (Standard), at, ch, fr, it

    • Über die Funktion PROPLANTA_Html und PROPLANTA_Html_Landscape wird ein HTML-Code für eine Vorhersage für die angegebenen Anzahl Tage (standardmäßig 3) erzeugt.
      Beispiel:
      define Vorschau weblink htmlCode {PROPLANTA_Html("Wetter"[, Tage])}


    Set

    • set <name> update
      Startet sofort ein neues Auslesen der Wetterdaten.

    Attribute

    • forecastDays <4-14>
      Anzahl Tage, für die die Vorhersage ausgelesen werden soll. Standard ist 14 Tage (inkl. heute).

    • INTERVAL <Abfrageintervall>
      Abfrageintervall in Sekunden (Standard 3600 = 1 Stunde)

    • URL <Internetadresse>
      Internetadresse, von der die Daten ausgelesen werden (überschreibt die Werte im 'define'-Term)

    • readingFnAttributes


    Vorhersagewerte

    • fc0|1|2|3...|13_... - Vorhersagewerte für heute|morgen|übermorgen|in 3|...|13 Tagen
    • fc0_...00|03|06|09|12|15|18|21 - Vorhersagewerte für heute um 00|03|06|09|12|15|18|21 Uhr
    • fc0_chOfRainDay|Night - heutiges Niederschlagsrisiko tagsüber|nachts in %
    • fc1_chOfRain15 - morgiges Niederschlagsrisiko um 15:00 Uhr in %
    • fc2_cloud15 - Wolkenbedeckungsgrad übermorgen um 15:00 Uhr in %
    • fc0_dew - Taubildung heute (0=keine, 1=leicht, 2=mäßig, 3=stark)
    • fc0_evapor - Verdunstung heute (0=keine, 1=gering, 2=mäßig, 3=stark)
    • fc0_frost - Bodenfrost heute (0=nein, 1=ja)
    • fc0_gust15 - maximale Windböen heute um 15:00 Uhr in km/h
    • fc1_moonRise|Set - Mondauf|untergang morgen
    • fc0_rad - Globalstrahlung heute
    • fc0_rain15 - Niederschlagsmenge heute um 15:00 Uhr in mm
    • fc0_sun - relative Sonnenscheindauer heute in % (zwischen Sonnenauf- und -untergang)
    • fc0_tempMin|Max - Minimal|Maximaltemperatur heute in °C
    • fc1_temp15 - Temperatur morgen um 15:00 Uhr in °C
    • fc0_uv - UV-Index heute
    • fc0_weatherMorning|Day|Evening|Night - Wetterzustand heute morgen|tagsüber|abends|nachts
    • fc0_weatherDayIcon - Icon Wetterzustand heute tagsüber
    • fc0_wind15 - Windgeschwindigkeit heute um 15:00 Uhr in km/h
    • fc0_windDir15 - Windrichtung heute um 15:00 Uhr in ° (Grad)
    • etc.

    Aktuelle Werte

    • cloudBaseMin|Max - Höhe der minimalen|maximalen Wolkenuntergrenze in m
    • dewPoint - Taupunkt in °C
    • humidity - relative Feuchtigkeit in %
    • obs_time - Uhrzeit der Wetterbeobachtung
    • pressure - Luftdruck in hPa
    • temperature - Temperature in °C
    • visibility - Sichtweite in km
    • weather - Wetterzustand
    • weatherIcon - Icon Wetterzustand
    • wind - Windgeschwindigkeit in km/h
    • windDir - Windrichtung in ° (Grad)


=end html_DE =cut