# $Id: 59_Weather.pm 16644 2018-04-22 08:07:35Z neubert $ ############################################################################## # # 59_Weather.pm # Copyright by Dr. Boris Neubert # e-mail: omega at online dot de # # Contributors: # - Marko Oldenburg (CoolTux) # - Lippie # # # This file is part of fhem. # # Fhem 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 # (at your option) any later version. # # Fhem 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. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # ############################################################################## package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); use HttpUtils; 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_de = ( 0 => "gleichbleibend", 1 => "steigend", 2 => "fallend" ); my %pressure_trend_txt_nl = ( 0 => "stabiel", 1 => "stijgend", 2 => "dalend" ); my %pressure_trend_txt_fr = ( 0 => "stable", 1 => "croissant", 2 => "décroissant" ); my %pressure_trend_txt_pl = ( 0 => "stabilne", 1 => "rośnie", 2 => "spada" ); my %pressure_trend_txt_it = ( 0 => "stabile", 1 => "in aumento", 2 => "in diminuzione" ); my %pressure_trend_sym = ( 0 => "=", 1 => "+", 2 => "-" ); my @directions_txt_en = ( 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW' ); my @directions_txt_de = ( 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW' ); my @directions_txt_nl = ( 'N', 'NNO', 'NO', 'ONO', 'O', 'OZO', 'ZO', 'ZZO', 'Z', 'ZZW', 'ZW', 'WZW', 'W', 'WNW', 'NW', 'NNW' ); my @directions_txt_fr = ( 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO' ); my @directions_txt_pl = ( 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW' ); my @directions_txt_it = ( 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO' ); my %wdays_txt_en = ( 'Mon' => 'Mon', 'Tue' => 'Tue', 'Wed' => 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun' ); my %wdays_txt_de = ( 'Mon' => 'Mo', 'Tue' => 'Di', 'Wed' => 'Mi', 'Thu' => 'Do', 'Fri' => 'Fr', 'Sat' => 'Sa', 'Sun' => 'So' ); my %wdays_txt_nl = ( 'Mon' => 'Ma', 'Tue' => 'Di', 'Wed' => 'Wo', 'Thu' => 'Do', 'Fri' => 'Vr', 'Sat' => 'Za', 'Sun' => 'Zo' ); my %wdays_txt_fr = ( 'Mon' => 'Lun', 'Tue' => 'Mar', 'Wed' => 'Mer', 'Thu' => 'Jeu', 'Fri' => 'Ven', 'Sat' => 'Sam', 'Sun' => 'Dim' ); my %wdays_txt_pl = ( 'Mon' => 'Pon', 'Tue' => 'Wt', 'Wed' => 'Śr', 'Thu' => 'Czw', 'Fri' => 'Pt', 'Sat' => 'Sob', 'Sun' => 'Nie' ); my %wdays_txt_it = ( 'Mon' => 'Lun', 'Tue' => 'Mar', 'Wed' => 'Mer', 'Thu' => 'Gio', 'Fri' => 'Ven', 'Sat' => 'Sab', 'Sun' => 'Dom' ); my %status_items_txt_en = ( 0 => "Wind", 1 => "Humidity", 2 => "Temperature", 3 => "Right Now", 4 => "Weather forecast for " ); my %status_items_txt_de = ( 0 => "Wind", 1 => "Feuchtigkeit", 2 => "Temperatur", 3 => "Jetzt Sofort", 4 => "Wettervorhersage für " ); my %status_items_txt_nl = ( 0 => "Wind", 1 => "Vochtigheid", 2 => "Temperatuur", 3 => "Actueel", 4 => "Weersvoorspelling voor " ); my %status_items_txt_fr = ( 0 => "Vent", 1 => "Humidité", 2 => "Température", 3 => "Maintenant", 4 => "Prévisions météo pour " ); my %status_items_txt_pl = ( 0 => "Wiatr", 1 => "Wilgotność", 2 => "Temperatura", 3 => "Teraz", 4 => "Prognoza pogody w " ); my %status_items_txt_it = ( 0 => "Vento", 1 => "Umidità", 2 => "Temperatura", 3 => "Adesso", 4 => "Previsioni del tempo per " ); my %wdays_txt_i18n; my @directions_txt_i18n; my %pressure_trend_txt_i18n; my %status_items_txt_i18n; my @iconlist = ( 'storm', 'storm', 'storm', 'thunderstorm', 'thunderstorm', 'rainsnow', 'sleet', 'snow', 'drizzle', 'drizzle', 'icy', 'chance_of_rain', 'chance_of_rain', 'snowflurries', 'chance_of_snow', 'heavysnow', 'snow', 'sleet', 'sleet', 'dust', 'fog', 'haze', 'smoke', 'flurries', 'windy', 'icy', 'cloudy', 'mostlycloudy_night', 'mostlycloudy', 'partly_cloudy_night', 'partly_cloudy', 'sunny', 'sunny', 'mostly_clear_night', 'mostly_sunny', 'heavyrain', 'sunny', 'scatteredthunderstorms', 'scatteredthunderstorms', 'scatteredthunderstorms', 'scatteredshowers', 'heavysnow', 'chance_of_snow', 'heavysnow', 'partly_cloudy', 'heavyrain', 'chance_of_snow', 'scatteredshowers' ); ################################### sub Weather_LanguageInitialize($) { my ($lang) = @_; if ( $lang eq "de" ) { %wdays_txt_i18n = %wdays_txt_de; @directions_txt_i18n = @directions_txt_de; %pressure_trend_txt_i18n = %pressure_trend_txt_de; %status_items_txt_i18n = %status_items_txt_de; } elsif ( $lang eq "nl" ) { %wdays_txt_i18n = %wdays_txt_nl; @directions_txt_i18n = @directions_txt_nl; %pressure_trend_txt_i18n = %pressure_trend_txt_nl; %status_items_txt_i18n = %status_items_txt_nl; } elsif ( $lang eq "fr" ) { %wdays_txt_i18n = %wdays_txt_fr; @directions_txt_i18n = @directions_txt_fr; %pressure_trend_txt_i18n = %pressure_trend_txt_fr; %status_items_txt_i18n = %status_items_txt_fr; } elsif ( $lang eq "pl" ) { %wdays_txt_i18n = %wdays_txt_pl; @directions_txt_i18n = @directions_txt_pl; %pressure_trend_txt_i18n = %pressure_trend_txt_pl; %status_items_txt_i18n = %status_items_txt_pl; } elsif ( $lang eq "it" ) { %wdays_txt_i18n = %wdays_txt_it; @directions_txt_i18n = @directions_txt_it; %pressure_trend_txt_i18n = %pressure_trend_txt_it; %status_items_txt_i18n = %status_items_txt_it; } else { %wdays_txt_i18n = %wdays_txt_en; @directions_txt_i18n = @directions_txt_en; %pressure_trend_txt_i18n = %pressure_trend_txt_en; %status_items_txt_i18n = %status_items_txt_en; } } ################################### sub Weather_DebugCodes($) { my ($lang) = @_; my @YahooCodes_i18n = YahooWeatherAPI_getYahooCodes($lang); Debug "Weather Code List, see http://developer.yahoo.com/weather/#codes"; for ( my $c = 0 ; $c <= 47 ; $c++ ) { Debug sprintf( "%2d %30s %30s", $c, $iconlist[$c], $YahooCodes_i18n[$c] ); } } ##################################### sub Weather_Initialize($) { my ($hash) = @_; $hash->{DefFn} = 'Weather_Define'; $hash->{UndefFn} = 'Weather_Undef'; $hash->{GetFn} = 'Weather_Get'; $hash->{SetFn} = 'Weather_Set'; $hash->{AttrList} = 'disable:0,1 ' . 'forecast:hourly,daily,every,off ' . 'forecastLimit ' . $readingFnAttributes; $hash->{NotifyFn} = 'Weather_Notify'; #Weather_DebugCodes('de'); } ################################### sub degrees_to_direction($@) { my ( $degrees, @directions_txt_i18n ) = @_; my $mod = int( ( ( $degrees + 11.25 ) % 360 ) / 22.5 ); return $directions_txt_i18n[$mod]; } sub Weather_ReturnWithError($$) { my ( $hash, $responseRef ) = @_; my $name = $hash->{NAME}; readingsBeginUpdate($hash); readingsBulkUpdate( $hash, 'lastError', $responseRef->{status} ); foreach my $r ( keys %{$responseRef} ) { readingsBulkUpdate( $hash, $r, $responseRef->{$r} ) if ( ref( $responseRef->{$r} ) ne 'HASH' ); } readingsBulkUpdate( $hash, 'state', 'API Maintainer: ' . $responseRef->{apiMaintainer} . ' ErrorMsg: ' . $responseRef->{status} ); readingsEndUpdate( $hash, 1 ); my $next = 60; # $next= $hash->{INTERVAL}; Weather_RearmTimer( $hash, gettimeofday() + $next ); return; } sub Weather_RetrieveCallbackFn($) { my $name = shift; my $hash = $defs{$name}; my $responseRef = $hash->{fhem}->{api}->getWeather; if ( $responseRef->{status} eq 'ok' ) { Weather_WriteReadings( $hash, $responseRef ); } else { Weather_ReturnWithError( $hash, $responseRef ); } } sub Weather_WriteReadings($$) { my ( $hash, $dataRef ) = @_; my $name = $hash->{NAME}; 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} ) ); # housekeeping information readingsBulkUpdate( $hash, 'lastError', '' ); foreach my $r ( keys %{$dataRef} ) { readingsBulkUpdate( $hash, $r, $dataRef->{$r} ) if ( ref( $dataRef->{$r} ) ne 'HASH' and ref( $dataRef->{$r} ) ne 'ARRAY' ); readingsBulkUpdate( $hash, '.license', $dataRef->{license}->{text} ); } ### 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' and AttrVal( $name, 'forecast', 'every' ) ne 'off' ) { ## hourly if ( defined( $dataRef->{forecast}->{hourly} ) and ref( $dataRef->{forecast}->{hourly} ) eq 'ARRAY' and scalar( @{ $dataRef->{forecast}->{hourly} } ) > 0 and ( AttrVal( $name, 'forecast', 'every' ) eq 'every' or AttrVal( $name, 'forecast', 'hourly' ) eq 'hourly' ) ) { my $i = 0; my $limit = AttrVal( $name, 'forecastLimit', -1 ); 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' ); } last if ( $i == $limit and $limit > 0 ); } } ## daily if ( defined( $dataRef->{forecast}->{daily} ) and ref( $dataRef->{forecast}->{daily} ) eq 'ARRAY' and scalar( @{ $dataRef->{forecast}->{daily} } ) > 0 and ( AttrVal( $name, 'forecast', 'every' ) eq 'every' or AttrVal( $name, 'forecast', 'daily' ) eq 'daily' ) ) { my $i = 0; my $limit = AttrVal( $name, 'forecastLimit', -1 ); 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' ); } last if ( $i == $limit and $limit > 0 ); } } } 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} . ' hPa'; Log3 $hash, 4, "$name: $val"; readingsBulkUpdate( $hash, 'state', $val ); readingsEndUpdate( $hash, 1 ); Weather_RearmTimer( $hash, gettimeofday() + $hash->{INTERVAL} ); return; } ################################### sub Weather_GetUpdate($) { my ($hash) = @_; my $name = $hash->{NAME}; if ( $attr{$name} && $attr{$name}->{disable} ) { Log3 $hash, 5, "Weather $name: retrieval of weather data is disabled by attribute."; readingsBeginUpdate($hash); readingsBulkUpdate( $hash, "pubDateComment", "disabled by attribute" ); readingsBulkUpdate( $hash, "validity", "stale" ); readingsEndUpdate( $hash, 1 ); Weather_RearmTimer( $hash, gettimeofday() + $hash->{INTERVAL} ); } else { # Weather_RetrieveData($name, 0); $hash->{fhem}->{api}->setRetrieveData; } return 1; } ################################### sub Weather_Get($@) { my ( $hash, @a ) = @_; return "argument is missing" if ( int(@a) != 2 ); my $reading = $a[1]; my $value; if ( defined( $hash->{READINGS}->{$reading} ) ) { $value = $hash->{READINGS}->{$reading}->{VAL}; } else { my $rt = ''; if ( defined( $hash->{READINGS} ) ) { $rt = join( ":noArg ", sort keys %{ $hash->{READINGS} } ); } return "Unknown reading $reading, choose one of " . $rt; } return "$a[0] $reading => $value"; } ################################### sub Weather_Set($@) { my ( $hash, @a ) = @_; my $cmd = $a[1]; # usage check if ( ( @a == 2 ) && ( $a[1] eq "update" ) ) { Weather_DisarmTimer($hash); Weather_GetUpdate($hash); return undef; } else { return "Unknown argument $cmd, choose one of update:noArg"; } } ################################### sub Weather_RearmTimer($$) { my ( $hash, $t ) = @_; InternalTimer( $t, "Weather_GetUpdate", $hash, 0 ); } sub Weather_DisarmTimer($) { my ($hash) = @_; RemoveInternalTimer($hash); } sub Weather_Notify($$) { my ( $hash, $dev ) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; return if ( $dev->{NAME} ne "global" ); return if ( !grep( m/^INITIALIZED|REREADCFG$/, @{ $dev->{CHANGED} } ) ); # return if($attr{$name} && $attr{$name}->{disable}); # update weather after initialization or change of configuration # wait 10 to 29 seconds to avoid congestion due to concurrent activities Weather_DisarmTimer($hash); my $delay = 10 + int( rand(20) ); #$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 ); ### quick run GetUpdate then Demo Weather_GetUpdate($hash) if ( defined( $hash->{APIKEY} ) and lc( $hash->{APIKEY} ) eq 'demo' ); return undef; } ##################################### sub Weather_Define($$) { my ( $hash, $def ) = @_; my $usage = "syntax: define Weather [API=] [apikey=] [location=] [interval=] [lang=]"; # defaults my $API = "DarkSkyAPI,cachemaxage:600"; my $interval = 3600; # parse parameters my ( $arrayref, $hashref ) = parseParams($def); my @a = @{$arrayref}; my %h = %{$hashref}; # check minimum syntax return $usage unless ( scalar @a == 2 ); my $name = $a[0]; my $location = $h{location} if exists $h{location}; my $apikey = $h{apikey} if exists $h{apikey}; my $lang = $h{lang} if exists $h{lang}; $interval = $h{interval} if exists $h{interval}; $API = $h{API} if exists $h{API}; # evaluate API options my ( $api, $apioptions ) = split( ',', $API, 2 ); $apioptions = "" unless ( defined($apioptions) ); eval { require "$api.pm"; }; return "$name: cannot load API $api: $@" if ($@); $hash->{NOTIFYDEV} = "global"; $hash->{fhem}->{interfaces} = "temperature;humidity;wind"; $hash->{LOCATION} = ( ( defined($location) and $location ) ? $location : AttrVal( 'global', 'latitude', 'error' ) . ',' . AttrVal( 'global', 'longitude', 'error' ) ); $hash->{INTERVAL} = $interval; $hash->{LANG} = ( ( defined($lang) and $lang ) ? $lang : lc( AttrVal( 'global', 'language', 'de' ) ) ); $hash->{API} = $api; $hash->{MODEL} = $api; $hash->{APIKEY} = $apikey; $hash->{APIOPTIONS} = $apioptions; $hash->{READINGS}->{current_date_time}->{TIME} = TimeNow(); $hash->{READINGS}->{current_date_time}->{VAL} = "none"; $hash->{fhem}->{allowCache} = 1; 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); return undef; } ##################################### sub Weather_Undef($$) { my ( $hash, $arg ) = @_; RemoveInternalTimer($hash); return undef; } ##################################### # Icon Parameter use constant ICONHIGHT => 120; use constant ICONWIDTH => 175; use constant ICONSCALE => 0.5; ##################################### sub WeatherIconIMGTag($) { my $width = int( ICONSCALE * ICONWIDTH ); my ($icon) = @_; my $url = FW_IconURL("weather/$icon"); my $style = " width=$width"; return "\"$icon\""; } ##################################### sub WeatherAsHtmlV($;$$) { my ( $d, $op1, $op2 ) = @_; my ( $f, $items ) = WeatherCheckOptions( $d, $op1, $op2 ); my $h = $defs{$d}; my $width = int( ICONSCALE * ICONWIDTH ); my $ret = ''; my $fc; if ( defined($f) and ( $f eq 'h' or $f eq 'd' ) ) { $fc = ( $f eq 'd' ? 'fc' : 'hfc' ); } else { $fc = ( ( defined( $h->{READINGS}->{fc1_day_of_week} ) and $h->{READINGS}->{fc1_day_of_week} ) ? 'fc' : 'hfc' ); } $ret .= sprintf( '', $width, WeatherIconIMGTag( ReadingsVal( $d, "icon", "" ) ), ReadingsVal( $d, "condition", "" ), ReadingsVal( $d, "temp_c", "" ), ReadingsVal( $d, "humidity", "" ), ReadingsVal( $d, "wind_condition", "" ) ); for ( my $i = 1 ; $i < $items ; $i++ ) { if ( defined( $h->{READINGS}->{"${fc}${i}_low_c"} ) and $h->{READINGS}->{"${fc}${i}_low_c"} ) { $ret .= sprintf( '', $width, WeatherIconIMGTag( ReadingsVal( $d, "${fc}${i}_icon", "" ) ), ReadingsVal( $d, "${fc}${i}_day_of_week", "" ), ReadingsVal( $d, "${fc}${i}_condition", "" ), ReadingsVal( $d, "${fc}${i}_low_c", " - " ), ReadingsVal( $d, "${fc}${i}_high_c", " - " ), ReadingsVal( $d, "${fc}${i}_wind_condition", " - " ) ); } else { $ret .= sprintf( '', $width, WeatherIconIMGTag( ReadingsVal( $d, "${fc}${i}_icon", "" ) ), ReadingsVal( $d, "${fc}${i}_day_of_week", "" ), ReadingsVal( $d, "${fc}${i}_condition", "" ), ReadingsVal( $d, "${fc}${i}_temperature", " - " ), ReadingsVal( $d, "${fc}${i}_wind_condition", " - " ) ); } } $ret .= "
%s%s
%s°C %s%%
%s
%s%s: %s
min %s°C max %s°C
%s
%s%s: %s
%s°C
%s
"; return $ret; } sub WeatherAsHtml($;$$) { my ( $d, $op1, $op2 ) = @_; my ( $f, $items ) = WeatherCheckOptions( $d, $op1, $op2 ); WeatherAsHtmlV( $d, $f, $items ); } sub WeatherAsHtmlH($;$$) { my ( $d, $op1, $op2 ) = @_; my ( $f, $items ) = WeatherCheckOptions( $d, $op1, $op2 ); my $h = $defs{$d}; my $width = int( ICONSCALE * ICONWIDTH ); my $format = '
%s
%s
%s°C %s%%
%s
'; my $ret = ''; my $fc; if ( defined($f) and ( $f eq 'h' or $f eq 'd' ) ) { $fc = ( $f eq 'd' ? 'fc' : 'hfc' ); } else { $fc = ( ( defined( $h->{READINGS}->{fc1_day_of_week} ) and $h->{READINGS}->{fc1_day_of_week} ) ? 'fc' : 'hfc' ); } # icons $ret .= sprintf( '', $width, WeatherIconIMGTag( ReadingsVal( $d, "icon", "" ) ) ); for ( my $i = 1 ; $i < $items ; $i++ ) { $ret .= sprintf( '', $width, WeatherIconIMGTag( ReadingsVal( $d, "${fc}${i}_icon", "" ) ) ); } $ret .= ''; # condition $ret .= sprintf( '', ReadingsVal( $d, "condition", "" ) ); for ( my $i = 1 ; $i < $items ; $i++ ) { $ret .= sprintf( '', ReadingsVal( $d, "${fc}${i}_day_of_week", "" ), ReadingsVal( $d, "${fc}${i}_condition", "" ) ); } $ret .= ''; # temp/hum | min $ret .= sprintf( '', ReadingsVal( $d, "temp_c", "" ), ReadingsVal( $d, "humidity", "" ) ); for ( my $i = 1 ; $i < $items ; $i++ ) { if ( defined( $h->{READINGS}->{"${fc}${i}_low_c"} ) and $h->{READINGS}->{"${fc}${i}_low_c"} ) { $ret .= sprintf( '', ReadingsVal( $d, "${fc}${i}_low_c", " - " ) ); } else { $ret .= sprintf( '', ReadingsVal( $d, "${fc}${i}_temperature", " - " ) ); } } $ret .= ''; # wind | max $ret .= sprintf( '', ReadingsVal( $d, "wind_condition", "" ) ); for ( my $i = 1 ; $i < $items ; $i++ ) { if ( defined( $h->{READINGS}->{"${fc}${i}_high_c"} ) and $h->{READINGS}->{"${fc}${i}_high_c"} ) { $ret .= sprintf( '', ReadingsVal( $d, "${fc}${i}_high_c", " - " ) ); } } $ret .= "
%s%s
%s%s: %s
%s°C %s%%min %s°C %s°C
%smax %s°C
"; return $ret; } sub WeatherAsHtmlD($;$$) { my ( $d, $op1, $op2 ) = @_; my ( $f, $items ) = WeatherCheckOptions( $d, $op1, $op2 ); if ($FW_ss) { WeatherAsHtmlV( $d, $f, $items ); } else { WeatherAsHtmlH( $d, $f, $items ); } } sub WeatherCheckOptions($@) { my ( $d, $op1, $op2 ) = @_; my $items = $op2; my $f = $op1; if ( defined($op1) and $op1 and $op1 =~ /[0-9]/g ) { $items = $op1; } if ( defined($op2) and $op2 and $op2 =~ /[dh]/g ) { $f = $op2; } $f =~ tr/dh/./cd if ( defined $f and $f ); $items =~ tr/0-9/./cd if ( defined($items) and $items ); $items = 6 if ( !$items ); return "$d is not a Weather instance
" if ( !$defs{$d} || $defs{$d}->{TYPE} ne "Weather" ); if ( AttrVal( $d, 'forecast', 'none' ) ne 'none' ) { $f = ( AttrVal( $d, 'forecast', 'none' ) eq 'daily' ? 'd' : ( AttrVal( $d, 'forecast', 'none' ) eq 'every' ? $f : 'h' ) ); } $f = 'h' if ( !$f || length($f) > 1 ); return ( $f, $items ); } ##################################### 1; =pod =item device =item summary provides current weather condition and forecast =item summary_DE stellt Wetterbericht und -vorhersage bereit =begin html

