From 68a1f41f3a5d66b6f3c6559eeef3a105baffb4f8 Mon Sep 17 00:00:00 2001 From: moises <> Date: Wed, 28 Feb 2018 22:12:56 +0000 Subject: [PATCH] 32_withings, 38_netatmo: maintenance update git-svn-id: https://svn.fhem.de/fhem/trunk@16296 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/32_withings.pm | 49 +++- fhem/FHEM/38_netatmo.pm | 580 ++++++++++++++++++++++++++++++++------- 3 files changed, 523 insertions(+), 107 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 2c0e293fe..080c05e85 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. + - bugfix: 32_withings, 38_netatmo: maintenance update - added: 72_XiaomiDevice: added new module for Xiaomi WiFi devices - feature: 93_DbRep: V7.14.0, new command syncStandby - bugfix: 21_HEOS: decode_json() in a eval, fix play_previous diff --git a/fhem/FHEM/32_withings.pm b/fhem/FHEM/32_withings.pm index c7461db1a..256ad1865 100644 --- a/fhem/FHEM/32_withings.pm +++ b/fhem/FHEM/32_withings.pm @@ -10,7 +10,7 @@ # # ############################################################################## -# Release 04 / 2017-06-25 +# Release 05 / 2018-03-01 package main; @@ -677,7 +677,7 @@ sub withings_connect($) { foreach my $d (keys %defs) { next if(!defined($defs{$d})); next if($defs{$d}{TYPE} ne "autocreate"); - return undef if(AttrVal($defs{$d}{NAME},"disable",undef)); + return undef if(IsDisabled($defs{$d}{NAME})); } my $autocreated = 0; @@ -717,6 +717,12 @@ sub withings_connect($) { my $d = $modules{$hash->{TYPE}}{defptr}{"D$device->{deviceid}"}; $d->{association} = $device->{association} if($device->{association}); + #get user from association + if(defined($device->{deviceproperties})){ + $d->{User} = $device->{deviceproperties}{linkuserid} if(defined($device->{deviceproperties}{linkuserid})); + $d->{color} = $device->{deviceproperties}{product_color} if(defined($device->{deviceproperties}{product_color})); + } + Log3 $name, 2, "$name: device '$device->{deviceid}' already defined"; next; } @@ -795,6 +801,12 @@ sub withings_autocreate($) { my $d = $modules{$hash->{TYPE}}{defptr}{"D$device->{deviceid}"}; $d->{association} = $device->{association} if($device->{association}); + #get user from association + if(defined($device->{deviceproperties})){ + $d->{User} = $device->{deviceproperties}{linkuserid} if(defined($device->{deviceproperties}{linkuserid})); + $d->{color} = $device->{deviceproperties}{product_color} if(defined($device->{deviceproperties}{product_color})); + } + Log3 $name, 2, "$name: device '$device->{deviceid}' already defined"; next; } @@ -1449,7 +1461,7 @@ sub withings_poll($;$) { RemoveInternalTimer($hash); - return undef if(AttrVal($name,"disable",0) eq "1"); + return undef if(IsDisabled($name)); my $resolve = inet_aton("healthmate.withings.com"); @@ -1576,6 +1588,17 @@ sub withings_getUserReadingsDaily($) { callback => \&withings_Dispatch, }); +# HttpUtils_NonblockingGet({ +# url => "https://scalews.health.nokia.com/cgi-bin/v2/activity", +# timeout => 60, +# noshutdown => 1, +# data => {sessionid => $hash->{IODev}->{SessionKey}, userid=> $hash->{User}, startdateymd => $startdateymd, enddateymd => $enddateymd, appname => 'hmw', appliver => $hash->{IODev}->{helper}{appliver}, apppfm => 'web', action => 'getbyuserid'}, +# hash => $hash, +# type => 'userDailyActivity', +# enddate => int($enddate), +# callback => \&withings_Dispatch, +# }); + my ($seconds) = gettimeofday(); $hash->{LAST_POLL} = FmtDateTime( $seconds ); readingsSingleUpdate( $hash, ".pollDaily", $seconds, 0 ); @@ -1760,8 +1783,8 @@ sub withings_parseMeasureGroups($$) { my $name = $hash->{NAME}; #parse Log3 "withings", 5, "$name: parsemeasuregroups"; - my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastData", 0 ); + my ($now) = int(time); + my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); my $newlastupdate = $lastupdate; $hash->{status} = $json->{status}; @@ -1828,7 +1851,7 @@ sub withings_parseMeasurements($$) { #parse Log3 "withings", 5, "$name: parsemeasurements"; my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastData", 0 ); + my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); my $newlastupdate = $lastupdate; my $i = 0; @@ -1917,7 +1940,7 @@ sub withings_parseAggregate($$) { #return undef; my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastAggregate", 0 ); + my $lastupdate = ReadingsVal( $name, ".lastAggregate", ($now-21*24*60*60) ); my $newlastupdate = $lastupdate; my $i = 0; my $unfinished; @@ -2031,7 +2054,7 @@ sub withings_parseActivity($$) { Log3 "withings", 5, "$name: parseactivity"; my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastActivity", 0 ); + my $lastupdate = ReadingsVal( $name, ".lastActivity", ($now-21*24*60*60) ); my $newlastupdate = $lastupdate; my $i = 0; my $unfinished; @@ -2140,8 +2163,8 @@ sub withings_parseVasistas($$;$) { Log3 "withings", 5, "$name: parsevasistas"; my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastData", 0 ); - $lastupdate = ReadingsVal( $name, ".lastDebug", 0 ) if($datatype =~ /Debug/); + my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); + $lastupdate = ReadingsVal( $name, ".lastDebug", ($now-21*24*60*60) ) if($datatype =~ /Debug/); if( $json ) { $hash->{status} = $json->{status}; @@ -2254,7 +2277,7 @@ sub withings_parseTimeline($$) { Log3 "withings", 5, "$name: parsemetimeline "; my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastAlert", 0 ); + my $lastupdate = ReadingsVal( $name, ".lastAlert", ($now-21*24*60*60) ); my $newlastupdate = $lastupdate; $hash->{status} = $json->{status}; @@ -2344,8 +2367,8 @@ sub withings_parseEvents($$) { #parse Log3 "withings", 5, "$name: parseevents"; my ($now) = time; - my $lastupdate = ReadingsVal( $name, ".lastData", 0 ); - my $lastalertupdate = ReadingsVal( $name, ".lastAlert", 0 ); + my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); + my $lastalertupdate = ReadingsVal( $name, ".lastAlert", ($now-21*24*60*60) ); my $newlastupdate = $lastupdate; $hash->{status} = $json->{status}; diff --git a/fhem/FHEM/38_netatmo.pm b/fhem/FHEM/38_netatmo.pm index 37b9c8e4e..5f7af7233 100644 --- a/fhem/FHEM/38_netatmo.pm +++ b/fhem/FHEM/38_netatmo.pm @@ -3,14 +3,15 @@ # # 38_netatmo.pm # -# 2017 Markus M. +# 2018 Markus Moises < vorname at nachname . de > +# # Based on original code by justme1968 # # https://forum.fhem.de/index.php/topic,53500.0.html # # ############################################################################## -# Release 12 / 2017-05-29 +# Release 16 / 2018-03-01 package main; @@ -51,13 +52,14 @@ netatmo_Initialize($) $hash->{DbLog_splitFn} = "netatmo_DbLog_splitFn"; $hash->{AttrFn} = "netatmo_Attr"; $hash->{AttrList} = "IODev ". - "disable:1 ". + "disable:0,1 ". "interval ". "videoquality:poor,low,medium,high ". "ignored_device_ids ". "setpoint_duration ". "webhookURL webhookPoll:0,1 ". - "addresslimit "; + "addresslimit ". + "serverAPI "; $hash->{AttrList} .= $readingFnAttributes; } @@ -72,6 +74,7 @@ netatmo_Define($$) my $name = $a[0]; $hash->{status} = "initialized"; + $hash->{helper}{last_status_store} = 0; my $subtype; if($a[2] eq "WEBHOOK") { @@ -98,7 +101,7 @@ netatmo_Define($$) $hash->{openRequests} = 0; - $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*15 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"}; return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); @@ -207,7 +210,7 @@ netatmo_Define($$) } - $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*30 if( !$hash->{helper}{INTERVAL} ); $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name)); $attr{$name}{devStateIcon} = ".*:no-icon" if( !defined($attr{$name}{devStateIcon}) && defined($name)); @@ -222,7 +225,7 @@ netatmo_Define($$) $hash->{openRequests} = 0; - $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*15 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"M$module"}; return "module $module already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); @@ -238,7 +241,7 @@ netatmo_Define($$) $hash->{openRequests} = 0; - $hash->{INTERVAL} = 60*60 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*60 if( !$hash->{helper}{INTERVAL} ); $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name)); $attr{$name}{devStateIcon} = ".*:no-icon" if( !defined($attr{$name}{devStateIcon}) && defined($name)); $attr{$name}{'event-on-change-reading'} = ".*" if( !defined($attr{$name}{'event-on-change-reading'}) && defined($name)); @@ -262,7 +265,7 @@ netatmo_Define($$) $hash->{openRequests} = 0; - $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*30 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"R$device"}; return "relay $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); @@ -280,13 +283,47 @@ netatmo_Define($$) $hash->{openRequests} = 0; $hash->{dataTypes} = "Temperature,Sp_Temperature,BoilerOn,BoilerOff"; - $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*30 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"T$module"}; return "thermostat $module already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); $modules{$hash->{TYPE}}{defptr}{"T$module"} = $hash; + } elsif( ($a[2] eq "HEATINGHOME" && @a == 4 ) ) { + $subtype = "HEATINGHOME"; + + my $home = $a[@a-1]; + + $hash->{Home} = $home; + + $hash->{openRequests} = 0; + #$hash->{dataTypes} = "Temperature,Sp_Temperature,BoilerOn,BoilerOff"; + $hash->{helper}{INTERVAL} = 60*30 if( !$hash->{helper}{INTERVAL} ); + + my $d = $modules{$hash->{TYPE}}{defptr}{"E$home"}; + return "heating home $home already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); + + $modules{$hash->{TYPE}}{defptr}{"E$home"} = $hash; + + } elsif( ($a[2] eq "HEATINGROOM" && @a == 5 ) ) { + $subtype = "HEATINGROOM"; + + my $room = $a[@a-1]; + my $home = $a[@a-2]; + + $hash->{Home} = $home; + $hash->{Room} = $room; + + $hash->{openRequests} = 0; + $hash->{dataTypes} = "Temperature,Sp_Temperature,heating_power_request,BoilerOn,BoilerOff"; + $hash->{helper}{INTERVAL} = 60*30 if( !$hash->{helper}{INTERVAL} ); + + my $d = $modules{$hash->{TYPE}}{defptr}{"O$room"}; + return "heating room $room already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); + + $modules{$hash->{TYPE}}{defptr}{"O$room"} = $hash; + } elsif( ($a[2] eq "HOME" && @a == 4 ) ) { $subtype = "HOME"; @@ -294,7 +331,7 @@ netatmo_Define($$) $hash->{Home} = $home; - $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*15 if( !$hash->{helper}{INTERVAL} ); $attr{$name}{videoquality} = "medium" if( !defined($attr{$name}{videoquality}) && defined($name)); @@ -312,7 +349,7 @@ netatmo_Define($$) $hash->{Home} = $home; $hash->{Person} = $person; - $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*15 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"P$person"}; return "person $person already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); @@ -328,7 +365,7 @@ netatmo_Define($$) $hash->{Home} = $home; $hash->{Camera} = $camera; - $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*15 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"C$camera"}; return "camera $camera already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); @@ -344,7 +381,7 @@ netatmo_Define($$) $hash->{Tag} = $tag; $hash->{Camera} = $camera; - #$hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} ); + #$hash->{helper}{INTERVAL} = 60*15 if( !$hash->{helper}{INTERVAL} ); my $d = $modules{$hash->{TYPE}}{defptr}{"G$tag"}; return "tag $tag already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); @@ -383,11 +420,12 @@ netatmo_Define($$) $hash->{helper}{client_id} = $client_id; $hash->{helper}{client_secret} = $client_secret; - $hash->{INTERVAL} = 60*60 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = 60*60 if( !$hash->{helper}{INTERVAL} ); $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name)); $modules{$hash->{TYPE}}{defptr}{"account"} = $hash; + $hash->{helper}{apiserver} = AttrVal($name, "serverAPI", "api.netatmo.com"); } else { return "Usage: define netatmo device\ @@ -414,6 +452,8 @@ netatmo_Define($$) netatmo_pingCamera($hash) if( $hash->{SUBTYPE} eq "CAMERA" ); netatmo_poll($hash) if( $hash->{SUBTYPE} eq "RELAY" ); netatmo_poll($hash) if( $hash->{SUBTYPE} eq "THERMOSTAT" ); + netatmo_initHeatingHome($hash) if( $hash->{SUBTYPE} eq "HEATINGHOME" ); + #netatmo_initHeatingRoom($hash) if( $hash->{SUBTYPE} eq "HEATINGROOM" ); netatmo_addExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" ); } @@ -444,6 +484,8 @@ sub netatmo_InitWait($) { netatmo_pingCamera($hash) if( $hash->{SUBTYPE} eq "CAMERA" ); netatmo_poll($hash) if( $hash->{SUBTYPE} eq "RELAY" ); netatmo_poll($hash) if( $hash->{SUBTYPE} eq "THERMOSTAT" ); + netatmo_initHeatingHome($hash) if( $hash->{SUBTYPE} eq "HEATINGHOME" ); + #netatmo_initHeatingRoom($hash) if( $hash->{SUBTYPE} eq "HEATINGROOM" ); netatmo_addExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" ); } @@ -476,6 +518,8 @@ netatmo_Notify($$) netatmo_pingCamera($hash) if( $hash->{SUBTYPE} eq "CAMERA" ); netatmo_poll($hash) if( $hash->{SUBTYPE} eq "RELAY" ); netatmo_poll($hash) if( $hash->{SUBTYPE} eq "THERMOSTAT" ); + netatmo_initHeatingHome($hash) if( $hash->{SUBTYPE} eq "HEATINGHOME" ); + #netatmo_initHeatingRoom($hash) if( $hash->{SUBTYPE} eq "HEATINGROOM" ); netatmo_addExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" ); @@ -497,6 +541,8 @@ netatmo_Undefine($$) delete( $modules{$hash->{TYPE}}{defptr}{"P$hash->{Person}"} ) if( $hash->{SUBTYPE} eq "PERSON" ); delete( $modules{$hash->{TYPE}}{defptr}{"R$hash->{Relay}"} ) if( $hash->{SUBTYPE} eq "RELAY" ); delete( $modules{$hash->{TYPE}}{defptr}{"T$hash->{Thermostat}"} ) if( $hash->{SUBTYPE} eq "THERMOSTAT" ); + delete( $modules{$hash->{TYPE}}{defptr}{"E$hash->{Home}"} ) if( $hash->{SUBTYPE} eq "HEATINGHOME" ); + delete( $modules{$hash->{TYPE}}{defptr}{"O$hash->{Room}"} ) if( $hash->{SUBTYPE} eq "HEATINGROOM" ); netatmo_removeExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" ); return undef; @@ -507,7 +553,7 @@ netatmo_Set($$@) { my ($hash, $name, $cmd, @parameters) = @_; - + $hash->{SUBTYPE} = "unknown" if(!defined($hash->{SUBTYPE})); my $list = ""; $list = "autocreate:noArg autocreate_homes:noArg autocreate_thermostats:noArg autocreate_homecoachs:noArg" if( $hash->{SUBTYPE} eq "ACCOUNT" ); #$list .= " unban:noArg" if( $hash->{SUBTYPE} eq "ACCOUNT" ); @@ -516,7 +562,7 @@ netatmo_Set($$@) $list = "enable disable irmode:auto,always,never led_on_live:on,off mirror:off,on audio:on,off" if ($hash->{SUBTYPE} eq "CAMERA"); $list = "enable disable light_mode:auto,on,off floodlight intensity:slider,0,1,100 night_always:true,false night_person:true,false night_vehicle:true,false night_animal:true,false night_movement:true,false" if ($hash->{SUBTYPE} eq "CAMERA" && defined($hash->{model}) && $hash->{model} eq "NOC"); $list = "calibrate:noArg" if ($hash->{SUBTYPE} eq "TAG"); - if ($hash->{SUBTYPE} eq "THERMOSTAT") + if ($hash->{SUBTYPE} eq "THERMOSTAT" || $hash->{SUBTYPE} eq "HEATINGROOM") { $list = "setpoint_mode:off,hg,away,program,manual,max setpoint_temp:5.0,5.5,6.0,6.5,7.0,7.5,8.0,8.5,9.0,9.5,10.0,10.5,11.0,11.5,12.0,12.5,13.0,13.5,14.0,14.5,15.0,15.5,16.0,16.5,17.0,17.5,18.0,18.5,19.0,19.5,20.0,20.5,21.0,21.5,22.0,22.5,23.0,23.5,24.0,24.5,25.0,25.5,26.0,26.5,27.0,27.5,28.0,28.5,29.0,29.5,30.0"; $list = "setpoint_mode:off,hg,away,program,manual,max program:".$hash->{schedulenames}." setpoint_temp:5.0,5.5,6.0,6.5,7.0,7.5,8.0,8.5,9.0,9.5,10.0,10.5,11.0,11.5,12.0,12.5,13.0,13.5,14.0,14.5,15.0,15.5,16.0,16.5,17.0,17.5,18.0,18.5,19.0,19.5,20.0,20.5,21.0,21.5,22.0,22.5,23.0,23.5,24.0,24.5,25.0,25.5,26.0,26.5,27.0,27.5,28.0,28.5,29.0,29.5,30.0" if(defined($hash->{schedulenames})); @@ -605,6 +651,7 @@ netatmo_Set($$@) my $setting = $parameters[0]; my $duration = $parameters[1]; return "You have to define a mode" if(!defined($setting) || $setting eq ""); + return netatmo_setRoomMode($hash,$setting,$duration) if($hash->{SUBTYPE} eq "HEATINGROOM"); return netatmo_setThermostatMode($hash,$setting,$duration); return undef; } @@ -612,6 +659,7 @@ netatmo_Set($$@) my $setting = $parameters[0]; my $duration = $parameters[1]; return "You have to define a temperature" if(!defined($setting) || $setting eq ""); + return netatmo_setRoomTemp($hash,$setting,$duration) if($hash->{SUBTYPE} eq "HEATINGROOM"); return netatmo_setThermostatTemp($hash,$setting,$duration); return undef; } @@ -655,7 +703,7 @@ netatmo_getToken($) return Log3 $name, 1, "$name: No password was found! (getToken)" if(!defined($hash->{helper}{password})); my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/oauth2/token", + url => "https://".$hash->{helper}{apiserver}."/oauth2/token", timeout => 5, noshutdown => 1, data => {grant_type => 'password', client_id => $hash->{helper}{client_id}, client_secret=> $hash->{helper}{client_secret}, username => netatmo_decrypt($hash->{helper}{username}), password => netatmo_decrypt($hash->{helper}{password}), scope => 'read_station read_thermostat write_thermostat write_camera read_camera access_camera read_presence write_presence access_presence read_homecoach'}, @@ -704,7 +752,7 @@ netatmo_refreshToken($;$) Log3 $name, 3, "$name: refreshing token"; - my $resolve = inet_aton("api.netatmo.com"); + my $resolve = inet_aton($hash->{helper}{apiserver}); if(!defined($resolve)) { $hash->{STATE} = "DNS error"; @@ -712,7 +760,7 @@ netatmo_refreshToken($;$) delete($hash->{access_token}); delete($hash->{access_token_app}); InternalTimer( gettimeofday() + 1800, "netatmo_refreshTokenTimer", $hash); - Log3 $name, 1, "$name: DNS error, cannot resolve api.netatmo.com"; + Log3 $name, 1, "$name: DNS error, cannot resolve ".$hash->{helper}{apiserver}; return undef; } else { $hash->{network} = "ok"; @@ -727,7 +775,7 @@ netatmo_refreshToken($;$) if( $nonblocking ) { HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/oauth2/token", + url => "https://".$hash->{helper}{apiserver}."/oauth2/token", timeout => 20, noshutdown => 1, data => {grant_type => 'refresh_token', client_id => $hash->{helper}{client_id}, client_secret=> $hash->{helper}{client_secret}, refresh_token => $hash->{refresh_token}}, @@ -737,7 +785,7 @@ netatmo_refreshToken($;$) }); } else { my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/oauth2/token", + url => "https://".$hash->{helper}{apiserver}."/oauth2/token", timeout => 5, noshutdown => 1, data => {grant_type => 'refresh_token', client_id => $hash->{helper}{client_id}, client_secret=> $hash->{helper}{client_secret}, refresh_token => $hash->{refresh_token}}, @@ -804,6 +852,11 @@ netatmo_refreshTokenTimer($) { my ($hash) = @_; my $name = $hash->{NAME}; + if(!defined($name)) + { + Log3 "netatmo", 1, "error ".Dumper($hash); + return undef; + } Log3 $name, 5, "$name: refreshing token (timer)"; @@ -833,7 +886,7 @@ netatmo_checkConnection($) HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/readtimeline", + url => "https://".$hash->{helper}{apiserver}."/api/readtimeline", timeout => 5, hash => $hash, callback => \&netatmo_parseConnection, @@ -1042,6 +1095,7 @@ netatmo_initDevice($) $hash->{date_setup} = FmtDateTime($device->{date_setup}) if(defined($device->{date_setup})); $hash->{last_setup} = FmtDateTime($device->{last_setup}) if(defined($device->{last_setup})); $hash->{last_status_store} = FmtDateTime($device->{last_status_store}) if(defined($device->{last_status_store})); + $hash->{helper}{last_status_store} = $device->{last_status_store} if(defined($device->{last_status_store}) && $device->{last_status_store} > $hash->{helper}{last_status_store}); $hash->{last_message} = FmtDateTime($device->{last_message}) if(defined($device->{last_message})); $hash->{last_seen} = FmtDateTime($device->{last_seen}) if(defined($device->{last_seen})); $hash->{wifi_status} = $device->{wifi_status} if(defined($device->{wifi_status})); @@ -1093,7 +1147,7 @@ netatmo_initDevice($) $attr{$name}{stateFormat} = $state_format if( !defined($attr{$name}{stateFormat}) && defined($state_format) && defined($name) ); - return undef if(AttrVal($name,"disable",0) eq "1" || !defined($name)); + return undef if(IsDisabled($name) || !defined($name)); InternalTimer(gettimeofday()+90, "netatmo_poll", $hash); #netatmo_poll($hash); @@ -1113,7 +1167,7 @@ netatmo_getDevices($;$) if( $blocking ) { my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/api/getstationsdata", + url => "https://".$hash->{helper}{apiserver}."/api/getstationsdata", timeout => 5, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1124,7 +1178,7 @@ netatmo_getDevices($;$) return $hash->{helper}{devices}; } else { HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getstationsdata", + url => "https://".$hash->{helper}{apiserver}."/api/getstationsdata", timeout => 20, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1149,7 +1203,7 @@ netatmo_getHomes($;$) if( $blocking ) { my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/api/gethomedata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomedata", timeout => 5, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1159,7 +1213,7 @@ netatmo_getHomes($;$) return $hash->{helper}{homes}; } else { HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/gethomedata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomedata", timeout => 20, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1180,9 +1234,10 @@ netatmo_getThermostats($;$) return Log3 $name, 1, "$name: No access token was found! (getThermostats)" if(!defined($hash->{access_token})); +# url => "https://".$hash->{helper}{apiserver}."/api/getthermostatsdata", if( $blocking ) { my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/api/getthermostatsdata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomesdata", timeout => 5, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1193,7 +1248,7 @@ netatmo_getThermostats($;$) return $hash->{helper}{thermostats}; } else { HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getthermostatsdata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomesdata", timeout => 20, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1219,7 +1274,7 @@ netatmo_getHomecoachs($;$) if( $blocking ) { my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/api/gethomecoachsdata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomecoachsdata", timeout => 5, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1230,7 +1285,7 @@ netatmo_getHomecoachs($;$) return $hash->{helper}{homecoachs}; } else { HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/gethomecoachsdata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomecoachsdata", timeout => 20, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -1438,7 +1493,7 @@ netatmo_getEvents($) return Log3 $name, 1, "$name: No access token was found! (getEvents)" if(!defined($iohash->{access_token})); HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getnextevents", + url => "https://".$iohash->{helper}{apiserver}."/api/getnextevents", timeout => 20, noshutdown => 1, data => { access_token => $iohash->{access_token}, home_id => $hash->{Home}, event_id => $hash->{lastevent}, }, @@ -1493,7 +1548,7 @@ netatmo_getPublicDevices($$;$$$$) if( $blocking ) { my($err,$data) = HttpUtils_BlockingGet({ - url => "https://api.netatmo.com/api/getpublicdata", + url => "https://".$iohash->{helper}{apiserver}."/api/getpublicdata", timeout => 5, noshutdown => 1, data => { access_token => $iohash->{access_token}, lat_ne => $lat_ne, lon_ne => $lon_ne, lat_sw => $lat_sw, lon_sw => $lon_sw }, @@ -1502,7 +1557,7 @@ netatmo_getPublicDevices($$;$$$$) return netatmo_dispatch( {hash=>$hash,type=>'publicdata'},$err,$data ); } else { HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getpublicdata", + url => "https://".$iohash->{helper}{apiserver}."/api/getpublicdata", timeout => 20, noshutdown => 1, data => { access_token => $iohash->{access_token}, lat_ne => $lat_ne, lon_ne => $lon_ne, lat_sw => $lat_sw, lon_sw => $lon_sw, filter => 'true' }, @@ -1600,6 +1655,8 @@ netatmo_requestDeviceReadings($@) return undef if( !defined($hash->{IODev}) ); + Log3 $name, 5, "$name: requestDeviceReadings start ($id $type $module)"; + my $iohash = $hash->{IODev}; $type = $hash->{dataTypes} if( !$type ); $type = "Temperature,CO2,Humidity,Noise,Pressure,health_idx" if( !$type && $hash->{SUBTYPE} eq "DEVICE" ); @@ -1619,7 +1676,7 @@ netatmo_requestDeviceReadings($@) Log3 $name, 3, "$name: requestDeviceReadings ($type)"; HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getmeasure", + url => "https://".$iohash->{helper}{apiserver}."/api/getmeasure", timeout => 20, noshutdown => 1, data => \%data, @@ -1650,7 +1707,7 @@ netatmo_initHome($@) Log3 $name, 3, "$name initHome (gethomedata)"; -# url => "https://api.netatmo.com/api/gethomedata", +# url => "https://".$iohash->{helper}{apiserver}."/api/gethomedata", # data => \%data, HttpUtils_NonblockingGet({ url => "https://app.netatmo.net/api/gethomesdata", @@ -1662,8 +1719,8 @@ netatmo_initHome($@) callback => \&netatmo_dispatch, }); - InternalTimer(gettimeofday()+$hash->{INTERVAL}, "netatmo_poll", $hash); - $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{INTERVAL}; + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "netatmo_poll", $hash); + $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{helper}{INTERVAL}; } sub @@ -1686,7 +1743,7 @@ netatmo_requestHomeReadings($@) #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) ); Log3 $name, 3, "$name requestHomeReadings (gethomedata)"; -# url => "https://api.netatmo.com/api/gethomedata", +# url => "https://".$iohash->{helper}{apiserver}."/api/gethomedata", # data => \%data, HttpUtils_NonblockingGet({ url => "https://app.netatmo.net/api/gethomesdata", @@ -1720,7 +1777,7 @@ netatmo_requestThermostatReadings($@) #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) ); HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getthermostatsdata", + url => "https://".$iohash->{helper}{apiserver}."/api/getthermostatsdata", timeout => 20, noshutdown => 1, data => \%data, @@ -1730,6 +1787,231 @@ netatmo_requestThermostatReadings($@) }); } +sub +netatmo_initHeatingHome($@) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + return undef if( !defined($hash->{IODev}) ); + + my $iohash = $hash->{IODev}; + netatmo_refreshToken( $iohash, defined($iohash->{access_token}) ); + netatmo_refreshAppToken( $iohash, defined($iohash->{access_token_app}) ); + + return Log3 $name, 1, "$name: No access token was found! (initHeatingHome)" if(!defined($iohash->{access_token})); + return Log3 $name, 1, "$name: No app access token was found! (initHeatingHome)" if(!defined($iohash->{access_token_app})); + + my %data = (app_type => 'app_thermostat', home_id => $hash->{Home}); + + my $lastupdate = ReadingsVal( $name, ".lastupdate", undef ); + #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) ); + + Log3 $name, 3, "$name initHeatingHome (gethomedata)"; + +# url => "https://".$iohash->{helper}{apiserver}."/api/gethomedata", +# data => \%data, + HttpUtils_NonblockingGet({ + url => "https://app.netatmo.net/api/gethomesdata", + timeout => 20, + noshutdown => 1, + header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token_app}, + data => \%data, + hash => $hash, + type => 'gethomedata', + callback => \&netatmo_dispatch, + }); + + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "netatmo_poll", $hash); + $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{helper}{INTERVAL}; +} + + +sub +netatmo_pollHeatingHome($@) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + return undef if( !defined($hash->{IODev}) ); + + my $iohash = $hash->{IODev}; + netatmo_refreshToken( $iohash, defined($iohash->{access_token}) ); + + return Log3 $name, 1, "$name: No app access token was found! (pollHeatingHome)" if(!defined($iohash->{access_token_app})); + + my %data = (home_id => $hash->{Home}); + + my $lastupdate = ReadingsVal( $name, ".lastupdate", undef ); + #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) ); + my $json = encode_json( { home_id => $hash->{Home} } ); + + $json =~s/\"true\"/true/g; + $json =~s/\"false\"/false/g; + + Log3 $name, 3, "$name pollHeatingHome (getheatinghomedata)"; + +# url => "https://".$iohash->{helper}{apiserver}."/api/gethomedata", +# data => \%data, + HttpUtils_NonblockingGet({ + url => "https://my.netatmo.com/syncapi/v1/gethomestatus", + timeout => 20, + noshutdown => 1, + header => "Content-Type: application/json;charset=utf-8\r\nAuthorization: Bearer ".$iohash->{access_token}, + data => $json, + hash => $hash, + type => 'getheatinghomedata', + callback => \&netatmo_dispatch, + }); + + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "netatmo_poll", $hash); + $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{helper}{INTERVAL}; + + return undef; +} + +sub +netatmo_pollHeatingRoom($@) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + return undef if( !defined($hash->{IODev}) ); + + my $iohash = $hash->{IODev}; + netatmo_refreshAppToken( $iohash, defined($iohash->{access_token_app}) ); + + return Log3 $name, 1, "$name: No app access token was found! (pollHeatingRoom)" if(!defined($iohash->{access_token_app})); + + $hash->{openRequests} = 0 if ( !defined( $hash->{openRequests}) ); + my @types = split( ',', $hash->{dataTypes} ) if(defined($hash->{dataTypes})); + Log3 $name, 4, "$name: pollHeatingRoom types [".$hash->{dataTypes} . "] for room [".$hash->{Room}."]" if(defined($hash->{dataTypes})); + + my $lastupdate = ReadingsVal( $name, ".lastupdate", undef ); + $lastupdate = (time-7*24*60*60) if(!$lastupdate); + $lastupdate += 10; + $hash->{openRequests} += 1; + + my $now = int(time); + my $json = encode_json( { home_id => $hash->{Home}, + room_id => $hash->{Room}, + scale => "max", + type => $hash->{dataTypes}, + date_begin => "$lastupdate", + date_end => "$now", + real_time => "true" } ); + + $json =~s/\"true\"/true/g; + $json =~s/\"false\"/false/g; + + Log3 $name, 3, "$name pollHeatingRoom (getheatinghomedata)"; + +# url => "https://".$iohash->{helper}{apiserver}."/api/gethomedata", +# data => \%data, + HttpUtils_NonblockingGet({ + url => "https://app.netatmo.net/api/getroommeasure", + timeout => 20, + noshutdown => 1, + header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token_app}, + data => $json, + hash => $hash, + requested => $hash->{dataTypes}, + type => 'getmeasure', + callback => \&netatmo_dispatch, + }); + + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "netatmo_poll", $hash); + $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{helper}{INTERVAL}; + + return undef; +} + +sub +netatmo_setRoomMode($$;$) +{ + my ($hash,$set,$duration) = @_; + my $name = $hash->{NAME}; + + return undef if( !defined($hash->{IODev}) ); + + my $iohash = $hash->{IODev}; + netatmo_refreshToken( $iohash, defined($iohash->{access_token}) ); + + return Log3 $name, 1, "$name: No access token was found! (setRoomMode)" if(!defined($iohash->{access_token})); + + my $json = encode_json( { home_id => $hash->{Home}, + room_id => $hash->{Room}, + mode => "$set" } ); + + + if(defined($duration) || $set eq "max") + { + $duration = AttrVal($name,"setpoint_duration",60) if(!defined($duration)); + my $endpoint = time + (60 * $duration); + my $json = encode_json( { home_id => $hash->{Home}, + room_id => $hash->{Room}, + mode => "$set", + endtime => $endpoint } ); + } + + $json =~s/\"true\"/true/g; + $json =~s/\"false\"/false/g; + + + Log3 $name, 3, "$name: setRoomMode ($set)"; + + HttpUtils_NonblockingGet({ + url => "https://".$iohash->{helper}{apiserver}."/syncapi/v1/setthermpoint", + timeout => 20, + noshutdown => 1, + header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token}, + data => $json, + hash => $hash, + type => 'setroom', + callback => \&netatmo_dispatch, + }); + + +} + +sub +netatmo_setRoomTemp($$;$) +{ + my ($hash,$set,$duration) = @_; + my $name = $hash->{NAME}; + + return undef if( !defined($hash->{IODev}) ); + + my $iohash = $hash->{IODev}; + netatmo_refreshToken( $iohash, defined($iohash->{access_token}) ); + + return Log3 $name, 1, "$name: No access token was found! (setRoomTemp)" if(!defined($iohash->{access_token})); + + $duration = AttrVal($name,"setpoint_duration",60) if(!defined($duration)); + my $endpoint = time + (60 * $duration); + + my $json = encode_json( { home_id => $hash->{Home}, + room_id => $hash->{Room}, + mode => "manual", + endtime => $endpoint, + temp => $set } ); + + Log3 $name, 3, "$name: setRoomTemp ($set)"; + + HttpUtils_NonblockingGet({ + url => "https://".$iohash->{helper}{apiserver}."/syncapi/v1/setthermpoint", + timeout => 20, + noshutdown => 1, + header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token}, + data => $json, + hash => $hash, + type => 'setroom', + callback => \&netatmo_dispatch, + }); + + +} + sub netatmo_requestPersonReadings($) @@ -1752,7 +2034,7 @@ netatmo_requestPersonReadings($) my $lastupdate = ReadingsVal( $name, ".lastupdate", undef ); HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getlasteventof", + url => "https://".$iohash->{helper}{apiserver}."/api/getlasteventof", timeout => 20, noshutdown => 1, data => \%data, @@ -2106,7 +2388,7 @@ netatmo_setTagCalibration($$) } sub -netatmo_setThermostatMode($$;$$) +netatmo_setThermostatMode($$;$) { my ($hash,$set,$duration) = @_; my $name = $hash->{NAME}; @@ -2132,7 +2414,7 @@ netatmo_setThermostatMode($$;$$) Log3 $name, 3, "$name: setThermostatMode ($set)"; HttpUtils_NonblockingGet({ - url => 'https://api.netatmo.com/api/setthermpoint', + url => "https://".$iohash->{helper}{apiserver}."/api/setthermpoint", timeout => 20, noshutdown => 1, data => \%data, @@ -2165,7 +2447,7 @@ netatmo_setThermostatTemp($$;$$) Log3 $name, 3, "$name: setThermostatTemp ($set)"; HttpUtils_NonblockingGet({ - url => 'https://api.netatmo.com/api/setthermpoint', + url => "https://".$iohash->{helper}{apiserver}."/api/setthermpoint", timeout => 20, noshutdown => 1, data => \%data, @@ -2201,7 +2483,7 @@ netatmo_setThermostatProgram($$) Log3 $name, 3, "$name: setThermostatProgram ($set / $schedule_id)"; HttpUtils_NonblockingGet({ - url => 'https://api.netatmo.com/api/switchschedule', + url => "https://".$iohash->{helper}{apiserver}."/api/switchschedule", timeout => 20, noshutdown => 1, data => \%data, @@ -2220,21 +2502,21 @@ netatmo_poll($) my $name = $hash->{NAME}; - return undef if(AttrVal($name,"disable",0) eq "1" || !defined($name)); + return undef if(IsDisabled($name) || !defined($name)); - # my $resolve = inet_aton("api.netatmo.com"); + # my $resolve = inet_aton($hash->{helper}{apiserver}); # if(!defined($resolve)) # { # Log3 $name, 1, "$name: DNS error on poll"; # InternalTimer( gettimeofday() + 1800, "netatmo_poll", $hash); # return undef; # } - $hash->{INTERVAL} = 3600 if(!defined($hash->{INTERVAL})); + $hash->{helper}{INTERVAL} = 3600 if(!defined($hash->{helper}{INTERVAL})); if(defined($hash->{status}) && ($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/)) { RemoveInternalTimer($hash); - InternalTimer(gettimeofday()+$hash->{INTERVAL}+1800, "netatmo_poll", $hash); + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}+1800, "netatmo_poll", $hash); Log3 $name, 1, "$name: API usage limit reached"; $hash->{status} = "postponed update"; readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data"); @@ -2291,6 +2573,10 @@ netatmo_poll($) netatmo_pollRelay($hash); } elsif( $hash->{SUBTYPE} eq "THERMOSTAT" ) { netatmo_pollThermostat($hash); + } elsif( $hash->{SUBTYPE} eq "HEATINGHOME" ) { + netatmo_pollHeatingHome($hash); + } elsif( $hash->{SUBTYPE} eq "HEATINGROOM" ) { + netatmo_pollHeatingRoom($hash); } elsif( $hash->{SUBTYPE} eq "PERSON" ) { netatmo_pollPerson($hash); } else { @@ -2301,8 +2587,8 @@ netatmo_poll($) if( defined($hash->{helper}{update_count}) && $hash->{helper}{update_count} > 1024 ) { InternalTimer(gettimeofday()+30, "netatmo_poll", $hash); } else { - $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{INTERVAL}; - InternalTimer(gettimeofday()+$hash->{INTERVAL}, "netatmo_poll", $hash); + $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{helper}{INTERVAL}; + InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "netatmo_poll", $hash); } } @@ -2443,6 +2729,8 @@ netatmo_dispatch($$$) netatmo_parseThermostatReadings($hash,$json); } elsif( $param->{type} eq 'setthermostat' ) { netatmo_parseThermostatStatus($hash,$json); + } elsif( $param->{type} eq 'getheatinghomedata' ) { + netatmo_parseHeatingHomeStatus($hash,$json); } elsif( $param->{type} eq 'getpersondata' ) { netatmo_parsePersonReadings($hash,$json); } elsif( $param->{type} eq 'publicdata' ) { @@ -2507,7 +2795,7 @@ netatmo_autocreate($;$) foreach my $d (keys %defs) { next if(!defined($defs{$d})); next if($defs{$d}{TYPE} ne "autocreate"); - return undef if(AttrVal($defs{$d}{NAME},"disable",undef)); + return undef if(IsDisabled($defs{$d}{NAME})); } } @@ -2571,7 +2859,7 @@ netatmo_autocreatehome($;$) foreach my $d (keys %defs) { next if(!defined($defs{$d})); next if($defs{$d}{TYPE} ne "autocreate"); - return undef if(AttrVal($defs{$d}{NAME},"disable",undef)); + return undef if(IsDisabled($defs{$d}{NAME})); } } @@ -2678,32 +2966,53 @@ netatmo_autocreatethermostat($;$) foreach my $d (keys %defs) { next if(!defined($defs{$d})); next if($defs{$d}{TYPE} ne "autocreate"); - return undef if(AttrVal($defs{$d}{NAME},"disable",undef)); + return undef if(IsDisabled($defs{$d}{NAME})); } } my $autocreated = 0; my $devices = $hash->{helper}{thermostats}; + + #Log3 $name, 1, "$name: autocreating ".Dumper($devices); + foreach my $device (@{$devices}) { - if( defined($modules{$hash->{TYPE}}{defptr}{"R$device->{_id}"}) ) { - Log3 $name, 4, "$name: relay '$device->{_id}' already defined"; + if( defined($modules{$hash->{TYPE}}{defptr}{"R$device->{id}"}) ) { + Log3 $name, 4, "$name: relay '$device->{id}' already defined"; next; } - if( defined($modules{$hash->{TYPE}}{defptr}{"T$device->{_id}"}) ) { - Log3 $name, 4, "$name: thermostat '$device->{_id}' already defined"; + if( defined($modules{$hash->{TYPE}}{defptr}{"T$device->{id}"}) ) { + Log3 $name, 4, "$name: thermostat '$device->{id}' already defined"; next; } - if(AttrVal($name,"ignored_device_ids","") =~ /$device->{_id}/) { - Log3 $name, 4, "$name: '$device->{_id}' ignored for autocreate"; + if( defined($modules{$hash->{TYPE}}{defptr}{"E$device->{id}"}) ) { + Log3 $name, 4, "$name: heating home '$device->{id}' already defined"; + next; + } + if( defined($modules{$hash->{TYPE}}{defptr}{"O$device->{id}"}) ) { + Log3 $name, 4, "$name: heating room '$device->{id}' already defined"; + next; + } + if(AttrVal($name,"ignored_device_ids","") =~ /$device->{id}/) { + Log3 $name, 4, "$name: '$device->{id}' ignored for autocreate"; next; } - my $id = $device->{_id}; + my $id = $device->{id}; my $devname = "netatmo_R". $id; $devname =~ s/:/_/g; my $define= "$devname netatmo RELAY $id"; - if( $device->{main_device} ) { + if( $device->{type} eq "Home" ) { + $devname = "netatmo_E". $id; + $devname =~ s/:/_/g; + $define= "$devname netatmo HEATINGHOME $id"; + } + elsif( $device->{type} eq "Room" ) { + $devname = "netatmo_O". $id; + $devname =~ s/:/_/g; + $define= "$devname netatmo HEATINGROOM $device->{Home} $id"; + } + elsif( $device->{main_device} ) { $devname = "netatmo_T". $id; $devname =~ s/:/_/g; $define= "$devname netatmo THERMOSTAT $device->{main_device} $id"; @@ -2714,12 +3023,13 @@ netatmo_autocreatethermostat($;$) if($cmdret) { Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret"; } else { - $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{station_name})) if( defined($device->{station_name}) ); - $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{module_name})) if( defined($device->{module_name}) ); + $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{name})) if( defined($device->{name}) ); + #$cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{module_name})) if( defined($device->{module_name}) ); $cmdret= CommandAttr(undef,"$devname room netatmo"); $cmdret= CommandAttr(undef,"$devname IODev $name"); $cmdret= CommandAttr(undef,"$devname devStateIcon .*:no-icon"); - $cmdret= CommandAttr(undef,"$devname stateFormat setpoint|temperature"); + $cmdret= CommandAttr(undef,"$devname stateFormat setpoint|temperature") if( $device->{main_device} ); + $cmdret= CommandAttr(undef,"$devname stateFormat active") if( !$device->{main_device} ); $autocreated++; } } @@ -2744,7 +3054,7 @@ netatmo_autocreatehomecoach($;$) foreach my $d (keys %defs) { next if(!defined($defs{$d})); next if($defs{$d}{TYPE} ne "autocreate"); - return undef if(AttrVal($defs{$d}{NAME},"disable",undef)); + return undef if(IsDisabled($defs{$d}{NAME})); } } @@ -2879,7 +3189,7 @@ netatmo_parseHomeList($$) my @homes = (); foreach my $home (@{$json->{body}{homes}}) { - push( @homes, $home ); + push( @homes, $home ) if(defined($home->{cameras}) && @{$home->{cameras}}); foreach my $camera (@{$home->{cameras}}) { $camera->{home} = $home->{id}; push( @homes, $camera ) if(defined($camera->{status})); @@ -2907,17 +3217,36 @@ netatmo_parseThermostatList($$) #$do_autocreate = 0 if( !defined($hash->{helper}{devices}) ); #autocreate my @devices = (); - foreach my $device (@{$json->{body}{devices}}) { + foreach my $home (@{$json->{body}{homes}}) { + + next if(!defined($home->{devices}) || !@{$home->{devices}}); + $home->{type} = "Home"; + $home->{firmware} = "-"; + push( @devices, $home ); + + foreach my $room (@{$home->{rooms}}) { + next if(!defined($room->{modules}) || !@{$room->{modules}}); + + $room->{Home} = $home->{id}; + $room->{type} = "Room"; + $room->{firmware} = "-"; + push( @devices, $room ); + + } + + foreach my $device (@{$home->{devices}}) { push( @devices, $device ); foreach my $module (@{$device->{modules}}) { - $module->{main_device} = $device->{_id}; + + $module->{main_device} = $device->{id}; push( @devices, $module ); } } + } $hash->{helper}{thermostats} = \@devices; @@ -3002,6 +3331,7 @@ netatmo_parseReadings($$;$) $hash->{status} = $json->{error}{message} if( $json->{error} ); my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 ); + my @r = (); my $readings = \@r; $readings = $hash->{readings} if( defined($hash->{readings}) ); @@ -3014,6 +3344,7 @@ netatmo_parseReadings($$;$) if(scalar(@{$json->{body}}) == 0) { $hash->{status} = "no data"; + readingsSingleUpdate( $hash, "active", "dead", 1 ) if($hash->{helper}{last_status_store} > 0 && $hash->{helper}{last_status_store} < (int(time) - 7200) ); } foreach my $values ( @{$json->{body}}) { @@ -3029,16 +3360,16 @@ netatmo_parseReadings($$;$) if( !defined($reading) ) { - next; + $reading = "undefined";#next; } if(lc($requested) =~ /wind/ && ($rname eq "temperature" || $rname eq "humidity")) { - Log3 $name, 4, "$name netatmo - wind sensor $rname reading: $reading ($time)"; + Log3 $name, 3, "$name netatmo - wind sensor $rname reading: $reading ($time)"; next;# if($reading == 0); } - if(($rname eq "noise" && $reading > 150) || ($rname eq "temperature" && $reading > 60) || ($rname eq "humidity" && $reading > 100) || ($rname eq "pressure" && $reading < 500)) + if(($rname eq "noise" && int($reading) > 150) || ($rname eq "temperature" && int($reading) > 60) || ($rname eq "humidity" && int($reading) > 100) || ($rname eq "pressure" && int($reading) < 500)) { Log3 $name, 1, "$name netatmo - invalid reading: $rname: ".Dumper($reading)." \n ".Dumper($reading_names); next; @@ -3048,7 +3379,20 @@ netatmo_parseReadings($$;$) $reading = $health_index{$reading}; } - push(@{$readings}, [$time, $rname, $reading]); + if($rname =~ /temperature/){ + $reading = sprintf( "%.1f", $reading); + } + + if($rname eq "rain" && $reading > 0) + { + my $rain_sum = ReadingsVal($name,"rain_sum",0); + $rain_sum += $reading; + readingsSingleUpdate($hash,"rain_sum",$rain_sum,1); + Log3 $name, 1, $name.": summed rain ".$reading." (to ".$rain_sum.")"; + } + + + push(@{$readings}, [$time, $rname, $reading]) if($reading ne "undefined"); } $last_time = $time if(defined($time)); @@ -3056,7 +3400,7 @@ netatmo_parseReadings($$;$) $time += $step_time if( $step_time ); } - $hash->{last_status_store} = FmtDateTime($last_time); + $hash->{helper}{last_status_store} = $last_time if($last_time > $hash->{helper}{last_status_store}); } if( $hash->{openRequests} > 1 ) { @@ -3065,6 +3409,7 @@ netatmo_parseReadings($$;$) my ($seconds,undef) = netatmo_updateReadings( $hash, $readings ); $hash->{LAST_POLL} = FmtDateTime( $seconds ); delete $hash->{readings}; +# readingsSingleUpdate($hash, ".lastupdate", $last_time, 0); } if(defined($last_time) && int($last_time) > 0 && defined($step_time)) { @@ -3206,6 +3551,14 @@ netatmo_parseGlobal($$) } if(defined($devicedata->{dashboard_data}{sum_rain_24})) { + my $rain_day = ReadingsVal($device->{NAME},"rain_day",0); + if($devicedata->{dashboard_data}{sum_rain_24} < $rain_day) + { + my $rain_total = ReadingsVal($device->{NAME},"rain_total",0); + $rain_total += $rain_day; + readingsSingleUpdate($device,"rain_total",$rain_total,1); + Log3 $name, 1, $device->{NAME}.": added rain ".$rain_day." (to ".$rain_total.")"; + } readingsBeginUpdate($device); $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{time_utc}); readingsBulkUpdate( $device, "rain_day", $devicedata->{dashboard_data}{sum_rain_24}, 1 ); @@ -3239,6 +3592,7 @@ netatmo_parseGlobal($$) $device->{co2_calibrating} = $devicedata->{co2_calibrating} if(defined($devicedata->{co2_calibrating})); $device->{last_status_store} = FmtDateTime($devicedata->{last_status_store}) if(defined($devicedata->{last_status_store})); + $device->{helper}{last_status_store} = $devicedata->{last_status_store} if(defined($devicedata->{last_status_store}) && $devicedata->{last_status_store} > $device->{helper}{last_status_store}); $device->{last_message} = FmtDateTime($devicedata->{last_message}) if(defined($devicedata->{last_message})); $device->{last_seen} = FmtDateTime($devicedata->{last_seen}) if(defined($devicedata->{last_seen})); $device->{wifi_status} = $devicedata->{wifi_status} if(defined($devicedata->{wifi_status})); @@ -3310,6 +3664,14 @@ netatmo_parseGlobal($$) } if(defined($moduledata->{dashboard_data}{sum_rain_24})) { + my $rain_day = ReadingsVal($module->{NAME},"rain_day",0); + if($moduledata->{dashboard_data}{sum_rain_24} < $rain_day) + { + my $rain_total = ReadingsVal($module->{NAME},"rain_total",0); + $rain_total += $rain_day; + readingsSingleUpdate($module,"rain_total",$rain_total,1); + Log3 $name, 1, $module->{NAME}.":_added rain ".$rain_day." (to ".$rain_total.")"; + } readingsBeginUpdate($module); $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc}); readingsBulkUpdate( $module, "rain_day", $moduledata->{dashboard_data}{sum_rain_24}, 1 ); @@ -3335,6 +3697,7 @@ netatmo_parseGlobal($$) $module->{co2_calibrating} = $moduledata->{co2_calibrating} if(defined($moduledata->{co2_calibrating})); $module->{last_status_store} = FmtDateTime($moduledata->{last_status_store}) if(defined($moduledata->{last_status_store})); + $module->{helper}{last_status_store} = $moduledata->{last_status_store} if(defined($moduledata->{last_status_store}) && $moduledata->{last_status_store} > $module->{helper}{last_status_store}); $module->{last_message} = FmtDateTime($moduledata->{last_message}) if(defined($moduledata->{last_message})); $module->{last_seen} = FmtDateTime($moduledata->{last_seen}) if(defined($moduledata->{last_seen})); $module->{wifi_status} = $moduledata->{wifi_status} if(defined($moduledata->{wifi_status})); @@ -4446,6 +4809,7 @@ netatmo_parseThermostatReadings($$;$) $hash->{date_setup} = FmtDateTime($devicedata->{date_setup}) if(defined($devicedata->{date_setup})); $hash->{last_setup} = FmtDateTime($devicedata->{last_setup}) if(defined($devicedata->{last_setup})); $hash->{last_status_store} = FmtDateTime($devicedata->{last_status_store}) if(defined($devicedata->{last_status_store})); + $hash->{helper}{last_status_store} = $devicedata->{last_status_store} if(defined($devicedata->{last_status_store}) && $devicedata->{last_status_store} > $hash->{helper}{last_status_store}); $hash->{last_message} = FmtDateTime($devicedata->{last_message}) if(defined($devicedata->{last_message})); $hash->{last_seen} = FmtDateTime($devicedata->{last_seen}) if(defined($devicedata->{last_seen})); $hash->{last_plug_seen} = FmtDateTime($devicedata->{last_plug_seen}) if(defined($devicedata->{last_plug_seen})); @@ -4460,6 +4824,8 @@ netatmo_parseThermostatReadings($$;$) $hash->{plug_connected_boiler} = $devicedata->{plug_connected_boiler} if(defined($devicedata->{plug_connected_boiler})); $hash->{syncing} = $devicedata->{syncing} if(defined($devicedata->{syncing})); + $hash->{room} = $devicedata->{room} if(defined($devicedata->{room})); + if( $devicedata->{place} ) { $hash->{country} = encode_utf8($devicedata->{place}{country}); $hash->{bssid} = $devicedata->{place}{bssid} if(defined($devicedata->{place}{bssid})); @@ -4491,6 +4857,7 @@ netatmo_parseThermostatReadings($$;$) $module->{date_setup} = FmtDateTime($moduledata->{date_setup}) if(defined($moduledata->{date_setup})); $module->{last_setup} = FmtDateTime($moduledata->{last_setup}) if(defined($moduledata->{last_setup})); $module->{last_status_store} = FmtDateTime($moduledata->{last_status_store}) if(defined($moduledata->{last_status_store})); + $module->{helper}{last_status_store} = $moduledata->{last_status_store} if(defined($moduledata->{last_status_store}) && $moduledata->{last_status_store} > $module->{helper}{last_status_store}); $module->{last_message} = FmtDateTime($moduledata->{last_message}) if(defined($moduledata->{last_message})); $module->{last_seen} = FmtDateTime($moduledata->{last_seen}) if(defined($moduledata->{last_seen})); $module->{last_plug_seen} = FmtDateTime($moduledata->{last_plug_seen}) if(defined($moduledata->{last_plug_seen})); @@ -4505,6 +4872,8 @@ netatmo_parseThermostatReadings($$;$) $module->{plug_connected_boiler} = $moduledata->{plug_connected_boiler} if(defined($moduledata->{plug_connected_boiler})); $module->{syncing} = $moduledata->{syncing} if(defined($moduledata->{syncing})); + $module->{room} = $moduledata->{room} if(defined($moduledata->{room})); + if( $moduledata->{place} ) { $module->{country} = $moduledata->{place}{country}; $module->{bssid} = $moduledata->{place}{bssid} if(defined($moduledata->{place}{bssid})); @@ -4535,7 +4904,7 @@ netatmo_parseThermostatReadings($$;$) { readingsBeginUpdate($module); $module->{".updateTimestamp"} = FmtDateTime($moduledata->{measured}{time}); - readingsBulkUpdate( $module, "setpoint_temp", $moduledata->{measured}{setpoint_temp}, 1 ); + readingsBulkUpdate( $module, "setpoint_temp", sprintf( "%.1f", $moduledata->{measured}{setpoint_temp}), 1 ); $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{measured}{time}); readingsEndUpdate($module,1); $setmode = sprintf( "%.1f", $moduledata->{measured}{setpoint_temp}) if($setmode ne "max" && $setmode ne "off"); @@ -4596,14 +4965,26 @@ netatmo_parseThermostatStatus($$;$) $hash->{status} = $json->{status}; $hash->{status} = $json->{error}{message} if( $json->{error} ); InternalTimer( gettimeofday() + 10, "netatmo_pollRelay", $thermostat) if($hash->{status} eq "ok"); - } - else{ + } else { netatmo_pollRelay($thermostat) if($thermostat->{status} !~ /usage/ && $thermostat->{status} !~ /too_many_connections/ && $thermostat->{status} !~ /postponed/);; } readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data"); } +sub +netatmo_parseHeatingHomeStatus($$) +{ + my($hash, $json) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 4, "$name: parseHeatingHomeStatus"; + my $thermostat = $modules{$hash->{TYPE}}{defptr}{"E$hash->{Home}"}; + + Log3 $name, 5, "$name: parseHeatingHomeStatus ".Dumper($json); + + return undef; +} sub netatmo_parsePublic($$) @@ -5057,19 +5438,21 @@ netatmo_pollDevice($) if( $hash->{Module} ) { - my @types = split( ' ', $hash->{dataTypes} ) if(defined($hash->{dataTypes})); + my @types = split( ',', $hash->{dataTypes} ) if(defined($hash->{dataTypes})); Log3 $name, 4, "$name: pollDevice types [".$hash->{dataTypes} . "] for modules [".$hash->{Module}."]" if(defined($hash->{dataTypes})); my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef ); $lastupdate = (time-7*24*60*60) if(!$lastupdate and !$hash->{model}); $hash->{openRequests} += int(@types); $hash->{openRequests} += 1 if(int(@types)==0); + + readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0) if(int(@types)>0); + foreach my $module (split( ' ', $hash->{Module} ) ) { - my $type; - $type = shift(@types) if( $module and @types); - - readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0) if($type); - netatmo_requestDeviceReadings( $hash, $hash->{Device}, $type, $module ne $hash->{Device}?$module:undef );# if($type); + #$type = shift(@types) if( $module and @types); + foreach my $type (@types) { + netatmo_requestDeviceReadings( $hash, $hash->{Device}, $type, ($module ne $hash->{Device})?$module:undef );# if($type); + } } } elsif( defined($hash->{Lat}) ) @@ -5120,7 +5503,7 @@ netatmo_pollGlobal($) Log3 $name, 4, "$name: pollGlobal"; HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/getstationsdata", + url => "https://".$hash->{helper}{apiserver}."/api/getstationsdata", timeout => 20, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -5144,7 +5527,7 @@ netatmo_pollGlobalHealth($) Log3 $name, 4, "$name: pollGlobalHealth"; HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/gethomecoachsdata", + url => "https://".$hash->{helper}{apiserver}."/api/gethomecoachsdata", timeout => 20, noshutdown => 1, data => { access_token => $hash->{access_token}, }, @@ -5183,7 +5566,7 @@ netatmo_pollForecast($) Log3 $name, 4, "$name: pollForecast (forecastdata)"; HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/simplifiedfuturemeasure", + url => "https://app.netatmo.net/api/simplifiedfuturemeasure", timeout => 20, noshutdown => 1, data => { device_id => $hash->{Station}, }, @@ -5266,7 +5649,9 @@ netatmo_Get($$@) || $hash->{SUBTYPE} eq "CAMERA" || $hash->{SUBTYPE} eq "PERSON" || $hash->{SUBTYPE} eq "RELAY" - || $hash->{SUBTYPE} eq "THERMOSTAT" ) { + || $hash->{SUBTYPE} eq "THERMOSTAT" + || $hash->{SUBTYPE} eq "HEATINGHOME" + || $hash->{SUBTYPE} eq "HEATINGROOM" ) { $list = "update:noArg"; $list = " ping:noArg video video_local live live_local snapshot" if($hash->{SUBTYPE} eq "CAMERA"); @@ -5350,7 +5735,7 @@ netatmo_Get($$@) my $ret; foreach my $home (@{$homes}) { $ret .= "\n" if( $ret ); - $ret .= "$home->{id} \t\tHome\t".encode_utf8($home->{name}) if(defined($home->{cameras})); + $ret .= "$home->{id} \t\tHome\t".encode_utf8($home->{name}) if(defined($home->{cameras}) && @{$home->{cameras}});; if(defined($home->{status})) { $ret .= "$home->{id} \t\t\tCamera\t".encode_utf8($home->{name}); @@ -5372,7 +5757,7 @@ netatmo_Get($$@) my $ret; foreach my $thermostat (@{$thermostats}) { $ret .= "\n" if( $ret ); - $ret .= "$thermostat->{_id}\t$thermostat->{firmware}\t$thermostat->{type}\t ".(defined($thermostat->{module_name}) ? $thermostat->{module_name} : $thermostat->{station_name}); + $ret .= "$thermostat->{id}\t$thermostat->{firmware}\t$thermostat->{type}\t ".$thermostat->{name}; } $ret = "id\t\t\tfw\ttype\t name\n" . $ret if( $ret ); @@ -5609,7 +5994,7 @@ netatmo_registerWebhook($) return undef if(!defined($webhookurl)); HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/addwebhook", + url => "https://".$iohash->{helper}{apiserver}."/api/addwebhook", timeout => 20, noshutdown => 1, data => { access_token => $iohash->{access_token}, url => $webhookurl, app_type => 'app_security', }, @@ -5634,7 +6019,7 @@ netatmo_dropWebhook($) Log3 $name, 3, "Dropping Netatmo webhook"; HttpUtils_NonblockingGet({ - url => "https://api.netatmo.com/api/dropwebhook", + url => "https://".$iohash->{helper}{apiserver}."/api/dropwebhook", timeout => 20, noshutdown => 1, data => { access_token => $iohash->{access_token}, app_type => 'app_security', }, @@ -5745,12 +6130,19 @@ sub netatmo_Attr($$$) $attrVal = 60*5 if($hash->{SUBTYPE} ne "HOME" && $attrVal < 60*5 && $attrVal != 0); #\$attrVal = 2700 if(($attrVal < 2700 && ($hash->{SUBTYPE} eq "ACCOUNT" || $hash->{SUBTYPE} eq "FORECAST"); - $hash->{INTERVAL} = $attrVal if($attrVal); - $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} ); + $hash->{helper}{INTERVAL} = $attrVal if($attrVal); + $hash->{helper}{INTERVAL} = 60*30 if( !$hash->{helper}{INTERVAL} ); } elsif( $attrName eq "setpoint_duration" ) { my $hash = $defs{$name}; #$hash->{SETPOINT_DURATION} = $attrVal; #$hash->{SETPOINT_DURATION} = 60 if( !$hash->{SETPOINT_DURATION} ); + } elsif( $attrName eq "serverAPI" ) { + my $hash = $defs{$name}; + if( $cmd eq "set" && $attrVal ne "" ) { + $hash->{helper}{apiserver} = $attrVal; + } else { + $hash->{helper}{apiserver} = "api.netatmo.com"; + } } elsif( $attrName eq "disable" ) { my $hash = $defs{$name}; RemoveInternalTimer($hash);