[VitoConnect] Getting values/sending commands via the "new" API by using rules@

As announced Viessmann will stop the “old” API, since the code code provided by @thetrueavatar is using the “old” API this solution will stop working on 15th July!

Using the “new” API solely by rules is possible!

You need an account on the Viessmann Developer Portal, I think your credentials from the ViCare App should do.

Getting an API Key

Do all steps as written under the Authentication Tab on the browser ( I used Postman to create the http post and get requests). If you have requested to Refresh your API once, you can use the RefreshToken in an openHAB rule that runs every hour to refresh your API key. My used rule (ECMA) for this looks like:

Edit: NOTE: The code posted by @AndreasBerz is better, I am going to use his code!

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var Transform = Java.type("org.openhab.core.transform.actions.Transformation");
var HTTP=Java.type("org.openhab.core.model.script.actions.HTTP");
var client_id = "myClientID";
var refresh_token=items["Viessmann_RefreshToken"];
var returnvalue = HTTP.sendHttpPostRequest("https://iam.viessmann.com/idp/v2/token", "application/x-www-form-urlencoded","grant_type=refresh_token&client_id=" + client_id + "&refresh_token=" + refresh_token, 10*1000);
if (returnvalue.indexOf("error") !== -1){
  logger.info("Refreshing the Token failed!!")
}else {
  var token = Transform.transform ("JSONPATH", "$.access_token", returnvalue);
  events.postUpdate('Viessmann_Token',token );
  token = Transform.transform ("JSONPATH", "$.refresh_token", returnvalue);
  events.postUpdate('Viessmann_RefreshToken',token );
};

I used the items Viessmann_Token and Viessmann_RefreshToken to store both of them and set RefreshToken-item to the token before starting the rule! The RefreshToken will stay the same all the time!

EDIT:
The statement above regarding the never changing RefreshToken has been proven wrong, it has to be renewed every 180 days doing the WHOLE AUTHENTIFICATION process!

Getting values

Getting values is working by posting a HTTPGetRequest using an url like:

var url = "https://api.viessmann.com/iot/v1/equipment/installations/"+ installationID +"/gateways/" +gatewaySerial + "/devices/0/features?regex=heating.(sensors.temperature.outside%7Cdhw.(oneTimeCharge%7Csensors.temperature.hotWaterStorage%7Cpumps.circulation))%7Cventilation.operating.programs.active"

The vars installationID and gatewaySerial have to be set according your system. My system uses a single device (hence the 0). The regex string is calling for the values of:

  • heating.sensors.temperature.outside
  • heating.dhw.oneTimeCharge
  • heating.dhw.sensors.temperature.hotWaterStorage
  • heating.dhw.pumps.circulation
  • ventilation.operating.programs.active

“%7C” is used instead of the “|” sign!

The token has to be set to the header like:

var token=items["Viessmann_Token"];
var headers = [];
headers["Authorization"] = "Bearer "+ token;

The full GetRequest looks like:
var returnstring = HTTP.sendHttpGetRequest(url, headers, 10*1000);

To get the values I parse the returned JSON returnstring like:

    OutsideTemperature = returnstring.data[0].properties.value.value;
    HotWaterStorageTemp= returnstring.data[3].properties.value.value;
    IsOneTimeCharge = returnstring.data[4].properties.active.value;

Sending Commands

In order to send a command (in this example activating oneTimeCharge) I use the following code (headers and ID vars as above):

var url ="https://api.viessmann.com/iot/v1/equipment/installations/"+ installationID +"/gateways/" +gatewaySerial + "/devices/0/features/heating.dhw.oneTimeCharge/commands/activate";
var returnvalue = HTTP.sendHttpPostRequest(url,"application/json", "{}",headers,2*1000);

The “{}” is an empty body since activating “oneTimeCharge” does not need further data.

2 Likes

