diff --git a/fhem/CHANGED b/fhem/CHANGED index 60f57cf80..6ea4194ce 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 59_Weather: included caching in YahooWeatherAPI - change: 49_SSCam: behavior of "set ... on" changed, Attr "recextend" added please have a look at commandref and Wiki - bugfix: 49_SSCam: setstate-warning if FHEM is restarted and SVS not diff --git a/fhem/FHEM/59_Weather.pm b/fhem/FHEM/59_Weather.pm index c91585328..10ffa3166 100755 --- a/fhem/FHEM/59_Weather.pm +++ b/fhem/FHEM/59_Weather.pm @@ -100,21 +100,6 @@ sub degrees_to_direction($@) { return $directions_txt_i18n[$mod]; } -sub F_to_C($) { - my ($F)= @_; - return(int(($F-32)*5/9+0.5)); -} - -sub inHg_to_hPa($) { - my ($inHg)= @_; - return int($inHg/33.86390+0.5); -} - -sub miles_to_km($) { - my ($miles)= @_; - return int(1.609347219*$miles+0.5); -} - ################################### sub Weather_RetrieveData($$) { my ($name, $blocking) = @_; @@ -132,9 +117,32 @@ sub Weather_RetrieveData($$) { hash => $hash, ); - YahooWeatherAPI_RetrieveData(\%args); + my $maxage= $hash->{fhem}{allowCache} ? 600 : 0; # use cached data if allowed + $hash->{fhem}{allowCache}= 1; + YahooWeatherAPI_RetrieveDataWithCache($maxage, \%args); } + +sub Weather_ReturnWithError($$$$$) { + my ($hash, $doTrigger, $err, $pubDate, $pubDateComment)= @_; + my $name= $hash->{NAME}; + + $hash->{fhem}{allowCache}= 0; # do not use cache on next try + + Log3 $hash, 3, "$name: $err"; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "lastError", $err); + readingsBulkUpdate($hash, "pubDateComment", $pubDateComment) if(defined($pubDateComment)); + readingsBulkUpdate($hash, "pubDateRemote", $pubDate) if(defined($pubDate)); + readingsBulkUpdate($hash, "validity", "stale"); + readingsEndUpdate($hash, $doTrigger); + + my $next= 60; # $next= $hash->{INTERVAL}; + Weather_RearmTimer($hash, gettimeofday()+$next); + + return; +} + sub Weather_RetrieveDataFinished($$$) { my ($argsRef, $err, $response)= @_; @@ -143,76 +151,23 @@ sub Weather_RetrieveDataFinished($$$) { my $name= $hash->{NAME}; my $doTrigger= $argsRef->{blocking} ? 0 : 1; - - # the next 4 stanzas need to be rewritten, they are too repetitive for my taste - # check for error from retrieving data - if($err) { - Log3 $hash, 3, "$name: $err"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "lastError", $err); - readingsBulkUpdate($hash, "validity", "stale"); - readingsEndUpdate($hash, $doTrigger); - Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL}); - return; - } + return Weather_ReturnWithError($hash, $doTrigger, $err, undef, undef) if($err); # decode JSON data from Weather Channel my $data; ($err, $data)= YahooWeatherAPI_JSONReturnChannelData($response); - if($err) { - Log3 $hash, 3, "$name: $err"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "lastError", $err); - readingsBulkUpdate($hash, "validity", "stale"); - readingsEndUpdate($hash, $doTrigger); - Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL}); - return; - } + return Weather_ReturnWithError($hash, $doTrigger, $err, undef, undef) if($err); # check if up-to-date my ($pubDateComment, $pubDate, $pubDateTs)= YahooWeatherAPI_pubDate($data); - if(!defined($pubDateTs)) { - Log3 $hash, 3, "$name: $pubDateComment"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "lastError", $pubDateComment); - readingsBulkUpdate($hash, "pubDateComment", $pubDateComment); - readingsBulkUpdate($hash, "pubDateRemote", $pubDate); - readingsBulkUpdate($hash, "validity", "stale"); - readingsEndUpdate($hash, $doTrigger); - Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL}); - return; - } else { - my $ts= defined($hash->{READINGS}{pubDateTs}) ? $hash->{READINGS}{pubDateTs}{VAL} : 0; - if($ts> $pubDateTs) { - $err= "stale data received"; - Log3 $hash, 3, "$name: $err"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "lastError", $err); - readingsBulkUpdate($hash, "pubDateComment", $pubDateComment); - readingsBulkUpdate($hash, "pubDateRemote", $pubDate); - readingsBulkUpdate($hash, "validity", "stale"); - readingsEndUpdate($hash, $doTrigger); - Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL}); - return; - } - } - - # units - my $units= YahooWeatherAPI_units($data); # units hash reference - if($units->{temperature} ne "C") { - $err= "wrong units received"; - Log3 $hash, 3, "$name: $err"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "lastError", $err); - readingsBulkUpdate($hash, "pubDateComment", $pubDateComment); - readingsBulkUpdate($hash, "pubDateRemote", $pubDate); - readingsBulkUpdate($hash, "validity", "stale"); - readingsEndUpdate($hash, $doTrigger); - Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL}); - return; - } - + return Weather_ReturnWithError($hash, $doTrigger, $pubDateComment, $pubDate, $pubDateComment) + unless(defined($pubDateTs)); + my $ts= defined($hash->{READINGS}{pubDateTs}) ? $hash->{READINGS}{pubDateTs}{VAL} : 0; + return Weather_ReturnWithError($hash, $doTrigger, "stale data received", $pubDate, $pubDateComment) + if($ts> $pubDateTs); + + # # from here on we assume that $data is complete and correct # @@ -249,21 +204,27 @@ sub Weather_RetrieveDataFinished($$$) { readingsBeginUpdate($hash); + # delete some unused readings + delete($hash->{READINGS}{temp_f}) if(defined($hash->{READINGS}{temp_f})); + delete($hash->{READINGS}{unit_distance}) if(defined($hash->{READINGS}{unit_distance})); + delete($hash->{READINGS}{unit_speed}) if(defined($hash->{READINGS}{unit_speed})); + delete($hash->{READINGS}{unit_pressuree}) if(defined($hash->{READINGS}{unit_pressuree})); + delete($hash->{READINGS}{unit_temperature}) if(defined($hash->{READINGS}{unit_temperature})); + + + + # convert to metric units as far as required + my $isConverted= YahooWeatherAPI_ConvertChannelData($data); + # housekeeping information readingsBulkUpdate($hash, "lastError", ""); readingsBulkUpdate($hash, "pubDateComment", $pubDateComment); readingsBulkUpdate($hash, "pubDate", $pubDate); readingsBulkUpdate($hash, "pubDateRemote", $pubDate); readingsBulkUpdate($hash, "pubDateTs", $pubDateTs); + readingsBulkUpdate($hash, "isConverted", $isConverted); readingsBulkUpdate($hash, "validity", "up-to-date"); - # units - readingsBulkUpdate($hash, "unit_distance", $units->{distance}); - readingsBulkUpdate($hash, "unit_speed", $units->{speed}); - readingsBulkUpdate($hash, "unit_pressuree", $units->{pressure}); - readingsBulkUpdate($hash, "unit_temperature", $units->{temperature}); - - # description readingsBulkUpdate($hash, "description", $data->{description}); @@ -278,22 +239,16 @@ sub Weather_RetrieveDataFinished($$$) { my $windspeed= int($data->{wind}{speed}+0.5); readingsBulkUpdate($hash, "wind", $windspeed); readingsBulkUpdate($hash, "wind_speed", $windspeed); - # wind_chill comes in °F - readingsBulkUpdate($hash, "wind_chill", F_to_C($data->{wind}{chill})); + readingsBulkUpdate($hash, "wind_chill", $data->{wind}{chill}); my $winddir= $data->{wind}{direction}; readingsBulkUpdate($hash, "wind_direction", $winddir); my $wdir= degrees_to_direction($winddir, @directions_txt_i18n); readingsBulkUpdate($hash, "wind_condition", "Wind: $wdir $windspeed km/h"); - - # delete the temp_f reading - delete($hash->{READINGS}{temp_f}) if(defined($hash->{READINGS}{temp_f})); - # atmosphere my $humidity= $data->{atmosphere}{humidity}; readingsBulkUpdate($hash, "humidity", $humidity); - # pressure is in inHg - my $pressure= inHg_to_hPa($data->{atmosphere}{pressure}); + my $pressure= $data->{atmosphere}{pressure}; readingsBulkUpdate($hash, "pressure", $pressure); readingsBulkUpdate($hash, "visibility", int($data->{atmosphere}{visibility}+0.5)); my $pressure_trend= $data->{atmosphere}{rising}; @@ -421,8 +376,7 @@ sub Weather_Notify($$) { Weather_DisarmTimer($hash); my $delay= 10+int(rand(20)); - # delay removed until further notice - $delay= 3; + #$delay= 3; # delay removed until further notice Log3 $hash, 5, "Weather $name: FHEM initialization or rereadcfg triggered update, delay $delay seconds."; Weather_RearmTimer($hash, gettimeofday()+$delay) ; @@ -461,6 +415,8 @@ sub Weather_Define($$) { $hash->{READINGS}{current_date_time}{TIME}= TimeNow(); $hash->{READINGS}{current_date_time}{VAL}= "none"; + $hash->{fhem}{allowCache}= 1; + Weather_GetUpdate($hash) if($init_done); return undef; diff --git a/fhem/FHEM/YahooWeatherAPI.pm b/fhem/FHEM/YahooWeatherAPI.pm index 2c2d8e083..b39fa4c60 100644 --- a/fhem/FHEM/YahooWeatherAPI.pm +++ b/fhem/FHEM/YahooWeatherAPI.pm @@ -131,7 +131,30 @@ my @YahooCodes_pl = ( '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'); +################################### +# Cache +my %YahooWeatherAPI_CachedData= (); +my %YahooWeatherAPI_CachedDataTs= (); + +################################### + +sub F_to_C($) { + my ($F)= @_; + return(int(($F-32)*5/9+0.5)); +} + +sub inHg_to_hPa($) { + my ($inHg)= @_; + return int($inHg/33.86390+0.5); +} + +sub miles_to_km($) { + my ($miles)= @_; + return int(1.609347219*$miles+0.5); +} + +################################### # call: YahooWeatherAPI_RetrieveData(%%args) # @@ -144,9 +167,33 @@ my @YahooCodes_pl = ( # 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}; @@ -157,14 +204,14 @@ sub YahooWeatherAPI_RetrieveData($) { if ($blocking) { # do not use noshutdown => 0 in parameters - my $response = HttpUtils_BlockingGet({ url => $url, timeout => 5 }); + 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 => 5, + timeout => 15, argsRef => $argsRef, callback => \&YahooWeatherAPI_RetrieveDataFinished, }); @@ -172,10 +219,16 @@ sub YahooWeatherAPI_RetrieveData($) { } sub YahooWeatherAPI_RetrieveDataFinished($$$) { - my ($paramRef, $err, $xml) = @_; + my ($paramRef, $err, $response) = @_; my $argsRef= $paramRef->{argsRef}; #Debug "Finished retrieving Yahoo Weather data for " . $argsRef->{hash}->{NAME}; - $argsRef->{callbackFnRef}($argsRef, $err, $xml); + 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); } @@ -184,6 +237,7 @@ 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($@); @@ -193,10 +247,34 @@ sub YahooWeatherAPI_JSONReturnChannelData($) { #Debug "$count result(s)."; return("$count result(s) retrieved", undef) unless($count == 1); my $channel= $query->{results}{channel}; - #Debug "Result: " . Dumper($channel); return(undef, $channel); } +sub YahooWeatherAPI_ConvertChannelData($) { + my ($data)= @_; # hash reference + + $data->{wind}{chill}= F_to_C($data->{wind}{chill}); # wrongly always °F + $data->{atmosphere}{pressure}= inHg_to_hPa($data->{atmosphere}{pressure}); # wrongly always in inHg + + + my $units= YahooWeatherAPI_units($data); # units hash reference + return 0 if($units->{temperature} eq "C"); + + $data->{wind}{speed}= miles_to_km($data->{wind}{speed}); + $data->{atmosphere}{visibility}= miles_to_km($data->{atmosphere}{visibility}); + + my $item= $data->{item}; + $item->{condition}{temp}= F_to_C($item->{condition}{temp}); + + my $forecast= $item->{forecast}; + foreach my $fc (@{$forecast}) { + $fc->{low}= F_to_C($fc->{low}); + $fc->{high}= F_to_C($fc->{high}); + } + return 1; + +} + sub YahooWeatherAPI_ParseDateTime($) {