FHEM-Tado/98_TadoAPI.pm

1399 lines
44 KiB
Perl
Raw Normal View History

2019-10-17 10:09:00 +00:00
#!/usr/bin/env perl
#===============================================================================
#
# FILE: 98_TadoAPI.pm
2019-10-17 10:09:00 +00:00
#
# USAGE: Module for FHEM
# Info: Turn verbose on for debugging
2019-10-17 10:09:00 +00:00
#
# REQUIREMENTS: Below modules should be pre-installed.
# HTTP::Request::Common
# HTTP::Headers
# Data::Dumper;
2019-10-17 10:09:00 +00:00
# JSON
#
2019-10-17 10:09:00 +00:00
# BUGS: ---
# NOTES: ---
# AUTHOR: Philipp Wolfmajer
# ORGANIZATION:
# VERSION: 1.0
# CREATED: 04/12/2019 07:55:44 PM
# REVISION: 11/21/2019 05:17:22 PM
2019-10-17 10:09:00 +00:00
#===============================================================================
package main;
use strict;
use warnings;
use utf8;
use FHEM::Meta;
use HTTP::Request::Common qw (POST GET PUT);
use HTTP::Headers;
use JSON;
####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";
2019-10-17 10:09:00 +00:00
my $header = {};
my $data = {};
my $TokenData = {};
2019-11-20 05:13:26 +00:00
# helpers
my $apiStatus = 1;
2019-10-17 10:09:00 +00:00
my %sets = (
"getTemperature" => "",
2019-10-17 10:09:00 +00:00
"refreshToken" => "noArg",
"password" => "",
"update" => "noArg",
"setGeo" => "",
"setZoneOverlay" => "",
"setAllOverlays" => ""
2019-10-17 10:09:00 +00:00
);
my %gets = (
"getZoneDevices" => "noArg",
"getZoneInfo" => "noArg",
"getGeo" => "",
"getXTest" => "",
"getMobileDevices" => "noArg"
2019-10-17 10:09:00 +00:00
);
sub
TadoAPI_Initialize($)
{
my ($hash) = @_;
$hash->{DefFn} = "TadoAPI_Define";
$hash->{InitFn} = "TadoAPI_Init";
$hash->{SetFn} = "TadoAPI_Set";
$hash->{GetFn} = "TadoAPI_Get";
$hash->{AttrList} =
"homeID " .
"mobileID " .
$main::readingFnAttributes;
}
sub TadoAPI_Init($$)
{
my ($hash,$args) = @_;
my $u = "wrong syntax: define <name> TadoAPI <username> <homeID> [<mobileID>]";
2019-10-23 12:52:51 +00:00
return $u if(int(@$args) < 2);
2019-10-17 10:09:00 +00:00
return undef;
}
sub TadoAPI_Define($$)
{
my ($hash, $def) = @_;
my @a = split( "[ \t]+", $def );
my $name = shift @a;
my $type = shift @a;
my $tokenFileName = $tokenFile."_".$name;
2019-10-17 10:09:00 +00:00
return "Invalid number of arguments: "
2019-11-20 05:13:26 +00:00
. "define <name> TadoAPI <username> [<homeID>]"
if ( int(@a) < 1 );
2019-10-17 10:09:00 +00:00
2019-11-20 05:13:26 +00:00
my ( $user, $homeID ) = @a;
2019-10-17 10:09:00 +00:00
Log3 $name, 3, "TadoAPI_Define $name: called ";
$hash->{STATE}="defined";
# Initialize the device
return $@ unless ( FHEM::Meta::SetInternals($hash) );
2019-10-17 10:09:00 +00:00
$hash->{TADO_USER} = $user;
$hash->{TOKEN_FILE} = $tokenFileName;
2019-10-17 10:09:00 +00:00
2019-11-20 05:13:26 +00:00
my @args = ($homeID);
2019-10-17 10:09:00 +00:00
if ($main::init_done) {
2019-10-23 12:52:51 +00:00
# do something?
2019-10-17 10:09:00 +00:00
return TadoAPI_Catch($@) if $@;
}
my $password = TadoAPI_readPassword($name);
2019-11-20 05:13:26 +00:00
if (defined($password)){
TadoAPI_LoadToken($hash);
# start the status update timer
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + 10, "TadoAPI_Update", $hash, 0 );
if ( defined($homeID) && $homeID ne "" ) {
$attr{$name}{homeID} = $homeID;
} else{
my $id = TadoAPI_GetHomeId($hash);
if ( defined($id) && $id ne "" ) {
$attr{$name}{homeID} = $id;
}
2019-11-20 05:13:26 +00:00
}
}
return undef;
2019-10-17 10:09:00 +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;
2019-10-17 10:09:00 +00:00
if(!defined($sets{$cmd})) {
my @cmds = ();
foreach my $key (sort keys %sets) {
push @cmds, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
}
return "Unknown argument $a[1], choose one of " . join(" ", @cmds);
}
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);
2019-10-17 10:09:00 +00:00
} else {
Log3 $name, 3, "TadoAPI: set $name: processing ($cmd)";
TadoAPI_SetGeoById($hash, $value, 0);
2019-10-17 10:09:00 +00:00
}
TadoAPI_GetGeoById($hash, $value);
2019-10-17 10:09:00 +00:00
} elsif( $cmd eq 'setZoneOverlay' ) {
Log3 $name, 5, "TadoAPI $name" . ": " . "processing ($cmd)";
2019-11-22 08:44:00 +00:00
return "Need at least two parameters (ZoneID, Setting) - Setting: off=delete overlay; 0=heating power off; 1<=desired temperature (overlay)" if(@a < 4);
if( $a[3] eq "off" ) {
TadoAPI_SetZoneOverlayById($hash, $value, "off");
2019-11-22 08:44:00 +00:00
} elsif ($a[3] >= 0) {
TadoAPI_SetZoneOverlayById($hash, $value, $a[3]);
}
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
} elsif( $cmd eq 'setAllOverlays' ) {
Log3 $name, 5, "TadoAPI $name" . ": " . "processing ($cmd)";
2019-11-22 08:44:00 +00:00
return "Need at least one parameter (Setting) - Setting: off=delete overlay; 0=heating power off; 1<=desired temperature (overlay)" if(@a < 3);
if( $value eq "off" ) {
TadoAPI_SetAllOverlays($hash, "off");
2019-11-22 08:44:00 +00:00
} elsif ($value >= 0) {
TadoAPI_SetAllOverlays($hash, $value);
}
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
2019-10-17 10:09:00 +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";
2019-10-17 10:09:00 +00:00
} elsif( $cmd eq 'update' ) {
Log3 $name, 3, "TadoAPI: set $name: processing ($cmd)";
TadoAPI_UpdateFn($hash);
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
2019-10-17 10:09:00 +00:00
} elsif( $cmd eq 'getTemperature' ) {
Log3 $name, 5, "TadoAPI $name" . ": " . "processing ($cmd)";
return "ZoneID as parameter needed" if (!$value);
if( $value >= 1 ) {
TadoAPI_GetZoneReadingsById($hash, $value);
} else {
return "Wrong ZoneID";
}
2019-10-17 10:09:00 +00:00
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
$subcmd = join(" ",@a);
$message = TadoAPI_storePassword($name,$subcmd);
# start the status update timer
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + 10, "TadoAPI_Update", $hash, 0 );
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
2019-10-17 10:09:00 +00:00
}
return $message if $message;
2019-10-17 10:09:00 +00:00
return TadoAPI_Catch($@) if $@;
}
sub TadoAPI_Get(@) {
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 $homeID = $attr{$name}{homeID};
my $message = undef;
2019-10-17 10:09:00 +00:00
if(!defined($gets{$cmd})) {
my @cmds = ();
foreach my $key (sort keys %gets) {
push @cmds, $gets{$key} ? $key.":".join(",",$gets{$key}) : $key;
}
return "Unknown argument $a[1], choose one of " . join(" ", @cmds);
}
if($cmd =~ /\Qget\E/) {
2019-10-17 10:09:00 +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";
foreach my $item ( @data ){
print "\n";
$message .= $item->{'name'} . ": " . $item->{'id'} . "\n";
};
2019-10-17 10:09:00 +00:00
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
last;
};
$cmd eq "getXTest" and do {
Log3 $name, 5, "TadoAPI $name" . ": " . "processing ($cmd)";
$message = "Name: " . TadoAPI_GetZoneNameById($hash, $value);
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
last;
};
$cmd eq "getZoneDevices" and do {
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)";
2019-11-22 20:57:40 +00:00
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'};
2019-11-22 14:21:01 +00:00
$message .= "ZoneID: " . ($devArr[$i]->{'id'});
my $spacer = 0;
foreach my $item ( @$tadodevices ){
2019-11-22 15:35:48 +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} . ")." ;
2019-10-17 10:09:00 +00:00
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";
2019-11-22 20:57:40 +00:00
my @devArr = TadoAPI_GetTadoDevices($hash);
for (my $i=0; $i < @devArr; $i++){
my $zoneid = $devArr[$i]->{'id'};
2019-11-22 14:21:01 +00:00
$message .= "Zone ID $zoneid: " . TadoAPI_GetZoneNameById($hash, $zoneid) . "\n";
}
$message .= "See Logfile for more Info";
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
last;
};
2019-10-17 10:09:00 +00:00
}
return $message if $message;
2019-10-17 10:09:00 +00:00
return TadoAPI_Catch($@) if $@;
return undef;
}
}
sub TadoAPI_Catch($) {
my $exception = shift;
if ($exception) {
$exception =~ /^(.*)( at.*FHEM.*)$/;
return $1;
}
return undef;
}
sub TadoAPI_Undefine($$) {
my ( $hash, $name ) = @_;
RemoveInternalTimer($hash);
#todo remove tokenfile
return undef;
}
sub TadoAPI_OnlineStatus(@){
2019-10-17 10:09:00 +00:00
my ($hash) = @_;
my $name = $hash->{NAME};
# test api status
my $param = {
url => $AuthURL,
timeout => 5,
hash => $hash,
method => "GET",
header => "",
callback => \&TadoAPI_callback
};
#test if api is reachable
HttpUtils_NonblockingGet($param);
return undef;
}
sub TadoAPI_LoadToken(@){
2019-10-17 10:09:00 +00:00
my ($hash) = @_;
my $name = $hash->{NAME};
2019-10-17 10:09:00 +00:00
my $tokenFileName = $tokenFile."_".$name;
my $tokenLifeTime = $hash->{TOKEN_LIFETIME};
$tokenLifeTime = 0 if(!defined $tokenLifeTime || $tokenLifeTime eq '');
my $Token = undef;
2019-10-17 10:09:00 +00:00
TadoAPI_OnlineStatus($hash);
2019-10-17 10:09:00 +00:00
if($apiStatus){
eval {
2019-10-17 10:09:00 +00:00
open(TOKENFILE, '<', $tokenFileName) or die("ERROR: $!");
$Token = decode_json(<TOKENFILE>)};
if($@ || $tokenLifeTime < gettimeofday()){
Log3 $name, 5, "TadoAPI $name" . ": " . "Error while loading: $@ ,requesting new one";
Log3 $name, 5, "TadoAPI $name" . ": " . "Token is expired, requesting new one" if $tokenLifeTime < gettimeofday();
$Token = TadoAPI_NewTokenRequest($hash);
}else{
Log3 $name, 5, "TadoAPI $name" . ": " . "Token expires at " . localtime($tokenLifeTime);
# if token is about to expire, refresh him
2019-11-22 08:44:00 +00:00
if(($tokenLifeTime-45) < gettimeofday()){
Log3 $name, 5, "TadoAPI $name" . ": " . "Token will expire soon, refreshing";
$Token = TadoAPI_TokenRefresh($hash);
2019-10-17 10:09:00 +00:00
}
}
close(TOKENFILE);
return $Token if $Token;
}
return undef;
2019-10-17 10:09:00 +00:00
}
sub TadoAPI_NewTokenRequest(@) {
2019-11-22 08:44:00 +00:00
my ($hash) = @_;
my $name = $hash->{NAME};
my $username = $hash->{TADO_USER};
my $password = TadoAPI_readPassword($name);
my $tokenFileName = $tokenFile."_".$name;
2019-10-17 10:09:00 +00:00
2019-11-22 08:44:00 +00:00
Log3 $name, 5, "TadoAPI $name" . ": " . "calling NewTokenRequest()";
2019-11-22 08:44:00 +00:00
$data = {
client_id => $client_id,
client_secret => $client_secret,
username => $username,
password => $password,
scope => $scope,
grant_type=>'password'
};
2019-11-22 08:44:00 +00:00
my $param = {
url => $AuthURL,
method => 'POST',
timeout => 5,
hash => $hash,
data => $data
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-11-22 08:44:00 +00:00
my ($err, $data) = HttpUtils_BlockingGet($param);
if($err ne "")
{
Log3 $name, 3, "TadoAPI $name" . ": " . "NewTokenRequest: 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" . ": " . "NewTokenRequest: decode_json failed, invalid json. error: $@ ";
}else{
#write token data in file
open(TOKENFILE,">$tokenFileName") or die("ERROR: $!");
print TOKENFILE $data . "\n";
close(TOKENFILE);
# 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;
}
2019-11-22 08:44:00 +00:00
}
}
2019-10-17 10:09:00 +00:00
sub TadoAPI_TokenRefresh(@) {
2019-11-22 08:44:00 +00:00
my ($hash) = @_;
my $name = $hash->{NAME};
my $tokenFileName = $tokenFile."_".$name;
2019-10-17 10:09:00 +00:00
2019-11-22 08:44:00 +00:00
# load token
eval {
open(TOKENFILE, '<', $hash->{TOKEN_FILE}) or die("ERROR: $!");
$TokenData = decode_json(<TOKENFILE>)};
2019-11-22 08:44:00 +00:00
$data = {
client_id => $client_id,
client_secret => $client_secret,
scope => $scope,
grant_type=>'refresh_token',
refresh_token => $TokenData->{'refresh_token'}
};
2019-11-22 08:44:00 +00:00
my $param = {
url => $AuthURL,
method => 'POST',
timeout => 5,
hash => $hash,
data => $data
};
#Log3 $name, 5, 'Blocking GET TokenRefresh: ' . Dumper($param);
2019-11-22 08:44:00 +00:00
my ($err, $data) = HttpUtils_BlockingGet($param);
if($err ne "")
{
Log3 $name, 3, "TadoAPI $name" . ": " . "TokenRefresh: Error in token retrival while requesting ".$param->{url}." - $err";
$hash->{STATE}="error";
}
elsif($data ne "")
{
Log3 $name, 5, "url ".$param->{url}." returned: $data";
my $decoded_data = eval{decode_json($data);};
if ($@){
Log3 $name, 3, "TadoAPI $name" . ": " . "TokenRefresh: decode_json failed, invalid json. error:$@\n" if $@;
$hash->{STATE}="error";
}else{
#write token data in file
open(TOKENFILE,">$tokenFileName") or die("ERROR: $!");
print TOKENFILE $data . "\n";
close(TOKENFILE);
# token lifetime management
$hash->{TOKEN_LIFETIME} = gettimeofday() + $decoded_data->{'expires_in'};
$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;
2019-11-22 08:44:00 +00:00
}
}
}
2019-10-17 10:09:00 +00:00
sub TadoAPI_Update(@){
my ($hash) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "TadoAPI $name" . ": " . "TadoAPI_Update called";
2019-10-17 10:09:00 +00:00
# timer loop
#
my $nextTimer = "none";
# if api online, try again in 5 minutes
if ( $apiStatus ) {
$nextTimer = gettimeofday() + 300;
}
RemoveInternalTimer($hash);
InternalTimer( $nextTimer, "TadoAPI_Update", $hash, 0 );
# update subs
TadoAPI_UpdateFn($hash);
2019-10-17 10:09:00 +00:00
return undef;
}
######################## tado methods ########################
##############################################################
sub TadoAPI_SetZoneOverlayById(@){
my ($hash, $zoneID, $setting) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
my $URL = $QueryURL . qq{/$homeID/zones/$zoneID/overlay};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData)){
my $method = "";
my $myjson ="";
Log3 $name, 5, "TadoAPI $name" . ": SetOverlay for Zone $zoneID (Setting: " . $setting . ") - " . "query-URL: $URL";
if ($setting eq "off"){
$method = "DELETE";
Log3 $name, 3, "TadoAPI $name" . ": " . "Deleting Overlay for Zone $zoneID";
2019-11-22 08:44:00 +00:00
}elsif($setting == 0){
$method = "PUT";
$myjson = {
setting => {
type => "HEATING",
power => "OFF",
},
termination => {
type => "MANUAL"
},
};
$myjson = encode_json($myjson);
}elsif($setting > 0){
$method = "PUT";
$myjson = {
setting => {
type => "HEATING",
power => "ON",
temperature => {
celsius => $setting
},
},
termination => {
type => "MANUAL"
},
};
$myjson = encode_json($myjson);
}
my $request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => $method,
2019-11-22 08:44:00 +00:00
timeout => 5,
callback => \&Tado_UpdateZoneOverlayCallback,
hash => $hash,
setting => $setting,
zoneID => $zoneID,
data => $myjson
};
#Log3 $name, 5, 'NonBlocking Request: ' . Dumper($request);
HttpUtils_NonblockingGet($request);
}
}
sub TadoAPI_SetAllOverlays(@){
my ($hash, $setting) = @_;
my $name = $hash->{NAME};
2019-11-19 04:05:44 +00:00
my $homeID = $attr{$name}{homeID};
my $TokenData = TadoAPI_LoadToken($hash);
2019-11-19 04:05:44 +00:00
if(defined($TokenData)){
my $message = "";
my $method = "";
my $myjson ="";
2019-11-22 20:57:40 +00:00
my @devArr = TadoAPI_GetTadoDevices($hash);
2019-11-22 15:35:48 +00:00
2019-11-22 20:57:40 +00:00
for (my $i=0; $i < @devArr; $i++) {
my $zoneid = $devArr[$i]->{'id'};
2019-11-22 15:35:48 +00:00
TadoAPI_SetZoneOverlayById($hash, $zoneid, $setting);
}
}
}
sub TadoAPI_UpdateFn(@){
my ($hash) = @_;
my $name = $hash->{NAME};
my $TokenData = TadoAPI_LoadToken($hash);
my $homeID = $attr{$name}{homeID};
if($apiStatus == 1 && defined($TokenData)){
# zone specific updates
my $URL = $QueryURL.qq{/$homeID/zones};
my $request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
2019-11-22 19:23:01 +00:00
timeout => 25,
2019-11-22 08:44:00 +00:00
incrementalTimout => 1,
hash => $hash,
2019-11-22 19:23:01 +00:00
callback => \&TadoAPI_UpdateAllZoneReadingsCallback
};
HttpUtils_NonblockingGet($request);
# mobile devices
$URL=$QueryURL . qq{/$homeID/mobileDevices};
$request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
2019-11-22 19:23:01 +00:00
timeout => 5,
2019-11-22 08:44:00 +00:00
incrementalTimout => 1,
callback => \&TadoAPI_UpdateMobileReadingsCallback,
hash => $hash
};
HttpUtils_NonblockingGet($request);
}
2019-10-17 10:09:00 +00:00
}
2019-11-22 14:21:01 +00:00
########################################################################################################################################################################
# Callback Subs
2019-11-22 14:21:01 +00:00
########################################################################################################################################################################
sub TadoAPI_callback($){
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
$param->{code} = 0 unless defined $param->{code};
if($param->{code} == 401 || $param->{code} == 400){
$apiStatus = 1;
$hash->{STATE}="reachable";
Log3 $name, 5, "TadoAPI $name" . ": " . "API is reachable. Callback Status: " . $param->{code};
}else{
$apiStatus = 0;
$hash->{STATE}="error";
Log3 $name, 5, "TadoAPI $name" . ": " . "API error: apiStatus $apiStatus ($err)";
}
return undef;
}
2019-10-17 10:09:00 +00:00
2019-11-22 19:23:01 +00:00
sub TadoAPI_UpdateAllZoneReadingsCallback($){
2019-11-22 14:21:01 +00:00
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if($err ne "")
{
Log3 $name, 3, "Error in TadoAPI_UpdateZoneCallback while requesting ".$param->{url}." - $err";
return undef;
}
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{
foreach my $zone ( @$decoded_data ){
my $zoneid = $zone->{'id'};
Log3 $name, 5, "TadoAPI $name" . ": " . "Set Reading Update for Zone $zoneid ";
my ($temperature, $humidity, $desiredTemp, $currentHeatingPower, $overlay ) = TadoAPI_GetZoneReadingsById($hash, $zoneid);
my $zoneName = TadoAPI_GetZoneNameById($hash, $zoneid);
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 );
}
2019-11-22 19:23:01 +00:00
# iterate through all devices in zone
my $devices = $zone->{'devices'};
foreach my $device ( @$devices ){
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "Battery_" . $device->{'serialNo'}, $device->{'batteryState'});
readingsEndUpdate( $hash, 1 );
$hash->{LastRequest}="OK";
}
2019-11-22 14:21:01 +00:00
}
}
my $zonecount = TadoAPI_GetZoneCount($hash);
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "ActiveZones", $zonecount);
readingsEndUpdate( $hash, 1 );
}
}
sub Tado_UpdateZoneOverlayCallback($)
{
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $zoneID = $param->{zoneID};
my $setting = $param->{setting};
if($err ne "")
{
Log3 $name, 3, "Error in UpdateZoneOverlayCallback while requesting ".$param->{url}." - $err";
return undef;
}
elsif($data ne "")
{
Log3 $name, 5, "url ".$param->{url}." returned: $data";
Log3 $name, 3, "TadoAPI $name" . ": " . "Set (async) Overlay for Zone $zoneID to: $setting";
2019-10-17 10:09:00 +00:00
}
# finaly update readings
TadoAPI_GetZoneReadingsById($hash, $zoneID);
}
sub TadoAPI_LogInfoCallback($){
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if($err ne "")
{
Log3 $name, 3, "Error in LogInfoCallback while requesting ".$param->{url}." - $err";
return undef;
}
elsif($data ne "")
{
Log3 $name, 3, "TadoAPI $name" . ": " . $param->{infotext} .":\n" . $data . "\n";
}
}
sub TadoAPI_UpdateTadoDeviceInformationCallback($){
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if($err ne "")
{
Log3 $name, 3, "Error in UpdateTadoDeviceInformationCallback while requesting ".$param->{url}." - $err";
}
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" . ": " . "UpdateTadoDeviceInformationCallback: decode_json failed, invalid json. error:$@\n" if $@;
Log3 $name, 3, "TadoAPI $name" . ": " . "UpdateTadoDeviceInformationCallback: Error in decoded data, Code: " . $decoded_data->{'errors'}->[0]->{'code'};
$hash->{LastRequest}="error";
2019-10-17 10:09:00 +00:00
}else{
foreach my $zone ( @$decoded_data ){
my $devices = $zone->{'devices'};
foreach my $device ( @$devices ){
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "Battery_" . $device->{'serialNo'}, $device->{'batteryState'});
readingsEndUpdate( $hash, 1 );
$hash->{LastRequest}="OK";
}
}
}
}
}
2019-11-22 14:21:01 +00:00
sub TadoAPI_SetGeoByIdCallback($){
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if($err ne "")
{
Log3 $name, 3, "Error in TadoAPI_SetGeoByIdCallback while requesting ".$param->{url}." - $err";
}
elsif($data ne "")
{
Log3 $name, 3, "url ".$param->{url}." returned: $data";
}
}
sub TadoAPI_UpdateMobileReadingsCallback($){
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if($err ne "")
{
Log3 $name, 3, "Error in UpdateMobileReadingsCallback while requesting ".$param->{url}." - $err";
}
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{
foreach my $item ( @$decoded_data ){
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "Geolocation_" . $item->{'id'}, $item->{'settings'}->{'geoTrackingEnabled'});
readingsEndUpdate( $hash, 1 );
$hash->{LastRequest}="OK";
}
2019-10-17 10:09:00 +00:00
}
}
}
sub TadoAPI_GetZoneInfo(@) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData)){
# HomeInfo
my $URL = qq{https://my.tado.com/api/v2/me};
my $request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
2019-11-22 20:57:40 +00:00
timeout => 8,
infotext => "HomeInfos",
hash => $hash,
callback => \&TadoAPI_LogInfoCallback
};
HttpUtils_NonblockingGet($request);
# TadoDevicesInfo
2019-11-22 20:57:40 +00:00
$URL = $QueryURL . qq{/$homeID/zones};
$request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
timeout => 3,
infotext => "Tado Devices Info",
hash => $hash,
callback => \&TadoAPI_LogInfoCallback
};
HttpUtils_NonblockingGet($request);
# Mobileinfo
$URL = $QueryURL . qq{/$homeID/mobileDevices};
$request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
timeout => 3,
infotext => "Mobile Devices Info",
hash => $hash,
callback => \&TadoAPI_LogInfoCallback
};
HttpUtils_NonblockingGet($request);
2019-11-22 20:02:03 +00:00
my @mobDev = TadoAPI_GetMobileDevices($hash);
for (my $i=0; $i < @mobDev; $i++) {
my $mobileID = $mobDev[$i]->{'id'};
2019-11-22 20:57:40 +00:00
$URL=$QueryURL . qq{/$homeID/mobileDevices/$mobileID/settings};
2019-11-22 20:02:03 +00:00
$request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
timeout => 3,
infotext => "Mobile Device $mobileID",
hash => $hash,
callback => \&TadoAPI_LogInfoCallback
};
2019-11-22 20:02:03 +00:00
HttpUtils_NonblockingGet($request);
}
# zones
2019-11-22 20:57:40 +00:00
my @devArr = TadoAPI_GetTadoDevices($hash);
for (my $i=0; $i < @devArr; $i++) {
my $zoneid = $devArr[$i]->{'id'};
my $URL=$QueryURL . qq{/$homeID/zones/$zoneid/state};
2019-11-22 20:02:03 +00:00
my $infotext = "ZoneID $zoneid (" . TadoAPI_GetZoneNameById($hash, $zoneid) . ") Status";
2019-11-22 15:35:48 +00:00
my $request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'GET',
timeout => 3,
infotext => $infotext,
hash => $hash,
callback => \&TadoAPI_LogInfoCallback
};
HttpUtils_NonblockingGet($request);
}
}
return undef;
}
sub TadoAPI_SetGeoById(@){
2019-11-22 20:02:03 +00:00
my ($hash, $mobileID, $geo) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
2019-11-22 20:57:40 +00:00
my $URL=$QueryURL . qq{/$homeID/mobileDevices/$mobileID/settings};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData)){
if($geo){
my $data = { geoTrackingEnabled=>"true" };
}else{
my $data = { geoTrackingEnabled=>"false" };
}
$data = encode_json($data);
my $request = {
url => $URL,
header => { "Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}" },
method => 'PUT',
timeout => 3,
2019-11-22 20:02:03 +00:00
mobileID => $mobileID,
data => $data,
hash => $hash,
callback => \&TadoAPI_SetGeoByIdCallback
};
HttpUtils_NonblockingGet($request);
}
}
#######################################################################################################
# API Requests
###############
######################################
############ 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;
}
2019-11-20 05:13:26 +00:00
sub TadoAPI_GetHomeId(@){
# returns first home id only
my ($hash) = @_;
my $name = $hash->{NAME};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData))
{
my $param = {
url => $DataURL,
header => {"Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}"},
method => 'GET',
timeout => 2,
hash => $hash,
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
2019-11-20 05:13:26 +00:00
my ($err, $data) = HttpUtils_BlockingGet($param);
if($err ne "")
{
Log3 $name, 3, "TadoAPI $name" . ": " . "GetHomeId: Error while requesting ".$param->{url}." - $err";
}
elsif($data ne "")
{
Log3 $name, 5, "url ".$param->{url}." returned: $data";
2019-11-20 05:13:26 +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-20 05:13:26 +00:00
}
}
return undef;
}
2019-11-22 20:57:40 +00:00
sub TadoAPI_GetGeoById(@){
my ($hash, $mobileID) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
my $URL=$QueryURL.qq{/$homeID/mobileDevices/$mobileID/settings};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData)){
my $param = {
url => $URL,
header => {"Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}"},
method => 'GET',
timeout => 4,
hash => $hash,
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
my ($err, $data) = HttpUtils_BlockingGet($param);
if($err ne "")
{
Log3 $name, 3, "TadoAPI $name" . ": " . "TadoAPI_GetGeoById: 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" . ": " . "TadoAPI_GetGeoById: decode_json failed, invalid json. error:$@\n";
}else{
my $setting = $decoded_data->{'geoTrackingEnabled'};
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "Geolocation_" . $mobileID, $setting);
readingsEndUpdate( $hash, 1 );
return $setting;
}
}
}
return undef;
}
sub TadoAPI_GetMobileDevices(@) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
2019-11-22 20:57:40 +00:00
my $URL=$QueryURL . qq{/$homeID/mobileDevices};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData)){
my $param = {
url => $URL,
header => {"Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}"},
method => 'GET',
timeout => 2,
hash => $hash
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
my ($err, $data) = HttpUtils_BlockingGet($param);
if($err ne "")
{
2019-11-22 20:57:40 +00:00
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 ($@){
2019-11-22 20:57:40 +00:00
Log3 $name, 3, "TadoAPI $name" . ": " . "GetMobileDevices: decode_json failed, invalid json. error:$@\n";
}else{
2019-11-22 14:21:01 +00:00
if(ref($decoded_data) eq 'ARRAY'){
2019-11-22 20:57:40 +00:00
foreach my $item( @$decoded_data ) {
push @devices, $item;
2019-11-22 14:21:01 +00:00
}
2019-11-22 20:57:40 +00:00
# default case
return @devices;
2019-11-22 14:21:01 +00:00
}elsif(ref($decoded_data) eq 'HASH'){
2019-11-22 20:57:40 +00:00
Log3 $name, 3, "TadoAPI $name" . ": " . "GetMobileDevices: " . $decoded_data->{'errors'}->[0]->{'code'} if (exists($decoded_data->{'errors'}));
}
}
}
}
2019-11-22 20:57:40 +00:00
return undef;
}
2019-11-22 20:57:40 +00:00
sub TadoAPI_GetZoneCount(@) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
my $URL=$QueryURL.qq{/$homeID/zones};
2019-11-22 20:57:40 +00:00
my $zonecount = 0;
my $TokenData = TadoAPI_LoadToken($hash);
2019-11-19 04:05:44 +00:00
if(defined($TokenData)){
my $param = {
url => $URL,
header => {"Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}"},
method => 'GET',
timeout => 2,
hash => $hash
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
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'){
2019-11-22 20:57:40 +00:00
foreach my $item( @$decoded_data ) {
$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'}));
}
}
}
2019-11-19 04:05:44 +00:00
}
return undef;
}
2019-11-22 20:57:40 +00:00
sub TadoAPI_GetZoneNameById(@) {
my ($hash, $zoneID) = @_;
my $name = $hash->{NAME};
my @devArr = TadoAPI_GetTadoDevices($hash);
my $zoneName = undef;
foreach my $zone ( @devArr ){
if ($zone->{'id'} == $zoneID){
$zoneName = TadoAPI_ReplaceUmlaute($zone->{'name'});
return $zoneName;
}
}
return undef;
}
sub TadoAPI_GetZoneReadingsById(@){
my ($hash, $zoneID) = @_;
my $name = $hash->{NAME};
my $homeID = $attr{$name}{homeID};
my $URL=$QueryURL.qq{/$homeID/zones/$zoneID/state};
my $temperature = 0;
my $humidity = 0;
my $desiredTemp = 0;
my $currentHeatingPower = 0;
my $overlay = 0;
my $TokenData = TadoAPI_LoadToken($hash);
2019-11-19 04:05:44 +00:00
if(defined($TokenData)){
my $param = {
url => $URL,
header => {"Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}"},
method => 'GET',
2019-11-22 20:57:40 +00:00
timeout => 5,
hash => $hash
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
my ($err, $data) = HttpUtils_BlockingGet($param);
if($err ne "")
{
2019-11-22 14:21:01 +00:00
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 ($@){
2019-11-22 14:21:01 +00:00
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneReadingsById: Zone $zoneID decode_json failed, invalid json. error:$@\n";
}else{
$temperature = $decoded_data->{'sensorDataPoints'}->{'insideTemperature'}->{'celsius'};
$humidity = $decoded_data->{'sensorDataPoints'}->{'humidity'}->{'percentage'};
$desiredTemp = $decoded_data->{'setting'}->{'temperature'}->{'celsius'};
$currentHeatingPower = $decoded_data->{'activityDataPoints'}->{'heatingPower'}->{'percentage'};
$overlay = $decoded_data->{'overlayType'};
if (!defined $overlay) {$overlay = "no overlay"};
my $zoneName = TadoAPI_GetZoneNameById($hash, $zoneID);
2019-11-22 20:57:40 +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 );
}
return ($temperature, $humidity, $desiredTemp, $currentHeatingPower, $overlay );
}
2019-11-19 04:05:44 +00:00
}
}
return undef;
}
2019-11-22 20:57:40 +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};
my $TokenData = TadoAPI_LoadToken($hash);
if(defined($TokenData)){
my $param = {
url => $URL,
header => {"Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}"},
method => 'GET',
timeout => 5,
hash => $hash
};
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
my ($err, $data) = HttpUtils_BlockingGet($param);
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 = ();
foreach my $dev (@$decoded_data){
push @devices, $dev;
}
return @devices;
}
}
}
}
return undef;
}
######################################################
# 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 = "";
if (eval "use Digest::MD5;1")
{
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
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);
if ( defined($error) )
{
Log3 $name,3, "$name, cant't read Tado password from FhemUtils/uniqueID: $error";
return undef;
}
if ( defined($password) )
{
if (eval "use Digest::MD5;1")
{
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
my $dec_pwd = '';
for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g))
{
my $decode=chop($key);
$dec_pwd.=chr(ord($char)^ord($decode));
$key=$decode.$key;
}
return $dec_pwd;
}
else
{
Log3 $name,3,"$name, no Tado password found in FhemUtils/uniqueID";
return undef;
}
}
2019-10-17 10:09:00 +00:00
1;
# Beginn der Commandref
=pod
=item [device]
=item summary integration of the Tado API
=item summary_DE Anbindung der Tado Heizungssteuerung über API
=begin html
<a name="TadoAPI"></a>
<h3>Tado</h3>
<ul>
<i>TadoAPI</i> implements an interface to the Tado cloud.
<br><br>
<a name="Define"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; TadoAPI &lt;tado-mailadress&gt; &lt;homeID&gt;</code>
<br><br>
Example: <code>define myTado TadoAPI mail@example.com 12345</code>
<br><br>
The username and password must match the username and password used on the Tado website.
After successful define, store PASSWORD with set myTado password &lt;yout-tado-password&gt;
Password is sored in encrypted FHEM store. All requests to the API are via oauth2 token.
</ul>
2019-10-17 10:09:00 +00:00
=end html
=begin html_DE
-
=end html
# Ende der Commandref
=cut