A small word about the old API being discontinued. It’s very unlikely to happen since ViCare is still based on this api. From the discussion I had on Viessmann board, it seems that it’s only for the new iot api that some change will be done with API key.
So the 15th of july won’t be then end of the world at all.
However, I have start migrating the code to the new api. I though Viessmann would provide an openapi specification and that a custom client won’t be needed anyomre.
However this is not currently the case.
I have then implemented a 2.2.0-SNAPSHOT with the same interface but that goes on the new iot api.
Ir requires user to set an api key generated from the developper portal(see Viessmann-Api/README-en.md at develop · thetrueavatar/Viessmann-Api · GitHub )
It’s possible to read value. I didn’t test writing yet but according to Viessmann this should still work.
However, at the moment there are a lot of feature that are missing on the iot api.
Viessmann is adding the missing feature on demand.

Of course, it’s only for information and the new iot data structure is far more easier to use than the ViCare one. So it might be easier right now to use direct call from openhab instead of using a bridge to a php client.

1 Like

Instead of installing the application Postman on my PC, I added the extension “Tabbed Postman - REST Client” to my Chrome Browser. This will do the job for receiving a refresh_token also.

1 Like

Based on the work of @opus I wrote a Rule-DSL file to do the following 3 tasks:

-Updating the refresh_token in 1h intervall
-Updating the heating items in 10min intervall
-Changing operation mode of one heating circuit (as an example)

// Global variables/constants
val String createdClientId = "<your clientID>"
val String createdRefreshToken = "<your refresh_token>"
val String installationId = "<your InstallationID>"
val String gatewaySerial = "<your gateway serial>"
val String deviceId = "0"
val java.util.concurrent.locks.ReentrantLock apiLock = new java.util.concurrent.locks.ReentrantLock


// Update the authorization tokens every 1 hour
rule "HEAT1_UpdateAuthorizationTokens"
when
    System started or
    Time cron "0 0 0/1 1/1 * ? *"
then
    var String currentRefreshToken = createdRefreshToken
    if (HEAT1_RefreshToken.state != NULL)
    {
        currentRefreshToken = HEAT1_RefreshToken.state.toString()
    }
    try
    {
        apiLock.lock()

        var String url = "https://iam.viessmann.com/idp/v2/token"
        var String contentType = "application/x-www-form-urlencoded"
        var String content = "grant_type=refresh_token&client_id=" + createdClientId + "&refresh_token=" + currentRefreshToken
        var String jsonData = sendHttpPostRequest(url, contentType, content, 10*1000)
        if (jsonData.isNullOrEmpty)
        {
            logError("viessmann.rules", "Cannot update the authorization tokens. JSON-data is empty.")
            return;
        }
        if (jsonData.contains("\"access_token\"") == false || jsonData.contains("\"refresh_token\"") == false)
        {
            logError("viessmann.rules", "Cannot update the authorization tokens. Error: " + jsonData)
            return;
        }

        logInfo("viessmann.rules", "Update Viessmann authorization tokens every 1 hour")

        var String newAccessToken = transform("JSONPATH", "$.access_token", jsonData)
        if (newAccessToken.isNullOrEmpty == false) HEAT1_AccessToken.postUpdate(newAccessToken)
        var String newRefreshToken = transform("JSONPATH", "$.refresh_token", jsonData)
        if (newRefreshToken.isNullOrEmpty == false) HEAT1_RefreshToken.postUpdate(newRefreshToken)
    }
    finally
    {
        apiLock.unlock()
    }
end


// Read Viessmann values every 10 minutes via HTTP-Get
rule "HEAT1_GetValuesJsonApi"
when
    Time cron "0 0/10 * 1/1 * ? *"
