diff --git a/fhem/CHANGED b/fhem/CHANGED
index e57e0d8cd..4423a05f6 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,7 @@
# 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.
+ - new: 59_LuftdatenInfo: introducing new module to fetch PM, temperature
+ and humidity data from Luftdaten.info
- change: 93_DbRep: new version 4.12.1, get tableinfo changed for MySQL
- bugfix: 98_DOIFtools: fixed browser issues for Chrome and IE
- feature: 98_monitoring: whitelist attribute added
diff --git a/fhem/FHEM/59_LuftdatenInfo.pm b/fhem/FHEM/59_LuftdatenInfo.pm
new file mode 100644
index 000000000..089ec51c1
--- /dev/null
+++ b/fhem/FHEM/59_LuftdatenInfo.pm
@@ -0,0 +1,634 @@
+# Id ##########################################################################
+# $Id$
+
+# copyright ###################################################################
+#
+# 59_LuftdatenInfo.pm
+#
+# Copyright by igami
+#
+# 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 .
+
+# packages ####################################################################
+package main;
+ use strict;
+ use warnings;
+
+ use Encode qw(decode encode);
+
+ use HttpUtils;
+
+# forward declarations ########################################################
+sub LuftdatenInfo_Initialize($);
+
+sub LuftdatenInfo_Define($$);
+sub LuftdatenInfo_Undefine($$);
+sub LuftdatenInfo_Set($@);
+sub LuftdatenInfo_Attr(@);
+
+sub LuftdatenInfo_GetHttpResponse($$);
+sub LuftdatenInfo_ParseHttpResponse($);
+
+sub LuftdatenInfo_readPassword($);
+sub LuftdatenInfo_statusRequest($);
+sub LuftdatenInfo_storePassword($$);
+
+# initialize ##################################################################
+sub LuftdatenInfo_Initialize($) {
+ my ($hash) = @_;
+ my $TYPE = "LuftdatenInfo";
+
+ $hash->{DefFn} = $TYPE."_Define";
+ $hash->{UndefFn} = $TYPE."_Undefine";
+ $hash->{SetFn} = $TYPE."_Set";
+ $hash->{AttrFn} = $TYPE."_Attr";
+
+ $hash->{AttrList} = ""
+ . "disable:1,0 "
+ . "disabledForIntervals "
+ . "interval "
+ . $readingFnAttributes
+ ;
+}
+
+# regular Fn ##################################################################
+sub LuftdatenInfo_Define($$) {
+ my ($hash, $def) = @_;
+ my ($SELF, $TYPE, @id) = split(/[\s]+/, $def);
+ my $rc = eval{
+ require JSON;
+ JSON->import();
+ 1;
+ };
+
+ return(
+ "Error loading JSON. Maybe this module is not installed? "
+ . "\nUnder debian (based) system it can be installed using "
+ . "\"apt-get install libjson-perl\""
+ ) unless($rc);
+
+ delete($hash->{READINGS});
+ delete($hash->{SENSORID1});
+ delete($hash->{SENSORID2});
+ delete($hash->{ADDRESS});
+
+ if(looks_like_number($id[0])){
+ return("Usage: define $TYPE []")
+ if(@id != 1 && @id != 2);
+
+ $id[1] = $id[0] + 1 unless($id[1]);
+
+ $hash->{SENSORID1} = $id[0];
+ $hash->{SENSORID2} = $id[1];
+ $hash->{CONNECTION} = "remote";
+ }
+ else{
+ return("Usage: define $TYPE ")
+ if(@id != 1);
+
+ $hash->{ADDRESS} = $id[0];
+ $hash->{CONNECTION} = "local";
+ }
+
+ my $minInterval = 300;
+ my $interval = AttrVal($SELF, "interval", $minInterval);
+ $interval = $minInterval unless(looks_like_number($interval));
+ $interval = $minInterval if($interval < $minInterval);
+
+ $hash->{INTERVAL} = $interval;
+
+ readingsSingleUpdate($hash, "state", "active", 1);
+
+ LuftdatenInfo_statusRequest($hash);
+
+ return;
+}
+
+sub LuftdatenInfo_Undefine($$) {
+ my ($hash, $arg) = @_;
+
+ HttpUtils_Close($hash);
+ RemoveInternalTimer($hash);
+
+ return;
+}
+
+sub LuftdatenInfo_Set($@) {
+ my ($hash, @a) = @_;
+ my $TYPE = $hash->{TYPE};
+
+ return "\"set $TYPE\" needs at least one argument" if(@a < 2);
+
+ my $SELF = shift @a;
+ my $argument = shift @a;
+ my $value = join(" ", @a) if (@a);
+
+ my %LuftdatenInfo_sets = (
+ "statusRequest" => "statusRequest:noArg",
+ );
+
+ return(
+ "Unknown argument $argument, choose one of "
+ . join(" ", values %LuftdatenInfo_sets)
+ ) if(!exists($LuftdatenInfo_sets{$argument}));
+
+ if(!IsDisabled($SELF)){
+ if($argument eq "statusRequest"){
+ LuftdatenInfo_statusRequest($hash);
+ }
+ }
+
+ return;
+}
+
+sub LuftdatenInfo_Attr(@) {
+ my ($cmd, $SELF, $attribute, $value) = @_;
+ my $hash = $defs{$SELF};
+ my $TYPE = $hash->{TYPE};
+
+ Log3($SELF, 5, "$TYPE ($SELF) - entering LuftdatenInfo_Attr");
+
+ if($attribute eq "disable"){
+ if($value && $value == 1){
+ readingsSingleUpdate($hash, "state", "disabled", 1);
+ }
+ elsif($cmd eq "del" || !$value){
+ LuftdatenInfo_statusRequest($hash);
+
+ readingsSingleUpdate($hash, "state", "active", 1);
+ }
+ }
+ elsif($attribute eq "interval"){
+ my $minInterval = 300;
+ my $interval = $cmd eq "set" ? $value : $minInterval;
+ $interval = $minInterval unless(looks_like_number($interval));
+ $interval = $minInterval if($interval < $minInterval);
+
+ $hash->{INTERVAL} = $interval;
+ }
+
+ return;
+}
+
+# HttpUtils Fn ################################################################
+sub LuftdatenInfo_GetHttpResponse($$) {
+ my ($hash, $id) = @_;
+ my $SELF = $hash->{NAME};
+ my $TYPE = $hash->{TYPE};
+ my $timeout = 5;
+ my $connection = $hash->{CONNECTION};
+
+ Log3($SELF, 5, "$TYPE ($SELF) - entering LuftdatenInfo_GetHttpResponse");
+
+ if($connection eq "remote"){
+ my $param = {
+ url => "http://api.luftdaten.info/v1/sensor/$id",
+ timeout => $timeout,
+ hash => $hash,
+ method => "GET",
+ header => "Accept: application/json",
+ callback => \&LuftdatenInfo_ParseHttpResponse,
+ };
+
+ HttpUtils_NonblockingGet($param);
+ }
+ elsif($connection eq "local"){
+ my $param = {
+ url => "http://".$id."/data.json",
+ timeout => $timeout,
+ hash => $hash,
+ method => "GET",
+ header => "Accept: application/json",
+ callback => \&LuftdatenInfo_ParseHttpResponse,
+ };
+
+ HttpUtils_NonblockingGet($param);
+ }
+}
+
+sub LuftdatenInfo_ParseHttpResponse($) {
+ my ($param, $err, $data) = @_;
+ my $hash = $param->{hash};
+ my $SELF = $hash->{NAME};
+ my $TYPE = $hash->{TYPE};
+ my $connection = $hash->{CONNECTION};
+
+ Log3($SELF, 5, "$TYPE ($SELF) - entering LuftdatenInfo_ParseHttpResponse");
+
+ if($err ne ""){
+ Log3($SELF, 2, "$TYPE ($SELF) - error while request: $err");
+
+ readingsSingleUpdate($hash, "state", "error", 1);
+ }
+ elsif($data eq "[]"){
+ if(index($param->{url}, $hash->{SENSORID2}) > -1){
+ delete($hash->{SENSORID2});
+
+ Log3($SELF, 2, "$TYPE ($SELF) - no second sensor found");
+ }
+ else{
+ Log3($SELF, 2, "$TYPE ($SELF) - no data returned");
+
+ readingsSingleUpdate($hash, "state", "no data", 1);
+ }
+ }
+ elsif($data ne ""){
+ Log3 $SELF, 4, "$TYPE ($SELF) - returned data: $data";
+
+ $data = encode('UTF-8', $data);
+ $data = JSON->new->utf8->decode($data);
+
+ if($param->{url} =~ m/openstreetmap/){
+ my $address = $data->{address};
+
+ readingsSingleUpdate(
+ $hash, "location", "$address->{postcode} $address->{city}", 1
+ );
+ }
+ elsif($connection eq "remote"){
+ my $sensor = @{$data}[-1];
+ my $sensor_type = $sensor->{sensor}{sensor_type}{name};
+ my $timestamp = $sensor->{timestamp};
+
+ return unless($timestamp ge ReadingsVal($SELF, ".timestamp", ""));
+
+ Log3 $SELF, 5, "$TYPE ($SELF) - returned data is newer than readings";
+
+ if($sensor_type eq "SDS011"){
+ Log3 $SELF, 5, "$TYPE ($SELF) - parsing $sensor_type data";
+
+ my $latitude = $sensor->{location}{latitude};
+ my $longitude = $sensor->{location}{longitude};
+
+ unless(ReadingsVal($SELF, "location", undef)){
+ my $param = {
+ url =>
+ "http://nominatim.openstreetmap.org/reverse?"
+ . "format=json&lat=$latitude&lon=$longitude"
+ ,
+ timeout => 5,
+ hash => $hash,
+ method => "GET",
+ header => "Accept: application/json",
+ callback => \&LuftdatenInfo_ParseHttpResponse,
+ };
+
+ HttpUtils_NonblockingGet($param);
+ }
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash, ".timestamp", $timestamp);
+
+ foreach (@{$sensor->{sensordatavalues}}){
+ if($_->{value_type} eq "P1"){
+ readingsBulkUpdate($hash, "PM10", $_->{value});
+ }
+ elsif($_->{value_type} eq "P2"){
+ readingsBulkUpdate($hash, "PM2.5", $_->{value});
+ }
+ }
+
+ readingsBulkUpdateIfChanged($hash, "latitude", $latitude); readingsBulkUpdateIfChanged($hash, "longitude", $longitude); readingsBulkUpdate($hash, "state", "active");
+ readingsEndUpdate($hash, 1);
+
+ my $SENSORID2 = InternalVal($SELF, "SENSORID2", undef);
+
+ LuftdatenInfo_GetHttpResponse($hash, $SENSORID2)
+ if(defined($SENSORID2));
+ }
+ elsif($sensor_type eq "DHT22"){
+ Log3 $SELF, 5, "$TYPE ($SELF) - parsing $sensor_type data";
+
+ if(
+ $sensor->{location}{latitude} ne
+ ReadingsVal($SELF, "latitude", "")
+ || $sensor->{location}{longitude} ne
+ ReadingsVal($SELF, "longitude", "")
+ ){
+ delete($hash->{SENSORID2});
+
+ Log3(
+ $SELF, 2
+ , "$TYPE ($SELF) - DHT22 position differs from SDS011 position"
+ );
+
+ return;
+ }
+ else{
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash, ".timestamp", $timestamp);
+
+ foreach (@{$sensor->{sensordatavalues}}){
+ $_->{value} =~ m/^(\S+)(\s|$)/;
+
+ if($_->{value_type} eq "temperature"){
+ readingsBulkUpdate($hash, "temperature", $1);
+ }
+ elsif($_->{value_type} eq "humidity"){
+ readingsBulkUpdate($hash, "humidity", $1);
+ }
+ }
+
+ readingsBulkUpdate($hash, "state", "active");
+ readingsEndUpdate($hash, 1);
+ }
+ }
+ }
+ elsif($connection eq "local"){
+ readingsBeginUpdate($hash);
+
+ foreach (@{$data->{sensordatavalues}}){
+ $_->{value} =~ m/^(\S+)(\s|$)/;
+
+ if($_->{value_type} eq "temperature"){
+ readingsBulkUpdate($hash, "temperature", $1);
+ }
+ elsif($_->{value_type} eq "humidity"){
+ readingsBulkUpdate($hash, "humidity", $1);
+ }
+ elsif($_->{value_type} eq "SDS_P1"){
+ readingsBulkUpdate($hash, "PM10", $1);
+ }
+ elsif($_->{value_type} eq "SDS_P2"){
+ readingsBulkUpdate($hash, "PM2.5", $1);
+ }
+ elsif($_->{value_type} eq "signal"){
+ readingsBulkUpdate($hash, "signal", $1);
+ }
+ }
+ readingsEndUpdate($hash, 1);
+ }
+ }
+
+ return;
+}
+
+# module Fn ###################################################################
+sub LuftdatenInfo_statusRequest($) {
+ my ($hash) = @_;
+ my $SELF = $hash->{NAME};
+ my $TYPE = $hash->{TYPE};
+ my $interval = $hash->{INTERVAL};
+ my $connection = $hash->{CONNECTION};
+
+ Log3($SELF, 5, "$TYPE ($SELF) - entering LuftdatenInfo_statusRequest");
+
+ RemoveInternalTimer($hash);
+
+ return if(IsDisabled($SELF));
+
+ InternalTimer(
+ gettimeofday() + $interval, "LuftdatenInfo_statusRequest", $hash
+ );
+
+ if($connection eq "remote"){
+ LuftdatenInfo_GetHttpResponse($hash, $hash->{SENSORID1});
+ }
+ elsif($connection eq "local"){
+ LuftdatenInfo_GetHttpResponse($hash, $hash->{ADDRESS});
+ }
+
+ return;
+}
+
+1;
+
+# commandref ##################################################################
+=pod
+=item summary provides data from Luftdaten.info
+=item summary_DE stellt Daten von Luftdaten.info bereit
+
+=begin html
+
+
+LuftdatenInfo
+(en | de)
+
+
+ LuftdatenInfo is the FHEM module to read particulate matter, temperature and humidity values from the self-assembly particulate matter sensors from Luftdaten.info.
+ The values can be queried directly from the server or locally.
+ A local query should only be made if the sensor is NOT sendig data to the server, otherwise the sensor may block and need to be restarted.
+
+ Prerequisites
+
+ The Perl module "JSON" is required.
+ Under Debian (based) system, this can be installed using
+ "apt-get install libjson-perl"
.
+
+
+
+ Define
+
+
+ define <name> LuftdatenInfo
+ (<SDS011sensorID> [<DHT22sensorID>]|<ip>)
+
+ To query the data from the server, the SDS011 SensorID must be specified.
+ The SensorID stands right at
+
+ http://maps.luftdaten.info/
+
+ . The DHT22 SensorID is usually the SDS011 SensorID + 1 and does not have
+ to be specified explicitly. While parsing the data the location values
+ from both sensors will be compared and a message will be written into the
+ log if they differ.
+ For a local query of the data, the IP address or hostname must be
+ specified.
+
+
+ Set
+
+ -
+
statusRequest
+ Starts a status request.
+
+
+
+ Readings
+
+ -
+
PM10
+ Quantity of particles with a diameter of less than 10 μm in μg / m³
+
+ -
+
PM2.5
+ Quantity of particles with a diameter of less than 2.5 μm in μg / m³
+
+ -
+
temperature
+ Temperature in °C
+
+ -
+
humidity
+ Relative humidity in%
+
+ -
+
latitude
+ latitude
+ Only available with remote query.
+
+ -
+
location
+ location as "postcode city"
+ Only available with remote query.
+
+ -
+
longitude
+ longitude
+ Only available with remote query.
+
+ -
+
signal
+ WLAN signal strength in dBm
+ Only available with local query.
+
+
+
+ Attribute
+
+
+
+
+=end html
+
+=begin html_DE
+
+
+LuftdatenInfo
+(en | de)
+
+
+ LuftdatenInfo ist das FHEM Modul um Feinstaub-, Temperatur- und
+ Luftfeuchtichkeitswerte von den selbstbau Feinstaub Sensoren von
+ Luftdaten.info auszulesen.
+ Dabei können die Werte direkt vom Server oder auch lokal abgefragt
+ werden.
+ Eine lokale Abfrage sollte nur erfolgen, wenn der Sensor NICHT an den
+ Server sendet, sonst kann es passieren, dass der Sensor blockiert und
+ neugestartet werden muss.
+
+ Vorraussetzungen
+
+ Das Perl-Modul "JSON" wird benötigt.
+ Unter Debian (basierten) System, kann dies mittels
+ "apt-get install libjson-perl"
installiert werden.
+
+
+
+ Define
+
+
+ define <name> LuftdatenInfo
+ (<SDS011sensorID> [<DHT22sensorID>]|<ip>)
+
+ Für eine Abfrage der Daten vom Server muss die SensorID von dem
+ SDS011 Sensor angegeben werden. Diese steht rechts auf der Seite
+
+ http://maps.luftdaten.info/
+
+ . Die DHT22 SensorID entspricht normalerweise der SDS011 SensorID + 1 und
+ muss nicht explizit mit angegeben werden. Bei einer Abfrage werden die
+ beiden Positionsangaben verglichen und bei Abweichung eine Meldung ins
+ Log geschrieben.
+ Für eine lokale Abfrage der Daten muss die IP Addresse oder der
+ Hostname angegeben werden.
+
+
+ Set
+
+ -
+
statusRequest
+ Startet eine Abfrage der Daten.
+
+
+
+ Readings
+
+ -
+
PM10
+ Menge der Partikel mit einem Durchmesser von weniger als 10 µm in µg/m³
+
+ -
+
PM2.5
+ Menge der Partikel mit einem Durchmesser von weniger als 2.5 µm in µg/m³
+
+ -
+
temperature
+ Temperatur in °C
+
+ -
+
humidity
+ Relative Luftfeuchtgkeit in %
+
+ -
+
latitude
+ Breitengrad
+ Nur bei remote Abfrage verfügbar.
+
+ -
+
location
+ Standort als "Postleitzahl Ort"
+ Nur bei remote Abfrage verfügbar.
+
+ -
+
longitude
+ Längengrad
+ Nur bei remote Abfrage verfügbar.
+
+ -
+
signal
+ WLAN Signalstärke in dBm
+ Nur bei local Abfrage verfügbar.
+
+
+
+ Attribute
+
+
+
+
+=end html_DE
+=cut
diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt
index 230d9b62c..45a1067b1 100644
--- a/fhem/MAINTAINER.txt
+++ b/fhem/MAINTAINER.txt
@@ -245,6 +245,7 @@ FHEM/56_POKEYS.pm axelberner http://forum.fhem.de Sonstiges
FHEM/57_Calendar.pm neubert http://forum.fhem.de Unterstuetzende Dienste/Kalendermodule
FHEM/57_CALVIEW.pm chris1284 http://forum.fhem.de Unterstuetzende Dienste/Kalendermodule
FHEM/59_HCS.pm mfr69bs http://forum.fhem.de Automatisierung
+FHEM/59_LuftdatenInfo igami http://forum.fhem.de Bastelecke
FHEM/59_OPENWEATHER.pm tupol http://forum.fhem.de Unterstuetzende Dienste/Wettermodule (Link als PM an tupol)
FHEM/59_Twilight.pm dietmar63 http://forum.fhem.de Unterstuetzende Dienste/Wettermodule
FHEM/59_PROPLANTA.pm tupol http://forum.fhem.de Unterstuetzende Dienste/Wettermodule (Link als PM an tupol)