From dd473592405368bd6d1d63797b084fe788d4e142 Mon Sep 17 00:00:00 2001 From: andreas13 <> Date: Fri, 16 Jul 2021 07:48:14 +0000 Subject: [PATCH] vitoconnect: using new Viessmann API git-svn-id: https://svn.fhem.de/fhem/trunk@24757 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/98_vitoconnect.pm | 684 ++++++++++++++++++++++-------------- 1 file changed, 422 insertions(+), 262 deletions(-) diff --git a/fhem/FHEM/98_vitoconnect.pm b/fhem/FHEM/98_vitoconnect.pm index c1d0acd8e..b6100cafb 100644 --- a/fhem/FHEM/98_vitoconnect.pm +++ b/fhem/FHEM/98_vitoconnect.pm @@ -187,7 +187,8 @@ # Workaround für Forum #561 # Neue Readings für "*ValueReadAt" # -# 2021-05-17 set +# 2021-07-19 Anpassungen für privaten apiKey. Redirect URIs muss "http://localhost:4200/" sein. +# Nutzung des refresh_token # # ToDo: timeout konfigurierbar machen # Attribute implementieren und dokumentieren @@ -211,16 +212,12 @@ use DateTime; use Time::Piece; use Time::Seconds; -my $client_id = '79742319e39245de5f91d15ff4cac2a8'; -my $client_secret = '8ad97aceb92c5892e102b093c7c083fa'; -my $authorizeURL = 'https://iam.viessmann.com/idp/v2/authorize'; -my $token_url = "https://iam.viessmann.com/idp/v2/token"; -my $apiURLBase = "https://api.viessmann-platform.io"; -my $apiURLInst = - "https://api.viessmann-platform.io/operational-data/v1/installations"; -my $general = "/general-management/installations?expanded=true&"; -my $callback_uri = "vicare://oauth-callback/everest"; +my $client_secret = "2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c"; +my $callback_uri = "http://localhost:4200/"; +my $apiURLBase = "https://api.viessmann-platform.io/iot/v1/equipment/"; +my $apiURL = "https://api.viessmann.com/iot/v1/equipment/"; + my $RequestList = { "heating.boiler.serial.value" => "Kessel_Seriennummer", @@ -829,13 +826,14 @@ sub vitoconnect_Define { my @param = split( '[ \t]+', $def ); if ( int(@param) < 5 ) { - return -"too few parameters: define vitoconnect "; + return "too few parameters: " + . "define vitoconnect "; } $hash->{user} = $param[2]; $hash->{intervall} = $param[4]; $hash->{counter} = 0; + $hash->{timeout} = 15; $hash->{".access_token"} = ""; $hash->{".installation"} = ""; $hash->{".gw"} = ""; @@ -848,6 +846,7 @@ sub vitoconnect_Define { else { Log3 $name, 3, "$name - Passwort war bereits gespeichert"; } + $hash->{apiKey} = vitoconnect_ReadKeyValue( $hash, "apiKey" ); InternalTimer( gettimeofday() + 10, "vitoconnect_GetUpdate", $hash ); return; } @@ -885,13 +884,21 @@ sub vitoconnect_Set { elsif ( $opt eq "password" ) { my $err = vitoconnect_StoreKeyValue( $hash, "passwd", $args[0] ); return $err if ($err); + vitoconnect_getCode($hash); + return; + } + elsif ( $opt eq "apiKey" ) { + $hash->{apiKey} = $args[0]; + my $err = vitoconnect_StoreKeyValue( $hash, "apiKey", $args[0] ); + RemoveInternalTimer($hash); + vitoconnect_getCode($hash); return; } elsif ( $opt eq "HK1-Heizkurve-Niveau" ) { my $slope = ReadingsVal( $name, "HK1-Heizkurve-Steigung", "" ); vitoconnect_action( $hash, - "heating.circuits.0.heating.curve/setCurve", + "heating.circuits.0.heating.curve/commands/setCurve", "{\"shift\":$args[0],\"slope\":$slope}", $name, $opt, @args ); @@ -901,7 +908,7 @@ sub vitoconnect_Set { my $slope = ReadingsVal( $name, "HK2-Heizkurve-Steigung", "" ); vitoconnect_action( $hash, - "heating.circuits.1.heating.curve/setCurve", + "heating.circuits.1.heating.curve/commands/setCurve", "{\"shift\":$args[0],\"slope\":$slope}", $name, $opt, @args ); @@ -911,7 +918,7 @@ sub vitoconnect_Set { my $slope = ReadingsVal( $name, "HK3-Heizkurve-Steigung", "" ); vitoconnect_action( $hash, - "heating.circuits.2.heating.curve/setCurve", + "heating.circuits.2.heating.curve/commands/setCurve", "{\"shift\":$args[0],\"slope\":$slope}", $name, $opt, @args ); @@ -921,7 +928,7 @@ sub vitoconnect_Set { my $shift = ReadingsVal( $name, "HK1-Heizkurve-Niveau", "" ); vitoconnect_action( $hash, - "heating.circuits.0.heating.curve/setCurve", + "heating.circuits.0.heating.curve/commands/setCurve", "{\"shift\":$shift,\"slope\":$args[0]}", $name, $opt, @args ); @@ -931,7 +938,7 @@ sub vitoconnect_Set { my $shift = ReadingsVal( $name, "HK2-Heizkurve-Niveau", "" ); vitoconnect_action( $hash, - "heating.circuits.1.heating.curve/setCurve", + "heating.circuits.1.heating.curve/commands/setCurve", "{\"shift\":$shift,\"slope\":$args[0]}", $name, $opt, @args ); @@ -941,7 +948,7 @@ sub vitoconnect_Set { my $shift = ReadingsVal( $name, "HK3-Heizkurve-Niveau", "" ); vitoconnect_action( $hash, - "heating.circuits.2.heating.curve/setCurve", + "heating.circuits.2.heating.curve/commands/setCurve", "{\"shift\":$shift,\"slope\":$args[0]}", $name, $opt, @args ); @@ -956,7 +963,7 @@ sub vitoconnect_Set { } vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.holiday/schedule", + "heating.circuits.0.operating.programs.holiday/commands/schedule", "{\"start\":\"$args[0]\",\"end\":\"$end\"}", $name, $opt, @args ); @@ -971,7 +978,7 @@ sub vitoconnect_Set { } vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.holiday/schedule", + "heating.circuits.1.operating.programs.holiday/commands/schedule", "{\"start\":\"$args[0]\",\"end\":\"$end\"}", $name, $opt, @args ); @@ -986,7 +993,7 @@ sub vitoconnect_Set { } vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.holiday/schedule", + "heating.circuits.2.operating.programs.holiday/commands/schedule", "{\"start\":\"$args[0]\",\"end\":\"$end\"}", $name, $opt, @args ); @@ -996,7 +1003,7 @@ sub vitoconnect_Set { my $start = ReadingsVal( $name, "HK1-Urlaub_Start", "" ); vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.holiday/schedule", + "heating.circuits.0.operating.programs.holiday/commands/schedule", "{\"start\":\"$start\",\"end\":\"$args[0]\"}", $name, $opt, @args ); @@ -1006,7 +1013,7 @@ sub vitoconnect_Set { my $start = ReadingsVal( $name, "HK2-Urlaub_Start", "" ); vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.holiday/schedule", + "heating.circuits.1.operating.programs.holiday/commands/schedule", "{\"start\":\"$start\",\"end\":\"$args[0]\"}", $name, $opt, @args ); @@ -1016,7 +1023,7 @@ sub vitoconnect_Set { my $start = ReadingsVal( $name, "HK3-Urlaub_Start", "" ); vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.holiday/schedule", + "heating.circuits.2.operating.programs.holiday/commands/schedule", "{\"start\":\"$start\",\"end\":\"$args[0]\"}", $name, $opt, @args ); @@ -1024,80 +1031,80 @@ sub vitoconnect_Set { } elsif ( $opt eq "HK1-Urlaub_unschedule" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.holiday/unschedule", + "heating.circuits.0.operating.programs.holiday/commands/unschedule", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK2-Urlaub_unschedule" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.holiday/unschedule", + "heating.circuits.1.operating.programs.holiday/commands/unschedule", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK3-Urlaub_unschedule" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.holiday/unschedule", + "heating.circuits.2.operating.programs.holiday/commands/unschedule", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK1-Zeitsteuerung_Heizung" ) { vitoconnect_action( $hash, - "heating.circuits.0.heating.schedule/setSchedule", + "heating.circuits.0.heating.schedule/commands/setSchedule", "{\"newSchedule\":@args}", $name, $opt, @args ); return; } elsif ( $opt eq "HK2-Zeitsteuerung_Heizung" ) { vitoconnect_action( $hash, - "heating.circuits.1.heating.schedule/setSchedule", + "heating.circuits.1.heating.schedule/commands/setSchedule", "{\"newSchedule\":@args}", $name, $opt, @args ); return; } elsif ( $opt eq "HK3-Zeitsteuerung_Heizung" ) { vitoconnect_action( $hash, - "heating.circuits.2.heating.schedule/setSchedule", + "heating.circuits.2.heating.schedule/commands/setSchedule", "{\"newSchedule\":@args}", $name, $opt, @args ); return; } elsif ( $opt eq "HK1-Betriebsart" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.modes.active/setMode", + "heating.circuits.0.operating.modes.active/commands/setMode", "{\"mode\":\"$args[0]\"}", $name, $opt, @args ); return; } elsif ( $opt eq "HK2-Betriebsart" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.modes.active/setMode", + "heating.circuits.1.operating.modes.active/commands/setMode", "{\"mode\":\"$args[0]\"}", $name, $opt, @args ); return; } elsif ( $opt eq "HK3-Betriebsart" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.modes.active/setMode", + "heating.circuits.2.operating.modes.active/commands/setMode", "{\"mode\":\"$args[0]\"}", $name, $opt, @args ); return; } elsif ( $opt eq "HK1-Solltemperatur_comfort_aktiv" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.comfort/$args[0]", + "heating.circuits.0.operating.programs.comfort/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK2-Solltemperatur_comfort_aktiv" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.comfort/$args[0]", + "heating.circuits.1.operating.programs.comfort/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK3-Solltemperatur_comfort_aktiv" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.comfort/$args[0]", + "heating.circuits.2.operating.programs.comfort/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK1-Solltemperatur_comfort" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.comfort/setTemperature", + "heating.circuits.0.operating.programs.comfort/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1106,7 +1113,7 @@ sub vitoconnect_Set { elsif ( $opt eq "HK2-Solltemperatur_comfort" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.comfort/setTemperature", + "heating.circuits.1.operating.programs.comfort/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1115,7 +1122,7 @@ sub vitoconnect_Set { elsif ( $opt eq "HK3-Solltemperatur_comfort" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.comfort/setTemperature", + "heating.circuits.2.operating.programs.comfort/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1123,27 +1130,27 @@ sub vitoconnect_Set { } elsif ( $opt eq "HK1-Solltemperatur_eco_aktiv" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.eco/$args[0]", + "heating.circuits.0.operating.programs.eco/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK2-Solltemperatur_eco_aktiv" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.eco/$args[0]", + "heating.circuits.1.operating.programs.eco/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK3-Solltemperatur_eco_aktiv" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.eco/$args[0]", + "heating.circuits.2.operating.programs.eco/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "HK1-Solltemperatur_normal" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.normal/setTemperature", + "heating.circuits.0.operating.programs.normal/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1152,7 +1159,7 @@ sub vitoconnect_Set { elsif ( $opt eq "HK2-Solltemperatur_normal" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.normal/setTemperature", + "heating.circuits.1.operating.programs.normal/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1161,7 +1168,7 @@ sub vitoconnect_Set { elsif ( $opt eq "HK3-Solltemperatur_normal" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.normal/setTemperature", + "heating.circuits.2.operating.programs.normal/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1170,7 +1177,7 @@ sub vitoconnect_Set { elsif ( $opt eq "HK1-Solltemperatur_reduziert" ) { vitoconnect_action( $hash, - "heating.circuits.0.operating.programs.reduced/setTemperature", + "heating.circuits.0.operating.programs.reduced/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1179,7 +1186,7 @@ sub vitoconnect_Set { elsif ( $opt eq "HK2-Solltemperatur_reduziert" ) { vitoconnect_action( $hash, - "heating.circuits.1.operating.programs.reduced/setTemperature", + "heating.circuits.1.operating.programs.reduced/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); @@ -1188,58 +1195,58 @@ sub vitoconnect_Set { elsif ( $opt eq "HK3-Solltemperatur_reduziert" ) { vitoconnect_action( $hash, - "heating.circuits.2.operating.programs.reduced/setTemperature", + "heating.circuits.2.operating.programs.reduced/commands/setTemperature", "{\"targetTemperature\":$args[0]}", $name, $opt, @args ); return; } elsif ( $opt eq "HK1-Name" ) { - vitoconnect_action( $hash, "heating.circuits.0/setName", + vitoconnect_action( $hash, "heating.circuits.0/commands/setName", "{\"name\":\"@args\"}", $name, $opt, @args ); return; } elsif ( $opt eq "HK2-Name" ) { - vitoconnect_action( $hash, "heating.circuits.1/setName", + vitoconnect_action( $hash, "heating.circuits.1/commands/setName", "{\"name\":\"@args\"}", $name, $opt, @args ); return; } elsif ( $opt eq "HK3-Name" ) { - vitoconnect_action( $hash, "heating.circuits.2/setName", + vitoconnect_action( $hash, "heating.circuits.2/commands/setName", "{\"name\":\"@args\"}", $name, $opt, @args ); return; } elsif ( $opt eq "WW-einmaliges_Aufladen" ) { - vitoconnect_action( $hash, "heating.dhw.oneTimeCharge/$args[0]", + vitoconnect_action( $hash, "heating.dhw.oneTimeCharge/commands/$args[0]", "{}", $name, $opt, @args ); return; } elsif ( $opt eq "WW-Zirkulationspumpe_Zeitplan" ) { vitoconnect_action( $hash, - "heating.dhw.pumps.circulation.schedule/setSchedule", + "heating.dhw.pumps.circulation.schedule/commands/setSchedule", "{\"newSchedule\":@args}", $name, $opt, @args ); return; } elsif ( $opt eq "WW-Zeitplan" ) { - vitoconnect_action( $hash, "heating.dhw.schedule/setSchedule", + vitoconnect_action( $hash, "heating.dhw.schedule/commands/setSchedule", "{\"newSchedule\":@args}", $name, $opt, @args ); return; } elsif ( $opt eq "WW-Haupttemperatur" ) { vitoconnect_action( $hash, - "heating.dhw.temperature.main/setTargetTemperature", + "heating.dhw.temperature.main/commands/setTargetTemperature", "{\"temperature\":$args[0]}", $name, $opt, @args ); return; } elsif ( $opt eq "WW-Solltemperatur" ) { vitoconnect_action( $hash, - "heating.dhw.temperature/setTargetTemperature", + "heating.dhw.temperature/commands/commands/setTargetTemperature", "{\"temperature\":$args[0]}", $name, $opt, @args ); return; } elsif ( $opt eq "WW-Temperatur_2" ) { vitoconnect_action( $hash, - "heating.dhw.temperature.temp2/setTargetTemperature", + "heating.dhw.temperature.temp2/commands/setTargetTemperature", "{\"temperature\":$args[0]}", $name, $opt, @args ); return; } @@ -1252,7 +1259,7 @@ sub vitoconnect_Set { } vitoconnect_action( $hash, - "heating.operating.programs.holiday/schedule", + "heating.operating.programs.holiday/commands/schedule", "{\"start\":\"$args[0]\",\"end\":\"$end\"}", $name, $opt, @args ); @@ -1262,7 +1269,7 @@ sub vitoconnect_Set { my $start = ReadingsVal( $name, "Urlaub_Start", "" ); vitoconnect_action( $hash, - "heating.operating.programs.holiday/schedule", + "heating.operating.programs.holiday/commands/schedule", "{\"start\":\"$start\",\"end\":\"$args[0]\"}", $name, $opt, @args ); @@ -1270,14 +1277,14 @@ sub vitoconnect_Set { } elsif ( $opt eq "Urlaub_unschedule" ) { vitoconnect_action( $hash, - "heating.operating.programs.holiday/unschedule", + "heating.operating.programs.holiday/commands/unschedule", "{}", $name, $opt, @args ); return; } my $val = "unknown value $opt, choose one of update:noArg clearReadings:noArg " - . "password logResponseOnce:noArg " + . "password apiKey logResponseOnce:noArg " . "WW-einmaliges_Aufladen:activate,deactivate " . "WW-Zirkulationspumpe_Zeitplan:textField-long " . "WW-Zeitplan:textField-long " @@ -1403,9 +1410,19 @@ sub vitoconnect_getCode { my ($hash) = @_; my $name = $hash->{NAME}; my $isiwebpasswd = vitoconnect_ReadKeyValue( $hash, "passwd" ); - my $param = { + my $client_id = $hash->{apiKey}; + if ( !defined($client_id) ) { + Log3 $name, 1, "$name - set apiKey first"; + readingsSingleUpdate( $hash, "state", "Set apiKey to continue", 1 ); + return; + } + my $authorizeURL = 'https://iam.viessmann.com/idp/v2/authorize'; + + my $param = { url => "$authorizeURL?client_id=$client_id" - . "&scope=openid&redirect_uri=$callback_uri" + . "&redirect_uri=$callback_uri&" + . "code_challenge=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&" + . "&scope=IoT%20User%20offline_access" . "&response_type=code", hash => $hash, header => "Content-Type: application/x-www-form-urlencoded", @@ -1413,7 +1430,7 @@ sub vitoconnect_getCode { user => $hash->{user}, pwd => $isiwebpasswd, sslargs => { SSL_verify_mode => 0 }, - timeout => 10, + timeout => $hash->{timeout}, method => "POST", callback => \&vitoconnect_getCodeCallback }; @@ -1435,29 +1452,24 @@ sub vitoconnect_getCodeCallback { $response_body =~ /code=(.*)"/; $hash->{".code"} = $1; Log3 $name, 4, "$name - code: " . $hash->{".code"}; - if ( $hash->{".code"} ) { + if ( $hash->{".code"} && $hash->{".code"} ne "4") { $hash->{login} = "ok"; } else { $hash->{login} = "failure"; - } } else { - # Error code, type of error, error message Log3 $name, 1, "$name - An error occured: $err"; $hash->{login} = "failure"; } if ( $hash->{login} eq "ok" ) { + readingsSingleUpdate( $hash, "state", "login ok", 1 ); vitoconnect_getAccessToken($hash); } else { - readingsSingleUpdate( $hash, "state", "login failure", 1 ); - Log3 $name, 1, "$name - Login failure"; - - # neuen Timer starten in einem konfigurierten Interval. - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + readingsSingleUpdate( $hash, "state", "Login failure. Check password and apiKey", 1 ); + Log3 $name, 1, "$name - Login failure. Check password and apiKey"; } return; } @@ -1465,19 +1477,23 @@ sub vitoconnect_getCodeCallback { sub vitoconnect_getAccessToken { my ($hash) = @_; my $name = $hash->{NAME}; + my $client_id = $hash->{apiKey}; my $param = { - url => $token_url, + url => "https://iam.viessmann.com/idp/v2/token", hash => $hash, header => - "Content-Type: application/x-www-form-urlencoded;charset=utf-8", - data => "client_id=$client_id&client_secret=$client_secret&code=" - . $hash->{".code"} - . "&redirect_uri=$callback_uri&grant_type=authorization_code", + "Content-Type: application/x-www-form-urlencoded", + data => "grant_type=authorization_code" + . "&code_verifier=" . $client_secret + . "&client_id=$client_id" + . "&redirect_uri=$callback_uri" + . "&code=" . $hash->{".code"} , sslargs => { SSL_verify_mode => 0 }, method => "POST", - timeout => 10, + timeout => $hash->{timeout}, callback => \&vitoconnect_getAccessTokenCallback }; + #Log3 $name, 1, "$name - " . $param->{"data"}; HttpUtils_NonblockingGet($param); return; } @@ -1500,11 +1516,14 @@ sub vitoconnect_getAccessTokenCallback { my $access_token = $decode_json->{"access_token"}; if ( $access_token ne "" ) { $hash->{".access_token"} = $access_token; - Log3 $name, 4, "$name - Access Token: $access_token"; + $hash->{"refresh_token"} = $decode_json->{"refresh_token"}; + + Log3 $name, 4, "$name - Access Token: " . substr ($access_token, 0, 20) . "..."; vitoconnect_getGw($hash); } else { Log3 $name, 1, "$name - Access Token: nicht definiert"; + Log3 $name, 5, "$name - Received response: $response_body\n"; InternalTimer( gettimeofday() + $hash->{intervall}, "vitoconnect_GetUpdate", $hash ); } @@ -1517,15 +1536,75 @@ sub vitoconnect_getAccessTokenCallback { return; } +sub vitoconnect_getRefresh { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $client_id = $hash->{apiKey}; + my $param = { + url => "https://iam.viessmann.com/idp/v2/token", + hash => $hash, + header => + "Content-Type: application/x-www-form-urlencoded", + data => "grant_type=refresh_token" + . "&client_id=$client_id" + . "&refresh_token=" . $hash->{"refresh_token"} , + sslargs => { SSL_verify_mode => 0 }, + method => "POST", + timeout => $hash->{timeout}, + callback => \&vitoconnect_getRefreshCallback + }; + #Log3 $name, 1, "$name - " . $param->{"data"}; + HttpUtils_NonblockingGet($param); + return; +} + +sub vitoconnect_getRefreshCallback { + my ( $param, $err, $response_body ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + if ( $err eq "" ) { + Log3 $name, 4, "$name - getRefreshCallback went ok"; + Log3 $name, 5, "$name - Received response: $response_body\n"; + my $decode_json = eval { decode_json($response_body) }; + if ($@) { + Log3 $name, 1, "$name - JSON error while request: $@"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + return; + } + my $access_token = $decode_json->{"access_token"}; + if ( $access_token ne "" ) { + $hash->{".access_token"} = $access_token; + #$hash->{"refresh_token"} = $decode_json->{"refresh_token"}; + + Log3 $name, 4, "$name - Access Token: " . substr ($access_token, 0, 20) . "..."; + vitoconnect_getGw($hash); + } + else { + Log3 $name, 1, "$name - Access Token: nicht definiert"; + Log3 $name, 5, "$name - Received response: $response_body\n"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); # zurück zu getCode? + } + } + else { + Log3 $name, 1, "$name - getRefresh: An error occured: $err"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + } + return; +} + sub vitoconnect_getGw { my ($hash) = @_; my $name = $hash->{NAME}; my $access_token = $hash->{".access_token"}; my $param = { - url => "$apiURLBase$general", + url => $apiURL ."gateways", hash => $hash, header => "Authorization: Bearer $access_token", - timeout => 10, + timeout => $hash->{timeout} , sslargs => { SSL_verify_mode => 0 }, callback => \&vitoconnect_getGwCallback }; @@ -1541,8 +1620,8 @@ sub vitoconnect_getGwCallback { if ( $err eq "" ) { Log3 $name, 4, "$name - getGwCallback went ok"; Log3 $name, 5, "$name - Received response: $response_body\n"; - my $decode_json = eval { decode_json($response_body) }; - if ($@) { + my $items = eval { decode_json($response_body) }; + if ($@) { readingsSingleUpdate( $hash, "state", "JSON error while request: $@", 1 ); Log3 $name, 1, "$name - JSON error while request: $@"; @@ -1550,60 +1629,15 @@ sub vitoconnect_getGwCallback { "vitoconnect_GetUpdate", $hash ); return; } - if ( $hash->{".logResponseOnce"} ) { + if ( $hash->{".logResponseOnce"} ) { my $dir = path( AttrVal( "global", "logdir", "log" ) ); my $file = $dir->child("gw.json"); my $file_handle = $file->openw_utf8(); - $file_handle->print( Dumper($decode_json) ); - } - - #Baustelle!! - if ( AttrVal( $name, "vitoconnect_gw_readings", 0 ) eq "1" ) { - readingsBeginUpdate($hash); - my $e = "0"; - for my $entity ( @{ $decode_json->{entities} } ) { - my $FieldName = "entity" . $e; - readingsBulkUpdate( - $hash, - $FieldName . ".class", - $entity->{class}[0] - ); - my %Properties = %{ $entity->{properties} }; - my @Keys = keys(%Properties); - for my $Key (@Keys) { - readingsBulkUpdate( $hash, $FieldName . ".property." . $Key, - $Properties{$Key} ); - } - my $e2 = "0"; - for my $entity2 ( @{ $entity->{entities} } ) { - my $FieldName2 = $FieldName . ".entity" . $e2; - readingsBulkUpdate( - $hash, - $FieldName2 . ".class", - $entity2->{class}[0] - ); - - # my %Properties2 = %{ $entity2->{properties} }; - # my @Keys2 = keys(%Properties); - # for my $Key2 (@Keys2) { - # readingsBulkUpdate( $hash, $FieldName2 .".property" . $Key2, - # $Properties2{$Key2} ); - # } - $e2 = $e2 + 1; - } - $e = $e + 1; - } - readingsEndUpdate( $hash, 1 ); - } - - my $installation = $decode_json->{entities}[0]->{properties}->{id}; - Log3 $name, 4, "$name - installation: $installation"; - $hash->{".installation"} = $installation; - my $gw = - $decode_json->{entities}[0]->{entities}[0]->{properties}->{serial}; - Log3 $name, 4, "$name - gw: $gw"; - $hash->{".gw"} = $gw; - vitoconnect_getResource($hash); + $file_handle->print( Dumper($items) ); + } + $hash->{".gw"} = $items->{data}[0]->{serial}; + readingsSingleUpdate ( $hash, "gw", $response_body, 1); + vitoconnect_getInstallation($hash); } else { Log3 $name, 1, "$name - An error occured: $err"; @@ -1613,47 +1647,32 @@ sub vitoconnect_getGwCallback { return; } -sub vitoconnect_getResource { - my ($hash) = @_; +sub vitoconnect_getInstallation { + my ($hash) = @_; my $name = $hash->{NAME}; my $access_token = $hash->{".access_token"}; - my $installation = $hash->{".installation"}; - my $gw = $hash->{".gw"}; - - #Log3 $name, 4, "$name - access_token: $access_token"; - #Log3 $name, 4, "$name - installation: $installation"; - #Log3 $name, 4, "$name - gw: $gw"; - if ( $access_token eq "" || $installation eq "" || $gw eq "" ) { - vitoconnect_getCode($hash); - return; - } - my $dev=AttrVal( $name, 'vitoconnect_device', 0 ); - #Log3 $name, 4, "$name - dev: $dev"; - my $param = { - url => "$apiURLBase/operational-data/installations/$installation/" - . "gateways/$gw/devices/$dev/features/", + my $param = { + url => $apiURL ."installations", hash => $hash, header => "Authorization: Bearer $access_token", - timeout => 10, + timeout => $hash->{timeout} , sslargs => { SSL_verify_mode => 0 }, - callback => \&vitoconnect_getResourceCallback + callback => \&vitoconnect_getInstallationCallback }; HttpUtils_NonblockingGet($param); - return; + return; } -sub vitoconnect_getResourceCallback { - my ( $param, $err, $response_body ) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $file_handle2 = ""; +sub vitoconnect_getInstallationCallback { + my ( $param, $err, $response_body ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; - #AnalyzeCommand( $hash, "deletereading $name .*" ); if ( $err eq "" ) { - Log3 $name, 4, "$name - getResourceCallback went ok"; - Log3 $name, 5, "$name - Received response: $response_body\n"; - my $decode_json = eval { decode_json($response_body) }; - if ($@) { + Log3 $name, 4, "$name - getInstallationCallback went ok"; + Log3 $name, 5, "$name - Received response: $response_body"; + my $items = eval { decode_json($response_body) }; + if ($@) { readingsSingleUpdate( $hash, "state", "JSON error while request: $@", 1 ); Log3 $name, 1, "$name - JSON error while request: $@"; @@ -1661,8 +1680,201 @@ sub vitoconnect_getResourceCallback { "vitoconnect_GetUpdate", $hash ); return; } - my $items = $decode_json; - if ( !$items->{statusCode} eq "" ) { + if ( $hash->{".logResponseOnce"} ) { + my $dir = path( AttrVal( "global", "logdir", "log" ) ); + my $file = $dir->child("installation.json"); + my $file_handle = $file->openw_utf8(); + $file_handle->print( Dumper($items) ); + } + my $id = $items->{data}[0]->{id}; + if ($id == "") { + Log3 $name, 1, "$name - Something went wrong. Will retry"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + } + else { + $hash->{".installation"} = $items->{data}[0]->{id}; + readingsSingleUpdate ( $hash, "installation", $response_body, 1); + vitoconnect_getDevice($hash); + } + } + else { + Log3 $name, 1, "$name - An error occured: $err"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + } + return; +} + +sub vitoconnect_getDevice { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $access_token = $hash->{".access_token"}; + my $installation = $hash->{".installation"}; + my $gw = $hash->{".gw"}; + my $param = { + url => $apiURL ."installations/$installation/gateways/$gw/devices", + hash => $hash, + header => "Authorization: Bearer $access_token", + timeout => $hash->{timeout} , + sslargs => { SSL_verify_mode => 0 }, + callback => \&vitoconnect_getDeviceCallback + }; + HttpUtils_NonblockingGet($param); + return; +} + +sub vitoconnect_getDeviceCallback { + my ( $param, $err, $response_body ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + if ( $err eq "" ) { + Log3 $name, 4, "$name - getDeviceCallback went ok"; + Log3 $name, 5, "$name - Received response: $response_body\n"; + my $items = eval { decode_json($response_body) }; + if ($@) { + readingsSingleUpdate( $hash, "state", + "JSON error while request: $@", 1 ); + Log3 $name, 1, "$name - JSON error while request: $@"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + return; + } + if ( $hash->{".logResponseOnce"} ) { + my $dir = path( AttrVal( "global", "logdir", "log" ) ); + my $file = $dir->child("device.json"); + my $file_handle = $file->openw_utf8(); + $file_handle->print( Dumper($items) ); + } + readingsSingleUpdate ( $hash, "device", $response_body, 1); + vitoconnect_getFeatures($hash); + vitoconnect_getResource($hash); + } + else { + Log3 $name, 1, "$name - An error occured: $err"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + } + return; +} +sub vitoconnect_getFeatures { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $access_token = $hash->{".access_token"}; + my $installation = $hash->{".installation"}; + my $gw = $hash->{".gw"}; + my $dev = AttrVal( $name, 'vitoconnect_device', 0); + + Log3 $name, 4, "$name - getFeatures went ok"; + # Service Documents + + # Gateway features + my $param = { + url => $apiURL ."installations/$installation/gateways/$gw/features", + hash => $hash, + header => "Authorization: Bearer $access_token", + timeout => $hash->{timeout} , + sslargs => { SSL_verify_mode => 0 }, + }; + ( my $err, my $msg ) = HttpUtils_BlockingGet($param); + my $decode_json = eval { decode_json($msg) }; + if ( $err ne "" || $decode_json->{statusCode} ne "" ) { + Log3 $name, 1, "$name - Fehler während " + . "Gateway features: $err :: $msg"; + } + else { + readingsSingleUpdate ( $hash, "gw_features", $msg, 1); + } + + # installation features + my $param = { + url => $apiURL ."installations/$installation/features", + hash => $hash, + header => "Authorization: Bearer $access_token", + timeout => $hash->{timeout} , + sslargs => { SSL_verify_mode => 0 }, + }; + ( my $err, my $msg ) = HttpUtils_BlockingGet($param); + my $decode_json = eval { decode_json($msg) }; + if ( $err ne "" || $decode_json->{statusCode} ne "" ) { + Log3 $name, 1, "$name - Fehler während " + . "installation features: $err :: $msg"; + } + else { + readingsSingleUpdate ( $hash, "installation_features", $msg, 1); + } + + #Events +# my $param = { +# url => "https://api.viessmann.com/iot/v1/events-history/events", +# hash => $hash, +# header => "Authorization: Bearer $access_token", +# data => "gatewaySerial=$gw", +# method => "POST", +# timeout => $hash->{timeout} , +# sslargs => { SSL_verify_mode => 0 }, +# }; +# ( my $err, my $msg ) = HttpUtils_BlockingGet($param); +# my $decode_json = eval { decode_json($msg) }; +# if ( $err ne "" || $decode_json->{statusCode} ne "" ) { +# Log3 $name, 1, "$name - Fehler während " +# . "events: $err :: $msg"; +# } +# else { +# readingsSingleUpdate ( $hash, "events", $msg, 1); +# } + + return; +} + +sub vitoconnect_getResource { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $access_token = $hash->{".access_token"}; + my $installation = $hash->{".installation"}; + my $gw = $hash->{".gw"}; + my $dev = AttrVal( $name, 'vitoconnect_device', 0); + + Log3 $name, 4, "$name - enter getResource"; + Log3 $name, 4, "$name - access_token: " . substr ($access_token, 0, 20) . "..."; + Log3 $name, 4, "$name - installation: $installation"; + Log3 $name, 4, "$name - gw: $gw"; + if ( $access_token eq "" || $installation eq "" || $gw eq "" ) { + vitoconnect_getCode($hash); + return; + } + my $param = { + url => $apiURL . "installations/$installation/gateways/$gw/devices/$dev/features", + hash => $hash, + header => "Authorization: Bearer $access_token", + timeout => $hash->{timeout} , + sslargs => { SSL_verify_mode => 0 }, + callback => \&vitoconnect_getResourceCallback + }; + HttpUtils_NonblockingGet($param); + return; +} + +sub vitoconnect_getResourceCallback { + my ( $param, $err, $response_body ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + if ( $err eq "" ) { + Log3 $name, 4, "$name - getResourceCallback went ok"; + Log3 $name, 5, "$name - Received response: " . substr ($response_body, 0,100) . "..."; + my $items = eval { decode_json($response_body) }; + if ($@) { + readingsSingleUpdate( $hash, "state", + "JSON error while request: $@", 1 ); + Log3 $name, 1, "$name - JSON error while request: $@"; + InternalTimer( gettimeofday() + $hash->{intervall}, + "vitoconnect_GetUpdate", $hash ); + return; + } + + if ( !$items->{statusCode} eq "" ) { Log3 $name, 4, "$name - statusCode: $items->{statusCode} " . "errorType: $items->{errorType} " @@ -1674,12 +1886,10 @@ sub vitoconnect_getResourceCallback { "statusCode: $items->{statusCode} " . "errorType: $items->{errorType} " . "message: $items->{message} " - . "error: $items->{error}", - 1 - ); + . "error: $items->{error}", 1 ); if ( $items->{statusCode} eq "401" ) { # EXPIRED TOKEN - vitoconnect_getCode($hash); + vitoconnect_getRefresh($hash); return; } elsif ( $items->{statusCode} eq "404" ) { @@ -1707,119 +1917,69 @@ sub vitoconnect_getResourceCallback { else { Log3 $name, 1, "$name - unbekannter Fehler: " . "Bitte den Entwickler informieren!"; + Log3 $name, 1, + "$name - statusCode: $items->{statusCode} " + . "errorType: $items->{errorType} " + . "message: $items->{message} " + . "error: $items->{error}"; InternalTimer( gettimeofday() + $hash->{intervall}, "vitoconnect_GetUpdate", $hash ); return; } } - - readingsBeginUpdate($hash); - if ( $hash->{".logResponseOnce"} ) { + + if ( $hash->{".logResponseOnce"} ) { my $dir = path( AttrVal( "global", "logdir", "log" ) ); - my $file = $dir->child("entities.json"); + my $file = $dir->child("resource.json"); my $file_handle = $file->openw_utf8(); - - #$file_handle->print(Dumper($items)); - $file_handle->print( Dumper( @{ $items->{entities} } ) ); - my $file2 = $dir->child("actions.json"); - $file_handle2 = $file2->openw_utf8(); - } - - for my $item ( @{ $items->{entities} } ) { - my $FieldName = $item->{class}[0]; - Log3 $name, 5, "$name - FieldName $FieldName"; - my %Properties = %{ $item->{properties} }; - my @Keys = keys(%Properties); - for my $Key (@Keys) { - my $Reading = $RequestList->{ $FieldName . "." . $Key }; + $file_handle->print( Dumper($items) ); + } + + readingsBeginUpdate($hash); + foreach (@{$items->{data}}) { + my $feature = $_; + my $properties = $feature->{properties}; + foreach my $key ( keys %$properties ) { + my $Reading = $RequestList->{ $feature->{feature} . "." . $key }; if ( !defined($Reading) || AttrVal( $name, 'vitoconnect_raw_readings', 0 ) eq "1" ) - { $Reading = $FieldName . "." . $Key; } - - # Log3 $name, 5, "$name - Property: $FieldName $Key"; - my $Type = $Properties{$Key}{type}; - my $Value = $Properties{$Key}{value}; - if ( $Type eq "string" ) { - readingsBulkUpdate( $hash, $Reading, $Value ); - Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Value ($Type)"; - } - elsif ( $Type eq "number" ) { - readingsBulkUpdate( $hash, $Reading, $Value ); - Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Value ($Type)"; - } - elsif ( $Type eq "array" ) { + { $Reading = $feature->{feature}. "." . $key; } + my $Type = $properties->{$key}->{type}; + my $Value = $properties->{$key}->{value}; + if ( $Type eq "array" ) { if ( defined($Value) ) { - # my $Array = join( ",", @$Value ); - # Log3 $name, 1, "$name - Array Workaround for Property: $FieldName $Key "; if ( ref($Value) eq 'ARRAY' ) { my $Array = ( join( ",", @$Value ) ); readingsBulkUpdate( $hash, $Reading, $Array ); Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Array ($Type)"; + "$name - $Reading $Array ($Type)"; } else { - Log3 $name, 4, "$name - Array Workaround for Property: $FieldName $Key "; + Log3 $name, 4, "$name - Array Workaround for Property: $Reading"; } } } - elsif ( $Type eq "boolean" ) { - readingsBulkUpdate( $hash, $Reading, $Value ); - Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Value ($Type)"; - } elsif ( $Type eq "Schedule" ) { my $Result = encode_json($Value); readingsBulkUpdate( $hash, $Reading, $Result ); Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Result ($Type)"; - } - elsif ( $Type eq "ErrorListChanges" ) { - my $Result = encode_json($Value); - readingsBulkUpdate( $hash, $Reading, $Result ); - Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Result ($Type)"; + "$name - $Reading: $Result ($Type)"; } else { - readingsBulkUpdate( $hash, $Reading, "Unknown: $Type" ); + readingsBulkUpdate( $hash, $Reading, $Value ); Log3 $name, 5, - "$name - $FieldName" . ".$Key: $Value ($Type)"; + "$name - $Reading: $Value ($Type)"; } } - - if ( AttrVal( $name, 'vitoconnect_actions_active', 0 ) eq "1" ) { - my @actions = @{ $item->{actions} }; - if (@actions) { - if ( $hash->{".logResponseOnce"} ) { - $file_handle2->print( Dumper(@actions) ); - } - for my $action (@actions) { - my @fields = @{ $action->{fields} }; - my $Result = "action: "; - for my $field (@fields) { - $Result .= $field->{name} . " "; - } - readingsBulkUpdate( $hash, - $FieldName . "." . $action->{"name"}, $Result ); - } - } - } - } - $hash->{counter} = $hash->{counter} + 1; + } + readingsBulkUpdate( $hash, "state", "last update: " . TimeNow() . "" ); + readingsEndUpdate( $hash, 1 ); } else { - readingsSingleUpdate( $hash, "state", "An error occured: $err", 1 ); Log3 $name, 1, "$name - An error occured: $err"; - InternalTimer( gettimeofday() + $hash->{intervall}, + } + InternalTimer( gettimeofday() + $hash->{intervall}, "vitoconnect_GetUpdate", $hash ); - return; - } - readingsBulkUpdate( $hash, "state", "last update: " . TimeNow() . "" ); - readingsEndUpdate( $hash, 1 ); - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - $hash->{".logResponseOnce"} = 0; - return; + return; } sub vitoconnect_action { @@ -1827,26 +1987,25 @@ sub vitoconnect_action { my $access_token = $hash->{".access_token"}; my $installation = $hash->{".installation"}; my $gw = $hash->{".gw"}; - - my $dev=AttrVal( $name, 'vitoconnect_device', 0 ); + my $dev = AttrVal( $name, 'vitoconnect_device', 0 ); my $param = { - url => "$apiURLInst/$installation/gateways/$gw/" - . "devices/$dev/features/$feature", + url => $apiURLBase ."installations/$installation/gateways/$gw/" + . "devices/$dev/features/$feature", hash => $hash, header => "Authorization: Bearer $access_token\r\n" . "Content-Type: application/json", data => $data, - timeout => 10, + timeout => $hash->{timeout}, method => "POST", sslargs => { SSL_verify_mode => 0 }, }; Log3 $name, 4, "$name - url=$param->{url}"; Log3 $name, 4, "$name - data=$param->{data}"; ( my $err, my $msg ) = HttpUtils_BlockingGet($param); - - if ( $err ne "" || $msg ne "" ) { - Log3 $name, 1, "$name - set $name $opt @args: Fehler während der " - . "Befehlsausführung: $err :: $msg"; + my $decode_json = eval { decode_json($msg) }; + if ( $err ne "" || $decode_json->{statusCode} ne "" ) { + Log3 $name, 1, "$name - set $name $opt @args: Fehler während der " + . "Befehlsausführung: $err :: $msg"; } else { Log3 $name, 3, "$name - set $name $opt @args"; } return; @@ -1934,6 +2093,7 @@ sub vitoconnect_ReadKeyValue { thetrueavatar
You need the user and password from the ViCare App account.
+ Attention: This module is limited to one 'installation' per account. If you have two or more heaters use one viessmann account and device for each heater.
For details see: FHEM Wiki (german)