then
    if (HEAT1_AccessToken.state == NULL)
    {
        logError("viessmann.rules", "Cannot get values from Viessmann IoT API. Access-Token is unavailable.")
        return;
    }
    if (apiLock.tryLock(5, java.util.concurrent.TimeUnit.SECONDS) == false)
    {
        logWarn("viessmann.rules", "Drop getting values from Viessmann IoT API. API is locked at the moment.")
        return;
    }

    var String jsonData = ""
    try
    {
        var String url = "https://api.viessmann.com/iot/v1/equipment/installations/" + installationId + "/gateways/" + gatewaySerial + "/devices/" + deviceId + "/features"
        val headers = newHashMap("Authorization" -> "Bearer " + HEAT1_AccessToken.state.toString(), "WWW-Authenticate"-> "Basic")
        jsonData = sendHttpGetRequest(url, headers, 10*1000)
        if (jsonData.isNullOrEmpty)
        {
            logError("viessmann.rules", "Cannot get values from Viessmann IoT API. JSON-data is empty.")
            return;
        }
        if (jsonData.contains("\"data\"") == false || jsonData.contains("\"feature\"") == false)
        {
            logError("viessmann.rules", "Cannot get values from Viessmann IoT API. Error: " + jsonData)
            return;
        }
    }
    finally
    {
        apiLock.unlock()
    }

    logInfo("viessmann.rules", "Updating Viessmann data values every 10 minutes")

    var String burnerActive = transform("JSONPATH", "$.data[?(@.feature == 'heating.burner')].properties.active.value", jsonData)
    if (burnerActive.contains("true")) HEAT1_BurnerActive.postUpdate(ON) else if (burnerActive.contains("false")) HEAT1_BurnerActive.postUpdate(OFF)
    var String outsideTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.sensors.temperature.outside')].properties.value.value", jsonData)
    if (outsideTemp.isNullOrEmpty == false) HEAT1_OutsideTemp.postUpdate(outsideTemp)
    var String boilerTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.boiler.sensors.temperature.main')].properties.value.value", jsonData)
    if (boilerTemp.isNullOrEmpty == false) HEAT1_BoilerTemp.postUpdate(boilerTemp)
    var String hotWaterTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.dhw.sensors.temperature.hotWaterStorage')].properties.value.value", jsonData)
    if (hotWaterTemp.isNullOrEmpty == false) HEAT1_HotWaterTemp.postUpdate(hotWaterTemp)
    var String solarCollectorTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.sensors.temperature.collector')].properties.value.value", jsonData)
    if (solarCollectorTemp.isNullOrEmpty == false) HEAT1_SolarCollectorTemp.postUpdate(solarCollectorTemp)
    var String solarDhwTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.sensors.temperature.dhw')].properties.value.value", jsonData)
    if (solarDhwTemp.isNullOrEmpty == false) HEAT1_SolarDhwTemp.postUpdate(solarDhwTemp)
    var String solarPumpActive = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.pumps.circuit')].properties.status.value", jsonData)
    if (solarPumpActive.contains("on")) HEAT1_SolarPumpActive.postUpdate(ON) else if (solarPumpActive.contains("off")) HEAT1_SolarPumpActive.postUpdate(OFF)

    var String modeCCT1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.1.operating.modes.active')].properties.value.value", jsonData)
    if (modeCCT1.isNullOrEmpty == false) HEAT1_ModeCCT1.postUpdate(modeCCT1)
    var String programCCT1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.1.operating.programs.active')].properties.value.value", jsonData)
    if (programCCT1.isNullOrEmpty == false) HEAT1_ProgramCCT1.postUpdate(programCCT1)
    var String roomTempCCT1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.1.sensors.temperature.room')].properties.value.value", jsonData)
    if (roomTempCCT1.isNullOrEmpty == false) HEAT1_RoomTempCCT1.postUpdate(roomTempCCT1)
    var String supplyTempCCT1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.1.sensors.temperature.supply')].properties.value.value", jsonData)
    if (supplyTempCCT1.isNullOrEmpty == false) HEAT1_SupplyTempCCT1.postUpdate(supplyTempCCT1)
    var String normalTempCCT1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.1.operating.programs.normal')].properties.temperature.value", jsonData)
    if (normalTempCCT1.isNullOrEmpty == false) HEAT1_NormalTempCCT1.postUpdate(normalTempCCT1)
    var String reducedTempCCT1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.1.operating.programs.reduced')].properties.temperature.value", jsonData)
    if (reducedTempCCT1.isNullOrEmpty == false) HEAT1_ReducedTempCCT1.postUpdate(reducedTempCCT1)

    var String modeCCT2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.2.operating.modes.active')].properties.value.value", jsonData)
    if (modeCCT2.isNullOrEmpty == false) HEAT1_ModeCCT2.postUpdate(modeCCT2)
    var String programCCT2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.2.operating.programs.active')].properties.value.value", jsonData)
    if (programCCT2.isNullOrEmpty == false) HEAT1_ProgramCCT2.postUpdate(programCCT2)
    var String roomTempCCT2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.2.sensors.temperature.room')].properties.value.value", jsonData)
    if (roomTempCCT2.isNullOrEmpty == false) HEAT1_RoomTempCCT2.postUpdate(roomTempCCT2)
    var String supplyTempCCT2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.2.sensors.temperature.supply')].properties.value.value", jsonData)
    if (supplyTempCCT2.isNullOrEmpty == false) HEAT1_SupplyTempCCT2.postUpdate(supplyTempCCT2)
    var String normalTempCCT2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.2.operating.programs.normal')].properties.temperature.value", jsonData)
    if (normalTempCCT2.isNullOrEmpty == false) HEAT1_NormalTempCCT2.postUpdate(normalTempCCT2)
    var String reducedTempCCT2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.2.operating.programs.reduced')].properties.temperature.value", jsonData)
    if (reducedTempCCT2.isNullOrEmpty == false) HEAT1_ReducedTempCCT2.postUpdate(reducedTempCCT2)

    var String powerConsumDay = transform("JSONPATH", "$.data[?(@.feature == 'heating.power.consumption.total')].properties.day.value[0]", jsonData)
    if (powerConsumDay.isNullOrEmpty == false) HEAT1_PowerConsumptionDay.postUpdate(powerConsumDay)
    var String powerConsumWeek = transform("JSONPATH", "$.data[?(@.feature == 'heating.power.consumption.total')].properties.week.value[0]", jsonData)
    if (powerConsumWeek.isNullOrEmpty == false) HEAT1_PowerConsumptionWeek.postUpdate(powerConsumWeek)
    var String powerConsumMonth = transform("JSONPATH", "$.data[?(@.feature == 'heating.power.consumption.total')].properties.month.value[0]", jsonData)
    if (powerConsumMonth.isNullOrEmpty == false) HEAT1_PowerConsumptionMonth.postUpdate(powerConsumMonth)
    var String powerConsumYear = transform("JSONPATH", "$.data[?(@.feature == 'heating.power.consumption.total')].properties.year.value[0]", jsonData)
    if (powerConsumYear.isNullOrEmpty == false) HEAT1_PowerConsumptionYear.postUpdate(powerConsumYear)

    var String dhwGasConsumDay = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.dhw')].properties.day.value[0]", jsonData)
    var String heatGasConsumDay = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.heating')].properties.day.value[0]", jsonData)
    if (dhwGasConsumDay.isNullOrEmpty == false && heatGasConsumDay.isNullOrEmpty == false) HEAT1_GasConsumptionDay.postUpdate(Float::parseFloat(dhwGasConsumDay) + Float::parseFloat(heatGasConsumDay))
    var String dhwGasConsumWeek = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.dhw')].properties.week.value[0]", jsonData)
    var String heatGasConsumWeek = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.heating')].properties.week.value[0]", jsonData)
    if (dhwGasConsumWeek.isNullOrEmpty == false && heatGasConsumWeek.isNullOrEmpty == false) HEAT1_GasConsumptionWeek.postUpdate(Float::parseFloat(dhwGasConsumWeek) + Float::parseFloat(heatGasConsumWeek))
    var String dhwGasConsumMonth = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.dhw')].properties.month.value[0]", jsonData)
    var String heatGasConsumMonth = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.heating')].properties.month.value[0]", jsonData)
    if (dhwGasConsumMonth.isNullOrEmpty == false && heatGasConsumMonth.isNullOrEmpty == false) HEAT1_GasConsumptionMonth.postUpdate(Float::parseFloat(dhwGasConsumMonth) + Float::parseFloat(heatGasConsumMonth))
    var String dhwGasConsumYear = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.dhw')].properties.year.value[0]", jsonData)
    var String heatGasConsumYear = transform("JSONPATH", "$.data[?(@.feature == 'heating.gas.consumption.heating')].properties.year.value[0]", jsonData)
    if (dhwGasConsumYear.isNullOrEmpty == false && heatGasConsumYear.isNullOrEmpty == false) HEAT1_GasConsumptionYear.postUpdate(Float::parseFloat(dhwGasConsumYear) + Float::parseFloat(heatGasConsumYear))

    var String solarPowerProdDay = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.power.production')].properties.day.value[0]", jsonData)
    if (solarPowerProdDay.isNullOrEmpty == false) HEAT1_SolarPowerProductionDay.postUpdate(solarPowerProdDay)
    var String solarPowerProdWeek = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.power.production')].properties.week.value[0]", jsonData)
    if (solarPowerProdWeek.isNullOrEmpty == false) HEAT1_SolarPowerProductionWeek.postUpdate(solarPowerProdWeek)
    var String solarPowerProdMonth = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.power.production')].properties.month.value[0]", jsonData)
    if (solarPowerProdMonth.isNullOrEmpty == false) HEAT1_SolarPowerProductionMonth.postUpdate(solarPowerProdMonth)
    var String solarPowerProdYear = transform("JSONPATH", "$.data[?(@.feature == 'heating.solar.power.production')].properties.year.value[0]", jsonData)
    if (solarPowerProdYear.isNullOrEmpty == false) HEAT1_SolarPowerProductionYear.postUpdate(solarPowerProdYear)
