3507e7a061
night/day forecasts for 5 days now implemented using hourly readings hfcX_ (misleading but no other option ...)
781 lines
34 KiB
Perl
781 lines
34 KiB
Perl
# $Id: wundergroundAPI.pm 19250 2019-04-23 19:50:40Z loredo $
|
|
|
|
package 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;
|
|
|
|
# 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;
|
|
};
|
|
if ($@) {
|
|
$@ = undef;
|
|
|
|
# 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;
|
|
};
|
|
|
|
if ($@) {
|
|
$@ = undef;
|
|
|
|
# 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;
|
|
};
|
|
|
|
if ($@) {
|
|
$@ = undef;
|
|
|
|
# In rare cases, JSON::XS may
|
|
# be installed but JSON not ...
|
|
eval {
|
|
require JSON::XS;
|
|
import JSON::XS qw(decode_json encode_json);
|
|
1;
|
|
};
|
|
|
|
if ($@) {
|
|
$@ = undef;
|
|
|
|
# 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;
|
|
};
|
|
|
|
if ($@) {
|
|
$@ = undef;
|
|
|
|
# Fallback to JSON::backportPP in really rare cases
|
|
require JSON::backportPP;
|
|
import JSON::backportPP qw(decode_json encode_json);
|
|
1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
use Data::Dumper; # for Debug only
|
|
## API URL
|
|
use constant 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/';
|
|
|
|
sub new {
|
|
my ( $class, $argsRef ) = @_;
|
|
my $apioptions = parseApiOptions( $argsRef->{apioptions} );
|
|
|
|
my $self = {
|
|
devName => $argsRef->{devName},
|
|
key => (
|
|
( defined( $argsRef->{apikey} ) and $argsRef->{apikey} )
|
|
? $argsRef->{apikey}
|
|
: 'none'
|
|
),
|
|
lang => $argsRef->{language},
|
|
lat => ( split( ',', $argsRef->{location} ) )[0],
|
|
long => ( split( ',', $argsRef->{location} ) )[1],
|
|
fetchTime => 0,
|
|
};
|
|
|
|
$self->{cachemaxage} = (
|
|
defined( $apioptions->{cachemaxage} )
|
|
? $apioptions->{cachemaxage}
|
|
: 900
|
|
);
|
|
$self->{cached} = _CreateForecastRef($self);
|
|
|
|
$self->{days} = (
|
|
defined( $apioptions->{days} )
|
|
? $apioptions->{days}
|
|
: 5
|
|
);
|
|
|
|
$self->{units} = (
|
|
defined( $apioptions->{units} )
|
|
? $apioptions->{units}
|
|
: 's'
|
|
);
|
|
|
|
$self->{stationId} = (
|
|
defined( $apioptions->{stationId} )
|
|
? $apioptions->{stationId}
|
|
: undef
|
|
);
|
|
|
|
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 setFetchTime {
|
|
my $self = shift;
|
|
|
|
$self->{fetchTime} = time();
|
|
return 0;
|
|
}
|
|
|
|
sub setRetrieveData {
|
|
my $self = shift;
|
|
|
|
_RetrieveDataFromWU($self);
|
|
return 0;
|
|
}
|
|
|
|
sub getFetchTime {
|
|
my $self = shift;
|
|
|
|
return $self->{fetchTime};
|
|
}
|
|
|
|
sub getWeather {
|
|
my $self = shift;
|
|
|
|
return $self->{cached};
|
|
}
|
|
|
|
sub _RetrieveDataFromWU($) {
|
|
my $self = shift;
|
|
|
|
# retrieve data from cache
|
|
if ( ( time() - $self->{fetchTime} ) < $self->{cachemaxage} ) {
|
|
return _CallWeatherCallbackFn($self);
|
|
}
|
|
|
|
my $paramRef = {
|
|
timeout => 15,
|
|
self => $self,
|
|
callback => (
|
|
$self->{stationId}
|
|
? \&_RetrieveDataFromPWS
|
|
: \&_RetrieveDataFinished
|
|
),
|
|
};
|
|
|
|
if ( $self->{lat} eq 'error'
|
|
or $self->{long} eq 'error'
|
|
or $self->{key} eq 'none' )
|
|
{
|
|
_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' );
|
|
|
|
_RetrieveDataFinished( $paramRef,
|
|
'No given api key. (define myWeather Weather apikey=[KEY])',
|
|
undef )
|
|
if ( $self->{key} eq 'none' );
|
|
}
|
|
else {
|
|
my $options = 'geocode=' . $self->{lat} . ',' . $self->{long};
|
|
$options .= '&format=json';
|
|
$options .= '&units=' . $self->{units};
|
|
$options .= '&language='
|
|
. (
|
|
$self->{lang} eq 'en'
|
|
? 'en-US'
|
|
: $self->{lang} . '-' . uc( $self->{lang} )
|
|
);
|
|
$options .= '&apiKey=' . $self->{key};
|
|
|
|
$paramRef->{url} =
|
|
URL
|
|
. 'v3/wx/forecast/daily/'
|
|
. $self->{days} . 'day' . '?'
|
|
. $options;
|
|
|
|
if ( lc( $self->{key} ) eq 'demo' ) {
|
|
_RetrieveDataFinished( $paramRef, undef, 'DEMODATA' . DEMODATA );
|
|
}
|
|
else { main::HttpUtils_NonblockingGet($paramRef); }
|
|
}
|
|
}
|
|
|
|
sub _RetrieveDataFromPWS($$$) {
|
|
my ( $paramRef, $err, $response ) = @_;
|
|
my $self = $paramRef->{self};
|
|
|
|
my $paramRefPWS = {
|
|
timeout => 15,
|
|
self => $self,
|
|
callback => \&_RetrieveDataFinished,
|
|
forecast => (
|
|
$response =~ /^\{.*\}$/
|
|
? '{"daily":' . $response . '}'
|
|
: $response
|
|
),
|
|
};
|
|
|
|
my $options = 'stationId=' . $self->{stationId};
|
|
$options .= '&format=json';
|
|
$options .= '&units=' . $self->{units};
|
|
$options .= '&apiKey=' . $self->{key};
|
|
|
|
$paramRefPWS->{url} = URL . 'v2/pws/observations/current?' . $options;
|
|
|
|
main::HttpUtils_NonblockingGet($paramRefPWS);
|
|
}
|
|
|
|
sub _RetrieveDataFinished($$$) {
|
|
my ( $paramRef, $err, $data ) = @_;
|
|
my $self = $paramRef->{self};
|
|
my $response;
|
|
|
|
# we got PWS and forecast data
|
|
if ( defined( $paramRef->{forecast} ) ) {
|
|
if ( !$data || $data eq '' ) {
|
|
$err = 'No Data Found for specific PWS' unless ($err);
|
|
$response = $paramRef->{forecast};
|
|
}
|
|
elsif ( $paramRef->{forecast} =~ m/^\{(.*)\}$/ ) {
|
|
my $fc = $1;
|
|
|
|
if ( $data =~ m/^\{(.*)\}$/ ) {
|
|
$response = '{' . $fc . ',' . $1 . '}';
|
|
}
|
|
else {
|
|
$err = 'PWS data is not in JSON format' unless ($err);
|
|
$response = $data;
|
|
}
|
|
}
|
|
else {
|
|
$err = 'Forecast data is not in JSON format' unless ($err);
|
|
$response = $data;
|
|
}
|
|
}
|
|
|
|
# just demo data
|
|
elsif ( $data =~ m/^DEMODATA(\{.*\})$/ ) {
|
|
$response = $1;
|
|
}
|
|
|
|
# just forecast data
|
|
else {
|
|
$response = $data;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
sub _ProcessingRetrieveData($$) {
|
|
my ( $self, $response ) = @_;
|
|
|
|
if ( $self->{cached}{status} eq 'ok'
|
|
and defined($response)
|
|
and $response )
|
|
{
|
|
if ( $response =~ m/^\{.*\}$/ ) {
|
|
my $data = eval { decode_json( encode_utf8($response) ) };
|
|
if ($@) {
|
|
_ErrorHandling( $self,
|
|
'Weather Underground decode JSON err ' . $@ );
|
|
}
|
|
|
|
# elsif ( defined( $data->{code} )
|
|
# and $data->{code}
|
|
# and defined( $data->{error} )
|
|
# and $data->{error} )
|
|
# {
|
|
# _ErrorHandling( $self,
|
|
# 'Code: ' . $data->{code} . ' Error: ' . $data->{error} );
|
|
# }
|
|
else {
|
|
# print Dumper $response; ## für Debugging
|
|
# 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 provided by Weather Underground; '
|
|
. 'part of The Weather Company, an IBM Business.';
|
|
|
|
if ( ref( $data->{observations} ) eq "ARRAY"
|
|
and scalar @{ $data->{observations} } > 0
|
|
and ref( $data->{observations}[0] ) eq "HASH"
|
|
and scalar keys %{ $data->{observations}[0] } > 0 )
|
|
{
|
|
my $data = $data->{observations}[0];
|
|
|
|
my $unit = (
|
|
defined( $data->{metric_si} ) ? 'metric_si'
|
|
: (
|
|
defined( $data->{metric} ) ? 'metric'
|
|
: (
|
|
defined( $data->{imperial} ) ? 'imperial'
|
|
: (
|
|
defined( $data->{uk_hybrid} ) ? 'uk_hybrid'
|
|
: '-'
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
$self->{cached}{current} = {
|
|
'dewPoint' =>
|
|
int( sprintf( "%.1f", $data->{$unit}{dewpt} ) + 0.5 ),
|
|
'heatIndex' => $data->{$unit}{heatIndex},
|
|
'precipRate' => $data->{$unit}{precipRate},
|
|
'precipTotal' => $data->{$unit}{precipTotal},
|
|
'pressure' => int(
|
|
sprintf( "%.1f", $data->{$unit}{pressure} ) + 0.5
|
|
),
|
|
'temperature' =>
|
|
int( sprintf( "%.1f", $data->{$unit}{temp} ) + 0.5 ),
|
|
'temp_c' =>
|
|
int( sprintf( "%.1f", $data->{$unit}{temp} ) + 0.5 ),
|
|
'wind_chill' => int(
|
|
sprintf( "%.1f", ( $data->{$unit}{windChill} ) ) +
|
|
0.5
|
|
),
|
|
'windGust' => int(
|
|
sprintf( "%.1f", ( $data->{$unit}{windGust} ) ) +
|
|
0.5
|
|
),
|
|
'wind' => int(
|
|
sprintf( "%.1f", ( $data->{$unit}{windSpeed} ) ) +
|
|
0.5
|
|
),
|
|
'wind_speed' => int(
|
|
sprintf( "%.1f", ( $data->{$unit}{windSpeed} ) ) +
|
|
0.5
|
|
),
|
|
'wind_direction' => $data->{winddir},
|
|
'solarRadiation' => $data->{solarRadiation},
|
|
'uvIndex' => $data->{uv},
|
|
'humidity' => $data->{humidity},
|
|
'pubDate' => strftimeWrapper(
|
|
"%a, %e %b %Y %H:%M",
|
|
localtime(
|
|
main::time_str2num( $data->{obsTimeLocal} )
|
|
)
|
|
),
|
|
'pwsLat' => $data->{lat},
|
|
'pwsLon' => $data->{lon},
|
|
'pwsElevation' => $data->{$unit}{elev},
|
|
'pwsQcStatus' => $data->{qcStatus},
|
|
'pwsRealtimeFrequency' => $data->{realtimeFrequency},
|
|
'pwsCountry' => $data->{country},
|
|
'pwsStationID' => $data->{stationID},
|
|
'pwsNeighborhood' => $data->{neighborhood},
|
|
'pwsSoftwareType' => $data->{softwareType},
|
|
};
|
|
}
|
|
|
|
if (
|
|
(
|
|
ref( $data->{temperatureMin} ) eq "ARRAY"
|
|
and scalar @{ $data->{temperatureMin} } > 0
|
|
)
|
|
|| ( ref( $data->{daily}{temperatureMin} ) eq "ARRAY"
|
|
and scalar @{ $data->{daily}{temperatureMin} } > 0 )
|
|
)
|
|
{
|
|
### löschen des alten Datensatzes
|
|
delete $self->{cached}{forecast};
|
|
|
|
my $data =
|
|
exists( $data->{daily} ) ? $data->{daily} : $data;
|
|
my $days = scalar @{ $data->{temperatureMin} };
|
|
|
|
my $i = 0;
|
|
while ( $i < $days ) {
|
|
$data->{moonriseTimeLocal}[$i] =~
|
|
s/^(....-..-..T..:..).*/$1/;
|
|
$data->{moonsetTimeLocal}[$i] =~
|
|
s/^(....-..-..T..:..).*/$1/;
|
|
$data->{sunriseTimeLocal}[$i] =~
|
|
s/^(....-..-..T..:..).*/$1/;
|
|
$data->{sunsetTimeLocal}[$i] =~
|
|
s/^(....-..-..T..:..).*/$1/;
|
|
|
|
push(
|
|
@{ $self->{cached}{forecast}{daily} },
|
|
{
|
|
'day_of_week' => $data->{dayOfWeek}[$i],
|
|
'moonPhase' => $data->{moonPhase}[$i],
|
|
'moonPhaseCode' => $data->{moonPhaseCode}[$i],
|
|
'moonPhaseDay' => $data->{moonPhaseDay}[$i],
|
|
'moonriseTime' => strftimeWrapper(
|
|
"%a, %e %b %Y %H:%M",
|
|
localtime(
|
|
main::time_str2num(
|
|
$data->{moonriseTimeLocal}[$i]
|
|
)
|
|
)
|
|
),
|
|
'moonsetTime' => strftimeWrapper(
|
|
"%a, %e %b %Y %H:%M",
|
|
localtime(
|
|
main::time_str2num(
|
|
$data->{moonsetTimeLocal}[$i]
|
|
)
|
|
)
|
|
),
|
|
'narrative' => $data->{narrative}[$i],
|
|
'precipProbability' => $data->{qpf}[$i],
|
|
'precipProbabilitySnow' => $data->{qpfSnow}[$i],
|
|
'sunriseTime' => strftimeWrapper(
|
|
"%a, %e %b %Y %H:%M",
|
|
localtime(
|
|
main::time_str2num(
|
|
$data->{sunriseTimeLocal}[$i]
|
|
)
|
|
)
|
|
),
|
|
'sunsetTime' => strftimeWrapper(
|
|
"%a, %e %b %Y %H:%M",
|
|
localtime(
|
|
main::time_str2num(
|
|
$data->{sunsetTimeLocal}[$i]
|
|
)
|
|
)
|
|
),
|
|
'low_c' => int(
|
|
sprintf( "%.1f",
|
|
$data->{temperatureMin}[$i] ) + 0.5
|
|
),
|
|
'high_c' => int(
|
|
sprintf(
|
|
"%.1f",
|
|
(
|
|
$data->{temperatureMax}[$i]
|
|
? $data->{temperatureMax}[$i]
|
|
: 0
|
|
)
|
|
) + 0.5
|
|
),
|
|
'tempLow' => int(
|
|
sprintf( "%.1f",
|
|
$data->{temperatureMin}[$i] ) + 0.5
|
|
),
|
|
'tempHigh' => int(
|
|
sprintf(
|
|
"%.1f",
|
|
(
|
|
$data->{temperatureMax}[$i]
|
|
? $data->{temperatureMax}[$i]
|
|
: 0
|
|
)
|
|
) + 0.5
|
|
),
|
|
}
|
|
);
|
|
|
|
$i++;
|
|
}
|
|
|
|
if ( ref( $data->{daypart} ) eq "ARRAY"
|
|
and scalar @{ $data->{daypart} } > 0
|
|
and ref( $data->{daypart}[0] ) eq "HASH"
|
|
and scalar keys %{ $data->{daypart}[0] } > 0
|
|
and ref( $data->{daypart}[0]{daypartName} ) eq "ARRAY"
|
|
and scalar @{ $data->{daypart}[0]{daypartName} } > 0 )
|
|
{
|
|
my $data = $data->{daypart}[0];
|
|
my $dayparts = scalar @{ $data->{daypartName} };
|
|
|
|
my $i = 0;
|
|
my $day = 0;
|
|
while ( $i < $dayparts ) {
|
|
|
|
my $part = (
|
|
$data->{dayOrNight}[$i]
|
|
&& $data->{dayOrNight}[$i] eq 'N'
|
|
? 'night'
|
|
: 'day'
|
|
);
|
|
|
|
# copy some day values to regular day forecast
|
|
if ( $part eq 'day' || $part eq 'night' ) {
|
|
my $self =
|
|
$self->{cached}{forecast}{daily}[$day];
|
|
|
|
$self->{cloudCover} = $data->{cloudCover}[$i]
|
|
unless ( !$data->{cloudCover}[$i]
|
|
|| defined( $self->{cloudCover} ) );
|
|
$self->{code} = $data->{iconCode}[$i]
|
|
unless ( !$data->{iconCode}[$i]
|
|
|| defined( $self->{code} ) );
|
|
$self->{iconAPI} = $data->{iconCode}[$i]
|
|
unless ( !$data->{iconCode}[$i]
|
|
|| defined( $self->{iconAPI} ) );
|
|
$self->{codeExtend} =
|
|
$data->{iconCodeExtend}[$i]
|
|
unless ( !$data->{iconCodeExtend}[$i]
|
|
|| defined( $self->{codeExtend} ) );
|
|
$self->{condition} = $data->{wxPhraseShort}[$i]
|
|
unless ( !$data->{wxPhraseShort}[$i]
|
|
|| defined( $self->{condition} ) );
|
|
$self->{condition} = $data->{wxPhraseLong}[$i]
|
|
unless ( !$data->{wxPhraseLong}[$i]
|
|
|| defined( $self->{condition} ) );
|
|
$self->{precipProbability} = $data->{qpf}[$i]
|
|
unless ( !$data->{qpf}[$i]
|
|
|| defined( $self->{precipProbability} ) );
|
|
$self->{precipProbability} = $data->{qpf}[$i]
|
|
unless ( !$data->{qpf}[$i]
|
|
|| defined( $self->{precipProbability} ) );
|
|
$self->{uvIndex} = $data->{uvIndex}[$i]
|
|
unless ( !$data->{uvIndex}[$i]
|
|
|| defined( $self->{uvIndex} ) );
|
|
}
|
|
|
|
# if this is today, copy some values to current
|
|
if ( $i eq '0' || $i eq '1' ) {
|
|
my $self = $self->{cached}{current};
|
|
|
|
$self->{cloudCover} = $data->{cloudCover}[$i]
|
|
unless ( !$data->{cloudCover}[$i]
|
|
|| defined( $self->{cloudCover} ) );
|
|
$self->{code} = $data->{iconCode}[$i]
|
|
unless ( !$data->{iconCode}[$i]
|
|
|| defined( $self->{code} ) );
|
|
$self->{iconAPI} = $data->{iconCode}[$i]
|
|
unless ( !$data->{iconCode}[$i]
|
|
|| defined( $self->{iconAPI} ) );
|
|
$self->{codeExtend} =
|
|
$data->{iconCodeExtend}[$i]
|
|
unless ( !$data->{iconCodeExtend}[$i]
|
|
|| defined( $self->{codeExtend} ) );
|
|
$self->{condition} = $data->{wxPhraseShort}[$i]
|
|
unless ( !$data->{wxPhraseShort}[$i]
|
|
|| defined( $self->{condition} ) );
|
|
$self->{condition} = $data->{wxPhraseLong}[$i]
|
|
unless ( !$data->{wxPhraseLong}[$i]
|
|
|| defined( $self->{condition} ) );
|
|
$self->{precipProbability} = $data->{qpf}[$i]
|
|
unless ( !$data->{qpf}[$i]
|
|
|| defined( $self->{precipProbability} ) );
|
|
$self->{precipProbability} = $data->{qpf}[$i]
|
|
unless ( !$data->{qpf}[$i]
|
|
|| defined( $self->{precipProbability} ) );
|
|
$self->{uvIndex} = $data->{uvIndex}[$i]
|
|
unless ( !$data->{uvIndex}[$i]
|
|
|| defined( $self->{uvIndex} ) );
|
|
}
|
|
|
|
push(
|
|
@{ $self->{cached}{forecast}{hourly} },
|
|
{
|
|
'cloudCover' => $data->{cloudCover}[$i],
|
|
'dayOrNight' => $data->{dayOrNight}[$i],
|
|
'day_of_week' => $data->{daypartName}[$i],
|
|
'code' => $data->{iconCode}[$i],
|
|
'iconAPI' => $data->{iconCode}[$i],
|
|
'codeExtend' => $data->{iconCodeExtend}[$i],
|
|
'narrative' => $data->{narrative}[$i],
|
|
'precipChance' => $data->{precipChance}[$i],
|
|
'precipType' => $data->{precipType}[$i],
|
|
'precipProbability' => $data->{qpf}[$i],
|
|
'precipProbabilitySnow' =>
|
|
$data->{qpfSnow}[$i],
|
|
'qualifierPhrase' =>
|
|
$data->{qualifierPhrase}[$i],
|
|
'humidity' => $data->{relativeHumidity}[$i],
|
|
'snowRange' => $data->{snowRange}[$i],
|
|
'temp_c' => $data->{temperature}[$i],
|
|
'temperature' => $data->{temperature}[$i],
|
|
'heatIndex' =>
|
|
$data->{temperatureHeatIndex}[$i],
|
|
'wind_chill' =>
|
|
$data->{temperatureWindChill}[$i],
|
|
'thunderCategory' =>
|
|
$data->{thunderCategory}[$i],
|
|
'thunderIndex' => $data->{thunderIndex}[$i],
|
|
'uvDescription' =>
|
|
$data->{uvDescription}[$i],
|
|
'uvIndex' => $data->{uvIndex}[$i],
|
|
'wind_direction' =>
|
|
$data->{windDirection}[$i],
|
|
'wind_directionCardinal' =>
|
|
$data->{windDirectionCardinal}[$i],
|
|
'windPhrase' => $data->{windPhrase}[$i],
|
|
'wind' => $data->{windSpeed}[$i],
|
|
'wind_speed' => $data->{windSpeed}[$i],
|
|
'condition' => $data->{wxPhraseLong}[$i],
|
|
'wxPhraseShort' =>
|
|
$data->{wxPhraseShort}[$i],
|
|
}
|
|
) if ( defined( $data->{temperature}[$i] ) );
|
|
|
|
$i++;
|
|
$day++ if ( $part eq 'night' );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
_ErrorHandling( $self, 'Weather Underground ' . $response );
|
|
}
|
|
}
|
|
|
|
## Aufruf der callbackFn
|
|
_CallWeatherCallbackFn($self);
|
|
}
|
|
|
|
sub _CallWeatherCallbackFn($) {
|
|
my $self = shift;
|
|
|
|
# ## Aufruf der callbackFn
|
|
main::Weather_RetrieveCallbackFn( $self->{devName} );
|
|
}
|
|
|
|
sub _ErrorHandling($$) {
|
|
my ( $self, $err ) = @_;
|
|
|
|
$self->{cached}{current_date_time} =
|
|
strftimeWrapper( "%a, %e %b %Y %H:%M", localtime( $self->{fetchTime} ) ),
|
|
$self->{cached}{status} = $err;
|
|
$self->{cached}{validity} = 'stale';
|
|
}
|
|
|
|
sub _CreateForecastRef($) {
|
|
my $self = shift;
|
|
|
|
my $forecastRef = (
|
|
{
|
|
lat => $self->{lat},
|
|
long => $self->{long},
|
|
apiMaintainer => 'Julian Pawlowski (loredo)',
|
|
apiVersion => wundergroundAPI->VERSION(),
|
|
}
|
|
);
|
|
|
|
return $forecastRef;
|
|
}
|
|
|
|
sub strftimeWrapper(@) {
|
|
my $string = POSIX::strftime(@_);
|
|
|
|
$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;
|
|
|
|
return $string;
|
|
}
|
|
|
|
##############################################################################
|
|
|
|
1;
|
|
|
|
=pod
|
|
|
|
=encoding utf8
|
|
|
|
=for :application/json;q=META.json wundergroundAPI.pm
|
|
{
|
|
"abstract": "Weather API for Weather Underground",
|
|
"x_lang": {
|
|
"de": {
|
|
"abstract": "Wetter API für Weather Underground"
|
|
}
|
|
},
|
|
"version": "v1.0.0",
|
|
"author": [
|
|
"Julian Pawlowski <julian.pawlowski@gmail.com>"
|
|
],
|
|
"x_fhem_maintainer": [
|
|
"loredo"
|
|
],
|
|
"x_fhem_maintainer_github": [
|
|
"jpawlowski"
|
|
],
|
|
"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
|