Weather

    Note: you need the JSON perl module. Use apt-get install libjson-perl on Debian and derivatives.

    The Weather module works with various weather APIs:


    Such a virtual Weather device periodically gathers current and forecast weather conditions from the chosen weather API.

    Define

      define <name> Weather [API=<API>[,<apioptions>]] [apikey=<apikey>] [location=<location>] [interval=<interval>] [lang=<lang>]

      The parameters have the following meanings:
      APIname of the weather API, e.g. DarkSkyAPI
      apioptionsindivual options for the chosen API
      apikeykey for the chosen API
      locationlocation for the weather forecast; e.g. coordinates, a town name or an ID, depending on the chosen API
      intervalduration in seconds between updates
      langlanguage of the forecast: de, en, pl, fr, it or nl

      A very simple definition is:

      define <name> Weather apikey=<DarkSkyAPISecretKey>

      This uses the Dark Sky API with an individual key that you need to retrieve from the Dark Sky web site.

      Examples:

            define Forecast Weather apikey=987498ghjgf864
            define MyWeather Weather API=OpenWeatherMapAPI,cachemaxage:600 apikey=09878945fdskv876 location=52.4545,13.4545 interval=3600 lang=de
          
      API-specific documentation follows.

      Dark Sky

      APIDarkSkyAPI
      apioptionscachemaxage=<cachemaxage>
      duration in seconds to retrieve the forecast from the cache instead from the API
      extend=hourly
      extends the number of hours forecast records to 149
      location<latitude,longitude>
      geographic coordinates in degrees of the location for which the weather is forecast; if missing, the values of the attributes of the global device are taken, if these exist.

      OpenWeatherMap

      APIOpenWeatherMapAPI
      apioptionscachemaxage=<cachemaxage>
      duration in seconds to retrieve the forecast from the cache instead from the API
      location<latitude,longitude>
      geographic coordinates in degrees of the location for which the weather is forecast; if missing, the values of the attributes of the global device are taken, if these exist.

      The module provides four additional functions WeatherAsHtml, WeatherAsHtmlV, WeatherAsHtmlH and WeatherAsHtmlD. 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.

      Example:

            define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather","h",10) }
          

      Set
        set <name> update

        Forces the retrieval of the weather data. The next automatic retrieval is scheduled to occur interval seconds later.


      Get
        get <name> <reading>

        Valid readings and their meaning (? can be one of 1, 2, 3, 4, 5 and stands for today, tomorrow, etc.):
      .licenselicense of the API provider, if available
      cityname of town returned for location
      codecurrent condition code
      conditioncurrent condition
      current_date_timelast update of forecast on server
      fc?_codeforecast condition code
      fc?_conditionforecast condition
      fc?_day_of_weekday of week for day +?
      fc?_high_cforecasted daily high in degrees centigrade
      fc?_iconforecast icon
      fc?_low_cforecasted daily low in degrees centigrade
      humiditycurrent humidity in %
      iconrelative path for current icon
      pressureair pressure in hPa
      pressure_trendair pressure trend (0= steady, 1= rising, 2= falling)
      pressure_trend_txttextual representation of air pressure trend
      pressure_trend_symsymbolic representation of air pressure trend
      temperaturecurrent temperature in degrees centigrade
      temp_ccurrent temperature in degrees centigrade
      temp_fcurrent temperature in degrees Fahrenheit
      visibilityvisibility in km
      windwind speed in km/h
      wind_chillwind chill in degrees centigrade
      wind_conditionwind direction and speed
      wind_directiondirection wind comes from in degrees (0 = north wind)
      wind_speedsame as wind

      The weekday of the forecast will be in the language of your FHEM system. Enter {$ENV{LANG}} into the FHEM command line to verify. If nothing is displayed or you see an unexpected language setting, add export LANG=de_DE.UTF-8 or something similar to your FHEM start script, restart FHEM and check again. If you get a locale warning when starting FHEM the required language pack might be missing. It can be installed depending on your OS and your preferences (e.g. dpkg-reconfigure locales, apt-get install language-pack-de or something similar).
      Depending on the chosen API, other readings can be shown as well. The meaning of these readings can be determined from the API provider's documentation.

    Attributes
    • disable: disables the retrieval of weather data - the timer runs according to schedule, though no data is requested from the API.
    • readingFnAttributes

