2019-11-23 10:49:06 +00:00
#===============================================================================
2020-02-06 04:47:50 +00:00
# $Id: 98_TadoAPI.pm 102 2019-12-27 15:58:26Z psycho160 $
2019-11-23 10:49:06 +00:00
#
# FILE: 98_TadoAPI.pm
#
# USAGE: Module for FHEM
# Info: Turn verbose on for debugging
#
# REQUIREMENTS: Below modules should be pre-installed.
# HTTP::Request::Common
# HTTP::Headers
# Data::Dumper;
# JSON
#
# BUGS: ---
# NOTES: ---
# AUTHOR: Philipp Wolfmajer
# ORGANIZATION:
2019-12-10 09:14:23 +00:00
# VERSION: 0.6
# CREATED: 04/12/2019
# REVISION: 12/10/2019
2019-11-23 10:49:06 +00:00
#===============================================================================
package main ;
use strict ;
use warnings ;
use utf8 ;
use HTTP::Request::Common qw (POST GET PUT ) ;
use HTTP::Headers ;
use JSON ;
2019-11-25 21:02:47 +00:00
use POSIX qw( strftime ) ;
2019-11-23 10:49:06 +00:00
####DEFAULTS############
my $ client_id = 'public-api-preview' ;
my $ client_secret = '4HJGRffVR8xb3XdEUQpjgZ1VplJi6Xgw' ;
my $ scope = 'home.user' ;
my $ AuthURL = qq{ https://auth.tado.com/oauth/token } ;
my $ DataURL = qq{ https://my.tado.com/api/v2/me } ;
my $ QueryURL = qq{ https://my.tado.com/api/v2/homes } ;
my $ tokenFile = "./FHEM/FhemUtils/TadoAPI_token" ;
my $ header = { } ;
2019-12-11 10:12:46 +00:00
my $ reqDebug = 5 ;
2019-11-23 10:49:06 +00:00
# helpers
my $ apiStatus = 1 ;
2020-04-19 22:36:32 +00:00
my % sets = (
"zoneUpdate" = > "" ,
2019-11-23 10:49:06 +00:00
"refreshToken" = > "noArg" ,
"password" = > "" ,
"update" = > "noArg" ,
"setGeo" = > "" ,
"setZoneOverlay" = > "" ,
2020-02-06 07:39:50 +00:00
"timedZoneOverlay" = > "" ,
2019-12-14 15:29:46 +00:00
"updateAllOverlays" = > "noArg" ,
2019-11-23 10:49:06 +00:00
"setAllOverlays" = > ""
) ;
2020-04-19 22:36:32 +00:00
my % gets = (
2019-11-23 10:49:06 +00:00
"getZoneDevices" = > "noArg" ,
"getZoneInfo" = > "noArg" ,
"getGeo" = > "" ,
2019-11-23 12:22:13 +00:00
#"getXTest" => "",
2019-11-23 10:49:06 +00:00
"getMobileDevices" = > "noArg"
) ;
sub
2020-04-20 14:13:19 +00:00
TadoAPI_Initialize
2019-11-23 10:49:06 +00:00
{
2020-04-20 14:13:19 +00:00
my $ hash = shift ;
my $ TYPE = "TadoAPI" ;
$ hash - > { DefFn } = $ TYPE . "_Define" ;
$ hash - > { InitFn } = $ TYPE . "_Init" ;
$ hash - > { SetFn } = $ TYPE . "_Set" ;
$ hash - > { GetFn } = $ TYPE . "_Get" ;
$ hash - > { AttrList } = ""
. "homeID "
. "mobileID "
. "showPosData:0,1 "
. "updateIntervall "
. $ readingFnAttributes
;
2019-11-23 10:49:06 +00:00
}
2020-04-20 14:13:19 +00:00
sub TadoAPI_Init
2019-11-23 10:49:06 +00:00
{
2020-04-20 14:13:19 +00:00
my $ hash = shift ;
my @ args = @ _ ;
2019-11-23 10:49:06 +00:00
my $ u = "wrong syntax: define <name> TadoAPI <username> <homeID> [<mobileID>]" ;
2020-04-20 14:13:19 +00:00
return $ u if ( int ( @ args ) < 2 ) ;
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
2020-04-20 14:13:19 +00:00
sub TadoAPI_Define
2019-11-23 10:49:06 +00:00
{
2020-04-20 14:13:19 +00:00
my $ hash = shift ;
my $ def = shift ;
2019-11-23 10:49:06 +00:00
my @ a = split ( "[ \t]+" , $ def ) ;
my $ name = shift @ a ;
my $ type = shift @ a ;
my $ tokenFileName = $ tokenFile . "_" . $ name ;
return "Invalid number of arguments: "
. "define <name> TadoAPI <username> [<homeID>]"
if ( int ( @ a ) < 1 ) ;
my ( $ user , $ homeID ) = @ a ;
Log3 $ name , 3 , "TadoAPI_Define $name: called " ;
$ hash - > { STATE } = "defined" ;
# Initialize the device
return $@ unless ( FHEM::Meta:: SetInternals ( $ hash ) ) ;
$ hash - > { TADO_USER } = $ user ;
$ hash - > { TOKEN_FILE } = $ tokenFileName ;
my @ args = ( $ homeID ) ;
if ( $ main:: init_done ) {
# do something?
return TadoAPI_Catch ( $@ ) if $@ ;
}
my $ password = TadoAPI_readPassword ( $ name ) ;
if ( defined ( $ password ) ) {
2019-12-10 14:28:32 +00:00
TadoAPI_CheckStatus ( $ hash ) ;
2019-11-23 10:49:06 +00:00
TadoAPI_LoadToken ( $ hash ) ;
# start the status update timer
RemoveInternalTimer ( $ hash ) ;
2019-11-23 11:47:42 +00:00
InternalTimer ( gettimeofday ( ) + 15 , "TadoAPI_Update" , $ hash , 0 ) ;
2019-11-23 10:49:06 +00:00
if ( defined ( $ homeID ) && $ homeID ne "" ) {
2020-04-19 22:36:32 +00:00
$ attr { $ name } { homeID } = $ homeID ;
2019-11-23 10:49:06 +00:00
} else {
my $ id = TadoAPI_GetHomeId ( $ hash ) ;
if ( defined ( $ id ) && $ id ne "" ) {
2020-04-19 22:36:32 +00:00
$ attr { $ name } { homeID } = $ id ;
2019-11-23 10:49:06 +00:00
}
}
2019-11-23 19:22:41 +00:00
} else {
$ hash - > { STATE } = "no password set" ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_Set (@) {
my ( $ hash , @ a ) = @ _ ;
return "Need at least one parameters" if ( @ a < 2 ) ;
my $ cmd = $ a [ 1 ] ;
my $ value = $ a [ 2 ] ;
my $ name = $ hash - > { NAME } ;
my $ subcmd ;
my $ message = undef ;
if ( ! defined ( $ sets { $ cmd } ) ) {
my @ cmds = ( ) ;
2020-04-21 13:09:51 +00:00
for my $ key ( sort keys % sets ) {
2019-11-23 10:49:06 +00:00
push @ cmds , $ sets { $ key } ? $ key . ":" . join ( "," , $ sets { $ key } ) : $ key ;
}
return "Unknown argument $a[1], choose one of " . join ( " " , @ cmds ) ;
}
2019-11-23 19:22:41 +00:00
if ( ( $ cmd ne "password" ) )
{
my $ pwd = TadoAPI_readPassword ( $ name ) ;
2020-04-19 22:36:32 +00:00
unless ( defined $ pwd )
2019-11-23 19:22:41 +00:00
{
$ message = "Error: no tado password set. Please define it with 'set $name password Your_tado_Password'" ;
Log3 $ name , 2 , "$name, $message" ;
$ hash - > { STATE } = "no password set" ;
return $ message ;
}
}
2019-11-23 10:49:06 +00:00
if ( $ cmd eq 'setGeo' ) {
return "Need at least two parameters (mobileID, Setting)" if ( @ a < 4 ) ;
if ( $ a [ 3 ] eq "on" ) {
Log3 $ name , 3 , "TadoAPI: set $name: processing ($cmd)" ;
TadoAPI_SetGeoById ( $ hash , $ value , 1 ) ;
} else {
Log3 $ name , 3 , "TadoAPI: set $name: processing ($cmd)" ;
TadoAPI_SetGeoById ( $ hash , $ value , 0 ) ;
}
TadoAPI_GetGeoById ( $ hash , $ value ) ;
} elsif ( $ cmd eq 'setZoneOverlay' ) {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
2020-02-06 04:47:50 +00:00
return "Need at least two parameters [ZoneID] [Setting] (duration in sec); Setting Info: remove=delete overlay; 0=heating power off; 1<=desired temperature (overlay)" if ( @ a < 4 ) ;
2020-04-19 22:36:32 +00:00
2019-12-27 08:56:19 +00:00
if ( $ a [ 3 ] eq "remove" ) {
TadoAPI_SetZoneOverlayById ( $ hash , $ value , "remove" ) ;
2019-11-25 21:02:47 +00:00
} elsif ( defined ( $ a [ 4 ] ) ) {
TadoAPI_SetZoneOverlayById ( $ hash , $ value , $ a [ 3 ] , $ a [ 4 ] ) ;
2019-11-23 10:49:06 +00:00
} elsif ( $ a [ 3 ] >= 0 ) {
TadoAPI_SetZoneOverlayById ( $ hash , $ value , $ a [ 3 ] ) ;
}
2019-11-25 21:02:47 +00:00
Log3 $ name , 4 , "TadoAPI $name" . ": " . "$cmd finished" ;
2019-11-23 10:49:06 +00:00
2020-02-06 07:39:50 +00:00
} elsif ( $ cmd eq 'timedZoneOverlay' ) {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
return "Need at least three parameters [ZoneID] [Duration (sec)] [Setting]" if ( @ a < 4 ) ;
2020-04-19 22:36:32 +00:00
2020-02-06 07:39:50 +00:00
if ( defined ( $ a [ 4 ] ) ) {
TadoAPI_SetTimedZoneOverlay ( $ hash , $ value , $ a [ 3 ] , $ a [ 4 ] ) ;
2020-04-19 22:36:32 +00:00
}
2020-02-06 07:39:50 +00:00
Log3 $ name , 4 , "TadoAPI $name" . ": " . "$cmd finished" ;
2019-11-23 10:49:06 +00:00
} elsif ( $ cmd eq 'setAllOverlays' ) {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
2019-12-27 08:56:19 +00:00
return "Need at least one parameter (Setting) - Setting: remove=delete overlay; 0=heating power off; 1<=desired temperature (overlay)" if ( @ a < 3 ) ;
if ( $ value eq "remove" ) {
TadoAPI_SetAllOverlays ( $ hash , "remove" ) ;
2019-11-23 10:49:06 +00:00
} elsif ( $ value >= 0 ) {
TadoAPI_SetAllOverlays ( $ hash , $ value ) ;
}
2019-11-25 21:02:47 +00:00
Log3 $ name , 4 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
2019-11-23 10:49:06 +00:00
2019-12-14 15:29:46 +00:00
} elsif ( $ cmd eq 'updateAllOverlays' ) {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
TadoAPI_GetAllZoneOverlays ( $ hash ) ;
Log3 $ name , 4 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
2019-11-23 10:49:06 +00:00
} elsif ( $ cmd eq 'refreshToken' ) {
Log3 $ name , 3 , "TadoAPI: set $name: processing ($cmd)" ;
RemoveInternalTimer ( $ hash ) ;
InternalTimer ( gettimeofday ( ) + 10 , "TadoAPI_Update" , $ hash , 0 ) ;
TadoAPI_LoadToken ( $ hash ) ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
} elsif ( $ cmd eq 'update' ) {
Log3 $ name , 3 , "TadoAPI: set $name: processing ($cmd)" ;
2019-12-10 12:21:10 +00:00
TadoAPI_UpdateFn ( $ hash ) ;
2019-11-23 10:49:06 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
2019-11-23 12:22:13 +00:00
} elsif ( $ cmd eq 'zoneUpdate' ) {
2019-11-23 10:49:06 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
return "ZoneID as parameter needed" if ( ! $ value ) ;
if ( $ value >= 1 ) {
2019-12-14 15:29:46 +00:00
my ( $ temperature , $ humidity , $ desiredTemp , $ currentHeatingPower , $ overlay ) = TadoAPI_GetZoneReadingsById ( $ hash , $ value ) ;
my $ zoneName = TadoAPI_GetZoneNameById ( $ hash , $ value ) ;
2020-02-07 13:26:15 +00:00
if ( defined ( $ zoneName ) ) {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "Temperatur_" . $ zoneName , $ temperature ) ;
readingsBulkUpdate ( $ hash , "Luftfeuchtigkeit_" . $ zoneName , $ humidity ) ;
readingsBulkUpdate ( $ hash , "Heizleistung_" . $ zoneName , $ currentHeatingPower ) ;
readingsBulkUpdate ( $ hash , "OverlayType_" . $ zoneName , $ overlay ) ;
readingsBulkUpdate ( $ hash , "DesiredTemp_" . $ zoneName , $ desiredTemp ) ;
readingsEndUpdate ( $ hash , 1 ) ;
2020-04-19 22:36:32 +00:00
2020-02-07 13:26:15 +00:00
$ message = "OK" ;
}
2019-11-23 10:49:06 +00:00
} else {
return "Wrong ZoneID" ;
}
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
} elsif ( $ cmd eq 'password' ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
# name und cmd überspringen
shift @ a ;
shift @ a ;
# den Rest der das passwort enthält, als ein String
2020-04-19 22:36:32 +00:00
$ subcmd = join ( " " , @ a ) ;
2019-11-23 10:49:06 +00:00
$ message = TadoAPI_storePassword ( $ name , $ subcmd ) ;
2020-04-19 22:36:32 +00:00
2019-11-23 10:49:06 +00:00
# start the status update timer
RemoveInternalTimer ( $ hash ) ;
InternalTimer ( gettimeofday ( ) + 10 , "TadoAPI_Update" , $ hash , 0 ) ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
}
return $ message if $ message ;
return TadoAPI_Catch ( $@ ) if $@ ;
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_Get (@) {
my ( $ hash , @ a ) = @ _ ;
return "Need at least one parameters" if ( @ a < 2 ) ;
my $ cmd = $ a [ 1 ] ;
my $ value = $ a [ 2 ] ;
2020-04-19 22:36:32 +00:00
my $ name = $ hash - > { NAME } ;
2019-11-23 10:49:06 +00:00
my $ homeID = $ attr { $ name } { homeID } ;
my $ message = undef ;
if ( ! defined ( $ gets { $ cmd } ) ) {
my @ cmds = ( ) ;
2020-04-21 13:09:51 +00:00
for my $ key ( sort keys % gets ) {
2019-11-23 10:49:06 +00:00
push @ cmds , $ gets { $ key } ? $ key . ":" . join ( "," , $ gets { $ key } ) : $ key ;
}
return "Unknown argument $a[1], choose one of " . join ( " " , @ cmds ) ;
}
2019-11-23 19:22:41 +00:00
my $ pwd = TadoAPI_readPassword ( $ name ) ;
2020-04-19 22:36:32 +00:00
unless ( defined $ pwd )
2019-11-23 19:22:41 +00:00
{
$ message = "Error: no tado password set. Please define it with 'set $name password Your_tado_Password'" ;
Log3 $ name , 2 , "$name, $message" ;
$ hash - > { STATE } = "no password set" ;
return $ message ;
}
2019-11-23 10:49:06 +00:00
if ( $ cmd =~ /\Qget\E/ ) {
2020-04-19 22:36:32 +00:00
2019-11-23 10:49:06 +00:00
COMMAND_HANDLER: {
$ cmd eq "getGeo" and do {
return "Need at least one parameter (mobileID)" if ( @ a < 3 ) ;
return "Wrong MobileID" if ( length ( $ value ) < 6 ) ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
TadoAPI_GetGeoById ( $ hash , $ value ) ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
last ;
} ;
$ cmd eq "getMobileDevices" and do {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
my @ data = TadoAPI_GetMobileDevices ( $ hash ) ;
$ message = "Device List:\n" ;
2020-04-21 13:09:51 +00:00
for my $ item ( @ data ) {
2019-11-23 10:49:06 +00:00
$ message . = $ item - > { 'name' } . ": " . $ item - > { 'id' } . "\n" ;
} ;
2019-12-04 09:31:40 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished" ;
2019-11-23 10:49:06 +00:00
last ;
} ;
2019-11-23 12:22:13 +00:00
# only for testing
2019-11-23 10:49:06 +00:00
$ cmd eq "getXTest" and do {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
my $ zoneName = TadoAPI_GetZoneNameById ( $ hash , $ value ) ;
$ zoneName = "wrong Zone ID" unless $ zoneName ;
$ message = "Name: " . $ zoneName ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
last ;
} ;
$ cmd eq "getZoneDevices" and do {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
my @ devArr = TadoAPI_GetTadoDevices ( $ hash ) ;
my $ devicecount = 0 ;
$ message = "Tado-Device(s):\n" ;
for ( my $ i = 0 ; $ i < @ devArr ; $ i + + ) {
my $ tadodevices = $ devArr [ $ i ] - > { 'devices' } ;
$ message . = "ZoneID: " . ( $ devArr [ $ i ] - > { 'id' } ) ;
my $ spacer = 0 ;
2020-04-21 13:09:51 +00:00
for my $ item ( @$ tadodevices ) {
2019-11-23 10:49:06 +00:00
$ message . = "\t " if ( $ spacer > 0 ) ;
$ message . = " " . $ item - > { 'serialNo' } . " Battery: " . $ item - > { 'batteryState' } . "\n" ;
$ devicecount + + ;
$ spacer + + ;
}
}
$ message . = "There are $devicecount Tado-Device(s) in this HomeID(" . $ attr { $ name } { homeID } . ")." ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
last ;
} ;
$ cmd eq "getZoneInfo" and do {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "processing ($cmd)" ;
TadoAPI_GetZoneInfo ( $ hash ) ;
my $ zonecount = TadoAPI_GetZoneCount ( $ hash ) ;
$ message = "You have $zonecount Zones in Home " . $ attr { $ name } { homeID } . ".\n" ;
my @ devArr = TadoAPI_GetTadoDevices ( $ hash ) ;
for ( my $ i = 0 ; $ i < @ devArr ; $ i + + ) {
2020-02-07 13:26:15 +00:00
my $ zoneID = $ devArr [ $ i ] - > { 'id' } ;
$ message . = "Zone ID $zoneID: " . TadoAPI_GetZoneNameById ( $ hash , $ zoneID ) . "\n" ;
2019-11-23 10:49:06 +00:00
}
$ message . = "See Logfile for more Info" ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "$cmd finished\n" ;
last ;
} ;
}
return $ message if $ message ;
return TadoAPI_Catch ( $@ ) if $@ ;
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
}
2020-04-19 21:44:16 +00:00
sub TadoAPI_Catch {
2019-11-23 10:49:06 +00:00
my $ exception = shift ;
if ( $ exception ) {
$ exception =~ /^(.*)( at.*FHEM.*)$/ ;
return $ 1 ;
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_Undefine ($$) {
my ( $ hash , $ name ) = @ _ ;
RemoveInternalTimer ( $ hash ) ;
#todo remove tokenfile
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
2019-12-10 14:28:32 +00:00
sub TadoAPI_CheckStatus (@) {
2019-11-23 10:49:06 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2019-12-10 14:28:32 +00:00
# test api status
2019-12-10 09:14:23 +00:00
my $ param = {
url = > $ AuthURL ,
2019-12-10 14:28:32 +00:00
timeout = > 5 ,
2019-12-10 09:14:23 +00:00
hash = > $ hash ,
method = > "GET" ,
2020-04-19 22:36:32 +00:00
header = > "" ,
callback = > \ & TadoAPI_callback
2019-12-10 09:14:23 +00:00
} ;
2019-12-10 14:28:32 +00:00
#test if api is reachable
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $AuthURL" ;
2019-12-10 14:28:32 +00:00
HttpUtils_NonblockingGet ( $ param ) ;
2020-04-19 20:13:20 +00:00
return ;
2019-12-10 14:28:32 +00:00
}
sub TadoAPI_LoadToken (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ tokenFileName = $ tokenFile . "_" . $ name ;
my $ tokenLifeTime = $ hash - > { TOKEN_LIFETIME } ;
$ tokenLifeTime = 0 if ( ! defined $ tokenLifeTime || $ tokenLifeTime eq '' ) ;
my $ Token = undef ;
2019-12-10 09:14:23 +00:00
2019-11-23 10:49:06 +00:00
if ( $ apiStatus ) {
2020-04-19 22:17:52 +00:00
open ( my $ TOKENFILE , q{ < } , $ tokenFileName ) or croak ( "ERROR: $!" ) ;
eval { $ Token = decode_json ( <$TOKENFILE> ) } ;
2020-04-19 20:13:20 +00:00
close ( $ TOKENFILE ) ;
2020-04-19 22:36:32 +00:00
if ( $@ || $ tokenLifeTime < gettimeofday ( ) ) {
2019-11-25 21:02:47 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Error while loading: $@ ,requesting new one" if $@ ;
2019-11-23 10:49:06 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Token is expired, requesting new one" if $ tokenLifeTime < gettimeofday ( ) ;
2019-12-10 09:14:23 +00:00
$ Token = TadoAPI_NewTokenRequest ( $ hash ) ;
2020-04-19 22:36:32 +00:00
TadoAPI_CheckStatus ( $ hash ) ;
2019-11-23 10:49:06 +00:00
} else {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Token expires at " . localtime ( $ tokenLifeTime ) ;
# if token is about to expire, refresh him
if ( ( $ tokenLifeTime - 45 ) < gettimeofday ( ) ) {
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Token will expire soon, refreshing" ;
$ Token = TadoAPI_TokenRefresh ( $ hash ) ;
}
}
2020-04-19 22:36:32 +00:00
return $ Token if $ Token ;
2019-12-10 08:31:43 +00:00
}
2020-04-19 20:13:20 +00:00
TadoAPI_CheckStatus ( $ hash ) ;
2020-04-19 22:36:32 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_NewTokenRequest (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ username = $ hash - > { TADO_USER } ;
my $ password = TadoAPI_readPassword ( $ name ) ;
my $ tokenFileName = $ tokenFile . "_" . $ name ;
Log3 $ name , 5 , "TadoAPI $name" . ": " . "calling NewTokenRequest()" ;
2020-04-20 14:13:19 +00:00
my $ data = {
2019-11-23 10:49:06 +00:00
client_id = > $ client_id ,
client_secret = > $ client_secret ,
username = > $ username ,
password = > $ password ,
scope = > $ scope ,
grant_type = > 'password'
} ;
my $ param = {
url = > $ AuthURL ,
method = > 'POST' ,
timeout = > 5 ,
hash = > $ hash ,
data = > $ data
} ;
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $AuthURL" ;
2020-04-19 21:44:16 +00:00
my ( $ err , $ returnData ) = HttpUtils_BlockingGet ( $ param ) ;
2019-11-23 10:49:06 +00:00
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "NewTokenRequest: Error while requesting " . $ param - > { url } . " - $err" ;
}
2020-04-19 21:44:16 +00:00
elsif ( $ returnData ne "" )
2019-11-23 10:49:06 +00:00
{
2020-04-19 21:44:16 +00:00
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $returnData" ;
my $ decoded_data = eval { decode_json ( $ returnData ) } ;
2019-11-23 10:49:06 +00:00
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "NewTokenRequest: decode_json failed, invalid json. error: $@ " ;
} else {
#write token data in file
2020-04-19 22:17:52 +00:00
open ( my $ TOKENFILE , q{ > } , $ tokenFileName ) or croak ( "ERROR: $!" ) ;
2020-04-19 21:44:16 +00:00
print $ TOKENFILE $ returnData . "\n" ;
2020-04-19 19:46:53 +00:00
close ( $ TOKENFILE ) ;
2019-11-23 10:49:06 +00:00
# token lifetime management
$ hash - > { TOKEN_LIFETIME } = gettimeofday ( ) + $ decoded_data - > { 'expires_in' } ;
$ hash - > { TOKEN_LIFETIME_HR } = localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Retrived new authentication token successfully. Valid until " . localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
$ hash - > { STATE } = "reachable" ;
return $ decoded_data ;
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_TokenRefresh (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ tokenFileName = $ tokenFile . "_" . $ name ;
2020-04-19 20:13:20 +00:00
my $ Token = undef ;
2019-11-23 10:49:06 +00:00
# load token
2020-04-19 22:17:52 +00:00
open ( my $ TOKENFILE , q{ < } , $ tokenFileName ) or croak ( "ERROR: $!" ) ;
eval { $ Token = decode_json ( <$TOKENFILE> ) } ;
2020-04-19 20:13:20 +00:00
close ( $ TOKENFILE ) ;
2019-11-23 10:49:06 +00:00
2020-04-20 14:13:19 +00:00
my $ data = {
2019-11-23 10:49:06 +00:00
client_id = > $ client_id ,
client_secret = > $ client_secret ,
scope = > $ scope ,
grant_type = > 'refresh_token' ,
2020-04-19 21:44:16 +00:00
refresh_token = > $ Token - > { 'refresh_token' }
2019-11-23 10:49:06 +00:00
} ;
my $ param = {
url = > $ AuthURL ,
method = > 'POST' ,
timeout = > 5 ,
hash = > $ hash ,
data = > $ data
} ;
#Log3 $name, 5, 'Blocking GET TokenRefresh: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $AuthURL" ;
2020-04-19 21:44:16 +00:00
my ( $ err , $ returnData ) = HttpUtils_BlockingGet ( $ param ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 22:36:32 +00:00
if ( $ err ne "" )
2019-11-23 10:49:06 +00:00
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "TokenRefresh: Error in token retrival while requesting " . $ param - > { url } . " - $err" ;
$ hash - > { STATE } = "error" ;
}
2020-04-19 22:36:32 +00:00
elsif ( $ returnData ne "" )
2019-11-23 10:49:06 +00:00
{
2020-04-19 21:44:16 +00:00
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $returnData" ;
my $ decoded_data = eval { decode_json ( $ returnData ) ; } ;
2019-11-23 10:49:06 +00:00
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "TokenRefresh: decode_json failed, invalid json. error:$@\n" if $@ ;
$ hash - > { STATE } = "error" ;
} else {
#write token data in file
2020-04-19 22:17:52 +00:00
open ( my $ TOKENFILE , q{ > } , $ tokenFileName ) or croak ( "ERROR: $!" ) ;
2020-04-19 21:44:16 +00:00
print $ TOKENFILE $ returnData . "\n" ;
2020-04-19 20:13:20 +00:00
close ( $ TOKENFILE ) ;
2019-11-23 10:49:06 +00:00
# token lifetime management
2020-04-19 22:36:32 +00:00
$ hash - > { TOKEN_LIFETIME } = gettimeofday ( ) + $ decoded_data - > { 'expires_in' } ;
2019-11-23 10:49:06 +00:00
$ hash - > { TOKEN_LIFETIME_HR } = localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
Log3 $ name , 5 , "TadoAPI $name" . ": " . "TokenRefresh: Refreshed authentication token successfully. Valid until " . localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
$ hash - > { STATE } = "reachable" ;
return $ decoded_data ;
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_Update (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
Log3 $ name , 5 , "TadoAPI $name" . ": " . "TadoAPI_Update called" ;
my $ nextTimer = "none" ;
2019-11-23 11:47:42 +00:00
my $ intervall = 300 ;
2019-12-10 14:28:32 +00:00
2019-11-23 11:47:42 +00:00
$ intervall = $ attr { $ name } { updateIntervall } if ( defined ( $ attr { $ name } { updateIntervall } ) && $ attr { $ name } { updateIntervall } =~ m/^-?\d+$/ ) ;
2019-12-10 14:28:32 +00:00
$ nextTimer = gettimeofday ( ) + $ intervall ;
$ hash - > { NEXT_UPDATE } = localtime ( $ nextTimer ) ;
2019-11-23 11:47:42 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Next Timer = $nextTimer" ;
2019-12-10 14:28:32 +00:00
2019-11-23 10:49:06 +00:00
RemoveInternalTimer ( $ hash ) ;
InternalTimer ( $ nextTimer , "TadoAPI_Update" , $ hash , 0 ) ;
# update subs
2019-12-10 12:21:10 +00:00
TadoAPI_UpdateFn ( $ hash ) ;
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
######################## tado methods ########################
##############################################################
sub TadoAPI_SetZoneOverlayById (@) {
2019-11-25 21:02:47 +00:00
my ( $ hash , $ zoneID , $ setting , $ duration ) = @ _ ;
2019-11-23 10:49:06 +00:00
my $ name = $ hash - > { NAME } ;
2020-04-19 22:36:32 +00:00
my $ homeID = $ attr { $ name } { homeID } ;
2019-11-23 10:49:06 +00:00
my $ URL = $ QueryURL . qq{ /$homeID/zones/$zoneID/overlay } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
my $ method = "" ;
2019-11-25 21:02:47 +00:00
my $ myjson = undef ;
2020-04-19 22:36:32 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": SetOverlay for Zone $zoneID (Setting: " . $ setting . ") - " . "query-URL: $URL" ;
2019-11-23 10:49:06 +00:00
2019-11-25 21:02:47 +00:00
my $ dt = time ( ) ;
2019-12-04 09:31:40 +00:00
$ dt += $ duration if defined ( $ duration ) ;
2019-11-25 21:02:47 +00:00
2020-02-07 13:26:15 +00:00
delete ( $ hash - > { helper } { LockedZones } { $ zoneID } ) ;
2019-11-25 21:02:47 +00:00
# remove overlay
2019-12-27 08:56:19 +00:00
if ( $ setting eq "remove" ) {
2019-11-25 21:02:47 +00:00
$ method = "DELETE" ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "Deleting Overlay for Zone $zoneID" ;
}
# turn heating of
elsif ( $ setting == 0 ) {
# turn off for timer
if ( defined ( $ duration ) && $ duration > 0 ) {
$ method = "PUT" ;
$ myjson = {
2020-04-19 22:36:32 +00:00
type = > "MANUAL" ,
setting = > {
2019-11-23 10:49:06 +00:00
type = > "HEATING" ,
2019-11-25 21:02:47 +00:00
power = > "OFF"
2019-11-23 10:49:06 +00:00
} ,
2019-11-25 21:02:47 +00:00
termination = > {
type = > "TIMER" ,
durationInSeconds = > $ duration ,
expiry = > strftime ( '%Y-%m-%dT%H:%M:%SZ' , gmtime ( $ dt ) )
}
} ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "Timer Overlay for Zone $zoneID . Power off for: $duration seconds" ;
}
# infinite off
else {
$ method = "PUT" ;
2020-04-19 22:36:32 +00:00
$ myjson = {
setting = > {
2019-11-25 21:02:47 +00:00
type = > "HEATING" ,
power = > "OFF" ,
} ,
termination = > {
type = > "MANUAL"
2019-11-23 10:49:06 +00:00
} ,
} ;
2019-11-25 21:02:47 +00:00
}
2020-04-19 22:36:32 +00:00
}
2019-11-25 21:02:47 +00:00
elsif ( $ setting > 0 ) {
2020-02-07 13:26:15 +00:00
# set timed overlay
2019-11-25 21:02:47 +00:00
if ( defined ( $ duration ) && $ duration > 0 ) {
$ method = "PUT" ;
$ myjson = {
2020-04-19 22:36:32 +00:00
type = > "MANUAL" ,
setting = > {
2019-11-25 21:02:47 +00:00
type = > "HEATING" ,
power = > "ON" ,
temperature = > {
2020-04-19 22:36:32 +00:00
celsius = > $ setting
2019-11-25 21:02:47 +00:00
}
} ,
termination = > {
type = > "TIMER" ,
durationInSeconds = > $ duration ,
expiry = > strftime ( '%Y-%m-%dT%H:%M:%SZ' , gmtime ( $ dt ) ) ,
}
} ;
2020-02-06 07:39:50 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "Set Timer Overlay for Zone $zoneID with $duration seconds expire." ;
2020-02-07 13:26:15 +00:00
# seet lock for this zone
$ hash - > { helper } { LockedZones } { $ zoneID } = "locked" ;
2019-11-25 21:02:47 +00:00
}
else {
# infinite setting
$ method = "PUT" ;
2020-04-19 22:36:32 +00:00
$ myjson = {
setting = > {
2019-11-23 10:49:06 +00:00
type = > "HEATING" ,
power = > "ON" ,
temperature = > {
2020-04-19 22:36:32 +00:00
celsius = > $ setting
2019-11-23 10:49:06 +00:00
} ,
} ,
2019-11-25 21:02:47 +00:00
termination = > {
2019-11-23 10:49:06 +00:00
type = > "MANUAL"
} ,
} ;
2019-11-25 21:02:47 +00:00
}
2019-11-23 10:49:06 +00:00
}
2019-11-25 21:02:47 +00:00
$ myjson = encode_json ( $ myjson ) if ( defined ( $ myjson ) ) ;
2019-11-23 10:49:06 +00:00
my $ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > $ method ,
timeout = > 5 ,
callback = > \ & Tado_UpdateZoneOverlayCallback ,
hash = > $ hash ,
setting = > $ setting ,
zoneID = > $ zoneID ,
data = > $ myjson
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_SetAllOverlays (@) {
2020-02-06 04:47:50 +00:00
my ( $ hash , $ setting , $ duration ) = @ _ ;
2019-11-23 10:49:06 +00:00
my $ name = $ hash - > { NAME } ;
my $ homeID = $ attr { $ name } { homeID } ;
2019-12-14 15:29:46 +00:00
my @ zones = TadoAPI_GetTadoDevices ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2019-12-14 15:29:46 +00:00
for ( my $ i = 0 ; $ i < @ zones ; $ i + + ) {
2020-02-07 13:26:15 +00:00
my $ zoneID = $ zones [ $ i ] - > { 'id' } ;
2020-02-06 04:47:50 +00:00
if ( defined ( $ duration ) && $ duration > 0 ) {
2020-02-07 13:26:15 +00:00
TadoAPI_SetZoneOverlayById ( $ hash , $ zoneID , $ setting , $ duration ) ;
2020-02-06 04:47:50 +00:00
} else {
2020-02-07 13:26:15 +00:00
TadoAPI_SetZoneOverlayById ( $ hash , $ zoneID , $ setting ) ;
2020-02-06 04:47:50 +00:00
}
2019-12-14 15:29:46 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-12-14 15:29:46 +00:00
}
2019-11-23 10:49:06 +00:00
2019-12-14 15:29:46 +00:00
sub TadoAPI_GetAllZoneOverlays (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my @ zones = TadoAPI_GetTadoDevices ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-21 13:09:51 +00:00
for my $ zone ( @ zones ) {
2020-02-07 13:26:15 +00:00
my $ zoneID = $ zone - > { 'id' } ;
2019-12-14 15:29:46 +00:00
my $ zoneName = TadoAPI_ReplaceUmlaute ( $ zone - > { 'name' } ) ;
2020-02-07 13:26:15 +00:00
my ( $ temperature , $ humidity , $ desiredTemp , $ currentHeatingPower , $ overlay ) = TadoAPI_GetZoneReadingsById ( $ hash , $ zoneID ) ;
2019-12-14 15:29:46 +00:00
readingsSingleUpdate ( $ hash , "OverlayType_" . $ zoneName , $ overlay , 1 ) ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 22:36:32 +00:00
sub TadoAPI_UpdateFn (@) {
2019-11-23 10:49:06 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
my $ homeID = $ attr { $ name } { homeID } ;
2020-04-19 21:44:16 +00:00
if ( $ apiStatus == 1 && defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
# zone specific updates
my $ URL = $ QueryURL . qq{ /$homeID/zones } ;
my $ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 25 ,
incrementalTimout = > 1 ,
hash = > $ hash ,
callback = > \ & TadoAPI_UpdateAllZoneReadingsCallback
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "UpdFN: Request $URL" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
2020-04-19 22:36:32 +00:00
2019-11-23 10:49:06 +00:00
# mobile devices
$ URL = $ QueryURL . qq{ /$homeID/mobileDevices } ;
$ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
2019-12-10 22:49:02 +00:00
timeout = > 7 ,
2019-11-23 10:49:06 +00:00
incrementalTimout = > 1 ,
callback = > \ & TadoAPI_UpdateMobileReadingsCallback ,
hash = > $ hash
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
########################################################################################################################################################################
# Callback Subs
########################################################################################################################################################################
sub TadoAPI_callback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
2020-04-19 22:36:32 +00:00
my $ name = $ hash - > { NAME } ;
2019-11-23 10:49:06 +00:00
$ param - > { code } = 0 unless defined $ param - > { code } ;
2020-04-19 22:36:32 +00:00
if ( $ param - > { code } == 401 || $ param - > { code } == 400 ) {
2019-11-23 10:49:06 +00:00
$ apiStatus = 1 ;
$ hash - > { STATE } = "reachable" ;
Log3 $ name , 5 , "TadoAPI $name" . ": " . "API is reachable. Callback Status: " . $ param - > { code } ;
} else {
$ apiStatus = 0 ;
$ hash - > { STATE } = "error" ;
2019-12-10 14:28:32 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "API error: apiStatus $apiStatus ($err)" ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_UpdateAllZoneReadingsCallback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
if ( $ err ne "" )
{
2020-04-19 22:36:32 +00:00
Log3 $ name , 3 , "Error in TadoAPI_UpdateZoneCallback while requesting " . $ param - > { url } . " - $err" ;
2019-11-23 10:49:06 +00:00
}
elsif ( $ data ne "" )
{
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
my $ decoded_data = eval { decode_json ( $ data ) } ;
# if api returns error
eval { my $ error = @$ decoded_data ; } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "UpdateAllZonesCallback: decode_json failed, invalid json. error:$@\n" if $@ ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "UpdateAllZonesCallback: Error in decoded data, Code: " . $ decoded_data - > { 'errors' } - > [ 0 ] - > { 'code' } if ( exists ( $ decoded_data - > { 'errors' } - > [ 0 ] - > { 'code' } ) ) ;
$ hash - > { LastRequest } = "error" ;
} else {
2020-04-19 22:36:32 +00:00
readingsBeginUpdate ( $ hash ) ;
2020-04-21 13:09:51 +00:00
for my $ zone ( @$ decoded_data ) {
2020-02-07 13:26:15 +00:00
my $ zoneID = $ zone - > { 'id' } ;
2019-12-11 10:12:46 +00:00
my $ zoneName = TadoAPI_ReplaceUmlaute ( $ zone - > { 'name' } ) ;
2020-02-07 13:26:15 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": " . "Set Reading Update for Zone $zoneID " ;
2019-11-23 10:49:06 +00:00
2020-02-07 13:26:15 +00:00
my ( $ temperature , $ humidity , $ desiredTemp , $ currentHeatingPower , $ overlay ) = TadoAPI_GetZoneReadingsById ( $ hash , $ zoneID ) ;
2019-12-10 16:41:08 +00:00
2019-12-10 15:20:57 +00:00
# updates zone readings
2019-12-10 16:41:08 +00:00
readingsBulkUpdate ( $ hash , "Temperatur_" . $ zoneName , $ temperature ) ;
readingsBulkUpdate ( $ hash , "Luftfeuchtigkeit_" . $ zoneName , $ humidity ) ;
readingsBulkUpdate ( $ hash , "Heizleistung_" . $ zoneName , $ currentHeatingPower ) ;
readingsBulkUpdate ( $ hash , "OverlayType_" . $ zoneName , $ overlay ) ;
readingsBulkUpdate ( $ hash , "DesiredTemp_" . $ zoneName , $ desiredTemp ) ;
2019-12-10 15:20:57 +00:00
2019-11-23 10:49:06 +00:00
# iterate through all devices in zone
my $ devices = $ zone - > { 'devices' } ;
2020-04-21 13:09:51 +00:00
for my $ device ( @$ devices ) {
2019-11-23 10:49:06 +00:00
readingsBulkUpdate ( $ hash , "Battery_" . $ device - > { 'serialNo' } , $ device - > { 'batteryState' } ) ;
}
}
2019-12-10 17:07:41 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
2019-11-23 10:49:06 +00:00
}
my $ zonecount = TadoAPI_GetZoneCount ( $ hash ) ;
2020-04-19 22:36:32 +00:00
readingsBeginUpdate ( $ hash ) ;
2019-11-23 10:49:06 +00:00
readingsBulkUpdate ( $ hash , "ActiveZones" , $ zonecount ) ;
2019-12-10 16:41:08 +00:00
readingsEndUpdate ( $ hash , 0 ) ;
2019-12-11 10:12:46 +00:00
$ hash - > { LastRequest } = "OK" ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub Tado_UpdateZoneOverlayCallback ($)
{
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
2020-02-07 13:26:15 +00:00
my $ zoneID = $ param - > { zoneID } ;
2019-11-23 10:49:06 +00:00
my $ setting = $ param - > { setting } ;
if ( $ err ne "" )
{
2020-04-19 22:36:32 +00:00
Log3 $ name , 3 , "Error in UpdateZoneOverlayCallback while requesting " . $ param - > { url } . " - $err" ;
2019-11-23 10:49:06 +00:00
}
elsif ( $ data ne "" )
{
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
2020-02-07 13:26:15 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "set (async) Overlay for Zone $zoneID to: $setting" ;
2020-04-19 22:36:32 +00:00
2019-11-23 10:49:06 +00:00
}
# finaly update readings
2020-02-07 13:26:15 +00:00
my ( $ temperature , $ humidity , $ desiredTemp , $ currentHeatingPower , $ overlay ) = TadoAPI_GetZoneReadingsById ( $ hash , $ zoneID ) ;
my $ zoneName = TadoAPI_GetZoneNameById ( $ hash , $ zoneID ) ;
if ( defined ( $ zoneName ) ) {
# updates zone readings
readingsBeginUpdate ( $ hash ) ;
# readingsBulkUpdate($hash, "Temperatur_" . $zoneName, $temperature);
# readingsBulkUpdate($hash, "Luftfeuchtigkeit_" . $zoneName, $humidity);
# readingsBulkUpdate($hash, "Heizleistung_" . $zoneName, $currentHeatingPower);
readingsBulkUpdate ( $ hash , "OverlayType_" . $ zoneName , $ overlay ) ;
readingsBulkUpdate ( $ hash , "DesiredTemp_" . $ zoneName , $ desiredTemp ) ;
2020-04-19 22:36:32 +00:00
# lock zone if timed overlay
2020-02-07 13:26:15 +00:00
if ( exists ( $ hash - > { helper } - > { LockedZones } { $ zoneID } ) ) {
readingsBulkUpdate ( $ hash , "Zone" . $ zoneID . "Lock" , "timer" ) ;
readingsEndUpdate ( $ hash , 1 ) ;
} else {
readingsEndUpdate ( $ hash , 1 ) ;
readingsDelete ( $ hash , "Zone" . $ zoneID . "Lock" ) ;
2020-04-19 22:36:32 +00:00
}
2020-02-07 13:26:15 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_LogInfoCallback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
if ( $ err ne "" )
{
2020-04-19 22:36:32 +00:00
Log3 $ name , 3 , "Error in LogInfoCallback while requesting " . $ param - > { url } . " - $err" ;
2019-11-23 10:49:06 +00:00
}
elsif ( $ data ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . $ param - > { infotext } . ":\n" . $ data . "\n" ;
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_SetGeoByIdCallback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
if ( $ err ne "" )
{
2020-04-19 22:36:32 +00:00
Log3 $ name , 3 , "Error in TadoAPI_SetGeoByIdCallback while requesting " . $ param - > { url } . " - $err" ;
2019-11-23 10:49:06 +00:00
}
elsif ( $ data ne "" )
{
2020-04-20 14:13:19 +00:00
Log3 $ name , 3 , "SetGeoById URL: " . $ param - > { url } . " returned: $data" ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_UpdateMobileReadingsCallback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
if ( $ err ne "" )
{
2020-04-19 22:36:32 +00:00
Log3 $ name , 3 , "Error in UpdateMobileReadingsCallback while requesting " . $ param - > { url } . " - $err" ;
2019-11-23 10:49:06 +00:00
}
elsif ( $ data ne "" )
{
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
my $ decoded_data = eval { decode_json ( $ data ) } ;
# if api returns error
eval { my $ error = @$ decoded_data ; } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "Decode_json failed, invalid json. error:$@\n" if $@ ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "Error in UpdateMobileReadingsCallback, Code: " . $ decoded_data - > { 'errors' } - > [ 0 ] - > { 'code' } ;
$ hash - > { LastRequest } = "error" ;
} else {
2020-04-21 13:09:51 +00:00
for my $ item ( @$ decoded_data ) {
2019-12-10 22:49:02 +00:00
TadoAPI_GetGeoById ( $ hash , $ item - > { 'id' } , $ item ) ;
2019-11-23 10:49:06 +00:00
}
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_GetZoneInfo (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2020-04-19 22:36:32 +00:00
my $ homeID = $ attr { $ name } { homeID } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
# HomeInfo
my $ URL = qq{ https://my.tado.com/api/v2/me } ;
my $ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 8 ,
infotext = > "HomeInfos" ,
hash = > $ hash ,
callback = > \ & TadoAPI_LogInfoCallback
} ;
HttpUtils_NonblockingGet ( $ request ) ;
# TadoDevicesInfo
$ URL = $ QueryURL . qq{ /$homeID/zones } ;
$ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 3 ,
infotext = > "Tado Devices Info" ,
hash = > $ hash ,
callback = > \ & TadoAPI_LogInfoCallback
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
# Mobileinfo
$ URL = $ QueryURL . qq{ /$homeID/mobileDevices } ;
$ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 3 ,
infotext = > "Mobile Devices Info" ,
hash = > $ hash ,
callback = > \ & TadoAPI_LogInfoCallback
} ;
HttpUtils_NonblockingGet ( $ request ) ;
my @ mobDev = TadoAPI_GetMobileDevices ( $ hash ) ;
for ( my $ i = 0 ; $ i < @ mobDev ; $ i + + ) {
my $ mobileID = $ mobDev [ $ i ] - > { 'id' } ;
$ URL = $ QueryURL . qq{ /$homeID/mobileDevices/$mobileID/settings } ;
$ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 3 ,
infotext = > "Mobile Device $mobileID" ,
hash = > $ hash ,
callback = > \ & TadoAPI_LogInfoCallback
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
}
# zones
my @ devArr = TadoAPI_GetTadoDevices ( $ hash ) ;
for ( my $ i = 0 ; $ i < @ devArr ; $ i + + ) {
2020-02-07 13:26:15 +00:00
my $ zoneID = $ devArr [ $ i ] - > { 'id' } ;
2020-04-19 21:44:16 +00:00
$ URL = $ QueryURL . qq{ /$homeID/zones/$zoneID/state } ;
2020-02-07 13:26:15 +00:00
my $ infotext = "ZoneID $zoneID (" . TadoAPI_GetZoneNameById ( $ hash , $ zoneID ) . ") Status" ;
2020-04-19 21:44:16 +00:00
$ request = {
2019-11-23 10:49:06 +00:00
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 3 ,
infotext = > $ infotext ,
hash = > $ hash ,
callback = > \ & TadoAPI_LogInfoCallback
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_SetGeoById (@) {
my ( $ hash , $ mobileID , $ geo ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ homeID = $ attr { $ name } { homeID } ;
my $ URL = $ QueryURL . qq{ /$homeID/mobileDevices/$mobileID/settings } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2020-04-20 14:13:19 +00:00
my $ data = { } ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
if ( $ geo ) {
2020-04-20 14:13:19 +00:00
$ data = { geoTrackingEnabled = > "true" } ;
2019-11-23 10:49:06 +00:00
} else {
2020-04-20 14:13:19 +00:00
$ data = { geoTrackingEnabled = > "false" } ;
2019-11-23 10:49:06 +00:00
}
$ data = encode_json ( $ data ) ;
my $ request = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'PUT' ,
timeout = > 3 ,
mobileID = > $ mobileID ,
data = > $ data ,
hash = > $ hash ,
callback = > \ & TadoAPI_SetGeoByIdCallback
} ;
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2020-04-20 14:13:19 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "PUT setting $data" ;
2019-11-23 10:49:06 +00:00
HttpUtils_NonblockingGet ( $ request ) ;
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
######################################
############ Helpers #################
######################################
sub TadoAPI_ReplaceUmlaute (@) {
my ( $ string ) = @ _ ;
my % umlaute = ( "ä" = > "ae" , "Ä" = > "Ae" , "ü" = > "ue" , "Ü" = > "Ue" , "ö" = > "oe" , "Ö" = > "Oe" , "ß" = > "ss" ) ;
my $ umlautkeys = join ( "|" , keys ( % umlaute ) ) ;
$ string =~ s/($umlautkeys)/$umlaute{$1}/g ;
return $ string ;
}
2020-02-07 13:26:15 +00:00
# helper sub for fhem tablet-ui thermostat widget: set timedZoneOverlay <zoneID> <duration> <setting>
2020-02-06 07:39:50 +00:00
sub TadoAPI_SetTimedZoneOverlay (@) {
2020-02-07 13:26:15 +00:00
my ( $ hash , $ zoneID , $ duration , $ setting ) = @ _ ;
my $ name = $ hash - > { NAME } ;
TadoAPI_SetZoneOverlayById ( $ hash , $ zoneID , $ setting , $ duration ) ;
2020-04-19 20:13:20 +00:00
return ;
2020-02-06 07:39:50 +00:00
}
2019-11-23 10:49:06 +00:00
sub TadoAPI_GetHomeId (@) {
# returns first home id only
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) )
2019-11-23 10:49:06 +00:00
{
my $ param = {
url = > $ DataURL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 2 ,
hash = > $ hash ,
2019-12-10 09:14:23 +00:00
} ;
2019-11-23 10:49:06 +00:00
2019-12-10 09:14:23 +00:00
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $DataURL" ;
2019-12-10 09:14:23 +00:00
my ( $ err , $ data ) = HttpUtils_BlockingGet ( $ param ) ;
2019-11-23 10:49:06 +00:00
2019-12-10 09:14:23 +00:00
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetHomeId: Error while requesting " . $ param - > { url } . " - $err" ;
}
elsif ( $ data ne "" )
{
2020-04-20 14:13:19 +00:00
Log3 $ name , 5 , "URL " . $ param - > { url } . " returned: $data" ;
2019-12-10 09:14:23 +00:00
my $ decoded_data = eval { decode_json ( $ data ) } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetHomeId: Decode_json failed, invalid json. error:$@" if $@ ;
$ hash - > { LastRequest } = "error" ;
} else {
$ hash - > { LastRequest } = "OK" ;
return $ decoded_data - > { 'homes' } - > [ 0 ] - > { 'id' } if ( exists ( $ decoded_data - > { 'homes' } ) ) ;
2019-11-23 10:49:06 +00:00
}
2019-12-10 09:14:23 +00:00
}
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_GetGeoById (@) {
2019-12-10 22:49:02 +00:00
# returns geo setting and distance from home; takes an item object or querys itself
my ( $ hash , $ mobileID , $ item ) = @ _ ;
2019-11-23 10:49:06 +00:00
my $ name = $ hash - > { NAME } ;
my $ homeID = $ attr { $ name } { homeID } ;
2019-12-10 22:49:02 +00:00
my $ URL = $ QueryURL . qq{ /$homeID/mobileDevices } ;
2019-11-23 10:49:06 +00:00
2019-12-10 22:49:02 +00:00
if ( ! defined ( $ item ) ) {
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-12-10 22:49:02 +00:00
my $ param = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-12-10 22:49:02 +00:00
method = > 'GET' ,
timeout = > 4 ,
hash = > $ hash ,
} ;
2019-11-23 10:49:06 +00:00
2019-12-10 22:49:02 +00:00
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-12-10 22:49:02 +00:00
my ( $ err , $ data ) = HttpUtils_BlockingGet ( $ param ) ;
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetGeoById: Error while requesting " . $ param - > { url } . " - $err" ;
}
elsif ( $ data ne "" )
{
2020-04-20 14:13:19 +00:00
Log3 $ name , 5 , "GetGeoById URL: " . $ param - > { url } . " returned: $data" ;
2019-12-10 22:49:02 +00:00
my $ decoded_data = eval { decode_json ( $ data ) } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetGeoById: Decode_json failed, invalid json. error:$@\n" if $@ ;
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetGeoById: Error in UpdateMobileReadingsCallback, Code: " . $ decoded_data - > { 'errors' } - > [ 0 ] - > { 'code' } ;
$ hash - > { LastRequest } = "error" ;
} else {
2020-04-21 13:09:51 +00:00
for my $ item ( @$ decoded_data ) {
2019-12-10 22:49:02 +00:00
if ( $ item - > { 'id' } eq $ mobileID ) {
return my ( $ setting , $ distance ) = TadoAPI_ParseMobileItem ( $ hash , $ item ) ;
}
}
}
2019-11-23 10:49:06 +00:00
}
2019-12-10 09:14:23 +00:00
}
2019-12-10 22:49:02 +00:00
} elsif ( defined ( $ item ) ) {
2019-12-11 10:12:46 +00:00
Log3 $ name , 5 , "TadoAPI $name" . ": " . "GetGeoById: parsing passed item" ;
2019-12-10 22:49:02 +00:00
return my ( $ setting , $ distance ) = TadoAPI_ParseMobileItem ( $ hash , $ item ) ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
2019-12-10 22:49:02 +00:00
sub TadoAPI_ParseMobileItem (@) {
my ( $ hash , $ item ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ setting = 0 ;
$ setting = 1 if $ item - > { 'settings' } - > { 'geoTrackingEnabled' } ;
2020-04-19 22:36:32 +00:00
my $ distance = "-" ;
2019-12-10 22:49:02 +00:00
$ distance = $ item - > { 'location' } - > { 'relativeDistanceFromHomeFence' } if $ setting ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "GeoTracking_" . $ item - > { 'id' } , $ item - > { 'settings' } - > { 'geoTrackingEnabled' } ) ;
if ( defined ( $ item - > { 'location' } - > { 'atHome' } ) && $ item - > { 'location' } - > { 'atHome' } ) {
# present
readingsBulkUpdate ( $ hash , "GeoLocation_" . $ item - > { 'id' } , "present" ) ;
} elsif ( defined ( $ item - > { 'location' } - > { 'atHome' } ) ) {
# away
readingsBulkUpdate ( $ hash , "GeoLocation_" . $ item - > { 'id' } , "away" ) ;
} else {
# no state
readingsDelete ( $ hash , "GeoLocation_" . $ item - > { 'id' } ) ;
}
readingsBulkUpdate ( $ hash , "GeoDistance_" . $ item - > { 'id' } , $ distance ) if $ setting && $ attr { $ name } { showPosData } ;
readingsDelete ( $ hash , "GeoDistance_" . $ item - > { 'id' } ) if ! defined ( $ attr { $ name } { showPosData } ) || $ attr { $ name } { showPosData } == 0 || ! $ setting ;
readingsEndUpdate ( $ hash , 1 ) ;
$ hash - > { LastRequest } = "OK" ;
return ( $ setting , $ distance ) ;
}
2019-11-23 10:49:06 +00:00
sub TadoAPI_GetMobileDevices (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ homeID = $ attr { $ name } { homeID } ;
my $ URL = $ QueryURL . qq{ /$homeID/mobileDevices } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
my $ param = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 2 ,
hash = > $ hash
} ;
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
my ( $ err , $ data ) = HttpUtils_BlockingGet ( $ param ) ;
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetMobileDevices: Error while requesting " . $ param - > { url } . " - $err" ;
}
elsif ( $ data ne "" )
{
my @ devices = ( ) ;
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
my $ decoded_data = eval { decode_json ( $ data ) } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetMobileDevices: decode_json failed, invalid json. error:$@\n" ;
} else {
if ( ref ( $ decoded_data ) eq 'ARRAY' ) {
2020-04-21 13:09:51 +00:00
for my $ item ( @$ decoded_data ) {
2019-11-23 10:49:06 +00:00
push @ devices , $ item ;
}
# default case
return @ devices ;
} elsif ( ref ( $ decoded_data ) eq 'HASH' ) {
2019-12-04 09:31:40 +00:00
# error, api response is a hash in case of error
2019-11-23 10:49:06 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetMobileDevices: " . $ decoded_data - > { 'errors' } - > [ 0 ] - > { 'code' } if ( exists ( $ decoded_data - > { 'errors' } ) ) ;
}
}
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_GetZoneCount (@) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ homeID = $ attr { $ name } { homeID } ;
my $ URL = $ QueryURL . qq{ /$homeID/zones } ;
my $ zonecount = 0 ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
my $ param = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 2 ,
hash = > $ hash
} ;
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
my ( $ err , $ data ) = HttpUtils_BlockingGet ( $ param ) ;
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetZoneCount: Error while requesting " . $ param - > { url } . " - $err" ;
}
elsif ( $ data ne "" )
{
my @ devices = ( ) ;
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
my $ decoded_data = eval { decode_json ( $ data ) } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetZoneCount: decode_json failed, invalid json. error:$@\n" ;
} else {
if ( ref ( $ decoded_data ) eq 'ARRAY' ) {
2020-04-21 13:09:51 +00:00
for my $ item ( @$ decoded_data ) {
2019-11-23 10:49:06 +00:00
$ zonecount + + ;
}
return $ zonecount ;
} elsif ( ref ( $ decoded_data ) eq 'HASH' ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetZoneCount: " . $ decoded_data - > { 'errors' } - > [ 0 ] - > { 'code' } if ( exists ( $ decoded_data - > { 'errors' } ) ) ;
}
}
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_GetZoneNameById (@) {
my ( $ hash , $ zoneID ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ zoneName = undef ;
2019-12-11 10:12:46 +00:00
my @ zones = TadoAPI_GetTadoDevices ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-21 13:09:51 +00:00
for my $ zone ( @ zones ) {
2019-11-23 10:49:06 +00:00
if ( $ zone - > { 'id' } == $ zoneID ) {
$ zoneName = TadoAPI_ReplaceUmlaute ( $ zone - > { 'name' } ) ;
return $ zoneName ;
}
}
2019-11-23 12:22:13 +00:00
Log3 $ name , 3 , "TadoAPI $name" . ": " . "Error GetZoneNameById: Wrong zone ID ($zoneID)" ;
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
2020-04-19 22:36:32 +00:00
sub TadoAPI_GetZoneReadingsById (@) {
2019-11-23 10:49:06 +00:00
my ( $ hash , $ zoneID ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2020-04-19 22:36:32 +00:00
my $ homeID = $ attr { $ name } { homeID } ;
2019-11-23 10:49:06 +00:00
my $ URL = $ QueryURL . qq{ /$homeID/zones/$zoneID/state } ;
my $ temperature = 0 ;
my $ humidity = 0 ;
my $ desiredTemp = 0 ;
my $ currentHeatingPower = 0 ;
my $ overlay = 0 ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
my $ param = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
2019-12-10 15:20:57 +00:00
timeout = > 4 ,
2019-11-23 10:49:06 +00:00
hash = > $ hash
} ;
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
my ( $ err , $ data ) = HttpUtils_BlockingGet ( $ param ) ;
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetZoneReadingsById: Error while requesting " . $ param - > { url } . " - $err" ;
}
elsif ( $ data ne "" )
{
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
my $ decoded_data = eval { decode_json ( $ data ) } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "GetZoneReadingsById: Zone $zoneID decode_json failed, invalid json. error:$@\n" ;
} else {
2019-12-10 16:41:08 +00:00
$ temperature = sprintf ( "%.1f" , $ decoded_data - > { 'sensorDataPoints' } - > { 'insideTemperature' } - > { 'celsius' } ) ;
$ humidity = $ decoded_data - > { 'sensorDataPoints' } - > { 'humidity' } - > { 'percentage' } ;
if ( $ decoded_data - > { 'setting' } - > { 'power' } eq "OFF" ) {
$ desiredTemp = "OFF" ;
} else {
$ desiredTemp = $ decoded_data - > { 'setting' } - > { 'temperature' } - > { 'celsius' } ;
}
$ currentHeatingPower = $ decoded_data - > { 'activityDataPoints' } - > { 'heatingPower' } - > { 'percentage' } ;
$ overlay = $ decoded_data - > { 'overlayType' } ;
if ( ! defined $ overlay ) { $ overlay = "no overlay" } ;
2020-04-19 22:36:32 +00:00
2019-12-11 10:12:46 +00:00
return ( $ temperature , $ humidity , $ desiredTemp , $ currentHeatingPower , $ overlay ) ;
2019-11-23 10:49:06 +00:00
}
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
sub TadoAPI_GetTadoDevices (@) {
# returns array with zonenames and zone devices
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ homeID = $ attr { $ name } { homeID } ;
my $ URL = $ QueryURL . qq{ /$homeID/zones } ;
2020-04-19 21:44:16 +00:00
my $ CurrentTokenData = TadoAPI_LoadToken ( $ hash ) ;
2019-11-23 10:49:06 +00:00
2020-04-19 21:44:16 +00:00
if ( defined ( $ CurrentTokenData ) ) {
2019-11-23 10:49:06 +00:00
my $ param = {
url = > $ URL ,
2020-04-19 21:44:16 +00:00
header = > { "Content-Type" = > "application/json;charset=UTF-8" , "Authorization" = > "$CurrentTokenData->{'token_type'} $CurrentTokenData->{'access_token'}" } ,
2019-11-23 10:49:06 +00:00
method = > 'GET' ,
timeout = > 5 ,
hash = > $ hash
} ;
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-12-11 10:12:46 +00:00
Log3 $ name , $ reqDebug , "TadoAPI $name" . ": " . "Request $URL" ;
2019-11-23 10:49:06 +00:00
my ( $ err , $ data ) = HttpUtils_BlockingGet ( $ param ) ;
2020-04-19 22:36:32 +00:00
2019-11-23 10:49:06 +00:00
if ( $ err ne "" )
{
Log3 $ name , 3 , "TadoAPI $name" . ": " . "RequestTadoDevices: Error while requesting " . $ param - > { url } . " - $err" ;
}
elsif ( $ data ne "" )
{
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $data" ;
my $ decoded_data = eval { decode_json ( $ data ) } ;
if ( $@ ) {
Log3 $ name , 3 , "TadoAPI $name" . ": " . "RequestTadoDevices: decode_json failed, invalid json. error:$@\n" ;
} else {
if ( ref ( $ decoded_data ) eq 'ARRAY' ) {
my @ devices = ( ) ;
2020-04-21 13:09:51 +00:00
for my $ dev ( @$ decoded_data ) {
2019-11-23 10:49:06 +00:00
push @ devices , $ dev ;
}
return @ devices ;
}
}
}
}
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
######################################################
# storePW & readPW Code geklaut aus 96_SIP.pm :)
######################################################
sub TadoAPI_storePassword ($$)
{
my ( $ name , $ password ) = @ _ ;
my $ index = "TadoAPI_" . $ name . "_passwd" ;
my $ key = getUniqueId ( ) . $ index ;
my $ e_pwd = "" ;
2020-04-19 22:36:32 +00:00
2020-04-19 19:46:53 +00:00
if ( eval { use Digest::MD5 ; 1 } )
2019-11-23 10:49:06 +00:00
{
$ key = Digest::MD5:: md5_hex ( unpack "H*" , $ key ) ;
$ key . = Digest::MD5:: md5_hex ( $ key ) ;
}
2020-04-19 22:36:32 +00:00
2019-11-23 10:49:06 +00:00
for my $ char ( split // , $ password )
{
my $ encode = chop ( $ key ) ;
$ e_pwd . = sprintf ( "%.2x" , ord ( $ char ) ^ ord ( $ encode ) ) ;
$ key = $ encode . $ key ;
}
my $ error = setKeyValue ( $ index , $ e_pwd ) ;
return "error while saving TadoAPI password : $error" if ( defined ( $ error ) ) ;
return "TadoAPI password successfully saved in FhemUtils/uniqueID Key $index" ;
}
sub TadoAPI_readPassword ($)
{
my ( $ name ) = @ _ ;
my $ index = "TadoAPI_" . $ name . "_passwd" ;
my $ key = getUniqueId ( ) . $ index ;
my ( $ password , $ error ) ;
#Log3 $name,5,"$name, read user password from FhemUtils/uniqueID Key $key";
( $ error , $ password ) = getKeyValue ( $ index ) ;
2020-04-19 22:36:32 +00:00
if ( defined ( $ error ) )
2019-11-23 10:49:06 +00:00
{
Log3 $ name , 3 , "$name, cant't read Tado password from FhemUtils/uniqueID: $error" ;
2020-04-19 22:36:32 +00:00
}
if ( defined ( $ password ) )
2019-11-23 10:49:06 +00:00
{
2020-04-19 22:36:32 +00:00
if ( eval { use Digest::MD5 ; 1 } )
2019-11-23 10:49:06 +00:00
{
$ key = Digest::MD5:: md5_hex ( unpack "H*" , $ key ) ;
$ key . = Digest::MD5:: md5_hex ( $ key ) ;
}
my $ dec_pwd = '' ;
2020-04-19 22:36:32 +00:00
for my $ char ( map { pack ( 'C' , hex ( $ _ ) ) } ( $ password =~ /(..)/g ) )
2019-11-23 10:49:06 +00:00
{
my $ decode = chop ( $ key ) ;
$ dec_pwd . = chr ( ord ( $ char ) ^ ord ( $ decode ) ) ;
$ key = $ decode . $ key ;
}
return $ dec_pwd ;
}
2020-04-19 22:36:32 +00:00
else
2019-11-23 10:49:06 +00:00
{
Log3 $ name , 3 , "$name, no Tado password found in FhemUtils/uniqueID" ;
2020-04-19 20:13:20 +00:00
return ;
2019-11-23 10:49:06 +00:00
}
}
1 ;
= pod
2019-12-14 15:29:46 +00:00
= item device
2019-11-23 10:49:06 +00:00
= item summary integration of the Tado API
= item summary_DE Anbindung der Tado Heizungssteuerung & uuml ; ber API
= begin html
< a name = "TadoAPI" > </a>
<h3> TadoAPI </h3>
<ul>
2019-12-14 15:29:46 +00:00
The TadoAPI module connects your tado devices to FHEM . Most zone readings are shown and desired temperature for a zone can be set . <br>
TadoAPI makes use of the ( unofficial ) tado api and does NOT rely on any addition local client installed . <br>
2019-11-23 10:49:06 +00:00
Notes:
<ul>
2019-12-14 15:29:46 +00:00
<li> JSON has to be installed on the FHEM host . <br>
Please install the module ( e . g . with <code> sudo apt - get install libjson - perl </code> ) or the correct method for the underlying platform /system.</ li >
2019-11-23 10:49:06 +00:00
</ul>
< a name = "TadoAPIdefine" > </a>
<b> Define </b>
<ul>
2019-11-23 11:47:42 +00:00
The username and password must match the username and password used on the Tado website . <br>
After successful define , store PASSWORD with <code> set & lt ; name & gt ; password & lt ; your - tado - password & gt ; </code> . <br>
Note: Password is encrypted and saved in FHEM uniqueID file . All requests to the API are handeld via oauth2 token . <br>
2019-11-23 10:49:06 +00:00
Examples:
<ul> <code>
define & lt ; name & gt ; TadoAPI mail @ example . com [ & lt ; homeID & gt ; ] <br>
</code> </ul>
<br>
</ul>
< a name = "TadoAPIset" > </a>
<b> Set </b>
<ul>
<li>
<code> set & lt ; name & gt ; & lt ; tado password & gt ; </code> <br>
Stores <code> password </code> from tado account encrypted in FHEM . <br>
Without stored password all functions are blocked ! <br>
IMPORTANT : if you rename the fhem Device you must set the password again !
</li>
<li>
<code> set & lt ; name & gt ; update </code> <br>
Reloads all information from the tado installation ( devices , battery state , geolocation , ... ) .
</li>
<li>
2019-11-25 21:02:47 +00:00
<code> set & lt ; name & gt ; setZoneOverlay & lt ; zoneID & gt ; & lt ; setting & gt ; [ & lt ; duration & gt ; ] </code> <br>
2019-12-27 08:56:19 +00:00
Setting: remove = delete overlay ; 0 = heating power off ; & gt ; 1 sets desired temperature to given value ( overlay )
2019-11-23 10:49:06 +00:00
</li>
<li>
<code> set & lt ; name & gt ; setAllOverlays & lt ; setting & gt ; </code> <br>
Same as above , but for all zones <br>
2019-12-27 08:56:19 +00:00
Setting: remove = delete overlay ; 0 = heating power off ; & gt ; 1 sets desired temperature to given value ( overlay ) </li>
2019-11-23 10:49:06 +00:00
<br>
</ul>
<br>
< a name = "TadoAPIattr" > </a>
<b> Attributes </b>
<ul>
<li> homeID <br>
Home ID that will be used for <b> API </b> querys .
</li>
2019-11-25 21:02:47 +00:00
<li> updateIntervall <br>
2019-12-11 12:37:41 +00:00
Intervall ( in seconds ) that is used for polling the <b> tado API </b> to update readings .
</li>
<li> showPosData <br>
If set to <b> 1 </b> readings with relative distance to tado home are shown .
2019-11-25 21:02:47 +00:00
</li>
2019-11-23 10:49:06 +00:00
</ul>
<br>
</ul>
= end html
# Ende der Commandref
2020-04-19 19:46:53 +00:00
= cut