diff --git a/59_Weather.pm b/FHEM/59_Weather.pm
similarity index 100%
rename from 59_Weather.pm
rename to FHEM/59_Weather.pm
diff --git a/OpenWeatherMapAPI.pm b/OpenWeatherMapAPI.pm
deleted file mode 100644
index 9c6cc3d..0000000
--- a/OpenWeatherMapAPI.pm
+++ /dev/null
@@ -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 (CoolTux)',
- 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 "
- ],
- "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__
diff --git a/YahooWeatherAPI.pm b/YahooWeatherAPI.pm
deleted file mode 100644
index a7be94c..0000000
--- a/YahooWeatherAPI.pm
+++ /dev/null
@@ -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 .
-#
-##############################################################################
-
-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;
diff --git a/DarkSkyAPI.pm b/lib/FHEM/APIs/Weather/DarkSkyAPI.pm
similarity index 59%
rename from DarkSkyAPI.pm
rename to lib/FHEM/APIs/Weather/DarkSkyAPI.pm
index 4902524..077b05c 100644
--- a/DarkSkyAPI.pm
+++ b/lib/FHEM/APIs/Weather/DarkSkyAPI.pm
@@ -27,22 +27,24 @@
#
#
###############################################################################
-
-package DarkSkyAPI;
+package FHEM::APIs::Weather::DarkSkyAPI;
use strict;
use warnings;
use FHEM::Meta;
-use Data::Dumper;
-
-FHEM::Meta::Load(__PACKAGE__);
-use version 0.50; our $VERSION = $main::packages{DarkSkyAPI}{META}{version};
-
-package DarkSkyAPI::Weather;
-use strict;
-use warnings;
use POSIX;
use HttpUtils;
+use experimental qw /switch/;
+
+my $META = {};
+my $ret = FHEM::Meta::getMetadata( __FILE__, $META );
+return "$@" if ($@);
+return $ret if ($ret);
+$::packages{DarkSkyAPI}{META} = $META;
+
+use version 0.77; our $VERSION = $META->{version};
+
+# use Data::Dumper;
# try to use JSON::MaybeXS wrapper
# for chance of better performance + open code
@@ -50,15 +52,11 @@ eval {
require JSON::MaybeXS;
import JSON::MaybeXS qw( decode_json encode_json );
1;
-};
-
-if ($@) {
- $@ = undef;
+} 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'
@@ -67,10 +65,7 @@ if ($@) {
require JSON;
import JSON qw( decode_json encode_json );
1;
- };
-
- if ($@) {
- $@ = undef;
+ } or do {
# In rare cases, Cpanel::JSON::XS may
# be installed but JSON|JSON::MaybeXS not ...
@@ -78,10 +73,7 @@ if ($@) {
require Cpanel::JSON::XS;
import Cpanel::JSON::XS qw(decode_json encode_json);
1;
- };
-
- if ($@) {
- $@ = undef;
+ } or do {
# In rare cases, JSON::XS may
# be installed but JSON not ...
@@ -89,10 +81,7 @@ if ($@) {
require JSON::XS;
import JSON::XS qw(decode_json encode_json);
1;
- };
-
- if ($@) {
- $@ = undef;
+ } or do {
# Fallback to built-in JSON which SHOULD
# be available since 5.014 ...
@@ -100,30 +89,32 @@ if ($@) {
require JSON::PP;
import JSON::PP qw(decode_json encode_json);
1;
- };
-
- if ($@) {
- $@ = undef;
+ } or do {
# Fallback to JSON::backportPP in really rare cases
require JSON::backportPP;
import JSON::backportPP qw(decode_json encode_json);
1;
- }
- }
- }
- }
-}
+ };
+ };
+ };
+ };
+};
my $missingModul = '';
-eval "use Encode qw(encode_utf8);1" or $missingModul .= "Encode ";
+## no critic (Conditional "use" statement. Use "require" to conditionally include a module (Modules::ProhibitConditionalUseStatements))
+eval { use Encode qw /encode_utf8/; 1 } or $missingModul .= 'Encode ';
+
+eval { use Readonly; 1 }
+ or $missingModul .= 'Readonly '; # apt install libreadonly-perl
+## use critic
# use Data::Dumper; # for Debug only
## API URL
-use constant DEMODATA =>
+Readonly my $DEMODATA =>
'{"latitude":50.112,"longitude":8.686,"timezone":"Europe/Berlin","currently":{"time":1551214558,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":9.65,"apparentTemperature":9.65,"dewPoint":1.39,"humidity":0.56,"pressure":1032.69,"windSpeed":0.41,"windGust":1.35,"windBearing":84,"cloudCover":0,"uvIndex":0,"visibility":10.01,"ozone":276.41},"hourly":{"summary":"Leicht bewölkt am heute Nacht.","icon":"partly-cloudy-night","data":[{"time":1551211200,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":10.59,"apparentTemperature":10.59,"dewPoint":1.84,"humidity":0.55,"pressure":1032.7,"windSpeed":0.28,"windGust":1.15,"windBearing":89,"cloudCover":0,"uvIndex":0,"visibility":10.01,"ozone":277},{"time":1551214800,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":9.58,"apparentTemperature":9.58,"dewPoint":1.36,"humidity":0.56,"pressure":1032.69,"windSpeed":0.42,"windGust":1.37,"windBearing":83,"cloudCover":0,"uvIndex":0,"visibility":10.01,"ozone":276.37},{"time":1551218400,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":8.61,"apparentTemperature":8.61,"dewPoint":0.73,"humidity":0.58,"pressure":1032.63,"windSpeed":0.5,"windGust":1.47,"windBearing":72,"cloudCover":0,"uvIndex":0,"visibility":11.47,"ozone":275.56},{"time":1551222000,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0,"precipProbability":0,"temperature":8.06,"apparentTemperature":8.06,"dewPoint":-0.45,"humidity":0.55,"pressure":1032.55,"windSpeed":0.86,"windGust":1.5,"windBearing":40,"cloudCover":0.26,"uvIndex":0,"visibility":16.09,"ozone":274.76},{"time":1551225600,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0,"precipProbability":0,"temperature":7.48,"apparentTemperature":7.48,"dewPoint":-1.38,"humidity":0.53,"pressure":1032.4,"windSpeed":1.14,"windGust":1.49,"windBearing":33,"cloudCover":0.42,"uvIndex":0,"visibility":16.09,"ozone":274.13},{"time":1551229200,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0,"precipProbability":0,"temperature":6.62,"apparentTemperature":6.62,"dewPoint":-1.89,"humidity":0.54,"pressure":1032.12,"windSpeed":1.11,"windGust":1.43,"windBearing":38,"cloudCover":0.36,"uvIndex":0,"visibility":16.09,"ozone":273.77},{"time":1551232800,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0,"precipProbability":0,"temperature":5.73,"apparentTemperature":5.73,"dewPoint":-2.39,"humidity":0.56,"pressure":1031.83,"windSpeed":1.07,"windGust":1.34,"windBearing":46,"cloudCover":0.29,"uvIndex":0,"visibility":16.09,"ozone":273.55},{"time":1551236400,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":4.91,"apparentTemperature":4.91,"dewPoint":-2.81,"humidity":0.57,"pressure":1031.49,"windSpeed":1.03,"windGust":1.23,"windBearing":54,"cloudCover":0.23,"uvIndex":0,"visibility":16.09,"ozone":273.44},{"time":1551240000,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":4.02,"apparentTemperature":4.02,"dewPoint":-3.26,"humidity":0.59,"pressure":1031.18,"windSpeed":0.99,"windGust":1.15,"windBearing":63,"cloudCover":0.21,"uvIndex":0,"visibility":16.09,"ozone":273.43},{"time":1551243600,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":3.26,"apparentTemperature":3.26,"dewPoint":-3.61,"humidity":0.61,"pressure":1030.85,"windSpeed":0.96,"windGust":1.08,"windBearing":73,"cloudCover":0.22,"uvIndex":0,"visibility":16.09,"ozone":273.5},{"time":1551247200,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":3.19,"apparentTemperature":3.19,"dewPoint":-3.51,"humidity":0.61,"pressure":1030.54,"windSpeed":0.92,"windGust":1.01,"windBearing":83,"cloudCover":0.22,"uvIndex":0,"visibility":16.09,"ozone":273.65},{"time":1551250800,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":4.28,"apparentTemperature":4.28,"dewPoint":-2.62,"humidity":0.61,"pressure":1030.25,"windSpeed":0.83,"windGust":0.88,"windBearing":93,"cloudCover":0.2,"uvIndex":0,"visibility":16.09,"ozone":273.79},{"time":1551254400,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":6.29,"apparentTemperature":6.29,"dewPoint":-1.28,"humidity":0.58,"pressure":1029.92,"windSpeed":0.72,"windGust":0.78,"windBearing":105,"cloudCover":0.19,"uvIndex":1,"visibility":16.09,"ozone":273.91},{"time":1551258000,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":8.45,"apparentTemperature":8.45,"dewPoint":-0.11,"humidity":0.55,"pressure":1029.54,"windSpeed":0.68,"windGust":0.77,"windBearing":116,"cloudCover":0.16,"uvIndex":1,"visibility":16.09,"ozone":274.33},{"time":1551261600,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":10.79,"apparentTemperature":10.79,"dewPoint":0.73,"humidity":0.5,"pressure":1028.98,"windSpeed":0.81,"windGust":0.9,"windBearing":125,"cloudCover":0.14,"uvIndex":2,"visibility":16.09,"ozone":275.21},{"time":1551265200,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":13.36,"apparentTemperature":13.36,"dewPoint":1.44,"humidity":0.44,"pressure":1028.33,"windSpeed":1.06,"windGust":1.12,"windBearing":131,"cloudCover":0.11,"uvIndex":3,"visibility":16.09,"ozone":276.39},{"time":1551268800,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":15.58,"apparentTemperature":15.58,"dewPoint":1.98,"humidity":0.4,"pressure":1027.59,"windSpeed":1.26,"windGust":1.28,"windBearing":140,"cloudCover":0.08,"uvIndex":3,"visibility":16.09,"ozone":277.31},{"time":1551272400,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":17.3,"apparentTemperature":17.3,"dewPoint":2.23,"humidity":0.36,"pressure":1026.71,"windSpeed":1.3,"windGust":1.31,"windBearing":154,"cloudCover":0.05,"uvIndex":2,"visibility":16.09,"ozone":277.73},{"time":1551276000,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":18.44,"apparentTemperature":18.44,"dewPoint":2.27,"humidity":0.34,"pressure":1025.7,"windSpeed":1.28,"windGust":1.28,"windBearing":172,"cloudCover":0.02,"uvIndex":2,"visibility":16.09,"ozone":277.96},{"time":1551279600,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":18.49,"apparentTemperature":18.49,"dewPoint":2.21,"humidity":0.34,"pressure":1024.91,"windSpeed":1.24,"windGust":1.27,"windBearing":184,"cloudCover":0,"uvIndex":1,"visibility":16.09,"ozone":278.36},{"time":1551283200,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":17.44,"apparentTemperature":17.44,"dewPoint":2.05,"humidity":0.36,"pressure":1024.53,"windSpeed":1.18,"windGust":1.25,"windBearing":191,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":278.97},{"time":1551286800,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":14.98,"apparentTemperature":14.98,"dewPoint":1.79,"humidity":0.41,"pressure":1024.34,"windSpeed":1.12,"windGust":1.27,"windBearing":194,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":279.76},{"time":1551290400,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":12.61,"apparentTemperature":12.61,"dewPoint":1.52,"humidity":0.47,"pressure":1024.09,"windSpeed":1.11,"windGust":1.39,"windBearing":195,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":280.33},{"time":1551294000,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":10.99,"apparentTemperature":10.99,"dewPoint":1.18,"humidity":0.51,"pressure":1023.68,"windSpeed":1.2,"windGust":1.51,"windBearing":194,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":280.69},{"time":1551297600,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":9.98,"apparentTemperature":9.98,"dewPoint":0.83,"humidity":0.53,"pressure":1023.18,"windSpeed":1.34,"windGust":1.64,"windBearing":191,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":280.94},{"time":1551301200,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":9.18,"apparentTemperature":8.72,"dewPoint":0.53,"humidity":0.55,"pressure":1022.72,"windSpeed":1.49,"windGust":1.77,"windBearing":190,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":281.11},{"time":1551304800,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":8.5,"apparentTemperature":7.72,"dewPoint":0.36,"humidity":0.57,"pressure":1022.32,"windSpeed":1.71,"windGust":1.9,"windBearing":194,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":281.02},{"time":1551308400,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":7.97,"apparentTemperature":6.87,"dewPoint":0.24,"humidity":0.58,"pressure":1021.93,"windSpeed":1.94,"windGust":2.05,"windBearing":200,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":280.74},{"time":1551312000,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":7.47,"apparentTemperature":6.15,"dewPoint":0.17,"humidity":0.6,"pressure":1021.49,"windSpeed":2.11,"windGust":2.19,"windBearing":206,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":280.75},{"time":1551315600,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":6.82,"apparentTemperature":5.37,"dewPoint":0.09,"humidity":0.62,"pressure":1020.9,"windSpeed":2.12,"windGust":2.28,"windBearing":208,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":281.23},{"time":1551319200,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":6.09,"apparentTemperature":4.58,"dewPoint":0.06,"humidity":0.65,"pressure":1020.22,"windSpeed":2.06,"windGust":2.36,"windBearing":210,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":281.97},{"time":1551322800,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":5.44,"apparentTemperature":3.82,"dewPoint":0.01,"humidity":0.68,"pressure":1019.67,"windSpeed":2.06,"windGust":2.58,"windBearing":211,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":282.78},{"time":1551326400,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":4.78,"apparentTemperature":2.93,"dewPoint":-0.17,"humidity":0.7,"pressure":1019.37,"windSpeed":2.18,"windGust":3.01,"windBearing":214,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":283.53},{"time":1551330000,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":4.24,"apparentTemperature":2.13,"dewPoint":-0.34,"humidity":0.72,"pressure":1019.2,"windSpeed":2.36,"windGust":3.59,"windBearing":216,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":284.44},{"time":1551333600,"summary":"Heiter","icon":"clear-night","precipIntensity":0,"precipProbability":0,"temperature":4.24,"apparentTemperature":1.93,"dewPoint":-0.18,"humidity":0.73,"pressure":1019.01,"windSpeed":2.58,"windGust":4.25,"windBearing":218,"cloudCover":0,"uvIndex":0,"visibility":16.09,"ozone":285.68},{"time":1551337200,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":5.15,"apparentTemperature":2.83,"dewPoint":0.57,"humidity":0.72,"pressure":1018.8,"windSpeed":2.82,"windGust":5.04,"windBearing":219,"cloudCover":0.2,"uvIndex":0,"visibility":16.09,"ozone":287.28},{"time":1551340800,"summary":"Leicht bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":6.58,"apparentTemperature":4.36,"dewPoint":1.66,"humidity":0.71,"pressure":1018.55,"windSpeed":3.07,"windGust":5.91,"windBearing":219,"cloudCover":0.47,"uvIndex":1,"visibility":16.09,"ozone":289.23},{"time":1551344400,"summary":"Überwiegend bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":8.36,"apparentTemperature":6.28,"dewPoint":2.82,"humidity":0.68,"pressure":1018.24,"windSpeed":3.44,"windGust":6.82,"windBearing":222,"cloudCover":0.66,"uvIndex":1,"visibility":16.09,"ozone":291.68},{"time":1551348000,"summary":"Überwiegend bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":10.57,"apparentTemperature":10.57,"dewPoint":4.07,"humidity":0.64,"pressure":1017.81,"windSpeed":4.01,"windGust":7.74,"windBearing":226,"cloudCover":0.7,"uvIndex":2,"visibility":16.09,"ozone":294.4},{"time":1551351600,"summary":"Überwiegend bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":13.39,"apparentTemperature":13.39,"dewPoint":5.36,"humidity":0.58,"pressure":1017.26,"windSpeed":4.7,"windGust":8.69,"windBearing":231,"cloudCover":0.66,"uvIndex":2,"visibility":16.09,"ozone":297.66},{"time":1551355200,"summary":"Leicht bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":15.38,"apparentTemperature":15.38,"dewPoint":6.21,"humidity":0.54,"pressure":1016.79,"windSpeed":5.32,"windGust":9.65,"windBearing":237,"cloudCover":0.58,"uvIndex":2,"visibility":16.09,"ozone":302.31},{"time":1551358800,"summary":"Leicht bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":16.34,"apparentTemperature":16.34,"dewPoint":6.33,"humidity":0.52,"pressure":1016.42,"windSpeed":5.9,"windGust":10.76,"windBearing":244,"cloudCover":0.47,"uvIndex":2,"visibility":16.09,"ozone":309.63},{"time":1551362400,"summary":"Leicht bewölkt","icon":"partly-cloudy-day","precipIntensity":0,"precipProbability":0,"temperature":16.41,"apparentTemperature":16.41,"dewPoint":6.01,"humidity":0.5,"pressure":1016.08,"windSpeed":6.41,"windGust":11.88,"windBearing":253,"cloudCover":0.32,"uvIndex":1,"visibility":16.09,"ozone":318.45},{"time":1551366000,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":16.25,"apparentTemperature":16.25,"dewPoint":5.68,"humidity":0.5,"pressure":1015.79,"windSpeed":6.54,"windGust":12.43,"windBearing":257,"cloudCover":0.23,"uvIndex":1,"visibility":16.09,"ozone":325.57},{"time":1551369600,"summary":"Heiter","icon":"clear-day","precipIntensity":0,"precipProbability":0,"temperature":15.56,"apparentTemperature":15.56,"dewPoint":5.41,"humidity":0.51,"pressure":1015.57,"windSpeed":5.99,"windGust":11.93,"windBearing":252,"cloudCover":0.24,"uvIndex":0,"visibility":16.09,"ozone":329.15},{"time":1551373200,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0,"precipProbability":0,"temperature":14.88,"apparentTemperature":14.88,"dewPoint":5.13,"humidity":0.52,"pressure":1015.39,"windSpeed":5.11,"windGust":10.86,"windBearing":244,"cloudCover":0.32,"uvIndex":0,"visibility":16.09,"ozone":331.01},{"time":1551376800,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0.0102,"precipProbability":0.02,"precipType":"rain","temperature":14.14,"apparentTemperature":14.14,"dewPoint":5.03,"humidity":0.54,"pressure":1015.15,"windSpeed":4.55,"windGust":10.16,"windBearing":239,"cloudCover":0.42,"uvIndex":0,"visibility":16.09,"ozone":334.57},{"time":1551380400,"summary":"Leicht bewölkt","icon":"partly-cloudy-night","precipIntensity":0.0406,"precipProbability":0.06,"precipType":"rain","temperature":13.44,"apparentTemperature":13.44,"dewPoint":5.32,"humidity":0.58,"pressure":1014.77,"windSpeed":4.64,"windGust":10.41,"windBearing":240,"cloudCover":0.53,"uvIndex":0,"visibility":16.09,"ozone":342.62},{"time":1551384000,"summary":"Überwiegend bewölkt","icon":"partly-cloudy-night","precipIntensity":0.1346,"precipProbability":0.14,"precipType":"rain","temperature":12.71,"apparentTemperature":12.71,"dewPoint":5.82,"humidity":0.63,"pressure":1014.32,"windSpeed":4.96,"windGust":11.04,"windBearing":244,"cloudCover":0.67,"uvIndex":0,"visibility":13.53,"ozone":352.36}]},"daily":{"summary":"Leichter Regen von Freitag bis Montag mit fallender Temperatur von 11°C am nächsten Dienstag.","icon":"rain","data":[{"time":1551135600,"summary":"Leicht bewölkt in der Nacht.","icon":"partly-cloudy-night","sunriseTime":1551161800,"sunsetTime":1551200555,"moonPhase":0.75,"precipIntensity":0,"precipIntensityMax":0.0051,"precipIntensityMaxTime":1551186000,"precipProbability":0,"temperatureHigh":21.42,"temperatureHighTime":1551193200,"temperatureLow":3.19,"temperatureLowTime":1551247200,"apparentTemperatureHigh":21.42,"apparentTemperatureHighTime":1551193200,"apparentTemperatureLow":3.19,"apparentTemperatureLowTime":1551247200,"dewPoint":0.24,"humidity":0.55,"pressure":1034.3,"windSpeed":0.51,"windGust":2.01,"windGustTime":1551189600,"windBearing":16,"cloudCover":0.02,"uvIndex":3,"uvIndexTime":1551182400,"visibility":10.2,"ozone":285.07,"temperatureMin":1.38,"temperatureMinTime":1551153600,"temperatureMax":21.42,"temperatureMaxTime":1551193200,"apparentTemperatureMin":1.38,"apparentTemperatureMinTime":1551153600,"apparentTemperatureMax":21.42,"apparentTemperatureMaxTime":1551193200},{"time":1551222000,"summary":"Den ganzen Tag lang heiter.","icon":"clear-day","sunriseTime":1551248079,"sunsetTime":1551287056,"moonPhase":0.78,"precipIntensity":0.0025,"precipIntensityMax":0.0051,"precipIntensityMaxTime":1551229200,"precipProbability":0.02,"precipType":"rain","temperatureHigh":18.49,"temperatureHighTime":1551279600,"temperatureLow":4.24,"temperatureLowTime":1551330000,"apparentTemperatureHigh":18.49,"apparentTemperatureHighTime":1551279600,"apparentTemperatureLow":1.93,"apparentTemperatureLowTime":1551333600,"dewPoint":-0.17,"humidity":0.5,"pressure":1027.91,"windSpeed":0.59,"windGust":1.9,"windGustTime":1551304800,"windBearing":139,"cloudCover":0.13,"uvIndex":3,"uvIndexTime":1551265200,"visibility":16.09,"ozone":276.58,"temperatureMin":3.19,"temperatureMinTime":1551247200,"temperatureMax":18.49,"temperatureMaxTime":1551279600,"apparentTemperatureMin":3.19,"apparentTemperatureMinTime":1551247200,"apparentTemperatureMax":18.49,"apparentTemperatureMaxTime":1551279600},{"time":1551308400,"summary":"Den ganzen Tag lang überwiegend bewölkt.","icon":"partly-cloudy-night","sunriseTime":1551334356,"sunsetTime":1551373557,"moonPhase":0.81,"precipIntensity":0.033,"precipIntensityMax":0.3175,"precipIntensityMaxTime":1551391200,"precipProbability":0.38,"precipType":"rain","temperatureHigh":16.41,"temperatureHighTime":1551362400,"temperatureLow":8.43,"temperatureLowTime":1551423600,"apparentTemperatureHigh":16.41,"apparentTemperatureHighTime":1551362400,"apparentTemperatureLow":6.74,"apparentTemperatureLowTime":1551423600,"dewPoint":3.24,"humidity":0.62,"pressure":1017.5,"windSpeed":3.8,"windGust":12.43,"windGustTime":1551366000,"windBearing":235,"cloudCover":0.34,"uvIndex":2,"uvIndexTime":1551348000,"visibility":15.27,"ozone":307.52,"temperatureMin":4.24,"temperatureMinTime":1551330000,"temperatureMax":16.41,"temperatureMaxTime":1551362400,"apparentTemperatureMin":1.93,"apparentTemperatureMinTime":1551333600,"apparentTemperatureMax":16.41,"apparentTemperatureMaxTime":1551362400},{"time":1551394800,"summary":"Überwiegend bewölkt bis abends.","icon":"partly-cloudy-day","sunriseTime":1551420633,"sunsetTime":1551460057,"moonPhase":0.84,"precipIntensity":0.188,"precipIntensityMax":0.8179,"precipIntensityMaxTime":1551409200,"precipProbability":0.88,"precipType":"rain","temperatureHigh":14.23,"temperatureHighTime":1551452400,"temperatureLow":5.98,"temperatureLowTime":1551506400,"apparentTemperatureHigh":14.23,"apparentTemperatureHighTime":1551452400,"apparentTemperatureLow":4.09,"apparentTemperatureLowTime":1551510000,"dewPoint":5.37,"humidity":0.71,"pressure":1014.79,"windSpeed":2.63,"windGust":8.53,"windGustTime":1551394800,"windBearing":284,"cloudCover":0.62,"uvIndex":2,"uvIndexTime":1551438000,"visibility":13.52,"ozone":343.54,"temperatureMin":8.43,"temperatureMinTime":1551423600,"temperatureMax":14.23,"temperatureMaxTime":1551452400,"apparentTemperatureMin":6.74,"apparentTemperatureMinTime":1551423600,"apparentTemperatureMax":14.23,"apparentTemperatureMaxTime":1551452400},{"time":1551481200,"summary":"Den ganzen Tag lang überwiegend bewölkt.","icon":"partly-cloudy-day","sunriseTime":1551506909,"sunsetTime":1551546556,"moonPhase":0.87,"precipIntensity":0.0381,"precipIntensityMax":0.2667,"precipIntensityMaxTime":1551549600,"precipProbability":0.29,"precipType":"rain","temperatureHigh":13.86,"temperatureHighTime":1551542400,"temperatureLow":9.92,"temperatureLowTime":1551596400,"apparentTemperatureHigh":13.86,"apparentTemperatureHighTime":1551542400,"apparentTemperatureLow":6.67,"apparentTemperatureLowTime":1551596400,"dewPoint":4.98,"humidity":0.73,"pressure":1017.33,"windSpeed":3.4,"windGust":11.14,"windGustTime":1551564000,"windBearing":223,"cloudCover":0.7,"uvIndex":2,"uvIndexTime":1551520800,"visibility":16.09,"ozone":338.68,"temperatureMin":5.98,"temperatureMinTime":1551506400,"temperatureMax":13.86,"temperatureMaxTime":1551542400,"apparentTemperatureMin":4.09,"apparentTemperatureMinTime":1551510000,"apparentTemperatureMax":13.86,"apparentTemperatureMaxTime":1551542400},{"time":1551567600,"summary":"Den ganzen Tag lang leichter Wind und Nacht leichter Regen.","icon":"rain","sunriseTime":1551593184,"sunsetTime":1551633056,"moonPhase":0.9,"precipIntensity":0.3886,"precipIntensityMax":0.8484,"precipIntensityMaxTime":1551650400,"precipProbability":1,"precipType":"rain","temperatureHigh":12.89,"temperatureHighTime":1551618000,"temperatureLow":9.58,"temperatureLowTime":1551664800,"apparentTemperatureHigh":12.89,"apparentTemperatureHighTime":1551618000,"apparentTemperatureLow":7.11,"apparentTemperatureLowTime":1551661200,"dewPoint":6.11,"humidity":0.71,"pressure":1010.92,"windSpeed":6.64,"windGust":18.8,"windGustTime":1551650400,"windBearing":229,"cloudCover":0.94,"uvIndex":2,"uvIndexTime":1551607200,"visibility":10.01,"ozone":332.44,"temperatureMin":9.78,"temperatureMinTime":1551600000,"temperatureMax":12.89,"temperatureMaxTime":1551618000,"apparentTemperatureMin":6.37,"apparentTemperatureMinTime":1551600000,"apparentTemperatureMax":12.89,"apparentTemperatureMaxTime":1551618000},{"time":1551654000,"summary":"Den ganzen Tag lang überwiegend bewölkt sowie leichter Wind bis Nachmittag.","icon":"wind","sunriseTime":1551679459,"sunsetTime":1551719555,"moonPhase":0.93,"precipIntensity":0.3023,"precipIntensityMax":0.8128,"precipIntensityMaxTime":1551654000,"precipProbability":0.92,"precipType":"rain","temperatureHigh":14.28,"temperatureHighTime":1551711600,"temperatureLow":7.89,"temperatureLowTime":1551769200,"apparentTemperatureHigh":14.28,"apparentTemperatureHighTime":1551711600,"apparentTemperatureLow":4.87,"apparentTemperatureLowTime":1551769200,"dewPoint":5.51,"humidity":0.67,"pressure":1003.91,"windSpeed":6.15,"windGust":20.06,"windGustTime":1551657600,"windBearing":230,"cloudCover":0.91,"uvIndex":2,"uvIndexTime":1551693600,"visibility":12.46,"ozone":369.63,"temperatureMin":9.58,"temperatureMinTime":1551664800,"temperatureMax":14.28,"temperatureMaxTime":1551711600,"apparentTemperatureMin":7.11,"apparentTemperatureMinTime":1551661200,"apparentTemperatureMax":14.28,"apparentTemperatureMaxTime":1551711600},{"time":1551740400,"summary":"Nachmittags Nebel.","icon":"fog","sunriseTime":1551765733,"sunsetTime":1551806054,"moonPhase":0.96,"precipIntensity":0.2083,"precipIntensityMax":0.4597,"precipIntensityMaxTime":1551780000,"precipProbability":0.72,"precipType":"rain","temperatureHigh":11.26,"temperatureHighTime":1551794400,"temperatureLow":5.99,"temperatureLowTime":1551855600,"apparentTemperatureHigh":11.26,"apparentTemperatureHighTime":1551794400,"apparentTemperatureLow":2.28,"apparentTemperatureLowTime":1551855600,"dewPoint":3.41,"humidity":0.65,"pressure":1001.87,"windSpeed":5.37,"windGust":16.22,"windGustTime":1551754800,"windBearing":230,"cloudCover":0.79,"uvIndex":1,"uvIndexTime":1551776400,"visibility":8.96,"ozone":442.27,"temperatureMin":7.89,"temperatureMinTime":1551769200,"temperatureMax":11.26,"temperatureMaxTime":1551794400,"apparentTemperatureMin":4.82,"apparentTemperatureMinTime":1551772800,"apparentTemperatureMax":11.26,"apparentTemperatureMaxTime":1551794400}]},"flags":{"sources":["meteoalarm","cmc","gfs","icon","isd","madis"],"meteoalarm-license":"Based on data from EUMETNET - MeteoAlarm [https://www.meteoalarm.eu/]. Time delays between this website and the MeteoAlarm website are possible; for the most up to date information about alert levels as published by the participating National Meteorological Services please use the MeteoAlarm website.","nearest-station":8.711,"units":"si"},"offset":1}';
-use constant URL => 'https://api.darksky.net/forecast/';
+Readonly my $URL => 'https://api.darksky.net/forecast/';
my %codes = (
'clear-day' => 32,
@@ -141,10 +132,11 @@ my %codes = (
'tornado' => 0,
);
+### begin public function
sub new {
### geliefert wird ein Hash
my ( $class, $argsRef ) = @_;
- my $apioptions = parseApiOptions( $argsRef->{apioptions} );
+ my $apioptions = _parseApiOptions( $argsRef->{apioptions} );
my $self = {
devName => $argsRef->{devName},
@@ -175,23 +167,6 @@ sub new {
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;
@@ -243,7 +218,29 @@ sub getWeather {
return $self->{cached};
}
-sub _RetrieveDataFromDarkSky($) {
+### begin privat function
+sub _parseApiOptions {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 _RetrieveDataFromDarkSky {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
my $self = shift;
# retrieve data from cache
@@ -291,7 +288,7 @@ sub _RetrieveDataFromDarkSky($) {
if ( $self->{extend} ne 'none' );
$paramRef->{url} =
- URL
+ $URL
. $self->{key} . '/'
. $self->{lat} . ','
. $self->{long}
@@ -300,19 +297,26 @@ sub _RetrieveDataFromDarkSky($) {
. $options;
if ( lc( $self->{key} ) eq 'demo' ) {
- _RetrieveDataFinished( $paramRef, undef, DEMODATA );
+ _RetrieveDataFinished( $paramRef, undef, $DEMODATA );
}
- else { main::HttpUtils_NonblockingGet($paramRef); }
+ else { ::HttpUtils_NonblockingGet($paramRef); }
}
+
+ return;
}
-sub _RetrieveDataFinished($$$) {
- my ( $paramRef, $err, $response ) = @_;
- my $self = $paramRef->{self};
+sub _RetrieveDataFinished {
+ return 0 unless ( 'main' eq caller(0) );
+
+ 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();
+ $self->{cached}->{validity} = 'up-to-date';
+ $self->{fetchTime} = time();
_ProcessingRetrieveData( $self, $response );
}
else {
@@ -320,16 +324,21 @@ sub _RetrieveDataFinished($$$) {
_ErrorHandling( $self, $err );
_ProcessingRetrieveData( $self, $response );
}
+
+ return;
}
-sub _ProcessingRetrieveData($$) {
- my ( $self, $response ) = @_;
+sub _ProcessingRetrieveData {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $response = shift;
if ( $self->{cached}->{status} eq 'ok'
and defined($response)
and $response )
{
- if ( $response =~ m/^{.*}$/ ) {
+ if ( $response =~ m/^{.*}$/x ) {
my $data = eval { decode_json($response) };
if ($@) {
@@ -345,417 +354,7 @@ sub _ProcessingRetrieveData($$) {
'Code: ' . $data->{code} . ' Error: ' . $data->{error} );
}
else {
- # print Dumper $data; ## für Debugging
-
- $self->{cached}->{current_date_time} =
- strftimeWrapper( "%a, %e %b %Y %H:%M",
- localtime( $self->{fetchTime} ) );
- $self->{cached}->{timezone} = $data->{timezone};
- $self->{cached}->{license}{text} =
- $data->{flags}->{'meteoalarm-license'};
- $self->{cached}->{current} = {
- 'temperature' => int(
- sprintf( "%.1f", $data->{currently}->{temperature} ) +
- 0.5
- ),
- 'temp_c' => int(
- sprintf( "%.1f", $data->{currently}->{temperature} ) +
- 0.5
- ),
- 'dewPoint' => int(
- sprintf( "%.1f", $data->{currently}->{dewPoint} ) + 0.5
- ),
- 'humidity' => $data->{currently}->{humidity} * 100,
- 'condition' => encode_utf8( $data->{currently}->{summary} ),
- 'pressure' => int(
- sprintf( "%.1f", $data->{currently}->{pressure} ) + 0.5
- ),
- 'wind' => int(
- sprintf( "%.1f",
- ( $data->{currently}->{windSpeed} * 3.6 ) ) + 0.5
- ),
- 'wind_speed' => int(
- sprintf( "%.1f",
- ( $data->{currently}->{windSpeed} * 3.6 ) ) + 0.5
- ),
- 'wind_direction' => $data->{currently}->{windBearing},
- 'windGust' => int(
- sprintf( "%.1f",
- ( $data->{currently}->{windGust} * 3.6 ) ) + 0.5
- ),
- 'cloudCover' => $data->{currently}->{cloudCover} * 100,
- 'uvIndex' => $data->{currently}->{uvIndex},
- 'visibility' => int(
- sprintf( "%.1f", $data->{currently}->{visibility} ) +
- 0.5
- ),
- 'ozone' => $data->{currently}->{ozone},
- 'code' => $codes{ $data->{currently}->{icon} },
- 'iconAPI' => $data->{currently}->{icon},
- 'pubDate' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime( $data->{currently}->{'time'} )
- ),
- 'precipProbability' =>
- $data->{currently}->{precipProbability} * 100,
- 'apparentTemperature' => int(
- sprintf( "%.1f",
- $data->{currently}->{apparentTemperature} ) + 0.5
- ),
- 'precipIntensity' => $data->{currently}->{precipIntensity},
- };
-
- if ( ref( $data->{daily}->{data} ) eq "ARRAY"
- and scalar( @{ $data->{daily}->{data} } ) > 0 )
- {
- ### löschen des alten Datensatzes
- delete $self->{cached}->{forecast};
-
- my $i = 0;
- foreach ( @{ $data->{daily}->{data} } ) {
- push(
- @{ $self->{cached}->{forecast}->{daily} },
- {
- 'pubDate' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]->{'time'}
- )
- ),
- 'day_of_week' => strftime(
- "%a",
- localtime(
- $data->{daily}->{data}->[$i]->{'time'}
- )
- ),
- 'low_c' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{temperatureLow} ) + 0.5
- ),
- 'high_c' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{temperatureHigh} ) + 0.5
- ),
- 'tempLow' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{temperatureLow} ) + 0.5
- ),
- 'tempLowTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{temperatureLowTime}
- )
- ),
- 'tempHigh' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{temperatureHigh} ) + 0.5
- ),
- 'tempHighTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{temperatureHighTime}
- )
- ),
- 'apparentTempLow' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{apparentTemperatureLow} ) + 0.5
- ),
- 'apparentTempLowTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{apparentTemperatureLowTime}
- )
- ),
- 'apparentTempHigh' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{apparentTemperatureHigh} ) + 0.5
- ),
- 'apparentTempHighTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{apparentTemperatureHighTime}
- )
- ),
- 'code' => $codes{
- $data->{daily}->{data}->[$i]->{icon}
- },
- 'iconAPI' =>
- $data->{daily}->{data}->[$i]->{icon},
- 'condition' => encode_utf8(
- $data->{daily}->{data}->[$i]->{summary}
- ),
- 'ozone' =>
- $data->{daily}->{data}->[$i]->{ozone},
- 'uvIndex' =>
- $data->{daily}->{data}->[$i]->{uvIndex},
- 'uvIndexTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{uvIndexTime}
- )
- ),
- 'dewPoint' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]->{dewPoint}
- ) + 0.5
- ),
- 'humidity' =>
- $data->{daily}->{data}->[$i]->{humidity} *
- 100,
- 'cloudCover' =>
- $data->{daily}->{data}->[$i]->{cloudCover} *
- 100,
- 'wind_direction' =>
- $data->{daily}->{data}->[$i]->{windBearing},
- 'wind' => int(
- sprintf(
- "%.1f",
- (
- $data->{daily}->{data}->[$i]
- ->{windSpeed} * 3.6
- )
- ) + 0.5
- ),
- 'wind_speed' => int(
- sprintf(
- "%.1f",
- (
- $data->{daily}->{data}->[$i]
- ->{windSpeed} * 3.6
- )
- ) + 0.5
- ),
- 'windGust' => int(
- sprintf(
- "%.1f",
- (
- $data->{daily}->{data}->[$i]
- ->{windGust} * 3.6
- )
- ) + 0.5
- ),
- 'windGustTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{windGustTime}
- )
- ),
- 'moonPhase' =>
- $data->{daily}->{data}->[$i]->{moonPhase},
- 'sunsetTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{sunsetTime}
- )
- ),
- 'sunriseTime' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{sunriseTime}
- )
- ),
- 'pressure' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]->{pressure}
- ) + 0.5
- ),
- 'visibility' => int(
- sprintf( "%.1f",
- $data->{daily}->{data}->[$i]
- ->{visibility} ) + 0.5
- ),
- }
- );
-
- $self->{cached}->{forecast}
- ->{daily}[$i]{precipIntensityMax} = (
- defined(
- $data->{daily}->{data}->[$i]
- ->{precipIntensityMax}
- )
- ? $data->{daily}->{data}->[$i]->{precipIntensityMax}
- : '-'
- );
- $self->{cached}->{forecast}
- ->{daily}[$i]{precipIntensity} = (
- defined(
- $data->{daily}->{data}->[$i]->{precipIntensity}
- )
- ? $data->{daily}->{data}->[$i]->{precipIntensity}
- : '-'
- );
- $self->{cached}->{forecast}
- ->{daily}[$i]{precipProbability} = (
- defined(
- $data->{daily}->{data}->[$i]
- ->{precipProbability}
- )
- ? $data->{daily}->{data}->[$i]->{precipProbability}
- * 100
- : '-'
- );
- $self->{cached}->{forecast}->{daily}[$i]{precipType} = (
- defined(
- $data->{daily}->{data}->[$i]->{precipType}
- )
- ? $data->{daily}->{data}->[$i]->{precipType}
- : '-'
- );
- $self->{cached}->{forecast}
- ->{daily}[$i]{precipIntensityMaxTime} = (
- defined(
- $data->{daily}->{data}->[$i]
- ->{precipIntensityMaxTime}
- )
- ? strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{daily}->{data}->[$i]
- ->{precipIntensityMaxTime}
- )
- )
- : '-'
- );
-
- $i++;
- }
-
- if ( ref( $data->{hourly}->{data} ) eq "ARRAY"
- and scalar( @{ $data->{hourly}->{data} } ) > 0 )
- {
- ### löschen des alten Datensatzes
- delete $self->{cached}->{forecast}->{hourly};
-
- my $i = 0;
- foreach ( @{ $data->{hourly}->{data} } ) {
- push(
- @{ $self->{cached}->{forecast}->{hourly} },
- {
- 'pubDate' => strftimeWrapper(
- "%a, %e %b %Y %H:%M",
- localtime(
- $data->{hourly}->{data}->[$i]
- ->{'time'}
- )
- ),
- 'day_of_week' => strftime(
- "%a, %H:%M",
- localtime(
- $data->{hourly}->{data}->[$i]
- ->{'time'}
- )
- ),
- 'temperature' => sprintf( "%.1f",
- $data->{hourly}->{data}->[$i]
- ->{temperature} ),
- 'code' => $codes{
- $data->{hourly}->{data}->[$i]->{icon}
- },
- 'iconAPI' =>
- $data->{hourly}->{data}->[$i]->{icon},
- 'condition' => encode_utf8(
- $data->{hourly}->{data}->[$i]->{summary}
- ),
- 'ozone' =>
- $data->{hourly}->{data}->[$i]->{ozone},
- 'uvIndex' =>
- $data->{hourly}->{data}->[$i]->{uvIndex},
- 'dewPoint' => sprintf( "%.1f",
- $data->{hourly}->{data}->[$i]
- ->{dewPoint} ),
- 'humidity' =>
- $data->{hourly}->{data}->[$i]->{humidity}
- * 100,
- 'cloudCover' =>
- $data->{hourly}->{data}->[$i]
- ->{cloudCover} * 100,
- 'wind_direction' =>
- $data->{hourly}->{data}->[$i]
- ->{windBearing},
- 'wind' => int(
- sprintf(
- "%.1f",
- (
- $data->{hourly}->{data}->[$i]
- ->{windSpeed} * 3.6
- )
- ) + 0.5
- ),
- 'wind_speed' => int(
- sprintf(
- "%.1f",
- (
- $data->{hourly}->{data}->[$i]
- ->{windSpeed} * 3.6
- )
- ) + 0.5
- ),
- 'windGust' => int(
- sprintf(
- "%.1f",
- (
- $data->{hourly}->{data}->[$i]
- ->{windGust} * 3.6
- )
- ) + 0.5
- ),
-
- 'pressure' => sprintf( "%.1f",
- $data->{hourly}->{data}->[$i]
- ->{pressure} ),
- 'visibility' => sprintf( "%.1f",
- $data->{hourly}->{data}->[$i]
- ->{visibility} ),
- }
- );
-
- $self->{cached}->{forecast}
- ->{hourly}[$i]{precipIntensity} = (
- defined(
- $data->{hourly}->{data}->[$i]
- ->{precipIntensity}
- )
- ? $data->{hourly}->{data}->[$i]
- ->{precipIntensity}
- : '-'
- );
- $self->{cached}->{forecast}
- ->{hourly}[$i]{precipProbability} = (
- defined(
- $data->{hourly}->{data}->[$i]
- ->{precipProbability}
- )
- ? $data->{hourly}->{data}->[$i]
- ->{precipProbability} * 100
- : '-'
- );
- $self->{cached}->{forecast}
- ->{hourly}[$i]{precipType} = (
- defined(
- $data->{hourly}->{data}->[$i]->{precipType}
- )
- ? $data->{hourly}->{data}->[$i]->{precipType}
- : '-'
- );
-
- $i++;
- }
- }
- }
+ $self = _FillSelfHashWithWeatherResponse( $self, $data );
}
}
else { _ErrorHandling( $self, 'DarkSky Weather ' . $response ); }
@@ -763,55 +362,397 @@ sub _ProcessingRetrieveData($$) {
## Aufruf der callbackFn
_CallWeatherCallbackFn($self);
+
+ return;
}
-sub _CallWeatherCallbackFn($) {
+sub _FillSelfHashWithWeatherResponse {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ # print Dumper $data; ## für Debugging
+ $self->{cached}->{current_date_time} =
+ _strftimeWrapper( "%a, %e %b %Y %H:%M", localtime( $self->{fetchTime} ) );
+ $self->{cached}->{timezone} = $data->{timezone};
+ $self->{cached}->{license}{text} =
+ $data->{flags}->{'meteoalarm-license'};
+
+ ## löschen des alten current Datensatzes
+ delete $self->{cached}->{current};
+
+ ## löschen des alten forecast Datensatzes
+ delete $self->{cached}->{forecast};
+
+ $self = _FillSelfHashWithWeatherResponseForCurrent( $self, $data );
+
+ if ( ref( $data->{daily}->{data} ) eq "ARRAY"
+ and scalar( @{ $data->{daily}->{data} } ) > 0 )
+ {
+ $self =
+ _FillSelfHashWithWeatherResponseForForecastDaily( $self, $data );
+
+ }
+
+ if ( ref( $data->{hourly}->{data} ) eq "ARRAY"
+ and scalar( @{ $data->{hourly}->{data} } ) > 0 )
+ {
+ $self =
+ _FillSelfHashWithWeatherResponseForForecastHourly( $self, $data );
+ }
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForCurrent {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ $self->{cached}->{current} = {
+ 'temperature' =>
+ int( sprintf( "%.0f", $data->{currently}->{temperature} ) ),
+ 'temp_c' => int( sprintf( "%.0f", $data->{currently}->{temperature} ) ),
+ 'dewPoint' => int( sprintf( "%.0f", $data->{currently}->{dewPoint} ) ),
+ 'humidity' => $data->{currently}->{humidity} * 100,
+ 'condition' => encode_utf8( $data->{currently}->{summary} ),
+ 'pressure' =>
+ int( sprintf( "%.1f", $data->{currently}->{pressure} ) + 0.5 ),
+ 'wind' => int(
+ sprintf( "%.1f", ( $data->{currently}->{windSpeed} * 3.6 ) ) + 0.5
+ ),
+ 'wind_speed' => int(
+ sprintf( "%.1f", ( $data->{currently}->{windSpeed} * 3.6 ) ) + 0.5
+ ),
+ 'wind_direction' => $data->{currently}->{windBearing},
+ 'windGust' => int(
+ sprintf( "%.1f", ( $data->{currently}->{windGust} * 3.6 ) ) + 0.5
+ ),
+ 'cloudCover' => $data->{currently}->{cloudCover} * 100,
+ 'uvIndex' => $data->{currently}->{uvIndex},
+ 'visibility' =>
+ int( sprintf( "%.1f", $data->{currently}->{visibility} ) + 0.5 ),
+ 'ozone' => $data->{currently}->{ozone},
+ 'code' => $codes{ $data->{currently}->{icon} },
+ 'iconAPI' => $data->{currently}->{icon},
+ 'pubDate' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{currently}->{'time'} )
+ ),
+ 'precipProbability' => $data->{currently}->{precipProbability} * 100,
+ 'apparentTemperature' =>
+ int( sprintf( "%.0f", $data->{currently}->{apparentTemperature} ) ),
+ 'precipIntensity' => $data->{currently}->{precipIntensity},
+ };
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForForecastDaily {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ my $i = 0;
+ foreach ( @{ $data->{daily}->{data} } ) {
+ push(
+ @{ $self->{cached}->{forecast}->{daily} },
+ {
+ 'pubDate' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{daily}->{data}->[$i]->{'time'} )
+ ),
+ 'day_of_week' => strftime(
+ "%a", localtime( $data->{daily}->{data}->[$i]->{'time'} )
+ ),
+ 'low_c' => int(
+ sprintf( "%.0f",
+ $data->{daily}->{data}->[$i]->{temperatureLow} )
+ ),
+ 'high_c' => int(
+ sprintf( "%.0f",
+ $data->{daily}->{data}->[$i]->{temperatureHigh} )
+ ),
+ 'tempLow' => int(
+ sprintf( "%.0f",
+ $data->{daily}->{data}->[$i]->{temperatureLow} )
+ ),
+ 'tempLowTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime(
+ $data->{daily}->{data}->[$i]->{temperatureLowTime}
+ )
+ ),
+ 'tempHigh' => int(
+ sprintf( "%.0f",
+ $data->{daily}->{data}->[$i]->{temperatureHigh} )
+ ),
+ 'tempHighTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime(
+ $data->{daily}->{data}->[$i]->{temperatureHighTime}
+ )
+ ),
+ 'apparentTempLow' => int(
+ sprintf( "%.0f",
+ $data->{daily}->{data}->[$i]->{apparentTemperatureLow} )
+ ),
+ 'apparentTempLowTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime(
+ $data->{daily}->{data}->[$i]
+ ->{apparentTemperatureLowTime}
+ )
+ ),
+ 'apparentTempHigh' => int(
+ sprintf( "%.0f",
+ $data->{daily}->{data}->[$i]->{apparentTemperatureHigh}
+ )
+ ),
+ 'apparentTempHighTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime(
+ $data->{daily}->{data}->[$i]
+ ->{apparentTemperatureHighTime}
+ )
+ ),
+ 'code' => $codes{ $data->{daily}->{data}->[$i]->{icon} },
+ 'iconAPI' => $data->{daily}->{data}->[$i]->{icon},
+ 'condition' =>
+ encode_utf8( $data->{daily}->{data}->[$i]->{summary} ),
+ 'ozone' => $data->{daily}->{data}->[$i]->{ozone},
+ 'uvIndex' => $data->{daily}->{data}->[$i]->{uvIndex},
+ 'uvIndexTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{daily}->{data}->[$i]->{uvIndexTime} )
+ ),
+ 'dewPoint' => int(
+ sprintf( "%.0f", $data->{daily}->{data}->[$i]->{dewPoint} )
+ ),
+ 'humidity' => $data->{daily}->{data}->[$i]->{humidity} * 100,
+ 'cloudCover' => $data->{daily}->{data}->[$i]->{cloudCover} *
+ 100,
+ 'wind_direction' => $data->{daily}->{data}->[$i]->{windBearing},
+ 'wind' => int(
+ sprintf( "%.1f",
+ ( $data->{daily}->{data}->[$i]->{windSpeed} * 3.6 ) ) +
+ 0.5
+ ),
+ 'wind_speed' => int(
+ sprintf( "%.1f",
+ ( $data->{daily}->{data}->[$i]->{windSpeed} * 3.6 ) ) +
+ 0.5
+ ),
+ 'windGust' => int(
+ sprintf( "%.1f",
+ ( $data->{daily}->{data}->[$i]->{windGust} * 3.6 ) ) +
+ 0.5
+ ),
+ 'windGustTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{daily}->{data}->[$i]->{windGustTime} )
+ ),
+ 'moonPhase' => $data->{daily}->{data}->[$i]->{moonPhase},
+ 'sunsetTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{daily}->{data}->[$i]->{sunsetTime} )
+ ),
+ 'sunriseTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{daily}->{data}->[$i]->{sunriseTime} )
+ ),
+ 'pressure' => int(
+ sprintf( "%.1f", $data->{daily}->{data}->[$i]->{pressure} )
+ + 0.5
+ ),
+ 'visibility' => int(
+ sprintf( "%.1f",
+ $data->{daily}->{data}->[$i]->{visibility} ) + 0.5
+ ),
+ }
+ );
+
+ $self->{cached}->{forecast}->{daily}[$i]{precipIntensityMax} = (
+ defined( $data->{daily}->{data}->[$i]->{precipIntensityMax} )
+ ? $data->{daily}->{data}->[$i]->{precipIntensityMax}
+ : '-'
+ );
+ $self->{cached}->{forecast}->{daily}[$i]{precipIntensity} = (
+ defined( $data->{daily}->{data}->[$i]->{precipIntensity} )
+ ? $data->{daily}->{data}->[$i]->{precipIntensity}
+ : '-'
+ );
+ $self->{cached}->{forecast}->{daily}[$i]{precipProbability} = (
+ defined( $data->{daily}->{data}->[$i]->{precipProbability} )
+ ? $data->{daily}->{data}->[$i]->{precipProbability} * 100
+ : '-'
+ );
+ $self->{cached}->{forecast}->{daily}[$i]{precipType} = (
+ defined( $data->{daily}->{data}->[$i]->{precipType} )
+ ? $data->{daily}->{data}->[$i]->{precipType}
+ : '-'
+ );
+ $self->{cached}->{forecast}->{daily}[$i]{precipIntensityMaxTime} = (
+ defined( $data->{daily}->{data}->[$i]->{precipIntensityMaxTime} )
+ ? _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime(
+ $data->{daily}->{data}->[$i]->{precipIntensityMaxTime}
+ )
+ )
+ : '-'
+ );
+
+ $i++;
+ }
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForForecastHourly {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ my $i = 0;
+
+ foreach ( @{ $data->{hourly}->{data} } ) {
+ push(
+ @{ $self->{cached}->{forecast}->{hourly} },
+ {
+ 'pubDate' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{hourly}->{data}->[$i]->{'time'} )
+ ),
+ 'day_of_week' => strftime(
+ "%a, %H:%M",
+ localtime( $data->{hourly}->{data}->[$i]->{'time'} )
+ ),
+ 'temperature' => sprintf( "%.0f",
+ $data->{hourly}->{data}->[$i]->{temperature} ),
+ 'code' => $codes{ $data->{hourly}->{data}->[$i]->{icon} },
+ 'iconAPI' => $data->{hourly}->{data}->[$i]->{icon},
+ 'condition' =>
+ encode_utf8( $data->{hourly}->{data}->[$i]->{summary} ),
+ 'ozone' => $data->{hourly}->{data}->[$i]->{ozone},
+ 'uvIndex' => $data->{hourly}->{data}->[$i]->{uvIndex},
+ 'dewPoint' =>
+ sprintf( "%.0f", $data->{hourly}->{data}->[$i]->{dewPoint} ),
+ 'humidity' => $data->{hourly}->{data}->[$i]->{humidity} * 100,
+ 'cloudCover' => $data->{hourly}->{data}->[$i]->{cloudCover} *
+ 100,
+ 'wind_direction' =>
+ $data->{hourly}->{data}->[$i]->{windBearing},
+ 'wind' => int(
+ sprintf( "%.1f",
+ ( $data->{hourly}->{data}->[$i]->{windSpeed} * 3.6 ) )
+ + 0.5
+ ),
+ 'wind_speed' => int(
+ sprintf( "%.1f",
+ ( $data->{hourly}->{data}->[$i]->{windSpeed} * 3.6 ) )
+ + 0.5
+ ),
+ 'windGust' => int(
+ sprintf( "%.1f",
+ ( $data->{hourly}->{data}->[$i]->{windGust} * 3.6 ) ) +
+ 0.5
+ ),
+
+ 'pressure' =>
+ sprintf( "%.1f", $data->{hourly}->{data}->[$i]->{pressure} ),
+ 'visibility' => sprintf(
+ "%.1f", $data->{hourly}->{data}->[$i]->{visibility}
+ ),
+ }
+ );
+
+ $self->{cached}->{forecast}->{hourly}[$i]{precipIntensity} = (
+ defined( $data->{hourly}->{data}->[$i]->{precipIntensity} )
+ ? $data->{hourly}->{data}->[$i]->{precipIntensity}
+ : '-'
+ );
+ $self->{cached}->{forecast}->{hourly}[$i]{precipProbability} = (
+ defined( $data->{hourly}->{data}->[$i]->{precipProbability} )
+ ? $data->{hourly}->{data}->[$i]->{precipProbability} * 100
+ : '-'
+ );
+ $self->{cached}->{forecast}->{hourly}[$i]{precipType} = (
+ defined( $data->{hourly}->{data}->[$i]->{precipType} )
+ ? $data->{hourly}->{data}->[$i]->{precipType}
+ : '-'
+ );
+
+ $i++;
+ }
+
+ return $self;
+}
+
+sub _CallWeatherCallbackFn {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
my $self = shift;
# ## Aufruf der callbackFn
- main::Weather_RetrieveCallbackFn( $self->{devName} );
+ return ::Weather_RetrieveCallbackFn( $self->{devName} );
}
-sub _ErrorHandling($$) {
- my ( $self, $err ) = @_;
+sub _ErrorHandling {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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;
+ __strftimeWrapper( "%a, %e %b %Y %H:%M",
+ localtime( $self->{fetchTime} ) );
+ $self->{cached}->{status} = $err;
$self->{cached}->{validity} = 'stale';
+
+ return;
}
-sub _CreateForecastRef($) {
+sub _CreateForecastRef {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
my $self = shift;
my $forecastRef = (
{
lat => $self->{lat},
long => $self->{long},
- apiMaintainer =>
-'Marko Oldenburg (CoolTux)',
- apiVersion => version->parse( DarkSkyAPI->VERSION() )->normal,
+ apiMaintainer => 'Marko Oldenburg ('
+ . $META->{x_fhem_maintainer}[0] . ')',
+ apiVersion => version->parse( __PACKAGE__->VERSION() )->normal,
}
);
return $forecastRef;
}
-sub strftimeWrapper(@) {
- my $string = POSIX::strftime(@_);
+sub _strftimeWrapper {
+ my @data = @_;
+ return 0 unless ( __PACKAGE__ eq caller(0) );
- $string =~ s/\xe4/ä/g;
- $string =~ s/\xc4/Ä/g;
- $string =~ s/\xf6/ö/g;
- $string =~ s/\xd6/Ö/g;
- $string =~ s/\xfc/ü/g;
- $string =~ s/\xdc/Ü/g;
- $string =~ s/\xdf/ß/g;
- $string =~ s/\xdf/ß/g;
- $string =~ s/\xe1/á/g;
- $string =~ s/\xe9/é/g;
- $string =~ s/\xc1/Á/g;
- $string =~ s/\xc9/É/g;
+ 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;
}
diff --git a/lib/FHEM/APIs/Weather/OpenWeatherMapAPI.pm b/lib/FHEM/APIs/Weather/OpenWeatherMapAPI.pm
new file mode 100644
index 0000000..e4f5adc
--- /dev/null
+++ b/lib/FHEM/APIs/Weather/OpenWeatherMapAPI.pm
@@ -0,0 +1,1029 @@
+# $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 = $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,
+);
+
+### begin public function
+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 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};
+}
+
+### begin privat function
+sub _parseApiOptions {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 _RetrieveDataFromOpenWeatherMap {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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'
+ ? ( $self->{apiversion} == 3.0 ? 'onecall' : 'weather' )
+ : 'forecast',
+ 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 {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 {
+ return 0 unless ( 'main' eq caller(0) );
+
+ 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 {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 {
+ $self = _FillSelfHashWithWeatherResponse( $self, $data );
+ }
+ }
+ else { _ErrorHandling( $self, 'OpenWeatherMap ' . $response ); }
+ }
+
+ $self->{endpoint} = 'none'
+ if ( $self->{endpoint} eq 'onecall'
+ or $self->{endpoint} eq 'forecast' );
+
+ _RetrieveDataFromOpenWeatherMap($self)
+ if ( $self->{endpoint} eq 'weather' );
+
+ _CallWeatherCallbackFn($self) if ( $self->{endpoint} eq 'none' );
+
+ return;
+}
+
+sub _FillSelfHashWithWeatherResponse {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ ### 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} ) );
+ $self->{cached}->{country} = $data->{sys}->{country};
+ $self->{cached}->{city} = encode_utf8( $data->{name} );
+ $self->{cached}->{license}{text} = 'none';
+
+ 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 =
+ _FillSelfHashWithWeatherResponseForOnecallCurrent( $self, $data );
+
+ if ( ref( $data->{hourly} ) eq "ARRAY"
+ && scalar( @{ $data->{hourly} } ) > 0 )
+ {
+ $self =
+ _FillSelfHashWithWeatherResponseForOnecallHourly( $self,
+ $data );
+ }
+
+ if ( ref( $data->{daily} ) eq "ARRAY"
+ && scalar( @{ $data->{daily} } ) > 0 )
+ {
+ $self =
+ _FillSelfHashWithWeatherResponseForOnecallDaily( $self,
+ $data );
+ }
+
+ if ( ref( $data->{alerts} ) eq "ARRAY"
+ && scalar( @{ $data->{alerts} } ) > 0 )
+ {
+ $self =
+ _FillSelfHashWithWeatherResponseForOnecallAlerts( $self,
+ $data );
+ }
+ }
+
+ when ('weather') {
+ ## löschen des alten current Datensatzes
+ delete $self->{cached}->{current};
+
+ ## löschen des alten Alerts Datensatzes
+ delete $self->{cached}->{alerts};
+
+ $self =
+ _FillSelfHashWithWeatherResponseForWeatherCurrent( $self, $data );
+ }
+
+ when ('forecast') {
+ ## löschen des alten forecast Datensatzes
+ delete $self->{cached}->{forecast};
+
+ if ( ref( $data->{list} ) eq "ARRAY"
+ and scalar( @{ $data->{list} } ) > 0 )
+ {
+ $self =
+ _FillSelfHashWithWeatherResponseForForecastHourly( $self,
+ $data );
+ }
+ }
+ }
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForOnecallAlerts {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ 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++;
+ }
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForWeatherCurrent {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ $self->{cached}->{current} = {
+ 'temperature' => int( sprintf( "%.0f", $data->{main}->{temp} ) ),
+ 'temp_c' => int( sprintf( "%.0f", $data->{main}->{temp} ) ),
+ 'low_c' => int( sprintf( "%.0f", $data->{main}->{temp_min} ) ),
+ 'high_c' => int( sprintf( "%.0f", $data->{main}->{temp_max} ) ),
+ 'tempLow' => int( sprintf( "%.0f", $data->{main}->{temp_min} ) ),
+ 'tempHigh' => int( sprintf( "%.0f", $data->{main}->{temp_max} ) ),
+ 'tempFeelsLike_c' =>
+ int( sprintf( "%.0f", $data->{main}->{feels_like} ) ),
+ 'humidity' => $data->{main}->{humidity},
+ 'condition' => encode_utf8( $data->{weather}->[0]->{description} ),
+ 'pressure' => int( sprintf( "%.1f", $data->{main}->{pressure} ) + 0.5 ),
+ 'wind' =>
+ int( sprintf( "%.1f", ( $data->{wind}->{speed} * 3.6 ) ) + 0.5 ),
+ 'wind_speed' =>
+ int( sprintf( "%.1f", ( $data->{wind}->{speed} * 3.6 ) ) + 0.5 ),
+ 'wind_gust' =>
+ int( sprintf( "%.1f", ( $data->{wind}->{gust} * 3.6 ) ) + 0.5 ),
+ 'wind_direction' => $data->{wind}->{deg},
+ 'cloudCover' => $data->{clouds}->{all},
+ 'code' => $codes{ $data->{weather}->[0]->{id} },
+ 'iconAPI' => $data->{weather}->[0]->{icon},
+ 'sunsetTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{sys}->{sunset} )
+ ),
+ 'sunriseTime' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{sys}->{sunrise} )
+ ),
+ 'pubDate' =>
+ _strftimeWrapper( "%a, %e %b %Y %H:%M", localtime( $data->{dt} ) ),
+ };
+
+ $self->{cached}->{current}->{'visibility'} =
+ int( sprintf( "%.1f", $data->{visibility} ) + 0.5 )
+ if ( exists $data->{visibility} );
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForForecastHourly {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ my $i = 0;
+ for ( @{ $data->{list} } ) {
+ push(
+ @{ $self->{cached}->{forecast}->{hourly} },
+ {
+ 'pubDate' => _strftimeWrapper(
+ "%a, %e %b %Y %H:%M",
+ localtime( $data->{list}->[$i]->{dt} )
+ ),
+ 'day_of_week' => strftime(
+ "%a, %H:%M", localtime( $data->{list}->[$i]->{dt} )
+ ),
+ 'temperature' =>
+ int( sprintf( "%.0f", $data->{list}->[$i]->{main}->{temp} ) ),
+ 'temp_c' =>
+ int( sprintf( "%.0f", $data->{list}->[$i]->{main}->{temp} ) ),
+ 'low_c' => int(
+ sprintf( "%.0f", $data->{list}->[$i]->{main}->{temp_min} )
+ ),
+ 'high_c' => int(
+ sprintf( "%.0f",
+ $data->{list}->[$i]->{main}->{temp_max} - 273.15 )
+ ),
+ 'tempLow' => int(
+ sprintf( "%.0f",
+ $data->{list}->[$i]->{main}->{temp_min} - 273.15 )
+ ),
+ 'tempHigh' => int(
+ sprintf( "%.0f",
+ $data->{list}->[$i]->{main}->{temp_max} - 273.15 )
+ ),
+ 'humidity' => $data->{list}->[$i]->{main}->{humidity},
+ 'condition' => encode_utf8(
+ $data->{list}->[$i]->{weather}->[0]->{description}
+ ),
+ 'pressure' => int(
+ sprintf( "%.1f", $data->{list}->[$i]->{main}->{pressure} )
+ + 0.5
+ ),
+ 'wind' => int(
+ sprintf( "%.1f",
+ ( $data->{list}->[$i]->{wind}->{speed} * 3.6 ) ) + 0.5
+ ),
+ 'wind_speed' => int(
+ sprintf( "%.1f",
+ ( $data->{list}->[$i]->{wind}->{speed} * 3.6 ) ) + 0.5
+ ),
+ 'wind_gust' => int(
+ sprintf( "%.1f",
+ ( $data->{list}->[$i]->{wind}->{gust} * 3.6 ) ) + 0.5
+ ),
+ 'cloudCover' => $data->{list}->[$i]->{clouds}->{all},
+ 'code' => $codes{ $data->{list}->[$i]->{weather}->[0]->{id} },
+ 'iconAPI' => $data->{list}->[$i]->{weather}->[0]->{icon},
+ 'rain1h' => $data->{list}->[$i]->{rain}->{'1h'},
+ 'rain3h' => $data->{list}->[$i]->{rain}->{'3h'},
+ 'snow1h' => $data->{list}->[$i]->{snow}->{'1h'},
+ 'snow3h' => $data->{list}->[$i]->{snow}->{'3h'},
+ }
+ );
+
+ $i++;
+ }
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForOnecallCurrent {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ $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},
+ };
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForOnecallDaily {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ 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++;
+ }
+
+ return $self;
+}
+
+sub _FillSelfHashWithWeatherResponseForOnecallHourly {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $data = shift;
+
+ 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++;
+ }
+
+ return $self;
+}
+
+sub _CallWeatherCallbackFn {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+
+ # print 'Dumperausgabe: ' . Dumper $self;
+ ### Aufruf der callbackFn
+ return ::Weather_RetrieveCallbackFn( $self->{devName} );
+}
+
+sub _ErrorHandling {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+
+ my $forecastRef = (
+ {
+ lat => $self->{lat},
+ long => $self->{long},
+ apiMaintainer => 'Marko Oldenburg ('
+ . $META->{x_fhem_maintainer}[0] . ')',
+ apiVersion => version->parse( __PACKAGE__->VERSION() )->normal,
+ }
+ );
+
+ return $forecastRef;
+}
+
+sub _strftimeWrapper {
+ my @data = @_;
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 "
+ ],
+ "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__
diff --git a/wundergroundAPI.pm b/lib/FHEM/APIs/Weather/wundergroundAPI.pm
similarity index 90%
rename from wundergroundAPI.pm
rename to lib/FHEM/APIs/Weather/wundergroundAPI.pm
index 2cf4704..3e7de9e 100644
--- a/wundergroundAPI.pm
+++ b/lib/FHEM/APIs/Weather/wundergroundAPI.pm
@@ -1,20 +1,22 @@
# $Id$
-package wundergroundAPI;
+package FHEM::APIs::Weather::wundergroundAPI;
use strict;
use warnings;
use FHEM::Meta;
-use Data::Dumper;
-
-FHEM::Meta::Load(__PACKAGE__);
-use version 0.77; our $VERSION = $main::packages{wundergroundAPI}{META}{version};
-
-package wundergroundAPI::Weather;
-use strict;
-use warnings;
use POSIX;
-use Encode;
use HttpUtils;
+use experimental qw /switch/;
+
+my $META = {};
+my $ret = FHEM::Meta::getMetadata( __FILE__, $META );
+return "$@" if ($@);
+return $ret if ($ret);
+$::packages{wundergroundAPI}{META} = $META;
+
+use version 0.77; our $VERSION = $META->{version};
+
+# use Data::Dumper;
# try to use JSON::MaybeXS wrapper
# for chance of better performance + open code
@@ -71,16 +73,25 @@ eval {
};
};
+my $missingModul = '';
+
# use Data::Dumper; # for Debug only
## API URL
-use constant DEMODATA =>
+eval { use Readonly; 1 }
+ or $missingModul .= 'Readonly '; # apt install libreadonly-perl
+## use critic
+
+# use Data::Dumper; # for Debug only
+## API URL
+Readonly my $DEMODATA =>
'{"daily":{"dayOfWeek":["Freitag","Samstag","Sonntag","Montag","Dienstag","Mittwoch"],"expirationTimeUtc":[1555688120,1555688120,1555688120,1555688120,1555688120,1555688120],"moonPhase":["Vollmond","abnehmender Halbmond","abnehmender Halbmond","abnehmender Halbmond","abnehmender Halbmond","abnehmender Halbmond"],"moonPhaseCode":["F","WNG","WNG","WNG","WNG","WNG"],"moonPhaseDay":[15,16,17,18,19,20],"moonriseTimeLocal":["2019-04-19T20:09:54+0200","2019-04-20T21:30:54+0200","2019-04-21T22:48:07+0200","","2019-04-23T00:00:38+0200","2019-04-24T01:05:27+0200"],"moonriseTimeUtc":[1555697394,1555788654,1555879687,null,1555970438,1556060727],"moonsetTimeLocal":["2019-04-19T06:31:01+0200","2019-04-20T06:54:19+0200","2019-04-21T07:20:19+0200","2019-04-22T07:50:19+0200","2019-04-23T08:25:54+0200","2019-04-24T09:09:28+0200"],"moonsetTimeUtc":[1555648261,1555736059,1555824019,1555912219,1556000754,1556089768],"narrative":["Meistens klar. Tiefsttemperatur 5C.","Meistens klar. Höchsttemperaturen 19 bis 21C und Tiefsttemperaturen 4 bis 6C.","Meistens klar. Höchsttemperaturen 20 bis 22C und Tiefsttemperaturen 6 bis 8C.","Meistens klar. Höchsttemperaturen 20 bis 22C und Tiefsttemperaturen 9 bis 11C.","Teilweise bedeckt und windig. Höchsttemperaturen 21 bis 23C und Tiefsttemperaturen 11 bis 13C.","Teilweise bedeckt. Höchsttemperaturen 22 bis 24C und Tiefsttemperaturen 12 bis 14C."],"qpf":[0.0,0.0,0.0,0.0,0.0,0.0],"qpfSnow":[0.0,0.0,0.0,0.0,0.0,0.0],"sunriseTimeLocal":["2019-04-19T06:00:46+0200","2019-04-20T05:58:38+0200","2019-04-21T05:56:31+0200","2019-04-22T05:54:25+0200","2019-04-23T05:52:20+0200","2019-04-24T05:50:15+0200"],"sunriseTimeUtc":[1555646446,1555732718,1555818991,1555905265,1555991540,1556077815],"sunsetTimeLocal":["2019-04-19T20:11:02+0200","2019-04-20T20:12:46+0200","2019-04-21T20:14:29+0200","2019-04-22T20:16:13+0200","2019-04-23T20:17:56+0200","2019-04-24T20:19:40+0200"],"sunsetTimeUtc":[1555697462,1555783966,1555870469,1555956973,1556043476,1556129980],"temperatureMax":[null,20,21,21,22,23],"temperatureMin":[5,5,7,10,12,13],"validTimeLocal":["2019-04-19T07:00:00+0200","2019-04-20T07:00:00+0200","2019-04-21T07:00:00+0200","2019-04-22T07:00:00+0200","2019-04-23T07:00:00+0200","2019-04-24T07:00:00+0200"],"validTimeUtc":[1555650000,1555736400,1555822800,1555909200,1555995600,1556082000],"daypart":[{"cloudCover":[null,0,25,8,0,0,7,26,55,46,62,44],"dayOrNight":[null,"N","D","N","D","N","D","N","D","N","D","N"],"daypartName":[null,"Heute Abend","Morgen","Morgen Abend","Sonntag","Sonntagnacht","Montag","Montagnacht","Dienstag","Dienstagnacht","Mittwoch","Mittwochnacht"],"iconCode":[null,31,34,33,32,31,34,33,24,29,30,29],"iconCodeExtend":[null,3100,3400,3300,3200,3100,3400,3300,3010,2900,3000,2900],"narrative":[null,"Meistens klar. Tiefsttemperatur 5C. Wind aus NO mit 2 bis 4 m/s.","Meistens klar. Höchsttemperatur 20C. Wind aus NNO mit 2 bis 4 m/s.","Meistens klar. Tiefsttemperatur 5C. Wind aus NO mit 2 bis 4 m/s.","Meistens klar. Höchsttemperatur 21C. Wind aus O und wechselhaft.","Meistens klar. Tiefsttemperatur 7C. Wind aus ONO und wechselhaft.","Meistens klar. Höchsttemperatur 21C. Wind aus O mit 4 bis 9 m/s.","Meistens klar. Tiefsttemperatur 10C. Wind aus O mit 4 bis 9 m/s.","Teilweise bedeckt und windig. Höchsttemperatur 22C. Wind aus OSO mit 9 bis 13 m/s.","Teilweise bedeckt. Tiefsttemperatur 12C. Wind aus SO mit 4 bis 9 m/s.","Teilweise bedeckt. Höchsttemperatur 23C. Wind aus SO mit 4 bis 9 m/s.","Teilweise bedeckt. Tiefsttemperatur 13C. Wind aus SO mit 2 bis 4 m/s."],"precipChance":[null,0,0,0,0,0,20,20,0,0,0,10],"precipType":[null,"rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain"],"qpf":[null,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],"qpfSnow":[null,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],"qualifierCode":[null,null,null,null,null,null,null,null,null,null,null,null],"qualifierPhrase":[null,null,null,null,null,null,null,null,null,null,null,null],"relativeHumidity":[null,50,44,55,41,55,42,48,45,55,53,64],"snowRange":[null,"","","","","","","","","","",""],"temperature":[null,5,20,5,21,7,21,10,22,12,23,13],"temperatureHeatIndex":[null,21,20,18,20,18,20,18,22,20,23,22],"temperatureWindChill":[null,5,5,5,6,6,7,8,8,10,10,13],"thunderCategory":[null,null,null,null,null,null,null,null,null,null,null,null],"thunderIndex":[null,0,0,0,0,0,0,0,0,0,0,0],"uvDescription":[null,"Niedrig","Mittel","Niedrig","Mittel","Niedrig","Mittel","Niedrig","Mittel","Niedrig","Mittel","Niedrig"],"uvIndex":[null,0,4,0,4,0,4,0,4,0,4,0],"windDirection":[null,45,18,41,85,74,95,98,114,124,139,131],"windDirectionCardinal":[null,"NO","NNO","NO","O","ONO","O","O","OSO","SO","SO","SO"],"windPhrase":[null,"Wind aus NO mit 2 bis 4 m/s.","Wind aus NNO mit 2 bis 4 m/s.","Wind aus NO mit 2 bis 4 m/s.","Wind aus O und wechselhaft.","Wind aus ONO und wechselhaft.","Wind aus O mit 4 bis 9 m/s.","Wind aus O mit 4 bis 9 m/s.","Wind aus OSO mit 9 bis 13 m/s.","Wind aus SO mit 4 bis 9 m/s.","Wind aus SO mit 4 bis 9 m/s.","Wind aus SO mit 2 bis 4 m/s."],"windSpeed":[null,4,3,3,2,2,6,6,9,7,6,4],"wxPhraseLong":[null,"Klar","Meist sonnig","Meist klar","Sonnig","Klar","Meist sonnig","Meist klar","Teilweise bedeckt/Wind","Wolkig","Wolkig","Wolkig"],"wxPhraseShort":[null,"","","","","","","","","","",""]}]},"observations":[{"stationID":"IMUNICH344","obsTimeUtc":"2019-04-19T15:24:22Z","obsTimeLocal":"2019-04-19 17:24:22","neighborhood":"Am Hartmannshofer Baechl 34","softwareType":"weewx-3.8.2","country":"DE","solarRadiation":null,"lon":11.49312592,"realtimeFrequency":null,"epoch":1555687462,"lat":48.18364716,"uv":null,"winddir":null,"humidity":27,"qcStatus":1,"metric_si":{"temp":23,"heatIndex":22,"dewpt":3,"windChill":23,"windSpeed":0,"windGust":1,"pressure":1025.84,"precipRate":0.0,"precipTotal":0.0,"elev":502}}]}';
-use constant URL => 'https://api.weather.com/';
+Readonly my $URL => 'https://api.weather.com/';
+### begin public function
sub new {
my ( $class, $argsRef ) = @_;
- my $apioptions = parseApiOptions( $argsRef->{apioptions} );
+ my $apioptions = _parseApiOptions( $argsRef->{apioptions} );
my $self = {
devName => $argsRef->{devName},
@@ -125,23 +136,6 @@ sub new {
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 setFetchTime {
my $self = shift;
@@ -189,7 +183,29 @@ sub getWeather {
return $self->{cached};
}
-sub _RetrieveDataFromWU($) {
+### begin privat function
+sub _parseApiOptions {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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 _RetrieveDataFromWU {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
my $self = shift;
# retrieve data from cache
@@ -243,21 +259,27 @@ sub _RetrieveDataFromWU($) {
$options .= '&apiKey=' . $self->{key};
$paramRef->{url} =
- URL
+ $URL
. 'v3/wx/forecast/daily/'
. $self->{days} . 'day' . '?'
. $options;
if ( lc( $self->{key} ) eq 'demo' ) {
- _RetrieveDataFinished( $paramRef, undef, 'DEMODATA' . DEMODATA );
+ _RetrieveDataFinished( $paramRef, undef, 'DEMODATA' . $DEMODATA );
}
- else { main::HttpUtils_NonblockingGet($paramRef); }
+ else { ::HttpUtils_NonblockingGet($paramRef); }
}
+
+ return;
}
-sub _RetrieveDataFromPWS($$$) {
- my ( $paramRef, $err, $response ) = @_;
- my $self = $paramRef->{self};
+sub _RetrieveDataFromPWS {
+ return 0 unless ( 'main' eq caller(0) );
+
+ my $paramRef = shift;
+ my $err = shift;
+ my $response = shift;
+ my $self = $paramRef->{self};
my $paramRefPWS = {
timeout => 15,
@@ -276,14 +298,18 @@ sub _RetrieveDataFromPWS($$$) {
$options .= '&numericPrecision=decimal';
$options .= '&apiKey=' . $self->{key};
- $paramRefPWS->{url} = URL . 'v2/pws/observations/current?' . $options;
+ $paramRefPWS->{url} = $URL . 'v2/pws/observations/current?' . $options;
- main::HttpUtils_NonblockingGet($paramRefPWS);
+ return ::HttpUtils_NonblockingGet($paramRefPWS);
}
-sub _RetrieveDataFinished($$$) {
- my ( $paramRef, $err, $data ) = @_;
- my $self = $paramRef->{self};
+sub _RetrieveDataFinished {
+ return 0 unless ( 'main' eq caller(0) );
+
+ my $paramRef = shift;
+ my $err = shift;
+ my $data = shift;
+ my $self = $paramRef->{self};
my $response;
# we got PWS and forecast data
@@ -292,10 +318,10 @@ sub _RetrieveDataFinished($$$) {
$err = 'No Data Found for specific PWS' unless ($err);
$response = $paramRef->{forecast};
}
- elsif ( $paramRef->{forecast} =~ m/^\{(.*)\}$/ ) {
+ elsif ( $paramRef->{forecast} =~ m/^\{(.*)\}$/x ) {
my $fc = $1;
- if ( $data =~ m/^\{(.*)\}$/ ) {
+ if ( $data =~ m/^\{(.*)\}$/x ) {
$response = '{' . $fc . ',' . $1 . '}';
}
else {
@@ -310,7 +336,7 @@ sub _RetrieveDataFinished($$$) {
}
# just demo data
- elsif ( $data =~ m/^DEMODATA(\{.*\})$/ ) {
+ elsif ( $data =~ m/^DEMODATA(\{.*\})$/x ) {
$response = $1;
}
@@ -321,7 +347,8 @@ sub _RetrieveDataFinished($$$) {
if ( !$err ) {
$self->{cached}{status} = 'ok';
- $self->{cached}{validity} = 'up-to-date', $self->{fetchTime} = time();
+ $self->{cached}{validity} = 'up-to-date';
+ $self->{fetchTime} = time();
_ProcessingRetrieveData( $self, $response );
}
else {
@@ -329,10 +356,15 @@ sub _RetrieveDataFinished($$$) {
_ErrorHandling( $self, $err );
_ProcessingRetrieveData( $self, $response );
}
+
+ return;
}
-sub _ProcessingRetrieveData($$) {
- my ( $self, $response ) = @_;
+sub _ProcessingRetrieveData {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ my $self = shift;
+ my $response = shift;
if ( $self->{cached}{status} eq 'ok'
and defined($response)
@@ -358,7 +390,7 @@ sub _ProcessingRetrieveData($$) {
# print Dumper $data; ## für Debugging
$self->{cached}{current_date_time} =
- strftimeWrapper( "%a, %e %b %Y %H:%M",
+ _strftimeWrapper( "%a, %e %b %Y %H:%M",
localtime( $self->{fetchTime} ) );
# $self->{cached}{timezone} = $data->{timezone};
@@ -415,7 +447,7 @@ sub _ProcessingRetrieveData($$) {
'solarRadiation' => $data->{solarRadiation},
'uvIndex' => $data->{uv},
'humidity' => $data->{humidity},
- 'pubDate' => strftimeWrapper(
+ 'pubDate' => _strftimeWrapper(
"%a, %e %b %Y %H:%M",
localtime(
main::time_str2num( $data->{obsTimeLocal} )
@@ -444,12 +476,14 @@ sub _ProcessingRetrieveData($$) {
{
### löschen des alten Datensatzes
delete $self->{cached}{forecast};
-
- my $data =
+ my $data;
+ $data =
exists( $data->{daily} ) ? $data->{daily} : $data;
my $days = scalar @{ $data->{temperatureMin} };
- my $i = 0;
+ my $i;
+ $i = 0;
+
while ( $i < $days ) {
$data->{moonriseTimeLocal}[$i] =~
s/^(....-..-..T..:..).*/$1/;
@@ -467,7 +501,7 @@ sub _ProcessingRetrieveData($$) {
'moonPhase' => $data->{moonPhase}[$i],
'moonPhaseCode' => $data->{moonPhaseCode}[$i],
'moonPhaseDay' => $data->{moonPhaseDay}[$i],
- 'moonriseTime' => strftimeWrapper(
+ 'moonriseTime' => _strftimeWrapper(
"%a, %e %b %Y %H:%M",
localtime(
main::time_str2num(
@@ -475,7 +509,7 @@ sub _ProcessingRetrieveData($$) {
)
)
),
- 'moonsetTime' => strftimeWrapper(
+ 'moonsetTime' => _strftimeWrapper(
"%a, %e %b %Y %H:%M",
localtime(
main::time_str2num(
@@ -486,7 +520,7 @@ sub _ProcessingRetrieveData($$) {
'narrative' => $data->{narrative}[$i],
'precipProbability' => $data->{qpf}[$i],
'precipProbabilitySnow' => $data->{qpfSnow}[$i],
- 'sunriseTime' => strftimeWrapper(
+ 'sunriseTime' => _strftimeWrapper(
"%a, %e %b %Y %H:%M",
localtime(
main::time_str2num(
@@ -494,7 +528,7 @@ sub _ProcessingRetrieveData($$) {
)
)
),
- 'sunsetTime' => strftimeWrapper(
+ 'sunsetTime' => _strftimeWrapper(
"%a, %e %b %Y %H:%M",
localtime(
main::time_str2num(
@@ -537,10 +571,10 @@ sub _ProcessingRetrieveData($$) {
and ref( $data->{daypart}[0]{daypartName} ) eq "ARRAY"
and scalar @{ $data->{daypart}[0]{daypartName} } > 0 )
{
- my $data = $data->{daypart}[0];
+ $data = $data->{daypart}[0];
my $dayparts = scalar @{ $data->{daypartName} };
- my $i = 0;
+ $i = 0;
my $day = 0;
while ( $i < $dayparts ) {
@@ -678,26 +712,35 @@ sub _ProcessingRetrieveData($$) {
}
## Aufruf der callbackFn
- _CallWeatherCallbackFn($self);
+ return _CallWeatherCallbackFn($self);
}
-sub _CallWeatherCallbackFn($) {
+sub _CallWeatherCallbackFn {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
my $self = shift;
# ## Aufruf der callbackFn
- main::Weather_RetrieveCallbackFn( $self->{devName} );
+ return ::Weather_RetrieveCallbackFn( $self->{devName} );
}
-sub _ErrorHandling($$) {
- my ( $self, $err ) = @_;
+sub _ErrorHandling {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
+ 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;
+ _strftimeWrapper( "%a, %e %b %Y %H:%M", localtime( $self->{fetchTime} ) );
+ $self->{cached}{status} = $err;
$self->{cached}{validity} = 'stale';
+
+ return;
}
-sub _CreateForecastRef($) {
+sub _CreateForecastRef {
+ return 0 unless ( __PACKAGE__ eq caller(0) );
+
my $self = shift;
my $forecastRef = (
@@ -705,28 +748,31 @@ sub _CreateForecastRef($) {
lat => $self->{lat},
long => $self->{long},
apiMaintainer => 'Julian Pawlowski (loredo)',
- apiVersion => wundergroundAPI->VERSION(),
+ apiVersion => version->parse( __PACKAGE__->VERSION() )->normal,
}
);
return $forecastRef;
}
-sub strftimeWrapper(@) {
- my $string = POSIX::strftime(@_);
+sub _strftimeWrapper {
+ my @data = @_;
+ return 0 unless ( __PACKAGE__ eq caller(0) );
- $string =~ s/\xe4/ä/g;
- $string =~ s/\xc4/Ä/g;
- $string =~ s/\xf6/ö/g;
- $string =~ s/\xd6/Ö/g;
- $string =~ s/\xfc/ü/g;
- $string =~ s/\xdc/Ü/g;
- $string =~ s/\xdf/ß/g;
- $string =~ s/\xdf/ß/g;
- $string =~ s/\xe1/á/g;
- $string =~ s/\xe9/é/g;
- $string =~ s/\xc1/Á/g;
- $string =~ s/\xc9/É/g;
+ 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;
}