diff --git a/fhem/CHANGED b/fhem/CHANGED index 74aa5c9a3..955f5bcf3 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII - SVN + - feature: new module 98_openweathermap.pm added (betateilchen) - feature: WeatherAsHtmlH() added to 59_Weather.pm (Boris) - feature: new module I2C_BMP180 for reading I2C digital pressure sensor BMP180 or BMP085 connected to Raspberry Pi (Dirk) diff --git a/fhem/FHEM/98_openweathermap.pm b/fhem/FHEM/98_openweathermap.pm new file mode 100644 index 000000000..b14e76dbe --- /dev/null +++ b/fhem/FHEM/98_openweathermap.pm @@ -0,0 +1,591 @@ +# $Id: $ +############################################################################## +# +# 98_openweathermap.pm +# An FHEM Perl module connecting to www.openweathermap.org (owo) +# providing the following tasks: +# +# 1. send weather data from your own weather station to owo network +# +# 2. set a wheater station as datasource inside your fhem installation +# +# 3. retrieve wheather date via owo APII from any weather station +# inside owo network +# +# All tasks can be accessed single or in any desired combination. +# Copyright: betateilchen ® +# e-mail : fhem.development@betateilchen.de +# +# 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 . +# +############################################################################## +# Changelog: + +package main; + +use strict; +use warnings; +use POSIX; +use HttpUtils; +use JSON qw/decode_json/; +use Try::Tiny; +use feature qw/say switch/; + +sub OWO_Set($@); +sub OWO_Get($@); +sub OWO_Attr(@); +sub OWO_Define($$); +sub OWO_GetStatus($;$); +sub OWO_Undefine($$); + +sub OWO_abs2rel($$$); +sub OWO_isday($$); + +################################### +sub +openweathermap_Initialize($) +{ + my ($hash) = @_; + $hash->{SetFn} = "OWO_Set"; + $hash->{GetFn} = "OWO_Get"; + $hash->{DefFn} = "OWO_Define"; + $hash->{UndefFn} = "OWO_Undefine"; + $hash->{AttrFn} = "OWO_Attr"; + + $hash->{AttrList} = "do_not_notify:0,1 loglevel:0,1,2,3,4,5 ". + "owoApiKey owoDebug:0,1 owoGetUrl owoInterval ". + "owoStation owoUser owoRaw:0,1 owoTimestamp:0,1 ". + "owoSrc00 owoSrc01 owoSrc02 owoSrc03 owoSrc04 ". + "owoSrc05 owoSrc06 owoSrc07 owoSrc08 owoSrc09 ". + "owoSrc10 owoSrc11 owoSrc12 owoSrc13 owoSrc14 ". + "owoSrc15 owoSrc16 owoSrc17 owoSrc18 owoSrc19 ". + $readingFnAttributes; +} + +################################### + +sub +OWO_Set($@){ + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + my $usage = "Unknown argument, choose one of stationById stationByGeo stationByName"; + my $response; + + return "No Argument given" if(!defined($a[1])); + + my $urlString = AttrVal($name, "owoGetUrl", undef); + return "Please set attribute owoGetUrl!" if(!defined($urlString)); + + my $cmd = $a[1]; + + given($cmd){ + when("?") { return $usage; } + + when("stationByName"){ + $urlString = $urlString."?q="; + my $count; + my $element = @a; + for ($count = 2; $count < $element; $count++) { + $urlString = $urlString."%20".$a[$count]; + } + } + + when("stationById"){ + $urlString = $urlString."?id=".$a[2]; + } + + when("stationByGeo"){ + $a[2] = AttrVal("global", "latitude", 0) unless(defined($a[2])); + $a[3] = AttrVal("global", "longitude", 0) unless(defined($a[3])); + $urlString = $urlString."?lat=$a[2]&lon=$a[3]"; + } + + default: { return $usage; } + } + + UpdateReadings($hash, $urlString, "c_"); + + + return; +} + +sub +OWO_Get($@){ + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + my $usage = "Unknown argument, choose one of stationById stationByGeo stationByName"; + my $response; + + return "No Argument given" if(!defined($a[1])); + + my $urlString = AttrVal($name, "owoGetUrl", undef); + return "Please set attribute owoGetUrl!" if(!defined($urlString)); + + my $cmd = $a[1]; + + given($cmd){ + when("?") { return $usage; } + + when("stationByName"){ + $urlString = $urlString."?q="; + my $count; + my $element = @a; + for ($count = 2; $count < $element; $count++) { + $urlString = $urlString."%20".$a[$count]; + } + } + + when("stationById"){ + $urlString = $urlString."?id=".$a[2]; + } + + when("stationByGeo"){ + $a[2] = AttrVal("global", "latitude", 0) unless(defined($a[2])); + $a[3] = AttrVal("global", "longitude", 0) unless(defined($a[3])); + $urlString = $urlString."?lat=$a[2]&lon=$a[3]"; + } + + default: { return $usage; } + } + + UpdateReadings($hash, $urlString, "g_"); + + return; + +# return $response; +} + +sub +OWO_Attr(@){ + my @a = @_; + my $hash = $defs{$a[1]}; + my (undef, $name, $attrName, $attrValue) = @a; + + given($attrName){ + + when("owoInterval"){ + if($attrValue ne ""){ + $attrValue = 600 if($attrValue < 600); + $hash->{helper}{INTERVAL} = $attrValue; + } else { + $hash->{helper}{INTERVAL} = 1800; + } + $attr{$name}{$attrName} = $attrValue; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "OWO_GetStatus", $hash, 0); + break; + } + + default { + $attr{$name}{$attrName} = $attrValue; + } + } + return ""; +} + +sub +OWO_GetStatus($;$){ + my ($hash, $local) = @_; + my $name = $hash->{NAME}; + my $loglevel = AttrVal($name, "loglevel", 3); + $local = 0 unless(defined($local)); + + $attr{$name}{"owoInterval"} = 600 if(AttrVal($name,"owoInterval",0) < 600); + +##### start of send job (own weather data) +# +# do we have anything to send from our own station? +# + + my ($user, $pass) = split(":", AttrVal($name, "owoUser","")); + my $station = AttrVal($name, "owoStation", undef); + + if(defined($user) && defined($station)){ + Log $loglevel, "openweather $name started: SendData"; + my $lat = AttrVal("global", "latitude", "?"); + my $lon = AttrVal("global", "longitude", "?"); + my $alt = AttrVal("global", "altitude", "?"); + + my $urlString = "http://$user:$pass\@openweathermap.org/data/post"; + my $dataString = "name=$station&lat=$lat&long=$lon&alt=$alt"; + + my ($count, $paraName, $paraVal, $p, $s, $v, $o); + for ($count = 0; $count < 20; $count++) { + $paraName = "owoSrc".sprintf("%02d",$count); + $paraVal = AttrVal($name, $paraName, undef); + if(defined($paraVal)){ + ($p, $s, $v, $o) = split(":", AttrVal($name, $paraName, "")); + $v = ReadingsVal($s, $v, "?") + $o; + $dataString = $dataString."&$p=$v"; + Log $loglevel, "openweather $name reading: $paraName $p $s $v"; + readingsSingleUpdate($hash, "my_".$p, $v, 1); + } + } + + $dataString .= "&APPID=".AttrVal($name, "owoApiKey", ""); + + my $sendString = $urlString."?".$dataString; + if(AttrVal($name, "owoDebug",1) == 0){ + GetFileFromURL($sendString); + Log $loglevel, "openweather $name sending: $dataString"; + } else { + Log $loglevel, "openweather $name debug: $dataString"; + } + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state","active"); + if(AttrVal($name, "owoTimestamp", 0) == 1){ + readingsBulkUpdate($hash, "my_lastSent", time); + } else { + readingsBulkUpdate($hash, "my_lastSent", localtime(time)); + } + readingsEndUpdate($hash, 1); + } + +##### end of send job + +##### start of update job (set station) +# +# Do we already have a stationId set? +# If yes => update this station +# + my $cId = ReadingsVal($name,"c_stationId", undef); + if(defined($cId)){ + my $cName = ReadingsVal($name,"stationName", ""); + Log $loglevel, "openweather $name retrievingStationData Id: $cId Name: $cName"; + fhem("set $name stationById $cId");# if($cId ne ""); + } + +##### end of update job + + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "OWO_GetStatus", $hash, 0) unless($local == 1); + return; +} + +sub +OWO_Define($$){ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $name = $hash->{NAME}; + + $hash->{helper}{INTERVAL} = 1800; + $hash->{helper}{AVAILABLE} = 1; + + $attr{$name}{"owoDebug"} = 1; + $attr{$name}{"owoInterval"} = 1800; + $attr{$name}{"owoGetUrl"} = "http://api.openweathermap.org/data/2.5/weather"; + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state","defined"); + readingsEndUpdate($hash, 1); + + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "OWO_GetStatus", $hash, 0); + Log 3, "openweather: $name created"; + + return; +} + +sub +OWO_Undefine($$){ + my($hash, $name) = @_; + RemoveInternalTimer($hash); + return undef; +} + +sub +UpdateReadings($$$){ + my ($hash, $url, $prefix) = @_; + my $name = $hash->{NAME}; + my $loglevel = AttrVal($name, "loglevel", 3); + my ($jsonWeather, $response); + + $url .= "&APPID=".AttrVal($name, "owoApiKey", ""); + $response = GetFileFromURL("$url"); + + if(AttrVal($name, "owoDebug", 1) == 1){ + Log $loglevel, "openweather $name response:\n$response"; + } + + my $json = JSON->new->allow_nonref; + try { + $jsonWeather = $json->decode($response); + } catch { + Log $loglevel, "openweather $name error: JSPON decode"; + return; + }; + + readingsBeginUpdate($hash); + if(AttrVal($name, "owoRaw", 0) == 1){ + readingsBulkUpdate($hash, $prefix."rawData", $response); + } else { + readingsBulkUpdate($hash, $prefix."rawData", "not requested"); + } + if(AttrVal($name, "owoTimestamp", 0) == 1){ + readingsBulkUpdate($hash, $prefix."lastWx", $jsonWeather->{dt}); + readingsBulkUpdate($hash, $prefix."sunrise", $jsonWeather->{sys}{sunrise}); + readingsBulkUpdate($hash, $prefix."sunset", $jsonWeather->{sys}{sunset}); + } else { + readingsBulkUpdate($hash, $prefix."lastWx", localtime($jsonWeather->{dt})); + readingsBulkUpdate($hash, $prefix."sunrise", localtime($jsonWeather->{sys}{sunrise})); + readingsBulkUpdate($hash, $prefix."sunset", localtime($jsonWeather->{sys}{sunset})); + } + readingsBulkUpdate($hash, $prefix."stationId", $jsonWeather->{id}); + readingsBulkUpdate($hash, $prefix."lastRxCode", $jsonWeather->{cod}); + readingsBulkUpdate($hash, $prefix."stationName", $jsonWeather->{name}); + readingsBulkUpdate($hash, $prefix."humidity", int($jsonWeather->{main}{humidity})); + readingsBulkUpdate($hash, $prefix."pressureAbs", int($jsonWeather->{main}{pressure})); + readingsBulkUpdate($hash, $prefix."pressureRel", int($jsonWeather->{main}{sea_level})); + readingsBulkUpdate($hash, $prefix."windSpeed", $jsonWeather->{wind}{speed}); + readingsBulkUpdate($hash, $prefix."windDir", int($jsonWeather->{wind}{deg})); + readingsBulkUpdate($hash, $prefix."clouds", $jsonWeather->{clouds}{all}); + readingsBulkUpdate($hash, $prefix."rain3h", $jsonWeather->{rain}{"3h"}); + readingsBulkUpdate($hash, $prefix."snow3h", $jsonWeather->{snow}{"3h"}); + readingsBulkUpdate($hash, $prefix."stationLat", sprintf("%.4f",$jsonWeather->{coord}{lat})); + readingsBulkUpdate($hash, $prefix."stationLon", sprintf("%.4f",$jsonWeather->{coord}{lon})); + readingsBulkUpdate($hash, $prefix."temperature", sprintf("%.1f",$jsonWeather->{main}{temp}-273.15)); + readingsBulkUpdate($hash, $prefix."tempMin", sprintf("%.1f",$jsonWeather->{main}{temp_min}-273.15)); + readingsBulkUpdate($hash, $prefix."tempMax", sprintf("%.1f",$jsonWeather->{main}{temp_max}-273.15)); + readingsBulkUpdate($hash, "state", "active"); + readingsEndUpdate($hash, 1); + + return; +} + +sub +OWO_abs2rel($$$){ +# Messwerte +my $Pa = $_[0]; +my $Temp = $_[1]; +my $Alti = $_[2]; + +# Konstanten +my $g0 = 9.80665; +my $R = 287.05; +my $T = 273.15; +my $Ch = 0.12; +my $a = 0.065; +my $E = 0; + +if($Temp < 9.1) { $E = 5.6402*(-0.0916 + exp(0.06 * $Temp)); } + else { $E = 18.2194*(1.0463 - exp(-0.0666 * $Temp)); } + +my $xp = $Alti * $g0 / ($R*($T+$Temp + $Ch*$E + $a*$Alti/2)); +my $Pr = $Pa*exp($xp); + +return int($Pr); +} + +sub +OWO_isday($$){ + my $name = $_[0]; + my $src = $_[1]; + my $response; + + if(AttrVal($name, "owoTimestamp",0)){ + $response = (time > ReadingsVal($name, $src."_sunrise", 0) && time < ReadingsVal($name, $src."_sunset", 0) ? "1" : "0"); + } else { + $response = "Attribute owoTimestamp not set to 1!"; + } + return $response; +} + + +# OpenWeatherMap API parameters +# ----------------------------- +# 01 wind_dir - wind direction, grad +# 02 wind_speed - wind speed, mps +# 03 temp - temperature, grad C +# 04 humidity - relative humidity, % +# 05 pressure - atmosphere pressure +# 06 wind_gust - speed of wind gust, mps +# 07 rain_1h - rain in recent hour, mm +# 08 rain_24h - rain in recent 24 hours, mm +# 09 rain_today - rain today, mm +# 10 snow - snow in recent 24 hours, mm +# 11 lum - illumination, W/M² +# 12 radiation - radiation +# 13 dewpoint - dewpoint +# 14 uv - UV index +# name - station name +# lat - latitude +# long - longitude +# alt - altitude, m + +1; + +=pod +=begin html + + +

openweathermap

+ + +=end html + +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 348fd0262..2ce40c4bc 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -168,6 +168,7 @@ FHEM/98_dewpoint.pm wherzig http://forum.fhem.de Automatis FHEM/98_dummy.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/98_fheminfo.pm mfr69bs http://forum.fhem.de Sonstiges FHEM/98_notice.pm mfr69bs http://forum.fhem.de Sonstiges +FHEM/98_openweathermap.pm betateilchen http://forum.fhem.de Unterstuetzende Dienste FHEM/98_structure.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/98_telnet.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/98_update.pm mfr69bs http://forum.fhem.de Sonstiges diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html index f9d3a91aa..5d7dc9552 100644 --- a/fhem/docs/commandref_frame.html +++ b/fhem/docs/commandref_frame.html @@ -94,10 +94,11 @@ holiday   LightScene   notify   + openweathermap   PID   PRESENCE   PachLog   - remotecontrol   + remotecontrol   SUNRISE_EL   sequence   speedtest