2019-01-09 09:37:55 +00:00
###############################################################################
#
# Developed with Kate
#
# (c) 2019 Copyright: Marko Oldenburg (leongaultier at gmail dot com)
# All rights reserved
#
# Special thanks goes to:
#
#
# 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.
#
#
# $Id$
#
###############################################################################
### Beispielaufruf
# https://api.openweathermap.org/data/2.5/weather?lat=[lat]&lon=[long]&APPID=[API] Current
2019-01-09 23:11:19 +00:00
# https://api.openweathermap.org/data/2.5/forecast?lat=[lat]&lon=[long]&APPID=[API] Forecast
2019-01-09 09:37:55 +00:00
# https://openweathermap.org/weather-conditions Icons und Conditions ID's
package OpenWeatherMapAPI::Weather ;
use strict ;
use warnings ;
use POSIX ;
use HttpUtils ;
my $ missingModul = '' ;
eval "use JSON;1"
or $ missingModul . =
"JSON " ; # apt-get install libperl-JSON on Debian and derivatives
eval "use Encode qw(encode_utf8);1" or $ missingModul . = "Encode " ;
use Data::Dumper ; # for Debug only
## API URL
use constant URL = > 'https://api.openweathermap.org/data/2.5/' ;
## URL . 'weather?' for current data
2019-01-09 23:11:19 +00:00
## URL . 'forecast?' for forecast data
2019-01-09 09:37:55 +00:00
my % codes = (
2019-01-09 11:17:10 +00:00
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 ,
2019-01-09 12:14:06 +00:00
600 = > 14 ,
601 = > 16 ,
602 = > 13 ,
611 = > 46 ,
612 = > 46 ,
2019-01-09 12:42:27 +00:00
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 ,
2019-01-09 09:37:55 +00:00
) ;
sub new {
### geliefert wird ein Hash
my ( $ class , $ argsRef ) = @ _ ;
my $ self = {
2019-01-09 11:55:21 +00:00
devName = > $ argsRef - > { devName } ,
2019-01-09 09:37:55 +00:00
key = > ( defined ( $ argsRef - > { apikey } ) ? $ argsRef - > { apikey } : 'none' ) ,
2019-01-10 09:55:03 +00:00
cachemaxage = > ( ( split ( ':' , $ argsRef - > { apioptions } ) ) [ 0 ] eq 'cachemaxage' ? ( split ( ':' , $ argsRef - > { apioptions } ) ) [ 1 ] : 900 ) ,
2019-01-09 09:37:55 +00:00
lang = > $ argsRef - > { language } ,
lat = > ( split ( ',' , $ argsRef - > { location } ) ) [ 0 ] ,
long = > ( split ( ',' , $ argsRef - > { location } ) ) [ 1 ] ,
fetchTime = > 0 ,
endpoint = > 'none' ,
} ;
2019-01-09 11:17:10 +00:00
2019-01-09 23:11:19 +00:00
$ self - > { cached } = _CreateForecastRef ( $ self ) ;
2019-01-09 09:37:55 +00:00
bless $ self , $ class ;
return $ self ;
}
sub setFetchTime {
my $ self = shift ;
$ self - > { fetchTime } = time ( ) ;
return 0 ;
}
sub setRetrieveData {
my $ self = shift ;
_RetrieveDataFromOpenWeatherMap ( $ self ) ;
return 0 ;
}
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
2019-01-09 23:11:19 +00:00
if ( $ self - > { endpoint } eq 'none' ) {
if ( ( time ( ) - $ self - > { fetchTime } ) < $ self - > { cachemaxage } )
{
return _CallWeatherCallbackFn ( $ self ) ;
}
2019-01-09 09:37:55 +00:00
}
my $ paramRef = {
timeout = > 15 ,
self = > $ self ,
2019-01-09 23:11:19 +00:00
endpoint = > ( $ self - > { endpoint } eq 'none' ? 'weather' : 'forecast' ) ,
2019-01-09 09:37:55 +00:00
callback = > \ & _RetrieveDataFinished ,
} ;
$ self - > { endpoint } = $ paramRef - > { endpoint } ;
if ( $ self - > { lat } eq 'error'
or $ self - > { long } eq 'error'
or $ self - > { key } eq 'none'
or $ missingModul )
{
2019-01-09 11:17:10 +00:00
_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' or $ self - > { long } eq 'error' ) ;
2019-01-09 09:37:55 +00:00
_RetrieveDataFinished ( $ paramRef ,
2019-01-09 11:17:10 +00:00
'No given api key. (define myWeather Weather apikey=[KEY])' ,
2019-01-09 09:37:55 +00:00
undef )
if ( $ self - > { key } eq 'none' ) ;
_RetrieveDataFinished ( $ paramRef ,
'Perl modul ' . $ missingModul . ' is missing.' , undef )
if ( $ missingModul ) ;
}
else {
$ paramRef - > { url } =
URL
2019-01-09 11:17:10 +00:00
. $ paramRef - > { endpoint } . '?' . 'lat='
. $ self - > { lat } . '&' . 'lon='
2019-01-09 09:37:55 +00:00
. $ self - > { long } . '&'
. 'APPID='
2019-01-09 11:17:10 +00:00
. $ self - > { key } . '&' . 'lang='
2019-01-09 09:37:55 +00:00
. $ self - > { lang } ;
main:: HttpUtils_NonblockingGet ( $ paramRef ) ;
}
}
sub _RetrieveDataFinished ($$$) {
my ( $ paramRef , $ err , $ response ) = @ _ ;
my $ self = $ paramRef - > { self } ;
if ( ! $ err ) {
2019-01-09 11:17:10 +00:00
$ self - > { cached } - > { status } = 'ok' ;
$ self - > { cached } - > { validity } = 'up-to-date' , $ self - > { fetchTime } = time ( ) ;
2019-01-09 09:37:55 +00:00
_ProcessingRetrieveData ( $ self , $ response ) ;
}
else {
$ self - > { fetchTime } = time ( ) if ( not defined ( $ self - > { fetchTime } ) ) ;
_ErrorHandling ( $ self , $ err ) ;
_ProcessingRetrieveData ( $ self , $ response ) ;
}
}
sub _ProcessingRetrieveData ($$) {
my ( $ self , $ response ) = @ _ ;
2019-01-09 11:17:10 +00:00
if ( $ self - > { cached } - > { status } eq 'ok' and defined ( $ response ) ) {
my $ data = eval { decode_json ( $ response ) } ;
if ( $@ ) {
_ErrorHandling ( $ self ,
'OpenWeatherMap Weather decode JSON err ' . $@ ) ;
2019-01-09 09:37:55 +00:00
}
2019-01-09 23:11:19 +00:00
elsif ( defined ( $ data - > { cod } ) and $ data - > { cod } != 200 and defined ( $ data - > { message } ) ) {
2019-01-09 11:17:10 +00:00
_ErrorHandling ( $ self , $ data - > { cod } . ': ' . $ data - > { message } ) ;
}
else {
2019-01-09 23:11:19 +00:00
###### Ab hier wird die ResponseHash Referenze für die Rückgabe zusammen gestellt
2019-01-09 11:17:10 +00:00
$ self - > { cached } - > { current_date_time } = strftime (
"%a,%e %b %Y %H:%M %p" ,
localtime ( $ self - > { fetchTime } )
2019-01-09 23:11:19 +00:00
) ;
if ( $ self - > { endpoint } eq 'weather' ) {
$ self - > { cached } - > { country } = $ data - > { sys } - > { country } ;
$ self - > { cached } - > { city } = $ data - > { name } ;
$ self - > { cached } - > { current } = {
'temperature' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { main } - > { temp } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'temp_c' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { main } - > { temp } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'low_c' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { main } - > { temp_min } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'high_c' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { main } - > { temp_max } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'tempLow' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { main } - > { temp_min } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'tempHigh' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { main } - > { temp_max } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'humidity' = > $ data - > { main } - > { humidity } ,
2019-01-10 13:08:31 +00:00
'condition' = > encode_utf8 ( $ data - > { weather } - > [ 0 ] - > { description } ) ,
'pressure' = > int ( sprintf ( "%.1f" ,
$ data - > { main } - > { pressure } ) + 0.5
) ,
'wind' = > int ( sprintf ( "%.1f" ,
$ data - > { wind } - > { speed } ) + 0.5 ) ,
'wind_speed' = > int ( sprintf ( "%.1f" ,
$ data - > { wind } - > { speed } ) + 0.5 ) ,
2019-01-09 23:11:19 +00:00
'wind_direction' = > $ data - > { wind } - > { deg } ,
'cloudCover' = > $ data - > { clouds } - > { all } ,
2019-01-10 13:08:31 +00:00
'visibility' = > int ( sprintf ( "%.1f" ,
$ data - > { visibility } ) + 0.5 ) ,
'code' = > $ codes { $ data - > { weather } - > [ 0 ] - > { id } } ,
'iconAPI' = > $ data - > { weather } - > [ 0 ] - > { icon } ,
2019-01-09 23:11:19 +00:00
'sunsetTime' = > strftime (
"%a,%e %b %Y %H:%M %p" ,
localtime ( $ data - > { sys } - > { sunset } )
) ,
'sunriseTime' = > strftime (
"%a,%e %b %Y %H:%M %p" ,
localtime ( $ data - > { sys } - > { sunrise } )
) ,
'pubDate' = >
strftime ( "%a,%e %b %Y %H:%M %p" , localtime ( $ data - > { dt } ) ) ,
} ;
}
2019-01-10 08:30:26 +00:00
if ( $ self - > { endpoint } eq 'forecast' ) {
2019-01-09 23:11:19 +00:00
if ( ref ( $ data - > { list } ) eq "ARRAY"
and scalar ( @ { $ data - > { list } } ) > 0 )
{
2019-01-10 08:30:26 +00:00
## löschen des alten Datensatzes
delete $ self - > { cached } - > { forecast } ;
my $ i = 0 ;
2019-01-09 23:11:19 +00:00
foreach ( @ { $ data - > { list } } ) {
push (
2019-01-10 13:08:31 +00:00
@ { $ self - > { cached } - > { forecast } - > { hourly } } ,
2019-01-09 23:11:19 +00:00
{
'temperature' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { list } - > [ $ i ] - > { main } - > { temp } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'temp_c' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { list } - > [ $ i ] - > { main } - > { temp } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'low_c' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { list } - > [ $ i ] - > { main } - > { temp_min } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'high_c' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { list } - > [ $ i ] - > { main } - > { temp_max } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'tempLow' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { list } - > [ $ i ] - > { main } - > { temp_min } - 273.15 )
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
'tempHigh' = > int (
2019-01-10 13:08:31 +00:00
sprintf ( "%.1f" , ( $ data - > { list } - > [ $ i ] - > { main } - > { temp_max } - 273.15 )
) + 0.5
) ,
'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 }
) + 0.5
) ,
'wind_speed' = > int ( sprintf ( "%.1f" ,
$ data - > { list } - > [ $ i ] - > { wind } - > { speed }
) + 0.5
2019-01-09 23:11:19 +00:00
) ,
2019-01-10 13:08:31 +00:00
'cloudCover' = > $ data - > { list } - > [ $ i ] - > { clouds } - > { all } ,
'code' = > $ codes { $ data - > { list } - > [ $ i ] - > { weather } - > [ 0 ] - > { id } } ,
'iconAPI' = > $ data - > { list } - > [ $ i ] - > { weather } - > [ 0 ] - > { icon } ,
2019-01-09 23:11:19 +00:00
'pubDate' = >
2019-01-10 13:08:31 +00:00
strftime ( "%a,%e %b %Y %H:%M %p" , localtime ( ( $ data - > { list } - > [ $ i ] - > { dt } ) - 3600 ) ) ,
} ,
2019-01-09 23:11:19 +00:00
) ;
$ i + + ;
}
}
}
2019-01-09 11:17:10 +00:00
}
}
2019-01-09 23:11:19 +00:00
$ self - > { endpoint } = 'none' if ( $ self - > { endpoint } eq 'forecast' ) ;
2019-01-09 09:37:55 +00:00
2019-01-09 11:17:10 +00:00
_RetrieveDataFromOpenWeatherMap ( $ self )
if ( $ self - > { endpoint } eq 'weather' ) ;
2019-01-09 23:11:19 +00:00
_CallWeatherCallbackFn ( $ self ) if ( $ self - > { endpoint } eq 'none' ) ;
2019-01-09 09:37:55 +00:00
}
sub _CallWeatherCallbackFn ($) {
my $ self = shift ;
2019-01-09 23:11:19 +00:00
# print 'Dumperausgabe: ' . Dumper $self;
### Aufruf der callbackFn
2019-01-09 11:55:21 +00:00
main:: Weather_RetrieveCallbackFn ( $ self - > { devName } ) ;
2019-01-09 09:37:55 +00:00
}
sub _ErrorHandling ($$) {
2019-01-09 11:17:10 +00:00
my ( $ self , $ err ) = @ _ ;
2019-01-09 09:37:55 +00:00
2019-01-09 11:17:10 +00:00
$ self - > { cached } - > { current_date_time } =
2019-01-09 23:11:19 +00:00
strftime ( "%a,%e %b %Y %H:%M %p" , localtime ( $ self - > { fetchTime } ) ) ,
$ self - > { cached } - > { status } = $ err ;
2019-01-09 09:37:55 +00:00
$ self - > { cached } - > { validity } = 'stale' ;
}
2019-01-09 23:11:19 +00:00
sub _CreateForecastRef ($) {
2019-01-09 09:37:55 +00:00
my $ self = shift ;
2019-01-09 23:11:19 +00:00
my $ forecastRef = (
2019-01-09 11:17:10 +00:00
{
lat = > $ self - > { lat } ,
long = > $ self - > { long } ,
apiMaintainer = >
'Leon Gaultier (<a href=https://forum.fhem.de/index.php?action=profile;u=13684>CoolTux</a>)' ,
}
) ;
2019-01-09 09:37:55 +00:00
2019-01-09 23:11:19 +00:00
return $ forecastRef ;
2019-01-09 09:37:55 +00:00
}
##############################################################################
1 ;