2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-07 16:59:18 +00:00

vitoconnect: oneTimeCharge implemented

git-svn-id: https://svn.fhem.de/fhem/trunk@18158 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
andreas13 2019-01-06 08:51:49 +00:00
parent 19e235f523
commit 973da5318f

View File

@ -45,20 +45,20 @@
# 2018-12-30 initial offical release # 2018-12-30 initial offical release
# remove special characters from readings # remove special characters from readings
# some internal improvements suggested by CoolTux # some internal improvements suggested by CoolTux
# 2019-??-?? "disabled" implemented # 2019-01-01 "disabled" implemented
# "set update implemented # "set update implemented
# renamed "WW-onTimeCharge_aktiv" into "WW-einmaliges_Aufladen_aktiv" # renamed "WW-onTimeCharge_aktiv" into "WW-einmaliges_Aufladen_aktiv"
# Attribute vitoconnect_raw_readings:0,1 " and ."vitoconnect_actions_active:0,1 " implemented # Attribute vitoconnect_raw_readings:0,1 " and ."vitoconnect_actions_active:0,1 " implemented
# "set clearReadings" implemented # "set clearReadings" implemented
# 2019-01-05 Passwort wird im KeyValue gespeichert statt im Klartext
# Action "oneTime
# #
# #
# ToDo: # ToDo: "set"s zum Steuern der Heizung
# Passwort im KeyValue speichern statt im Klartext
# "set"s zum Steuern der Heizung
# Dokumentation (auch auf Deutsch) # Dokumentation (auch auf Deutsch)
# Nicht bei jedem Lesen neu einloggen # Nicht bei jedem Lesen neu einloggen
# Fehlerbehandlung verbessern # Fehlerbehandlung verbessern
# Attribute implementieren und dokumentieren (disable, ....) # Attribute implementieren und dokumentieren
# "sinnvolle" Readings statt 1:1 aus der API übernommene # "sinnvolle" Readings statt 1:1 aus der API übernommene
# ErrorListChanges implementieren # ErrorListChanges implementieren
# mapping der Readings optional machen # mapping der Readings optional machen
@ -71,6 +71,7 @@ use warnings;
use Time::HiRes qw(gettimeofday); use Time::HiRes qw(gettimeofday);
use JSON; use JSON;
use HttpUtils; use HttpUtils;
use Encode qw(decode encode);
#use Data::Dumper; #use Data::Dumper;
#use Path::Tiny; #use Path::Tiny;
@ -226,10 +227,12 @@ sub vitoconnect_Define($$) {
if(int(@param) < 5) { return "too few parameters: define <name> vitoconnect <user> <passwd> <intervall>"; } if(int(@param) < 5) { return "too few parameters: define <name> vitoconnect <user> <passwd> <intervall>"; }
$hash->{user} = $param[2]; $hash->{user} = $param[2];
$hash->{passwd} = $param[3];
$hash->{intervall} = $param[4]; $hash->{intervall} = $param[4];
$hash->{counter} = 0; $hash->{counter} = 0;
my $err = vitoconnect_StoreKeyValue($hash, "passwd", $param[3]);
return $err if ($err);
InternalTimer(gettimeofday()+2, "vitoconnect_GetUpdate", $hash); InternalTimer(gettimeofday()+2, "vitoconnect_GetUpdate", $hash);
return undef; return undef;
} }
@ -242,21 +245,49 @@ sub vitoconnect_Undef($$) {
sub vitoconnect_Get($@) { sub vitoconnect_Get($@) {
my ($hash, $name, $opt, @args) = @_; my ($hash, $name, $opt, @args) = @_;
return "get $name needs at least one argument" unless (defined($opt)); return "get $name needs at least one argument" unless (defined($opt));
return undef; return undef;
} }
sub vitoconnect_Set($@) { sub vitoconnect_Set($@) {
my ($hash, $name, $opt, @args) = @_; my ($hash, $name, $opt, @args) = @_;
my $access_token = $hash->{access_token};
my $installation = $hash->{installation};
my $gw = $hash->{gw};
return "set $name needs at least one argument" unless (defined($opt)); return "set $name needs at least one argument" unless (defined($opt));
if ($opt eq "update"){ vitoconnect_GetUpdate($hash); return undef; } if ($opt eq "update"){
elsif ($opt eq "clearReadings") { vitoconnect_GetUpdate($hash); return undef;
} elsif ($opt eq "clearReadings") {
AnalyzeCommand ($hash, "deletereading $name .*"); AnalyzeCommand ($hash, "deletereading $name .*");
return undef; return undef;
} elsif ($opt eq "password") {
my $err = vitoconnect_StoreKeyValue($hash, "passwd", $args[0]); return $err if ($err);
return undef;
} elsif ($opt eq "setMode") {
my $param = {
url => "https://api.viessmann-platform.io/operational-data/installations/$installation/gateways/$gw/devices/0/features/heating.circuits.0.operating.modes.active/setMode",
hash => $hash,
header => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
data => '{"mode":"$args[0]"}',
method => "POST",
timeout => 10,
sslargs => {SSL_verify_mode => 0},
};
# Log5 $name, 3, Dumper($param);
(my $err, my $data) = HttpUtils_BlockingGet($param);
if ($err ne "" || $data ne "") {
Log3 $name, 1, "$name: Fehler während der Befehlsausführung: err = $err data=$data";
} else {
Log3 $name, 4, "$name: Befehlsausführung ok";
} }
return "unknown value $opt, choose one of update:noArg clearReadings:noArg"; return undef;
} elsif ($opt eq "oneTimeCharge") {
vitoconnect_action($hash);
return undef;
}
#return "unknown value $opt, choose one of update:noArg clearReadings:noArg setMode:standby,dhw,dhwAndHeating,forcedReduced,forcedNormal oneTimeCharge:noArg password";
return "unknown value $opt, choose one of update:noArg clearReadings:noArg oneTimeCharge:noArg password";
} }
sub vitoconnect_Attr(@) { sub vitoconnect_Attr(@) {
my ($cmd,$name,$attr_name,$attr_value) = @_; my ($cmd,$name,$attr_name,$attr_value) = @_;
@ -286,8 +317,8 @@ sub vitoconnect_Attr(@) {
sub vitoconnect_GetUpdate($) { sub vitoconnect_GetUpdate($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 $name, 3, "$name: GetUpdate called ..."; Log3 $name, 4, "$name: GetUpdate called ...";
if ( IsDisabled($name) ) { Log3 $name, 3, "$name: device disabled"; if ( IsDisabled($name) ) { Log3 $name, 4, "$name: device disabled";
} else { vitoconnect_getCode($hash); } } else { vitoconnect_getCode($hash); }
return undef; return undef;
} }
@ -295,16 +326,11 @@ sub vitoconnect_GetUpdate($) {
sub vitoconnect_getCode($) { sub vitoconnect_getCode($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $url = "$authorizeURL?client_id=$client_id&scope=openid&redirect_uri=$callback_uri&response_type=code";
my @header = ("Content-Type: application/x-www-form-urlencoded");
my $isiwebuserid = $hash->{user}; my $isiwebuserid = $hash->{user};
my $isiwebpasswd = $hash->{passwd}; my $isiwebpasswd = vitoconnect_ReadKeyValue($hash, "passwd");
Log3 $name, 3, "$name - getCode went ok";
Log3 $name, 5, "getCode: $url";
my $param = { my $param = {
url => $url, url => "$authorizeURL?client_id=$client_id&scope=openid&redirect_uri=$callback_uri&response_type=code",
hash => $hash, hash => $hash,
header => "Content-Type: application/x-www-form-urlencoded", header => "Content-Type: application/x-www-form-urlencoded",
ignoreredirects => 1, ignoreredirects => 1,
@ -315,7 +341,7 @@ sub vitoconnect_getCode($) {
callback => \&vitoconnect_getCodeCallback callback => \&vitoconnect_getCodeCallback
}; };
# Log3 $name, 3, Dumper($hash); # Log3 $name, 5, Dumper($hash);
HttpUtils_NonblockingGet($param); HttpUtils_NonblockingGet($param);
return undef; return undef;
} }
@ -326,11 +352,11 @@ sub vitoconnect_getCodeCallback ($) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if ($err eq "") { if ($err eq "") {
Log3 $name, 3, "$name - getCodeCallback went ok"; Log3 $name, 4, "$name - getCodeCallback went ok";
Log3 $name, 5, "Received response: $response_body"; Log3 $name, 5, "$name: Received response: $response_body";
$response_body =~ /code=(.*)"/; $response_body =~ /code=(.*)"/;
$hash->{code} = $1; $hash->{code} = $1;
Log3 $name, 5, "code = $hash->{code}"; Log3 $name, 5, "$name: code = $hash->{code}";
if ($hash->{code}) { if ($hash->{code}) {
$hash->{login} = "ok"; $hash->{login} = "ok";
} else { } else {
@ -338,7 +364,7 @@ sub vitoconnect_getCodeCallback ($) {
} }
} else { } else {
# Error code, type of error, error message # Error code, type of error, error message
Log3 $name, 1, "An error happened: $err"; Log3 $name, 1, "$name: An error happened: $err";
$hash->{login} = "failure"; $hash->{login} = "failure";
} }
if ($hash->{login} eq "ok") { if ($hash->{login} eq "ok") {
@ -375,8 +401,8 @@ sub vitoconnect_getAccessTokenCallback($) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if ($err eq "") { if ($err eq "") {
Log3 $name, 3, "$name - getAccessTokenCallback went ok"; Log3 $name, 4, "$name - getAccessTokenCallback went ok";
Log3 $name, 5, "Received response: $response_body\n"; Log3 $name, 5, "$name: Received response: $response_body\n";
my $decode_json = eval{decode_json($response_body)}; my $decode_json = eval{decode_json($response_body)};
if($@) { if($@) {
Log3 $name, 1, "$name - JSON error while request: $@"; Log3 $name, 1, "$name - JSON error while request: $@";
@ -385,14 +411,14 @@ sub vitoconnect_getAccessTokenCallback($) {
my $access_token = $decode_json->{"access_token"}; my $access_token = $decode_json->{"access_token"};
if ($access_token ne "") { if ($access_token ne "") {
$hash->{access_token} = $access_token; $hash->{access_token} = $access_token;
Log3 $name, 5, "Access Token: $access_token"; Log3 $name, 5, "$name: Access Token: $access_token";
vitoconnect_getGw($hash); vitoconnect_getGw($hash);
} else { } else {
Log3 $name, 1, "Access Token: undef"; Log3 $name, 1, "$name: Access Token: undef";
InternalTimer(gettimeofday()+$hash->{intervall}, "vitoconnect_GetUpdate", $hash); InternalTimer(gettimeofday()+$hash->{intervall}, "vitoconnect_GetUpdate", $hash);
} }
} else { } else {
Log3 $name, 1, "getAccessToken: An error happened: $err"; Log3 $name, 1, "$name: getAccessToken: An error happened: $err";
InternalTimer(gettimeofday()+$hash->{intervall}, "vitoconnect_GetUpdate", $hash); InternalTimer(gettimeofday()+$hash->{intervall}, "vitoconnect_GetUpdate", $hash);
} }
return undef; return undef;
@ -419,27 +445,19 @@ sub vitoconnect_getGwCallback($) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if ($err eq "") { if ($err eq "") {
Log3 $name, 3, "$name - getGwCallback went ok"; Log3 $name, 4, "$name - getGwCallback went ok";
Log3 $name, 5, "Received response: $response_body\n"; Log3 $name, 5, "$name: Received response: $response_body\n";
my $decode_json = eval{decode_json($response_body)}; my $decode_json = eval{decode_json($response_body)};
if($@) { if($@) { Log3 $name, 1, "$name - JSON error while request: $@"; return; }
Log3 $name, 1, "$name - JSON error while request: $@";
return;
}
my $installation = $decode_json->{entities}[0]->{properties}->{id}; my $installation = $decode_json->{entities}[0]->{properties}->{id};
Log3 $name, 5, "installation: $installation"; Log3 $name, 5, "$name: installation: $installation";
$hash->{installation} = $installation; $hash->{installation} = $installation;
$decode_json = eval{decode_json($response_body)};
if($@) {
Log3 $name, 1, "$name - JSON error while request: $@";
return;
}
my $gw = $decode_json->{entities}[0]->{entities}[0]->{properties}->{serial}; my $gw = $decode_json->{entities}[0]->{entities}[0]->{properties}->{serial};
Log3 $name, 5, "gw: $gw"; Log3 $name, 5, "$name gw: $gw";
$hash->{gw} = $gw; $hash->{gw} = $gw;
vitoconnect_getResource($hash); vitoconnect_getResource($hash);
} else { } else {
Log3 $name, 1, "An error happened: $err"; Log3 $name, 1, "$name: An error happened: $err";
InternalTimer(gettimeofday()+$hash->{intervall}, "vitoconnect_GetUpdate", $hash); InternalTimer(gettimeofday()+$hash->{intervall}, "vitoconnect_GetUpdate", $hash);
} }
return undef; return undef;
@ -468,9 +486,8 @@ sub vitoconnect_getResourceCallback($) {
my $hash = $param->{hash}; my $hash = $param->{hash};
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if ($err eq "") { if ($err eq "") {
Log3 $name, 3, "$name - getResourceCallback went ok"; Log3 $name, 4, "$name - getResourceCallback went ok";
Log3 $name, 5, "Received response: $response_body\n"; Log3 $name, 5, "Received response: $response_body\n";
my $decode_json = eval{decode_json($response_body)}; my $decode_json = eval{decode_json($response_body)};
if($@) { if($@) {
@ -550,9 +567,14 @@ sub vitoconnect_getResourceCallback($) {
if (@actions) { if (@actions) {
# $file_handle->print($FieldName."\n"); # $file_handle->print($FieldName."\n");
# $file_handle->print(Dumper(@actions)); # $file_handle->print(Dumper(@actions));
#Log3 $name, 1, Dumper(@actions) #Log3 $name, 5, Dumper(@actions)
for my $action (@actions) { for my $action (@actions) {
readingsBulkUpdate($hash, $FieldName.".".$action->{"name"}, "action"); my @fields = @{$action->{fields}};
my $Result = "action: ";
for my $field (@fields) {
$Result .= $field->{name}." ";
}
readingsBulkUpdate($hash, $FieldName.".".$action->{"name"}, $Result);
} }
} }
} }
@ -573,6 +595,148 @@ sub vitoconnect_getResourceCallback($) {
return undef; return undef;
} }
sub vitoconnect_action($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $isiwebuserid = $hash->{user};
my $isiwebpasswd = vitoconnect_ReadKeyValue($hash, "passwd");
my $err = "";
my $response_body = "";
my $code = "";
my $access_token = "";
my $installation = "";
my $gw = "";
my $param = {
url => "$authorizeURL?client_id=$client_id&scope=openid&redirect_uri=$callback_uri&response_type=code",
hash => $hash,
header => "Content-Type: application/x-www-form-urlencoded",
ignoreredirects => 1,
user => $isiwebuserid,
pwd => $isiwebpasswd,
sslargs => {SSL_verify_mode => 0},
method => "POST" };
($err, $response_body) = HttpUtils_BlockingGet($param);
if ($err eq "") {
$response_body =~ /code=(.*)"/;
$code = $1;
Log3 $name, 5, "$name - response_body: $response_body";
Log3 $name, 5, "$name - code: $code";
} else { Log3 $name, 1, "$name An error happened: $err"; }
$param = {
url => $token_url,
hash => $hash,
header => "Content-Type: application/x-www-form-urlencoded;charset=utf-8",
data => "client_id=$client_id&client_secret=$client_secret&code=$code&redirect_uri=$callback_uri&grant_type=authorization_code",
sslargs => {SSL_verify_mode => 0},
method => "POST" };
($err, $response_body) = HttpUtils_BlockingGet($param);
if ($err eq "") {
my $decode_json = eval{decode_json($response_body)};
if($@) { Log3 $name, 1, "$name - JSON error while request: $@"; return; }
$access_token = $decode_json->{access_token};
Log3 $name, 5, "$name - access_token: $access_token";
} else { Log3 $name, 1, "$name: getAccessToken: An error happened: $err"; }
$param = {
url => "$apiURLBase$general",
hash => $hash,
header => "Authorization: Bearer $access_token",
sslargs => {SSL_verify_mode => 0}
};
($err, $response_body) = HttpUtils_BlockingGet($param);
if ($err eq "") {
Log3 $name, 5, "$name - action (installation and gw): $response_body";
my $decode_json = eval{decode_json($response_body)};
if($@) { Log3 $name, 1, "$name - JSON error while request: $@"; return; }
$installation = $decode_json->{entities}[0]->{properties}->{id};
$gw = $decode_json->{entities}[0]->{entities}[0]->{properties}->{serial};
Log3 $name, 4, "$name: installation: $installation :: gw: $gw"
} else { Log3 $name, 1, "$name: An error happened: $err"; }
$param = {
url => "https://api.viessmann-platform.io/operational-data/installations/$installation/gateways/$gw/devices/0/features/heating.dhw.oneTimeCharge/activate",
hash => $hash,
header => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
data => '{"mode":"activate"}',
data => '{}',
method => "POST",
timeout => 10,
sslargs => {SSL_verify_mode => 0},
};
# Log3 $name, 5, Dumper($param);
($err, $response_body) = HttpUtils_BlockingGet($param);
if ($err ne "" || $response_body ne "") { Log3 $name, 1, "$name: Fehler während der Befehlsausführung: $err :: $response_body";
} else { Log3 $name, 5, "$name : Befehlsausführung ok"; }
return undef;
}
###################################################
# checks and stores obfuscated keys like passwords
# based on / copied from FRITZBOX_storePassword
sub vitoconnect_StoreKeyValue($$$) {
my ($hash, $kName, $value) = @_;
my $index = $hash->{TYPE}."_".$hash->{NAME}."_".$kName;
my $key = getUniqueId().$index;
my $enc = "";
if(eval "use Digest::MD5;1") {
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
for my $char (split //, $value) {
my $encode=chop($key);
$enc.=sprintf("%.2x",ord($char)^ord($encode));
$key=$encode.$key;
}
my $err = setKeyValue($index, $enc);
return "error while saving the value - $err" if(defined($err));
return undef;
}
#####################################################
# reads obfuscated value
sub vitoconnect_ReadKeyValue($$) {
my ($hash, $kName) = @_;
my $name = $hash->{NAME};
my $index = $hash->{TYPE}."_".$hash->{NAME}."_".$kName;
my $key = getUniqueId().$index;
my ($value, $err);
Log3 $name, 5, "$name: ReadKeyValue tries to read value for $kName from file";
($err, $value) = getKeyValue($index);
if ( defined($err) ) {
Log3 $name, 1, "$name: ReadKeyValue is unable to read value from file: $err";
return undef;
}
if ( defined($value) ) {
if ( eval "use Digest::MD5;1" ) {
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
my $dec = '';
for my $char (map { pack('C', hex($_)) } ($value =~ /(..)/g)) {
my $decode=chop($key);
$dec.=chr(ord($char)^ord($decode));
$key=$decode.$key;
}
return $dec;
} else {
Log3 $name, 1, "$name: ReadKeyValue could not find key $kName in file";
return undef;
}
return;
}
1; 1;
=pod =pod
@ -610,6 +774,11 @@ sub vitoconnect_getResourceCallback($) {
update readings immeadiatlely</li> update readings immeadiatlely</li>
<li>clearReadings<br> <li>clearReadings<br>
clear all readings immeadiatlely</li> clear all readings immeadiatlely</li>
<li>password <passwd><br>
store password in key store</li>
<li>oneTimeCharge><br>
store password in key store</li>
</ul> </ul>
<br> <br>
@ -630,8 +799,14 @@ sub vitoconnect_getResourceCallback($) {
<br><br> <br><br>
Attributes: Attributes:
<ul> <ul>
<li><i>disable</i>:<br>
xxxx
</li>
<li><i>verbose</i>:<br>
xxxx
</li>
<li><i>vitoconnect_raw_readings</i>:<br> <li><i>vitoconnect_raw_readings</i>:<br>
create readings with plain JSON names like 'heating.circuits.0.heating.curve.value' instead of german identifiers create readings with plain JSON names like 'heating.circuits.0.heating.curve.slope' instead of german identifiers
</li> </li>
<li><i>vitoconnect_actions_active</i>:<br> <li><i>vitoconnect_actions_active</i>:<br>
create readings for actions e.g. 'heating.circuits.0.heating.curve.setCurve' create readings for actions e.g. 'heating.circuits.0.heating.curve.setCurve'