end


// Betriebsart von Heizkreis HK2 umschalten via HTTP-Post
rule "HEAT1_ModeCCT1-CommandApi"
when
    Item HEAT1_ModeCCT1 received command
then
    if (HEAT1_AccessToken.state == NULL)
    {
        logError("viessmann.rules", "Cannot change operation mode of CCT1 by Viessmann IoT API. Access-Token is unavailable.")
        return;
    }
    if (apiLock.tryLock(5, java.util.concurrent.TimeUnit.SECONDS) == false)
    {
        logWarn("viessmann.rules", "Drop changing operation mode of CCT1 by Viessmann IoT API. API is locked at the moment.")
        return;
    }

    try
    {
        var String url = "https://api.viessmann.com/iot/v1/equipment/installations/" + installationId + "/gateways/" + gatewaySerial + "/devices/" + deviceId + "/features/heating.circuits.1.operating.modes.active/commands/setMode"
        var String contentType = "application/json"
        var String content = ""
        val headers = newHashMap("Authorization" -> "Bearer " + HEAT1_AccessToken.state.toString(), "WWW-Authenticate"-> "Basic")

        switch(receivedCommand)
        {
            case "dhw",
            case "dhwAndHeating",
            case "forcedNormal",
            case "forcedReduced":
            {
                logInfo("viessmann.rules", "Changing operation mode of CCT1 by mode: " + receivedCommand.toString())
                content = "{\"mode\": \"" + receivedCommand.toString() + "\"}"
            }
            default:
            {
                logError("viessmann.rules", "Changing operation mode of CCT1 dropped because of unknown mode: " + receivedCommand.toString())
                return;
            }
        }
        
        sendHttpPostRequest(url, contentType, content, headers, 10*1000)
    }
    finally
    {
        apiLock.unlock()
    }
