testing #29
| @@ -1,962 +0,0 @@ | ||||
| # $Id:  $ | ||||
| ############################################################################### | ||||
| # | ||||
| # Developed with VSCodium and richterger perl plugin | ||||
| # | ||||
| #  (c) 2019-2023 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) | ||||
| #  All rights reserved | ||||
| # | ||||
| #   Special thanks goes to: | ||||
| #       - Harry (harryman) for many tests and patch that implements onecall API | ||||
| # | ||||
| # | ||||
| #  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 | ||||
| #  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 textfile 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. | ||||
| # | ||||
| # | ||||
| ############################################################################### | ||||
|  | ||||
| ### Beispielaufruf | ||||
| # https://api.openweathermap.org/data/2.5/weather?lat=[lat]&lon=[long]&APPID=[API]   Current | ||||
| # https://api.openweathermap.org/data/2.5/forecast?lat=[lat]&lon=[long]&APPID=[API]   Forecast | ||||
| # https://api.openweathermap.org/data/3.0/onecall?lat=[lat]&lon=[long]&APPID=[API]   Current,Forecast | ||||
| # https://openweathermap.org/weather-conditions     Icons und Conditions ID's | ||||
|  | ||||
| package FHEM::APIs::Weather::OpenWeatherMapAPI; | ||||
| use strict; | ||||
| use warnings; | ||||
| use FHEM::Meta; | ||||
|  | ||||
| use POSIX; | ||||
| use HttpUtils; | ||||
| use experimental qw /switch/; | ||||
|  | ||||
| my %META; | ||||
| my $ret = FHEM::Meta::getMetadata( __FILE__, \%META ); | ||||
| return "$@" if ($@); | ||||
| return $ret if ($ret); | ||||
| $::packages{OpenWeatherMapAPI}{META} = \%META; | ||||
| use version 0.77; our $VERSION = $::packages{OpenWeatherMapAPI}{META}{version}; | ||||
|  | ||||
| # use Data::Dumper; | ||||
|  | ||||
| # try to use JSON::MaybeXS wrapper | ||||
| #   for chance of better performance + open code | ||||
| eval { | ||||
|     require JSON::MaybeXS; | ||||
|     import JSON::MaybeXS qw( decode_json encode_json ); | ||||
|     1; | ||||
| } or do { | ||||
|  | ||||
|     # try to use JSON wrapper | ||||
|     #   for chance of better performance | ||||
|     eval { | ||||
|         # JSON preference order | ||||
|         local $ENV{PERL_JSON_BACKEND} = | ||||
|           'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' | ||||
|           unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); | ||||
|  | ||||
|         require JSON; | ||||
|         import JSON qw( decode_json encode_json ); | ||||
|         1; | ||||
|     } or do { | ||||
|  | ||||
|         # In rare cases, Cpanel::JSON::XS may | ||||
|         #   be installed but JSON|JSON::MaybeXS not ... | ||||
|         eval { | ||||
|             require Cpanel::JSON::XS; | ||||
|             import Cpanel::JSON::XS qw(decode_json encode_json); | ||||
|             1; | ||||
|         } or do { | ||||
|  | ||||
|             # In rare cases, JSON::XS may | ||||
|             #   be installed but JSON not ... | ||||
|             eval { | ||||
|                 require JSON::XS; | ||||
|                 import JSON::XS qw(decode_json encode_json); | ||||
|                 1; | ||||
|             } or do { | ||||
|  | ||||
|                 # Fallback to built-in JSON which SHOULD | ||||
|                 #   be available since 5.014 ... | ||||
|                 eval { | ||||
|                     require JSON::PP; | ||||
|                     import JSON::PP qw(decode_json encode_json); | ||||
|                     1; | ||||
|                 } or do { | ||||
|  | ||||
|                     # Fallback to JSON::backportPP in really rare cases | ||||
|                     require JSON::backportPP; | ||||
|                     import JSON::backportPP qw(decode_json encode_json); | ||||
|                     1; | ||||
|                 }; | ||||
|             }; | ||||
|         }; | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| my $missingModul = ''; | ||||
| ## no critic (Conditional "use" statement. Use "require" to conditionally include a module (Modules::ProhibitConditionalUseStatements)) | ||||
| eval { use Encode qw /encode_utf8/; 1 } or $missingModul .= 'Encode '; | ||||
|  | ||||
| # use Data::Dumper;    # for Debug only | ||||
| ## API URL | ||||
| eval { use Readonly; 1 } | ||||
|   or $missingModul .= 'Readonly ';    # apt install libreadonly-perl | ||||
| ## use critic | ||||
|  | ||||
| # Readonly my $URL => 'https://api.openweathermap.org/data/2.5/'; | ||||
| Readonly my $URL => 'https://api.openweathermap.org/data/'; | ||||
| ## URL . 'weather?' for current data | ||||
| ## URL . 'onecall?' for current,forecast data | ||||
|  | ||||
| my %codes = ( | ||||
|     200 => 45, | ||||
|     201 => 45, | ||||
|     202 => 45, | ||||
|     210 => 4, | ||||
|     211 => 4, | ||||
|     212 => 3, | ||||
|     221 => 4, | ||||
|     230 => 45, | ||||
|     231 => 45, | ||||
|     232 => 45, | ||||
|     300 => 9, | ||||
|     301 => 9, | ||||
|     302 => 9, | ||||
|     310 => 9, | ||||
|     311 => 9, | ||||
|     312 => 9, | ||||
|     313 => 9, | ||||
|     314 => 9, | ||||
|     321 => 9, | ||||
|     500 => 35, | ||||
|     501 => 35, | ||||
|     502 => 35, | ||||
|     503 => 35, | ||||
|     504 => 35, | ||||
|     511 => 35, | ||||
|     520 => 35, | ||||
|     521 => 35, | ||||
|     522 => 35, | ||||
|     531 => 35, | ||||
|     600 => 14, | ||||
|     601 => 16, | ||||
|     602 => 13, | ||||
|     611 => 46, | ||||
|     612 => 46, | ||||
|     613 => 46, | ||||
|     615 => 5, | ||||
|     616 => 5, | ||||
|     620 => 14, | ||||
|     621 => 46, | ||||
|     622 => 42, | ||||
|     701 => 19, | ||||
|     711 => 22, | ||||
|     721 => 19, | ||||
|     731 => 23, | ||||
|     741 => 20, | ||||
|     751 => 23, | ||||
|     761 => 19, | ||||
|     762 => 3200, | ||||
|     771 => 1, | ||||
|     781 => 0, | ||||
|     800 => 32, | ||||
|     801 => 30, | ||||
|     802 => 26, | ||||
|     803 => 26, | ||||
|     804 => 28, | ||||
| ); | ||||
|  | ||||
| sub new { | ||||
|     ### geliefert wird ein Hash | ||||
|     my $class   = shift; | ||||
|     my $argsRef = shift; | ||||
|  | ||||
|     my $apioptions = _parseApiOptions( $argsRef->{apioptions} ); | ||||
|  | ||||
|     my $self = { | ||||
|         devName => $argsRef->{devName}, | ||||
|         key     => ( | ||||
|             ( defined( $argsRef->{apikey} ) && $argsRef->{apikey} ) | ||||
|             ? $argsRef->{apikey} | ||||
|             : 'none' | ||||
|         ), | ||||
|         lang      => $argsRef->{language}, | ||||
|         lat       => ( split( ',', $argsRef->{location} ) )[0], | ||||
|         long      => ( split( ',', $argsRef->{location} ) )[1], | ||||
|         fetchTime => 0, | ||||
|         endpoint  => 'none', | ||||
|         forecast  => '', | ||||
|         alerts    => 0, | ||||
|     }; | ||||
|  | ||||
|     $self->{cachemaxage} = ( | ||||
|         defined( $apioptions->{cachemaxage} ) | ||||
|         ? $apioptions->{cachemaxage} | ||||
|         : 900 | ||||
|     ); | ||||
|  | ||||
|     $self->{apiversion} = | ||||
|       ( $apioptions->{version} ? $apioptions->{version} : '2.5' ); | ||||
|  | ||||
|     $self->{cached} = _CreateForecastRef($self); | ||||
|  | ||||
|     bless $self, $class; | ||||
|     return $self; | ||||
| } | ||||
|  | ||||
| sub _parseApiOptions { | ||||
|     my $apioptions = shift; | ||||
|  | ||||
|     my @params; | ||||
|     my %h; | ||||
|  | ||||
|     @params = split( ',', $apioptions ); | ||||
|     while (@params) { | ||||
|         my $param = shift(@params); | ||||
|         next if ( $param eq '' ); | ||||
|         my ( $key, $value ) = split( ':', $param, 2 ); | ||||
|         $h{$key} = $value; | ||||
|     } | ||||
|  | ||||
|     return \%h; | ||||
| } | ||||
|  | ||||
| sub setAlerts { | ||||
|     my $self   = shift; | ||||
|     my $alerts = shift // 0; | ||||
|  | ||||
|     $self->{alerts} = $alerts; | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub setForecast { | ||||
|     my $self     = shift; | ||||
|     my $forecast = shift // ''; | ||||
|  | ||||
|     $self->{forecast} = $forecast; | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub setFetchTime { | ||||
|     my $self = shift; | ||||
|  | ||||
|     $self->{fetchTime} = time(); | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub setRetrieveData { | ||||
|     my $self = shift; | ||||
|  | ||||
|     _RetrieveDataFromOpenWeatherMap($self); | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub setLocation { | ||||
|     my $self = shift; | ||||
|     my $lat  = shift; | ||||
|     my $long = shift; | ||||
|  | ||||
|     $self->{lat}  = $lat; | ||||
|     $self->{long} = $long; | ||||
|  | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub getFetchTime { | ||||
|     my $self = shift; | ||||
|  | ||||
|     return $self->{fetchTime}; | ||||
| } | ||||
|  | ||||
| sub getWeather { | ||||
|     my $self = shift; | ||||
|  | ||||
|     return $self->{cached}; | ||||
| } | ||||
|  | ||||
| sub _RetrieveDataFromOpenWeatherMap { | ||||
|     my $self = shift; | ||||
|  | ||||
|     # retrieve data from cache | ||||
|     if (   ( time() - $self->{fetchTime} ) < $self->{cachemaxage} | ||||
|         && $self->{cached}->{lat} == $self->{lat} | ||||
|         && $self->{cached}->{long} == $self->{long} | ||||
|         && $self->{endpoint} eq 'none' ) | ||||
|     { | ||||
|         return _CallWeatherCallbackFn($self); | ||||
|     } | ||||
|  | ||||
|     $self->{cached}->{lat} = $self->{lat} | ||||
|       unless ( $self->{cached}->{lat} == $self->{lat} ); | ||||
|     $self->{cached}->{long} = $self->{long} | ||||
|       unless ( $self->{cached}->{long} == $self->{long} ); | ||||
|  | ||||
|     my $paramRef = { | ||||
|         timeout  => 15, | ||||
|         self     => $self, | ||||
|         endpoint => $self->{endpoint} eq 'none' ? 'onecall' : 'none', | ||||
|         callback => \&_RetrieveDataFinished, | ||||
|     }; | ||||
|  | ||||
|     $self->{endpoint} = $paramRef->{endpoint}; | ||||
|  | ||||
|     if (   $self->{lat} eq 'error' | ||||
|         || $self->{long} eq 'error' | ||||
|         || $self->{key} eq 'none' | ||||
|         || $missingModul ) | ||||
|     { | ||||
|         _RetrieveDataFinished( | ||||
|             $paramRef, | ||||
| 'The given location is invalid. (wrong latitude or longitude?) put both as an attribute in the global device or set define option location=[LAT],[LONG]', | ||||
|             undef | ||||
|         ) if ( $self->{lat} eq 'error' || $self->{long} eq 'error' ); | ||||
|  | ||||
|         _RetrieveDataFinished( $paramRef, | ||||
|             'No given api key. (define  myWeather Weather apikey=[KEY])', | ||||
|             undef ) | ||||
|           if ( $self->{key} eq 'none' ); | ||||
|  | ||||
|         _RetrieveDataFinished( $paramRef, | ||||
|             'Perl modul ' . $missingModul . ' is missing.', undef ) | ||||
|           if ($missingModul); | ||||
|     } | ||||
|     else { | ||||
|         $paramRef->{url} = | ||||
|             $URL | ||||
|           . $self->{apiversion} . '/' | ||||
|           . $paramRef->{endpoint} . '?' . 'lat=' | ||||
|           . $self->{lat} . '&' . 'lon=' | ||||
|           . $self->{long} . '&' | ||||
|           . 'APPID=' | ||||
|           . $self->{key} . '&' | ||||
|           . 'units=' | ||||
|           . 'metric' . '&' . 'lang=' | ||||
|           . $self->{lang} . '&' | ||||
|           . 'exclude=' | ||||
|           . _CreateExcludeString( $self->{forecast}, $self->{alerts} ); | ||||
|  | ||||
|         ::HttpUtils_NonblockingGet($paramRef); | ||||
|     } | ||||
|  | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub _CreateExcludeString { | ||||
|     my $forecast = shift; | ||||
|     my $alerts   = shift; | ||||
|  | ||||
|     my @exclude  = qw/alerts minutely hourly daily/; | ||||
|     my @forecast = split( ',', $forecast ); | ||||
|     my @alerts   = ( $alerts ? 'alerts' : '' ); | ||||
|  | ||||
|     my %in_forecast = map  { $_ => 1 } @forecast, @alerts; | ||||
|     my @diff        = grep { not $in_forecast{$_} } @exclude; | ||||
|  | ||||
|     return join( ',', @diff ); | ||||
| } | ||||
|  | ||||
| sub _RetrieveDataFinished { | ||||
|     my $paramRef = shift; | ||||
|     my $err      = shift; | ||||
|     my $response = shift; | ||||
|     my $self     = $paramRef->{self}; | ||||
|  | ||||
|     if ( !$err ) { | ||||
|         $self->{cached}->{status}   = 'ok'; | ||||
|         $self->{cached}->{validity} = 'up-to-date'; | ||||
|         $self->{fetchTime}          = time(); | ||||
|         _ProcessingRetrieveData( $self, $response ); | ||||
|     } | ||||
|     else { | ||||
|         $self->{fetchTime} = time() if ( not defined( $self->{fetchTime} ) ); | ||||
|         _ErrorHandling( $self, $err ); | ||||
|         _ProcessingRetrieveData( $self, $response ); | ||||
|     } | ||||
|  | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub _ProcessingRetrieveData { | ||||
|     my $self     = shift; | ||||
|     my $response = shift; | ||||
|  | ||||
|     if (   $self->{cached}->{status} eq 'ok' | ||||
|         && defined($response) | ||||
|         && $response ) | ||||
|     { | ||||
|         if ( $response =~ m/^{.*}$/x ) { | ||||
|             my $data = eval { decode_json($response) }; | ||||
|  | ||||
|             if ($@) { | ||||
|                 _ErrorHandling( $self, | ||||
|                     'OpenWeatherMap Weather decode JSON err ' . $@ ); | ||||
|             } | ||||
|             elsif (defined( $data->{cod} ) | ||||
|                 && $data->{cod} | ||||
|                 && $data->{cod} != 200 | ||||
|                 && defined( $data->{message} ) | ||||
|                 && $data->{message} ) | ||||
|             { | ||||
|                 _ErrorHandling( $self, $data->{cod} . ': ' . $data->{message} ); | ||||
|             } | ||||
|             else { | ||||
|                 ### Debug | ||||
|                 # print '!!! DEBUG !!! - Endpoint: ' . $self->{endpoint} . "\n"; | ||||
|                 # print '!!! DEBUG !!! - Response: ' . Dumper $data; | ||||
|                 ###### Ab hier wird die ResponseHash Referenze für die Rückgabe zusammen gestellt | ||||
|                 $self->{cached}->{current_date_time} = | ||||
|                   strftimeWrapper( "%a, %e %b %Y %H:%M", | ||||
|                     localtime( $self->{fetchTime} ) ); | ||||
|  | ||||
|                 given ( $self->{endpoint} ) { | ||||
|                     when ('onecall') { | ||||
|                         ## löschen des alten current Datensatzes | ||||
|                         delete $self->{cached}->{current}; | ||||
|  | ||||
|                         ## löschen des alten forecast Datensatzes | ||||
|                         delete $self->{cached}->{forecast}; | ||||
|  | ||||
|                         ## löschen des alten Alerts Datensatzes | ||||
|                         delete $self->{cached}->{alerts}; | ||||
|  | ||||
|                         $self->{cached}->{current} = { | ||||
|                             'temperature' => int( | ||||
|                                 sprintf( "%.0f", $data->{current}->{temp} ) | ||||
|                             ), | ||||
|                             'temp_c' => int( | ||||
|                                 sprintf( "%.0f", $data->{current}->{temp} ) | ||||
|                             ), | ||||
|                             'tempFeelsLike_c' => int( | ||||
|                                 sprintf( "%.0f", | ||||
|                                     $data->{current}->{feels_like} ) | ||||
|                             ), | ||||
|                             'dew_point' => int( | ||||
|                                 sprintf( | ||||
|                                     "%.0f", $data->{current}->{dew_point} | ||||
|                                 ) | ||||
|                             ), | ||||
|                             'humidity'  => $data->{current}->{humidity}, | ||||
|                             'condition' => encode_utf8( | ||||
|                                 $data->{current}->{weather}->[0]->{description} | ||||
|                             ), | ||||
|                             'pressure' => int( | ||||
|                                 sprintf( "%.1f", $data->{current}->{pressure} ) | ||||
|                                   + 0.5 | ||||
|                             ), | ||||
|                             'wind' => int( | ||||
|                                 sprintf( "%.1f", | ||||
|                                     ( $data->{current}->{wind_speed} * 3.6 ) ) | ||||
|                                   + 0.5 | ||||
|                             ), | ||||
|                             'wind_speed' => int( | ||||
|                                 sprintf( "%.1f", | ||||
|                                     ( $data->{current}->{wind_speed} * 3.6 ) ) | ||||
|                                   + 0.5 | ||||
|                             ), | ||||
|                             'wind_gust' => int( | ||||
|                                 sprintf( "%.1f", | ||||
|                                     ( $data->{current}->{wind_gust} * 3.6 ) ) + | ||||
|                                   0.5 | ||||
|                             ), | ||||
|                             'wind_direction' => $data->{current}->{wind_deg}, | ||||
|                             'rain_1h'        => $data->{rain}->{'1h'}, | ||||
|                             'cloudCover'     => $data->{current}->{clouds}, | ||||
|                             'code'           => | ||||
|                               $codes{ $data->{current}->{weather}->[0]->{id} }, | ||||
|                             'iconAPI' => | ||||
|                               $data->{current}->{weather}->[0]->{icon}, | ||||
|                             'condition' => encode_utf8( | ||||
|                                 $data->{current}->{weather}->[0]->{description} | ||||
|                             ), | ||||
|                             'sunsetTime' => strftimeWrapper( | ||||
|                                 "%a, %e %b %Y %H:%M", | ||||
|                                 localtime( $data->{current}->{sunset} ) | ||||
|                             ), | ||||
|                             'sunriseTime' => strftimeWrapper( | ||||
|                                 "%a, %e %b %Y %H:%M", | ||||
|                                 localtime( $data->{current}->{sunrise} ) | ||||
|                             ), | ||||
|                             'pubDate' => strftimeWrapper( | ||||
|                                 "%a, %e %b %Y %H:%M", | ||||
|                                 localtime( $data->{current}->{dt} ) | ||||
|                             ), | ||||
|                             'visibility' => int( | ||||
|                                 sprintf( "%.1f", | ||||
|                                     $data->{current}->{visibility} ) + 0.5 | ||||
|                             ), | ||||
|                             'uvi'             => $data->{current}->{uvi}, | ||||
|                             'timezone'        => $data->{timezone}, | ||||
|                             'timezone_offset' => $data->{timezone_offset}, | ||||
|                         }; | ||||
|  | ||||
|                         if ( ref( $data->{hourly} ) eq "ARRAY" | ||||
|                             && scalar( @{ $data->{hourly} } ) > 0 ) | ||||
|                         { | ||||
|                             my $i = 0; | ||||
|                             for ( @{ $data->{hourly} } ) { | ||||
|                                 push( | ||||
|                                     @{ $self->{cached}->{forecast}->{hourly} }, | ||||
|                                     { | ||||
|                                         'pubDate' => strftimeWrapper( | ||||
|                                             "%a, %e %b %Y %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{hourly}->[$i]->{dt} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'day_of_week' => strftime( | ||||
|                                             "%a, %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{hourly}->[$i]->{dt} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'temperature' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{hourly}->[$i]->{temp} ) | ||||
|                                         ), | ||||
|                                         'temp_c' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{hourly}->[$i]->{temp} ) | ||||
|                                         ), | ||||
|                                         'tempFeelsLike' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{hourly}->[$i] | ||||
|                                                   ->{feels_like} ) | ||||
|                                         ), | ||||
|                                         'dew_point' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{hourly}->[$i] | ||||
|                                                   ->{dew_point} ) | ||||
|                                         ), | ||||
|                                         'humidity' => | ||||
|                                           $data->{hourly}->[$i]->{humidity}, | ||||
|                                         'condition' => encode_utf8( | ||||
|                                             $data->{hourly}->[$i]->{weather} | ||||
|                                               ->[0]->{description} | ||||
|                                         ), | ||||
|                                         'pressure' => int( | ||||
|                                             sprintf( "%.1f", | ||||
|                                                 $data->{hourly}->[$i] | ||||
|                                                   ->{pressure} ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{hourly}->[$i] | ||||
|                                                       ->{wind_speed} * 3.6 | ||||
|                                                 ) | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind_speed' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{hourly}->[$i] | ||||
|                                                       ->{wind_speed} * 3.6 | ||||
|                                                 ) | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind_gust' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{hourly}->[$i] | ||||
|                                                       ->{wind_gust} * 3.6 | ||||
|                                                 ) | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind_direction' => | ||||
|                                           $data->{hourly}->[$i]->{wind_deg}, | ||||
|                                         'cloudCover' => | ||||
|                                           $data->{hourly}->[$i]->{clouds}, | ||||
|                                         'code' => $codes{ | ||||
|                                             $data->{hourly}->[$i]->{weather} | ||||
|                                               ->[0]->{id} | ||||
|                                         }, | ||||
|                                         'iconAPI' => | ||||
|                                           $data->{hourly}->[$i]->{weather}->[0] | ||||
|                                           ->{icon}, | ||||
|                                         'rain1h' => | ||||
|                                           $data->{hourly}->[$i]->{rain}->{'1h'}, | ||||
|                                         'snow1h' => | ||||
|                                           $data->{hourly}->[$i]->{snow}->{'1h'}, | ||||
|                                         'uvi' => $data->{hourly}->[$i]->{uvi}, | ||||
|                                         'visibility' => int( | ||||
|                                             sprintf( "%.1f", | ||||
|                                                 $data->{hourly}->[$i] | ||||
|                                                   ->{visibility} ) + 0.5 | ||||
|                                         ), | ||||
|                                     }, | ||||
|                                 ); | ||||
|  | ||||
|                                 $i++; | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         if ( ref( $data->{daily} ) eq "ARRAY" | ||||
|                             && scalar( @{ $data->{daily} } ) > 0 ) | ||||
|                         { | ||||
|                             my $i = 0; | ||||
|                             for ( @{ $data->{daily} } ) { | ||||
|                                 push( | ||||
|                                     @{ $self->{cached}->{forecast}->{daily} }, | ||||
|                                     { | ||||
|                                         'pubDate' => strftimeWrapper( | ||||
|                                             "%a, %e %b %Y %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{daily}->[$i]->{dt} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'day_of_week' => strftime( | ||||
|                                             "%a, %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{daily}->[$i]->{dt} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'sunrise' => strftime( | ||||
|                                             "%H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{daily}->[$i]->{sunrise} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'sunset' => strftime( | ||||
|                                             "%a, %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{daily}->[$i]->{sunset} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'moonrise' => strftime( | ||||
|                                             "%a, %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{daily}->[$i]->{moonrise} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'moon_phase' => | ||||
|                                           $data->{daily}->[$i]->{moon_phase}, | ||||
|                                         'moonset' => strftime( | ||||
|                                             "%a, %H:%M", | ||||
|                                             localtime( | ||||
|                                                 $data->{daily}->[$i]->{moonset} | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'temperature' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{day} ) | ||||
|                                         ), | ||||
|                                         'temperature_morn' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{morn} ) | ||||
|                                         ), | ||||
|                                         'temperature_eve' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{eve} ) | ||||
|                                         ), | ||||
|                                         'temperature_night' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{night} ) | ||||
|                                         ), | ||||
|                                         'tempFeelsLike_morn' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i] | ||||
|                                                   ->{feels_like}->{morn} ) | ||||
|                                         ), | ||||
|                                         'tempFeelsLike_eve' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i] | ||||
|                                                   ->{feels_like}->{eve} ) | ||||
|                                         ), | ||||
|                                         'tempFeelsLike_night' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i] | ||||
|                                                   ->{feels_like}->{night} ) | ||||
|                                         ), | ||||
|                                         'tempFeelsLike_day' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i] | ||||
|                                                   ->{feels_like}->{day} ) | ||||
|                                         ), | ||||
|                                         'temp_c' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{day} ) | ||||
|                                         ), | ||||
|                                         'low_c' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{min} ) | ||||
|                                         ), | ||||
|                                         'high_c' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{max} ) | ||||
|                                         ), | ||||
|                                         'tempLow' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{min} ) | ||||
|                                         ), | ||||
|                                         'tempHigh' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i]->{temp} | ||||
|                                                   ->{max} ) | ||||
|                                         ), | ||||
|                                         'dew_point' => int( | ||||
|                                             sprintf( "%.0f", | ||||
|                                                 $data->{daily}->[$i] | ||||
|                                                   ->{dew_point} ) | ||||
|                                         ), | ||||
|                                         'humidity' => | ||||
|                                           $data->{daily}->[$i]->{humidity}, | ||||
|                                         'condition' => encode_utf8( | ||||
|                                             $data->{daily}->[$i]->{weather} | ||||
|                                               ->[0]->{description} | ||||
|                                         ), | ||||
|                                         'code' => $codes{ | ||||
|                                             $data->{daily}->[$i]->{weather} | ||||
|                                               ->[0]->{id} | ||||
|                                         }, | ||||
|                                         'iconAPI' => | ||||
|                                           $data->{daily}->[$i]->{weather}->[0] | ||||
|                                           ->{icon}, | ||||
|                                         'pressure' => int( | ||||
|                                             sprintf( "%.1f", | ||||
|                                                 $data->{daily}->[$i]->{pressure} | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{daily}->[$i] | ||||
|                                                       ->{wind_speed} * 3.6 | ||||
|                                                 ) | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind_speed' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{daily}->[$i] | ||||
|                                                       ->{wind_speed} * 3.6 | ||||
|                                                 ) | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind_gust' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{daily}->[$i] | ||||
|                                                       ->{wind_gust} * 3.6 | ||||
|                                                 ) | ||||
|                                             ) + 0.5 | ||||
|                                         ), | ||||
|                                         'wind_direction' => int( | ||||
|                                             sprintf( | ||||
|                                                 "%.1f", | ||||
|                                                 ( | ||||
|                                                     $data->{daily}->[$i] | ||||
|                                                       ->{wind_deg} | ||||
|                                                 ) | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'cloudCover' => | ||||
|                                           $data->{daily}->[$i]->{clouds}, | ||||
|                                         'code' => $codes{ | ||||
|                                             $data->{daily}->[$i]->{weather} | ||||
|                                               ->[0]->{id} | ||||
|                                         }, | ||||
|                                         'rain' => $data->{daily}->[$i]->{rain}, | ||||
|                                         'snow' => $data->{daily}->[$i]->{snow}, | ||||
|                                         'uvi'  => $data->{daily}->[$i]->{uvi}, | ||||
|                                     }, | ||||
|                                 ); | ||||
|  | ||||
|                                 $i++; | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         if ( ref( $data->{alerts} ) eq "ARRAY" | ||||
|                             && scalar( @{ $data->{alerts} } ) > 0 ) | ||||
|                         { | ||||
|                             my $i = 0; | ||||
|                             for ( @{ $data->{alerts} } ) { | ||||
|                                 push( | ||||
|                                     @{ $self->{cached}->{alerts} }, | ||||
|                                     { | ||||
|                                         'End' => strftimeWrapper( | ||||
|                                             "%a, %e %b %Y %H:%M", | ||||
|                                             localtime( | ||||
|                                                 ( | ||||
|                                                     $data->{alerts}->[$i]->{end} | ||||
|                                                 ) | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'Start' => strftimeWrapper( | ||||
|                                             "%a, %e %b %Y %H:%M", | ||||
|                                             localtime( | ||||
|                                                 ( | ||||
|                                                     $data->{alerts}->[$i] | ||||
|                                                       ->{start} | ||||
|                                                 ) | ||||
|                                             ) | ||||
|                                         ), | ||||
|                                         'Description' => encode_utf8( | ||||
|                                             $data->{alerts}->[$i]->{description} | ||||
|                                         ), | ||||
|                                         'SenderName' => encode_utf8( | ||||
|                                             $data->{alerts}->[$i]->{sender_name} | ||||
|                                         ), | ||||
|                                         'Event' => encode_utf8( | ||||
|                                             $data->{alerts}->[$i]->{event} | ||||
|                                         ), | ||||
|                                     }, | ||||
|                                 ); | ||||
|  | ||||
|                                 $i++; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else { _ErrorHandling( $self, 'OpenWeatherMap ' . $response ); } | ||||
|     } | ||||
|  | ||||
|     $self->{endpoint} = 'none' if ( $self->{endpoint} eq 'onecall' ); | ||||
|  | ||||
|     _RetrieveDataFromOpenWeatherMap($self) | ||||
|       if ( $self->{endpoint} eq 'weather' ); | ||||
|  | ||||
|     _CallWeatherCallbackFn($self) if ( $self->{endpoint} eq 'none' ); | ||||
|  | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub _CallWeatherCallbackFn { | ||||
|     my $self = shift; | ||||
|  | ||||
|     #     print 'Dumperausgabe: ' . Dumper $self; | ||||
|     ### Aufruf der callbackFn | ||||
|     ::Weather_RetrieveCallbackFn( $self->{devName} ); | ||||
|  | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub _ErrorHandling { | ||||
|     my $self = shift; | ||||
|     my $err  = shift; | ||||
|  | ||||
|     $self->{cached}->{current_date_time} = | ||||
|       strftimeWrapper( "%a, %e %b %Y %H:%M", localtime( $self->{fetchTime} ) ); | ||||
|     $self->{cached}->{status}   = $err; | ||||
|     $self->{cached}->{validity} = 'stale'; | ||||
|  | ||||
|     return; | ||||
| } | ||||
|  | ||||
| sub _CreateForecastRef { | ||||
|     my $self = shift; | ||||
|  | ||||
|     my $forecastRef = ( | ||||
|         { | ||||
|             lat           => $self->{lat}, | ||||
|             long          => $self->{long}, | ||||
|             apiMaintainer => | ||||
| 'Marko Oldenburg (<a href=https://forum.fhem.de/index.php?action=profile;u=13684>CoolTux</a>)', | ||||
|             apiVersion => version->parse( __PACKAGE__->VERSION() )->normal, | ||||
|         } | ||||
|     ); | ||||
|  | ||||
|     return $forecastRef; | ||||
| } | ||||
|  | ||||
| sub strftimeWrapper { | ||||
|     my @data   = @_; | ||||
|     my $string = POSIX::strftime(@data); | ||||
|  | ||||
|     $string =~ s/\xe4/ä/xg; | ||||
|     $string =~ s/\xc4/Ä/xg; | ||||
|     $string =~ s/\xf6/ö/xg; | ||||
|     $string =~ s/\xd6/Ö/xg; | ||||
|     $string =~ s/\xfc/ü/xg; | ||||
|     $string =~ s/\xdc/Ü/xg; | ||||
|     $string =~ s/\xdf/ß/xg; | ||||
|     $string =~ s/\xdf/ß/xg; | ||||
|     $string =~ s/\xe1/á/xg; | ||||
|     $string =~ s/\xe9/é/xg; | ||||
|     $string =~ s/\xc1/Á/xg; | ||||
|     $string =~ s/\xc9/É/xg; | ||||
|  | ||||
|     return $string; | ||||
| } | ||||
|  | ||||
| ############################################################################## | ||||
|  | ||||
| 1; | ||||
|  | ||||
| =pod | ||||
|  | ||||
| =encoding utf8 | ||||
|  | ||||
| =for :application/json;q=META.json OpenWeatherMapAPI.pm | ||||
| { | ||||
|   "abstract": "Weather API for Weather OpenWeatherMap", | ||||
|   "x_lang": { | ||||
|     "de": { | ||||
|       "abstract": "Wetter API für OpenWeatherMap" | ||||
|     } | ||||
|   }, | ||||
|   "version": "v3.0.2", | ||||
|   "author": [ | ||||
|     "Marko Oldenburg <fhemdevelopment@cooltux.net>" | ||||
|   ], | ||||
|   "x_fhem_maintainer": [ | ||||
|     "CoolTux" | ||||
|   ], | ||||
|   "x_fhem_maintainer_github": [ | ||||
|     "CoolTuxNet" | ||||
|   ], | ||||
|   "prereqs": { | ||||
|     "runtime": { | ||||
|       "requires": { | ||||
|         "FHEM::Meta": 0, | ||||
|         "HttpUtils": 0, | ||||
|         "strict": 0, | ||||
|         "warnings": 0, | ||||
|         "constant": 0, | ||||
|         "POSIX": 0, | ||||
|         "JSON::PP": 0 | ||||
|       }, | ||||
|       "recommends": { | ||||
|         "JSON": 0 | ||||
|       }, | ||||
|       "suggests": { | ||||
|         "JSON::XS": 0, | ||||
|         "Cpanel::JSON::XS": 0 | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| =end :application/json;q=META.json | ||||
|  | ||||
| =cut | ||||
|  | ||||
| __END__ | ||||
| @@ -1,374 +0,0 @@ | ||||
| # $Id: YahooWeatherAPI.pm 16641 2018-04-21 12:28:38Z neubert $ | ||||
|  | ||||
| ############################################################################## | ||||
| # | ||||
| #     YahooWeatherAPI.pm | ||||
| #     Copyright 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 HttpUtils; | ||||
| use JSON;               # apt-get install libperl-JSON on Debian and derivatives | ||||
| #use Data::Dumper;       # for Debug only | ||||
|  | ||||
| # Yahoo! Weather API: http://developer.yahoo.com/weather/ | ||||
|  | ||||
| use constant URL => "https://query.yahooapis.com/v1/public/yql?q=select%%20*%%20from%%20weather.forecast%%20where%%20woeid=%s%%20and%%20u=%%27c%%27&format=%s&env=store%%3A%%2F%%2Fdatatables.org%%2Falltableswithkeys"; | ||||
|  | ||||
|  | ||||
| # Mapping / translation of current weather codes 0-47 | ||||
| my @YahooCodes_en = ( | ||||
|        'tornado', 'tropical storm', 'hurricane', 'severe thunderstorms', 'thunderstorms', 'mixed rain and snow', | ||||
|        'mixed rain and sleet', 'mixed snow and sleet', 'freezing drizzle', 'drizzle', 'freezing rain' ,'showers', | ||||
|        'showers', 'snow flurries', 'light snow showers', 'blowing snow', 'snow', 'hail', | ||||
|        'sleet', 'dust', 'foggy', 'haze', 'smoky', 'blustery', | ||||
|        'windy', 'cold', 'cloudy', | ||||
|        'mostly cloudy', # night | ||||
|        'mostly cloudy', # day | ||||
|        'partly cloudy', # night | ||||
|        'partly cloudy', # day | ||||
|        'clear', | ||||
|        'sunny', | ||||
|        'fair', #night | ||||
|        'fair', #day | ||||
|        'mixed rain and hail', | ||||
|        'hot', 'isolated thunderstorms', 'scattered thunderstorms', 'scattered thunderstorms', 'scattered showers', 'heavy snow', | ||||
|        'scattered snow showers', 'heavy snow', 'partly cloudy', 'thundershowers', 'snow showers', 'isolated thundershowers'); | ||||
|  | ||||
| my @YahooCodes_de = ( | ||||
|        'Tornado', 'schwerer Sturm', 'Orkan', 'schwere Gewitter', 'Gewitter', 'Regen und Schnee', | ||||
|        'Regen und Graupel', 'Schnee und Graupel', 'Eisregen', 'Nieselregen', 'gefrierender Regen' ,'Schauer', | ||||
|        'Schauer', 'Schneetreiben', 'leichte Schneeschauer', 'Schneeverwehungen', 'Schnee', 'Hagel', | ||||
|        'Graupel', 'Staub', 'Nebel', 'Dunst', 'Smog', 'Sturm', | ||||
|        'windig', 'kalt', 'wolkig', | ||||
|        'überwiegend wolkig', # night | ||||
|        'überwiegend wolkig', # day | ||||
|        'teilweise wolkig', # night | ||||
|        'teilweise wolkig', # day | ||||
|        'klar', # night | ||||
|        'sonnig', | ||||
|        'heiter', # night | ||||
|        'heiter', # day | ||||
|        'Regen und Hagel', | ||||
|        'heiß', 'einzelne Gewitter', 'vereinzelt Gewitter', 'vereinzelt Gewitter', 'vereinzelt Schauer', 'starker Schneefall', | ||||
|        'vereinzelt Schneeschauer', 'starker Schneefall', 'teilweise wolkig', 'Gewitterregen', 'Schneeschauer', 'vereinzelt Gewitter'); | ||||
|  | ||||
| my @YahooCodes_nl = ( | ||||
|        'tornado', 'zware storm', 'orkaan', 'hevig onweer', 'onweer', | ||||
|        'regen en sneeuw', | ||||
|        'regen en ijzel', 'sneeuw en ijzel', 'aanvriezende motregen', | ||||
|        'motregen', 'aanvriezende regen' ,'buien', | ||||
|        'buien', 'sneeuw windstoten', 'lichte sneeuwbuien', | ||||
|        'stuifsneeuw', 'sneeuw', 'hagel', | ||||
|        'ijzel', 'stof', 'mist', 'waas', 'smog', 'onstuimig', | ||||
|        'winderig', 'koud', 'bewolkt', | ||||
|        'overwegend bewolkt', # night | ||||
|        'overwegend bewolkt', # day | ||||
|        'gedeeltelijk bewolkt', # night | ||||
|        'gedeeltelijk bewolkt', # day | ||||
|        'helder', #night | ||||
|        'zonnig', | ||||
|        'mooi', #night | ||||
|        'mooi', #day | ||||
|        'regen en hagel', | ||||
|        'heet', 'plaatselijk onweer', 'af en toe onweer', 'af en toe onweer', 'af en toe regenbuien', 'hevige sneeuwval', | ||||
|        'af en toe sneeuwbuien', 'hevige sneeuwval', 'deels bewolkt', | ||||
|        'onweersbuien', 'sneeuwbuien', 'af en toe onweersbuien'); | ||||
|  | ||||
| my @YahooCodes_fr = ( | ||||
|        'tornade', 'tempête tropicale', 'ouragan', 'tempête sévère', 'orage', 'pluie et neige', | ||||
|        'pluie et grésil', 'neige et grésil', 'bruine verglassante', 'bruine', 'pluie verglassante' ,'averse', | ||||
|        'averses', 'tourbillon de neige', 'légères averses de neige', 'rafale de neige', 'neige', 'grêle', | ||||
|        'giboulées', 'poussières', 'brouillard', 'brume', 'enfumé', 'orageux', | ||||
|        'venteux', 'froid', 'nuageux', | ||||
|        'couverte', # night | ||||
|        'couvert', # day | ||||
|        'partiellement couverte', # night | ||||
|        'partiellement couvert', # day | ||||
|        'clair', | ||||
|        'ensoleillé', | ||||
|        'douce', #night | ||||
|        'agréable', #day | ||||
|        'pluie et grêle', | ||||
|        'chaud', 'orages isolés', 'tempêtes éparses', 'orages épars', 'averses éparses', 'tempête de neige', | ||||
|        'chûtes de neiges éparses', 'tempêtes de neige', 'partielement nuageux', 'averses orageuses', 'chûte de neige', 'chûtes de neige isolées'); | ||||
|  | ||||
| my @YahooCodes_pl = ( | ||||
|        'tornado', 'burza tropikalna', 'huragan', 'porywiste burze', 'burze', 'deszcz ze śniegiem', | ||||
|        'deszcz i deszcz ze śniegiem', 'śnieg i deszcz ze śniegiem', 'marznąca mżawka', 'mżawka', 'marznący deszcz' ,'deszcz', | ||||
|        'deszcz', 'przelotne opady śniegu', 'lekkie opady śniegu', 'zamieć śnieżna', 'śnieg', 'grad', | ||||
|        'deszcz ze śniegiem', 'pył', 'mgła', 'mgła', 'smog', 'przenikliwie', | ||||
|        'wietrznie', 'zimno', 'pochmurno', | ||||
|        'pochmurno', # night | ||||
|        'pochmurno', # day | ||||
|        'częściowe zachmurzenie', # night | ||||
|        'częściowe zachmurzenie', # day | ||||
|        'czyste niebo', | ||||
|        'słonecznie', | ||||
|        'ładna noc', #night | ||||
|        'ładny dzień', #day | ||||
|        'deszcz z gradem', | ||||
|        'gorąco', 'gdzieniegdzie burze', 'burze', 'burze', 'przelotne opady śniegu', 'duże opady śniegu', | ||||
|        'ciężkie opady śniegu', 'dużo śniegu', 'częściowe zachmurzenie', 'burze z deszczem', 'opady śniegu', 'przejściowo burze'); | ||||
|  | ||||
| my @YahooCodes_it = ( | ||||
|        'tromba d\'aria', 'tempesta tropicale', 'uragano', 'temporali di grande intensità', 'temporali', 'pioggia mista e neve', | ||||
|        'pioggia mista e nevischio', 'neve mista e nevischio', 'pioggia gelata', 'pioggia leggera', 'grandine' ,'rovesci', | ||||
|        'piogge', 'raffiche di neve', 'deboli nevicate', 'bufera di neve', 'neve', 'grandine', | ||||
|        'nevischio', 'pulviscolo', 'nebbia', 'foschia', 'smog', 'ventoso', | ||||
|        'ventoso', 'freddo', 'nuvoloso', | ||||
|        'parzialmente nuvoloso', # night | ||||
|        'parzialmente nuvoloso', # day | ||||
|        'parzialmente nuvoloso', # night | ||||
|        'parzialmente nuvoloso', # day | ||||
|        'sereno', | ||||
|        'soleggiato', | ||||
|        'bel tempo', #night | ||||
|        'bel tempo', #day | ||||
|        'pioggia mista a grandine', | ||||
|        'caldo', 'temporali isolati', 'temporali sparsi', 'temporali sparsi', 'piogge sparse', 'forti nevicate', | ||||
|        'nevicate sparse', 'forti nevicate', 'parzialmente nuvoloso', 'rovesci temporaleschi', 'rovesci di neve', 'temporali isolati'); | ||||
|  | ||||
|  | ||||
| ################################### | ||||
|  | ||||
| # Cache | ||||
| my %YahooWeatherAPI_CachedData= (); | ||||
| my %YahooWeatherAPI_CachedDataTs= (); | ||||
|  | ||||
| ################################### | ||||
|  | ||||
| # | ||||
| # there is a bug in the Yahoo Weather API that gets all units wrong | ||||
| # these routines fix that | ||||
|  | ||||
|  | ||||
| sub value_to_C($) { | ||||
|     my ($F)= @_; | ||||
|     return(int(($F-32)*5/9+0.5)); | ||||
| } | ||||
|  | ||||
| sub value_to_hPa($) { | ||||
|     my ($inHg)= @_; | ||||
|     return int($inHg/33.86390+0.5); | ||||
| } | ||||
|  | ||||
| sub value_to_km($) { | ||||
|     my ($value)= @_; | ||||
|     return int($value/1.609347219+0.5); | ||||
| } | ||||
|  | ||||
| ################################### | ||||
|  | ||||
| # call: YahooWeatherAPI_RetrieveData(%%args) | ||||
| # | ||||
| # the args hash reference must contain at least | ||||
| #   woeid          => WOEID [WHERE-ON-EARTH-ID], go to http://weather.yahoo.com to find out | ||||
| #   format         => xml or json | ||||
| #   blocking       => 0 or 1 | ||||
| #   callbackFnRef  => reference to callback function with arguments ($argsRef, $err, $result) | ||||
| # the args hash reference is returned as first argument of the callbackFn | ||||
| # | ||||
|  | ||||
| sub YahooWeatherAPI_RetrieveData($) { | ||||
|     my ($argsRef)= @_; | ||||
|     YahooWeatherAPI_RetrieveDataWithCache(0, $argsRef); | ||||
| } | ||||
|  | ||||
| sub YahooWeatherAPI_RetrieveDataWithCache($$) { | ||||
|  | ||||
|     my ($maxage, $argsRef)= @_; | ||||
|     my $woeid= $argsRef->{woeid}; | ||||
|  | ||||
|     Log3 undef, 5, "YahooWeatherAPI: retrieve weather for $woeid."; | ||||
|  | ||||
|     # retrieve data from cache | ||||
|     my $ts= $YahooWeatherAPI_CachedDataTs{$woeid}; | ||||
|     if(defined($ts)) { | ||||
|         my $now= time(); | ||||
|         my $age= $now- $ts; | ||||
|         if($age< $maxage) { | ||||
|             Log3 undef, 5, "YahooWeatherAPI: data is cached, age $age seconds < $maxage seconds."; | ||||
|             $argsRef->{callbackFnRef}($argsRef, "", $YahooWeatherAPI_CachedData{$woeid}); | ||||
|             return; | ||||
|         } else { | ||||
|             Log3 undef, 5, "YahooWeatherAPI: cache is expired, age $age seconds > $maxage seconds."; | ||||
|         } | ||||
|     } else { | ||||
|             Log3 undef, 5, "YahooWeatherAPI: no data in cache."; | ||||
|     } | ||||
|  | ||||
|     my $format= $argsRef->{format}; | ||||
|     my $blocking= $argsRef->{blocking}; | ||||
|     my $callbackFnRef= $argsRef->{callbackFnRef}; | ||||
|  | ||||
|     my $url = sprintf(URL, $woeid, $format); | ||||
|  | ||||
|     #Debug "Retrieve Yahoo Weather data for " . $argsRef->{hash}->{NAME}; | ||||
|  | ||||
|     if ($blocking) { | ||||
|         # do not use noshutdown => 0 in parameters | ||||
|         my $response = HttpUtils_BlockingGet({ url => $url, timeout => 15 }); | ||||
|         my %param= (argsRef => $argsRef); | ||||
|         YahooWeatherAPI_RetrieveDataFinished(\%param, undef, $response); | ||||
|     } else { | ||||
|         # do not use noshutdown => 0 in parameters | ||||
|         HttpUtils_NonblockingGet({ | ||||
|             url        => $url, | ||||
|             timeout    => 15, | ||||
|             argsRef    => $argsRef, | ||||
|             callback   => \&YahooWeatherAPI_RetrieveDataFinished, | ||||
|             }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub YahooWeatherAPI_RetrieveDataFinished($$$) { | ||||
|     my ($paramRef, $err, $response) = @_; | ||||
|     my $argsRef= $paramRef->{argsRef}; | ||||
|     #Debug "Finished retrieving Yahoo Weather data for " . $argsRef->{hash}->{NAME}; | ||||
|     if(!$err) { | ||||
|         my $woeid= $argsRef->{woeid}; | ||||
|         $YahooWeatherAPI_CachedDataTs{$woeid}= time(); | ||||
|         $YahooWeatherAPI_CachedData{$woeid}= $response; | ||||
|         Log3 undef, 5, "YahooWeatherAPI: caching data."; | ||||
|     } | ||||
|     $argsRef->{callbackFnRef}($argsRef, $err, $response); | ||||
| } | ||||
|  | ||||
|  | ||||
| # this decodes a JSON result and returns the Weather Channel hash reference | ||||
| sub YahooWeatherAPI_JSONReturnChannelData($) { | ||||
|     my ($response)= @_; | ||||
|     return("empty response", undef) unless($response); | ||||
|     #Debug "Decoding response: $response"; | ||||
|     #Debug "response: " . Dumper($response); | ||||
|     my $data; | ||||
|     eval { $data= decode_json($response) }; | ||||
|     return($@, undef) if($@); | ||||
|     my $query= $data->{query}; | ||||
|     #Debug Dumper($query); | ||||
|     my $count= $query->{count}; | ||||
|     #Debug "$count result(s)."; | ||||
|     return("$count result(s) retrieved", undef) unless($count == 1); | ||||
|     my $channel= $query->{results}{channel}; | ||||
|     return(undef, $channel); | ||||
| } | ||||
|  | ||||
| sub YahooWeatherAPI_ConvertChannelData($) { | ||||
|     my ($data)= @_; # hash reference | ||||
|  | ||||
|     $data->{wind}{chill}= value_to_C($data->{wind}{chill}); # # API delivers wrong value | ||||
|     $data->{atmosphere}{pressure}= value_to_hPa($data->{atmosphere}{pressure}); # API delivers wrong value | ||||
|  | ||||
|  | ||||
|     my $units= YahooWeatherAPI_units($data); # units hash reference | ||||
|  | ||||
|     $data->{wind}{speed}= value_to_km($data->{wind}{speed}); # API delivers km | ||||
|     $data->{atmosphere}{visibility}= value_to_km($data->{atmosphere}{visibility}); # API delivers km | ||||
|  | ||||
|     return 0 if($units->{temperature} eq "C"); | ||||
|  | ||||
|     my $item= $data->{item}; | ||||
|     $item->{condition}{temp}= value_to_C($item->{condition}{temp}); | ||||
|  | ||||
|     my $forecast= $item->{forecast}; | ||||
|     foreach my $fc (@{$forecast}) { | ||||
|         $fc->{low}= value_to_C($fc->{low}); | ||||
|         $fc->{high}= value_to_C($fc->{high}); | ||||
|     } | ||||
|     return 1; | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| sub YahooWeatherAPI_ParseDateTime($) { | ||||
|  | ||||
|     my ($value)= @_; ### "Fri, 13 Nov 2015 8:00 am CET" | ||||
|  | ||||
|     my @months= qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/; | ||||
|     my %monthindex; | ||||
|     @monthindex{@months} = (0..$#months); | ||||
|  | ||||
|     if($value =~ '^(\w{3}), (\d{1,2}) (\w{3}) (\d{4}) (\d{1,2}):(\d{2}) (\w{2}) (\w{3,4})$') { | ||||
|         my ($wd, $d, $mon, $y, $h, $n, $p, $tz)= ($1,$2,$3,$4,$5,$6,$7,$8); | ||||
|         # 12 AM= 0, 12 PM= 12 | ||||
|         $h+=12 if($h==12); if($p eq "PM") { $h= ($h+12) % 24 } else { $h%= 12 }; | ||||
|         my $m= $monthindex{$mon}; | ||||
|         return undef unless defined($m); | ||||
|         #main::Debug "######  $value -> $wd $d $m $y $h:$n $tz"; | ||||
|         # $mday= 1.. | ||||
|         # $month= 0..11 | ||||
|         # $year is year-1900 | ||||
|         # we ignore the time zone as it probably never changes for a weather device an assume | ||||
|         # local time zone | ||||
|         return fhemTimeLocal(0, $n, $h, $d, $m, $y-1900); | ||||
|     } else { | ||||
|         return undef; | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub YahooWeatherAPI_pubDate($) { | ||||
|  | ||||
|     my ($channel)= @_; | ||||
|  | ||||
|     ### pubDate  Fri, 13 Nov 2015 8:00 am CET | ||||
|     if(!defined($channel->{item}{pubDate})) { | ||||
|         return("no pubDate received", "", undef); | ||||
|     }; | ||||
|     my $pubDate= $channel->{item}{pubDate}; | ||||
|     my $ts= YahooWeatherAPI_ParseDateTime($pubDate); | ||||
|     if(defined($ts)) { | ||||
|         return("okay", $pubDate, $ts); | ||||
|     } else { | ||||
|         return("could not parse pubDate $pubDate", $pubDate, undef); | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub YahooWeatherAPI_units($) { | ||||
|  | ||||
|     my ($channel)= @_; | ||||
|     return $channel->{units}; | ||||
| } | ||||
|  | ||||
| sub YahooWeatherAPI_getYahooCodes($) { | ||||
|  | ||||
|     my ($lang)= @_; | ||||
|  | ||||
|     if($lang eq "de") { | ||||
|         return @YahooCodes_de; | ||||
|     } elsif($lang eq "nl") { | ||||
|         return @YahooCodes_nl; | ||||
|     } elsif($lang eq "fr") { | ||||
|         return @YahooCodes_fr; | ||||
|     } elsif($lang eq "pl") { | ||||
|         return @YahooCodes_pl; | ||||
|     } elsif($lang eq "it") { | ||||
|         return @YahooCodes_it; | ||||
|     } else { | ||||
|         return @YahooCodes_en; | ||||
|     } | ||||
| } | ||||
|  | ||||
| ############################################################################## | ||||
|  | ||||
| 1; | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1029
									
								
								lib/FHEM/APIs/Weather/OpenWeatherMapAPI.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1029
									
								
								lib/FHEM/APIs/Weather/OpenWeatherMapAPI.pm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user