| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625 |
- # $Id: 59_WWO.pm 8215 2015-03-15 07:32:49Z baumrasen $
- ##############################################################################
- #
- # 59_WWO.pm
- # Copyright by Andreas Vogt
- # e-mail: sourceforge at baumrasen dot de
- #
- # get current weather condition and forecast from worldweatheronline.com
- #
- # based / modified from 59_Weather.pm written by Dr. Boris Neubert
- # e-mail: omega at online dot de
- #
- # This file is part of fhem.
- #
- # Fhem 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.
- #
- # Fhem 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.
- #
- # You should have received a copy of the GNU General Public License
- # along with fhem. If not, see <http://www.gnu.org/licenses/>.
- #
- ##############################################################################
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday);
- use HttpUtils;
- # use Date::Calc qw(Day_of_Week Day_of_Week_to_Text);
- #
- # uses the Free Weather API: http://developer.worldweatheronline.com
- #
- # Mapping of current supported encodings
- my %DEFAULT_ENCODINGS = (
- en => 'latin1',
- da => 'latin1',
- de => 'latin1',
- es => 'latin1',
- fi => 'latin1',
- fr => 'latin1',
- it => 'latin1',
- ja => 'utf-8',
- ko => 'utf-8',
- nl => 'latin1',
- no => 'latin1',
- 'pt-BR' => 'latin1',
- ru => 'utf-8',
- sv => 'latin1',
- 'zh-CN' => 'utf-8',
- 'zh-TW' => 'utf-8',
- );
- #####################################
- sub WWO_Initialize($) {
- my ($hash) = @_;
- # Provider
- # $hash->{Clients} = undef;
- # Consumer
- $hash->{DefFn} = "WWO_Define";
- $hash->{UndefFn} = "WWO_Undef";
- $hash->{GetFn} = "WWO_Get";
- $hash->{SetFn} = "WWO_Set";
- #$hash->{AttrFn} = "WWO_Attr";
- #$hash->{AttrList}= "days:0,1,2,3,4,5 loglevel:0,1,2,3,4,5 localicons event-on-update-reading event-on-change-reading";
- #$hash->{AttrList}= "loglevel:0,1,2,3,4,5 localicons event-on-update-reading event-on-change-reading";
- $hash->{AttrList}= "localicons ".
- $readingFnAttributes;
-
- }
- ###################################
- sub latin1_to_utf8($) {
- # http://perldoc.perl.org/perluniintro.html, UNICODE IN OLDER PERLS
- my ($s)= @_;
- $s =~ s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg;
- return $s;
- }
- ###################################
- #sub temperature_in_c {
- # my ($temperature, $unitsystem)= @_;
- # return $unitsystem ne "SI" ? int(($temperature-32)*5/9+0.5) : $temperature;
- #}
- #sub wind_in_km_per_h {
- # my ($wind, $unitsystem)= @_;
- # return $unitsystem ne "SI" ? int(1.609344*$wind+0.5) : $wind;
- #}
- ###################################
- sub WWO_UpdateReading($$$$) {
- my ($hash,$prefix,$key,$value)= @_;
- #Debug "WWO: $prefix $key $value";
- #my $unitsystem= $hash->{READINGS}{unit_system}{VAL};
-
- #not needed
- if($key eq "date") {
- my @da = split("-", $value);
- $value = sprintf("%02d.%02d.",$da[2],$da[1]);
- $value= $value;
- }
- # elsif($key eq "tempMaxC") {
- # $key= "tempMaxC";
- # #$value= temperature_in_c($value,$unitsystem);
- # $value= $value;
- # } elsif($key eq "humidity") {
- # # standardize reading - allow generic logging of humidity.
- # $value=~ s/.*?(\d+).*/$1/; # extract numeric
- # }
- #Debug "WWO: $prefix $key $value";
- my $reading= $prefix . $key;
- readingsBulkUpdate($hash,$reading,$value);
- if($key eq "temp_C") {
- readingsBulkUpdate($hash,"temperature",$value); # additional entry for compatibility
- }
- # if($key eq "date") {
- # $reading = $prefix . "shortdate";
- # my @da = split("-", $value);
- # $value = left(Day_of_Week_to_Text(Day_of_Week($da[0], $da[1], $da[2]), 3),2);
- # readingsBulkUpdate($hash,$reading,$value); # additional entry
- # }
-
- if($key eq "weatherIconUrl") {
- # $value =~ s/.*\/([^.\/]*)\.*/$1/;
- $value =~ s/.*\/([^\/]+\.[^\.]+)/$1/;
-
- $reading= $prefix . "icon";
- readingsBulkUpdate($hash,$reading,$value); # additional entry for icon name
- }
- if($reading eq "windspeedKmph") {
- #$value=~ s/.*?(\d+).*/$1/; # extract numeric
- # readingsBulkUpdate($hash,"wind",wind_in_km_per_h($value,$unitsystem)); # additional entry for compatibility
- readingsBulkUpdate($hash,"wind",$value); # additional entry for compatibility
- }
- return 1;
- }
- ###################################
- sub WWO_RetrieveDataDirectly($)
- {
- my ($hash)= @_;
- my $location= $hash->{LOCATION};
- my $apikey = $hash->{APIKEY};
- my $days = $hash->{DAYS};
-
- $days = 5;
-
- #$location =~ s/([^\w()’*~!.-])/sprintf '%%%02x', ord $1/eg;
- my $lang= $hash->{LANG};
- my $fc = 0;
- my $fd = 0;
- my $days_addon = "&fx=no";
- if ($days > 0) {$days_addon = "&num_of_days=" . $days;}
- my $theurl = "http://api2.worldweatheronline.com/free/v1/weather.ashx?q=" . $location . "&extra=localObsTime&format=xml" . $days_addon . "&key=" . $apikey;
- #Debug "WWO: fecht url: $theurl";
- # my $xml = GetFileFromURL("http://free.worldweatheronline.com/feed/weather.ashx?q=" . $location . "&extra=localObsTime&format=xml" . $days_addon . "&key=" . $apikey);
- # my $xml = GetFileFromURL($theurl);
- my $xml = CustomGetFileFromURL(0, $theurl);
- #Debug "WWO: xml file content: $xml";
-
- # return 0 if( ! defined $xml || $xml eq "");
- if( ! defined $xml || $xml eq "") { # Log-Entry if nothing is returned
- Log3 $hash, 1,
- "WWO The API returns nothing. Look for an output of CustomGetFileFromURL above";
- return 0;
- }
-
- if ($xml eq "<h1>Developer Inactive</h1>") { # Log-Entry if API-Key not valid
- Log3 $hash, 1,
- "WWO The API returns, that the Developer is Inactive. Maybe the API-Key is not valid.";
- return 0;
- }
-
- if (index($xml, "error") != -1) { # check for an error-tag in the returned xml file
- Log3 $hash, 1,
- "WWO The API returns an error: $xml";
- return 0;
- }
-
- foreach my $llll (split("\/request>",$xml)) {
- #Debug "WWO: llll=\"$llll\"";
- foreach my $lll (split("\/current_condition>",$llll)) {
- #Debug "WWO: lll=\"$lll\"";
- $fc++;
- foreach my $ll (split("\/weather>",$lll)) {
- $fd++;
- # fc/fd = 1/1 > City/Type
- # fc/fd = 2/2 > current_condition
- # fc/fd = 3/3..5 > today, and following days
-
- #Debug "WWO: ll=\"$ll\"";
- foreach my $l (split(/<\/[\w]*>/,$ll)) { # with closing tag
- #Debug "WWO: all_line fc=\"$fc\" line=\"$l\"";
- next if($l eq ""); # skip empty lines
- next if($l =~ m/\/[\w]*>/); # skip closing tag lines
- next if($l =~ m/\?xml/); # skip xml declaration
-
- $l =~ s/<!\[CDATA\[//; # strip off <![CDATA[
- $l =~ s/\]\]>//; # strip of [[>
- #$l =~ s/<!\[CDATA\[([\w\d\s.\/:]*)]]>/$1/; # strip of [[>
- #Debug "WWO: clean1 fc=\"$fc\" line=\"$l\"";
-
- #Debug "WWO: 1fc=\"$fc\" fd=\"$fd\" line=\"$l\"";
- $l =~ s/(<[\w]*>)(<[\w]*>)/$2/; # remove first tag in case of two tags in line
- #Debug "WWO: 2fc=\"$fc\" fd=\"$fd\" line=\"$l\"";
-
-
- #$l =~ s/(\/|\?)?>$//; # strip off /> and >
- $l =~ s/<//; # strip off /> and >
-
- my ($tag,$value)= split(">", $l, 2); # split tag data=..... at the first blank
- #Debug "WWO: 3tag=\"$tag\" value=\"$value\"";
-
- #$fc= 0 if($tag eq "current_condition");
- #$fc++ if($tag eq "weather");
- #next if((!defined($value)) || ($value == ""));
- next if((!defined($value)) || (!defined($tag)) || ($tag eq "") || ($value eq ""));
- #Debug "WWO: CHECKED tag=\"$tag\" value=\"$value\"";
- my $prefix = "";
- if ($fc == 3) {
- $prefix= $fd ? "fc" . ($fd-3) ."_" : "";
- } else {
- $prefix= ""; # may be it would be helpfull to set to 'now_' or so
- }
-
- my $key= $tag;
- #$value=~ s/^data=\"(.*)\"$/$1/; # extract DATA from data="DATA"
- # if($DEFAULT_ENCODINGS{$lang} eq "latin1") {
- # $value= latin1_to_utf8($value); # latin1 -> UTF-8
- # }
- #Debug "WWO: prefix=\"$prefix\" tag=\"$tag\" value=\"$value\"";
- WWO_UpdateReading($hash,$prefix,$key,$value);
- }
- }
- }
- }
-
- return 1;
- }
- ###################################
- sub WWO_GetUpdate($)
- {
- my ($hash) = @_;
- if(!$hash->{LOCAL}) {
- InternalTimer(gettimeofday()+$hash->{INTERVAL}, "WWO_GetUpdate", $hash, 1);
- }
-
- readingsBeginUpdate($hash);
- WWO_RetrieveDataDirectly($hash);
- my $temperature= $hash->{READINGS}{temperature}{VAL};
- my $humidity= $hash->{READINGS}{humidity}{VAL};
- my $pressure= $hash->{READINGS}{pressure}{VAL};
- my $wind= $hash->{READINGS}{wind}{VAL};
- my $val= "T: $temperature H: $humidity W: $wind P: $pressure";
-
- #Log GetLogLevel($hash->{NAME},4), "WWO: Log-->". $hash->{NAME} . ": $val";
- Log3 $hash, 4, "WWO ". $hash->{NAME} . ": $val";
-
- #Debug "Now i will push the changed notify";
- #$hash->{CHANGED}[0]= $val;
- #push @{$hash->{CHANGED}}, "$val";
- #$hash->{STATE}= $val;
-
- ##$hash->{STATE} = $val; # List overview
- ##$hash->{READINGS}{state}{VAL} = $val;
- ##$hash->{CHANGED}[0] = $val; # For notify
-
- ##Log 1, "WWO $val";
-
- ##addEvent($hash, $val);
- ##readingsEndUpdate($hash, defined($hash->{LOCAL} ? 0 : 1)); # DoTrigger, because sub is called by a timer instead of dispatch
-
- readingsBulkUpdate($hash, "state", $val);
- readingsEndUpdate($hash, defined($hash->{LOCAL} ? 0 : 1)); # DoTrigger, because sub is called by a timer instead of dispatch
-
- return 1;
- }
- # Perl Special: { $defs{WWO}{READINGS}{condition}{VAL} }
- # conditions: Mostly Cloudy, Overcast, Clear, Chance of Rain
- ###################################
- sub WWO_Get($@) {
- my ($hash, @a) = @_;
- return "argument is missing" if(int(@a) != 2);
- $hash->{LOCAL} = 1;
- WWO_GetUpdate($hash);
- delete $hash->{LOCAL};
- my $reading= $a[1];
- my $value;
- if(defined($hash->{READINGS}{$reading})) {
- $value= $hash->{READINGS}{$reading}{VAL};
- } else {
- return "no such reading: $reading";
- }
- return "$a[0] $reading => $value";
- }
- ###################################
- sub WWO_Set($@) {
- my ($hash, @a) = @_;
- my $cmd= $a[1];
- # usage check
- if((@a == 2) && ($a[1] eq "update")) {
- RemoveInternalTimer($hash);
- WWO_GetUpdate($hash);
- return undef;
- } else {
- return "Unknown argument $cmd, only update is valid";
- }
- }
- #####################################
- sub WWO_Define($$) {
- my ($hash, $def) = @_;
- # define <name> WWO <location> <apikey>
- # define MyWWO WWO Berlin,Germany xxxxxxxxxxxxxxxxxxxx 3600
- my @a = split("[ \t][ \t]*", $def);
- #return "syntax: define <name> WWO <location> <apikey> [interval]"
- return "syntax: define <name> WWO <location> <apikey> [interval]" # interval option not acitve
- #if(int(@a) < 3 && int(@a) > 4);
- if(int(@a) < 3 && int(@a) > 3); # interval option not acitve
- $hash->{STATE} = "Initialized";
- $hash->{fhem}{interfaces}= "temperature;humidity;wind";
- my $name = $a[0];
- my $location = $a[2];
- my $apikey = $a[3];
- my $interval = 3600;
- my $lang = "en";
-
- # if(int(@a)>=5) { $interval= $a[4]; }
- my $days = 5; # fixed to 5 days, right values are a number from 0-5
-
- $hash->{LOCATION} = $location;
- $hash->{INTERVAL} = $interval;
- $hash->{APIKEY} = $apikey;
- $hash->{LANG} = $lang;
- $hash->{DAYS} = $days;
- $hash->{READINGS}{current_date_time}{TIME}= TimeNow();
- $hash->{READINGS}{current_date_time}{VAL}= "none";
- $hash->{LOCAL} = 1;
- WWO_GetUpdate($hash);
- delete $hash->{LOCAL};
- InternalTimer(gettimeofday()+$hash->{INTERVAL}, "WWO_GetUpdate", $hash, 0);
- return undef;
- }
- #####################################
- sub WWO_Undef($$) {
- my ($hash, $arg) = @_;
- RemoveInternalTimer($hash);
- return undef;
- }
- ######################################
- # sub
- # WWO_Attr(@)
- # {
- # my @a = @_;
- # my $attr= $a[2];
- #
- # if($a[0] eq "set") { # set attribute
- # if($attr eq "days") {
- # }
- # }
- # elsif($a[0] eq "del") { # delete attribute
- # if($attr eq "days") {
- # }
- # }
- #
- # return undef;
- #
- # }
- #####################################
- sub
- WWOIconIMGTag($$$) {
- use constant WWOURL => "http://www.worldweatheronline.com/images/wsymbols01_png_64/";
- use constant SIZE => "50%";
- my ($icon,$uselocal,$isday)= @_;
- my $url;
- my $style= "";
-
- if($uselocal) {
- # strip off path and extension
- $icon =~ s,^/images/wsymbols01_png_64/(.*)\.png$,$1,;
- if($isday) {
- $icon= "weather/${icon}"
- } else {
- $icon= "weather/${icon}_night"
- }
- $url= "fhem/icons/$icon";
- $style= " height=".SIZE." width=".SIZE;
- } else {
- $url= WWOURL . $icon;
- }
- return "<img src=\"$url\"$style>";
- }
- #####################################
- sub
- WWOAsHtml($)
- {
- my ($d) = @_;
- $d = "<none>" if(!$d);
- return "$d is not a WWO instance<br>"
- if(!$defs{$d} || $defs{$d}{TYPE} ne "WWO");
- my $uselocal= AttrVal($d,"localicons",0);
- my $isday;
- if(exists &isday) {
- $isday = isday();
- } else {
- $isday = 1; #($hour>6 && $hour<19);
- }
-
- my $ret = "<table>";
- $ret .= sprintf('<tr><td>%s</td><td><br></td></tr>',
- ReadingsVal($d, "query", ""));
- $ret .= sprintf('<tr><td>%s</td><td>%s %s<br>temp: %s °C, hum %s<br>wind: %s km/h %s<br>pressure: %s bar visibility: %s km</td></tr>',
- WWOIconIMGTag(ReadingsVal($d, "icon", ""),$uselocal,$isday),
- ReadingsVal($d, "localObsDateTime", ""),ReadingsVal($d, "weatherDesc", ""),
- ReadingsVal($d, "temp_C", ""), ReadingsVal($d, "humidity", ""),
- ReadingsVal($d, "windspeedKmph", ""), ReadingsVal($d, "winddir16Point", ""),
- ReadingsVal($d, "pressure", ""),ReadingsVal($d, "visibility", ""));
- for(my $i=0; $i<=4; $i++) {
- $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>',
- WWOIconIMGTag(ReadingsVal($d, "fc${i}_icon", ""),$uselocal,$isday),
- ReadingsVal($d, "fc${i}_date", ""),
- ReadingsVal($d, "fc${i}_weatherDesc", ""),
- ReadingsVal($d, "fc${i}_tempMinC", ""), ReadingsVal($d, "fc${i}_tempMaxC", ""),
- ReadingsVal($d, "fc${i}_windspeedKmph", ""), ReadingsVal($d, "fc${i}_winddir16Point", ""),
- ReadingsVal($d, "fc${i}_precipMM", ""));
- }
-
- $ret .= "</table>";
-
- $ret .= "<br> Powered by <a href=\"[url]http://www.worldweatheronline.com/[/url]\" title=\"Free local weather content provider\" target=\"_blank\">World Weather Online</a> ";
-
- return $ret;
- }
- #####################################
- sub right{
- my ($string,$nr) = @_;
- return substr $string, -$nr, $nr;
- }
- #####################################
- sub left{
- my ($string,$nr) = @_;
- return substr $string, 0, $nr;
- }
- #####################################
- 1;
- =pod
- =begin html
- <a name="WWO"></a>
- <h3>WWO</h3>
- <ul>
- <br>
- <a name="WWOdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> WWO <location> <apikey></code><br>
- <br>
- Defines a virtual device for WWO forecasts.<br><br>
- A WWO device periodically gathers current and forecast weather conditions
- from worldweatheronline.com (the free api version)<br>
- You need to signup at <a href="http://developer.worldweatheronline.com">http://developer.worldweatheronline.com</a> to get an apikey)<br><br>
- The parameter <code>location</code> is the WOEID (WHERE-ON-EARTH-ID), go to
- <a href="http://www.worldweatheronline.com">http://www.worldweatheronline.com</a> to find it out for your valid location.<br><br>
- The natural language in which the forecast information appears is english.
- <br><br>
-
- The interval is set to update the values every hour.
- <br><br>
- Examples:
- <pre>
- define MyWeather WWO Berlin,Germany
- </pre>
-
- The module provides one additional function <code>WWOAsHtml</code>. The function return the HTML code for a
- vertically arranged weather forecast.
- <br><br>
-
- Example:
- <pre>
- define MyWeatherWeblink weblink htmlCode { WWOAsHtml("MyWeather") }
- </pre>
-
- </ul>
- <br>
- <a name="Weatherset"></a>
- <b>Set </b>
- <ul>
- <code>set <name> update</code><br><br>
- Forces the retrieval of the weather data. The next automatic retrieval is scheduled to occur
- <code>interval</code> seconds later.<br><br>
- </ul>
- <br>
- <a name="Weatherget"></a>
- <b>Get</b>
- <ul>
- <code>get <name> <reading></code><br><br>
- Valid readings and their meaning (? can be one of 0, 1, 2, 3, 4, 5 and stands
- for today, tomorrow, etc. - with 'fc?_' or without! - without is meaning 'current condition'):<br>
- <table>
- <tr><td>cloudcover</td><td>cloudcover in percent</td></tr>
- <tr><td>current_date_time</td><td>last update of forecast on server</td></tr>
- <tr><td>fc?_date</td><td>date of the forecast condition - not valid without 'fc?'</td></tr>
- <tr><td>fc?_icon</td><td>name of the forecasticon</td></tr>
- <tr><td>fc?_precipMM</td><td>preciption for day</td></tr>
- <tr><td>fc?_tempMaxC</td><td>forecasted daily high in degrees centigrade</td></tr>
- <tr><td>fc?_tempMaxF</td><td>forecasted daily high in degrees fahrenheit</td></tr>
- <tr><td>fc?_tempMinC</td><td>forecasted daily low in degrees centigrade</td></tr>
- <tr><td>fc?_tempMinF</td><td>forecasted daily low in degrees fahrenheit</td></tr>
- <tr><td>fc?_weatherCode</td><td>weathercode</td></tr>
- <tr><td>fc?_weatherDesc</td><td>short weather desciption</td></tr>
- <tr><td>fc?_weatherIconUrl</td><td>full url to the weathericonfile</td></tr>
- <tr><td>fc?_winddir16Point</td><td>winddirection with 16 points</td></tr>
- <tr><td>fc?_winddirDegree</td><td>windirection in degrees</td></tr>
- <tr><td>fc?_winddirection</td><td>winddirection</td></tr>
- <tr><td>fc?_windspeedKmph</td><td>windspeed in km/h</td></tr>
- <tr><td>fc?_windspeedMiles</td><td>windspeed in miles/h</td></tr>
- <tr><td>humidity</td><td>current humidity in %</td></tr>
- <tr><td>localObsDateTime</td><td>local time of observation</td></tr>
- <tr><td>observation_time</td><td>time of observation</td></tr>
- <tr><td>pressure</td><td>air pressure in hPa</td></tr>
- <tr><td>query</td><td>returns the queried location</td></tr>
- <tr><td>temperature</td><td>current temperature in degrees centigrade</td></tr>
- <tr><td>visibility</td><td>current visibilit in km</td></tr>
- </table>
- </ul>
- <br>
- <a name="Weatherattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
- </ul>
- <br>
- </ul>
- =end html
- =cut
|