mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-10 03:06:37 +00:00
59_Weather: completely reworked
git-svn-id: https://svn.fhem.de/fhem/trunk@18222 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
fb7fc229d7
commit
7de2adc63d
@ -1,5 +1,6 @@
|
|||||||
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
|
# 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.
|
# Do not insert empty lines here, update check depends on it.
|
||||||
|
- change: 59_Weather completely reworked
|
||||||
- bugfix: 98_Verkehrsinfo.pm: internalTimer
|
- bugfix: 98_Verkehrsinfo.pm: internalTimer
|
||||||
- change: 32_withings: improve Aura handling
|
- change: 32_withings: improve Aura handling
|
||||||
- bugfix: 49_SSCam: fix blocking sscam operation if snap was executed with
|
- bugfix: 49_SSCam: fix blocking sscam operation if snap was executed with
|
||||||
|
@ -31,6 +31,8 @@ use Time::HiRes qw(gettimeofday);
|
|||||||
use HttpUtils;
|
use HttpUtils;
|
||||||
use vars qw($FW_ss);
|
use vars qw($FW_ss);
|
||||||
|
|
||||||
|
# use Data::Dumper; # for Debug only
|
||||||
|
|
||||||
my %pressure_trend_txt_en = ( 0 => "steady", 1 => "rising", 2 => "falling" );
|
my %pressure_trend_txt_en = ( 0 => "steady", 1 => "rising", 2 => "falling" );
|
||||||
my %pressure_trend_txt_de = ( 0 => "gleichbleibend", 1 => "steigend", 2 => "fallend" );
|
my %pressure_trend_txt_de = ( 0 => "gleichbleibend", 1 => "steigend", 2 => "fallend" );
|
||||||
my %pressure_trend_txt_nl = ( 0 => "stabiel", 1 => "stijgend", 2 => "dalend" );
|
my %pressure_trend_txt_nl = ( 0 => "stabiel", 1 => "stijgend", 2 => "dalend" );
|
||||||
@ -136,7 +138,7 @@ sub Weather_Initialize($) {
|
|||||||
$hash->{UndefFn} = "Weather_Undef";
|
$hash->{UndefFn} = "Weather_Undef";
|
||||||
$hash->{GetFn} = "Weather_Get";
|
$hash->{GetFn} = "Weather_Get";
|
||||||
$hash->{SetFn} = "Weather_Set";
|
$hash->{SetFn} = "Weather_Set";
|
||||||
$hash->{AttrList}= "disable " . $readingFnAttributes;
|
$hash->{AttrList}= "disable:0,1 " . $readingFnAttributes;
|
||||||
$hash->{NotifyFn}= "Weather_Notify";
|
$hash->{NotifyFn}= "Weather_Notify";
|
||||||
|
|
||||||
#Weather_DebugCodes('de');
|
#Weather_DebugCodes('de');
|
||||||
@ -150,43 +152,19 @@ sub degrees_to_direction($@) {
|
|||||||
return $directions_txt_i18n[$mod];
|
return $directions_txt_i18n[$mod];
|
||||||
}
|
}
|
||||||
|
|
||||||
###################################
|
|
||||||
sub Weather_RetrieveData($$) {
|
|
||||||
my ($name, $blocking) = @_;
|
|
||||||
my $hash = $defs{$name};
|
|
||||||
|
|
||||||
# WOEID [WHERE-ON-EARTH-ID], go to http://weather.yahoo.com to find out
|
sub Weather_ReturnWithError($$) {
|
||||||
my $location= $hash->{LOCATION};
|
my ($hash, $responseRef)= @_;
|
||||||
my $units= $hash->{UNITS};
|
|
||||||
|
|
||||||
my %args= (
|
|
||||||
woeid => $location,
|
|
||||||
format => "json",
|
|
||||||
blocking => $blocking,
|
|
||||||
callbackFnRef => \&Weather_RetrieveDataFinished,
|
|
||||||
hash => $hash,
|
|
||||||
);
|
|
||||||
|
|
||||||
# this needs to be finalized to use the APIOPTIONS
|
|
||||||
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};
|
my $name= $hash->{NAME};
|
||||||
|
|
||||||
$hash->{fhem}{allowCache}= 0; # do not use cache on next try
|
|
||||||
|
|
||||||
Log3 $hash, 3, "$name: $err";
|
|
||||||
readingsBeginUpdate($hash);
|
readingsBeginUpdate($hash);
|
||||||
readingsBulkUpdate($hash, "lastError", $err);
|
readingsBulkUpdate($hash, 'lastError', $responseRef->{status});
|
||||||
readingsBulkUpdate($hash, "pubDateComment", $pubDateComment) if(defined($pubDateComment));
|
|
||||||
readingsBulkUpdate($hash, "pubDateRemote", $pubDate) if(defined($pubDate));
|
foreach my $r (keys %{$responseRef} ) {
|
||||||
readingsBulkUpdate($hash, "validity", "stale");
|
readingsBulkUpdate($hash, $r, $responseRef->{$r}) if ( ref($responseRef->{$r}) ne 'HASH' );
|
||||||
readingsEndUpdate($hash, $doTrigger);
|
}
|
||||||
|
readingsBulkUpdate($hash, 'state', 'API Maintainer: ' . $responseRef->{apiMaintainer} . ' ErrorMsg: ' . $responseRef->{status});
|
||||||
|
readingsEndUpdate($hash, 1);
|
||||||
|
|
||||||
my $next= 60; # $next= $hash->{INTERVAL};
|
my $next= 60; # $next= $hash->{INTERVAL};
|
||||||
Weather_RearmTimer($hash, gettimeofday()+$next);
|
Weather_RearmTimer($hash, gettimeofday()+$next);
|
||||||
@ -194,126 +172,129 @@ sub Weather_ReturnWithError($$$$$) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub Weather_RetrieveDataFinished($$$) {
|
sub Weather_RetrieveCallbackFn($) {
|
||||||
|
|
||||||
my ($argsRef, $err, $response)= @_;
|
my $name = shift;
|
||||||
|
my $hash = $defs{$name};
|
||||||
|
my $responseRef = $hash->{fhem}->{api}->getWeather;
|
||||||
|
|
||||||
my $hash= $argsRef->{hash};
|
if ( $responseRef->{status} eq 'ok' ) {
|
||||||
my $name= $hash->{NAME};
|
Weather_WriteReadings($hash,$responseRef);
|
||||||
my $doTrigger= $argsRef->{blocking} ? 0 : 1;
|
}
|
||||||
|
else {
|
||||||
# check for error from retrieving data
|
Weather_ReturnWithError($hash,$responseRef);
|
||||||
return Weather_ReturnWithError($hash, $doTrigger, $err, undef, undef) if($err);
|
}
|
||||||
|
}
|
||||||
# decode JSON data from Weather Channel
|
|
||||||
my $data;
|
|
||||||
($err, $data)= YahooWeatherAPI_JSONReturnChannelData($response);
|
|
||||||
return Weather_ReturnWithError($hash, $doTrigger, $err, undef, undef) if($err);
|
|
||||||
|
|
||||||
# check if up-to-date
|
|
||||||
my ($pubDateComment, $pubDate, $pubDateTs)= YahooWeatherAPI_pubDate($data);
|
|
||||||
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
|
|
||||||
#
|
|
||||||
my $lang= $hash->{LANG};
|
|
||||||
|
|
||||||
my @YahooCodes_i18n= YahooWeatherAPI_getYahooCodes($lang);
|
|
||||||
|
|
||||||
my $item= $data->{item};
|
|
||||||
|
|
||||||
|
sub Weather_WriteReadings($$) {
|
||||||
|
my ($hash,$dataRef) = @_;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
readingsBeginUpdate($hash);
|
readingsBeginUpdate($hash);
|
||||||
|
|
||||||
# delete some unused readings
|
# delete some unused readings
|
||||||
delete($hash->{READINGS}{temp_f}) if(defined($hash->{READINGS}{temp_f}));
|
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_distance}) if(defined($hash->{READINGS}->{unit_distance}));
|
||||||
delete($hash->{READINGS}{unit_speed}) if(defined($hash->{READINGS}{unit_speed}));
|
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_pressuree}) if(defined($hash->{READINGS}->{unit_pressuree}));
|
||||||
delete($hash->{READINGS}{unit_temperature}) if(defined($hash->{READINGS}{unit_temperature}));
|
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
|
# housekeeping information
|
||||||
readingsBulkUpdate($hash, "lastError", "");
|
readingsBulkUpdate($hash, 'lastError', '');
|
||||||
readingsBulkUpdate($hash, "pubDateComment", $pubDateComment);
|
foreach my $r (keys %{$dataRef} ) {
|
||||||
readingsBulkUpdate($hash, "pubDate", $pubDate);
|
readingsBulkUpdate($hash, $r, $dataRef->{$r})
|
||||||
readingsBulkUpdate($hash, "pubDateRemote", $pubDate);
|
if ( ref($dataRef->{$r}) ne 'HASH' and ref($dataRef->{$r}) ne 'ARRAY' );
|
||||||
readingsBulkUpdate($hash, "pubDateTs", $pubDateTs);
|
readingsBulkUpdate($hash, '.license', $dataRef->{license}->{text});
|
||||||
readingsBulkUpdate($hash, "isConverted", $isConverted);
|
|
||||||
readingsBulkUpdate($hash, "validity", "up-to-date");
|
|
||||||
|
|
||||||
# description
|
|
||||||
readingsBulkUpdate($hash, "description", $data->{description});
|
|
||||||
|
|
||||||
# location
|
|
||||||
readingsBulkUpdate($hash, "city", $data->{location}{city});
|
|
||||||
readingsBulkUpdate($hash, "region", $data->{location}{region});
|
|
||||||
readingsBulkUpdate($hash, "country", $data->{location}{country});
|
|
||||||
readingsBulkUpdate($hash, "lat", $item->{lat});
|
|
||||||
readingsBulkUpdate($hash, "long", $item->{long});
|
|
||||||
|
|
||||||
# wind
|
|
||||||
my $windspeed= int($data->{wind}{speed}+0.5);
|
|
||||||
readingsBulkUpdate($hash, "wind", $windspeed);
|
|
||||||
readingsBulkUpdate($hash, "wind_speed", $windspeed);
|
|
||||||
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");
|
|
||||||
|
|
||||||
# atmosphere
|
|
||||||
my $humidity= $data->{atmosphere}{humidity};
|
|
||||||
readingsBulkUpdate($hash, "humidity", $humidity);
|
|
||||||
my $pressure= $data->{atmosphere}{pressure};
|
|
||||||
readingsBulkUpdate($hash, "pressure", $pressure);
|
|
||||||
readingsBulkUpdate($hash, "visibility", int($data->{atmosphere}{visibility}+0.5));
|
|
||||||
my $pressure_trend= $data->{atmosphere}{rising};
|
|
||||||
readingsBulkUpdate($hash, "pressure_trend", $pressure_trend);
|
|
||||||
readingsBulkUpdate($hash, "pressure_trend_txt", $pressure_trend_txt_i18n{$pressure_trend});
|
|
||||||
readingsBulkUpdate($hash, "pressure_trend_sym", $pressure_trend_sym{$pressure_trend});
|
|
||||||
|
|
||||||
# condition
|
|
||||||
my $date= $item->{condition}{date};
|
|
||||||
readingsBulkUpdate($hash, "current_date_time", $date);
|
|
||||||
readingsBulkUpdate($hash, "day_of_week", $wdays_txt_i18n{substr($date,0,3)});
|
|
||||||
my $code= $item->{condition}{code};
|
|
||||||
readingsBulkUpdate($hash, "code", $code);
|
|
||||||
readingsBulkUpdate($hash, "condition", $YahooCodes_i18n[$code]);
|
|
||||||
readingsBulkUpdate($hash, "icon", $iconlist[$code]);
|
|
||||||
my $temp= $item->{condition}{temp};
|
|
||||||
readingsBulkUpdate($hash, "temp_c", $temp);
|
|
||||||
readingsBulkUpdate($hash, "temperature", $temp);
|
|
||||||
|
|
||||||
# forecast
|
|
||||||
my $forecast= $item->{forecast};
|
|
||||||
my $i= 0;
|
|
||||||
foreach my $fc (@{$forecast}) {
|
|
||||||
$i++;
|
|
||||||
my $f= "fc" . $i ."_";
|
|
||||||
readingsBulkUpdate($hash, $f . "day_of_week", $wdays_txt_i18n{$fc->{day}});
|
|
||||||
readingsBulkUpdate($hash, $f . "date", $fc->{date});
|
|
||||||
readingsBulkUpdate($hash, $f . "low_c", $fc->{low});
|
|
||||||
readingsBulkUpdate($hash, $f . "high_c", $fc->{high});
|
|
||||||
my $fccode= $fc->{code};
|
|
||||||
readingsBulkUpdate($hash, $f . "code", $fccode);
|
|
||||||
readingsBulkUpdate($hash, $f . "condition", $YahooCodes_i18n[$fccode]);
|
|
||||||
readingsBulkUpdate($hash, $f . "icon", $iconlist[$fccode]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#my $val= "T:$temp°C " . substr($status_items_txt_i18n{1}, 0, 1) .":$humidity% " . substr($status_items_txt_i18n{0}, 0, 1) . ":$windspeed km/h P:$pressure mbar";
|
readingsBulkUpdate($hash, "validity", "up-to-date");
|
||||||
my $val= "T: $temp H: $humidity W: $windspeed P: $pressure";
|
|
||||||
Log3 $hash, 4, "$name: $val";
|
|
||||||
readingsBulkUpdate($hash, "state", $val);
|
|
||||||
|
|
||||||
readingsEndUpdate($hash, $doTrigger);
|
|
||||||
|
### current
|
||||||
|
if ( defined($dataRef->{current}) and ref( $dataRef->{current} ) eq 'HASH' ) {
|
||||||
|
while( my ($r,$v) = each %{$dataRef->{current}} ) {
|
||||||
|
readingsBulkUpdate($hash, $r, $v)
|
||||||
|
if ( ref($dataRef->{$r}) ne 'HASH' and ref($dataRef->{$r}) ne 'ARRAY' );
|
||||||
|
}
|
||||||
|
|
||||||
|
readingsBulkUpdate($hash, 'icon', $iconlist[$dataRef->{current}->{code}]);
|
||||||
|
if ( defined($dataRef->{current}->{wind_direction})
|
||||||
|
and $dataRef->{current}->{wind_direction}
|
||||||
|
and defined($dataRef->{current}->{wind_speed})
|
||||||
|
and $dataRef->{current}->{wind_speed}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
my $wdir= degrees_to_direction($dataRef->{current}->{wind_direction}, @directions_txt_i18n);
|
||||||
|
readingsBulkUpdate($hash, 'wind_condition', 'Wind: ' . $wdir . ' ' . $dataRef->{current}->{wind_speed} . ' km/h');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
### forecast
|
||||||
|
if ( ref( $dataRef->{forecast} ) eq 'HASH' ) {
|
||||||
|
## hourly
|
||||||
|
if ( defined($dataRef->{forecast}->{hourly})
|
||||||
|
and ref( $dataRef->{forecast}->{hourly} ) eq 'ARRAY'
|
||||||
|
and scalar( @{ $dataRef->{forecast}->{hourly} } ) > 0 )
|
||||||
|
{
|
||||||
|
my $i= 0;
|
||||||
|
foreach my $fc (@{$dataRef->{forecast}->{hourly}}) {
|
||||||
|
$i++;
|
||||||
|
my $f= "hfc" . $i ."_";
|
||||||
|
|
||||||
|
while( my ($r,$v) = each %{$fc} ) {
|
||||||
|
readingsBulkUpdate($hash, $f.$r, $v)
|
||||||
|
if ( ref($dataRef->{$r}) ne 'HASH' and ref($dataRef->{$r}) ne 'ARRAY' );
|
||||||
|
}
|
||||||
|
readingsBulkUpdate($hash, $f . 'icon', $iconlist[$dataRef->{forecast}->{hourly}[$i-1]{code}]);
|
||||||
|
|
||||||
|
if ( defined($dataRef->{forecast}->{hourly}[$i-1]{wind_direction})
|
||||||
|
and $dataRef->{forecast}->{hourly}[$i-1]{wind_direction}
|
||||||
|
and defined($dataRef->{forecast}->{hourly}[$i-1]{wind_speed})
|
||||||
|
and $dataRef->{forecast}->{hourly}[$i-1]{wind_speed}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
my $wdir= degrees_to_direction($dataRef->{forecast}->{hourly}[$i-1]{wind_direction}, @directions_txt_i18n);
|
||||||
|
readingsBulkUpdate($hash, $f . 'wind_condition', 'Wind: ' . $wdir . ' ' . $dataRef->{forecast}->{hourly}[$i-1]{wind_speed} . ' km/h');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## daily
|
||||||
|
if ( defined($dataRef->{forecast}->{daily}) and ref( $dataRef->{forecast}->{daily} ) eq 'ARRAY'
|
||||||
|
and scalar( @{ $dataRef->{forecast}->{daily} } ) > 0 )
|
||||||
|
{
|
||||||
|
my $i= 0;
|
||||||
|
foreach my $fc (@{$dataRef->{forecast}->{daily}}) {
|
||||||
|
$i++;
|
||||||
|
my $f= "fc" . $i ."_";
|
||||||
|
|
||||||
|
while( my ($r,$v) = each %{$fc} ) {
|
||||||
|
readingsBulkUpdate($hash, $f.$r, $v)
|
||||||
|
if ( ref($dataRef->{$r}) ne 'HASH' and ref($dataRef->{$r}) ne 'ARRAY' );
|
||||||
|
}
|
||||||
|
readingsBulkUpdate($hash, $f . 'icon', $iconlist[$dataRef->{forecast}->{daily}[$i-1]{code}]);
|
||||||
|
|
||||||
|
if ( defined($dataRef->{forecast}->{daily}[$i-1]{wind_direction})
|
||||||
|
and $dataRef->{forecast}->{daily}[$i-1]{wind_direction}
|
||||||
|
and defined($dataRef->{forecast}->{daily}[$i-1]{wind_speed})
|
||||||
|
and $dataRef->{forecast}->{daily}[$i-1]{wind_speed}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
my $wdir= degrees_to_direction($dataRef->{forecast}->{daily}[$i-1]{wind_direction}, @directions_txt_i18n);
|
||||||
|
readingsBulkUpdate($hash, $f . 'wind_condition', 'Wind: ' . $wdir . ' ' . $dataRef->{forecast}->{daily}[$i-1]{wind_speed} . ' km/h');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $val= 'T:' . $dataRef->{current}->{temperature} . '°C'
|
||||||
|
.' ' . substr($status_items_txt_i18n{1}, 0, 1) . ':' . $dataRef->{current}->{humidity} . '%'
|
||||||
|
.' ' . substr($status_items_txt_i18n{0}, 0, 1) . ':' . $dataRef->{current}->{wind} . 'km/h'
|
||||||
|
.' P:' . $dataRef->{current}->{pressure} . 'mbar';
|
||||||
|
|
||||||
|
Log3 $hash, 4, "$name: $val";
|
||||||
|
readingsBulkUpdate($hash, 'state', $val);
|
||||||
|
|
||||||
|
readingsEndUpdate($hash, 1);
|
||||||
|
|
||||||
Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL});
|
Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL});
|
||||||
return;
|
return;
|
||||||
@ -326,7 +307,7 @@ sub Weather_GetUpdate($) {
|
|||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
if($attr{$name} && $attr{$name}{disable}) {
|
if($attr{$name} && $attr{$name}->{disable}) {
|
||||||
Log3 $hash, 5, "Weather $name: retrieval of weather data is disabled by attribute.";
|
Log3 $hash, 5, "Weather $name: retrieval of weather data is disabled by attribute.";
|
||||||
readingsBeginUpdate($hash);
|
readingsBeginUpdate($hash);
|
||||||
readingsBulkUpdate($hash, "pubDateComment", "disabled by attribute");
|
readingsBulkUpdate($hash, "pubDateComment", "disabled by attribute");
|
||||||
@ -334,7 +315,8 @@ sub Weather_GetUpdate($) {
|
|||||||
readingsEndUpdate($hash, 1);
|
readingsEndUpdate($hash, 1);
|
||||||
Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL});
|
Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL});
|
||||||
} else {
|
} else {
|
||||||
Weather_RetrieveData($name, 0);
|
# Weather_RetrieveData($name, 0);
|
||||||
|
$hash->{fhem}->{api}->setRetrieveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
@ -350,13 +332,14 @@ sub Weather_Get($@) {
|
|||||||
my $reading= $a[1];
|
my $reading= $a[1];
|
||||||
my $value;
|
my $value;
|
||||||
|
|
||||||
if(defined($hash->{READINGS}{$reading})) {
|
if(defined($hash->{READINGS}->{$reading})) {
|
||||||
$value= $hash->{READINGS}{$reading}{VAL};
|
$value = $hash->{READINGS}->{$reading}->{VAL};
|
||||||
} else {
|
} else {
|
||||||
my $rt= "";
|
my $rt = '';
|
||||||
if(defined($hash->{READINGS})) {
|
if(defined($hash->{READINGS})) {
|
||||||
$rt= join(" ", sort keys %{$hash->{READINGS}});
|
$rt = join(":noArg ", sort keys %{$hash->{READINGS}});
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Unknown reading $reading, choose one of " . $rt;
|
return "Unknown reading $reading, choose one of " . $rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +358,7 @@ sub Weather_Set($@) {
|
|||||||
Weather_GetUpdate($hash);
|
Weather_GetUpdate($hash);
|
||||||
return undef;
|
return undef;
|
||||||
} else {
|
} else {
|
||||||
return "Unknown argument $cmd, choose one of update";
|
return "Unknown argument $cmd, choose one of update:noArg";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +384,7 @@ sub Weather_Notify($$) {
|
|||||||
return if($dev->{NAME} ne "global");
|
return if($dev->{NAME} ne "global");
|
||||||
return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
|
return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
|
||||||
|
|
||||||
# return if($attr{$name} && $attr{$name}{disable});
|
# return if($attr{$name} && $attr{$name}->{disable});
|
||||||
|
|
||||||
# update weather after initialization or change of configuration
|
# update weather after initialization or change of configuration
|
||||||
# wait 10 to 29 seconds to avoid congestion due to concurrent activities
|
# wait 10 to 29 seconds to avoid congestion due to concurrent activities
|
||||||
@ -421,47 +404,29 @@ sub Weather_Define($$) {
|
|||||||
|
|
||||||
my ($hash, $def) = @_;
|
my ($hash, $def) = @_;
|
||||||
|
|
||||||
# define <name> Weather <location> [interval]
|
my $usage= "syntax: define <name> Weather [API=<API>] [apikey=<apikey>] [location=<location>] [interval=<interval>] [lang=<lang>]";
|
||||||
# define MyWeather Weather "Maintal,HE" 3600
|
|
||||||
|
|
||||||
# define <name> Weather location=<location> [API=<API>] [interval=<interval>] [lang=<lang>]
|
# defaults
|
||||||
|
my $API="DarkSkyAPI,cachemaxage:600";
|
||||||
my $name;
|
|
||||||
my $API="YahooWeatherAPI,transport:https,cachemaxage:600";
|
|
||||||
my $location;
|
|
||||||
my $interval = 3600;
|
my $interval = 3600;
|
||||||
my $lang = "en";
|
|
||||||
|
|
||||||
if($def =~ /=/) {
|
# parse parameters
|
||||||
|
my ($arrayref, $hashref)= parseParams($def);
|
||||||
|
my @a= @{$arrayref};
|
||||||
|
my %h= %{$hashref};
|
||||||
|
|
||||||
my $usage= "syntax: define <name> Weather location=<location> [API=<API>] [lang=<lang>]";
|
# check minimum syntax
|
||||||
|
return $usage unless(scalar @a == 2);
|
||||||
|
my $name= $a[0];
|
||||||
|
|
||||||
my ($arrayref, $hashref)= parseParams($def);
|
|
||||||
my @a= @{$arrayref};
|
|
||||||
my %h= %{$hashref};
|
|
||||||
|
|
||||||
return $usage unless(scalar @a == 2);
|
my $location= $h{location} if exists $h{location};
|
||||||
$name= $a[0];
|
my $apikey = $h{apikey} if exists $h{apikey};
|
||||||
|
my $lang= $h{lang} if exists $h{lang};
|
||||||
return $usage unless exists $h{location};
|
$interval= $h{interval} if exists $h{interval};
|
||||||
$location= $h{location};
|
$API = $h{API} if exists $h{API};
|
||||||
$lang= $h{lang} if exists $h{lang};
|
|
||||||
$interval= $h{interval} if exists $h{interval};
|
|
||||||
$API= $h{API} if exists $h{API};
|
|
||||||
|
|
||||||
} else {
|
|
||||||
my @a = split("[ \t][ \t]*", $def);
|
|
||||||
|
|
||||||
return "syntax: define <name> Weather <location> [interval [en|de|nl|fr|pl|it]]"
|
|
||||||
if(int(@a) < 3 && int(@a) > 5);
|
|
||||||
|
|
||||||
$name = $a[0];
|
|
||||||
$location = $a[2];
|
|
||||||
if(int(@a)>=4) { $interval= $a[3]; }
|
|
||||||
if(int(@a)==5) { $lang= $a[4]; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# evaluate API options
|
||||||
my ($api,$apioptions)= split(',', $API, 2);
|
my ($api,$apioptions)= split(',', $API, 2);
|
||||||
$apioptions= "" unless(defined($apioptions));
|
$apioptions= "" unless(defined($apioptions));
|
||||||
eval {
|
eval {
|
||||||
@ -470,21 +435,24 @@ sub Weather_Define($$) {
|
|||||||
return "$name: cannot load API $api: $@" if($@);
|
return "$name: cannot load API $api: $@" if($@);
|
||||||
|
|
||||||
$hash->{NOTIFYDEV} = "global";
|
$hash->{NOTIFYDEV} = "global";
|
||||||
$hash->{STATE} = "Initialized";
|
$hash->{fhem}->{interfaces}= "temperature;humidity;wind";
|
||||||
$hash->{fhem}{interfaces}= "temperature;humidity;wind";
|
$hash->{LOCATION} = ( (defined($location) and $location) ? $location : AttrVal( 'global', 'latitude', 'error' ).','.AttrVal( 'global', 'longitude', 'error' ) );
|
||||||
|
|
||||||
$hash->{LOCATION} = $location;
|
|
||||||
$hash->{INTERVAL} = $interval;
|
$hash->{INTERVAL} = $interval;
|
||||||
$hash->{LANG} = $lang;
|
$hash->{LANG} = ( (defined($lang) and $lang) ? $lang : lc(AttrVal('global','language','de')) );
|
||||||
$hash->{API} = $api;
|
$hash->{API} = $api;
|
||||||
|
$hash->{APIKEY} = $apikey;
|
||||||
$hash->{APIOPTIONS} = $apioptions;
|
$hash->{APIOPTIONS} = $apioptions;
|
||||||
$hash->{UNITS} = "c"; # hardcoded to use degrees centigrade (Celsius)
|
#$hash->{UNITS} = "c"; # hardcoded to use degrees centigrade (Celsius)
|
||||||
$hash->{READINGS}{current_date_time}{TIME}= TimeNow();
|
$hash->{READINGS}->{current_date_time}->{TIME}= TimeNow();
|
||||||
$hash->{READINGS}{current_date_time}{VAL}= "none";
|
$hash->{READINGS}->{current_date_time}->{VAL}= "none";
|
||||||
|
|
||||||
$hash->{fhem}{allowCache}= 1;
|
$hash->{fhem}->{allowCache}= 1;
|
||||||
|
|
||||||
Weather_LanguageInitialize($lang);
|
readingsSingleUpdate($hash,'state','Initialized',1);
|
||||||
|
Weather_LanguageInitialize($hash->{LANG});
|
||||||
|
|
||||||
|
my $apistring = $api . '::Weather';
|
||||||
|
$hash->{fhem}->{api} = $apistring->new( { devName => $hash->{NAME}, apikey => $hash->{APIKEY}, location => $hash->{LOCATION}, apioptions => $hash->{APIOPTIONS}, language => $hash->{LANG} } );
|
||||||
|
|
||||||
Weather_GetUpdate($hash) if($init_done);
|
Weather_GetUpdate($hash) if($init_done);
|
||||||
|
|
||||||
@ -510,8 +478,7 @@ use constant ICONSCALE => 0.5;
|
|||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
sub
|
sub WeatherIconIMGTag($) {
|
||||||
WeatherIconIMGTag($) {
|
|
||||||
|
|
||||||
my $width= int(ICONSCALE*ICONWIDTH);
|
my $width= int(ICONSCALE*ICONWIDTH);
|
||||||
my ($icon)= @_;
|
my ($icon)= @_;
|
||||||
@ -523,16 +490,16 @@ WeatherIconIMGTag($) {
|
|||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
sub
|
sub WeatherAsHtmlV($;$)
|
||||||
WeatherAsHtmlV($;$)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
my ($d,$items) = @_;
|
my ($d,$items) = @_;
|
||||||
$d = "<none>" if(!$d);
|
$d = "<none>" if(!$d);
|
||||||
$items = 10 if( !$items );
|
$items = 9 if( !$items );
|
||||||
return "$d is not a Weather instance<br>"
|
return "$d is not a Weather instance<br>"
|
||||||
if(!$defs{$d} || $defs{$d}{TYPE} ne "Weather");
|
if(!$defs{$d} || $defs{$d}->{TYPE} ne "Weather");
|
||||||
|
|
||||||
|
my $h = $defs{$d};
|
||||||
my $width= int(ICONSCALE*ICONWIDTH);
|
my $width= int(ICONSCALE*ICONWIDTH);
|
||||||
|
|
||||||
my $ret = '<table class="weather">';
|
my $ret = '<table class="weather">';
|
||||||
@ -543,36 +510,36 @@ WeatherAsHtmlV($;$)
|
|||||||
ReadingsVal($d, "temp_c", ""), ReadingsVal($d, "humidity", ""),
|
ReadingsVal($d, "temp_c", ""), ReadingsVal($d, "humidity", ""),
|
||||||
ReadingsVal($d, "wind_condition", ""));
|
ReadingsVal($d, "wind_condition", ""));
|
||||||
|
|
||||||
|
my $fc = ( (defined($h->{READINGS}->{fc1_day_of_week}) and $h->{READINGS}->{fc1_day_of_week}) ? 'fc' : 'hfc' );
|
||||||
for(my $i=1; $i<$items; $i++) {
|
for(my $i=1; $i<$items; $i++) {
|
||||||
$ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td><td class="weatherValue"><span class="weatherDay">%s: %s</span><br><span class="weatherMin">min %s°C</span> <span class="weatherMax">max %s°C</span></td></tr>',
|
$ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td><td class="weatherValue"><span class="weatherDay">%s: %s</span><br><span class="weatherMin">min %s°C</span> <span class="weatherMax">max %s°C</span></td></tr>',
|
||||||
$width,
|
$width,
|
||||||
WeatherIconIMGTag(ReadingsVal($d, "fc${i}_icon", "")),
|
WeatherIconIMGTag(ReadingsVal($d, "${fc}${i}_icon", "")),
|
||||||
ReadingsVal($d, "fc${i}_day_of_week", ""),
|
ReadingsVal($d, "${fc}${i}_day_of_week", ""),
|
||||||
ReadingsVal($d, "fc${i}_condition", ""),
|
ReadingsVal($d, "${fc}${i}_condition", ""),
|
||||||
ReadingsVal($d, "fc${i}_low_c", ""), ReadingsVal($d, "fc${i}_high_c", ""));
|
ReadingsVal($d, "${fc}${i}_low_c", ""), ReadingsVal($d, "${fc}${i}_high_c", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
$ret .= "</table>";
|
$ret .= "</table>";
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub
|
sub WeatherAsHtml($;$)
|
||||||
WeatherAsHtml($;$)
|
|
||||||
{
|
{
|
||||||
my ($d,$i) = @_;
|
my ($d,$i) = @_;
|
||||||
WeatherAsHtmlV($d,$i);
|
WeatherAsHtmlV($d,$i);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub
|
sub WeatherAsHtmlH($;$)
|
||||||
WeatherAsHtmlH($;$)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
my ($d,$items) = @_;
|
my ($d,$items) = @_;
|
||||||
$d = "<none>" if(!$d);
|
$d = "<none>" if(!$d);
|
||||||
$items = 10 if( !$items );
|
$items = 9 if( !$items );
|
||||||
return "$d is not a Weather instance<br>"
|
return "$d is not a Weather instance<br>"
|
||||||
if(!$defs{$d} || $defs{$d}{TYPE} ne "Weather");
|
if(!$defs{$d} || $defs{$d}->{TYPE} ne "Weather");
|
||||||
|
|
||||||
|
my $h = $defs{$d};
|
||||||
my $width= int(ICONSCALE*ICONWIDTH);
|
my $width= int(ICONSCALE*ICONWIDTH);
|
||||||
|
|
||||||
|
|
||||||
@ -580,41 +547,41 @@ WeatherAsHtmlH($;$)
|
|||||||
my $format= '<td><table border=1><tr><td class="weatherIcon" width=%d>%s</td></tr><tr><td class="weatherValue">%s</td></tr><tr><td class="weatherValue">%s°C %s%%</td></tr><tr><td class="weatherValue">%s</td></tr></table></td>';
|
my $format= '<td><table border=1><tr><td class="weatherIcon" width=%d>%s</td></tr><tr><td class="weatherValue">%s</td></tr><tr><td class="weatherValue">%s°C %s%%</td></tr><tr><td class="weatherValue">%s</td></tr></table></td>';
|
||||||
|
|
||||||
my $ret = '<table class="weather">';
|
my $ret = '<table class="weather">';
|
||||||
|
my $fc = ( (defined($h->{READINGS}->{fc1_day_of_week}) and $h->{READINGS}->{fc1_day_of_week}) ? 'fc' : 'hfc' );
|
||||||
|
|
||||||
# icons
|
# icons
|
||||||
$ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td>', $width, WeatherIconIMGTag(ReadingsVal($d, "icon", "")));
|
$ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td>', $width, WeatherIconIMGTag(ReadingsVal($d, "icon", "")));
|
||||||
for(my $i=1; $i<$items; $i++) {
|
for(my $i=1; $i<$items; $i++) {
|
||||||
$ret .= sprintf('<td class="weatherIcon" width=%d>%s</td>', $width, WeatherIconIMGTag(ReadingsVal($d, "fc${i}_icon", "")));
|
$ret .= sprintf('<td class="weatherIcon" width=%d>%s</td>', $width, WeatherIconIMGTag(ReadingsVal($d, "${fc}${i}_icon", "")));
|
||||||
}
|
}
|
||||||
$ret .= '</tr>';
|
$ret .= '</tr>';
|
||||||
|
|
||||||
# condition
|
# condition
|
||||||
$ret .= sprintf('<tr><td class="weatherDay">%s</td>', ReadingsVal($d, "condition", ""));
|
$ret .= sprintf('<tr><td class="weatherDay">%s</td>', ReadingsVal($d, "condition", ""));
|
||||||
for(my $i=1; $i<$items; $i++) {
|
for(my $i=1; $i<$items; $i++) {
|
||||||
$ret .= sprintf('<td class="weatherDay">%s: %s</td>', ReadingsVal($d, "fc${i}_day_of_week", ""),
|
$ret .= sprintf('<td class="weatherDay">%s: %s</td>', ReadingsVal($d, "${fc}${i}_day_of_week", ""),
|
||||||
ReadingsVal($d, "fc${i}_condition", ""));
|
ReadingsVal($d, "${fc}${i}_condition", ""));
|
||||||
}
|
}
|
||||||
$ret .= '</tr>';
|
$ret .= '</tr>';
|
||||||
|
|
||||||
# temp/hum | min
|
# temp/hum | min
|
||||||
$ret .= sprintf('<tr><td class="weatherMin">%s°C %s%%</td>', ReadingsVal($d, "temp_c", ""), ReadingsVal($d, "humidity", ""));
|
$ret .= sprintf('<tr><td class="weatherMin">%s°C %s%%</td>', ReadingsVal($d, "temp_c", ""), ReadingsVal($d, "humidity", ""));
|
||||||
for(my $i=1; $i<$items; $i++) {
|
for(my $i=1; $i<$items; $i++) {
|
||||||
$ret .= sprintf('<td class="weatherMin">min %s°C</td>', ReadingsVal($d, "fc${i}_low_c", ""));
|
$ret .= sprintf('<td class="weatherMin">min %s°C</td>', ReadingsVal($d, "${fc}${i}_low_c", ""));
|
||||||
}
|
}
|
||||||
$ret .= '</tr>';
|
$ret .= '</tr>';
|
||||||
|
|
||||||
# wind | max
|
# wind | max
|
||||||
$ret .= sprintf('<tr><td class="weatherMax">%s</td>', ReadingsVal($d, "wind_condition", ""));
|
$ret .= sprintf('<tr><td class="weatherMax">%s</td>', ReadingsVal($d, "wind_condition", ""));
|
||||||
for(my $i=1; $i<$items; $i++) {
|
for(my $i=1; $i<$items; $i++) {
|
||||||
$ret .= sprintf('<td class="weatherMax">max %s°C</td>', ReadingsVal($d, "fc${i}_high_c", ""));
|
$ret .= sprintf('<td class="weatherMax">max %s°C</td>', ReadingsVal($d, "${fc}${i}_high_c", ""));
|
||||||
}
|
}
|
||||||
$ret .= "</tr></table>";
|
$ret .= "</tr></table>";
|
||||||
|
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub
|
sub WeatherAsHtmlD($;$)
|
||||||
WeatherAsHtmlD($;$)
|
|
||||||
{
|
{
|
||||||
my ($d,$i) = @_;
|
my ($d,$i) = @_;
|
||||||
if($FW_ss) {
|
if($FW_ss) {
|
||||||
@ -631,52 +598,95 @@ WeatherAsHtmlD($;$)
|
|||||||
|
|
||||||
=pod
|
=pod
|
||||||
=item device
|
=item device
|
||||||
=item summary provides current weather condition and forecast (source: Yahoo Weather API)
|
=item summary provides current weather condition and forecast
|
||||||
=item summary_DE stellt Wetterbericht und -vorhersage bereit (Quelle: Yahoo Weather API)
|
=item summary_DE stellt Wetterbericht und -vorhersage bereit
|
||||||
=begin html
|
=begin html
|
||||||
|
|
||||||
<a name="Weather"></a>
|
<a name="Weather"></a>
|
||||||
<h3>Weather</h3>
|
<h3>Weather</h3>
|
||||||
<ul>
|
<ul>
|
||||||
You need the JSON perl module. Use <code>apt-get install libjson-perl</code> on Debian and derivatives.<br><br>
|
Note: you need the JSON perl module. Use <code>apt-get install
|
||||||
|
libjson-perl</code> on Debian and derivatives.<p><p>
|
||||||
|
|
||||||
|
The Weather module works with various weather APIs:
|
||||||
|
<ul>
|
||||||
|
<li>DarkSky (<a href="https://darksky.net">web site</a>, standard)</li>
|
||||||
|
<li>OpenWeatherMap (<a href="https://openweathermap.org/">web site)</a></li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
Such a virtual Weather device periodically gathers current and forecast
|
||||||
|
weather conditions from the chosen weather API.<br><br>
|
||||||
|
|
||||||
<a name="Weatherdefine"></a>
|
<a name="Weatherdefine"></a>
|
||||||
<b>Define</b>
|
<b>Define</b><br><br>
|
||||||
<ul>
|
<ul>
|
||||||
<code>define <name> Weather <location> [<interval> [<language>]]</code><br>
|
<code>define <name> Weather [API=<API>[,<apiotions>]] [apikey=<apikey>]
|
||||||
<br>
|
[location=<location>] [interval=<interval>] [lang=<lang>]</code><br><br>
|
||||||
Defines a virtual device for weather forecasts.<br><br>
|
|
||||||
|
|
||||||
A Weather device periodically gathers current and forecast weather conditions
|
|
||||||
from the Yahoo Weather API.<br><br>
|
|
||||||
|
|
||||||
The parameter <code>location</code> is the WOEID (WHERE-ON-EARTH-ID), go to
|
The parameters have the following meanings:<br>
|
||||||
<a href="http://weather.yahoo.com">http://weather.yahoo.com</a> to find it out for your location.<br><br>
|
|
||||||
|
|
||||||
The optional parameter <code>interval</code> is the time between subsequent updates
|
<table border="1">
|
||||||
in seconds. It defaults to 3600 (1 hour).<br><br>
|
<tr><td><code>API</code></td><td>name of the weather API, e.g. <code>DarkSkyAPI</code></td></tr>
|
||||||
|
<tr><td><code>apioptions</code></td><td>indivual options for the chosen API</td></tr>
|
||||||
|
<tr><td><code>apikey</code></td><td>key for the chosen API</td></tr>
|
||||||
|
<tr><td><code>location</code></td><td>location for the weather forecast;
|
||||||
|
e.g. coordinates, a town name or an ID, depending on the chosen API</td></tr>
|
||||||
|
<tr><td><code>interval</code></td><td>duration in seconds between updates</td></tr>
|
||||||
|
<tr><td><code>lang</code></td><td>language of the forecast: <code>de</code>,
|
||||||
|
<code>en</code>, <code>pl</code>, <code>fr</code>, <code>it</code> or <code>nl</code></td></tr>
|
||||||
|
</ table>
|
||||||
|
<p>
|
||||||
|
|
||||||
The optional language parameter may be one of
|
A very simple definition is:<br><br>
|
||||||
<code>de</code>,
|
<code>define <name> Weather apikey=<DarkSkyAPISecretKey></code><br><br>
|
||||||
<code>en</code>,
|
This uses the Dark Sky API with an individual key that you need to
|
||||||
<code>pl</code>,
|
retrieve from the Dark Sky web site.<p><p>
|
||||||
<code>fr</code>,
|
|
||||||
<code>nl</code>,
|
|
||||||
<code>it</code>,
|
|
||||||
|
|
||||||
It determines the natural language in which the forecast information appears.
|
|
||||||
It defaults to <code>en</code>. If you want to set the language you also have to set the interval.<br><br>
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
<pre>
|
<pre>
|
||||||
define MyWeather Weather 673513
|
define Forecast Weather apikey=987498ghjgf864
|
||||||
define Forecast Weather 673513 1800
|
define MyWeather Weather api=OpenWeatherMapAPI,cachemaxage:600 apikey=09878945fdskv876 location=52.4545,13.4545 interval=3600 language=de
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
The module provides four additional functions <code>WeatherAsHtml</code>, <code>WeatherAsHtmlV</code>, <code>WeatherAsHtmlH</code> and
|
|
||||||
<code>WeatherAsHtmlD</code>. The former two functions are identical: they return the HTML code for a
|
API-specific documentation follows.<p>
|
||||||
vertically arranged weather forecast. The third function returns the HTML code for a horizontally arranged weather forecast. The
|
|
||||||
latter function dynamically picks the orientation depending on wether a smallscreen style is set (vertical layout) or not (horizontal layout). Each version accepts an additional paramter to limit the numer of icons to display.<br><br>
|
<b>Dark Sky</b><p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr><td>API</td><td><code>DarkSkyAPI</code></td></tr>
|
||||||
|
<tr><td>apioptions</td><td><code>cachemaxage=<cachemaxage></code><br>duration
|
||||||
|
in seconds to retrieve the forecast from the cache instead from the API</td></tr>
|
||||||
|
<tr><td>location</td><td><code><latitude,longitude></code><br>
|
||||||
|
geographic coordinates in degrees of the location for which the
|
||||||
|
weather is forecast; if missing, the values of the attributes
|
||||||
|
of the <code>global</code> device are taken, if these exist.</td></tr>
|
||||||
|
</ table>
|
||||||
|
<p><p>
|
||||||
|
|
||||||
|
<b>OpenWeatherMap</b><p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr><td>API</td><td><code>OpenWeatherMapAPI</code></td></tr>
|
||||||
|
<tr><td>apioptions</td><td><code>cachemaxage=<cachemaxage></code><br>duration
|
||||||
|
in seconds to retrieve the forecast from the cache instead from the API</td></tr>
|
||||||
|
<tr><td>location</td><td><code><latitude,longitude></code><br>
|
||||||
|
geographic coordinates in degrees of the location for which the
|
||||||
|
weather is forecast; if missing, the values of the attributes
|
||||||
|
of the <code>global</code> device are taken, if these exist.</td></tr>
|
||||||
|
</ table>
|
||||||
|
<p><p>
|
||||||
|
|
||||||
|
The module provides four additional functions <code>WeatherAsHtml</code>,
|
||||||
|
<code>WeatherAsHtmlV</code>, <code>WeatherAsHtmlH</code> and
|
||||||
|
<code>WeatherAsHtmlD</code>. The former two functions are identical:
|
||||||
|
they return the HTML code for a vertically arranged weather forecast.
|
||||||
|
The third function returns the HTML code for a horizontally arranged
|
||||||
|
weather forecast. The latter function dynamically picks the orientation
|
||||||
|
depending on wether a smallscreen style is set (vertical layout) or not
|
||||||
|
(horizontal layout). Each version accepts an additional paramter
|
||||||
|
to limit the numer of icons to display.<br><br>
|
||||||
Example:
|
Example:
|
||||||
<pre>
|
<pre>
|
||||||
define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather") }
|
define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather") }
|
||||||
@ -704,6 +714,7 @@ WeatherAsHtmlD($;$)
|
|||||||
Valid readings and their meaning (? can be one of 1, 2, 3, 4, 5 and stands
|
Valid readings and their meaning (? can be one of 1, 2, 3, 4, 5 and stands
|
||||||
for today, tomorrow, etc.):<br>
|
for today, tomorrow, etc.):<br>
|
||||||
<table>
|
<table>
|
||||||
|
<tr><td>.locense</td><td>license of the API provider, if available</td></tr>
|
||||||
<tr><td>city</td><td>name of town returned for location</td></tr>
|
<tr><td>city</td><td>name of town returned for location</td></tr>
|
||||||
<tr><td>code</td><td>current condition code</td></tr>
|
<tr><td>code</td><td>current condition code</td></tr>
|
||||||
<tr><td>condition</td><td>current condition</td></tr>
|
<tr><td>condition</td><td>current condition</td></tr>
|
||||||
@ -731,13 +742,9 @@ WeatherAsHtmlD($;$)
|
|||||||
<tr><td>wind_speed</td><td>same as wind</td></tr>
|
<tr><td>wind_speed</td><td>same as wind</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<br>
|
<br>
|
||||||
The following readings help to identify whether a workaround has kicked in to avoid the retrieval of
|
Depending on the chosen API, other readings can be shown as well.
|
||||||
stale data from the remote server:
|
The meaning of these readings can be determined from the API provider's
|
||||||
<table>
|
documentation.
|
||||||
<tr><td>pubDate</td><td>publication time of forecast for current set of readings</td></tr>
|
|
||||||
<tr><td>pubDateRemote</td><td>publication time of forecast as seen on remote server</td></tr>
|
|
||||||
<tr><td>validity</td><td>stale, if publication time as seen on remote server is before that of current set of readings</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
@ -759,37 +766,101 @@ WeatherAsHtmlD($;$)
|
|||||||
<a name="Weather"></a>
|
<a name="Weather"></a>
|
||||||
<h3>Weather</h3>
|
<h3>Weather</h3>
|
||||||
<ul>
|
<ul>
|
||||||
Es wird das Perl-Modul JSON benötigt. Mit <code>apt-get install libjson-perl</code> kann es unter Debian und Derivaten installiert werden.<br><br>
|
Hinweis: es wird das Perl-Modul JSON benötigt. Mit <code>apt-get install
|
||||||
|
libjson-perl</code> kann es unter Debian und Derivaten installiert
|
||||||
|
werden.<p><p>
|
||||||
|
|
||||||
|
Das Weather-Modul arbeitet mit verschiedenen Wetter-APIs zusammen:
|
||||||
|
<ul>
|
||||||
|
<li>DarkSky (<a href="https://darksky.net">Webseite</a>, Standard)</li>
|
||||||
|
<li>OpenWeatherMap (<a href="https://openweathermap.org/">Webseite)</a></li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
Eine solche virtuelle Wetterstation sammelt periodisch aktuelle Wetterdaten
|
||||||
|
und Wettervorhersagen aus dem verwendeten API.<br><br>
|
||||||
|
|
||||||
|
|
||||||
<a name="Weatherdefine"></a>
|
<a name="Weatherdefine"></a>
|
||||||
<b>Define</b>
|
<b>Define</b><br><br>
|
||||||
<ul>
|
<ul>
|
||||||
<code>define <name> Weather <location> [<interval> [<language>]]</code><br>
|
<code>define <name> Weather [API=<API>[,<apiotions>]] [apikey=<apikey>]
|
||||||
<br>
|
[location=<location>] [interval=<interval>] [lang=<lang>]</code><br><br>
|
||||||
Bezechnet ein virtuelles Gerät für Wettervorhersagen.<br><br>
|
|
||||||
|
|
||||||
Eine solche virtuelle Wetterstation sammelt periodisch aktuelle und zukünftige Wetterdaten aus der Yahoo-Wetter-API.<br><br>
|
Die Parameter haben die folgende Bedeutung:<br>
|
||||||
|
|
||||||
Der Parameter <code>location</code> entspricht der sechsstelligen WOEID (WHERE-ON-EARTH-ID). Die WOEID für den eigenen Standort kann auf <a href="http://weather.yahoo.com">http://weather.yahoo.com</a> gefunden werden.<br><br>
|
<table border="1">
|
||||||
|
<tr><td><code>API</code></td><td>Name des Wetter-APIs, z.B. <code>DarkSkyAPI</code></td></tr>
|
||||||
|
<tr><td><code>apioptions</code></td><td>Individuelle Optionen für das gewählte API</td></tr>
|
||||||
|
<tr><td><code>apikey</code></td><td>Schlüssel für das gewählte API</td></tr>
|
||||||
|
<tr><td><code>location</code></td><td>Ort, für den das Wetter vorhergesagt wird.
|
||||||
|
Abhängig vom API z.B. die Koordinaten, ein Ortsname oder eine ID.</td></tr>
|
||||||
|
<tr><td><code>interval</code></td><td>Dauer in Sekunden zwischen den einzelnen
|
||||||
|
Aktualisierungen der Wetterdaten</td></tr>
|
||||||
|
<tr><td><code>lang</code></td><td>Sprache der Wettervorhersage: <code>de</code>,
|
||||||
|
<code>en</code>, <code>pl</code>, <code>fr</code>, <code>it</code> oder <code>nl</code></td></tr>
|
||||||
|
</ table>
|
||||||
|
<p>
|
||||||
|
|
||||||
Der optionale Parameter <code>interval</code> gibt die Dauer in Sekunden zwischen den einzelnen Aktualisierungen der Wetterdaten an. Der Standardwert ist 3600 (1 Stunde). Wird kein Wert angegeben, gilt der Standardwert.<br><br>
|
Eine ganz einfache Definition ist:<br><br>
|
||||||
|
<code>define <name> Weather apikey=<DarkSkyAPISecretKey></code><br><br>
|
||||||
Der optionale Parameter für die möglichen Sprachen darf einen der folgende Werte annehmen: <code>de</code>, <code>en</code>, <code>pl</code>, <code>fr</code> oder <code>nl</code>. Er bezeichnet die natürliche Sprache, in der die Wetterinformationen dargestellt werden. Der Standardwert ist <code>en</code>. Wird für die Sprache kein Wert angegeben, gilt der Standardwert. Wird allerdings der Parameter für die Sprache gesetzt, muss ebenfalls ein Wert für das Abfrageintervall gesetzt werden.<br><br>
|
|
||||||
|
|
||||||
|
Bei dieser Definition wird die API von Dark Sky verwendet mit einem
|
||||||
|
individuellen Schlüssel, den man sich auf der Webseite von Dark Sky
|
||||||
|
beschaffen muss.<p><p>
|
||||||
|
|
||||||
Beispiele:
|
Beispiele:
|
||||||
<pre>
|
<pre>
|
||||||
define MyWeather Weather 673513
|
define Forecast Weather apikey=987498ghjgf864
|
||||||
define Forecast Weather 673513 1800
|
define MyWeather Weather api=OpenWeatherMapAPI,cachemaxage:600 apikey=09878945fdskv876 location=52.4545,13.4545 interval=3600 language=de
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
Das Modul unterstützt zusätzlich vier verschiedene Funktionen <code>WeatherAsHtml</code>, <code>WeatherAsHtmlV</code>, <code>WeatherAsHtmlH</code> und <code>WeatherAsHtmlD</code>. Die ersten beiden Funktionen sind identisch: sie erzeugen den HTML-Code für eine vertikale Darstellung des Wetterberichtes. Die dritte Funktion liefert den HTML-Code für eine horizontale Darstellung des Wetterberichtes. Die letztgenannte Funktion wählt automatisch eine Ausrichtung, die abhängig davon ist, ob ein Smallcreen Style ausgewählt ist (vertikale Darstellung) oder nicht (horizontale Darstellung). Alle vier Funnktionen akzeptieren einen zusätzlichen optionalen Paramter um die Anzahl der darzustellenden Icons anzugeben.<br><br>
|
Es folgt die API-spezifische Dokumentation.<p>
|
||||||
|
|
||||||
|
<b>Dark Sky</b><p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr><td>API</td><td><code>DarkSkyAPI</code></td></tr>
|
||||||
|
<tr><td>apioptions</td><td><code>cachemaxage=<cachemaxage></code><br>Zeitdauer in
|
||||||
|
Sekunden, innerhalb derer die Wettervorhersage nicht neu abgerufen
|
||||||
|
sondern aus dem Cache zurück geliefert wird.</td></tr>
|
||||||
|
<tr><td>location</td><td><code><latitude,longitude></code><br> Geographische Breite
|
||||||
|
und Länge des Ortes in Grad, für den das Wetter vorhergesagt wird.
|
||||||
|
Bei fehlender Angabe werden die Werte aus den gleichnamigen Attributen
|
||||||
|
des <code>global</code>-Device genommen, sofern vorhanden.</td></tr>
|
||||||
|
</ table>
|
||||||
|
<p><p>
|
||||||
|
|
||||||
|
<b>OpenWeatherMap</b><p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr><td>API</td><td><code>OpenWeatherMapAPI</code></td></tr>
|
||||||
|
<tr><td>apioptions</td><td><code>cachemaxage=<cachemaxage></code> Zeitdauer in
|
||||||
|
Sekunden, innerhalb derer die Wettervorhersage nicht neu abgerufen
|
||||||
|
sondern aus dem Cache zurück geliefert wird.</td></tr>
|
||||||
|
<tr><td>location</td><td><code><latitude,longitude></code> Geographische Breite
|
||||||
|
und Länge des Ortes in Grad, für den das Wetter vorhergesagt wird.
|
||||||
|
Bei fehlender Angabe werden die Werte aus den gleichnamigen Attributen
|
||||||
|
des <code>global</code>-Device genommen, sofern vorhanden.</td></tr>
|
||||||
|
</ table>
|
||||||
|
<p><p>
|
||||||
|
|
||||||
|
Das Modul unterstützt zusätzlich vier verschiedene Funktionen
|
||||||
|
<code>WeatherAsHtml</code>, <code>WeatherAsHtmlV</code>,
|
||||||
|
<code>WeatherAsHtmlH</code> und <code>WeatherAsHtmlD</code>.
|
||||||
|
Die ersten beiden Funktionen sind identisch: sie erzeugen
|
||||||
|
den HTML-Kode für eine vertikale Darstellung des Wetterberichtes.
|
||||||
|
Die dritte Funktion liefert den HTML-Code für eine horizontale
|
||||||
|
Darstellung des Wetterberichtes. Die letztgenannte Funktion wählt
|
||||||
|
automatisch eine Ausrichtung, die abhängig davon ist, ob ein
|
||||||
|
Smallcreen Style ausgewählt ist (vertikale Darstellung) oder
|
||||||
|
nicht (horizontale Darstellung). Alle vier Funnktionen akzeptieren
|
||||||
|
einen zusätzlichen optionalen Paramter um die Anzahl der
|
||||||
|
darzustellenden Icons anzugeben.<br><br>
|
||||||
Beispiel:
|
Beispiel:
|
||||||
<pre>
|
<pre>
|
||||||
define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather") }
|
define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather") }
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@ -798,7 +869,9 @@ WeatherAsHtmlD($;$)
|
|||||||
<ul>
|
<ul>
|
||||||
<code>set <name> update</code><br><br>
|
<code>set <name> update</code><br><br>
|
||||||
|
|
||||||
Erzwingt eine Abfrage der Wetterdaten. Die darauffolgende Abfrage wird gemäß dem eingestellten Intervall <code>interval</code> Sekunden später durchgeführt.<br><br>
|
Erzwingt eine Abfrage der Wetterdaten. Die darauffolgende Abfrage
|
||||||
|
wird gemäß dem eingestellten
|
||||||
|
Intervall <code>interval</code> Sekunden später durchgeführt.<br><br>
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@ -807,50 +880,44 @@ WeatherAsHtmlD($;$)
|
|||||||
<ul>
|
<ul>
|
||||||
<code>get <name> <reading></code><br><br>
|
<code>get <name> <reading></code><br><br>
|
||||||
|
|
||||||
Gültige ausgelesene Daten (readings) und ihre Bedeutung (das ? kann einen der Werte 1, 2, 3 , 4 oder 5 annehmen und steht für heute, morgen, übermorgen etc.):<br><br>
|
Gültige ausgelesene Daten (readings) und ihre Bedeutung (das ? kann einen der Werte 1, 2, 3 , 4 oder 5 annehmen und steht für heute, morgen, übermorgen etc.):<br><br>
|
||||||
<table>
|
<table>
|
||||||
<tr><td>city</td><td>Name der Stadt, der aufgrund der WOEID übermittelt wird</td></tr>
|
<tr><td>.license</td><td>Lizenz des jeweiligen API-Anbieters, sofern vorhanden</td></tr>
|
||||||
<tr><td>code</td><td>Code für die aktuellen Wetterverhältnisse</td></tr>
|
<tr><td>city</td><td>Name der Stadt, der für die location übermittelt wird</td></tr>
|
||||||
<tr><td>condition</td><td>aktuelle Wetterverhältnisse</td></tr>
|
<tr><td>code</td><td>Code für die aktuellen Wetterverhältnisse</td></tr>
|
||||||
|
<tr><td>condition</td><td>aktuelle Wetterverhältnisse</td></tr>
|
||||||
<tr><td>current_date_time</td><td>Zeitstempel der letzten Aktualisierung der Wetterdaten vom Server</td></tr>
|
<tr><td>current_date_time</td><td>Zeitstempel der letzten Aktualisierung der Wetterdaten vom Server</td></tr>
|
||||||
<tr><td>fc?_code</td><td>Code für die vorhergesagten Wetterverhältnisse</td></tr>
|
<tr><td>fc?_code</td><td>Code für die vorhergesagten Wetterverhältnisse</td></tr>
|
||||||
<tr><td>fc?_condition</td><td>vorhergesagte Wetterverhältnisse</td></tr>
|
<tr><td>fc?_condition</td><td>vorhergesagte Wetterverhältnisse</td></tr>
|
||||||
<tr><td>fc?_day_of_week</td><td>Wochentag des Tages, der durch ? dargestellt wird</td></tr>
|
<tr><td>fc?_day_of_week</td><td>Wochentag des Tages, der durch ? dargestellt wird</td></tr>
|
||||||
<tr><td>fc?_high_c</td><td>vorhergesagte maximale Tagestemperatur in Grad Celsius</td></tr>
|
<tr><td>fc?_high_c</td><td>vorhergesagte maximale Tagestemperatur in Grad Celsius</td></tr>
|
||||||
<tr><td>fc?_icon</td><td>Icon für Vorhersage</td></tr>
|
<tr><td>fc?_icon</td><td>Icon für Vorhersage</td></tr>
|
||||||
<tr><td>fc?_low_c</td><td>vorhergesagte niedrigste Tagestemperatur in Grad Celsius</td></tr>
|
<tr><td>fc?_low_c</td><td>vorhergesagte niedrigste Tagestemperatur in Grad Celsius</td></tr>
|
||||||
<tr><td>humidity</td><td>gegenwärtige Luftfeuchtgkeit in %</td></tr>
|
<tr><td>humidity</td><td>gegenwärtige Luftfeuchtgkeit in %</td></tr>
|
||||||
<tr><td>icon</td><td>relativer Pfad für das aktuelle Icon</td></tr>
|
<tr><td>icon</td><td>relativer Pfad für das aktuelle Icon</td></tr>
|
||||||
<tr><td>pressure</td><td>Luftdruck in hPa</td></tr>
|
<tr><td>pressure</td><td>Luftdruck in hPa</td></tr>
|
||||||
<tr><td>pressure_trend</td><td>Luftdrucktendenz (0= gleichbleibend, 1= steigend, 2= fallend)</td></tr>
|
<tr><td>temperature</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
|
||||||
<tr><td>pressure_trend_txt</td><td>textliche Darstellung der Luftdrucktendenz</td></tr>
|
<tr><td>temp_c</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
|
||||||
<tr><td>pressure_trend_sym</td><td>symbolische Darstellung der Luftdrucktendenz</td></tr>
|
<tr><td>temp_f</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
|
||||||
<tr><td>temperature</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
|
|
||||||
<tr><td>temp_c</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
|
|
||||||
<tr><td>temp_f</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
|
|
||||||
<tr><td>visibility</td><td>Sichtweite in km</td></tr>
|
<tr><td>visibility</td><td>Sichtweite in km</td></tr>
|
||||||
<tr><td>wind</td><td>Windgeschwindigkeit in km/h</td></tr>
|
<tr><td>wind</td><td>Windgeschwindigkeit in km/h</td></tr>
|
||||||
<tr><td>wind_chill</td><td>gefühlte Temperatur in Grad Celsius</td></tr>
|
|
||||||
<tr><td>wind_condition</td><td>Windrichtung und -geschwindigkeit</td></tr>
|
<tr><td>wind_condition</td><td>Windrichtung und -geschwindigkeit</td></tr>
|
||||||
<tr><td>wind_direction</td><td>Gradangabe der Windrichtung (0 = Nordwind)</td></tr>
|
<tr><td>wind_direction</td><td>Gradangabe der Windrichtung (0 = Nordwind)</td></tr>
|
||||||
<tr><td>wind_speed</td><td>Windgeschwindigkeit in km/h (mit wind identisch)</td></tr>
|
<tr><td>wind_speed</td><td>Windgeschwindigkeit in km/h (mit wind identisch)</td></tr>
|
||||||
</table>
|
|
||||||
<br>
|
|
||||||
Die folgenden Daten helfen zu identifizieren, ob ein Workaround angeschlagen hat, der die Verwendung von
|
|
||||||
veralteten Daten auf dem entfernten Server verhindert:
|
|
||||||
<table>
|
|
||||||
<tr><td>pubDate</td><td>Veröffentlichungszeitpunkt der Wettervorhersage in den aktuellen Daten (readings)</td></tr>
|
|
||||||
<tr><td>pubDateRemote</td><td>Veröffentlichungszeitpunkt der Wettervorhersage auf dem entfernten Server</td></tr>
|
|
||||||
<tr><td>validity</td><td>stale, wenn der Veröffentlichungszeitpunkt auf dem entfernten Server vor dem Zeitpunkt der aktuellen Daten (readings) liegt</td></tr>
|
<tr><td>validity</td><td>stale, wenn der Veröffentlichungszeitpunkt auf dem entfernten Server vor dem Zeitpunkt der aktuellen Daten (readings) liegt</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<br>
|
||||||
|
Je nach verwendeter API ist es durchaus möglich, dass weitere
|
||||||
|
Readings geschrieben werden. Die Bedeutung dieser Readings kann man
|
||||||
|
der API-Beschreibung des Anbieters entnehmen.
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<a name="Weatherattr"></a>
|
<a name="Weatherattr"></a>
|
||||||
<b>Attribute</b>
|
<b>Attribute</b>
|
||||||
<ul>
|
<ul>
|
||||||
<li>disable: stellt die Abfrage der Wetterdaten ab - der Timer läft gemäß Plan doch es werden keine Daten vom
|
<li>disable: stellt die Abfrage der Wetterdaten ab - der Timer läft
|
||||||
|
gemäß Plan doch es werden keine Daten vom
|
||||||
API angefordert.</li>
|
API angefordert.</li>
|
||||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
524
fhem/FHEM/DarkSkyAPI.pm
Normal file
524
fhem/FHEM/DarkSkyAPI.pm
Normal file
@ -0,0 +1,524 @@
|
|||||||
|
# $Id$
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
package DarkSkyAPI::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.darksky.net/forecast/';
|
||||||
|
|
||||||
|
my %codes = (
|
||||||
|
'clear-day' => 32,
|
||||||
|
'clear-night' => 31,
|
||||||
|
'rain' => 11,
|
||||||
|
'snow' => 16,
|
||||||
|
'sleet' => 18,
|
||||||
|
'wind' => 24,
|
||||||
|
'fog' => 20,
|
||||||
|
'cloudy' => 26,
|
||||||
|
'partly-cloudy-day' => 30,
|
||||||
|
'partly-cloudy-night' => 29,
|
||||||
|
'hail' => 17,
|
||||||
|
'thunderstorm' => 4,
|
||||||
|
'tornado' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
### geliefert wird ein Hash
|
||||||
|
my ( $class, $argsRef ) = @_;
|
||||||
|
|
||||||
|
my $self = {
|
||||||
|
devName => $argsRef->{devName},
|
||||||
|
key => (
|
||||||
|
( defined( $argsRef->{apikey} ) and $argsRef->{apikey} )
|
||||||
|
? $argsRef->{apikey}
|
||||||
|
: 'none'
|
||||||
|
),
|
||||||
|
cachemaxage => (
|
||||||
|
( defined( $argsRef->{apioptions} ) and $argsRef->{apioptions} )
|
||||||
|
? (
|
||||||
|
( split( ':', $argsRef->{apioptions} ) )[0] eq 'cachemaxage'
|
||||||
|
? ( split( ':', $argsRef->{apioptions} ) )[1]
|
||||||
|
: 900
|
||||||
|
)
|
||||||
|
: 900
|
||||||
|
),
|
||||||
|
lang => $argsRef->{language},
|
||||||
|
lat => ( split( ',', $argsRef->{location} ) )[0],
|
||||||
|
long => ( split( ',', $argsRef->{location} ) )[1],
|
||||||
|
fetchTime => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{cached} = _CreateForecastRef($self);
|
||||||
|
|
||||||
|
bless $self, $class;
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setFetchTime {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
$self->{fetchTime} = time();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setRetrieveData {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
_RetrieveDataFromDarkSky($self);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getFetchTime {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
return $self->{fetchTime};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getWeather {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
return $self->{cached};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _RetrieveDataFromDarkSky($) {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
# retrieve data from cache
|
||||||
|
if ( ( time() - $self->{fetchTime} ) < $self->{cachemaxage} ) {
|
||||||
|
return _CallWeatherCallbackFn($self);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $paramRef = {
|
||||||
|
timeout => 15,
|
||||||
|
self => $self,
|
||||||
|
callback => \&_RetrieveDataFinished,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( $self->{lat} eq 'error'
|
||||||
|
or $self->{long} eq 'error'
|
||||||
|
or $self->{key} eq 'none'
|
||||||
|
or $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' or $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->{key} . '/'
|
||||||
|
. $self->{lat} . ','
|
||||||
|
. $self->{long}
|
||||||
|
. '?lang='
|
||||||
|
. $self->{lang}
|
||||||
|
. '&units=auto';
|
||||||
|
|
||||||
|
main::HttpUtils_NonblockingGet($paramRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _RetrieveDataFinished($$$) {
|
||||||
|
my ( $paramRef, $err, $response ) = @_;
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _ProcessingRetrieveData($$) {
|
||||||
|
my ( $self, $response ) = @_;
|
||||||
|
|
||||||
|
if ( $self->{cached}->{status} eq 'ok'
|
||||||
|
and defined($response)
|
||||||
|
and $response )
|
||||||
|
{
|
||||||
|
my $data = eval { decode_json($response) };
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
_ErrorHandling( $self, 'DarkSky Weather 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 $data; ## für Debugging
|
||||||
|
|
||||||
|
$self->{cached}->{current_date_time} =
|
||||||
|
strftime( "%a, %e %b %Y %H:%M %p",
|
||||||
|
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} ) + 0.5
|
||||||
|
),
|
||||||
|
'wind_speed' => int(
|
||||||
|
sprintf( "%.1f", $data->{currently}->{windSpeed} ) + 0.5
|
||||||
|
),
|
||||||
|
'wind_direction' => $data->{currently}->{windBearing},
|
||||||
|
'windGust' => int(
|
||||||
|
sprintf( "%.1f", $data->{currently}->{windGust} ) + 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' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime( $data->{currently}->{'time'} )
|
||||||
|
),
|
||||||
|
'precipProbability' => $data->{currently}->{precipProbability},
|
||||||
|
'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' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
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
|
||||||
|
),
|
||||||
|
'tempMin' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureMin} ) + 0.5
|
||||||
|
),
|
||||||
|
'tempMinTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureMinTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'tempMax' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureMax} ) + 0.5
|
||||||
|
),
|
||||||
|
'tempMaxTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureMaxTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'tempLow' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureLow} ) + 0.5
|
||||||
|
),
|
||||||
|
'tempLowTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureLowTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'tempHigh' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureHigh} ) + 0.5
|
||||||
|
),
|
||||||
|
'tempHighTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{temperatureHighTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'apparentTempLow' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureLow} ) + 0.5
|
||||||
|
),
|
||||||
|
'apparentTempLowTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureLowTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'apparentTempHigh' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureHigh} ) + 0.5
|
||||||
|
),
|
||||||
|
'apparentTempHighTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureHighTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'apparenttempMin' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureMin} ) + 0.5
|
||||||
|
),
|
||||||
|
'apparenttempMinTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureMinTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'apparenttempMax' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureMax} ) + 0.5
|
||||||
|
),
|
||||||
|
'apparenttempMaxTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{apparentTemperatureMaxTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'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' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]->{uvIndexTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'precipIntensity' =>
|
||||||
|
$data->{daily}->{data}->[$i]->{precipIntensity},
|
||||||
|
'precipIntensityMax' =>
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{precipIntensityMax},
|
||||||
|
'precipIntensityMaxTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]
|
||||||
|
->{precipIntensityMaxTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'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,
|
||||||
|
'precipType' =>
|
||||||
|
$data->{daily}->{data}->[$i]->{precipType},
|
||||||
|
|
||||||
|
'wind_direction' =>
|
||||||
|
$data->{daily}->{data}->[$i]->{windBearing},
|
||||||
|
'wind' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]->{windSpeed} )
|
||||||
|
+ 0.5
|
||||||
|
),
|
||||||
|
'wind_speed' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]->{windSpeed} )
|
||||||
|
+ 0.5
|
||||||
|
),
|
||||||
|
'windGust' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]->{windGust} )
|
||||||
|
+ 0.5
|
||||||
|
),
|
||||||
|
'windGustTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]->{windGustTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'moonPhase' =>
|
||||||
|
$data->{daily}->{data}->[$i]->{moonPhase},
|
||||||
|
'sunsetTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]->{sunsetTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'sunriseTime' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
$data->{daily}->{data}->[$i]->{sunriseTime}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
'precipProbability' =>
|
||||||
|
$data->{daily}->{data}->[$i]->{precipProbability},
|
||||||
|
'pressure' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]->{pressure} )
|
||||||
|
+ 0.5
|
||||||
|
),
|
||||||
|
'visibility' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
$data->{daily}->{data}->[$i]->{visibility} )
|
||||||
|
+ 0.5
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## 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} =
|
||||||
|
strftime( "%a, %e %b %Y %H:%M %p", 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 =>
|
||||||
|
'Leon Gaultier (<a href=https://forum.fhem.de/index.php?action=profile;u=13684>CoolTux</a>)',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $forecastRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
1;
|
484
fhem/FHEM/OpenWeatherMapAPI.pm
Normal file
484
fhem/FHEM/OpenWeatherMapAPI.pm
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
# $Id$
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
### 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://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
|
||||||
|
## URL . 'forecast?' for 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,
|
||||||
|
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, $argsRef ) = @_;
|
||||||
|
|
||||||
|
my $self = {
|
||||||
|
devName => $argsRef->{devName},
|
||||||
|
key => (
|
||||||
|
( defined( $argsRef->{apikey} ) and $argsRef->{apikey} )
|
||||||
|
? $argsRef->{apikey}
|
||||||
|
: 'none'
|
||||||
|
),
|
||||||
|
cachemaxage => (
|
||||||
|
( defined( $argsRef->{apioptions} ) and $argsRef->{apioptions} )
|
||||||
|
? (
|
||||||
|
( split( ':', $argsRef->{apioptions} ) )[0] eq 'cachemaxage'
|
||||||
|
? ( split( ':', $argsRef->{apioptions} ) )[1]
|
||||||
|
: 900
|
||||||
|
)
|
||||||
|
: 900
|
||||||
|
),
|
||||||
|
lang => $argsRef->{language},
|
||||||
|
lat => ( split( ',', $argsRef->{location} ) )[0],
|
||||||
|
long => ( split( ',', $argsRef->{location} ) )[1],
|
||||||
|
fetchTime => 0,
|
||||||
|
endpoint => 'none',
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{cached} = _CreateForecastRef($self);
|
||||||
|
|
||||||
|
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
|
||||||
|
if ( $self->{endpoint} eq 'none' ) {
|
||||||
|
if ( ( time() - $self->{fetchTime} ) < $self->{cachemaxage} ) {
|
||||||
|
return _CallWeatherCallbackFn($self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $paramRef = {
|
||||||
|
timeout => 15,
|
||||||
|
self => $self,
|
||||||
|
endpoint => ( $self->{endpoint} eq 'none' ? 'weather' : 'forecast' ),
|
||||||
|
callback => \&_RetrieveDataFinished,
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{endpoint} = $paramRef->{endpoint};
|
||||||
|
|
||||||
|
if ( $self->{lat} eq 'error'
|
||||||
|
or $self->{long} eq 'error'
|
||||||
|
or $self->{key} eq 'none'
|
||||||
|
or $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' or $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
|
||||||
|
. $paramRef->{endpoint} . '?' . 'lat='
|
||||||
|
. $self->{lat} . '&' . 'lon='
|
||||||
|
. $self->{long} . '&'
|
||||||
|
. 'APPID='
|
||||||
|
. $self->{key} . '&' . 'lang='
|
||||||
|
. $self->{lang};
|
||||||
|
|
||||||
|
main::HttpUtils_NonblockingGet($paramRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _RetrieveDataFinished($$$) {
|
||||||
|
my ( $paramRef, $err, $response ) = @_;
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _ProcessingRetrieveData($$) {
|
||||||
|
my ( $self, $response ) = @_;
|
||||||
|
|
||||||
|
if ( $self->{cached}->{status} eq 'ok'
|
||||||
|
and defined($response)
|
||||||
|
and $response )
|
||||||
|
{
|
||||||
|
my $data = eval { decode_json($response) };
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
_ErrorHandling( $self,
|
||||||
|
'OpenWeatherMap Weather decode JSON err ' . $@ );
|
||||||
|
}
|
||||||
|
elsif ( defined( $data->{cod} )
|
||||||
|
and $data->{cod}
|
||||||
|
and $data->{cod} != 200
|
||||||
|
and defined( $data->{message} )
|
||||||
|
and $data->{message} )
|
||||||
|
{
|
||||||
|
_ErrorHandling( $self, $data->{cod} . ': ' . $data->{message} );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
###### Ab hier wird die ResponseHash Referenze für die Rückgabe zusammen gestellt
|
||||||
|
$self->{cached}->{current_date_time} =
|
||||||
|
strftime( "%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime( $self->{fetchTime} ) );
|
||||||
|
|
||||||
|
if ( $self->{endpoint} eq 'weather' ) {
|
||||||
|
$self->{cached}->{country} = $data->{sys}->{country};
|
||||||
|
$self->{cached}->{city} = $data->{name};
|
||||||
|
$self->{cached}->{license}{text} = 'none';
|
||||||
|
$self->{cached}->{current} = {
|
||||||
|
'temperature' => int(
|
||||||
|
sprintf( "%.1f", ( $data->{main}->{temp} - 273.15 ) ) +
|
||||||
|
0.5
|
||||||
|
),
|
||||||
|
'temp_c' => int(
|
||||||
|
sprintf( "%.1f", ( $data->{main}->{temp} - 273.15 ) ) +
|
||||||
|
0.5
|
||||||
|
),
|
||||||
|
'low_c' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
( $data->{main}->{temp_min} - 273.15 ) ) + 0.5
|
||||||
|
),
|
||||||
|
'high_c' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
( $data->{main}->{temp_max} - 273.15 ) ) + 0.5
|
||||||
|
),
|
||||||
|
'tempLow' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
( $data->{main}->{temp_min} - 273.15 ) ) + 0.5
|
||||||
|
),
|
||||||
|
'tempHigh' => int(
|
||||||
|
sprintf( "%.1f",
|
||||||
|
( $data->{main}->{temp_max} - 273.15 ) ) + 0.5
|
||||||
|
),
|
||||||
|
'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} ) + 0.5 ),
|
||||||
|
'wind_speed' =>
|
||||||
|
int( sprintf( "%.1f", $data->{wind}->{speed} ) + 0.5 ),
|
||||||
|
'wind_direction' => $data->{wind}->{deg},
|
||||||
|
'cloudCover' => $data->{clouds}->{all},
|
||||||
|
'visibility' =>
|
||||||
|
int( sprintf( "%.1f", $data->{visibility} ) + 0.5 ),
|
||||||
|
'code' => $codes{ $data->{weather}->[0]->{id} },
|
||||||
|
'iconAPI' => $data->{weather}->[0]->{icon},
|
||||||
|
'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} )
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $self->{endpoint} eq 'forecast' ) {
|
||||||
|
if ( ref( $data->{list} ) eq "ARRAY"
|
||||||
|
and scalar( @{ $data->{list} } ) > 0 )
|
||||||
|
{
|
||||||
|
## löschen des alten Datensatzes
|
||||||
|
delete $self->{cached}->{forecast};
|
||||||
|
|
||||||
|
my $i = 0;
|
||||||
|
foreach ( @{ $data->{list} } ) {
|
||||||
|
push(
|
||||||
|
@{ $self->{cached}->{forecast}->{hourly} },
|
||||||
|
{
|
||||||
|
'pubDate' => strftime(
|
||||||
|
"%a, %e %b %Y %H:%M %p",
|
||||||
|
localtime(
|
||||||
|
( $data->{list}->[$i]->{dt} ) - 3600
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'day_of_week' => strftime(
|
||||||
|
"%a",
|
||||||
|
localtime(
|
||||||
|
( $data->{list}->[$i]->{dt} ) - 3600
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'temperature' => int(
|
||||||
|
sprintf(
|
||||||
|
"%.1f",
|
||||||
|
(
|
||||||
|
$data->{list}->[$i]->{main}->{temp}
|
||||||
|
- 273.15
|
||||||
|
)
|
||||||
|
) + 0.5
|
||||||
|
),
|
||||||
|
'temp_c' => int(
|
||||||
|
sprintf(
|
||||||
|
"%.1f",
|
||||||
|
(
|
||||||
|
$data->{list}->[$i]->{main}->{temp}
|
||||||
|
- 273.15
|
||||||
|
)
|
||||||
|
) + 0.5
|
||||||
|
),
|
||||||
|
'low_c' => int(
|
||||||
|
sprintf(
|
||||||
|
"%.1f",
|
||||||
|
(
|
||||||
|
$data->{list}->[$i]->{main}
|
||||||
|
->{temp_min} - 273.15
|
||||||
|
)
|
||||||
|
) + 0.5
|
||||||
|
),
|
||||||
|
'high_c' => int(
|
||||||
|
sprintf(
|
||||||
|
"%.1f",
|
||||||
|
(
|
||||||
|
$data->{list}->[$i]->{main}
|
||||||
|
->{temp_max} - 273.15
|
||||||
|
)
|
||||||
|
) + 0.5
|
||||||
|
),
|
||||||
|
'tempLow' => int(
|
||||||
|
sprintf(
|
||||||
|
"%.1f",
|
||||||
|
(
|
||||||
|
$data->{list}->[$i]->{main}
|
||||||
|
->{temp_min} - 273.15
|
||||||
|
)
|
||||||
|
) + 0.5
|
||||||
|
),
|
||||||
|
'tempHigh' => int(
|
||||||
|
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
|
||||||
|
),
|
||||||
|
'cloudCover' =>
|
||||||
|
$data->{list}->[$i]->{clouds}->{all},
|
||||||
|
'code' =>
|
||||||
|
$codes{ $data->{list}->[$i]->{weather}->[0]
|
||||||
|
->{id} },
|
||||||
|
'iconAPI' =>
|
||||||
|
$data->{list}->[$i]->{weather}->[0]->{icon},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{endpoint} = 'none' if ( $self->{endpoint} eq 'forecast' );
|
||||||
|
|
||||||
|
_RetrieveDataFromOpenWeatherMap($self)
|
||||||
|
if ( $self->{endpoint} eq 'weather' );
|
||||||
|
|
||||||
|
_CallWeatherCallbackFn($self) if ( $self->{endpoint} eq 'none' );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _CallWeatherCallbackFn($) {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
# print 'Dumperausgabe: ' . Dumper $self;
|
||||||
|
### Aufruf der callbackFn
|
||||||
|
main::Weather_RetrieveCallbackFn( $self->{devName} );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _ErrorHandling($$) {
|
||||||
|
my ( $self, $err ) = @_;
|
||||||
|
|
||||||
|
$self->{cached}->{current_date_time} =
|
||||||
|
strftime( "%a, %e %b %Y %H:%M %p", 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 =>
|
||||||
|
'Leon Gaultier (<a href=https://forum.fhem.de/index.php?action=profile;u=13684>CoolTux</a>)',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $forecastRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
1;
|
@ -538,7 +538,9 @@ FHEM/TimeSeries.pm neubert /jensb FHEM Development
|
|||||||
FHEM/TR064Utils.pm rudolfkoenig Automatisierung
|
FHEM/TR064Utils.pm rudolfkoenig Automatisierung
|
||||||
FHEM/UConv.pm loredo FHEM Development
|
FHEM/UConv.pm loredo FHEM Development
|
||||||
FHEM/Unit.pm loredo FHEM Development
|
FHEM/Unit.pm loredo FHEM Development
|
||||||
FHEM/YahooWeatherAPI.pm neubert Unterstuetzende Dienste/Wettermodule
|
FHEM/DarkSkyAPI.pm CoolTux Unterstuetzende Dienste/Wettermodule
|
||||||
|
FHEM/OpenWeatherAPI.pm CoolTux Unterstuetzende Dienste/Wettermodule
|
||||||
|
FHEM/YahooWeatherAPI.pm neubert (deprecated)
|
||||||
FHEM/lib/Device/Firmata/* jensb Sonstige Systeme
|
FHEM/lib/Device/Firmata/* jensb Sonstige Systeme
|
||||||
FHEM/lib/Device/MySensors/* Hauswart/Beta-User Bastelecke/MySensors
|
FHEM/lib/Device/MySensors/* Hauswart/Beta-User Bastelecke/MySensors
|
||||||
FHEM/lib/MP3/* Reinerlein Multimedia
|
FHEM/lib/MP3/* Reinerlein Multimedia
|
||||||
|
Loading…
x
Reference in New Issue
Block a user