From a7fda15d2110ccb2b14ae665a70b3e42526edf17 Mon Sep 17 00:00:00 2001 From: justme-1968 Date: Thu, 24 Jul 2014 20:47:07 +0000 Subject: [PATCH] added plz support for public stations git-svn-id: https://svn.fhem.de/fhem/trunk@6313 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/38_netatmo.pm | 288 +++++++++++++++++++++++++++++++++------- 2 files changed, 244 insertions(+), 45 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 5622ddb87..c60b6684b 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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. + - feature: netatmo: added plz support for public stations - change: 70_ENIGMA2: keep reading for recordings up-to-date during standby - bugfix: 98_Text2Speech: - playing mp3files directly, eg: :ring.mp3: - playing any mp3file only text diff --git a/fhem/FHEM/38_netatmo.pm b/fhem/FHEM/38_netatmo.pm index 9a4af4f44..f3067a7d1 100644 --- a/fhem/FHEM/38_netatmo.pm +++ b/fhem/FHEM/38_netatmo.pm @@ -11,6 +11,15 @@ use JSON; use HttpUtils; +my $netatmo_isFritzBox = undef; +sub +netatmo_isFritzBox() +{ + $netatmo_isFritzBox = int( qx( [ -f /usr/bin/ctlmgr_ctl ] && echo 1 || echo 0 ) ) if( !defined( $netatmo_isFritzBox) ); + + return $netatmo_isFritzBox; +} + sub netatmo_Initialize($) { @@ -41,8 +50,9 @@ netatmo_Define($$) my @a = split("[ \t][ \t]*", $def); - my $subtype; my $name = $a[0]; + + my $subtype; if( @a == 3 ) { $subtype = "DEVICE"; @@ -57,29 +67,54 @@ netatmo_Define($$) $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash; - } elsif( ($a[2] eq "PUBLIC" && @a > 5 ) ) { - $subtype = "DEVICE"; - + } elsif( ($a[2] eq "PUBLIC" && @a > 3 ) ) { my $device = $a[3]; - my $lon = $a[4]; - my $lat = $a[5]; - my $rad = $a[6]; - $rad = 0.02 if( !$rad ); - - delete( $hash->{LAST_POLL} ); - $hash->{Device} = $device; - $hash->{Lat} = $lat; - $hash->{Lon} = $lon; - $hash->{Rad} = $rad; + + if( $a[4] && $a[4] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ ) { + $subtype = "MODULE"; + + my $module = ""; + my $readings = ""; + + my @a = splice( @a, 4 ); + while( @a ) { + $module .= " " if( $module ); + $module .= shift( @a ); + + $readings .= " " if( $readings ); + $readings .= shift( @a ); + } + + $hash->{Module} = $module; + $hash->{dataTypes} = $readings; + + my $d = $modules{$hash->{TYPE}}{defptr}{"M$module"}; + return "module $module already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); + + $modules{$hash->{TYPE}}{defptr}{"M$module"} = $hash; + } else { + my $lon = $a[4]; + my $lat = $a[5]; + my $rad = $a[6]; + $rad = 0.02 if( !$rad ); + + $hash->{Lat} = $lat; + $hash->{Lon} = $lon; + $hash->{Rad} = $rad; + + delete( $hash->{LAST_POLL} ); + + $subtype = "DEVICE"; + + my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"}; + return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); + + $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash; + } $hash->{INTERVAL} = 60*15; - my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"}; - return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); - - $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash; - } elsif( ($a[2] eq "MODULE" && @a == 5 ) ) { $subtype = "MODULE"; @@ -110,6 +145,9 @@ netatmo_Define($$) $hash->{password} = $password; $hash->{client_id} = $client_id; $hash->{client_secret} = $client_secret; + + $attr{$name}{nossl} = 1 if( !$init_done && netatmo_isFritzBox() ); + } else { return "Usage: define netatmo device\ define netatmo userid publickey\ @@ -269,7 +307,6 @@ netatmo_initDevice($) my $state_format; if( $device->{data_type} ) { delete($hash->{dataTypes}); - delete($hash->{helper}{dataTypes}); my @reading_names = (); foreach my $type (@{$device->{data_type}}) { @@ -290,6 +327,7 @@ netatmo_initDevice($) $hash->{helper}{readingNames} = \@reading_names; } + $attr{$name}{stateFormat} = $state_format if( !defined($attr{$name}{stateFormat}) && defined($state_format) ); netatmo_poll($hash); @@ -376,6 +414,60 @@ netatmo_getPublicDevices($$;$$$$) }); } } + +sub +netatmo_getAddress($$$$) +{ + my ($hash,$blocking,$lon,$lat) = @_; + my $name = $hash->{NAME}; + + my $iohash = $hash->{IODev}; + $iohash = $hash if( !defined($iohash) ); + + if( $blocking ) { + my($err,$data) = HttpUtils_BlockingGet({ + url => "$iohash->{https}://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lon", + noshutdown => 1, + }); + + return netatmo_dispatch( {hash=>$hash,type=>'address'},$err,$data ); + } else { + HttpUtils_NonblockingGet({ + url => "$iohash->{https}://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lon", + noshutdown => 1, + hash => $hash, + type => 'address', + callback => \&netatmo_dispatch, + }); + } +} +sub +netatmo_getLatLong($$$) +{ + my ($hash,$blocking,$addr) = @_; + my $name = $hash->{NAME}; + + my $iohash = $hash->{IODev}; + $iohash = $hash if( !defined($iohash) ); + + if( $blocking ) { + my($err,$data) = HttpUtils_BlockingGet({ + url => "$iohash->{https}://maps.googleapis.com/maps/api/geocode/json?address=germany+$addr", + noshutdown => 1, + }); + + return netatmo_dispatch( {hash=>$hash,type=>'latlng'},$err,$data ); + } else { + HttpUtils_NonblockingGet({ + url => "$iohash->{https}://maps.googleapis.com/maps/api/geocode/json?address=germany+$addr", + noshutdown => 1, + hash => $hash, + type => 'latlng', + callback => \&netatmo_dispatch, + }); + } +} + sub netatmo_getDeviceDetail($$) { @@ -394,13 +486,13 @@ netatmo_getDeviceDetail($$) sub netatmo_requestDeviceReadings($@) { - my ($hash,$id,$module) = @_; + my ($hash,$id,$type,$module) = @_; my $name = $hash->{NAME}; return undef if( !defined($hash->{IODev}) ); my $iohash = $hash->{IODev}; - my $type = $hash->{dataTypes}; + $type = $hash->{dataTypes} if( !$type ); $type = "Temperature,Co2,Humidity,Noise,Pressure" if( !$type ); netatmo_refreshToken( $iohash ); @@ -418,6 +510,7 @@ netatmo_requestDeviceReadings($@) data => \%data, hash => $hash, type => 'getmeasure', + requested => $type, callback => \&netatmo_dispatch, }); } @@ -450,17 +543,25 @@ netatmo_dispatch($$$) my $hash = $param->{hash}; my $name = $hash->{NAME}; + $hash->{openRequests} -= 1 if( $param->{type} eq 'getmeasure' ); + if( $err ) { Log3 $name, 2, "$name: http request failed: $err"; } elsif( $data ) { Log3 $name, 4, "$name: $data"; - if( $data !~ m/^{.*}$/ ) { - Log3 $name, 2, "$name: invalid json detected: $data"; + $data =~ s/\n//g; + if( $data !~ m/^{.*}$/m ) { + Log3 $name, 2, "$name: invalid json detected: >>$data<<"; return undef; } - my $json = JSON->new->utf8(0)->decode($data); + my $json; + if( netatmo_isFritzBox() ) { + $json = decode_json($data); + } else { + $json = JSON->new->utf8(0)->decode($data); + } if( $json->{error} ) { #$hash->{lastError} = $json->{error}{message}; @@ -471,9 +572,13 @@ netatmo_dispatch($$$) } elsif( $param->{type} eq 'devicelist' ) { netatmo_parseDeviceList($hash,$json); } elsif( $param->{type} eq 'getmeasure' ) { - netatmo_parseReadings($hash,$json); + netatmo_parseReadings($hash,$json,$param->{requested}); } elsif( $param->{type} eq 'publicdata' ) { return netatmo_parsePublic($hash,$json); + } elsif( $param->{type} eq 'address' ) { + return netatmo_parseAddress($hash,$json); + } elsif( $param->{type} eq 'latlng' ) { + return netatmo_parseLatLng($hash,$json); } } } @@ -586,7 +691,7 @@ netatmo_updateReadings($$) my ($seconds) = gettimeofday(); my $latest = 0; - if( @{$readings} ) { + if( $readings && @{$readings} ) { readingsBeginUpdate($hash); my $i = 0; foreach my $reading (sort { $a->[0] <=> $b->[0] } @{$readings}) { @@ -608,38 +713,51 @@ netatmo_updateReadings($$) return ($seconds,$latest); } sub -netatmo_parseReadings($$) +netatmo_parseReadings($$;$) { - my($hash, $json) = @_; + my($hash, $json, $requested) = @_; my $name = $hash->{NAME}; + my $reading_names = $hash->{helper}{readingNames}; + if( $requested ) { + my @readings = split( ',', $requested ); + $reading_names = \@readings; + } + if( $json ) { $hash->{status} = $json->{status}; $hash->{status} = $json->{error}{message} if( $json->{error} ); my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 ); - my @readings = (); + my @r = (); + my $readings = \@r; + $readings = $hash->{readings} if( defined($hash->{readings}) ); if( $hash->{status} eq "ok" ) { foreach my $values ( @{$json->{body}}) { my $time = $values->{beg_time}; my $step_time = $values->{step_time}; - my $i = -1; foreach my $value (@{$values->{value}}) { + my $i = 0; foreach my $reading (@{$value}) { - $i++; next if( !defined($reading) ); - my $name = $hash->{helper}{readingNames}[$i]; + #my $name = $hash->{helper}{readingNames}[$i]; + my $name = lc($reading_names->[$i++]); - push(@readings, [$time, $name, $reading]); + push(@{$readings}, [$time, $name, $reading]); } $time += $step_time if( $step_time ); } } - my ($seconds,undef) = netatmo_updateReadings( $hash, \@readings ); - $hash->{LAST_POLL} = FmtDateTime( $seconds ); + if( $hash->{openRequests} ) { + $hash->{readings} = $readings; + } else { + my ($seconds,undef) = netatmo_updateReadings( $hash, $readings ); + $hash->{LAST_POLL} = FmtDateTime( $seconds ); + delete $hash->{readings}; + } } } } @@ -696,16 +814,67 @@ netatmo_parsePublic($$) } } +sub +netatmo_parseAddress($$) +{ + my($hash, $json) = @_; + my $name = $hash->{NAME}; + + if( $json ) { + $hash->{status} = $json->{status}; + $hash->{status} = $json->{error}{message} if( $json->{error} ); + if( $hash->{status} eq "OK" ) { + if( $json->{results} ) { + return $json->{results}->[0]->{formatted_address}; + } + } else { + return $hash->{status}; + } + } +} + +sub +netatmo_parseLatLng($$) +{ + my($hash, $json) = @_; + my $name = $hash->{NAME}; + + if( $json ) { + $hash->{status} = $json->{status}; + $hash->{status} = $json->{error}{message} if( $json->{error} ); + if( $hash->{status} eq "OK" ) { + if( $json->{results} ) { + return $json->{results}->[0]->{geometry}->{bounds}; + } + } else { + return $hash->{status}; + } + } +} + sub netatmo_pollDevice($) { my ($hash) = @_; + $hash->{openRequests} = 0 if ( !defined( $hash->{openRequests}) ); + if( $hash->{Module} ) { - netatmo_requestDeviceReadings( $hash, $hash->{Device}, $hash->{Module} ); + my @types = split( ' ', $hash->{dataTypes} ); + my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef ); + #$lastupdate = "2014-07-18 14:06:54"; + $hash->{openRequests} += int(@types); + foreach my $module (split( ' ', $hash->{Module} ) ) { + my $type; + $type = shift(@types) if( $hash->{Module} =~ m/ / ); + readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0); + netatmo_requestDeviceReadings( $hash, $hash->{Device}, $type, $module ne $hash->{Device}?$module:undef ); + } } elsif( defined($hash->{Lat}) ) { + $hash->{openRequests} += 1; netatmo_getPublicDevices($hash, 0, $hash->{Lon}, $hash->{Lat}, $hash->{Rad} ); } else { + $hash->{openRequests} += 1; netatmo_requestDeviceReadings( $hash, $hash->{Device} ); } } @@ -736,24 +905,47 @@ netatmo_Get($$@) my $devices = netatmo_getDevices($hash,1); my $ret; foreach my $device (@{$devices}) { - $ret .= "$device->{_id}\t$device->{module_name}\t$device->{hw_version}\t$device->{firmware}\n"; + $ret .= "\n" if( $ret ); + $ret .= "$device->{_id}\t$device->{module_name}\t$device->{hw_version}\t$device->{firmware}"; } $ret = "id\t\t\tname\t\thw\tfw\n" . $ret if( $ret ); $ret = "no devices found" if( !$ret ); return $ret; } elsif( $cmd eq "public" ) { + my $station; + $station = shift @args if( $args[0] && $args[0] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ ); + + if( $args[0] && ( $args[0] =~ m/\d{5}/ + || $args[0] =~ m/^a:/ ) ) { + my $addr = shift @args; + $addr = substr($addr,2) if( $addr =~ m/^a:/ ); + + my $bounds = netatmo_getLatLong( $hash,1,$addr ); + $args[0] = $bounds->{northeast}->{lng}; + $args[1] = $bounds->{northeast}->{lat}; + $args[2] = $bounds->{southwest}->{lng}; + $args[3] = $bounds->{southwest}->{lat}; + } + my $devices = netatmo_getPublicDevices($hash, 1, $args[0], $args[1], $args[2], $args[3] ); my $ret; if( ref($devices) eq "ARRAY" ) { foreach my $device (@{$devices}) { - $ret .= sprintf( "%s\t%.8f\t%.8f\t%i", $device->{_id}, - $device->{place}->{location}->[0], $device->{place}->{location}->[1], - $device->{place}->{altitude} ); + next if( $station && $station ne $device->{_id} ); + $ret .= "\n" if( $ret ); + $ret .= sprintf( "%s\t%.8f\t%.8f\t%i", $device->{_id}, + $device->{place}->{location}->[0], $device->{place}->{location}->[1], + $device->{place}->{altitude} ); + my $addr .= netatmo_getAddress( $hash, 1, $device->{place}->{location}->[0], $device->{place}->{location}->[1] ); next if( ref($device->{measures}) ne "HASH" ); + my $ext; foreach my $module ( keys %{$device->{measures}}) { next if( ref($device->{measures}->{$module}->{res}) ne "HASH" ); + $ext .= "$module "; + $ext .= join(',', @{$device->{measures}->{$module}->{type}}); + $ext .= " "; foreach my $timestamp ( keys %{$device->{measures}->{$module}->{res}} ) { my $i = 0; $ret .= "\t"; @@ -775,8 +967,12 @@ netatmo_Get($$@) last; } } - $ret .= "\n"; - } + + #$ret .= "\n\t\t"; + $ret .= "\t$addr"; + + $ret .= "\n\tdefine netatmo_P$device->{_id} netatmo PUBLIC $device->{_id} $ext" if( $station ); + } } else { $ret = $devices if( !ref($devices) ); } @@ -880,11 +1076,13 @@ netatmo_Attr($$$)
  • update
    trigger an update
  • -
  • public
    +
  • public [<station>] [<address>]
    no arguments -> get all public stations in a radius of 0.025° around global fhem longitude/latitude
    <rad> -> get all public stations in a radius of <rad>° around global fhem longitude/latitude
    <lon> <lat> [<rad>] -> get all public stations in a radius of 0.025° or <rad>° around <lon>/<lat>
    - <lon_ne> <lat_ne> <lon_sw> <lat_sw> -> get all public stations in the area of <lon_ne> <lat_ne> <lon_sw> <lat_sw>
  • + <lon_ne> <lat_ne> <lon_sw> <lat_sw> -> get all public stations in the area of <lon_ne> <lat_ne> <lon_sw> <lat_sw>
    + if <station> is given then print fhem define for this station
    + if <address> is given then list stations in the area of this address. can be given as 5 digit postal code a: followed by a textual address. all spaces have to be replaced by a +.