=end html =begin html_DE

Weather

    Hinweis: es wird das Perl-Modul JSON benötigt. Mit apt-get install libjson-perl kann es unter Debian und Derivaten installiert werden.

    Das Weather-Modul arbeitet mit verschiedenen Wetter-APIs zusammen:


    Eine solche virtuelle Wetterstation sammelt periodisch aktuelle Wetterdaten und Wettervorhersagen aus dem verwendeten API.

    Define

      define <name> Weather [API=<API>[,<apioptions>]] [apikey=<apikey>] [location=<location>] [interval=<interval>] [lang=<lang>]

      Die Parameter haben die folgende Bedeutung:
      APIName des Wetter-APIs, z.B. DarkSkyAPI
      apioptionsIndividuelle Optionen für das gewählte API
      apikeySchlüssel für das gewählte API
      locationOrt, für den das Wetter vorhergesagt wird. Abhängig vom API z.B. die Koordinaten, ein Ortsname oder eine ID.
      intervalDauer in Sekunden zwischen den einzelnen Aktualisierungen der Wetterdaten
      langSprache der Wettervorhersage: de, en, pl, fr, it oder nl

      Eine ganz einfache Definition ist:

      define <name> Weather apikey=<DarkSkyAPISecretKey>

      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.

      Beispiele:

            define Forecast Weather apikey=987498ghjgf864
            define MyWeather Weather API=OpenWeatherMapAPI,cachemaxage:600 apikey=09878945fdskv876 location=52.4545,13.4545 interval=3600 lang=de
          
      Es folgt die API-spezifische Dokumentation.

      Dark Sky

      APIDarkSkyAPI
      apioptionscachemaxage=<cachemaxage>
      Zeitdauer in Sekunden, innerhalb derer die Wettervorhersage nicht neu abgerufen sondern aus dem Cache zurück geliefert wird.
      extend=hourly
      erweitert die Anzahl der Datensätze für die Stundenvorhersage auf 149
      location<latitude,longitude>
      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 global-Device genommen, sofern vorhanden.

      OpenWeatherMap

      APIOpenWeatherMapAPI
      apioptionscachemaxage=<cachemaxage> Zeitdauer in Sekunden, innerhalb derer die Wettervorhersage nicht neu abgerufen sondern aus dem Cache zurück geliefert wird.
      location<latitude,longitude> 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 global-Device genommen, sofern vorhanden.

      Das Modul unterstützt zusätzlich vier verschiedene Funktionen WeatherAsHtml, WeatherAsHtmlV, WeatherAsHtmlH und WeatherAsHtmlD. 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 Funktionen akzeptieren einen zusätzlichen optionalen Paramter um die Anzahl der darzustellenden Icons anzugeben.
      Zusätzlich erlauben die Funktionen 2 und 3 noch einen dritten Parameter (d oder h) welcher die Forecast-Art (h-Hourly oder d-Daily) mit an gibt.
      Wird der dritte Parameter verwendet muss auch der zweite Parameter für die Anzahl der darzustellenden Icons gesetzt werden.

      Beispiel:

            define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather","h",10) }
          

      Set
        set <name> update

        Erzwingt eine Abfrage der Wetterdaten. Die darauffolgende Abfrage wird gemäß dem eingestellten Intervall interval Sekunden später durchgeführt.


      Get
        get <name> <reading>

        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.):

      .licenseLizenz des jeweiligen API-Anbieters, sofern vorhanden
      cityName der Stadt, der für die location übermittelt wird
      codeCode für die aktuellen Wetterverhältnisse
      conditionaktuelle Wetterverhältnisse
      current_date_timeZeitstempel der letzten Aktualisierung der Wetterdaten vom Server
      fc?_codeCode für die vorhergesagten Wetterverhältnisse
      fc?_conditionvorhergesagte Wetterverhältnisse
      fc?_day_of_weekWochentag des Tages, der durch ? dargestellt wird
      fc?_high_cvorhergesagte maximale Tagestemperatur in Grad Celsius
      fc?_iconIcon für Vorhersage
      fc?_low_cvorhergesagte niedrigste Tagestemperatur in Grad Celsius
      humiditygegenwärtige Luftfeuchtgkeit in %
      iconrelativer Pfad für das aktuelle Icon
      pressureLuftdruck in hPa
      temperaturegegenwärtige Temperatur in Grad Celsius
      temp_cgegenwärtige Temperatur in Grad Celsius
      temp_fgegenwärtige Temperatur in Grad Celsius
      visibilitySichtweite in km
      windWindgeschwindigkeit in km/h
      wind_conditionWindrichtung und -geschwindigkeit
      wind_directionGradangabe der Windrichtung (0 = Nordwind)
      wind_speedWindgeschwindigkeit in km/h (mit wind identisch)
      validitystale, wenn der Veröffentlichungszeitpunkt auf dem entfernten Server vor dem Zeitpunkt der aktuellen Daten (readings) liegt

      Der Wochentag der Prognose wird in der Sprache Ihres FHEM-Systems angezeigt. Geben Sie zur Überprüfung {$ ENV {LANG}} in die Befehlszeile von FHEM ein. Wenn nichts angezeigt wird oder eine unerwartete Spracheinstellung angezeigt wird, fügen Sie export LANG = de_DE.UTF-8 oder etwas Ähnliches zu Ihrem FHEM-Startskript hinzu. Starten Sie FHEM erneut und überprüfen Sie es erneut. Wenn Sie beim Starten von FHEM eine Ländereinstellung erhalten, fehlt möglicherweise das erforderliche Sprachpaket. Sie kann abhängig von Ihrem Betriebssystem und Ihren Präferenzen installiert werden (z. B. Gebietsschemas dpkg-reconfigure, apt-get install language-pack-de oder ähnliches).
      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.

    Attribute
    • disable: stellt die Abfrage der Wetterdaten ab - der Timer läft gemäß Plan doch es werden keine Daten vom API angefordert.
    • readingFnAttributes
    • forecast - every/hourly/daily/off, Anzeige von forecast Daten. Alle, nur Stundenforecast, nur Tageforecast, keine.
    • forecastLimit - Anzahl der Forecast-Datensätze welche als Reading geschrieben werden sollen.

=end html_DE =cut