end

Of course, you have to change the global variables createdClientId, createdRefreshToken, installationId, gatewaySerial to your individual values first. For determine these values, read the API-Documentation of Viessmann.

HEAT1_BurnerActive, HEAT1_SolarPumpActive are switch items.
HEAT1_ModeCCT1, HEAT1_ProgramCCT1 are string items (using MAP transformation)
The rest are Number items (Number:Temperature)

This Rule-DSL “parses” JSON data representing data of a certain HVAC assembly!
In this case, a wall mounted gas heating Vitodens 300-W with two heating circuits and solar heat panels. If you own a different HVAC device like heat pump, oil burner, ventilation, … the JSON data will be different and you have to change the code reading the JSON addressing different/other data points.
Its more an inspiration how to do it.

2 Likes

I used the webclient, no application installed.

touchez!
Nothing installed nor a browser extension is best of all :+1:

BTW:
I like your way to get the correct JSON-array element. The way I posted above will fail if the return is changed in order ( which it already did!). Actually I’m using a switch case to check which array-element I’m checking, yours is better! Will convert my rule if I have time.

Thank you very much guys, you’re awesome! I was just trying to implement opus’ rules and then I’ve luckily seen Andreas’ post. Which I must say were easier to implement. But anyway, kudos for both of you for figuring out this stuff.
I want to add an easy to copy list of the items used in the rules by Andreas’.

