Hi all, for all looking for some starting point to get the Viessmann-API integrated into the RaspberryMatic, please find below the script I am using for my heat-pump AWB201.E10 2C. You need to set "debug" to true (1) to get the fundamental systemvariables created once and to add the code-challenge either from the examples given by Viessmann or create your own. Last, you need to provide your personal login-credentials here (at least once to get the refresh_token). The client-id you get from here after initial registration: https://app.developer.viessmann.com/ go with the standard recommendations and you are fine. Since I have the system up-and-running and did delete my credentials and some bits around this I could not test it from scratch. !-- HeatPump AllInOne -- v0.10 --
!_________________________________________________________________________________________________
! All-in-One script to generate tokens and get parameters of heat-pump form Viessmann-API
! ------------------------------------------------------------------------------------------
! VitoHeld
! ******************************************************************************************
!
! --------------------------------------------------------------------------------------------------
! https://documentation.viessmann.com/static/getting-started
! --------------------------------------------------------------------------------------------------
! set to 'true' for first execution to create system variables
boolean debug = true;
boolean initializeAllTokens = false;
boolean renewalAccessToken = false;
integer timeout = 5;
! access data Viessmann acount
! https://account.viessmann.com/
string client_id = ""; ! enter your client-id
string email = ""; ! enter your login email
string password = ""; ! enter your password
string AUTHORIZE_URL = "https://iam.viessmann.com/idp/v3/authorize";
string TOKEN_URL = "https://iam.viessmann.com/idp/v3/token";
string FEATURE_URL = "https://api.viessmann.com/iot/v1/equipment/installations";
string EVENT_URL = "https://api.viessmann.com/iot/v2/events-history/installations";
string REDIRECT_URI = "http://localhost:4200/";
string VIESSMANN_SCOPE = "IoT%20User%20offline_access";
! Code Challenge using S256
! https://developer.pingidentity.com/en/tools/pkce-code-generator.html
string code_verifier = ""; ! enter your personlized code-challenge here; on issues: use example provided by Viessmann
string code_challenge = ""; ! enter your personlized code-challenge here; on issues: use example provided by Viessmann
string stdout = "";
string stderr = "";
string func = "";
string access_token = "";
string refresh_token = "";
string api_error = "";
string hpStatus = "";
time now = system.Date("%F %X").ToTime();
! if not present: create system variables to store access credentials; also hpStatus - but not for features
if (debug) {
object svObjects = dom.GetObject(ID_SYSTEM_VARIABLES);
string svName = "hpIDInstallation"; object svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("Installation-ID Viessmann"); svObj.Name(svName); svObj.ValueType(ivtString); svObj.ValueSubType(istChar8859); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(""); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
svName = "hpIDGateway"; svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("Gateway-ID Viessmann"); svObj.Name(svName); svObj.ValueType(ivtString); svObj.ValueSubType(istChar8859); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(""); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
svName = "hpIDDevice"; svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("Device-ID Viessmann"); svObj.Name(svName); svObj.ValueType(ivtString); svObj.ValueSubType(istChar8859); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(""); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
svName = "hpTokenAccess"; svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("Access-Token Viessmann-API"); svObj.Name(svName); svObj.ValueType(ivtString); svObj.ValueSubType(istChar8859); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(""); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
svName = "hpTokenRefresh"; svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("Refresh-Token Viessmann-API"); svObj.Name(svName); svObj.ValueType(ivtString); svObj.ValueSubType(istChar8859); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(""); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
svName = "hpStatus"; svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("Status Wärmepumpe/API"); svObj.Name(svName); svObj.ValueType(ivtString); svObj.ValueSubType(istChar8859); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(""); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
svName = "sysStatus"; svObj = dom.GetObject(svName);
if (!svObj) { svObj = dom.CreateObject(OT_VARDP); svObjects.Add(svObj.ID()); svObj.DPInfo("CCU3 Status"); svObj.Name(svName); svObj.ValueType(ivtBinary); svObj.ValueSubType(istBool); svObj.ValueName0("Normalbetrieb"); svObj.ValueName1("Reboot"); svObj.ValueUnit(""); svObj.DPArchive(true); svObj.State(false); svObj.Internal(false); svObj.Visible(true); dom.RTUpdate(false); }
if (debug) { WriteLine("...sytem variables created/checked successfully."); }
}
! exit while rebooting... but reset deviceID in order to active token-generation
if (dom.GetObject(ID_SYSTEM_VARIABLES).Get("sysStatus")) { if (dom.GetObject(ID_SYSTEM_VARIABLES).Get("sysStatus").Value()) { dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDDevice").State(""); quit; } }
! get information if already available
string installationID = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDInstallation").Value();
string gatewayID = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDGateway").Value();
string deviceID = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDDevice").Value();
if (debug) { WriteLine("installationID: " # installationID # "\ngatewayID: " # gatewayID # "\n(deviceID: " # deviceID # ")"); }
! TTL = 3600s for access_token -> revoke script e.g. every 3333 seconds
string access_token = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenAccess").Value();
if (access_token <> "")
{
object sysvar = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenAccess");
time lastChange = sysvar.Timestamp();
integer atValidity = 3600 - (now - lastChange).ToInteger();
if (debug) { WriteLine("Access-Token last update: " # lastChange # " (still valid for: " # atValidity # " sec)"); }
}
elseif (debug) { WriteLine("No Access-Token found - will get generated..."); }
! TTL = 15552000 for refresh_token
string refresh_token = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenRefresh").Value();
if (refresh_token <> "")
{
sysvar = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenRefresh");
lastChange = sysvar.Timestamp();
integer rtValidity = 15552000 - (now - lastChange).ToInteger();
if (debug) { WriteLine("Refresh-Token last update: " # lastChange # " (still valid for: " # rtValidity # " sec)"); }
}
elseif (debug) { WriteLine("No Refresh-Token found - will get generated..."); }
! revoke tokens as required
if ((rtValidity < 333333) || (deviceID == "")) { initializeAllTokens = true; } elseif (atValidity < 333) { renewalAccessToken = true; }
! if not all tokens and IDs are given, generate or get them
if ((installationID == "") || (gatewayID == "") || (deviceID == "") || (access_token == "") || (refresh_token == "")) { initializeAllTokens = true; }
! initialize or renew tokens only if required
if (initializeAllTokens)
{
! Step 1: Authorization request
func = AUTHORIZE_URL # "?client_id=" # client_id # "&redirect_uri=" # REDIRECT_URI # "&scope=" # VIESSMANN_SCOPE # "&response_type=code&code_challenge_method=S256&code_challenge=" # code_challenge;
func = "curl -m " # timeout # " -u '" # email # ":" # password # "' '" # func # "'";
system.Exec(func, &stdout, &stderr);
! exit on error, provide API-return code as hpStatus
if (stdout.Find('{"viErrorId":') == 0) { api_error = stdout.Substr(stdout.Find(',"error":')+10, stdout.Length()-(stdout.Find(',"error":')+10)); api_error = "API-Error: " # api_error.Substr(0, api_error.Find("}")-1); dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); quit; }
string code = stdout.Substr(stdout.Find("?code=")+6, stdout.Length()-(stdout.Find("?code=")+6));
code = code.Substr(0, code.Find("\""));
if (debug) { WriteLine("Code = " # code); }
! Step 2: Authorization code exchange
func = "curl -m " # timeout # " -X POST '" # TOKEN_URL # "' -H 'Content-Type: application/x-www-form-urlencoded' -d 'client_id=" # client_id # "&redirect_uri=" # REDIRECT_URI # "&grant_type=authorization_code&code_verifier=" # code_verifier # "&code=" # code # "'";
system.Exec(func, &stdout, &stderr);
! exit on error, provide API-return code as hpStatus
if (stdout.Find('{"viErrorId":') == 0) { api_error = stdout.Substr(stdout.Find(',"error":')+10, stdout.Length()-(stdout.Find(',"error":')+10)); api_error = "API-Error: " # api_error.Substr(0, api_error.Find("}")-1); dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); quit; }
! TTL (time to life) of access_token is: 3600s = 1 hour
access_token = stdout.Substr(stdout.Find("access_token")+15, stdout.Length()-(stdout.Find("access_token")+15));
access_token = access_token.Substr(0, access_token.Find(",")-1);
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenAccess").State(access_token);
! TTL (time to life) of refresh_token is: 15552000s = 180 days
refresh_token = stdout.Substr(stdout.Find("refresh_token")+16, stdout.Length()-(stdout.Find("refresh_token")+16));
refresh_token = refresh_token.Substr(0, refresh_token.Find(",")-1);
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenRefresh").State(refresh_token);
if (debug) { WriteLine("Access-Token: " # access_token); WriteLine("Refresh-Token: " # refresh_token); }
}
elseif (renewalAccessToken)
{
! Refreshing an access token
! curl -X POST "https://iam.viessmann.com/idp/v3/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=refresh_token&client_id=my_oauth_client_id&refresh_token=083ed7fe41a619242df5978190fd11b5"
access_token = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenAccess").Value();
refresh_token = dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenRefresh").Value();
if (debug) { WriteLine("Refresh-Token: " # refresh_token); }
! renewal of access_token
func = "curl -m " # timeout # " -X POST '" # TOKEN_URL # "' -H 'Content-Type: application/x-www-form-urlencoded' -d 'client_id=" # client_id # "&grant_type=refresh_token&refresh_token=" # refresh_token # "'";
system.Exec(func, &stdout, &stderr);
! exit on error, provide API-return code as hpStatus
if (stdout.Find('{"viErrorId":') == 0) { api_error = stdout.Substr(stdout.Find(',"error":')+10, stdout.Length()-(stdout.Find(',"error":')+10)); api_error = "API-Error: " # api_error.Substr(0, api_error.Find("}")-1); dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); quit; }
! TTL (time to life) of access_token is: 3600s = 1 hour
access_token = stdout.Substr(stdout.Find("access_token")+15, stdout.Length()-(stdout.Find("access_token")+15));
access_token = access_token.Substr(0, access_token.Find(",")-1);
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpTokenAccess").State(access_token);
if (debug) { WriteLine("Access-Token: " # access_token); WriteLine("Refresh-Token: " # refresh_token); }
}
! ensure all IDs are available
if (deviceID == "")
{
! For using IoT features, you need essential three numbers of your heating device: installationID, gatewaySerial and deviceID.
! curl -X GET "https://api.viessmann.com/iot/v1/equipment/installations?includeGateways=true" -H "Authorization: Bearer {{Acces_Token}}"
func = "curl -m " # timeout # " -X GET '" # FEATURE_URL # "?includeGateways=true' -H 'content-type: application/json' -H 'authorization:Bearer " # access_token # "'";
system.Exec(func, &stdout, &stderr);
! exit on error, provide API-return code as hpStatus
if (stdout.Find('{"viErrorId":') == 0) { api_error = stdout.Substr(stdout.Find(',"error":')+10, stdout.Length()-(stdout.Find(',"error":')+10)); api_error = "API-Error: " # api_error.Substr(0, api_error.Find("}")-1); dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); quit; }
! get installationID
installationID = stdout.Substr(stdout.Find("id")+4, stdout.Length()-(stdout.Find("id")+4));
installationID = installationID.Substr(0, installationID.Find(","));
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDInstallation").State(installationID);
if (debug) { WriteLine("InstallationID = " # installationID); }
! get GatewayID
gatewayID = stdout.Substr(stdout.Find("gatewaySerial")+16, stdout.Length()-(stdout.Find("gatewaySerial")+16));
gatewayID = gatewayID.Substr(0, gatewayID.Find(",")-1);
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDGateway").State(gatewayID);
if (debug) { WriteLine("GatewayID = " # gatewayID); }
! curl -X GET https://api.viessmann.com/iot/v1/equipment/installations/{{installationID}}/gateways/{{gatewaySerial}}/devices
func = "curl -m " # timeout # " -X GET '" # FEATURE_URL # "/" # installationID # "/gateways/" # gatewayID # "/devices' -H 'content-type: application/json' -H 'authorization:Bearer " # access_token # "'";
system.Exec(func, &stdout, &stderr);
! exit on error, provide API-return code as hpStatus
if (stdout.Find('{"viErrorId":') == 0) { api_error = stdout.Substr(stdout.Find(',"error":')+10, stdout.Length()-(stdout.Find(',"error":')+10)); api_error = "API-Error: " # api_error.Substr(0, api_error.Find("}")-1); dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); quit; }
! get DeviceID
string deviceID = stdout.Substr(stdout.Find(',"id":"0","boilerSerial":"')+26, stdout.Length()-(stdout.Find(',"id":"0","boilerSerial":"')+26));
! worked until 01.02.2024: string deviceID = stdout.Substr(stdout.Find("boilerSerial")+15, stdout.Length()-(stdout.Find("boilerSerial")+15));
deviceID = deviceID.Substr(0, deviceID.Find(",")-1);
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDDevice").State(deviceID);
if (debug) { WriteLine("DeviceID = " # deviceID); }
}
! get overall status of heatpump
func = "curl -m " # timeout # " -X GET '" # FEATURE_URL # "' -H 'content-type: application/json' -H 'authorization:Bearer " # access_token # "'";
system.Exec(func, &stdout, &stderr);
! exit on error, provide API-return code as hpStatus
if (stdout.Find('{"viErrorId":') == 0) { if (stdout.Find(',"error":') > 0) { api_error = stdout.Substr(stdout.Find(',"error":')+10, stdout.Length()-(stdout.Find(',"error":')+10)); api_error = api_error.Substr(0, api_error.Find("}")-1); } elseif (stdout.Find(',"errorType":') > 0) { api_error = stdout.Substr(stdout.Find(',"errorType":')+14, stdout.Length()-(stdout.Find(',"errorType":')+14)); api_error = api_error.Substr(0, api_error.Find('",')); } else { api_error = "unknown"; } api_error = "API-Error: " # api_error; if (api_error.Find("TOKEN") > 0) { dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpIDDevice").State(""); } if (debug) { WriteLine("hpStatus := " # api_error); } dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); quit; }
if (stdout.Find('{"cursor":{"next":') <> 0) { api_error = "unexpected content returned by API-call"; if (stderr.Find("Operation timed out after") > 0) { api_error = api_error # " (timeout/vi-maintenance)"; } dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(api_error); if (debug) { WriteLine(api_error); } quit; }
hpStatus = stdout.Substr(stdout.Find("aggregatedStatus")+19, stdout.Length()-(stdout.Find("aggregatedStatus")+19));
hpStatus = hpStatus.Substr(0, hpStatus.Find(",")-1);
dom.GetObject(ID_SYSTEM_VARIABLES).Get("hpStatus").State(hpStatus);
if (debug) { WriteLine("hpStatus = " # hpStatus); }
... Mehr anzeigen