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.