Switch HEAT1_BurnerActive
Switch HEAT1_SolarPumpActive

String HEAT1_RefreshToken
String HEAT1_AccessToken
String HEAT1_ModeCCT1
String HEAT1_ModeCCT2
String HEAT1_ProgramCCT1
String HEAT1_ProgramCCT2

Number HEAT1_OutsideTemp
Number HEAT1_BoilerTemp
Number HEAT1_HotWaterTemp
Number HEAT1_SolarCollectorTemp
Number HEAT1_SolarDhwTemp
Number HEAT1_RoomTempCCT1
Number HEAT1_RoomTempCCT2
Number HEAT1_SupplyTempCCT1
Number HEAT1_SupplyTempCCT2
Number HEAT1_NormalTempCCT1
Number HEAT1_NormalTempCCT2
Number HEAT1_ReducedTempCCT1
Number HEAT1_ReducedTempCCT2
1 Like

:+1: I’m going to use this code as well. Added a note in the OP.

Unfortunately, the old api is not working anymore this morning. By digging into the ViCare application it seems that it’s also using the new iot api. The good news is that if a feature works on ViCare it will also works with the IOT integration.
I have then released a 2.0.0 version of the php client which is using the IOT api:

1 Like

Connection established according to the information heren and the rule works perfectly. Thanks for writing that rule Andreas !
Now i “only” need to find out the parameters for my heatpump :wink:

EDIT: I got it. Have to install the transformation Service JsonPath Transformation vie GUI Preferences > AddOns

Original Post: Thanks for the Code, I was able to set it up but now have a problem on the finish line.

[b.core.model.script.actions.BusEvent] - Cannot convert '{"data":[{"properties":{"value":{"type":"number","value":0,"unit":""}},"commands":{},"components":["levels"],"apiVersion":1,"uri":"https://api.viessmann-platform.io/iot/v1/equipment/installations/......all the features here......"timestamp":"2021-07-22T12:55:58.099Z","isEnabled":true,"isReady":true,"deviceId":"0"}]}' to a state type which item 'Sensor_WaermepumpeAPI_Aussentemperatur' accepts: [DecimalType, QuantityType, UnDefType].

So it seems that my openHab can’t do the transform of the JSON Data.

I created a rules file in the openhab/rules folder.

I also tried pasting the script in a DSL-Rule in the GUI, but that gives me the following error:

[ERROR] [internal.handler.ScriptActionHandler] ....

I never really had luck with the DSL rules in the GUI as the log statements are nearly useless… :frowning:

My System ist still on openHAB 3.0.1, don’t know if this is a Problem, will try if updating helps.

To be continued.

I’m going to list here my rule for data in connection with a Viessmann VitoCal 300A heat pump.

If anyone has additions i couldnt find yet please feel free to comment.

Items: 

