############################################################################### # $Id$ # http://api.wunderground.com/weather/api # package main; use strict; use warnings; use vars qw(%data); use HttpUtils; use utf8; use Encode qw(encode_utf8 decode_utf8); use Unit; use Data::Dumper; use FHEM::Meta; # initialize ################################################################## sub Wunderground_Initialize($) { my ($hash) = @_; Log3 $hash, 5, "Wunderground_Initialize: Entering"; my $webhookFWinstance = join( ",", devspec2array('TYPE=FHEMWEB:FILTER=TEMPORARY!=1') ); $hash->{DefFn} = "Wunderground_Define"; $hash->{UndefFn} = "Wunderground_Undefine"; $hash->{SetFn} = "Wunderground_Set"; $hash->{AttrFn} = "Wunderground_Attr"; $hash->{DbLog_splitFn} = "Unit_DbLog_split"; $hash->{parseParams} = 1; $hash->{AttrList} = "disable:0,1 disabledForIntervals do_not_notify:1,0 timeout:1,2,3,4,5 pollInterval:300,450,600,750,900 wu_lang:en,de,at,ch,nl,fr,pl wu_pws:1,0 wu_bestfct:1,0 stateReadings stateReadingsFormat:0,1 " . "wu_features:multiple-strict,alerts,almanac,astronomy,conditions,currenthurricane,forecast,forecast10day,hourly,hourly10day " . $readingFnAttributes; $hash->{readingsDesc} = { 'UV' => { rtype => 'uvi' }, 'dewpoint' => { rtype => 'c', formula_symbol => 'Td', }, 'dewpoint_f' => { rtype => 'f', formula_symbol => 'Td', }, 'fc0_heat_index_c' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc0_heatindex_f' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc0_high_c' => { rtype => 'c', format => '%i', formula_symbol => 'Th', }, 'fc0_high_f' => { rtype => 'f', format => '%i', formula_symbol => 'Th', }, 'fc0_humidity' => { rtype => 'pct', formula_symbol => 'H' }, 'fc0_humidity_max' => { rtype => 'pct', formula_symbol => 'H' }, 'fc0_humidity_min' => { rtype => 'pct', formula_symbol => 'H' }, 'fc0_icon_url' => { rtype => 'url_http' }, 'fc0_icon_url_night' => { rtype => 'url_http' }, 'fc0_low_c' => { rtype => 'c', format => '%i', formula_symbol => 'Tl', }, 'fc0_low_f' => { rtype => 'f', format => '%i', formula_symbol => 'Tl', }, 'fc0_rain_day' => { rtype => 'mm' }, 'fc0_rain_day_in' => { rtype => 'in' }, 'fc0_rain_daytime' => { rtype => 'mm' }, 'fc0_rain_daytime_in' => { rtype => 'in' }, 'fc0_rain_night' => { rtype => 'mm' }, 'fc0_rain_night_in' => { rtype => 'in' }, 'fc0_snow_day' => { rtype => 'cm' }, 'fc0_snow_day_in' => { rtype => 'in' }, 'fc0_snow_daytime' => { rtype => 'cm' }, 'fc0_snow_daytime_in' => { rtype => 'in' }, 'fc0_snow_night' => { rtype => 'cm' }, 'fc0_snow_night_in' => { rtype => 'in' }, 'fc0_title' => { rtype => 'weekday', showLong => 1 }, 'fc0_title_night' => { rtype => 'weekday_night', showLong => 1 }, 'fc0_wind_chill' => { rtype => 'c', formula_symbol => 'Wc', scope => { empty_replace => '--.-' }, }, 'fc0_wind_chill_f' => { rtype => 'f', formula_symbol => 'Wc', scope => { empty_replace => '--.-' } }, 'fc0_wind_direction' => { rtype => 'compasspoint' }, 'fc0_wind_direction_max' => { rtype => 'compasspoint' }, 'fc0_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc0_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc0_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc0_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc1_heat_index_c' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc1_heat_index_f' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc1_high_c' => { rtype => 'c', format => '%i', formula_symbol => 'Th', }, 'fc1_high_f' => { rtype => 'f', format => '%i', formula_symbol => 'Th', }, 'fc1_humidity' => { rtype => 'pct', formula_symbol => 'H' }, 'fc1_humidity_max' => { rtype => 'pct', formula_symbol => 'H' }, 'fc1_humidity_min' => { rtype => 'pct', formula_symbol => 'H' }, 'fc1_icon_url' => { rtype => 'url_http' }, 'fc1_icon_url_night' => { rtype => 'url_http' }, 'fc1_low_c' => { rtype => 'c', format => '%i', formula_symbol => 'Tl', }, 'fc1_low_f' => { rtype => 'f', format => '%i', formula_symbol => 'Tl', }, 'fc1_rain_day' => { rtype => 'mm' }, 'fc1_rain_day_in' => { rtype => 'in' }, 'fc1_rain_daytime' => { rtype => 'mm' }, 'fc1_rain_daytime_in' => { rtype => 'in' }, 'fc1_rain_night' => { rtype => 'mm' }, 'fc1_rain_night_in' => { rtype => 'in' }, 'fc1_snow_day' => { rtype => 'cm' }, 'fc1_snow_day_in' => { rtype => 'in' }, 'fc1_snow_night' => { rtype => 'cm' }, 'fc1_snow_night_in' => { rtype => 'in' }, 'fc1_title' => { rtype => 'weekday', showLong => 1 }, 'fc1_title_night' => { rtype => 'weekday_night', showLong => 1 }, 'fc1_wind_chill' => { rtype => 'c', formula_symbol => 'Wc', scope => { empty_replace => '--.-' }, }, 'fc1_wind_chill_f' => { rtype => 'f', formula_symbol => 'Wc', scope => { empty_replace => '--.-' } }, 'fc1_wind_direction' => { rtype => 'compasspoint' }, 'fc1_wind_direction_max' => { rtype => 'compasspoint' }, 'fc1_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc1_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc1_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc1_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc2_heat_index_c' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc2_heat_index_f' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc2_high_c' => { rtype => 'c', format => '%i', formula_symbol => 'Th', }, 'fc2_high_f' => { rtype => 'f', format => '%i', formula_symbol => 'Th', }, 'fc2_humidity' => { rtype => 'pct', formula_symbol => 'H' }, 'fc2_humidity_max' => { rtype => 'pct', formula_symbol => 'H' }, 'fc2_humidity_min' => { rtype => 'pct', formula_symbol => 'H' }, 'fc2_icon_url' => { rtype => 'url_http' }, 'fc2_icon_url_night' => { rtype => 'url_http' }, 'fc2_low_c' => { rtype => 'c', format => '%i', formula_symbol => 'Tl', }, 'fc2_low_f' => { rtype => 'f', format => '%i', formula_symbol => 'Tl', }, 'fc2_rain_day' => { rtype => 'mm' }, 'fc2_rain_day_in' => { rtype => 'in' }, 'fc2_rain_daytime' => { rtype => 'mm' }, 'fc2_rain_daytime_in' => { rtype => 'in' }, 'fc2_rain_night' => { rtype => 'mm' }, 'fc2_rain_night_in' => { rtype => 'in' }, 'fc2_snow_day' => { rtype => 'cm' }, 'fc2_snow_day_in' => { rtype => 'in' }, 'fc2_snow_night' => { rtype => 'cm' }, 'fc2_snow_night_in' => { rtype => 'in' }, 'fc2_title' => { rtype => 'weekday', showLong => 1 }, 'fc2_title_night' => { rtype => 'weekday_night', showLong => 1, }, 'fc2_wind_chill' => { rtype => 'c', formula_symbol => 'Wc', scope => { empty_replace => '--.-' }, }, 'fc2_wind_chill_f' => { rtype => 'f', formula_symbol => 'Wc', scope => { empty_replace => '--.-' } }, 'fc2_wind_direction' => { rtype => 'compasspoint' }, 'fc2_wind_direction_max' => { rtype => 'compasspoint' }, 'fc2_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc2_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc2_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc2_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc3_heat_index_c' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc3_heat_index_f' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'fc3_high_c' => { rtype => 'c', format => '%i', formula_symbol => 'Th', }, 'fc3_high_f' => { rtype => 'f', format => '%i', formula_symbol => 'Th', }, 'fc3_humidity' => { rtype => 'pct', formula_symbol => 'H' }, 'fc3_humidity_max' => { rtype => 'pct', formula_symbol => 'H' }, 'fc3_humidity_min' => { rtype => 'pct', formula_symbol => 'H' }, 'fc3_icon_url' => { rtype => 'url_http' }, 'fc3_icon_url_night' => { rtype => 'url_http' }, 'fc3_low_c' => { rtype => 'c', format => '%i', formula_symbol => 'Tl', }, 'fc3_low_f' => { rtype => 'f', format => '%i', formula_symbol => 'Tl', }, 'fc3_rain_day' => { rtype => 'mm' }, 'fc3_rain_day_in' => { rtype => 'in' }, 'fc3_rain_daytime' => { rtype => 'mm' }, 'fc3_rain_daytime_in' => { rtype => 'in' }, 'fc3_rain_night' => { rtype => 'mm' }, 'fc3_rain_night_in' => { rtype => 'in' }, 'fc3_snow_day' => { rtype => 'cm' }, 'fc3_snow_day_in' => { rtype => 'in' }, 'fc3_snow_night' => { rtype => 'cm' }, 'fc3_snow_night_in' => { rtype => 'in' }, 'fc3_title' => { rtype => 'weekday', showLong => 1 }, 'fc3_title_night' => { rtype => 'weekday_night', showLong => 1 }, 'fc3_wind_chill' => { rtype => 'c', formula_symbol => 'Wc', scope => { empty_replace => '--.-' }, }, 'fc3_wind_chill_f' => { rtype => 'f', formula_symbol => 'Wc', scope => { empty_replace => '--.-' } }, 'fc3_wind_direction' => { rtype => 'compasspoint' }, 'fc3_wind_direction_max' => { rtype => 'compasspoint' }, 'fc3_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc3_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'fc3_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'fc3_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' }, 'feelslike_c' => { rtype => 'c', formula_symbol => 'Tf', }, 'feelslike_f' => { rtype => 'f', formula_symbol => 'Tf', }, 'forecast_url' => { rtype => 'url_http' }, 'heat_index_c' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'heat_index_f' => { formula_symbol => 'HI', scope => { empty_replace => '--.-' } }, 'history_url' => { rtype => 'url_http' }, 'humidity' => { rtype => 'pct', formula_symbol => 'H' }, 'icon_url' => { rtype => 'url_http' }, 'israining' => { rtype => 'bool', }, 'lastQueryResult' => { rtype => 'oknok' }, 'moon_age' => { rtype => 'd' }, 'moon_pct' => { rtype => 'pct' }, 'moon_rise' => { rtype => 'time' }, 'moon_set' => { rtype => 'time' }, 'ob_url' => { rtype => 'url_http' }, 'pressure' => { rtype => 'hpamb' }, 'pressure_in' => { rtype => 'inhg' }, 'pressure_trend' => { rtype => 'trend' }, 'rain' => { rtype => 'mm' }, 'rain_day' => { rtype => 'mm' }, 'rain_day_in' => { rtype => 'in' }, 'rain_in' => { rtype => 'in' }, 'solarradiation' => { rtype => 'wpsm' }, 'sunrise' => { rtype => 'time' }, 'sunset' => { rtype => 'time' }, 'temp_c' => { rtype => 'c' }, 'temp_f' => { rtype => 'f' }, 'visibility' => { rtype => 'km', scope => { empty_replace => '--.-' } }, 'visibility_mi' => { rtype => 'mi', scope => { empty_replace => '--.-' } }, 'wind_chill' => { rtype => 'c', formula_symbol => 'Wc', scope => { empty_replace => '--.-' }, }, 'wind_chill_f' => { rtype => 'f', formula_symbol => 'Wc', scope => { empty_replace => '--.-' } }, 'wind_direction' => { rtype => 'compasspoint' }, 'wind_gust' => { rtype => 'kmph', formula_symbol => 'Wg' }, 'wind_gust_mph' => { rtype => 'mph', formula_symbol => 'Wg' }, 'wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' }, 'wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' } }; return FHEM::Meta::InitMod( __FILE__, $hash ); } # regular Fn ################################################################## sub Wunderground_Define($$$) { my ( $hash, $a, $h ) = @_; my $name = $hash->{NAME}; my $infix = "Wunderground"; Log3 $name, 5, "Wunderground $name: called function Wunderground_Define()"; eval { require JSON; import JSON qw( decode_json ); }; return "Please install Perl JSON to use module Wunderground" if ($@); if ( int(@$a) < 4 ) { my $msg = "Wrong syntax: define Wunderground "; Log3 $name, 4, $msg; return $msg; } $hash->{TYPE} = "Wunderground"; $hash->{API_KEY} = @$a[2]; $hash->{QUERY} = @$a[3]; # Initialize the module and the device return $@ unless ( FHEM::Meta::SetInternals($hash) ); $hash->{QUERY} = "pws:" . $hash->{QUERY} if ( $hash->{QUERY} =~ /^[A-Z]{3,}\d{1,}$/ ); if ( $init_done && !defined( $hash->{OLDDEF} ) ) { fhem 'attr ' . $name . ' stateReadings temp_c humidity'; fhem 'attr ' . $name . ' stateReadingsFormat 1'; fhem 'attr ' . $name . ' wu_features astronomy,conditions,forecast'; } # start the status update timer Wunderground_GetStatus( $hash, 2 ); return undef; } sub Wunderground_Undefine($$$) { my ( $hash, $a, $h ) = @_; my $name = $hash->{NAME}; if ( defined( $hash->{fhem}{infix} ) ) { Wunderground_removeExtension( $hash->{fhem}{infix} ); } Log3 $name, 5, "Wunderground $name: called function Wunderground_Undefine()"; # Stop the internal GetStatus-Loop and exit RemoveInternalTimer($hash); # release reverse pointer delete $modules{Wunderground}{defptr}{$name}; return undef; } sub Wunderground_Set($$$) { my ( $hash, $a, $h ) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "Wunderground $name: called function Wunderground_Set()"; return "Argument is missing" if ( int(@$a) < 1 ); my $usage = "Unknown argument " . @$a[1] . ", choose one of update:noArg"; my $cmd = ''; my $result; # update if ( lc( @$a[1] ) eq "update" ) { Log3 $name, 3, "Wunderground set $name " . @$a[1]; Wunderground_GetStatus($hash); } # return usage hint else { return $usage; } return $result; } sub Wunderground_Attr(@) { my ( $cmd, $name, $attrName, $attrVal ) = @_; my $hash = $defs{$name}; Log3 $name, 5, "Wunderground $name: called function Wunderground_Attr()"; return "Invalid value for attribute $attrName: minimum value is 1 second, maximum 5 seconds" if ( $attrVal && $attrName eq "timeout" && ( $attrVal < 1 || $attrVal > 5 ) ); return "Invalid value for attribute $attrName: minimum value is 300 seconds" if ( $attrVal && $attrName eq "pollInterval" && $attrVal < 300 ); return undef; } # module Fn #################################################################### sub Wunderground_GetStatus($;$) { my ( $hash, $delay ) = @_; my $name = $hash->{NAME}; $hash->{INTERVAL} = AttrVal( $name, "pollInterval", "300" ); my $bestfct = AttrVal( $name, "wu_bestfct", undef ); my $features = AttrVal( $name, "wu_features", "astronomy,conditions,forecast" ); my $lang = AttrVal( $name, "wu_lang", "en" ); my $pws = AttrVal( $name, "wu_pws", undef ); my $interval = ( $delay ? $delay : $hash->{INTERVAL} ); Log3 $name, 5, "Wunderground $name: called function Wunderground_GetStatus()"; RemoveInternalTimer($hash); InternalTimer( gettimeofday() + $interval, "Wunderground_GetStatus", $hash, 0 ); return if ( $delay || AttrVal( $name, "disable", 0 ) == 1 ); my $langmap = { at => 'OS', de => 'DL', 'en-gb' => 'LI', gb => 'LI', uk => 'LI', }; if ( defined( $langmap->{ lc($lang) } ) ) { $hash->{LANG} = $langmap->{ lc($lang) }; } else { $hash->{LANG} = uc($lang); } $features =~ s/,{2,}/,/g; $features =~ s/\s//g; $features =~ s/,/\//g; $features .= "/lang:" . $hash->{LANG}; $features .= "/pws:$pws" if ( defined($pws) ); $features .= "/bestfct:$bestfct" if ( defined($bestfct) ); $hash->{FEATURES} = $features; Wunderground_SendCommand( $hash, $features ); return; } sub Wunderground_SendCommand($$) { my ( $hash, $features ) = @_; my $name = $hash->{NAME}; my $apikey = $hash->{API_KEY}; my $query = $hash->{QUERY}; my $URL = "https://api.wunderground.com/api/%APIKEY%/%FEATURES%/q/%QUERY%.json"; Log3 $name, 5, "Wunderground $name: called function Wunderground_SendCommand()"; $URL =~ s/%APIKEY%/$apikey/; $URL =~ s/%FEATURES%/$features/; $URL =~ s/%QUERY%/$query/; Log3 $name, 5, "Wunderground $name: GET " . urlDecode($URL); HttpUtils_NonblockingGet( { url => $URL, timeout => AttrVal( $name, "timeout", "3" ), hash => $hash, method => "GET", callback => \&Wunderground_ReceiveCommand, httpversion => "1.1", loglevel => AttrVal( $name, "httpLoglevel", 4 ), header => { Agent => 'FHEM-Wunderground/1.0.0', 'User-Agent' => 'FHEM-Wunderground/1.0.0', Accept => 'application/json;charset=UTF-8', 'Accept-Charset' => 'UTF-8', }, } ); return; } sub Wunderground_ReceiveCommand($$$) { my ( $param, $err, $data ) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; my $lastQueryResult = ReadingsVal( $name, "lastQueryResult", "Initialized" ); my $state = "Initialized"; my $return; Log3 $name, 5, "Wunderground $name: called function Wunderground_ReceiveCommand()"; readingsBeginUpdate($hash); # service not reachable if ($err) { Log3 $name, 4, "Wunderground $name: RCV TIMEOUT: $err"; $lastQueryResult = "unavailable"; } # data received elsif ($data) { Log3 $name, 4, "Wunderground $name: RCV"; if ( $data ne "" ) { # fix malformed JSON ... $data =~ s/^[\s\r\n0-9a-zA-Z]*//; $data =~ s/[\s\r\n0-9a-zA-Z]*$//; $data =~ s/[\r\n]+[0-9a-zA-Z]+[\r\n]+//g; eval '$return = decode_json( Encode::encode_utf8($data) ); 1'; if ($@) { Log3 $name, 5, "Wunderground $name: RES ERROR - unable to parse malformed JSON: $@\n" . $data; return undef; } else { Log3 $name, 5, "Wunderground $name: RES\n$data"; } } $lastQueryResult = "undefined"; ####################### # process return data # if ( $return && ref($return) eq "HASH" ) { $lastQueryResult = Wunderground_Hash2Readings( $hash, $return ); } } # state my $stateReadings = AttrVal( $name, "stateReadings", "" ); my $stateReadingsLang = AttrVal( $name, "stateReadingsLang", "en" ); my $stateReadingsFormat = AttrVal( $name, "stateReadingsFormat", "0" ); # $state = # makeSTATE( $name, $stateReadings, # $stateReadingsLang, $stateReadingsFormat ); $state = makeSTATE( $name, $stateReadings, $stateReadingsFormat ); readingsBulkUpdate( $hash, "state", $state ); readingsBulkUpdateIfChanged( $hash, "lastQueryResult", $lastQueryResult ); readingsEndUpdate( $hash, 1 ); return; } sub Wunderground_Hash2Readings($$;$); sub Wunderground_Hash2Readings($$;$) { my ( $hash, $h, $r ) = @_; my $name = $hash->{NAME}; my $lang = AttrVal( $name, "wu_lang", "en" ); my $loop = defined($r) ? 1 : 0; if ( ref($h) eq "HASH" ) { foreach my $k ( keys %{$h} ) { # error return $h->{response}{error}{type} if ( $k eq "response" && defined( $h->{response}{error}{type} ) ); next if ( $k eq "image" || $k eq "response" || $k eq "station_id" || $k =~ /^.*_string$/ ); my $reading; my $cr = $k; # hash level1 renaming $cr = "" if ( $cr eq "current_observation" ); # custom reading $cr = "condition" if ( $cr eq "weather" ); $cr = "dewpoint" if ( $cr eq "dewpoint_c" ); $cr = "humidity" if ( $cr eq "relative_humidity" ); $cr = "pressure" if ( $cr eq "pressure_mb" ); $cr = "rain" if ( $cr eq "precip_1hr_metric" ); $cr = "rain_day" if ( $cr eq "precip_today_metric" ); $cr = "rain_in" if ( $cr eq "precip_1hr_in" ); $cr = "rain_day_in" if ( $cr eq "precip_today_in" ); $cr = "wind_direction" if ( $cr eq "wind_degrees" ); $cr = "wind_gust" if ( $cr eq "wind_gust_kph" ); $cr = "wind_gust_mph" if ( $cr eq "wind_gust_mph" ); $cr = "wind_speed" if ( $cr eq "wind_kph" ); $cr = "wind_speed_mph" if ( $cr eq "wind_mph" ); $cr = "wind_chill" if ( $cr eq "windchill_c" ); $cr = "wind_chill_f" if ( $cr eq "windchill_f" ); $cr = "visibility" if ( $cr eq "visibility_km" ); next if ( $cr =~ /^sun_phase(.*)$/ || $cr eq "date" || $cr eq "wind_dir" ); next if ( $r && $r =~ /^display_location.*$/ ); next if ( $k eq "wind_degrees" && ( !looks_like_number( $h->{$k} ) || $h->{$k} < 0 ) ); # observation_* if ( $cr =~ /^observation_.*$/ ) { $hash->{LAST_OBSERVATION} = $h->{observation_epoch}; next; } # local_* elsif ( $cr =~ /^local_.*$/ ) { $hash->{LAST} = $h->{local_epoch}; next; } # moon_phase elsif ( $cr =~ /^moon_phase(.*)$/ ) { my $sunrise = $h->{moon_phase}{sunrise}{hour} . ":" . $h->{moon_phase}{sunrise}{minute}; my $sunset = $h->{moon_phase}{sunset}{hour} . ":" . $h->{moon_phase}{sunset}{minute}; my $moonrise = $h->{moon_phase}{moonrise}{hour} . ":" . $h->{moon_phase}{moonrise}{minute}; my $moonset = $h->{moon_phase}{moonset}{hour} . ":" . $h->{moon_phase}{moonset}{minute}; $sunrise =~ s/^(\d):(\d\d)$/0$1:$2/; $sunrise =~ s/^(\d\d):(\d)$/$1:0$2/; $sunset =~ s/^(\d):(\d\d)$/0$1:$2/; $sunset =~ s/^(\d\d):(\d)$/$1:0$2/; $moonrise =~ s/^(\d):(\d\d)$/0$1:$2/; $moonrise =~ s/^(\d\d):(\d)$/$1:0$2/; $moonset =~ s/^(\d):(\d\d)$/0$1:$2/; $moonset =~ s/^(\d\d):(\d)$/$1:0$2/; readingsBulkUpdate( $hash, "sunrise", $sunrise ); readingsBulkUpdate( $hash, "sunset", $sunset ); readingsBulkUpdate( $hash, "moonrise", $moonrise ); readingsBulkUpdate( $hash, "moonset", $moonset ); readingsBulkUpdate( $hash, "moon_age", $h->{moon_phase}{ageOfMoon} ); readingsBulkUpdate( $hash, "moon_pct", $h->{moon_phase}{percentIlluminated} ); readingsBulkUpdate( $hash, "moon_phase", $h->{moon_phase}{phaseofMoon} ); } # hourly_forecast elsif ($r && $r =~ /^hourly_forecast(\d+)$/ ) { my $period = $r; $period =~ s/[^\d]//g; $period++; $reading = "hfc" . $period . "_"; readingsBulkUpdate( $hash, $reading . "condition", $h->{conditions} ); readingsBulkUpdate( $hash, $reading . "dewpoint_c", $h->{dewpoint}{metric} ); readingsBulkUpdate( $hash, $reading . "dewpoint_f", $h->{dewpoint}{english} ); readingsBulkUpdate( $hash, $reading . "feelslike_c", $h->{feelslike}{metric} ); readingsBulkUpdate( $hash, $reading . "feelslike_f", $h->{feelslike}{english} ); readingsBulkUpdate( $hash, $reading . "heat_index_c", $h->{heatindex}{metric} ) if ( $h->{heatindex}{metric} ne "-9999" ); readingsBulkUpdateIfChanged( $hash, $reading . "heat_index_c", " " ) if ( $h->{heatindex}{metric} eq "-9999" ); readingsBulkUpdate( $hash, $reading . "heat_index_f", $h->{heatindex}{english} ) if ( $h->{heatindex}{english} ne "-9999" ); readingsBulkUpdateIfChanged( $hash, $reading . "heat_index_f", " " ) if ( $h->{heatindex}{english} eq "-9999" ); readingsBulkUpdate( $hash, $reading . "humidity", $h->{humidity} ); readingsBulkUpdate( $hash, $reading . "icon", $h->{icon} ); readingsBulkUpdate( $hash, $reading . "icon_url", $h->{icon_url} ); readingsBulkUpdate( $hash, $reading . "mslp_c", $h->{mslp}{metric} ); readingsBulkUpdate( $hash, $reading . "mslp_f", $h->{mslp}{english} ); readingsBulkUpdate( $hash, $reading . "pop", $h->{pop} ); readingsBulkUpdate( $hash, $reading . "temp_c", $h->{temp}{metric} ); readingsBulkUpdate( $hash, $reading . "temp_f", $h->{temp}{english} ); readingsBulkUpdate( $hash, $reading . "rain", $h->{qpf}{metric} ); readingsBulkUpdate( $hash, $reading . "rain_in", $h->{qpf}{english} ); readingsBulkUpdate( $hash, $reading . "snow", $h->{snow}{metric} ); readingsBulkUpdate( $hash, $reading . "snow_in", $h->{snow}{english} ); readingsBulkUpdate( $hash, $reading . "wind_direction", $h->{wdir}{degrees} ); readingsBulkUpdate( $hash, $reading . "wind_chill", $h->{windchill}{metric} ) if ( $h->{windchill}{metric} ne "-9999" ); readingsBulkUpdateIfChanged( $hash, $reading . "wind_chill", " " ) if ( $h->{windchill}{metric} eq "-9999" ); readingsBulkUpdate( $hash, $reading . "wind_chill_f", $h->{windchill}{english} ) if ( $h->{windchill}{english} ne "-9999" ); readingsBulkUpdateIfChanged( $hash, $reading . "wind_chill_f", " " ) if ( $h->{windchill}{english} eq "-9999" ); readingsBulkUpdate( $hash, $reading . "wind_speed", $h->{wspd}{metric} ); readingsBulkUpdate( $hash, $reading . "wind_speed_mph", $h->{wspd}{english} ); readingsBulkUpdate( $hash, $reading . "sky", $h->{sky} ); readingsBulkUpdate( $hash, $reading . "UV", $h->{uvi} ); my $time = $h->{FCTTIME}{hour} . ":" . $h->{FCTTIME}{min}; $time =~ s/^(\d):(\d\d)$/0$1:$2/; readingsBulkUpdate( $hash, $reading . "time", $time ); } # simpleforecast elsif ($r && $r =~ /^forecast\/simpleforecast\/forecastday(\d+)$/ ) { my $period = $h->{period} - 1; $reading = "fc" . $period . "_"; readingsBulkUpdate( $hash, $reading . "condition", $h->{conditions} ); readingsBulkUpdate( $hash, $reading . "high_c", $h->{high}{celsius} ); readingsBulkUpdate( $hash, $reading . "high_f", $h->{high}{fahrenheit} ); readingsBulkUpdate( $hash, $reading . "humidity", $h->{avehumidity} ); readingsBulkUpdate( $hash, $reading . "humidity_min", $h->{minhumidity} ); readingsBulkUpdate( $hash, $reading . "humidity_max", $h->{maxhumidity} ); readingsBulkUpdate( $hash, $reading . "icon", $h->{icon} ); readingsBulkUpdate( $hash, $reading . "icon_url", $h->{icon_url} ); readingsBulkUpdate( $hash, $reading . "low_c", $h->{low}{celsius} ); readingsBulkUpdate( $hash, $reading . "low_f", $h->{low}{fahrenheit} ); readingsBulkUpdate( $hash, $reading . "pop", $h->{pop} ); readingsBulkUpdate( $hash, $reading . "rain_day", $h->{qpf_allday}{mm} ); readingsBulkUpdate( $hash, $reading . "rain_day_in", $h->{qpf_allday}{in} ); readingsBulkUpdate( $hash, $reading . "rain_daytime", $h->{qpf_day}{mm} ); readingsBulkUpdate( $hash, $reading . "rain_daytime_in", $h->{qpf_day}{in} ); readingsBulkUpdate( $hash, $reading . "rain_night", $h->{qpf_night}{mm} ); readingsBulkUpdate( $hash, $reading . "rain_night_in", $h->{qpf_night}{in} ); readingsBulkUpdate( $hash, $reading . "snow_day", $h->{snow_allday}{cm} ); readingsBulkUpdate( $hash, $reading . "snow_day_in", $h->{snow_allday}{in} ); readingsBulkUpdate( $hash, $reading . "snow_daytime", $h->{snow_day}{cm} ); readingsBulkUpdate( $hash, $reading . "snow_daytime_in", $h->{snow_day}{in} ); readingsBulkUpdate( $hash, $reading . "snow_night", $h->{snow_night}{cm} ); readingsBulkUpdate( $hash, $reading . "snow_night_in", $h->{snow_night}{in} ); readingsBulkUpdate( $hash, $reading . "wind_direction", $h->{avewind}{degrees} ); readingsBulkUpdate( $hash, $reading . "wind_direction_max", $h->{maxwind}{degrees} ); readingsBulkUpdate( $hash, $reading . "wind_speed", $h->{avewind}{kph} ); readingsBulkUpdate( $hash, $reading . "wind_speed_mph", $h->{avewind}{mph} ); readingsBulkUpdate( $hash, $reading . "wind_speed_max", $h->{maxwind}{kph} ); readingsBulkUpdate( $hash, $reading . "wind_speed_max_mph", $h->{maxwind}{mph} ); last; } # txt_forecast elsif ($r && $r =~ /^forecast\/txt_forecast\/forecastday(\d+)$/ ) { my $period = $h->{period}; my $night = ( $period eq "1" || $period eq "3" || $period eq "5" || $period eq "7" ? "_night" : "" ); if ( $period < 2 ) { $period = "0"; } elsif ( $period < 4 ) { $period = "1"; } elsif ( $period < 6 ) { $period = "2"; } elsif ( $period < 8 ) { $period = "3"; } $reading = "fc" . $period . "_"; my $symbol_c = Encode::encode_utf8( chr(0x202F) . chr(0x00B0) . 'C' ); my $symbol_f = Encode::encode_utf8( chr(0x202F) . chr(0x00B0) . 'F' ); my $symbol_pct = Encode::encode_utf8( chr(0x202F) . '%' ); # my $symbol_kmh = Encode::encode_utf8(chr(0x00A0) . 'km/h'); # my $symbol_mph = Encode::encode_utf8(chr(0x00A0) . 'mph'); $h->{fcttext_metric} =~ s/(\d+)C/$1$symbol_c/g; $h->{fcttext} =~ s/(\d+)F/$1$symbol_f/g; $h->{fcttext_metric} =~ s/(\d+)\s*%/$1$symbol_pct/g; $h->{fcttext} =~ s/(\d+)\s*%/$1$symbol_pct/g; # $h->{fcttext_metric} =~ s/(\d)\s*km\/h/$1$symbol_kmh/g; # $h->{fcttext} =~ s/(\d+)\s*km\/h/$1$symbol_kmh/g; # $h->{fcttext_metric} =~ s/(\d)\s*mph/$1$symbol_mph/g; # $h->{fcttext} =~ s/(\d+)\s*mph/$1$symbol_mph/g; readingsBulkUpdate( $hash, $reading . "icon$night", $h->{icon} ); readingsBulkUpdate( $hash, $reading . "icon_url$night", $h->{icon_url} ); readingsBulkUpdate( $hash, $reading . "pop$night", $h->{pop} ); readingsBulkUpdate( $hash, $reading . "title$night", $h->{title} ); readingsBulkUpdate( $hash, $reading . "text$night", $h->{fcttext_metric} ); readingsBulkUpdate( $hash, $reading . "text_f$night", $h->{fcttext} ); $hash->{readingDesc}{"title$night"}{lang} = $lang if ($lang); $hash->{readingDesc}{"text$night"}{lang} = $lang if ($lang); $hash->{readingDesc}{"text_f$night"}{lang} = $lang if ($lang); last; } # almanac/temp_high elsif ($r && $r =~ /^almanac\/temp_high(.*)$/ ) { $reading = "almanac_high_"; readingsBulkUpdate( $hash, $reading . "year", $h->{recordyear} ); readingsBulkUpdate( $hash, $reading . "c", $h->{normal}{C} ); readingsBulkUpdate( $hash, $reading . "f", $h->{normal}{F} ); readingsBulkUpdate( $hash, $reading . "record_c", $h->{record}{C} ); readingsBulkUpdate( $hash, $reading . "record_f", $h->{record}{F} ); } elsif ($r && $r =~ /^almanac\/temp_low(.*)$/ ) { $reading = "almanac_low_"; readingsBulkUpdate( $hash, $reading . "year", $h->{recordyear} ); readingsBulkUpdate( $hash, $reading . "c", $h->{normal}{C} ); readingsBulkUpdate( $hash, $reading . "f", $h->{normal}{F} ); readingsBulkUpdate( $hash, $reading . "record_c", $h->{record}{C} ); readingsBulkUpdate( $hash, $reading . "record_f", $h->{record}{F} ); } elsif ( ref( $h->{$k} ) eq "HASH" || ref( $h->{$k} ) eq "ARRAY" ) { $reading .= $r . "/" if ( $r && $r ne "" ); $reading .= $cr; Wunderground_Hash2Readings( $hash, $h->{$k}, $reading ); } else { $reading .= $r . "_." if ( $r && $r ne "" ); $reading .= $cr; my $value = $h->{$k}; $value = "" if ( !defined($value) || $value =~ m/^n\/?a$/i ); if ( $reading eq "icon" || $reading eq "icon_url" ) { my $sunrise = ReadingsVal( $hash->{NAME}, "sunrise", 0 ); my $sunset = ReadingsVal( $hash->{NAME}, "sunset", 0 ); $sunrise = time_str2num("1970-01-01 $sunrise:00") if ($sunrise); $sunset = time_str2num("1970-01-01 $sunset:00") if ($sunset); my ( $second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings ) = localtime(); my $currentTime = ( ( $hour * 3600 ) + ( $minute * 60 ) ); # for icon and icon_url, make sure to add nt_ prefix # during the night if ( ( $currentTime > 43200 && $currentTime >= $sunset ) || ( $currentTime <= 43200 && $currentTime < $sunrise ) ) { $value = "nt_" . $value if ( $reading eq "icon" ); $value = $1 . "nt_" . $2 . $3 if ( $reading eq "icon_url" && $value =~ /^(.*\/)([A-Za-z0-9]+)(\.[a-z]+)$/ ); } # for icon and icon_url, make sure to remove nt_ prefix # during the daytime else { $value =~ s/^nt_// if ( $reading eq "icon" ); $value =~ s/\/nt_// if ( $reading eq "icon_url" ); } } elsif ( $reading eq "almanac_.airport_code" ) { $reading = "almanac_airport_code"; } $value = "0" if ( $reading =~ /^wind_(gust|speed).*$/ && looks_like_number($value) && $value < 0 ); $value =~ s/^(\d+)%$/$1/; readingsBulkUpdate( $hash, $reading, $value ); } } } elsif ( ref($h) eq "ARRAY" ) { my $i = 0; foreach ( @{$h} ) { if ( ref($_) eq "HASH" || ref($_) eq "ARRAY" ) { Wunderground_Hash2Readings( $hash, $_, $r . $i ); } else { readingsBulkUpdate( $hash, $r . $i, $_ ); } $i++; } } return "ok" if ( !$loop ); } 1; =pod =item device =item summary Get weather data and forecast from Weather Underground =item summary_DE Ruft Wetterdaten und Vorhersage von Weather Underground ab =begin html

Wunderground

    This module gets weather data and forecast from Weather Underground weather service.

    Define

      define <name> Wunderground <api-key> <query>

      Example:

        define WUweather Wunderground d123ab11bb2c3456 EDDF
        define WUweather Wunderground d123ab11bb2c3456 pws:IBAYERNM70
        define WUweather Wunderground d123ab11bb2c3456 Germany/Berlin




    Set
    • update - refresh data


    Attributes
    • pollInterval - Set regular polling interval in seconds (default=300)
    • wu_bestfct - Use Weather Undergrond Best Forecast for forecast (default=1)
    • wu_features - One or more of the data features to be fetched (default=astronomy,conditions,forecast)
    • wu_lang - Returns the API response in the specified language (default=en)
    • wu_pws - Use personal weather stations for conditions (default=1)


=end html =begin html_DE

Wunderground

    Eine deutsche Version der Dokumentation ist derzeit nicht vorhanden. Die englische Version ist hier zu finden:
=end html_DE =for :application/json;q=META.json 59_Wunderground.pm { "author": [ "Julian Pawlowski " ], "x_fhem_maintainer": [ "loredo" ], "x_fhem_maintainer_github": [ "jpawlowski" ] } =end :application/json;q=META.json =cut