# $Id$ 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} : 'm' ); $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 setLocation { my ($self,$lat,$long) = @_; $self->{lat} = $lat; $self->{long} = $long; 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} and $self->{cached}->{lat} == $self->{lat} and $self->{cached}->{long} == $self->{long} ) { 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, 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.1", "author": [ "Julian Pawlowski " ], "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