Read Data to be included into the rule of Andreas:

    if (outsideTemp.isNullOrEmpty == false) Vito_OutsideTemp.postUpdate(outsideTemp)
    var String returnTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.sensors.temperature.return')].properties.value.value", jsonData)
    if (returnTemp.isNullOrEmpty == false) VitoCal_Ruecklauftemperatur.postUpdate(returnTemp)
    var String supplyTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.circuits.0.sensors.temperature.supply')].properties.value.value", jsonData)
    if (supplyTemp.isNullOrEmpty == false) VitoCal_Vorlauftemperatur.postUpdate(supplyTemp)
     var String hotWaterTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.dhw.sensors.temperature.hotWaterStorage')].properties.value.value", jsonData)
    if (hotWaterTemp.isNullOrEmpty == false) Vito_HotWaterTemp.postUpdate(hotWaterTemp)
    var String hotWaterTopTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.dhw.sensors.temperature.hotWaterStorage.top')].properties.value.value", jsonData)
    if (hotWaterTopTemp.isNullOrEmpty == false) VitoCal_HotWaterTopTemp.postUpdate(hotWaterTopTemp)
    var String pumpsCirculation = transform("JSONPATH", "$.data[?(@.feature == 'heating.dhw.pumps.circulation')].properties.status.value", jsonData)
    if (pumpsCirculation.isNullOrEmpty == false) Vito_CircPump.postUpdate(pumpsCirculation)
    var String statHours = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.starts.value", jsonData)
    if (statHours.isNullOrEmpty == false) VitoStat_Starts.postUpdate(statHours)
    var String statStarts = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.hours.value", jsonData)
    if (statStarts.isNullOrEmpty == false) VitoStat_hours.postUpdate(statStarts)
    var String statHoursLoadClass1 = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.hoursLoadClassOne.value", jsonData)
    if (statHoursLoadClass1.isNullOrEmpty == false) VitoStat_hlcl1.postUpdate(statHoursLoadClass1)
    var String statHoursLoadClass2 = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.hoursLoadClassTwo.value", jsonData)
    if (statHoursLoadClass2.isNullOrEmpty == false) VitoStat_hlcl2.postUpdate(statHoursLoadClass2)
    var String statHoursLoadClass3 = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.hoursLoadClassThree.value", jsonData)
    if (statHoursLoadClass3.isNullOrEmpty == false) VitoStat_hlcl3.postUpdate(statHoursLoadClass3)
    var String statHoursLoadClass4 = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.hoursLoadClassFour.value", jsonData)
    if (statHoursLoadClass4.isNullOrEmpty == false) VitoStat_hlcl4.postUpdate(statHoursLoadClass4)
    var String statHoursLoadClass5 = transform("JSONPATH", "$.data[?(@.feature == 'heating.compressors.0.statistics')].properties.hoursLoadClassFive.value", jsonData)
    if (statHoursLoadClass5.isNullOrEmpty == false) VitoStat_hlcl5.postUpdate(statHoursLoadClass5)

General Hint to find out parameters of your HVAC device:
In Rule “HEAT1_GetValuesJsonApi” you can temporarily modify the line…
if (jsonData.contains(“data”) == false || jsonData.contains(“feature”) == false)
…into…
if (jsonData.contains(“xata”) == false || jsonData.contains(“feature”) == false)
…this will lead the code execution into error handling (when rule is executed).

The whole json data received, is pasted into the openHAB logfile. open SSH shell on Windows-PC and log into computer running openHAB. Run command “sudo openhab-cli showlogs” or just “openhab-cli showlogs” if you are root. Copy the whole json data from command shell into clipboard and paste it into Texteditor and save it as “data.json”. I suggest to use Texteditor Notepad++ with Plugin “JSON Viewer” installed. With “JSON Viewer” you can indent (or nice) the JSON data text and there is also a hierarchical view. Then you can explore which data are available for your device.

Or one could use a logInfo line in order to print the whole received jsondata into the logs.

Hi. During the trial attempt, the message “This site cannot be reached”
Where am I making a mistake?

Hi. I coped with the token, but now I get such a warning very often. What is caused by. I have OpenHab 2.5.

2021-08-24 14:40:05.059 [WARN ] [arthome.model.script.viessmann.rules] - Drop getting values from Viessmann IoT API. API is locked at the moment.

The warning is preceded by an error:

2021-08-24 14:20:00.078 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'HEAT1_GetValuesJsonApi': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.HTTP.sendHttpGetRequest(java.lang.String,int) on instance: null

According the first message I would assume that your Key is temporary blocked(probably because you did to much API calls in a timeframe).
The limit is no more then 1450 calls in 24hrs!

I refresh the token once an hour and make one query every 10 minutes. The message about blockage appears the first time you try to make a role that reads the outside temperature. I think I have a bug in the role code somewhere. In the postman service, the temperature reading works. Where could the error be?

My rule:

// Global variables/constants
val String createdClientId = "XXXXXX"
val String createdRefreshToken = "XXXXXXXXXXXX"
val String installationId = "XXXXXX"
val String gatewaySerial = "XXXXXXXXXXXX"
val String deviceId = "0"
val java.util.concurrent.locks.ReentrantLock apiLock = new java.util.concurrent.locks.ReentrantLock


