[viessmann] refactor channel commands and add units (#20300)

* [viessmann] Refactor channel command handling

Signed-off-by: Ronny Grun <ronny.grun@t-online.de>
This commit is contained in:
Ronny Grun
2026-03-08 22:31:14 +01:00
committed by GitHub
parent 9f03494bf8
commit 1edae9500b
56 changed files with 1986 additions and 1789 deletions
@@ -100,7 +100,7 @@ public class BindingServlet extends HttpServlet {
String ua = req.getHeader("User-Agent");
logger.trace("User-Agent: {}", ua);
if (!ua.contains("Jetty")) {
if (ua == null || !ua.contains("Jetty")) {
html.append(errorCallUriFromBrowser);
logger.warn("doGet Authcode warn: {}", errorCallUriFromBrowser);
} else {
@@ -14,7 +14,7 @@ package org.openhab.binding.viessmann.internal;
import static java.util.Map.entry;
import java.util.List;
import java.time.LocalTime;
import java.util.Map;
import java.util.Set;
@@ -40,9 +40,14 @@ public class ViessmannBindingConstants {
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
public static final String THING_TYPE_VERSION = "thingTypeVersion";
public static final int DEVICE_THING_TYPE_VERSION = 5;
public static final Set<ThingTypeUID> DISCOVERABLE_DEVICE_TYPE_UIDS = Set.of(THING_TYPE_DEVICE);
public static final String COUNT_API_CALLS = "count-api-calls";
public static final LocalTime RESET_API_CALLS_FROM = LocalTime.of(0, 0, 1);
public static final LocalTime RESET_API_CALLS_TO = LocalTime.of(1, 0, 0);
// References for needed API identifiers
public static final String VIESSMANN_HOST = "api.viessmann-climatesolutions.com";
@@ -63,7 +68,9 @@ public class ViessmannBindingConstants {
public static final Map<String, String> UNIT_MAP = Map.ofEntries(entry("celsius", SIUnits.CELSIUS.getSymbol()), //
entry("kelvin", Units.KELVIN.toString()), //
entry("wattHour", Units.WATT_HOUR.toString()), //
entry("kilowattHour", Units.KILOWATT_HOUR.toString()), //
entry("kilowattpeak", "kilowattpeak"), //
entry("kilowattHour/year", "kilowattHour/year"), //
entry("gas-kilowattHour", Units.KILOWATT_HOUR.toString()), //
entry("power-kilowattHour", Units.KILOWATT_HOUR.toString()), //
@@ -81,7 +88,10 @@ public class ViessmannBindingConstants {
entry("bar", Units.BAR.toString()), //
entry("ampere", Units.AMPERE.toString()), //
entry("milliAmpere", MetricPrefix.MILLI(Units.AMPERE).toString()), //
entry("volt", Units.VOLT.toString()), //
entry("revolutionsPerSecond", Units.HERTZ.toString()), //
entry("meter", SIUnits.METRE.toString()), //
entry("degree", Units.DEGREE_ANGLE.toString()), //
entry("kiloJoule", MetricPrefix.KILO(Units.JOULE).toString()));
public static final Map<String, String> SUB_CHANNEL_TYPE_MAP = Map.of( //
@@ -95,20 +105,4 @@ public class ViessmannBindingConstants {
public static final String CHANNEL_ERROR_IS_ACTIVE = "error-is-active";
public static final String CHANNEL_RUN_QUERY_ONCE = "run-query-once";
public static final String CHANNEL_RUN_ERROR_QUERY_ONCE = "run-error-query-once";
public static final List<String> PROPERTIES_URIS = List.of( //
"setNameUri", //
"setCurveUri", //
"setScheduleUri", //
"setModeUri", //
"setTemperatureUri", //
"temperatureUri", //
"activateUri", //
"deactivateUri", //
"changeEndDateUri", //
"scheduleUri", //
"unscheduleUri", //
"setMinUri", //
"setMaxUri", //
"setHysteresisUri");
}
@@ -16,6 +16,7 @@ import static org.openhab.binding.viessmann.internal.ViessmannBindingConstants.*
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;
@@ -54,7 +55,7 @@ public class ViessmannApi {
private static final String HTTP_METHOD_GET = "GET";
private static final String HTTP_METHOD_POST = "POST";
private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";
private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json; charset=UTF-8";
private static final String PARAM_VI_ERROR_ID = "viErrorId";
private final Logger logger = LoggerFactory.getLogger(ViessmannApi.class);
@@ -227,7 +228,7 @@ public class ViessmannApi {
response = response.replace("enum", "enumValue");
int i = response.indexOf("\"entries\":{\"type\":\"array\",\"value\"");
while (i > 0) {
response = response.substring(0, i) + "\"errorEntries\"" + response.substring(i + 9, response.length());
response = response.substring(0, i) + "\"errorEntries\"" + response.substring(i + 9);
i = response.indexOf("\"entries\":{\"type\":\"array\",\"value\"");
}
return GSON.fromJson(response, FeaturesDTO.class);
@@ -244,7 +245,7 @@ public class ViessmannApi {
response = response.replace("enum", "enumValue");
int i = response.indexOf("\"entries\":{\"type\":\"array\",\"value\"");
while (i > 0) {
response = response.substring(0, i) + "\"errorEntries\"" + response.substring(i + 9, response.length());
response = response.substring(0, i) + "\"errorEntries\"" + response.substring(i + 9);
i = response.indexOf("\"entries\":{\"type\":\"array\",\"value\"");
}
return GSON.fromJson(response, FeaturesDTO.class);
@@ -331,7 +332,7 @@ public class ViessmannApi {
throws ViessmannCommunicationException {
String response = null;
try {
logger.trace("API: GET Request URL is '{}'", url);
logger.debug("API: GET Request URL is '{}'", url);
long startTime = System.currentTimeMillis();
response = HttpUtil.executeUrl(HTTP_METHOD_GET, url, setHeaders(), null, null, API_TIMEOUT_MS);
logger.trace("API: Response took {} msec: {}", System.currentTimeMillis() - startTime, response);
@@ -351,10 +352,11 @@ public class ViessmannApi {
private boolean executePost(ApiInterface interfaceHandler, String url, String json)
throws ViessmannCommunicationException {
try {
logger.trace("API: POST Request URL is '{}', JSON is '{}'", url, json);
logger.debug("API: POST Request URL is '{}', JSON is '{}'", url, json);
long startTime = System.currentTimeMillis();
byte[] payload = json.getBytes(StandardCharsets.UTF_8);
String response = HttpUtil.executeUrl(HTTP_METHOD_POST, url, setHeaders(),
new ByteArrayInputStream(json.getBytes()), CONTENT_TYPE_APPLICATION_JSON, API_TIMEOUT_MS);
new ByteArrayInputStream(payload), CONTENT_TYPE_APPLICATION_JSON, API_TIMEOUT_MS);
logger.trace("API: Response took {} msec: {}", System.currentTimeMillis() - startTime, response);
if (response.contains(PARAM_VI_ERROR_ID)) {
handleViError(interfaceHandler, response);
@@ -409,8 +411,10 @@ public class ViessmannApi {
logger.debug("ViError: {} | Device not reachable", viError.getMessage());
throw new ViessmannCommunicationException(viError.getMessage());
case HttpStatus.BAD_REQUEST_400:
logger.debug("ViError: {} | Gateway offline", viError.getExtendedPayload().getReason());
throw new ViessmannCommunicationException(viError.getExtendedPayload().getReason());
logger.debug("ViError: {} | {}", viError.getExtendedPayload().getReason(),
viError.getExtendedPayload().getDetails());
throw new ViessmannCommunicationException(viError.getExtendedPayload().getReason() + " | "
+ viError.getExtendedPayload().getDetails());
default:
logger.error("ViError: {} | StatusCode: {} | Reason: {}", viError.getMessage(),
viError.getStatusCode(), viError.getExtendedPayload().getReason());
@@ -227,7 +227,7 @@ public class ViessmannAuth {
setState(ViessmannAuthState.NEED_AUTH);
return;
}
logger.trace("ViessmannAuth: Got a valid token response: {}", response);
logger.debug("ViessmannAuth: Got a valid token response: {}", response);
api.setTokenResponseDTO(tokenResponse);
refreshToken = tokenResponse.refreshToken;
api.setTokenExpiryDate(TimeUnit.SECONDS.toMillis(tokenResponse.expiresIn));
@@ -286,7 +286,8 @@ public class ViessmannAuth {
}
private @Nullable String executeUrlAuthorize(String url) {
String authorization = new String(Base64.getEncoder().encode((user + ":" + password).getBytes()),
String credentials = user + ":" + password;
String authorization = new String(Base64.getEncoder().encode(credentials.getBytes(StandardCharsets.UTF_8)),
StandardCharsets.UTF_8);
httpClient.getAuthenticationStore().clearAuthentications();
httpClient.getCookieStore().removeAll();
@@ -1,39 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto;
/**
* The {@link HeatingCircuit} provides the params for a heating circuit
*
* @author Ronny Grun - Initial contribution
*/
public class HeatingCircuit {
public String slope;
public String shift;
public String getSlope() {
return slope;
}
public void setSlope(String slope) {
this.slope = slope;
}
public String getShift() {
return shift;
}
public void setShift(String shift) {
this.shift = shift;
}
}
@@ -12,12 +12,16 @@
*/
package org.openhab.binding.viessmann.internal.dto;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.viessmann.internal.dto.features.FeatureCommands;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.viessmann.internal.dto.features.FeatureCommand;
import org.openhab.binding.viessmann.internal.util.ViessmannUtil;
/**
@@ -26,6 +30,7 @@ import org.openhab.binding.viessmann.internal.util.ViessmannUtil;
* @author Ronny Grun - Initial contribution
*/
public class ThingMessageDTO {
private static final Set<String> IGNORED_SUFFIXES = Set.of("value", "name", "entries", "overlapAllowed");
private String type;
private String channelType;
private String uom;
@@ -37,7 +42,8 @@ public class ThingMessageDTO {
private String suffix;
private String unit;
private final Map<String, String> properties = new HashMap<>();
public FeatureCommands commands;
public Map<@NonNull String, @NonNull FeatureCommand> commands = new HashMap<>();
public boolean isSubChannel = false;
public String getType() {
return type;
@@ -118,11 +124,11 @@ public class ThingMessageDTO {
this.deviceId = deviceId;
}
public FeatureCommands getCommands() {
public Map<@NonNull String, @NonNull FeatureCommand> getCommands() {
return commands;
}
public void setCommands(FeatureCommands commands) {
public void setCommands(Map<@NonNull String, @NonNull FeatureCommand> commands) {
this.commands = commands;
}
@@ -139,8 +145,7 @@ public class ThingMessageDTO {
}
public void setSuffix(String suffix) {
if ("value".equals(suffix) || "name".equals(suffix) || "entries".equals(suffix)
|| "overlapAllowed".equals(suffix)) {
if (IGNORED_SUFFIXES.contains(suffix)) {
this.suffix = "";
} else {
this.suffix = suffix;
@@ -163,7 +168,7 @@ public class ThingMessageDTO {
int count = 0;
for (String str : parts) {
if (count != 0) {
sb.append(str.substring(0, 1).toUpperCase()).append(str.substring(1));
sb.append(str.substring(0, 1).toUpperCase(Locale.ROOT)).append(str.substring(1));
} else {
sb.append(str);
}
@@ -187,4 +192,12 @@ public class ThingMessageDTO {
}
return circuitId;
}
public void setIsSubChannel(boolean bool) {
this.isSubChannel = bool;
}
public ArrayList<@NonNull String> getAllCommands() {
return new ArrayList<>(commands.keySet());
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.device;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* The {@link StoredChannelValues} provides the values from a device
*
* @author Ronny Grun - Initial contribution
*/
public class StoredChannelValues {
public Map<String, String> prop = new ConcurrentHashMap<>();
public void putProperty(String key, String value) {
prop.put(key, value);
}
public String getProperty(String key) {
return prop.get(key);
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureChangeEndDate} provides data of change end date features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureChangeEndDate {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureChangeEndDateParams params;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureChangeEndDateParams} provides parameters of change end date features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureChangeEndDateParams {
public FeatureSetDate end;
}
@@ -12,14 +12,22 @@
*/
package org.openhab.binding.viessmann.internal.dto.features;
import java.util.ArrayList;
import java.util.Map;
/**
* The {@link FeatureSchedule} provides schedule of features
* The {@link FeatureCommand} provides default command of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSchedule {
public class FeatureCommand {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureScheduleParams params;
public boolean isExecutable;
public Map<String, FeatureCommandParams> params;
public boolean isDeprecated;
public ArrayList<String> getAllParams() {
return params == null ? new ArrayList<>() : new ArrayList<>(params.keySet());
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
import java.util.Map;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link FeatureCommandParams} provides parameters of features command
*
* @author Ronny Grun - Initial contribution
*/
public final class FeatureCommandParams {
public final String type;
public final boolean required;
public final Map<String, Object> constraints;
@JsonCreator
public FeatureCommandParams(@JsonProperty("type") String type, @JsonProperty("required") boolean required,
@JsonProperty("constraints") Map<String, Object> constraints) {
this.type = Objects.requireNonNullElse(type, "");
this.required = required;
this.constraints = Objects.requireNonNullElse(constraints, Map.of());
}
public String getType() {
return type;
}
public boolean isRequired() {
return required;
}
public Map<String, Object> getConstraints() {
return constraints;
}
}
@@ -1,137 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* The {@link FeatureCommands} provides command of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureCommands {
public FeatureSetName setName;
public FeatureSetCurve setCurve;
public FeatureSetSchedule setSchedule;
public FeatureSetMode setMode;
public FeatureSetTemperature setTemperature;
public FeatureDefaultCommands activate;
public FeatureDefaultCommands deactivate;
public FeatureChangeEndDate changeEndDate;
public FeatureSchedule schedule;
public FeatureDefaultCommands unschedule;
public FeatureSetTargetTemperature setTargetTemperature;
public FeatureSetTargetTemperature setMin;
public FeatureSetTargetTemperature setMax;
public FeatureSetLevels setLevels;
public FeatureSetHysteresis setHysteresis;
public FeatureSetHysteresis setHysteresisSwitchOnValue;
public FeatureSetHysteresis setHysteresisSwitchOffValue;
public ArrayList<String> getUsedCommands() {
ArrayList<String> list = new ArrayList<>();
if (setName != null) {
list.add("setName");
}
if (setCurve != null) {
list.add("setCurve");
}
if (setSchedule != null) {
list.add("setSchedule");
}
if (setMode != null) {
list.add("setMode");
}
if (setTemperature != null) {
list.add("setTemperature");
}
if (activate != null) {
list.add("activate");
}
if (deactivate != null) {
list.add("deactivate");
}
if (changeEndDate != null) {
list.add("changeEndDate");
}
if (schedule != null) {
list.add("schedule");
}
if (unschedule != null) {
list.add("unschedule");
}
if (setTargetTemperature != null) {
list.add("setTargetTemperature");
}
if (setHysteresis != null) {
list.add("setHysteresis");
}
if (setHysteresisSwitchOnValue != null) {
list.add("setHysteresisSwitchOnValue");
}
if (setHysteresisSwitchOffValue != null) {
list.add("setHysteresisSwitchOffValue");
}
return list;
}
public Map<String, String> getUris() {
Map<String, String> uris = new HashMap<>();
if (setName != null) {
uris.put("setNameUri", setName.uri);
}
if (setCurve != null) {
uris.put("setCurveUri", setCurve.uri);
}
if (setSchedule != null) {
uris.put("setScheduleUri", setSchedule.uri);
}
if (setMode != null) {
uris.put("setModeUri", setMode.uri);
}
if (setTemperature != null) {
uris.put("setTemperatureUri", setTemperature.uri);
}
if (activate != null) {
uris.put("activateUri", activate.uri);
}
if (deactivate != null) {
uris.put("deactivateUri", deactivate.uri);
}
if (changeEndDate != null) {
uris.put("changeEndDateUri", changeEndDate.uri);
}
if (schedule != null) {
uris.put("scheduleUri", schedule.uri);
}
if (unschedule != null) {
uris.put("unscheduleUri", unschedule.uri);
}
if (setTargetTemperature != null) {
uris.put("setTargetTemperatureUri", setTargetTemperature.uri);
}
if (setHysteresis != null) {
uris.put("setHysteresisUri", setHysteresis.uri);
}
if (setHysteresisSwitchOnValue != null) {
uris.put("setHysteresisSwitchOnValueUri", setHysteresisSwitchOnValue.uri);
}
if (setHysteresisSwitchOffValue != null) {
uris.put("setHysteresisSwitchOffValueUri", setHysteresisSwitchOffValue.uri);
}
return uris;
}
}
@@ -1,21 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureConstraintsEmpty} provides constraints of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureConstraintsEmpty {
}
@@ -1,23 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureConstraintsLength} provides length constraints of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureConstraintsLength {
public Integer minLength;
public Integer maxLength;
}
@@ -1,27 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
import java.util.List;
/**
* The {@link FeatureConstraintsSchedule} provides schedule constraints of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureConstraintsSchedule {
public Integer maxEntries;
public Integer resolution;
public List<String> modes = null;
public String defaultMode;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureConstraintsSteppingDouble} provides double stepping constraints of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureConstraintsSteppingDouble {
public Double min;
public Double max;
public Double stepping;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureConstraintsSteppingInteger} provides integer stepping constraints of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureConstraintsSteppingInteger {
public Integer min;
public Integer max;
public Integer stepping;
}
@@ -13,6 +13,14 @@
package org.openhab.binding.viessmann.internal.dto.features;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.viessmann.internal.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* The {@link FeatureDataDTO} provides all data of a feature
@@ -20,6 +28,9 @@ import java.util.List;
* @author Ronny Grun - Initial contribution
*/
public class FeatureDataDTO {
private final Logger logger = LoggerFactory.getLogger(FeatureDataDTO.class);
public Integer apiVersion;
public Boolean isEnabled;
public Boolean isReady;
@@ -29,6 +40,45 @@ public class FeatureDataDTO {
public String deviceId;
public String timestamp;
public FeatureProperties properties;
public FeatureCommands commands;
public Map<String, FeatureCommand> commands;
public List<String> components = null;
@JsonIgnore
public String installationId;
public void setInstallationId(String id) {
this.installationId = id;
}
/**
* @return formatted anonymised JSON representation of this DTO (null on error)
*/
public @Nullable String toPrettyJson() {
try {
String json = JsonUtil.toPrettyJson(this);
if (gatewayId != null) {
json = json.replace("\"" + gatewayId + "\"", "\"{{gatewaySerial}}\"").replace("/" + gatewayId + "/",
"/{{gatewaySerial}}/");
}
if (installationId != null) {
json = json.replace("/" + installationId + "/", "/{{installationId}}/");
}
return json;
} catch (Exception e) {
logger.debug("FeatureDataDTO serialization failed", e);
return null;
}
}
/**
* @return formatted JSON representation of this DTO (null on error)
*/
public @Nullable String toJson() {
try {
return JsonUtil.toJson(this);
} catch (Exception e) {
logger.debug("FeatureDataDTO serialization failed", e);
return null;
}
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureDefaultCommands} provides default command of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureDefaultCommands {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureEmptyParams params;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureDefaultSetterParamsDouble} provides default parameters of double features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureDefaultSetterParamsDouble {
public Boolean required;
public String type;
public FeatureConstraintsSteppingDouble constraints;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureDefaultSetterParamsInteger} provides default parameters of integer features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureDefaultSetterParamsInteger {
Boolean required;
String type;
public FeatureConstraintsSteppingInteger constraints;
}
@@ -1,21 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureEmptyParams} provides empty parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureEmptyParams {
}
@@ -15,10 +15,12 @@ package org.openhab.binding.viessmann.internal.dto.features;
import java.util.List;
/**
* The {@link FeatureConstraintsEnumValue} provides enumValue constraints of features
* The {@link FeatureListString} provides list with string value of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureConstraintsEnumValue {
public List<String> enumValue = null;
public class FeatureListString {
public String type;
public List<String> value = null;
public String unit;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureMode} provides modes of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureMode {
public Boolean required;
public String type;
public FeatureConstraintsEnumValue constraints;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureModeParams} provides mode parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureModeParams {
public FeatureMode mode;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureNewSchedule} provides new schedule of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureNewSchedule {
public Boolean required;
public String type;
public FeatureConstraintsSchedule constraints;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureParamsName} provides name parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureParamsName {
public Boolean required;
public String type;
public FeatureConstraintsLength constraints;
}
@@ -13,16 +13,29 @@
package org.openhab.binding.viessmann.internal.dto.features;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.JsonAdapter;
/**
* The {@link FeatureProperties} provides properties of features
*
* @author Ronny Grun - Initial contribution
*/
@JsonAdapter(FeatureProperties.FeaturePropertiesAdapter.class)
public class FeatureProperties {
public FeatureString value;
public FeatureString status;
public FeatureBoolean active;
public FeaturePropertiesEnabled enabled;
public FeatureString name;
public FeatureInteger shift;
public FeatureDouble slope;
@@ -31,6 +44,7 @@ public class FeatureProperties {
public FeatureBoolean overlapAllowed;
public FeatureInteger temperature;
public FeatureString start;
public FeatureString begin;
public FeatureString end;
public FeatureInteger top;
public FeatureInteger middle;
@@ -52,7 +66,40 @@ public class FeatureProperties {
public FeatureString phase;
public FeatureString switchOnValue;
public FeatureString switchOffValue;
public FeatureDouble currentDay;
public FeatureDouble lastSevenDays;
public FeatureDouble currentMonth;
public FeatureDouble lastMonth;
public FeatureDouble currentYear;
public FeatureDouble lastYear;
public FeatureInteger target;
public FeatureInteger current;
public FeatureListString weekdays;
public FeatureInteger startHour;
public FeatureInteger startMinute;
/**
* Holds unknown/variable properties from the API.
*
* IMPORTANT:
* - @JsonIgnore prevents it from being serialized as "additionalProperties"
* - @JsonAnyGetter below flattens it into the "properties" object
*/
@JsonIgnore
public Map<String, JsonElement> additionalProperties = new HashMap<>();
/**
* Flatten additionalProperties into the JSON object of "properties".
*
* Example output:
* "properties": { "horizontal": {...}, "vertical": {...} }
*/
@JsonAnyGetter
public Map<String, JsonElement> any() {
return additionalProperties;
}
@JsonIgnore
public ArrayList<String> getUsedEntries() {
ArrayList<String> list = new ArrayList<>();
@@ -65,6 +112,9 @@ public class FeatureProperties {
if (active != null) {
list.add("active");
}
if (enabled != null) {
list.add("enabled");
}
if (name != null) {
list.add("name");
}
@@ -86,6 +136,9 @@ public class FeatureProperties {
if (start != null) {
list.add("start");
}
if (begin != null) {
list.add("begin");
}
if (end != null) {
list.add("end");
}
@@ -122,16 +175,16 @@ public class FeatureProperties {
if (hoursLoadClassOne != null) {
list.add("hoursLoadClassOne");
}
if (hoursLoadClassOne != null) {
if (hoursLoadClassTwo != null) {
list.add("hoursLoadClassTwo");
}
if (hoursLoadClassOne != null) {
if (hoursLoadClassThree != null) {
list.add("hoursLoadClassThree");
}
if (hoursLoadClassOne != null) {
if (hoursLoadClassFour != null) {
list.add("hoursLoadClassFour");
}
if (hoursLoadClassOne != null) {
if (hoursLoadClassFive != null) {
list.add("hoursLoadClassFive");
}
if (min != null) {
@@ -149,7 +202,199 @@ public class FeatureProperties {
if (switchOffValue != null) {
list.add("switchOffValue");
}
if (currentDay != null) {
list.add("currentDay");
}
if (lastSevenDays != null) {
list.add("lastSevenDays");
}
if (currentMonth != null) {
list.add("currentMonth");
}
if (lastMonth != null) {
list.add("lastMonth");
}
if (currentYear != null) {
list.add("currentYear");
}
if (lastYear != null) {
list.add("lastYear");
}
if (target != null) {
list.add("target");
}
if (current != null) {
list.add("current");
}
if (weekdays != null) {
list.add("weekdays");
}
if (startHour != null) {
list.add("startHour");
}
if (startMinute != null) {
list.add("startMinute");
}
list.addAll(additionalProperties.keySet());
return list;
}
public static class FeaturePropertiesAdapter implements JsonDeserializer<FeatureProperties> {
@Override
public FeatureProperties deserialize(JsonElement json, java.lang.reflect.Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
FeatureProperties props = new FeatureProperties();
if (json.isJsonNull() || !json.isJsonObject()) {
return props;
}
JsonObject obj = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
String key = entry.getKey();
JsonElement value = entry.getValue();
switch (key) {
case "value":
props.value = context.deserialize(value, FeatureString.class);
break;
case "status":
props.status = context.deserialize(value, FeatureString.class);
break;
case "active":
props.active = context.deserialize(value, FeatureBoolean.class);
break;
case "enabled":
props.enabled = context.deserialize(value, FeaturePropertiesEnabled.class);
break;
case "name":
props.name = context.deserialize(value, FeatureString.class);
break;
case "shift":
props.shift = context.deserialize(value, FeatureInteger.class);
break;
case "slope":
props.slope = context.deserialize(value, FeatureDouble.class);
break;
case "entries":
props.entries = context.deserialize(value, FeatureEntriesWeekDays.class);
break;
case "errorEntries":
props.errorEntries = context.deserialize(value, FeatureErrorEntries.class);
break;
case "overlapAllowed":
props.overlapAllowed = context.deserialize(value, FeatureBoolean.class);
break;
case "temperature":
props.temperature = context.deserialize(value, FeatureInteger.class);
break;
case "start":
props.start = context.deserialize(value, FeatureString.class);
break;
case "begin":
props.begin = context.deserialize(value, FeatureString.class);
break;
case "end":
props.end = context.deserialize(value, FeatureString.class);
break;
case "top":
props.top = context.deserialize(value, FeatureInteger.class);
break;
case "middle":
props.middle = context.deserialize(value, FeatureInteger.class);
break;
case "bottom":
props.bottom = context.deserialize(value, FeatureInteger.class);
break;
case "day":
props.day = context.deserialize(value, FeatureListDouble.class);
break;
case "week":
props.week = context.deserialize(value, FeatureListDouble.class);
break;
case "month":
props.month = context.deserialize(value, FeatureListDouble.class);
break;
case "year":
props.year = context.deserialize(value, FeatureListDouble.class);
break;
case "unit":
props.unit = context.deserialize(value, FeatureString.class);
break;
case "hours":
props.hours = context.deserialize(value, FeatureDouble.class);
break;
case "starts":
props.starts = context.deserialize(value, FeatureInteger.class);
break;
case "hoursLoadClassOne":
props.hoursLoadClassOne = context.deserialize(value, FeatureInteger.class);
break;
case "hoursLoadClassTwo":
props.hoursLoadClassTwo = context.deserialize(value, FeatureInteger.class);
break;
case "hoursLoadClassThree":
props.hoursLoadClassThree = context.deserialize(value, FeatureInteger.class);
break;
case "hoursLoadClassFour":
props.hoursLoadClassFour = context.deserialize(value, FeatureInteger.class);
break;
case "hoursLoadClassFive":
props.hoursLoadClassFive = context.deserialize(value, FeatureInteger.class);
break;
case "min":
props.min = context.deserialize(value, FeatureInteger.class);
break;
case "max":
props.max = context.deserialize(value, FeatureInteger.class);
break;
case "phase":
props.phase = context.deserialize(value, FeatureString.class);
break;
case "switchOnValue":
props.switchOnValue = context.deserialize(value, FeatureString.class);
break;
case "switchOffValue":
props.switchOffValue = context.deserialize(value, FeatureString.class);
break;
case "currentDay":
props.currentDay = context.deserialize(value, FeatureDouble.class);
break;
case "lastSevenDays":
props.lastSevenDays = context.deserialize(value, FeatureDouble.class);
break;
case "currentMonth":
props.currentMonth = context.deserialize(value, FeatureDouble.class);
break;
case "lastMonth":
props.lastMonth = context.deserialize(value, FeatureDouble.class);
break;
case "currentYear":
props.currentYear = context.deserialize(value, FeatureDouble.class);
break;
case "lastYear":
props.lastYear = context.deserialize(value, FeatureDouble.class);
break;
case "target":
props.target = context.deserialize(value, FeatureInteger.class);
break;
case "current":
props.current = context.deserialize(value, FeatureInteger.class);
break;
case "weekdays":
props.weekdays = context.deserialize(value, FeatureListString.class);
break;
case "startHour":
props.startHour = context.deserialize(value, FeatureInteger.class);
break;
case "startMinute":
props.startMinute = context.deserialize(value, FeatureInteger.class);
break;
default:
props.additionalProperties.put(key, value);
break;
}
}
return props;
}
}
}
@@ -12,13 +12,14 @@
*/
package org.openhab.binding.viessmann.internal.dto.features;
import com.google.gson.JsonElement;
/**
* The {@link FeatureSetDate} provides set date of features
* The {@link FeaturePropertiesEnabled} provides enabled property of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetDate {
public Boolean required;
public class FeaturePropertiesEnabled {
public String type;
public FeatureConstraintsEmpty constraints;
public JsonElement value;
}
@@ -1,23 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureScheduleParams} provides schedule parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureScheduleParams {
public FeatureSetDate start;
public FeatureSetDate end;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetCurve} provides set curve of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetCurve {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureSetHeatingCurve params;
}
@@ -1,23 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetHeatingCurve} provides set heating curve of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetHeatingCurve {
public FeatureSlope slope;
public FeatureShift shift;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetHysteresis} provides set hysteresis of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetHysteresis {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureSetHysteresisParams params;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetHysteresisParams} provides set hysteresis parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetHysteresisParams {
public FeatureDefaultSetterParamsDouble hysteresis;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetLevels} provides set levels of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetLevels {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureSetLevelsParams params;
}
@@ -1,23 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetLevelsParams} provides levels parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetLevelsParams {
public FeatureDefaultSetterParamsInteger minTemperature;
public FeatureDefaultSetterParamsInteger maxTemperature;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetMode} provides set mode of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetMode {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureModeParams params;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetName} provides set name of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetName {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureSetNameParams params;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetNameParams} provides set name parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetNameParams {
public FeatureParamsName name;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetSchedule} provides set schedule of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetSchedule {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureSetScheduleParams params;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetScheduleParams} provides set schedule parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetScheduleParams {
public FeatureNewSchedule newSchedule;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetTargetTemperature} provides set target temperature of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetTargetTemperature {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureSetTargetTemperatureParams params;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetTargetTemperatureParams} provides set target temperature parameters of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetTargetTemperatureParams {
public FeatureDefaultSetterParamsInteger temperature;
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSetTemperature} provides set temperature of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSetTemperature {
public String uri;
public String name;
public Boolean isExecutable;
public FeatureTargetTemperatureParams params;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureShift} provides shift of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureShift {
public Boolean required;
public String type;
public FeatureConstraintsSteppingInteger constraints;
}
@@ -1,24 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureSlope} provides slope of features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureSlope {
public Boolean required;
public String type;
public FeatureConstraintsSteppingDouble constraints;
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.dto.features;
/**
* The {@link FeatureTargetTemperatureParams} provides parameters of target temperature features
*
* @author Ronny Grun - Initial contribution
*/
public class FeatureTargetTemperatureParams {
public FeatureDefaultSetterParamsInteger targetTemperature;
}
@@ -16,6 +16,7 @@ import static org.openhab.binding.viessmann.internal.ViessmannBindingConstants.*
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -389,8 +390,9 @@ public class ViessmannAccountHandler extends BaseBridgeHandler implements ApiInt
}
private void checkResetApiCalls() {
LocalTime time = LocalTime.now();
if (time.isAfter(LocalTime.of(0, 0, 1)) && (time.isBefore(LocalTime.of(1, 0, 0)))) {
LocalTime now = ZonedDateTime.now(ViessmannUtil.getOpenHABZoneId()).toLocalTime();
if (now.isAfter(RESET_API_CALLS_FROM) && now.isBefore(RESET_API_CALLS_TO)) {
if (countReset) {
logger.debug("Resetting API call counts");
apiCalls = 0;
@@ -464,6 +466,7 @@ public class ViessmannAccountHandler extends BaseBridgeHandler implements ApiInt
List<FeatureDataDTO> featuresData = allFeatures.data;
if (featuresData != null && !featuresData.isEmpty()) {
for (FeatureDataDTO featureDataDTO : featuresData) {
featureDataDTO.setInstallationId(installationId);
handler.handleUpdate(featureDataDTO);
}
} else {
@@ -16,6 +16,7 @@ import static org.openhab.binding.viessmann.internal.ViessmannBindingConstants.*
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -134,10 +135,6 @@ public class ViessmannBridgeHandler extends BaseBridgeHandler implements BridgeI
return new ArrayList<>(devicesList);
}
public List<Thing> getChildren() {
return getThing().getThings().stream().filter(Thing::isEnabled).collect(Collectors.toList());
}
private void setConfigInstallationGatewayId() {
Configuration conf = editConfiguration();
conf.put("installationId", newInstallationId);
@@ -384,8 +381,9 @@ public class ViessmannBridgeHandler extends BaseBridgeHandler implements BridgeI
}
private void checkResetApiCalls() {
LocalTime time = LocalTime.now();
if (time.isAfter(LocalTime.of(0, 0, 1)) && (time.isBefore(LocalTime.of(1, 0, 0)))) {
LocalTime now = ZonedDateTime.now(ViessmannUtil.getOpenHABZoneId()).toLocalTime();
if (now.isAfter(RESET_API_CALLS_FROM) && now.isBefore(RESET_API_CALLS_TO)) {
if (countReset) {
logger.debug("Resetting API call counts");
apiCalls = 0;
@@ -32,8 +32,6 @@ import org.openhab.binding.viessmann.internal.config.GatewayConfiguration;
import org.openhab.binding.viessmann.internal.dto.device.DeviceDTO;
import org.openhab.binding.viessmann.internal.dto.device.DeviceData;
import org.openhab.binding.viessmann.internal.dto.events.EventsDTO;
import org.openhab.binding.viessmann.internal.dto.features.FeatureDataDTO;
import org.openhab.binding.viessmann.internal.dto.features.FeaturesDTO;
import org.openhab.binding.viessmann.internal.interfaces.BridgeInterface;
import org.openhab.binding.viessmann.internal.util.ViessmannUtil;
import org.openhab.core.library.types.OnOffType;
@@ -83,11 +81,11 @@ public class ViessmannGatewayHandler extends BaseBridgeHandler implements Bridge
protected final List<String> devicesList = new ArrayList<>();
protected final List<DeviceData> discoveredDeviceList = new ArrayList<>();
private final ItemChannelLinkRegistry linkRegistry;
private @Nullable final ItemChannelLinkRegistry linkRegistry;
private GatewayConfiguration config = new GatewayConfiguration();
public ViessmannGatewayHandler(Bridge bridge, ItemChannelLinkRegistry linkRegistry) {
public ViessmannGatewayHandler(Bridge bridge, @Nullable ItemChannelLinkRegistry linkRegistry) {
super(bridge);
this.linkRegistry = linkRegistry;
}
@@ -238,22 +236,27 @@ public class ViessmannGatewayHandler extends BaseBridgeHandler implements Bridge
updateThing(editThing().withChannels(newChannels).build());
if (linkRegistry != null) {
ItemChannelLinkRegistry registry = linkRegistry;
if (registry != null) {
for (Map.Entry<ChannelUID, ChannelUID> e : renameMap.entrySet()) {
ChannelUID oldUid = e.getKey();
ChannelUID newUid = e.getValue();
Collection<ItemChannelLink> links = new ArrayList<>(linkRegistry.getLinks(oldUid));
Collection<ItemChannelLink> existing = registry.getLinks(oldUid);
if (existing.isEmpty()) {
continue;
}
Collection<ItemChannelLink> links = new ArrayList<>(existing);
for (ItemChannelLink link : links) {
String item = link.getItemName();
try {
linkRegistry.remove(link.getUID());
registry.remove(link.getUID());
} catch (Exception ex) {
logger.warn("Could not remove old link {} -> {}: {}", item, oldUid, ex.getMessage());
}
linkRegistry.add(new ItemChannelLink(item, newUid));
registry.add(new ItemChannelLink(item, newUid));
logger.info("Re-linked item '{}' from '{}' to '{}'", item, oldUid.getId(), newUid.getId());
}
}
@@ -360,36 +363,9 @@ public class ViessmannGatewayHandler extends BaseBridgeHandler implements Bridge
@Override
public void updateFeaturesOfDevice(@Nullable DeviceHandler handler) {
String deviceId = "";
if (handler != null) {
deviceId = handler.getDeviceId();
logger.debug("Loading features from Device ID: {}", deviceId);
try {
FeaturesDTO allFeatures = null;
BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
allFeatures = ((ViessmannAccountHandler) bridgeHandler).getAllFeatures(deviceId, installationId,
gatewaySerial);
}
if (allFeatures != null) {
List<FeatureDataDTO> featuresData = allFeatures.data;
if (featuresData != null && !featuresData.isEmpty()) {
for (FeatureDataDTO featureDataDTO : featuresData) {
handler.handleUpdate(featureDataDTO);
}
} else {
logger.warn("Features of Device ID \"{}\" is empty.", deviceId);
String statusMessage = String.format("@text/offline.comm-error.features-empty [%s]", deviceId);
handler.updateThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, statusMessage);
}
}
} catch (ViessmannCommunicationException e) {
String statusMessage = String.format("@text/offline.comm-error.device-not-reachable [%s]",
e.getMessage());
handler.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, statusMessage);
} catch (JsonSyntaxException | IllegalStateException e) {
logger.warn("Parsing Viessmann response fails: {}", e.getMessage());
if (getBridgeHandler() instanceof ViessmannAccountHandler bridgeHandler) {
bridgeHandler.updateFeaturesOfDevice(handler);
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.util;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* The {@link GsonJsonElementSerializer} provides Jackson serializer
* for Gson JsonElement trees (JsonObject/JsonArray/JsonPrimitive).
* Prevents Jackson from reflecting into Gson getters like "asDouble".
*
* @author Ronny Grun - Initial contribution
*/
@NonNullByDefault
public class GsonJsonElementSerializer extends JsonSerializer<JsonElement> {
@Override
@NonNullByDefault({})
public void serialize(JsonElement value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
write(value, gen);
}
private static void write(JsonElement el, JsonGenerator gen) throws IOException {
if (el instanceof JsonNull || el.isJsonNull()) {
gen.writeNull();
return;
}
if (el.isJsonPrimitive()) {
JsonPrimitive p = el.getAsJsonPrimitive();
if (p.isBoolean()) {
gen.writeBoolean(p.getAsBoolean());
} else if (p.isNumber()) {
// BigDecimal avoids double rounding noise in debug JSON
gen.writeNumber(p.getAsBigDecimal());
} else {
gen.writeString(p.getAsString());
}
return;
}
if (el.isJsonArray()) {
JsonArray arr = el.getAsJsonArray();
gen.writeStartArray();
for (JsonElement e : arr) {
write(e, gen);
}
gen.writeEndArray();
return;
}
if (el.isJsonObject()) {
JsonObject obj = el.getAsJsonObject();
gen.writeStartObject();
for (var entry : obj.entrySet()) {
gen.writeFieldName(entry.getKey());
write(entry.getValue(), gen);
}
gen.writeEndObject();
return;
}
// Fallback (should not happen)
gen.writeString(el.toString());
}
}
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.viessmann.internal.util;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.gson.JsonElement;
/**
* The {@link JsonUtil} class provides utility methods for JSON serialization
* and deserialization using Jackson.
*
* @author Ronny Grun - Initial contribution
*/
@NonNullByDefault
public final class JsonUtil {
/**
* Module that teaches Jackson how to serialize Gson's JsonElement tree types (JsonObject/JsonArray/...).
* Without this, Jackson may reflect into Gson internals (e.g. "asDouble") and fail.
*/
private static final SimpleModule GSON_MODULE = createGsonModule();
private static final ObjectMapper COMPACT_MAPPER = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL).registerModule(GSON_MODULE);
private static final ObjectMapper PRETTY_MAPPER = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL).enable(SerializationFeature.INDENT_OUTPUT)
.registerModule(GSON_MODULE);
private JsonUtil() {
// utility class
}
private static SimpleModule createGsonModule() {
SimpleModule m = new SimpleModule("gson-json");
m.addSerializer(JsonElement.class, new GsonJsonElementSerializer());
return m;
}
/**
* Serializes the given object to compact JSON.
*
* @param object the object to serialize
* @return JSON representation
* @throws Exception if serialization fails
*/
public static String toJson(Object object) throws Exception {
return COMPACT_MAPPER.writeValueAsString(object);
}
/**
* Serializes the given object to formatted (pretty-printed) JSON.
*
* @param object the object to serialize
* @return formatted JSON representation
* @throws Exception if serialization fails
*/
public static String toPrettyJson(Object object) throws Exception {
return PRETTY_MAPPER.writeValueAsString(object);
}
/**
* Deserializes the given JSON string into the specified type.
*
* @param json the JSON string
* @param type the target class
* @param <T> the target type
* @return deserialized object
* @throws Exception if deserialization fails
*/
public static <T> T fromJson(String json, Class<T> type) throws Exception {
return COMPACT_MAPPER.readValue(json, type);
}
}
@@ -12,18 +12,18 @@
*/
package org.openhab.binding.viessmann.internal.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.viessmann.internal.handler.DeviceHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
/**
* The {@link ViessmannUtil} class provides utility methods for the Viessmann binding.
@@ -32,50 +32,11 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public final class ViessmannUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ViessmannUtil.class);
private ViessmannUtil() {
throw new AssertionError("No instances allowed");
}
/**
* Gets a resource file as {@link InputStream}.
*
* @param clazz class used to get the classloader
* @param fileName resource file name
* @return optional stream of the resource
*/
public static Optional<InputStream> getResourceStream(Class<?> clazz, String fileName) {
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader == null) {
LOGGER.warn("Could not get classloader for class '{}'", clazz);
return Optional.empty();
}
return Optional.ofNullable(classLoader.getResourceAsStream(fileName));
}
/**
* Reads a properties file into a {@link Map}.
*
* @param clazz class used to get the classloader
* @param fileName properties file name
* @return map with key-value pairs, or empty map if not readable
*/
public static Map<String, String> readProperties(Class<?> clazz, String fileName) {
return Objects.requireNonNull(getResourceStream(clazz, fileName).map(inputStream -> {
Properties properties = new Properties();
try {
properties.load(inputStream);
return properties.entrySet().stream()
.collect(Collectors.toMap(e -> (String) e.getKey(), e -> (String) e.getValue()));
} catch (IOException e) {
LOGGER.warn("Could not read resource file '{}', binding will probably fail: {}", fileName,
e.getMessage());
return new HashMap<String, String>();
}
}).orElse(Map.of()));
}
/**
* Converts camelCase to hyphenated lowercase.
*
@@ -85,6 +46,63 @@ public final class ViessmannUtil {
public static String camelToHyphen(String input) {
String result = input.replaceAll("([a-z])([A-Z])", "$1-$2").replaceAll("([A-Z])([A-Z][a-z])", "$1-$2");
result = result.replaceAll("([a-zA-Z])([0-9]+)", "$1-$2").replaceAll("([0-9]+)([a-zA-Z])", "$1-$2");
return result.toLowerCase();
return result.toLowerCase(Locale.ROOT);
}
/**
* Converts a hyphen-separated string to camelCase or UpperCamelCase.
*
* @param input the hyphen-separated input (e.g. {@code "flow-temperature"})
* @param capitalizeFirst {@code true} for UpperCamelCase, {@code false} for camelCase
* @return the converted string, or the input if {@code null} or blank
*/
public static @Nullable String hyphenToCamel(@Nullable String input, boolean capitalizeFirst) {
if (input == null || input.isBlank()) {
return input;
}
StringBuilder result = new StringBuilder();
boolean upperNext = capitalizeFirst;
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '-') {
upperNext = true;
continue;
}
if (upperNext) {
result.append(Character.toUpperCase(c));
upperNext = false;
} else {
result.append(Character.toLowerCase(c));
}
}
return result.toString();
}
public static ZoneId getOpenHABZoneId() {
Bundle bundle = FrameworkUtil.getBundle(DeviceHandler.class);
if (bundle == null) {
return ZoneId.systemDefault();
}
BundleContext ctx = bundle.getBundleContext();
if (ctx == null) {
return ZoneId.systemDefault();
}
ServiceReference<TimeZoneProvider> ref = ctx.getServiceReference(TimeZoneProvider.class);
if (ref != null) {
TimeZoneProvider tzProvider = ctx.getService(ref);
if (tzProvider != null) {
try {
TimeZone tz = TimeZone.getTimeZone(tzProvider.getTimeZone());
return tz.toZoneId();
} finally {
ctx.ungetService(ref);
}
}
}
return ZoneId.systemDefault();
}
}
@@ -224,7 +224,7 @@ viessmann.feature.name.ventilation.schedule = Schedule
viessmann.feature.name.heating.gas.consumption.summary.dhw = Gas consumption summary DHW
viessmann.feature.name.heating.gas.consumption.summary.heating = Gas consumption summary heating
viessmann.feature.name.heating.power.consumption.summary.heating = Power consumption summary heating
viessmann.feature.name.heating.power.consumption.summary.dhw =
viessmann.feature.name.heating.power.consumption.summary.dhw = Power consumption summary DHW
# Feature Descriptions
@@ -476,9 +476,9 @@ viessmann.feature.description.heating.solar.rechargeSuppression = Shows the stat
viessmann.feature.description.heating.solar.sensors.temperature.collector = Shows Solar Collector temperature in °C
viessmann.feature.description.heating.solar.sensors.temperature.dhw = Shows solar dhw temperature in °C
viessmann.feature.description.heating.solar.statistics = Shows statistics of solar operating hours
viessmann.feature.description.heating.spf.dhw = Shows the SPF for DHW
viessmann.feature.description.heating.spf.heating = Shows the SPF for heating
viessmann.feature.description.heating.spf.total = Shows the SPF of heating and DHW
viessmann.feature.description.heating.spf.dhw = Shows the Seasonal Performance Factor (SPF) for DHW
viessmann.feature.description.heating.spf.heating = Shows the Seasonal Performance Factor (SPF) for heating
viessmann.feature.description.heating.spf.total = Shows the Seasonal Performance Factor (SPF) of heating and DHW
viessmann.feature.description.heating.temperature.commonSupply = Shows the value and unit of common flow temperature setpoint. Also includes the command to change the common flow temperature setpoint
viessmann.feature.description.pcc.transfer.consumption.total = Shows the value of total consumption over the point of coupling in Wh
viessmann.feature.description.pcc.transfer.feedIn.total = Shows the value of total feed in over the point of coupling in Wh
@@ -559,6 +559,12 @@ viessmann.command.label.filterChange = Filter change
viessmann.command.label.permanent = Permanent
viessmann.command.label.sensorDriven = Sensor driven
viessmann.command.label.sensorOverride = Sensor override
viessmann.command.label.off = Off
viessmann.command.label.preparing = Preparing
viessmann.command.label.not-ready = Not ready
viessmann.command.label.ready = Ready
viessmann.command.label.efficientWithMinComfort = Efficient with min comfort
viessmann.command.label.efficient = Efficient
# thing status descriptions
@@ -218,7 +218,7 @@
<semantic-equipment-tag>WaterHeater</semantic-equipment-tag>
<properties>
<property name="thingTypeVersion">4</property>
<property name="thingTypeVersion">5</property>
</properties>
<representation-property>deviceId</representation-property>
<config-description>
@@ -329,6 +329,17 @@
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="watt-hour">
<item-type unitHint="Wh">Number:Energy</item-type>
<label>Electric Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="heat-energy">
<item-type>Number:Energy</item-type>
<label>Heat Energy</label>
@@ -362,6 +373,17 @@
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="kilowattpeak">
<item-type>Number</item-type>
<label>Peak Power (kWp)</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.1f kWp" readOnly="true"/>
</channel-type>
<channel-type id="house-heating-load">
<item-type>Number</item-type>
<label>Specific Heating Demand</label>
@@ -410,7 +432,18 @@
</channel-type>
<channel-type id="duration">
<item-type>Number:Time</item-type>
<item-type unitHint="h">Number:Time</item-type>
<label>Duration</label>
<category>Time</category>
<tags>
<tag>Status</tag>
<tag>Duration</tag>
</tags>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="duration-minute">
<item-type unitHint="min">Number:Time</item-type>
<label>Duration</label>
<category>Time</category>
<tags>
@@ -544,6 +577,9 @@
<channel-type id="slope">
<item-type>Number</item-type>
<label>Heating Curve Slope</label>
<tags>
<tag>Setpoint</tag>
</tags>
<state min="0.2" max="3.5" step="0.1" pattern="%.1f" readOnly="false"/>
</channel-type>
@@ -551,6 +587,9 @@
<channel-type id="shift">
<item-type>Number</item-type>
<label>Heating Curve Shift</label>
<tags>
<tag>Setpoint</tag>
</tags>
<state min="-13" max="40" step="1" pattern="%d" readOnly="false"/>
</channel-type>
@@ -586,4 +625,55 @@
<state pattern="%.0f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="volt">
<item-type>Number:ElectricPotential</item-type>
<label>Volt</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="start-hour">
<item-type>Number</item-type>
<label>Start Time Hour</label>
<category>Time</category>
<tags>
<tag>Setpoint</tag>
<tag>Temperature</tag> <!-- Workaround UI stepper-card -->
</tags>
<state min="0" max="23" step="1" pattern="%d"/>
</channel-type>
<channel-type id="start-minute">
<item-type>Number</item-type>
<label>Start Time Minute</label>
<category>Time</category>
<tags>
<tag>Setpoint</tag>
<tag>Temperature</tag> <!-- Workaround UI stepper-card -->
</tags>
<state min="0" max="50" step="10" pattern="%d"/>
</channel-type>
<channel-type id="meter">
<item-type>Number:Length</item-type>
<label>Length</label>
<tags>
<tag>Measurement</tag>
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="degree">
<item-type>Number:Angle</item-type>
<label>Angle</label>
<tags>
<tag>Measurement</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>