1305 lines
43 KiB
Perl
1305 lines
43 KiB
Perl
#!/usr/bin/env perl
|
|
#===============================================================================
|
|
#
|
|
# FILE: 98_TadoAPI_API.pm
|
|
#
|
|
# USAGE: Module for FHEM
|
|
# Info: Turn $debug on for debugging
|
|
#
|
|
# REQUIREMENTS: Below modules should be pre-installed.
|
|
# HTTP::Request::Common
|
|
# HTTP::Headers
|
|
# LWP::UserAgent::Paranoid
|
|
# Data::Dumper;
|
|
# JSON
|
|
#
|
|
# BUGS: ---
|
|
# NOTES: ---
|
|
# AUTHOR: Philipp Wolfmajer
|
|
# ORGANIZATION:
|
|
# VERSION: 1.0
|
|
# CREATED: 04/12/2019 07:55:44 PM
|
|
# REVISION: 11/15/2019 05:17:22 PM
|
|
#===============================================================================
|
|
package main;
|
|
use strict;
|
|
use warnings;
|
|
use utf8;
|
|
use FHEM::Meta;
|
|
|
|
|
|
use HTTP::Request::Common qw (POST GET PUT);
|
|
use HTTP::Headers;
|
|
use LWP::UserAgent::Paranoid;
|
|
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";
|
|
my $debug = 0;
|
|
my $header = {};
|
|
my $data = {};
|
|
my $TokenData = {};
|
|
my $apiStatus = 1;
|
|
|
|
|
|
|
|
#####################################
|
|
#Request data for HTTP Request & Response
|
|
my $Request=undef;
|
|
my $Response=undef;
|
|
|
|
#Empty variable for LWP User Agent for sending HTTP Request
|
|
my $UserAgent = undef;
|
|
|
|
my %sets = (
|
|
"getTemperature" => "",
|
|
"refreshToken" => "noArg",
|
|
"password" => "",
|
|
"update" => "noArg",
|
|
"setGeo" => "",
|
|
"setZoneOverlay" => "",
|
|
"setAllOverlays" => ""
|
|
);
|
|
|
|
my %gets = (
|
|
"getZoneDevices" => "noArg",
|
|
"getZoneInfo" => "noArg",
|
|
"getGeo" => "",
|
|
#"getXTest" => "noArg",
|
|
"getMobileDevices" => "noArg"
|
|
);
|
|
|
|
|
|
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 " .
|
|
"debug:1,0 " .
|
|
$main::readingFnAttributes;
|
|
|
|
}
|
|
|
|
sub TadoAPI_Init($$)
|
|
{
|
|
my ($hash,$args) = @_;
|
|
my $u = "wrong syntax: define <name> TadoAPI <username> <homeID> [<mobileID>]";
|
|
return $u if(int(@$args) < 2);
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_Define($$)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
my @a = split( "[ \t]+", $def );
|
|
my $name = shift @a;
|
|
my $type = shift @a;
|
|
|
|
return "Invalid number of arguments: "
|
|
. "define <name> TadoAPI <username> <homeID> [<mobileID>]"
|
|
if ( int(@a) < 2 );
|
|
|
|
my ( $user, $homeID, $mobileID ) = @a;
|
|
Log3 $name, 3, "TadoAPI_Define $name: called ";
|
|
$hash->{STATE}="defined";
|
|
|
|
# Initialize the device
|
|
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
|
|
|
$hash->{TADO_USER} = $user;
|
|
|
|
if ( defined($homeID) && $homeID ne "" ) {
|
|
$attr{$name}{homeID} = $homeID;
|
|
}
|
|
|
|
if ( defined($mobileID) && $mobileID ne "" ) {
|
|
$main::attr{$a[0]}{"mobileID"}= $mobileID;
|
|
}
|
|
|
|
my @args = ($homeID, $mobileID);
|
|
|
|
if ($main::init_done) {
|
|
# do something?
|
|
|
|
return TadoAPI_Catch($@) if $@;
|
|
}
|
|
|
|
# start the status update timer
|
|
RemoveInternalTimer($hash);
|
|
InternalTimer( gettimeofday() + 10, "TadoAPI_Update", $hash, 0 );
|
|
|
|
return undef;
|
|
}
|
|
|
|
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;
|
|
|
|
#debug
|
|
$debug = $attr{$name}{debug};
|
|
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);
|
|
} else {
|
|
Log3 $name, 3, "TadoAPI: set $name: processing ($cmd)";
|
|
TadoAPI_SetGeoById($hash, $value, 0);
|
|
}
|
|
TadoAPI_GetGeoById($hash, $value);
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'setZoneOverlay' ) {
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)" if $debug;
|
|
return "Need at least two parameters (ID, Setting)" if(@a < 4);
|
|
if( $a[3] > 10 ) {
|
|
TadoAPI_SetZoneOverlay($hash, $value, $a[3]);
|
|
} else {
|
|
TadoAPI_SetZoneOverlay($hash, $value, "off");
|
|
}
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'setAllOverlays' ) {
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)" if $debug;
|
|
return "Need at least one parameter (Setting)" if(@a < 3);
|
|
if( $value > 1 ) {
|
|
TadoAPI_SetAllOverlays($hash, $value);
|
|
} else {
|
|
TadoAPI_SetAllOverlays($hash, "off");
|
|
}
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'refreshToken' ) {
|
|
Log3 $name, 3, "TadoAPI: set $name: processing ($cmd)";
|
|
RemoveInternalTimer($hash);
|
|
InternalTimer( gettimeofday() + 10, "TadoAPI_Update", $hash, 0 );
|
|
TadoAPI_Connect($hash);
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'update' ) {
|
|
Log3 $name, 3, "TadoAPI: set $name: processing ($cmd)";
|
|
TadoAPI_UpdateFn($hash);
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'getTemperature' ) {
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)" if $debug;
|
|
return "ZoneID as parameter needed" if (!$value);
|
|
if( $value >= 1 ) {
|
|
TadoAPI_GetZoneReadingsById($hash, $value);
|
|
} else {
|
|
return "Wrong ZoneID";
|
|
}
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
return undef;
|
|
|
|
|
|
} elsif( $cmd eq 'password' ) {
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)" if $debug;
|
|
# name und cmd überspringen
|
|
shift @a;
|
|
shift @a;
|
|
# den Rest der das passwort enthält, als ein String
|
|
$subcmd = join(" ",@a);
|
|
return TadoAPI_storePassword($name,$subcmd);
|
|
#Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
return undef;
|
|
}
|
|
|
|
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 $mobileID = $attr{$name}{mobileID};
|
|
my $message = undef;
|
|
|
|
#debug
|
|
$debug = $attr{$name}{debug};
|
|
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/) {
|
|
|
|
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)" if $debug;
|
|
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)" if $debug;
|
|
my @data = TadoAPI_RequestMobileDevices($hash);
|
|
$message = "Device List:\n";
|
|
foreach my $item ( @data ){
|
|
print "\n";
|
|
$message .= $item->{'name'} . ": " . $item->{'id'} . "\n";
|
|
};
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
last;
|
|
};
|
|
|
|
$cmd eq "getXTest" and do {
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)" if $debug;
|
|
TadoAPI_UpdateFn($hash);
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
last;
|
|
};
|
|
|
|
$cmd eq "getZoneDevices" and do {
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "processing ($cmd)" if $debug;
|
|
TadoAPI_Connect($hash);
|
|
my @devArr = TadoAPI_RequestTadoDevices($hash);
|
|
my $devicecount = 0;
|
|
$message = "Tado-Device(s):\n";
|
|
for (my $i=0; $i < @devArr; $i++){
|
|
my $tadodevices = $devArr[$i]->{'devices'};
|
|
$message .= "ZoneID:" . ($i+1);
|
|
my $spacer = 0;
|
|
foreach my $item ( @$tadodevices ){
|
|
$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)" if $debug;
|
|
TadoAPI_GetZoneInfo($hash);
|
|
my $zonecount = TadoAPI_GetZoneCount($hash);
|
|
$message = "You have $zonecount Zones.\n";
|
|
for (my $i=1; $i <= $zonecount; $i++) {
|
|
$message .= "Zone ID:$i: " . TadoAPI_GetZoneNameById($hash, $i) . "\n";
|
|
}
|
|
$message .= "See Logfile for more Info";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "$cmd finished\n";
|
|
last;
|
|
};
|
|
}
|
|
|
|
return $message if $message;
|
|
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_callback($$$){
|
|
my ($param, $err, $data) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
$param = 0 unless defined $param;
|
|
|
|
if($param->{code} == 401 || $param->{code} == 400){
|
|
$apiStatus = 1;
|
|
$hash->{STATE}="reachable";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "API is reachable. Callback Status: " . $param->{code} if $debug;
|
|
|
|
}else{
|
|
$apiStatus = 0;
|
|
$hash->{STATE}="error";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "API error: apiStatus $apiStatus ($err)" if $debug;
|
|
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_Status(@){
|
|
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_Connect(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $tokenFileName = $tokenFile."_".$name;
|
|
my $tokenLifeTime = $hash->{TOKEN_LIFETIME};
|
|
|
|
$tokenLifeTime = 0 if(!defined $tokenLifeTime || $tokenLifeTime eq '');
|
|
|
|
#debug
|
|
$debug = $attr{$name}{debug};
|
|
|
|
#load existing token, or try to refresh old one
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Loading Token Data from file: $tokenFileName." if $debug;
|
|
|
|
TadoAPI_Status($hash);
|
|
|
|
if($apiStatus){
|
|
eval {
|
|
open(TOKENFILE, '<', $tokenFileName) or die("ERROR: $!");
|
|
$TokenData = decode_json(<TOKENFILE>)};
|
|
|
|
if($@ || $tokenLifeTime < gettimeofday()){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error while loading: $@" if $debug && $@;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Token expired, requesting new one" if $debug && $tokenLifeTime < gettimeofday();
|
|
TadoAPI_requestNewToken($hash);
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Token expires at " . localtime($tokenLifeTime) if $debug;
|
|
# if token is about to expire, refresh him
|
|
if (($tokenLifeTime-60) < gettimeofday()){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Token will expire soon, refreshing" if $debug;
|
|
TadoAPI_refreshToken($hash);
|
|
}
|
|
|
|
}
|
|
close(TOKENFILE);
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_requestNewToken(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $username = $hash->{TADO_USER};
|
|
my $password = TadoAPI_readPassword($name);
|
|
my $tokenFileName = $tokenFile."_".$name;
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Requesting new Token (TadoAPI_requestNewToken)" if $debug;
|
|
|
|
$data = {
|
|
client_id => $client_id,
|
|
client_secret => $client_secret,
|
|
username => $username,
|
|
password => $password,
|
|
scope => $scope,
|
|
grant_type=>'password'
|
|
};
|
|
|
|
my $req = POST($AuthURL,$data);
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
$TokenData = decode_json($res->content);
|
|
|
|
#write token data in file
|
|
open(TOKENFILE,">$tokenFileName") or die("ERROR: $!");
|
|
print TOKENFILE $res->content."\n";
|
|
close(TOKENFILE);
|
|
|
|
# token time management
|
|
$hash->{TOKEN_LIFETIME} = gettimeofday() + $TokenData->{'expires_in'};
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Retrived new authentication token successfully. Valid until " . localtime($hash->{TOKEN_LIFETIME}) if $debug;
|
|
|
|
#return to apistatus
|
|
$hash->{STATE}="reachable";
|
|
return 1;
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error in token retrival [Authentication Error]";
|
|
print $Response->status_line."\n" if $debug;
|
|
#apiStatus down
|
|
$hash->{STATE}="error";
|
|
return 0;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_refreshToken(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $tokenFileName = $tokenFile."_".$name;
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "calling TadoAPI_refreshToken()" if $debug;
|
|
|
|
$data = {
|
|
client_id => $client_id,
|
|
client_secret => $client_secret,
|
|
scope => $scope,
|
|
grant_type=>'refresh_token',
|
|
refresh_token => $TokenData->{'refresh_token'}
|
|
};
|
|
|
|
$Request = POST($AuthURL,$data);
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 3,);
|
|
$Response = $UserAgent->request($Request);
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8");
|
|
|
|
if($Response->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "refreshed authentication token successfully." if $debug;
|
|
$TokenData = decode_json($Response->content);
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "writting refreshed Token Data to file $tokenFileName" if $debug;
|
|
open(TOKENFILE,">$tokenFileName") or die("ERROR: $!");
|
|
print TOKENFILE $Response->content."\n";
|
|
close(TOKENFILE);
|
|
#Log3 $name, 3, "TadoAPI $name" . ": " . "refreshed Token successful written" if $debug;
|
|
return 1;
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "token expired, requesting new token";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . $Response->status_line if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "response code: " . $Response->code if $debug;
|
|
#if token is expired and connection up, request new one
|
|
if($Response->code == 500){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "error: not connected - response: 500" if $debug;
|
|
$apiStatus = 0;
|
|
$hash->{STATE}="error";
|
|
}
|
|
else{
|
|
TadoAPI_requestNewToken($hash);
|
|
}
|
|
return 0;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_Update(@){
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "TadoAPI_Update called" if $debug;
|
|
|
|
# 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);
|
|
|
|
return undef;
|
|
}
|
|
|
|
######################## tado methods ########################
|
|
##############################################################
|
|
sub TadoAPI_SetZoneOverlay(@){
|
|
my ($hash, $zoneID, $setting) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL = $QueryURL . qq{/$homeID/zones/$zoneID/overlay};
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": SetOverlay for Zone $zoneID (Setting: " . $setting . ") - " . "query-URL: $URL" if $debug;
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
my $zoneName = TadoAPI_GetZoneNameById($hash, $zoneID);
|
|
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$UserAgent->default_headers($header);
|
|
|
|
my $req = undef;
|
|
my $res = undef;
|
|
my $message = "";
|
|
|
|
if ($setting eq "off"){
|
|
# Delete overlay
|
|
$req = HTTP::Request->new( 'DELETE', $URL );
|
|
$req->content_type('application/json');
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Deleting Overlay (off)" if $debug;
|
|
$message = "no overlay";
|
|
}elsif($setting > 10){
|
|
$req = HTTP::Request->new( 'PUT', $URL );
|
|
$req->content_type('application/json');
|
|
my $message = {
|
|
setting => {
|
|
type => "HEATING",
|
|
power => "ON",
|
|
temperature => {
|
|
celsius => $setting
|
|
},
|
|
},
|
|
termination => {
|
|
type => "MANUAL"
|
|
},
|
|
};
|
|
my $myjson = encode_json($message);
|
|
#print Dumper($myjson);
|
|
$req->content($myjson);
|
|
|
|
$message = "MANUAL";
|
|
}
|
|
$UserAgent->default_headers($header);
|
|
$res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
print "\n Retriving State:\n" if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Retriving state:\n" . $res->content if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Set Overlay for Zone $zoneID to: $setting";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error in SetOverlay()";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Status (setOverlay): " . $res->status_line if $debug;
|
|
}
|
|
# finaly update readings
|
|
TadoAPI_GetZoneReadingsById($hash, $zoneID);
|
|
}
|
|
}
|
|
|
|
sub TadoAPI_SetAllOverlays(@){
|
|
my ($hash, $setting) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
my $zonecount = TadoAPI_RequestZones($hash);
|
|
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = undef;
|
|
my $res = undef;
|
|
my $message = "";
|
|
|
|
for (my $i=1; $i <= $zonecount; $i++) {
|
|
my $URL = $QueryURL . qq{/$homeID/zones/$i/overlay};
|
|
|
|
if ($setting eq "off"){
|
|
# Delete overlay
|
|
$req = HTTP::Request->new( 'DELETE', $URL );
|
|
$req->content_type('application/json');
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Deleting Overlay (off)" if $debug;
|
|
$message = "no overlay";
|
|
}elsif($setting > 10){
|
|
$req = HTTP::Request->new( 'PUT', $URL );
|
|
$req->content_type('application/json');
|
|
$message = {
|
|
setting => {
|
|
type => "HEATING",
|
|
power => "ON",
|
|
temperature => {
|
|
celsius => $setting
|
|
},
|
|
},
|
|
termination => {
|
|
type => "MANUAL"
|
|
},
|
|
};
|
|
my $myjson = encode_json($message);
|
|
#print Dumper($myjson);
|
|
$req->content($myjson);
|
|
|
|
$message = "MANUAL";
|
|
}
|
|
$ua->default_headers($header);
|
|
$res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
print "\n Retriving State:\n" if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Retriving state:\n" . $res->content if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Set Overlay for Zone $i to: $setting";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error in SetOverlay()";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Status (setOverlay): " . $res->status_line if $debug;
|
|
}
|
|
|
|
}
|
|
# finaly update readings
|
|
#TadoAPI_UpdateFn($hash);
|
|
}
|
|
}
|
|
|
|
sub TadoAPI_UpdateFn(@){
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
TadoAPI_Connect($hash);
|
|
|
|
# zone specific updates
|
|
if($apiStatus == 1){
|
|
my @zones = TadoAPI_RequestZones($hash);
|
|
my @devices = TadoAPI_RequestTadoDevices($hash);
|
|
my $zonecount = @zones;
|
|
|
|
for (my $i=0; $i < $zonecount; $i++) {
|
|
print "HomeMode_" . encode("UTF-8", $devices[$i]->{'name'}) . $zones[$i]->{'tadoMode'} . "\n" if $debug;
|
|
my $overlay = $zones[$i]->{'overlayType'};
|
|
if (!defined $overlay) {$overlay = "no overlay"};
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "HomeMode_" . TadoAPI_ReplaceUmlaute($devices[$i]->{'name'}), $zones[$i]->{'tadoMode'});
|
|
readingsBulkUpdate($hash, "Temperatur_" . TadoAPI_ReplaceUmlaute($devices[$i]->{'name'}), $zones[$i]->{'sensorDataPoints'}->{'insideTemperature'}->{'celsius'});
|
|
readingsBulkUpdate($hash, "Luftfeuchtigkeit_" . TadoAPI_ReplaceUmlaute($devices[$i]->{'name'}), $zones[$i]->{'sensorDataPoints'}->{'humidity'}->{'percentage'});
|
|
readingsBulkUpdate($hash, "Heizleistung_" . TadoAPI_ReplaceUmlaute($devices[$i]->{'name'}), $zones[$i]->{'activityDataPoints'}->{'heatingPower'}->{'percentage'});
|
|
readingsBulkUpdate($hash, "OverlayType_" . TadoAPI_ReplaceUmlaute($devices[$i]->{'name'}), $overlay);
|
|
readingsBulkUpdate($hash, "DesiredTemp_" . TadoAPI_ReplaceUmlaute($devices[$i]->{'name'}), $zones[$i]->{'setting'}->{'temperature'}->{'celsius'});
|
|
readingsEndUpdate( $hash, 1 );
|
|
}
|
|
|
|
# mobile devices
|
|
my @mobDev = TadoAPI_RequestMobileDevices($hash);
|
|
my $mobDevCount = @mobDev;
|
|
|
|
for (my $i=0; $i < $mobDevCount; $i++) {
|
|
print "Geolocation_" . encode("UTF-8", $mobDev[$i]->{'name'}) . $mobDev[$i]->{'settings'}->{'geoTrackingEnabled'} . "\n" if $debug;
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "Geolocation_" . encode("UTF-8", $mobDev[$i]->{'id'}), $mobDev[$i]->{'settings'}->{'geoTrackingEnabled'});
|
|
readingsEndUpdate( $hash, 1 );
|
|
}
|
|
|
|
# tado devices
|
|
for (my $i=0; $i < $zonecount; $i++) {
|
|
my $deviceList = $devices[$i]->{'devices'};
|
|
foreach my $dev ( @$deviceList ){
|
|
print "Battery_" . encode("UTF-8", $dev->{'serialNo'}) . $dev->{'batteryState'} . "\n" if $debug;
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "Battery_" . $dev->{'serialNo'}, $dev->{'batteryState'});
|
|
readingsEndUpdate( $hash, 1 );
|
|
}
|
|
}
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "ActiveZones", $zonecount);
|
|
readingsEndUpdate( $hash, 1 );
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_GetGeoById(@){
|
|
my ($hash, $mobileID) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL.qq{/$homeID/mobileDevices/$mobileID/settings};
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "query-URL: $URL" if $debug;
|
|
TadoAPI_Connect($hash);
|
|
|
|
if($apiStatus == 1){
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = GET($URL);
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
my $ResponseData = decode_json($res->content);
|
|
my $setting = $ResponseData->{'geoTrackingEnabled'};
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Actual geo setting for $mobileID is: $setting" if $debug;
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "Geolocation_" . $mobileID, $setting);
|
|
readingsEndUpdate( $hash, 1 );
|
|
return $setting;
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "[Authentication Error in TadoAPI_GetGeoById( $mobileID )]";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error: ". $res->status_line if $debug;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub TadoAPI_GetZoneInfo(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL.qq{/$homeID/zones/1/state};
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$UserAgent->default_headers($header);
|
|
|
|
# HomeInfo
|
|
$URL = qq{https://my.tado.com/api/v2/me};
|
|
|
|
my $req = GET($URL);
|
|
my $res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Home Info:\n" . $res->content . "\n";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneInfo: [Authentication Error]". $res->status_line;
|
|
}
|
|
# TadoDevicesInfo
|
|
$URL = $QueryURL.qq{/$homeID/zones};
|
|
|
|
$req = GET($URL);
|
|
$res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Tado Devices:\n" . $res->content . "\n";
|
|
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneInfo: [Authentication Error]". $res->status_line;
|
|
}
|
|
# Mobileinfo
|
|
$URL = $QueryURL . qq{/$homeID/mobileDevices};
|
|
|
|
$req = GET($URL);
|
|
$res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Mobile Devices:\n" . $res->content . "\n";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneInfo: [Authentication Error]". $res->status_line;
|
|
}
|
|
|
|
my $mobileID = $attr{$name}{mobileID};
|
|
$URL=$QueryURL.qq{/$homeID/mobileDevices/$mobileID/settings};
|
|
|
|
$req = GET($URL);
|
|
$res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Mobile Device $mobileID :\n" . $res->content . "\n";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneInfo: [Authentication Error]". $res->status_line;
|
|
}
|
|
|
|
# zones
|
|
for (my $i=1; $i <= TadoAPI_GetZoneCount($hash); $i++) {
|
|
$URL=$QueryURL.qq{/$homeID/zones/$i/state};
|
|
|
|
$req = GET($URL);
|
|
$res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
print "\n";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "ZoneID $i (" . TadoAPI_GetZoneNameById($hash, $i) . ") Status:\n" . $res->content;
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneInfo [Authentication Error]". $res->status_line;
|
|
}
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_SetGeo(@){
|
|
my ($hash, $geo) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $mobileID = $attr{$name}{mobileID};
|
|
my $URL=$QueryURL.qq{/$homeID/mobileDevices/$mobileID/settings};
|
|
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "homeID: $homeID" if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "geo: $geo" if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "query-URL: $URL" if $debug;
|
|
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$UserAgent->default_headers($header);
|
|
|
|
$Request = HTTP::Request->new('PUT',$URL);
|
|
$Request->content_type('application/json');
|
|
|
|
if($geo){
|
|
$Request->content('{"geoTrackingEnabled":"true"}');
|
|
}else{
|
|
$Request->content('{"geoTrackingEnabled":"false"}');
|
|
}
|
|
|
|
$Response = $UserAgent->request($Request);
|
|
|
|
if($Response->is_success){
|
|
print "\n Retriving State:\n" if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Retriving state:\n" . $Response->content if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Set geo setting for $mobileID to: $geo";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error in setGeo()";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Status: " . $Response->status_line if $debug;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub TadoAPI_SetGeoById(@){
|
|
my ($hash, $mobID, $geo) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL.qq{/$homeID/mobileDevices/$mobID/settings};
|
|
|
|
TadoAPI_Connect($hash);
|
|
|
|
if($apiStatus == 1){
|
|
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = HTTP::Request->new('PUT',$URL);
|
|
$req->content_type('application/json');
|
|
|
|
if($geo){
|
|
$req->content('{"geoTrackingEnabled":"true"}');
|
|
}else{
|
|
$req->content('{"geoTrackingEnabled":"false"}');
|
|
}
|
|
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
print "\n Retriving State:\n" if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Retriving state:\n" . $res->content if $debug;
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Set geo setting for $mobID to: $geo";
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Error in setGeo()";
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Status: " . $res->status_line if $debug;
|
|
}
|
|
}
|
|
}
|
|
|
|
#######################################################################################################
|
|
# API Subs
|
|
###########
|
|
|
|
sub TadoAPI_RequestHome(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $URL=qq{https://my.tado.com/api/v2/me};
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = GET($URL);
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "TadoAPI_RequestHome:\n" . $res->content . "\n" if $debug;
|
|
my $data = $res->content;
|
|
# validate response from api
|
|
eval { decode_json($data) };
|
|
if ($@)
|
|
{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "TadoAPI_RequestHome: decode_json failed, invalid json. error:$@\n";
|
|
}else{
|
|
# returns json
|
|
return $data;
|
|
}
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "TadoAPI_RequestHome: [Authentication Error]". $res->status_line;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
|
|
sub TadoAPI_RequestTadoDevices(@) {
|
|
# returns array with zonenames and zone devices
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL.qq{/$homeID/zones};
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = GET($URL);
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestTadoDevices:\n" . $res->content . "\n" if $debug;
|
|
my $data = $res->content;
|
|
# validate response from api
|
|
my $decoded_data = eval { decode_json($data) };
|
|
if ($@)
|
|
{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestTadoDevices: decode_json failed, invalid json. error:$@\n";
|
|
}else{
|
|
my @devices = ();
|
|
foreach my $dev (@$decoded_data){
|
|
push @devices, $dev;
|
|
}
|
|
return @devices;
|
|
}
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestTadoDevices: [Authentication Error]". $res->status_line;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_RequestZones(@) {
|
|
# returns array with state of all zones
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my @zones = ();
|
|
|
|
for (my $i=1; $i <= TadoAPI_GetZoneCount($hash); $i++) {
|
|
my $URL=$QueryURL.qq{/$homeID/zones/$i/state};
|
|
|
|
my $req = GET($URL);
|
|
my $res = $UserAgent->request($req);
|
|
|
|
if($res->is_success){
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestZones: Zone $i Status:\n" . $res->content if $debug;
|
|
my $data = $res->content;
|
|
# validate response from api
|
|
my $decoded_data = eval { decode_json($data) };
|
|
if ($@)
|
|
{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestZones: Zone $i decode_json failed, invalid json. error:$@\n";
|
|
}else{
|
|
# returns a json object with zone i
|
|
push @zones, $decoded_data;
|
|
}
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestZones: [Authentication Error]". $res->status_line;
|
|
}
|
|
}
|
|
return @zones;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_RequestMobileDevices(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL . qq{/$homeID/mobileDevices};
|
|
|
|
if($apiStatus == 1){
|
|
TadoAPI_Connect($hash);
|
|
my @devices = ();
|
|
|
|
my $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = GET($URL);
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
my $data = $res->content;
|
|
# validate response from api
|
|
my $decoded_data = eval { decode_json($data) };
|
|
if ($@)
|
|
{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestMobileDevice: decode_json failed, invalid json. error:$@\n";
|
|
}else{
|
|
foreach my $item( @$decoded_data ) {
|
|
push @devices, $item;
|
|
}
|
|
}
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "RequestMobileDevices: [Authentication Error]". $res->status_line;
|
|
}
|
|
return @devices;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
|
|
######################################
|
|
############ 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;
|
|
}
|
|
|
|
|
|
sub TadoAPI_GetZoneCount(@) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL.qq{/$homeID/zones};
|
|
my $zonecount = 0;
|
|
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$UserAgent->default_headers($header);
|
|
|
|
$Request = GET($URL);
|
|
$Response = $UserAgent->request($Request);
|
|
|
|
if($Response->is_success){
|
|
my $ResponseData = decode_json($Response->content);
|
|
foreach my $item( @$ResponseData ) {
|
|
$zonecount++;
|
|
}
|
|
return $zonecount;
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneCount: [Authentication Error]". $Response->status_line;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_GetZoneNameById(@) {
|
|
my ($hash, $zoneID) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $homeID = $attr{$name}{homeID};
|
|
my $URL=$QueryURL.qq{/$homeID/zones};
|
|
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$UserAgent->default_headers($header);
|
|
|
|
$Request = GET($URL);
|
|
$Response = $UserAgent->request($Request);
|
|
|
|
if($Response->is_success){
|
|
my $ResponseData = decode_json($Response->content);
|
|
my $zoneName = TadoAPI_ReplaceUmlaute(@$ResponseData[$zoneID - 1]->{'name'});
|
|
$zoneName = encode("UTF-8", $zoneName);
|
|
|
|
return $zoneName;
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneNameById: [Authentication Error]". $Response->status_line;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub TadoAPI_GetZoneTemperatureById(@){
|
|
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;
|
|
|
|
$header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
$UserAgent = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$UserAgent->default_headers($header);
|
|
|
|
$Request = GET($URL);
|
|
$Response = $UserAgent->request($Request);
|
|
|
|
if($Response->is_success){
|
|
my $ResponseData = decode_json($Response->content);
|
|
$temperature = $ResponseData->{'sensorDataPoints'}->{'insideTemperature'}->{'celsius'};
|
|
$humidity = $ResponseData->{'sensorDataPoints'}->{'humidity'}->{'percentage'};
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "Temperature: $temperature Humidity: $humidity\n" if $debug;
|
|
return ($temperature, $humidity);
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneTemperatureById: [Authentication Error]". $Response->status_line;
|
|
}
|
|
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 $header = HTTP::Headers->new("Content-Type"=>"application/json;charset=UTF-8","Authorization" => "$TokenData->{'token_type'} $TokenData->{'access_token'}");
|
|
my $ua = LWP::UserAgent::Paranoid->new(ssl_opts => { verify_hostname => 1 },protocols_allowed => ['https','http'],request_timeout => 5,);
|
|
$ua->default_headers($header);
|
|
|
|
my $req = GET($URL);
|
|
my $res = $ua->request($req);
|
|
|
|
if($res->is_success){
|
|
my $ResponseData = decode_json($res->content);
|
|
$temperature = $ResponseData->{'sensorDataPoints'}->{'insideTemperature'}->{'celsius'};
|
|
$humidity = $ResponseData->{'sensorDataPoints'}->{'humidity'}->{'percentage'};
|
|
$desiredTemp = $ResponseData->{'setting'}->{'temperature'}->{'celsius'};
|
|
$currentHeatingPower = $ResponseData->{'activityDataPoints'}->{'heatingPower'}->{'percentage'};
|
|
$overlay = $ResponseData->{'overlayType'};
|
|
if (!defined $overlay) {$overlay = "no overlay"};
|
|
|
|
my $zoneName = TadoAPI_GetZoneNameById($hash, $zoneID);
|
|
|
|
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 );
|
|
}else{
|
|
Log3 $name, 3, "TadoAPI $name" . ": " . "GetZoneReadingsById: [Authentication Error]". $Response->status_line;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
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
|
|
-
|
|
=end html
|
|
|
|
=begin html_DE
|
|
-
|
|
=end html
|
|
|
|
# Ende der Commandref
|
|
=cut |