// Update the authorization tokens every 1 hour
rule "HEAT1_UpdateAuthorizationTokens"
when
    System started or
    Time cron "0 0 0/1 1/1 * ? *"
then
    var String currentRefreshToken = createdRefreshToken
    if (HEAT1_RefreshToken.state != NULL)
    {
        currentRefreshToken = HEAT1_RefreshToken.state.toString()
    }
    try
    {
        apiLock.lock()

        var String url = "https://iam.viessmann.com/idp/v2/token"
        var String contentType = "application/x-www-form-urlencoded"
        var String content = "grant_type=refresh_token&client_id=" + createdClientId + "&refresh_token=" + currentRefreshToken
        var String jsonData = sendHttpPostRequest(url, contentType, content, 10*1000)
        if (jsonData.isNullOrEmpty)
        {
            logError("viessmann.rules", "Cannot update the authorization tokens. JSON-data is empty.")
            return;
        }
        if (jsonData.contains("\"access_token\"") == false || jsonData.contains("\"refresh_token\"") == false)
        {
            logError("viessmann.rules", "Cannot update the authorization tokens. Error: " + jsonData)
            return;
        }

        logInfo("viessmann.rules", "Update Viessmann authorization tokens every 1 hour")

        var String newAccessToken = transform("JSONPATH", "$.access_token", jsonData)
        if (newAccessToken.isNullOrEmpty == false) HEAT1_AccessToken.postUpdate(newAccessToken)
        var String newRefreshToken = transform("JSONPATH", "$.refresh_token", jsonData)
        if (newRefreshToken.isNullOrEmpty == false) HEAT1_RefreshToken.postUpdate(newRefreshToken)
    }
    finally
    {
        apiLock.unlock()
    }
end


// Read Viessmann values every 10 minutes via HTTP-Get
rule "HEAT1_GetValuesJsonApi"
when
    Time cron "0 0/10 * 1/1 * ? *"
then
    if (HEAT1_AccessToken.state == NULL)
    {
        logError("viessmann.rules", "Cannot get values from Viessmann IoT API. Access-Token is unavailable.")
        return;
    }
    if (apiLock.tryLock(5, java.util.concurrent.TimeUnit.SECONDS) == false)
    {
        logWarn("viessmann.rules", "Drop getting values from Viessmann IoT API. API is locked at the moment.")
        return;
    }

    var String jsonData = ""
    try
    {
        var String url = "https://api.viessmann.com/iot/v1/equipment/installations/" + installationId + "/gateways/" + gatewaySerial + "/devices/" + deviceId + "/features"
        val headers = newHashMap("Authorization" -> "Bearer " + HEAT1_AccessToken.state.toString(), "WWW-Authenticate"-> "Basic")
        jsonData = sendHttpGetRequest(url, headers, 10*1000)
        if (jsonData.isNullOrEmpty)
        {
            logError("viessmann.rules", "Cannot get values from Viessmann IoT API. JSON-data is empty.")
            return;
        }
        if (jsonData.contains("\"data\"") == false || jsonData.contains("\"feature\"") == false)
        {
            logError("viessmann.rules", "Cannot get values from Viessmann IoT API. Error: " + jsonData)
            return;
        }
    }
    finally
    {
        apiLock.unlock()
    }

    logInfo("viessmann.rules", "Updating Viessmann data values every 10 minutes")

    var String outsideTemp = transform("JSONPATH", "$.data[?(@.feature == 'heating.sensors.temperature.outside')].properties.value.value", jsonData)
    if (outsideTemp.isNullOrEmpty == false) HEAT1_OutsideTemp.postUpdate(outsideTemp)
end



Items:

String HEAT1_RefreshToken
String HEAT1_AccessToken

Number HEAT1_OutsideTemp

The posted warn message is from your OWN code!
My guess is that the you get such message every houre since you are using the lock only while getting a new api-key. Why aren’t you moving einher the request for the new key or for the heating values from a full minute to 30 seconds after?