diff --git a/bundles/org.openhab.binding.siemensrds/.classpath b/bundles/org.openhab.binding.siemensrds/.classpath
index a5d95095ccaaf..28fd99941d198 100644
--- a/bundles/org.openhab.binding.siemensrds/.classpath
+++ b/bundles/org.openhab.binding.siemensrds/.classpath
@@ -18,6 +18,7 @@
+
diff --git a/bundles/org.openhab.binding.siemensrds/pom.xml b/bundles/org.openhab.binding.siemensrds/pom.xml
index ed63e6c3bb50a..cdb857d2dc1e8 100644
--- a/bundles/org.openhab.binding.siemensrds/pom.xml
+++ b/bundles/org.openhab.binding.siemensrds/pom.xml
@@ -1,4 +1,6 @@
-
+
+
4.0.0
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsAccessToken.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsAccessToken.java
index 5507cee3baaa7..ea3c82d78617e 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsAccessToken.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsAccessToken.java
@@ -30,12 +30,12 @@
import javax.net.ssl.HttpsURLConnection;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
-import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
/**
@@ -44,27 +44,27 @@
* @author Andrew Fiddian-Green - Initial contribution
*
*/
-class RdsAccessToken {
+@NonNullByDefault
+public class RdsAccessToken {
/*
* NOTE: requires a static logger because the class has static methods
*/
- protected static final Logger LOGGER = LoggerFactory.getLogger(RdsAccessToken.class);
+ protected final Logger logger = LoggerFactory.getLogger(RdsAccessToken.class);
private static final Gson GSON = new Gson();
@SerializedName("access_token")
- private String accessToken;
+ private @Nullable String accessToken;
@SerializedName(".expires")
- private String expires;
+ private @Nullable String expires;
- private Date expDate = null;
+ private @Nullable Date expDate = null;
/*
- * private method: execute the HTTP POST on the server
+ * public static method: execute the HTTP POST on the server
*/
- private static String httpGetTokenJson(String apiKey, String user, String password)
- throws RdsCloudException, IOException {
+ public static String httpGetTokenJson(String apiKey, String payload) throws RdsCloudException, IOException {
/*
* NOTE: this class uses JAVAX HttpsURLConnection library instead of the
* preferred JETTY library; the reason is that JETTY does not allow sending the
@@ -85,12 +85,12 @@ private static String httpGetTokenJson(String apiKey, String user, String passwo
try (OutputStream outputStream = https.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
- dataOutputStream.writeBytes(String.format(TOKEN_REQUEST, user, password));
+ dataOutputStream.writeBytes(payload);
dataOutputStream.flush();
}
if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
- throw new RdsCloudException("invalid HTTP response");
+ throw new IOException(https.getResponseMessage());
}
try (InputStream inputStream = https.getInputStream();
@@ -106,35 +106,33 @@ private static String httpGetTokenJson(String apiKey, String user, String passwo
}
/*
- * public method: execute a POST on the cloud server, parse the JSON, and create
- * a class that encapsulates the data
+ * public method: parse the JSON, and create a class that encapsulates the data
*/
- public static @Nullable RdsAccessToken create(String apiKey, String user, String password) {
- try {
- String json = httpGetTokenJson(apiKey, user, password);
- return GSON.fromJson(json, RdsAccessToken.class);
- } catch (JsonSyntaxException | RdsCloudException | IOException e) {
- LOGGER.warn("create {}: \"{}\"", e.getClass().getName(), e.getMessage());
- return null;
- }
+ public static @Nullable RdsAccessToken createFromJson(String json) {
+ return GSON.fromJson(json, RdsAccessToken.class);
}
/*
* public method: return the access token
*/
- public String getToken() {
- return accessToken;
+ public String getToken() throws RdsCloudException {
+ String accessToken = this.accessToken;
+ if (accessToken != null) {
+ return accessToken;
+ }
+ throw new RdsCloudException("no access token");
}
/*
* public method: check if the token has expired
*/
- public Boolean isExpired() {
+ public boolean isExpired() {
+ Date expDate = this.expDate;
if (expDate == null) {
try {
expDate = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z").parse(expires);
} catch (ParseException e) {
- LOGGER.debug("isExpired: expiry date parsing exception");
+ logger.debug("isExpired: expiry date parsing exception");
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsBindingConstants.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsBindingConstants.java
index b0fa690a672db..67135fed79e34 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsBindingConstants.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsBindingConstants.java
@@ -12,6 +12,7 @@
*/
package org.openhab.binding.siemensrds.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
/**
@@ -19,6 +20,7 @@
*
* @author Andrew Fiddian-Green - Initial contribution
*/
+@NonNullByDefault
public class RdsBindingConstants {
/*
@@ -101,42 +103,46 @@ public class RdsBindingConstants {
public static final String PROP_PLANT_ID = "plantId";
/*
- * ==================== USED DATA POINTS ========================== where: HIE_
- * = the Hierarchy Name in the ClimatixIc server CHA_ = the Channel ID in the
- * OpenHAB binding
+ * ==================== USED DATA POINTS ==========================
+ *
+ * where: HIE_xxx = the point class suffix part of the hierarchy name in the
+ * ClimatixIc server, and CHA_xxx = the Channel ID in the OpenHAB binding
+ *
*/
-
// device name
- protected static final String HIE_DESCRIPTION = "R(1)'Description";
+ public static final String HIE_DESCRIPTION = "'Description";
+
+ // online state
+ public static final String HIE_ONLINE = "#Online";
// room (actual) temperature (read-only)
protected static final String CHA_ROOM_TEMP = "roomTemperature";
- private static final String HIE_ROOM_TEMP = "RTemp";
+ public static final String HIE_ROOM_TEMP = "'RTemp";
// room relative humidity (read-only)
protected static final String CHA_ROOM_HUMIDITY = "roomHumidity";
- private static final String HIE_ROOM_HUMIDITY = "RHuRel";
+ public static final String HIE_ROOM_HUMIDITY = "'RHuRel";
// room air quality (low/med/high) (read-only)
protected static final String CHA_ROOM_AIR_QUALITY = "roomAirQuality";
- private static final String HIE_ROOM_AIR_QUALITY = "RAQualInd";
+ public static final String HIE_ROOM_AIR_QUALITY = "'RAQualInd";
// energy savings level (green leaf) (poor..excellent) (read-write)
// note: writing the value "5" forces the device to green leaf mode
protected static final String CHA_ENERGY_SAVINGS_LEVEL = "energySavingsLevel";
- protected static final String HIE_ENERGY_SAVINGS_LEVEL = "REei";
+ public static final String HIE_ENERGY_SAVINGS_LEVEL = "'REei";
// outside air temperature (read-only)
protected static final String CHA_OUTSIDE_TEMP = "outsideTemperature";
- private static final String HIE_OUTSIDE_TEMP = "TOa";
+ public static final String HIE_OUTSIDE_TEMP = "'TOa";
// set-point override (read-write)
protected static final String CHA_TARGET_TEMP = "targetTemperature";
- private static final String HIE_TARGET_TEMP = "SpTR";
+ public static final String HIE_TARGET_TEMP = "'SpTR";
// heating/cooling state (read-only)
protected static final String CHA_OUTPUT_STATE = "thermostatOutputState";
- private static final String HIE_OUTPUT_STATE = "HCSta";
+ public static final String HIE_OUTPUT_STATE = "'HCSta";
/*
* thermostat occupancy state (absent, present) (read-write) NOTE: uses
@@ -144,7 +150,7 @@ public class RdsBindingConstants {
* absent, present states
*/
protected static final String CHA_STAT_OCC_MODE_PRESENT = "occupancyModePresent";
- protected static final String HIE_STAT_OCC_MODE_PRESENT = "OccMod";
+ public static final String HIE_STAT_OCC_MODE_PRESENT = "'OccMod";
/*
* thermostat program mode (read-write) NOTE: uses different parameters as
@@ -153,8 +159,8 @@ public class RdsBindingConstants {
* to command to the auto mode
*/
protected static final String CHA_STAT_AUTO_MODE = "thermostatAutoMode";
- private static final String HIE_PR_OP_MOD_RSN = "PrOpModRsn";
- protected static final String HIE_STAT_CMF_BTN = "CmfBtn";
+ public static final String HIE_PR_OP_MOD_RSN = "'PrOpModRsn";
+ public static final String HIE_STAT_CMF_BTN = "'CmfBtn";
/*
* domestic hot water state (off, on) (read-write) NOTE: uses different
@@ -162,7 +168,7 @@ public class RdsBindingConstants {
* states
*/
protected static final String CHA_DHW_OUTPUT_STATE = "hotWaterOutputState";
- private static final String HIE_DHW_OUTPUT_STATE = "DhwMod";
+ public static final String HIE_DHW_OUTPUT_STATE = "'DhwMod";
/*
* domestic hot water program mode (manual, auto) (read-write) NOTE: uses
@@ -215,17 +221,38 @@ public class RdsBindingConstants {
* private static final String HIE_COMFORT_BUTTON = "CmfBtn";
*
*/
+
+ /*
+ * logger strings
+ */
+ public static final String LOG_HTTP_COMMAND = "{} for url {} characters long";
+ public static final String LOG_CONTENT_LENGTH = "{} {} characters..";
+ public static final String LOG_PAYLOAD_FMT = "{} {}";
+
+ public static final String LOG_HTTP_COMMAND_ABR = "{} for url {} characters long (set log level to TRACE to see full url)..";
+ public static final String LOG_CONTENT_LENGTH_ABR = "{} {} characters (set log level to TRACE to see full string)..";
+ public static final String LOG_PAYLOAD_FMT_ABR = "{} {} ...";
+
+ public static final String LOG_RECEIVED_MSG = "received";
+ public static final String LOG_RECEIVED_MARK = "<<";
+
+ public static final String LOG_SENDING_MSG = "sending";
+ public static final String LOG_SENDING_MARK = ">>";
+
+ public static final String LOG_SYSTEM_EXCEPTION = "system exception in {}, type={}, message=\"{}\"";
+ public static final String LOG_RUNTIME_EXCEPTION = "runtime exception in {}, type={}, message=\"{}\"";
}
/**
* @author Andrew Fiddian-Green - Initial contribution
*/
+@NonNullByDefault
class ChannelMap {
- public String channelId;
- public String hierarchyName;
+ public String id;
+ public String clazz;
- public ChannelMap(String channelId, String hierarchyName) {
- this.channelId = channelId;
- this.hierarchyName = hierarchyName;
+ public ChannelMap(String channelId, String pointClass) {
+ this.id = channelId;
+ this.clazz = pointClass;
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudConfiguration.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudConfiguration.java
index 75b71b646caa2..c95347bee17cf 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudConfiguration.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudConfiguration.java
@@ -12,16 +12,19 @@
*/
package org.openhab.binding.siemensrds.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* The {@link RdsCloudConfiguration} class contains the thing configuration
* parameters for the Climatix IC cloud user account
*
* @author Andrew Fiddian-Green - Initial contribution
*/
+@NonNullByDefault
public class RdsCloudConfiguration {
- public String userEmail;
- public String userPassword;
+ public String userEmail = "";
+ public String userPassword = "";
public int pollingInterval;
- public String apiKey;
+ public String apiKey = "";
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudException.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudException.java
index d01b12a6dcdb4..ecaa5b98bcfc3 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudException.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudException.java
@@ -12,13 +12,16 @@
*/
package org.openhab.binding.siemensrds.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Custom Cloud Server communication exception
*
* @author Andrew Fiddian-Green - Initial contribution
*
*/
-public class RdsCloudException extends RuntimeException {
+@NonNullByDefault
+public class RdsCloudException extends Exception {
private static final long serialVersionUID = -7048044632627280917L;
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudHandler.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudHandler.java
index 614a9b45c8a68..abca0064d80c0 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudHandler.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsCloudHandler.java
@@ -14,6 +14,10 @@
import static org.openhab.binding.siemensrds.internal.RdsBindingConstants.*;
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ThingStatus;
@@ -23,6 +27,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonParseException;
+
/**
* The {@link RdsCloudHandler} is the handler for Siemens RDS cloud account (
* also known as the Climatix IC server account )
@@ -30,12 +36,14 @@
* @author Andrew Fiddian-Green - Initial contribution
*
*/
+@NonNullByDefault
public class RdsCloudHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(RdsCloudHandler.class);
- private RdsCloudConfiguration config;
- private RdsAccessToken accessToken;
+ private @Nullable RdsCloudConfiguration config = null;
+
+ private @Nullable RdsAccessToken accessToken = null;
public RdsCloudHandler(Bridge bridge) {
super(bridge);
@@ -48,12 +56,7 @@ public void handleCommand(ChannelUID channelUID, Command command) {
@Override
public void initialize() {
- config = getConfigAs(RdsCloudConfiguration.class);
-
- if (config == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing configuration");
- return;
- }
+ RdsCloudConfiguration config = this.config = getConfigAs(RdsCloudConfiguration.class);
if (config.userEmail.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing email address");
@@ -84,8 +87,12 @@ public void dispose() {
* public method: used by RDS smart thermostat handlers return the polling
* interval (seconds)
*/
- public int getPollInterval() {
- return (config != null ? config.pollingInterval : -1);
+ public int getPollInterval() throws RdsCloudException {
+ RdsCloudConfiguration config = this.config;
+ if (config != null) {
+ return config.pollingInterval;
+ }
+ throw new RdsCloudException("missing polling interval");
}
/*
@@ -93,8 +100,39 @@ public int getPollInterval() {
* necessary
*/
private synchronized void refreshToken() {
+ RdsCloudConfiguration config = this.config;
+ RdsAccessToken accessToken = this.accessToken;
+
if (accessToken == null || accessToken.isExpired()) {
- accessToken = RdsAccessToken.create(config.apiKey, config.userEmail, config.userPassword);
+ try {
+ if (config == null) {
+ throw new RdsCloudException("missing configuration");
+ }
+
+ String url = URL_TOKEN;
+ String payload = String.format(TOKEN_REQUEST, config.userEmail, config.userPassword);
+
+ logger.debug(LOG_HTTP_COMMAND, HTTP_POST, url.length());
+ logger.debug(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
+ logger.debug(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, payload);
+
+ String json = RdsAccessToken.httpGetTokenJson(config.apiKey, payload);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK,
+ json.substring(0, Math.min(json.length(), 30)));
+ }
+
+ accessToken = this.accessToken = RdsAccessToken.createFromJson(json);
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "refreshToken()", e.getClass().getName(), e.getMessage());
+ } catch (JsonParseException | IOException e) {
+ logger.warn(LOG_RUNTIME_EXCEPTION, "refreshToken()", e.getClass().getName(), e.getMessage());
+ }
}
if (accessToken != null) {
@@ -113,12 +151,20 @@ private synchronized void refreshToken() {
* public method: used by RDS smart thermostat handlers to fetch the current
* token
*/
- public synchronized String getToken() {
+ public synchronized String getToken() throws RdsCloudException {
refreshToken();
- return (accessToken != null ? accessToken.getToken() : "");
+ RdsAccessToken accessToken = this.accessToken;
+ if (accessToken != null) {
+ return accessToken.getToken();
+ }
+ throw new RdsCloudException("no access token");
}
- public String getApiKey() {
- return (config != null ? config.apiKey : "");
+ public String getApiKey() throws RdsCloudException {
+ RdsCloudConfiguration config = this.config;
+ if (config != null) {
+ return config.apiKey;
+ }
+ throw new RdsCloudException("no api key");
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsConfiguration.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsConfiguration.java
index 581105b91caa9..8d0ddc42f4fde 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsConfiguration.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsConfiguration.java
@@ -12,14 +12,16 @@
*/
package org.openhab.binding.siemensrds.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* The {@link RdsConfiguration} class contains the thing configuration
* parameters for RDS thermostats
*
* @author Andrew Fiddian-Green - Initial contribution
*/
-
+@NonNullByDefault
public class RdsConfiguration {
- public String plantId;
+ public String plantId = "";
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDataPoints.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDataPoints.java
index a021dca973c23..9c9e03040955f 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDataPoints.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDataPoints.java
@@ -21,34 +21,29 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.smarthome.core.library.types.DecimalType;
-import org.eclipse.smarthome.core.library.types.OnOffType;
-import org.eclipse.smarthome.core.library.types.StringType;
-import org.eclipse.smarthome.core.types.State;
+import org.openhab.binding.siemensrds.points.BasePoint;
+import org.openhab.binding.siemensrds.points.PointDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-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.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
/**
@@ -58,20 +53,29 @@
* @author Andrew Fiddian-Green - Initial contribution
*
*/
-class RdsDataPoints {
+@NonNullByDefault
+public class RdsDataPoints {
/*
* NOTE: requires a static logger because the class has static methods
*/
- protected static final Logger LOGGER = LoggerFactory.getLogger(RdsDataPoints.class);
+ protected final Logger logger = LoggerFactory.getLogger(RdsDataPoints.class);
private static final Gson GSON = new GsonBuilder().registerTypeAdapter(BasePoint.class, new PointDeserializer())
.create();
+ /*
+ * this is a second index into to the JSON "values" points Map below; the
+ * purpose is to allow point lookups by a) pointId (which we do directly from
+ * the Map, and b) by pointClass (which we do indirectly "double dereferenced"
+ * via this index
+ */
+ private final Map indexClassToId = new HashMap<>();
+
@SerializedName("totalCount")
- private String totalCount;
+ private @Nullable String totalCount;
@SerializedName("values")
- private Map points;
+ public @Nullable Map points;
private String valueFilter = "";
@@ -97,10 +101,8 @@ protected static String httpGenericGetJson(String apiKey, String token, String u
https.setRequestProperty(SUBSCRIPTION_KEY, apiKey);
https.setRequestProperty(AUTHORIZATION, String.format(BEARER, token));
- int responseCode = https.getResponseCode();
-
- if (responseCode != HttpURLConnection.HTTP_OK) {
- throw new RdsCloudException("invalid HTTP response");
+ if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ throw new IOException(https.getResponseMessage());
}
try (InputStream inputStream = https.getInputStream();
@@ -116,30 +118,17 @@ protected static String httpGenericGetJson(String apiKey, String token, String u
}
/*
- * public static method: execute a GET on the cloud server, parse the JSON, and
- * create a real instance of this class that encapsulates all the retrieved data
- * point values
+ * public static method: parse the JSON, and create a real instance of this
+ * class that encapsulates the data data point values
*/
- public static @Nullable RdsDataPoints create(String apiKey, String token, String plantId) {
- try {
- String json = httpGenericGetJson(apiKey, token, String.format(URL_POINTS, plantId));
-
- LOGGER.debug("create: received {} characters (log:set TRACE to see all)", json.length());
- if (LOGGER.isTraceEnabled()) {
- LOGGER.trace("create: response={}", json);
- }
-
- return GSON.fromJson(json, RdsDataPoints.class);
- } catch (JsonSyntaxException | RdsCloudException | IOException e) {
- LOGGER.warn("create {}: \"{}\"", e.getClass().getName(), e.getMessage());
- return null;
- }
+ public static @Nullable RdsDataPoints createFromJson(String json) {
+ return GSON.fromJson(json, RdsDataPoints.class);
}
/*
* private method: execute an HTTP PUT on the server to set a data point value
*/
- private void httpSetPointValueJson(String apiKey, String token, String pointId, String json)
+ private void httpSetPointValueJson(String apiKey, String token, String pointUrl, String json)
throws RdsCloudException, UnsupportedEncodingException, ProtocolException, MalformedURLException,
IOException {
/*
@@ -147,7 +136,7 @@ private void httpSetPointValueJson(String apiKey, String token, String pointId,
* preferred JETTY library; the reason is that JETTY does not allow sending the
* square brackets characters "[]" verbatim over HTTP connections
*/
- URL url = new URL(String.format(URL_SETVAL, pointId));
+ URL url = new URL(pointUrl);
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
@@ -166,150 +155,95 @@ private void httpSetPointValueJson(String apiKey, String token, String pointId,
}
if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
- throw new RdsCloudException("invalid HTTP response");
+ throw new IOException(https.getResponseMessage());
}
}
/*
- * private method: retrieve the data point with the given hierarchyName
+ * public method: retrieve the data point with the given pointClass
*/
- private BasePoint getPoint(String hierarchyName) {
- if (hierarchyName != null) {
- for (Map.Entry entry : points.entrySet()) {
- BasePoint point = entry.getValue();
- if (point != null && point.hierarchyName != null && point.hierarchyName.contains(hierarchyName)) {
- return point;
- }
- }
+ public BasePoint getPointByClass(String pointClass) throws RdsCloudException {
+ if (indexClassToId.isEmpty()) {
+ initClassToIdNameIndex();
}
- return null;
- }
-
- /*
- * private method: retrieve the data point with the given hierarchyName
- */
- private String getPointId(String hierarchyName) {
- if (hierarchyName != null) {
- for (Map.Entry entry : points.entrySet()) {
- BasePoint point = entry.getValue();
- if (point != null && point.hierarchyName != null && point.hierarchyName.contains(hierarchyName)) {
- return entry.getKey();
- }
- }
+ @Nullable
+ String pointId = indexClassToId.get(pointClass);
+ if (pointId != null) {
+ return getPointById(pointId);
}
- return null;
+ throw new RdsCloudException(String.format("pointClass \"%s\" not found", pointClass));
}
/*
- * public method: retrieve the state of the data point with the given
- * hierarchyName
+ * public method: retrieve the data point with the given pointId
*/
- public synchronized State getRaw(String hierarchyName) {
- BasePoint point = getPoint(hierarchyName);
- if (point != null) {
- State state = point.getRaw();
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("getRaw: {}={}", hierarchyName, state.toString());
- }
- return state;
- }
- LOGGER.warn("getRaw: {}=No Value!", hierarchyName);
- return null;
- }
-
- /*
- * public method: return the presentPriority of the data point with the given
- * hierarchyName
- */
- public synchronized int getPresPrio(String hierarchyName) {
- BasePoint point = getPoint(hierarchyName);
- if (point != null) {
- int presentPriority = point.getPresentPriority();
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("getPresentPriority: {}={}", hierarchyName, presentPriority);
+ public BasePoint getPointById(String pointId) throws RdsCloudException {
+ Map points = this.points;
+ if (points != null) {
+ @Nullable
+ BasePoint point = points.get(pointId);
+ if (point != null) {
+ return point;
}
- return presentPriority;
}
- LOGGER.warn("getPresentPriority: {}=No Value!", hierarchyName);
- return 0;
+ throw new RdsCloudException(String.format("pointId \"%s\" not found", pointId));
}
/*
- * public method: return the presentPriority of the data point with the given
- * hierarchyName
+ * private method: retrieve Id of data point with the given pointClass
*/
- public synchronized int asInt(String hierarchyName) {
- BasePoint point = getPoint(hierarchyName);
- if (point != null) {
- int value = point.asInt();
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("asInt: {}={}", hierarchyName, value);
- }
- return value;
+ public String pointClassToId(String pointClass) throws RdsCloudException {
+ if (indexClassToId.isEmpty()) {
+ initClassToIdNameIndex();
}
- LOGGER.warn("getAsInt: {}=No Value!", hierarchyName);
- return 0;
- }
-
- /*
- * public method: retrieve the enum state of the data point with the given
- * hierarchyName
- */
- public synchronized State getEnum(String hierarchyName) {
- BasePoint point = getPoint(hierarchyName);
- if (point != null) {
- State state = point.getEnum();
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("getEnum: {}={}", hierarchyName, state.toString());
- }
- return state;
+ @Nullable
+ String pointId = indexClassToId.get(pointClass);
+ if (pointId != null) {
+ return pointId;
}
- LOGGER.warn("getEnum: {}=No Value!", hierarchyName);
- return null;
+ throw new RdsCloudException(String.format("no pointId to match pointClass \"%s\"", pointClass));
}
/*
* public method: return the state of the "Online" data point
*/
- public Boolean isOnline() {
- for (Map.Entry entry : points.entrySet()) {
- BasePoint point = entry.getValue();
- if (point != null && point.memberName != null && point.memberName.equals("Online")) {
- return (point.asInt() == 1);
- }
- }
- return false;
+ public boolean isOnline() throws RdsCloudException {
+ BasePoint point = getPointByClass(HIE_ONLINE);
+ return "Online".equals(point.getEnum().toString());
}
/*
* public method: set a new data point value on the server
*/
- public void setValue(String apiKey, String token, String hierarchyName, String value) {
- String pointId = getPointId(hierarchyName);
- BasePoint point = getPoint(hierarchyName);
-
- if (pointId != null && point != null) {
- String json = point.commandJson(value);
-
- LOGGER.debug("setValue: {}=>{}", hierarchyName, json);
-
- try {
- httpSetPointValueJson(apiKey, token, pointId, json);
- } catch (RdsCloudException | IOException e) {
- LOGGER.warn("setValue {} {}: \"{}\"", hierarchyName, e.getClass().getName(), e.getMessage());
- return;
+ public void setValue(String apiKey, String token, String pointClass, String value) {
+ try {
+ String pointId = pointClassToId(pointClass);
+ BasePoint point = getPointByClass(pointClass);
+
+ String url = String.format(URL_SETVAL, pointId);
+ String payload = point.commandJson(value);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_HTTP_COMMAND, HTTP_PUT, url.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
+ logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, payload);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_PUT, url.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK,
+ payload.substring(0, Math.min(payload.length(), 30)));
}
- } else {
- LOGGER.warn("setValue: point or pointId not found for {}", hierarchyName);
+
+ httpSetPointValueJson(apiKey, token, url, payload);
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "setValue()", e.getClass().getName(), e.getMessage());
+ } catch (JsonParseException | IOException e) {
+ logger.warn(LOG_RUNTIME_EXCEPTION, "setValue()", e.getClass().getName(), e.getMessage());
}
}
/*
- * public method: set a new data point value on the server
+ * public method: refresh the data point value from the server
*/
public boolean refresh(String apiKey, String token) {
try {
@@ -319,337 +253,117 @@ public boolean refresh(String apiKey, String token) {
String pointId;
for (ChannelMap chan : CHAN_MAP) {
- pointId = getPointId(chan.hierarchyName);
- if (pointId != null) {
+ pointId = pointClassToId(chan.clazz);
+ if (!pointId.isEmpty()) {
set.add(String.format("\"%s\"", pointId));
}
}
- for (Map.Entry entry : points.entrySet()) {
- BasePoint point = entry.getValue();
- if (point != null && point.memberName != null && point.memberName.equals("Online")) {
- set.add(String.format("\"%s\"", entry.getKey()));
- break;
+ Map points = this.points;
+ if (points != null) {
+ for (Map.Entry entry : points.entrySet()) {
+ @Nullable
+ BasePoint point = entry.getValue();
+ if (point != null) {
+ if ("Online".equals(point.getMemberName())) {
+ set.add(String.format("\"%s\"", entry.getKey()));
+ break;
+ }
+ }
}
}
valueFilter = String.join(",", set);
}
- if (LOGGER.isTraceEnabled()) {
- LOGGER.trace("refresh: request={}", valueFilter);
- }
+ String url = String.format(URL_VALUES, valueFilter);
- String json = httpGenericGetJson(apiKey, token, String.format(URL_VALUES, valueFilter));
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_HTTP_COMMAND, HTTP_GET, url.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_GET, url.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
+ }
- LOGGER.debug("refresh: received {} characters (log:set TRACE to see all)", json.length());
+ String json = httpGenericGetJson(apiKey, token, url);
- if (LOGGER.isTraceEnabled()) {
- LOGGER.trace("refresh: response={}", json);
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK, json.substring(0, Math.min(json.length(), 30)));
}
@Nullable
RdsDataPoints newPoints = GSON.fromJson(json, RdsDataPoints.class);
+ Map newPointsMap = newPoints.points;
+
+ if (newPointsMap == null) {
+ throw new RdsCloudException("new points map empty");
+ }
+
synchronized (this) {
- for (Map.Entry entry : newPoints.points.entrySet()) {
+ for (Entry entry : newPointsMap.entrySet()) {
+ @Nullable
+ String pointId = entry.getKey();
+
+ @Nullable
BasePoint newPoint = entry.getValue();
- BasePoint existingPoint = points.get(entry.getKey());
+ if (newPoint == null) {
+ throw new RdsCloudException("invalid new point");
+ }
- if (newPoint instanceof BooleanPoint && existingPoint instanceof BooleanPoint) {
- ((BooleanPoint) existingPoint).value = ((BooleanPoint) newPoint).value;
- } else
+ @Nullable
+ BasePoint myPoint = getPointById(pointId);
- if (newPoint instanceof TextPoint && existingPoint instanceof TextPoint) {
- ((TextPoint) existingPoint).value = ((TextPoint) newPoint).value;
- } else
+ if (!(newPoint.getClass().equals(myPoint.getClass()))) {
+ throw new RdsCloudException("existing vs. new point class mismatch");
+ }
- if (newPoint instanceof NumericPoint && existingPoint instanceof NumericPoint) {
- ((NumericPoint) existingPoint).value = ((NumericPoint) newPoint).value;
- } else
+ myPoint.refreshValueFrom((BasePoint) newPoint);
- if (newPoint instanceof InnerValuePoint && existingPoint instanceof InnerValuePoint) {
- ((InnerValuePoint) existingPoint).inner.value = ((InnerValuePoint) newPoint).inner.value;
- ((InnerValuePoint) existingPoint).inner.presentPriority = ((InnerValuePoint) newPoint).inner.presentPriority;
- } else {
- LOGGER.warn("refresh: point type mismatch");
+ if (logger.isDebugEnabled()) {
+ logger.debug("refresh {}.{}: {} << {}", getDescription(), myPoint.getPointClass(),
+ myPoint.getState(), ((BasePoint) newPoint).getState());
}
-
}
}
return true;
- } catch (JsonSyntaxException | RdsCloudException | IOException e) {
- LOGGER.warn("refresh {}: \"{}\"", e.getClass().getName(), e.getMessage());
- return false;
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "refresh()", e.getClass().getName(), e.getMessage());
+ } catch (JsonParseException | IOException e) {
+ logger.warn(LOG_RUNTIME_EXCEPTION, "refresh()", e.getClass().getName(), e.getMessage());
}
- }
-}
-
-/**
- * private class: a generic data point
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-abstract class BasePoint {
- @SerializedName("rep")
- protected int rep;
- @SerializedName("type")
- protected int type;
- @SerializedName("write")
- protected boolean write;
- @SerializedName("descr")
- protected String descr;
- @SerializedName("limits")
- protected float[] limits;
- @SerializedName("descriptionName")
- protected String descriptionName;
- @SerializedName("objectName")
- protected String objectName;
- @SerializedName("memberName")
- protected String memberName;
- @SerializedName("hierarchyName")
- protected String hierarchyName;
- @SerializedName("translated")
- protected boolean translated;
-
- private String[] enumVals;
- private boolean enumParsed = false;
- protected boolean isEnum = false;
-
- /*
- * initialize the enum value list
- */
- private boolean initEnum() {
- if (!enumParsed) {
- if (descr != null && descr.contains("*")) {
- enumVals = descr.split("\\*");
- isEnum = true;
- }
- }
- enumParsed = true;
- return isEnum;
- }
-
- public int getPresentPriority() {
- return 0;
- }
-
- /*
- * => MUST be overridden
- */
- protected abstract int asInt();
-
- protected boolean isEnum() {
- return (enumParsed ? isEnum : initEnum());
- }
-
- public State getEnum() {
- if (isEnum()) {
- int index = asInt();
- if (index >= 0 && index < enumVals.length) {
- return new StringType(enumVals[index]);
- }
- }
- return null;
- }
-
- /*
- * property getter for openHAB => MUST be overridden
- */
- public State getRaw() {
- return null;
- }
-
- /*
- * property getter for openHAB returns the "Units" of the point value
- */
- public String getUnits() {
- return (descr != null ? descr : "");
+ return false;
}
/*
- * property getter for JSON => MAY be overridden
+ * initialize the second index into to the points Map
*/
- public String commandJson(String newVal) {
- if (isEnum()) {
- for (int index = 0; index < enumVals.length; index++) {
- if (enumVals[index].equals(newVal)) {
- return String.format("{\"value\":%d}", index);
+ private void initClassToIdNameIndex() {
+ Map points = this.points;
+ if (points != null) {
+ indexClassToId.clear();
+ for (Entry entry : points.entrySet()) {
+ @Nullable
+ String pointKey = entry.getKey();
+ @Nullable
+ BasePoint pointValue = entry.getValue();
+ if (pointValue != null) {
+ indexClassToId.put(pointValue.getPointClass(), pointKey);
}
}
}
- return String.format("{\"value\":%s}", newVal);
- }
-}
-
-/**
- * private class a data point where "value" is a JSON text element
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-class TextPoint extends BasePoint {
- @SerializedName("value")
- protected String value;
-
- @Override
- protected int asInt() {
- try {
- return Integer.parseInt(value);
- } catch (Exception e) {
- return -1;
- }
- }
-
- /*
- * if appropriate return the enum string representation, otherwise return the
- * decimal representation
- */
- @Override
- public State getRaw() {
- return new StringType(value);
- }
-}
-
-/**
- * private class a data point where "value" is a JSON boolean element
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-class BooleanPoint extends BasePoint {
- @SerializedName("value")
- protected boolean value;
-
- @Override
- protected int asInt() {
- return (value ? 1 : 0);
- }
-
- @Override
- public State getRaw() {
- return OnOffType.from(value);
- }
-}
-
-/**
- * private class inner (helper) class for an embedded JSON numeric element
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-class NestedValue {
- @SerializedName("value")
- protected float value;
- @SerializedName("presentPriority")
- protected float presentPriority;
-}
-
-/**
- * private class a data point where "value" is a nested JSON numeric element
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-class InnerValuePoint extends BasePoint {
- @SerializedName("value")
- protected NestedValue inner;
-
- @Override
- protected int asInt() {
- return (inner != null ? (int) inner.value : -1);
- }
-
- /*
- * if appropriate return the enum string representation, otherwise return the
- * decimal representation
- */
- @Override
- public State getRaw() {
- if (inner != null) {
- return new DecimalType(inner.value);
- }
- return null;
- }
-
- @Override
- public int getPresentPriority() {
- if (inner != null) {
- return (int) inner.presentPriority;
- }
- return 0;
- }
-}
-
-/**
- * private class a data point where "value" is a JSON numeric element
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-class NumericPoint extends BasePoint {
- @SerializedName("value")
- protected float value;
-
- @Override
- protected int asInt() {
- return (int) value;
}
/*
- * if appropriate return the enum string representation, otherwise return the
- * decimal representation
+ * public method: return the state of the "Description" data point
*/
- @Override
- public State getRaw() {
- return new DecimalType(value);
- }
-}
-
-/**
- * private class a JSON de-serializer for the Data Point classes above
- *
- * @author Andrew Fiddian-Green - Initial contribution
- *
- */
-class PointDeserializer implements JsonDeserializer {
-
- @Override
- public BasePoint deserialize(JsonElement element, Type guff, JsonDeserializationContext ctxt)
- throws JsonParseException {
- JsonObject obj = element.getAsJsonObject();
- JsonElement val = obj.get("value");
-
- if (val != null) {
- if (val.isJsonPrimitive()) {
- if (val.getAsJsonPrimitive().isBoolean()) {
- return ctxt.deserialize(obj, BooleanPoint.class);
- }
- if (val.getAsJsonPrimitive().isNumber()) {
- return ctxt.deserialize(obj, NumericPoint.class);
- }
- if (val.getAsJsonPrimitive().isString()) {
- return ctxt.deserialize(obj, TextPoint.class);
- }
- } else {
- if (val.isJsonObject()) {
- JsonElement rep = obj.get("rep");
- if (rep == null) {
- return ctxt.deserialize(obj, InnerValuePoint.class);
- } else {
- if (rep.isJsonPrimitive() && rep.getAsJsonPrimitive().isNumber()) {
- switch (rep.getAsInt()) {
- case 1:
- case 3:
- return ctxt.deserialize(obj, InnerValuePoint.class);
- }
- }
- }
- }
- }
- }
- return null;
+ public String getDescription() throws RdsCloudException {
+ return getPointByClass(HIE_DESCRIPTION).getState().toString();
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDebouncer.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDebouncer.java
index bcfa6195e2173..0c2ac5ef0d152 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDebouncer.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDebouncer.java
@@ -18,27 +18,33 @@
import java.util.HashMap;
import java.util.Map;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
/**
* The {@link RdsDebouncer} determines if change events should be forwarded to a
* channel
*
* @author Andrew Fiddian-Green - Initial contribution
*/
+@NonNullByDefault
public class RdsDebouncer {
- private final Map channels = new HashMap<>();
+ private final Map channels = new HashMap<>();
+ @SuppressWarnings("null")
+ @NonNullByDefault
static class DebounceDelay {
private long expireTime;
- public DebounceDelay(Boolean enabled) {
+ public DebounceDelay(boolean enabled) {
if (enabled) {
expireTime = new Date().getTime() + (DEBOUNCE_DELAY * 1000);
}
}
- public Boolean timeExpired() {
+ public boolean timeExpired() {
return (expireTime < new Date().getTime());
}
}
@@ -51,6 +57,13 @@ public void initialize(String channelId) {
}
public Boolean timeExpired(String channelId) {
- return (channels.containsKey(channelId) ? channels.get(channelId).timeExpired() : true);
+ if (channels.containsKey(channelId)) {
+ @Nullable
+ DebounceDelay debounceDelay = channels.get(channelId);
+ if (debounceDelay != null) {
+ return ((DebounceDelay) debounceDelay).timeExpired();
+ }
+ }
+ return true;
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDiscoveryService.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDiscoveryService.java
index bb074bd25a987..183abbc8edd84 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDiscoveryService.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsDiscoveryService.java
@@ -14,13 +14,17 @@
import static org.openhab.binding.siemensrds.internal.RdsBindingConstants.*;
+import java.io.IOException;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService;
import org.eclipse.smarthome.config.discovery.DiscoveryResult;
import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder;
@@ -28,21 +32,26 @@
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.types.State;
+import org.openhab.binding.siemensrds.internal.RdsPlants.PlantInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonParseException;
+
/**
* Discovery service for Siemens RDS thermostats
*
* @author Andrew Fiddian-Green - Initial contribution
*
*/
+@NonNullByDefault
public class RdsDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(RdsDiscoveryService.class);
- private ScheduledFuture> discoveryScheduler;
- private RdsCloudHandler cloud;
+ private @Nullable ScheduledFuture> discoveryScheduler;
+
+ private @Nullable RdsCloudHandler cloud;
public static final Set DISCOVERABLE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_RDS).collect(Collectors.toSet()));
@@ -64,10 +73,17 @@ public void deactivate() {
@Override
protected void startScan() {
- if (cloud.getThing().getStatus() != ThingStatus.ONLINE) {
- cloud.getToken();
+ RdsCloudHandler cloud = this.cloud;
+
+ if (cloud != null && cloud.getThing().getStatus() != ThingStatus.ONLINE) {
+ try {
+ cloud.getToken();
+ } catch (RdsCloudException e) {
+ logger.debug("unexpected: {} = \"{}\"", e.getClass().getName(), e.getMessage());
+ }
}
- if (cloud.getThing().getStatus() == ThingStatus.ONLINE) {
+
+ if (cloud != null && cloud.getThing().getStatus() == ThingStatus.ONLINE) {
discoverPlants();
}
}
@@ -76,8 +92,9 @@ protected void startScan() {
protected void startBackgroundDiscovery() {
logger.debug("start background discovery..");
+ ScheduledFuture> discoveryScheduler = this.discoveryScheduler;
if (discoveryScheduler == null || discoveryScheduler.isCancelled()) {
- discoveryScheduler = scheduler.scheduleWithFixedDelay(this::startScan, 10, DISCOVERY_REFRESH_PERIOD,
+ this.discoveryScheduler = scheduler.scheduleWithFixedDelay(this::startScan, 10, DISCOVERY_REFRESH_PERIOD,
TimeUnit.SECONDS);
}
}
@@ -86,53 +103,109 @@ protected void startBackgroundDiscovery() {
protected void stopBackgroundDiscovery() {
logger.debug("stop background discovery..");
+ ScheduledFuture> discoveryScheduler = this.discoveryScheduler;
if (discoveryScheduler != null && !discoveryScheduler.isCancelled()) {
discoveryScheduler.cancel(true);
+ this.discoveryScheduler = null;
}
}
private void discoverPlants() {
+ RdsCloudHandler cloud = this.cloud;
+
if (cloud != null) {
- RdsPlants plants = RdsPlants.create(cloud.getApiKey(), cloud.getToken());
- if (plants != null) {
- for (RdsPlants.PlantInfo plant : plants.getPlants()) {
- publishPlant(plant);
+ @Nullable
+ RdsPlants plantClass = null;
+
+ try {
+ String url = URL_PLANTS;
+
+ logger.debug(LOG_HTTP_COMMAND, HTTP_GET, url.length());
+ logger.debug(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
+
+ String json = RdsDataPoints.httpGenericGetJson(cloud.getApiKey(), cloud.getToken(), url);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK,
+ json.substring(0, Math.min(json.length(), 30)));
+ }
+
+ plantClass = RdsPlants.createFromJson(json);
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "discoverPlants()", e.getClass().getName(), e.getMessage());
+ return;
+ } catch (JsonParseException | IOException e) {
+ logger.warn(LOG_RUNTIME_EXCEPTION, "discoverPlants()", e.getClass().getName(), e.getMessage());
+ return;
+ }
+
+ if (plantClass != null) {
+ List plants = plantClass.getPlants();
+ if (plants != null) {
+ for (PlantInfo plant : plants) {
+ publishPlant(plant);
+ }
}
}
}
}
- private void publishPlant(RdsPlants.PlantInfo plant) {
- if (plant != null) {
+ private void publishPlant(PlantInfo plant) {
+ RdsCloudHandler cloud = this.cloud;
+ try {
+ if (cloud == null) {
+ throw new RdsCloudException("missing cloud handler");
+ }
+
String plantId = plant.getId();
+ String url = String.format(URL_POINTS, plantId);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_HTTP_COMMAND, HTTP_GET, url.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_GET, url.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
+ }
- if (plantId != null && !plantId.isEmpty()) {
- RdsDataPoints points = RdsDataPoints.create(cloud.getApiKey(), cloud.getToken(), plantId);
+ String json = RdsDataPoints.httpGenericGetJson(cloud.getApiKey(), cloud.getToken(), url);
- if (points != null) {
- State desc = points.getRaw(HIE_DESCRIPTION);
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK, json.substring(0, Math.min(json.length(), 30)));
+ }
- if (desc != null) {
- String label = desc.toString().replaceAll("\\s+", "_");
+ RdsDataPoints points = RdsDataPoints.createFromJson(json);
+ if (points == null) {
+ throw new RdsCloudException("no points returned");
+ }
- ThingTypeUID typeUID = THING_TYPE_RDS;
- ThingUID bridgeUID = cloud.getThing().getUID();
- ThingUID plantUID = new ThingUID(typeUID, bridgeUID, plantId);
+ State desc = points.getPointByClass(HIE_DESCRIPTION).getState();
+ String label = desc.toString().replaceAll("\\s+", "_");
- DiscoveryResult disco = DiscoveryResultBuilder.create(plantUID).withBridge(bridgeUID)
- .withLabel(label).withProperty(PROP_PLANT_ID, plantId)
- .withRepresentationProperty(PROP_PLANT_ID).build();
+ ThingTypeUID typeUID = THING_TYPE_RDS;
+ ThingUID bridgeUID = cloud.getThing().getUID();
+ ThingUID plantUID = new ThingUID(typeUID, bridgeUID, plantId);
- logger.debug("discovered typeUID={}, plantUID={}, brigeUID={}, label={}, plantId={}, ", typeUID,
- plantUID, bridgeUID, label, plantId);
+ DiscoveryResult disco = DiscoveryResultBuilder.create(plantUID).withBridge(bridgeUID).withLabel(label)
+ .withProperty(PROP_PLANT_ID, plantId).withRepresentationProperty(PROP_PLANT_ID).build();
- thingDiscovered(disco);
+ logger.debug("discovered typeUID={}, plantUID={}, brigeUID={}, label={}, plantId={}, ", typeUID, plantUID,
+ bridgeUID, label, plantId);
- return;
- }
- }
- }
+ thingDiscovered(disco);
+ ;
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "publishPlant()", e.getClass().getName(), e.getMessage());
+ } catch (JsonParseException | IOException e) {
+ logger.warn(LOG_RUNTIME_EXCEPTION, "publishPlant()", e.getClass().getName(), e.getMessage());
}
- logger.debug("discovery error!");
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandler.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandler.java
index e9ba2fa22d90d..b9f702933621c 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandler.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandler.java
@@ -14,12 +14,17 @@
import static org.openhab.binding.siemensrds.internal.RdsBindingConstants.*;
+import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.OnOffType;
+import org.eclipse.smarthome.core.library.types.QuantityType;
import org.eclipse.smarthome.core.library.types.StringType;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
@@ -32,9 +37,12 @@
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.RefreshType;
import org.eclipse.smarthome.core.types.State;
+import org.openhab.binding.siemensrds.points.BasePoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonParseException;
+
/**
* The {@link RdsHandler} is the OpenHab Handler for Siemens RDS smart
* thermostats
@@ -42,20 +50,21 @@
* @author Andrew Fiddian-Green - Initial contribution
*
*/
+@NonNullByDefault
public class RdsHandler extends BaseThingHandler {
protected final Logger logger = LoggerFactory.getLogger(RdsHandler.class);
- private ScheduledFuture> lazyPollingScheduler;
- private ScheduledFuture> fastPollingScheduler;
+ private @Nullable ScheduledFuture> lazyPollingScheduler = null;
+ private @Nullable ScheduledFuture> fastPollingScheduler = null;
private final AtomicInteger fastPollingCallsToGo = new AtomicInteger();
private RdsDebouncer debouncer = new RdsDebouncer();
- private RdsConfiguration config = null;
+ private @Nullable RdsConfiguration config = null;
- private RdsDataPoints points = null;
+ private @Nullable RdsDataPoints points = null;
public RdsHandler(Thing thing) {
super(thing);
@@ -73,50 +82,53 @@ public void handleCommand(ChannelUID channelUID, Command command) {
public void initialize() {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING);
- config = getConfigAs(RdsConfiguration.class);
+ RdsConfiguration config = this.config = getConfigAs(RdsConfiguration.class);
- if (config == null || config.plantId == null || config.plantId.isEmpty()) {
+ if (config.plantId.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing Plant Id");
return;
}
- RdsCloudHandler cloud = getCloudHandler();
+ updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING);
- if (cloud == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing cloud server handler");
- return;
- }
+ try {
+ RdsCloudHandler cloud = getCloudHandler();
+
+ if (cloud.getThing().getStatus() != ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "cloud server offline");
+ return;
+ }
- if (cloud.getThing().getStatus() != ThingStatus.ONLINE) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "cloud server offline");
+ initializePolling();
+ } catch (RdsCloudException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing cloud server handler");
return;
}
-
- initializePolling();
}
public void initializePolling() {
- RdsCloudHandler cloud = getCloudHandler();
-
- if (cloud != null) {
- int pollInterval = cloud.getPollInterval();
-
- if (pollInterval > 0) {
- // create a "lazy" polling scheduler
- if (lazyPollingScheduler == null || lazyPollingScheduler.isCancelled()) {
- lazyPollingScheduler = scheduler.scheduleWithFixedDelay(this::lazyPollingSchedulerExecute,
- pollInterval, pollInterval, TimeUnit.SECONDS);
- }
-
- // create a "fast" polling scheduler
- fastPollingCallsToGo.set(FAST_POLL_CYCLES);
- if (fastPollingScheduler == null || fastPollingScheduler.isCancelled()) {
- fastPollingScheduler = scheduler.scheduleWithFixedDelay(this::fastPollingSchedulerExecute,
- FAST_POLL_INTERVAL, FAST_POLL_INTERVAL, TimeUnit.SECONDS);
- }
+ try {
+ int pollInterval = getCloudHandler().getPollInterval();
+
+ // create a "lazy" polling scheduler
+ ScheduledFuture> lazyPollingScheduler = this.lazyPollingScheduler;
+ if (lazyPollingScheduler == null || lazyPollingScheduler.isCancelled()) {
+ this.lazyPollingScheduler = scheduler.scheduleWithFixedDelay(this::lazyPollingSchedulerExecute,
+ pollInterval, pollInterval, TimeUnit.SECONDS);
+ }
- startFastPollingBurst();
+ // create a "fast" polling scheduler
+ fastPollingCallsToGo.set(FAST_POLL_CYCLES);
+ ScheduledFuture> fastPollingScheduler = this.fastPollingScheduler;
+ if (fastPollingScheduler == null || fastPollingScheduler.isCancelled()) {
+ this.fastPollingScheduler = scheduler.scheduleWithFixedDelay(this::fastPollingSchedulerExecute,
+ FAST_POLL_INTERVAL, FAST_POLL_INTERVAL, TimeUnit.SECONDS);
}
+
+ startFastPollingBurst();
+ } catch (RdsCloudException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ logger.warn(LOG_SYSTEM_EXCEPTION, "initializePolling()", e.getClass().getName(), e.getMessage());
}
}
@@ -132,15 +144,17 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
@Override
public void dispose() {
// clean up the lazy polling scheduler
+ ScheduledFuture> lazyPollingScheduler = this.lazyPollingScheduler;
if (lazyPollingScheduler != null && !lazyPollingScheduler.isCancelled()) {
lazyPollingScheduler.cancel(true);
- lazyPollingScheduler = null;
+ this.lazyPollingScheduler = null;
}
// clean up the fast polling scheduler
+ ScheduledFuture> fastPollingScheduler = this.fastPollingScheduler;
if (fastPollingScheduler != null && !fastPollingScheduler.isCancelled()) {
fastPollingScheduler.cancel(true);
- fastPollingScheduler = null;
+ this.fastPollingScheduler = null;
}
}
@@ -178,96 +192,92 @@ private void fastPollingSchedulerExecute() {
* states
*/
private void doPollNow() {
- RdsCloudHandler cloud = getCloudHandler();
-
- if (cloud == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing cloud server handler");
- return;
- }
+ try {
+ RdsCloudHandler cloud = getCloudHandler();
- String token = cloud.getToken();
-
- if (cloud.getThing().getStatus() != ThingStatus.ONLINE) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "cloud server offline");
- return;
- }
+ if (cloud.getThing().getStatus() != ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "cloud server offline");
+ return;
+ }
- String apiKey = cloud.getApiKey();
+ RdsDataPoints points = this.points;
+ if ((points == null || (!points.refresh(cloud.getApiKey(), cloud.getToken())))) {
+ points = fetchPoints();
+ }
- if (points == null || (!points.refresh(apiKey, token))) {
- points = RdsDataPoints.create(apiKey, token, config.plantId);
- }
+ if (points == null) {
+ if (getThing().getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing data points");
+ }
+ throw new RdsCloudException("missing data points");
+ }
- if (points == null) {
- if (getThing().getStatus() == ThingStatus.ONLINE) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "cloud server has no info this device");
+ if (!points.isOnline()) {
+ if (getThing().getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "cloud server reports device offline");
+ }
+ return;
}
- return;
- }
- if (!points.isOnline()) {
- if (getThing().getStatus() == ThingStatus.ONLINE) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "cloud server reports device offline");
+ if (getThing().getStatus() != ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "server response ok");
}
- return;
- }
- if (getThing().getStatus() != ThingStatus.ONLINE) {
- updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "received info from cloud server");
- }
+ for (ChannelMap channel : CHAN_MAP) {
+ if (!debouncer.timeExpired(channel.id)) {
+ continue;
+ }
- for (ChannelMap chan : CHAN_MAP) {
- if (debouncer.timeExpired(chan.channelId)) {
+ BasePoint point = points.getPointByClass(channel.clazz);
State state = null;
- switch (chan.channelId) {
+ switch (channel.id) {
case CHA_ROOM_TEMP:
case CHA_ROOM_HUMIDITY:
case CHA_OUTSIDE_TEMP:
case CHA_TARGET_TEMP: {
- state = points.getRaw(chan.hierarchyName);
+ state = point.getState();
break;
}
case CHA_ROOM_AIR_QUALITY:
case CHA_ENERGY_SAVINGS_LEVEL: {
- state = points.getEnum(chan.hierarchyName);
+ state = point.getEnum();
break;
}
case CHA_OUTPUT_STATE: {
- state = points.getEnum(chan.hierarchyName);
- /*
- * convert the state text "Neither" to the more easy to understand word "Off"
- */
- if (state.toString().equals(STATE_NEITHER)) {
+ state = point.getEnum();
+ // convert the state text "Neither" to the easier to understand word "Off"
+ if (STATE_NEITHER.equals(state.toString())) {
state = new StringType(STATE_OFF);
}
break;
}
case CHA_STAT_AUTO_MODE: {
- state = OnOffType.from(points.getPresPrio(chan.hierarchyName) > 13
- || points.asInt(HIE_STAT_OCC_MODE_PRESENT) == 2);
+ state = OnOffType.from(point.getPresentPriority() > 13
+ || points.getPointByClass(HIE_STAT_OCC_MODE_PRESENT).asInt() == 2);
break;
}
case CHA_STAT_OCC_MODE_PRESENT: {
- state = OnOffType.from(points.asInt(chan.hierarchyName) == 3);
+ state = OnOffType.from(point.asInt() == 3);
break;
}
case CHA_DHW_AUTO_MODE: {
- state = OnOffType.from(points.getPresPrio(chan.hierarchyName) > 13);
+ state = OnOffType.from(point.getPresentPriority() > 13);
break;
}
case CHA_DHW_OUTPUT_STATE: {
- state = OnOffType.from(points.asInt(chan.hierarchyName) == 2);
+ state = OnOffType.from(point.asInt() == 2);
break;
}
}
if (state != null) {
- updateState(chan.channelId, state);
+ updateState(channel.id, state);
}
}
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "doPollNow()", e.getClass().getName(), e.getMessage());
}
}
@@ -275,18 +285,34 @@ private void doPollNow() {
* private method: sends a new channel value to the cloud server
*/
private synchronized void doHandleCommand(String channelId, Command command) {
- RdsCloudHandler cloud = getCloudHandler();
- if (cloud != null && points == null) {
- points = RdsDataPoints.create(cloud.getApiKey(), cloud.getToken(), config.plantId);
- }
+ RdsDataPoints points = this.points;
+ try {
+ RdsCloudHandler cloud = getCloudHandler();
+
+ String apiKey = cloud.getApiKey();
+ String token = cloud.getToken();
+
+ if ((points == null || (!points.refresh(apiKey, token)))) {
+ points = fetchPoints();
+ }
+
+ if (points == null) {
+ throw new RdsCloudException("missing data points");
+ }
- if (points != null && cloud != null) {
- for (ChannelMap chan : CHAN_MAP) {
- if (channelId.equals(chan.channelId)) {
- switch (chan.channelId) {
+ for (ChannelMap channel : CHAN_MAP) {
+ if (channelId.equals(channel.id)) {
+ switch (channel.id) {
case CHA_TARGET_TEMP: {
- points.setValue(cloud.getApiKey(), cloud.getToken(), chan.hierarchyName,
- command.format("%s"));
+ Command doCommand = command;
+ if (command instanceof QuantityType>) {
+ Unit> unit = points.getPointByClass(channel.clazz).getUnit();
+ QuantityType> temp = ((QuantityType>) command).toUnit(unit);
+ if (temp != null) {
+ doCommand = temp;
+ }
+ }
+ points.setValue(apiKey, token, channel.clazz, doCommand.format("%s"));
debouncer.initialize(channelId);
break;
}
@@ -296,32 +322,30 @@ private synchronized void doHandleCommand(String channelId, Command command) {
* use Comfort Button = 1 to set to Manual
*/
if (command == OnOffType.ON) {
- points.setValue(cloud.getApiKey(), cloud.getToken(), HIE_ENERGY_SAVINGS_LEVEL, "5");
+ points.setValue(apiKey, token, HIE_ENERGY_SAVINGS_LEVEL, "5");
} else {
- points.setValue(cloud.getApiKey(), cloud.getToken(), HIE_STAT_CMF_BTN, "1");
+ points.setValue(apiKey, token, HIE_STAT_CMF_BTN, "1");
}
debouncer.initialize(channelId);
break;
}
case CHA_STAT_OCC_MODE_PRESENT: {
- points.setValue(cloud.getApiKey(), cloud.getToken(), chan.hierarchyName,
- command == OnOffType.OFF ? "2" : "3");
+ points.setValue(apiKey, token, channel.clazz, command == OnOffType.OFF ? "2" : "3");
debouncer.initialize(channelId);
break;
}
case CHA_DHW_AUTO_MODE: {
if (command == OnOffType.ON) {
- points.setValue(cloud.getApiKey(), cloud.getToken(), chan.hierarchyName, "0");
+ points.setValue(apiKey, token, channel.clazz, "0");
} else {
- points.setValue(cloud.getApiKey(), cloud.getToken(), chan.hierarchyName,
- String.valueOf(points.asInt(chan.hierarchyName)));
+ points.setValue(apiKey, token, channel.clazz,
+ Integer.toString(points.getPointByClass(channel.clazz).asInt()));
}
debouncer.initialize(channelId);
break;
}
case CHA_DHW_OUTPUT_STATE: {
- points.setValue(cloud.getApiKey(), cloud.getToken(), chan.hierarchyName,
- command == OnOffType.OFF ? "1" : "2");
+ points.setValue(apiKey, token, channel.clazz, command == OnOffType.OFF ? "1" : "2");
debouncer.initialize(channelId);
break;
}
@@ -330,30 +354,70 @@ private synchronized void doHandleCommand(String channelId, Command command) {
case CHA_OUTSIDE_TEMP:
case CHA_ROOM_AIR_QUALITY:
case CHA_OUTPUT_STATE: {
- logger.debug("error: unexpected command to channel {}", chan.channelId);
+ logger.debug("error: unexpected command to channel {}", channel.id);
break;
}
}
break;
}
}
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "doHandleCommand()", e.getClass().getName(), e.getMessage());
}
}
/*
* private method: returns the cloud server handler
*/
- private RdsCloudHandler getCloudHandler() {
+ private RdsCloudHandler getCloudHandler() throws RdsCloudException {
@Nullable
Bridge b;
-
@Nullable
BridgeHandler h;
if ((b = getBridge()) != null && (h = b.getHandler()) != null && h instanceof RdsCloudHandler) {
return (RdsCloudHandler) h;
}
+ throw new RdsCloudException("no cloud handler found");
+ }
- return null;
+ public @Nullable RdsDataPoints fetchPoints() {
+ RdsConfiguration config = this.config;
+ try {
+ if (config == null) {
+ throw new RdsCloudException("missing configuration");
+ }
+
+ String url = String.format(URL_POINTS, config.plantId);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_HTTP_COMMAND, HTTP_GET, url.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_GET, url.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
+ }
+
+ RdsCloudHandler cloud = getCloudHandler();
+ String apiKey = cloud.getApiKey();
+ String token = cloud.getToken();
+
+ String json = RdsDataPoints.httpGenericGetJson(apiKey, token, url);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
+ logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
+ logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK, json.substring(0, Math.min(json.length(), 30)));
+ }
+
+ return this.points = RdsDataPoints.createFromJson(json);
+ } catch (RdsCloudException e) {
+ logger.warn(LOG_SYSTEM_EXCEPTION, "fetchPoints()", e.getClass().getName(), e.getMessage());
+ } catch (JsonParseException | IOException e) {
+ logger.warn(LOG_RUNTIME_EXCEPTION, "fetchPoints()", e.getClass().getName(), e.getMessage());
+ }
+ return this.points = null;
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandlerFactory.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandlerFactory.java
index 1266ff2fc9ea8..c511dc14f12fd 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandlerFactory.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsHandlerFactory.java
@@ -22,6 +22,7 @@
import java.util.Map;
import java.util.Set;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.config.discovery.DiscoveryService;
import org.eclipse.smarthome.core.thing.Bridge;
@@ -39,13 +40,14 @@
*
* @author Andrew Fiddian-Green - Initial contribution
*/
+@NonNullByDefault
@Component(configurationPid = "binding.siemensrds", service = ThingHandlerFactory.class)
public class RdsHandlerFactory extends BaseThingHandlerFactory {
private static final Set SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(THING_TYPE_CLOUD, THING_TYPE_RDS)));
- private final Map> discos = new HashMap<>();
+ private final Map> discos = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -103,10 +105,11 @@ private synchronized void createDiscoveryService(RdsCloudHandler handler) {
*/
private synchronized void destroyDiscoveryService(RdsCloudHandler handler) {
// fetch the respective thing's service registration from our list
+ @Nullable
ServiceRegistration> serviceReg = discos.remove(handler.getThing().getUID());
+ // retrieve the respective discovery service
if (serviceReg != null) {
- // retrieve the respective discovery service
RdsDiscoveryService disco = (RdsDiscoveryService) bundleContext.getService(serviceReg.getReference());
// unregister the service
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsPlants.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsPlants.java
index 66c40a5c92d6c..45c510cfe19e3 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsPlants.java
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/internal/RdsPlants.java
@@ -12,17 +12,14 @@
*/
package org.openhab.binding.siemensrds.internal;
-import static org.openhab.binding.siemensrds.internal.RdsBindingConstants.URL_PLANTS;
-
-import java.io.IOException;
import java.util.List;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
-import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
/**
@@ -32,49 +29,49 @@
* @author Andrew Fiddian-Green - Initial contribution
*
*/
-class RdsPlants {
+@NonNullByDefault
+public class RdsPlants {
- protected static final Logger LOGGER = LoggerFactory.getLogger(RdsPlants.class);
+ protected final Logger logger = LoggerFactory.getLogger(RdsPlants.class);
@SerializedName("items")
- private List plants;
+ private @Nullable List plants;
private static final Gson GSON = new Gson();
- static class PlantInfo {
+ @SuppressWarnings("null")
+ @NonNullByDefault
+ public static class PlantInfo {
@SerializedName("id")
- private String plantId;
+ private @Nullable String plantId;
@SerializedName("isOnline")
- private Boolean online;
+ private boolean online;
- public String getId() {
- return plantId;
+ public String getId() throws RdsCloudException {
+ String plantId = this.plantId;
+ if (plantId != null) {
+ return plantId;
+ }
+ throw new RdsCloudException("plant has no Id");
}
- public Boolean isOnline() {
+ public boolean isOnline() {
return online;
}
}
/*
- * public method: execute a GET on the cloud server, parse JSON, and create a
- * class that encapsulates the data
+ * public method: parse JSON, and create a class that encapsulates the data
*/
- public static @Nullable RdsPlants create(String apiKey, String token) {
- try {
- String json = RdsDataPoints.httpGenericGetJson(apiKey, token, URL_PLANTS);
- return GSON.fromJson(json, RdsPlants.class);
- } catch (JsonSyntaxException | RdsCloudException | IOException e) {
- LOGGER.warn("create {}: \"{}\"", e.getClass().getName(), e.getMessage());
- return null;
- }
+ public static @Nullable RdsPlants createFromJson(String json) {
+ return GSON.fromJson(json, RdsPlants.class);
}
/*
* public method: return the plant list
*/
- public List getPlants() {
+ public @Nullable List getPlants() {
return plants;
}
}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/BasePoint.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/BasePoint.java
new file mode 100644
index 0000000000000..c45801dc6cade
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/BasePoint.java
@@ -0,0 +1,187 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.library.unit.ImperialUnits;
+import org.eclipse.smarthome.core.library.unit.SIUnits;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+
+import com.google.gson.annotations.SerializedName;
+
+import tec.uom.se.AbstractUnit;
+import tec.uom.se.unit.Units;
+
+/**
+ * private class: a generic data point
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public abstract class BasePoint {
+ /*
+ * note: temperature symbols with a degree sign: the MVN Spotless formatter
+ * trashes the "degree" (looks like *) symbol, so we must escape these symbols
+ * as octal \260 or unicode \u00B00
+ */
+ public static final String DEGREES_CELSIUS = "\260C";
+ public static final String DEGREES_FAHRENHEIT = "\260F";
+ public static final String DEGREES_KELVIN = "K";
+
+ public static final int UNDEFINED_VALUE = -1;
+
+ @SerializedName("rep")
+ protected int rep;
+ @SerializedName("type")
+ protected int type;
+ @SerializedName("write")
+ protected boolean write;
+ @SerializedName("descr")
+ protected @Nullable String descr;
+ @SerializedName("limits")
+ protected float @Nullable [] limits;
+ @SerializedName("descriptionName")
+ protected @Nullable String descriptionName;
+ @SerializedName("objectName")
+ protected @Nullable String objectName;
+ @SerializedName("memberName")
+ private @Nullable String memberName;
+ @SerializedName("hierarchyName")
+ private @Nullable String hierarchyName;
+ @SerializedName("translated")
+ protected boolean translated;
+ @SerializedName("presentPriority")
+ protected int presentPriority;
+
+ private @Nullable String @Nullable [] enumVals;
+ private boolean enumParsed = false;
+ protected boolean isEnum = false;
+
+ /*
+ * initialize the enum value list
+ */
+ private boolean initEnum() {
+ if (!enumParsed) {
+ String descr = this.descr;
+ if (descr != null && descr.contains("*")) {
+ enumVals = descr.split("\\*");
+ isEnum = true;
+ }
+ }
+ enumParsed = true;
+ return isEnum;
+ }
+
+ public int getPresentPriority() {
+ return presentPriority;
+ }
+
+ /*
+ * abstract methods => MUST be overridden
+ */
+ public abstract int asInt();
+
+ public void refreshValueFrom(BasePoint from) {
+ presentPriority = from.presentPriority;
+ }
+
+ protected boolean isEnum() {
+ return (enumParsed ? isEnum : initEnum());
+ }
+
+ public State getEnum() {
+ if (isEnum()) {
+ int index = asInt();
+ String[] enumVals = this.enumVals;
+ if (index >= 0 && enumVals != null && index < enumVals.length) {
+ return new StringType(enumVals[index]);
+ }
+ }
+ return UnDefType.NULL;
+ }
+
+ /*
+ * property getter for openHAB State => MUST be overridden
+ */
+ public State getState() {
+ return UnDefType.NULL;
+ }
+
+ /*
+ * property getter for openHAB returns the Units of Measure of the point value
+ */
+ public Unit> getUnit() {
+ /*
+ * determine the Units of Measure if available; note that other possible units
+ * (Ampere, hours, milliseconds, minutes) are currently not implemented
+ */
+ String descr = this.descr;
+ if (descr != null) {
+ if (DEGREES_CELSIUS.equals(descr)) {
+ return SIUnits.CELSIUS;
+ } else if (DEGREES_FAHRENHEIT.equals(descr)) {
+ return ImperialUnits.FAHRENHEIT;
+ } else if (DEGREES_KELVIN.equals(descr)) {
+ return Units.KELVIN;
+ }
+ }
+ return AbstractUnit.ONE;
+ }
+
+ /*
+ * property getter for JSON => MAY be overridden
+ */
+ public String commandJson(String newVal) {
+ if (isEnum()) {
+ String[] enumVals = this.enumVals;
+ if (enumVals != null) {
+ for (int index = 0; index < enumVals.length; index++) {
+ if (enumVals[index].equals(newVal)) {
+ return String.format("{\"value\":%d}", index);
+ }
+ }
+ }
+ }
+ return String.format("{\"value\":%s}", newVal);
+ }
+
+ public String getMemberName() {
+ String memberName = this.memberName;
+ return memberName != null ? memberName : "undefined";
+ }
+
+ private @Nullable String hierarchyNameSuffix() {
+ String fullHierarchyName = this.hierarchyName;
+ if (fullHierarchyName != null) {
+ int suffixPosition = fullHierarchyName.lastIndexOf("'");
+ if (suffixPosition >= 0) {
+ return fullHierarchyName.substring(suffixPosition, fullHierarchyName.length());
+ }
+ }
+ return fullHierarchyName;
+ }
+
+ public String getPointClass() {
+ String shortHierarchyName = hierarchyNameSuffix();
+ if (shortHierarchyName != null) {
+ return shortHierarchyName;
+ }
+ return "#".concat(getMemberName());
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NestedNumberPoint.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NestedNumberPoint.java
new file mode 100644
index 0000000000000..c5b9f1fdf9a41
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NestedNumberPoint.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * private class a data point where "value" is a nested JSON numeric element
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NestedNumberPoint extends BasePoint {
+
+ @SerializedName("value")
+ protected @Nullable NestedNumberValue inner;
+
+ @Override
+ public int asInt() {
+ NestedNumberValue inner = this.inner;
+ if (inner != null) {
+ Number innerValue = inner.value;
+ if (innerValue != null) {
+ return innerValue.intValue();
+ }
+ }
+ return UNDEFINED_VALUE;
+ }
+
+ @Override
+ public State getState() {
+ NestedNumberValue inner = this.inner;
+ if (inner != null) {
+ Number innerValue = inner.value;
+ if (innerValue != null) {
+ return new QuantityType<>(innerValue.doubleValue(), getUnit());
+ }
+ }
+ return UnDefType.NULL;
+ }
+
+ @Override
+ public int getPresentPriority() {
+ NestedNumberValue inner = this.inner;
+ return inner != null ? inner.presentPriority : UNDEFINED_VALUE;
+ }
+
+ public void setPresentPriority(int value) {
+ NestedNumberValue inner = this.inner;
+ if (inner != null) {
+ inner.presentPriority = value;
+ }
+ }
+
+ @Override
+ public void refreshValueFrom(BasePoint from) {
+ super.refreshValueFrom(from);
+ if (from instanceof NestedNumberPoint) {
+ NestedNumberValue fromInner = ((NestedNumberPoint) from).inner;
+ NestedNumberValue thisInner = this.inner;
+ if (thisInner != null && fromInner != null) {
+ thisInner.value = fromInner.value;
+ thisInner.presentPriority = fromInner.presentPriority;
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NestedNumberValue.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NestedNumberValue.java
new file mode 100644
index 0000000000000..ec901876c3ff8
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NestedNumberValue.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * private class inner (helper) class for an embedded JSON numeric element
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NestedNumberValue {
+ @SerializedName("value")
+ protected @Nullable Number value;
+ @SerializedName("presentPriority")
+ protected int presentPriority;
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NumberPoint.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NumberPoint.java
new file mode 100644
index 0000000000000..29dd2d5728c71
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/NumberPoint.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.types.State;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * private class a data point where "value" is a JSON numeric element
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NumberPoint extends BasePoint {
+
+ @SerializedName("value")
+ private @Nullable Number value;
+
+ @Override
+ public int asInt() {
+ Number value = this.value;
+ return value != null ? value.intValue() : UNDEFINED_VALUE;
+ }
+
+ @Override
+ public State getState() {
+ Number value = this.value;
+ return value != null ? new DecimalType(value.doubleValue()) : new DecimalType(UNDEFINED_VALUE);
+ }
+
+ @Override
+ public void refreshValueFrom(BasePoint from) {
+ super.refreshValueFrom(from);
+ if (from instanceof NumberPoint) {
+ this.value = ((NumberPoint) from).value;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/PointDeserializer.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/PointDeserializer.java
new file mode 100644
index 0000000000000..467a05682aeda
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/PointDeserializer.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+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.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * private class a JSON de-serializer for the Data Point classes above
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public class PointDeserializer implements JsonDeserializer {
+
+ private static enum PointType {
+ UNDEFINED,
+ STRING,
+ NESTED_NUMBER,
+ NUMBER
+ }
+
+ @Override
+ public BasePoint deserialize(@Nullable JsonElement element, @Nullable Type guff,
+ @Nullable JsonDeserializationContext ctxt) throws JsonParseException {
+ if (element == null || ctxt == null) {
+ throw new JsonParseException("method called with null argument(s)");
+ }
+
+ JsonObject obj = element.getAsJsonObject();
+ JsonElement value = obj.get("value");
+ if (value == null) {
+ UndefPoint point = ctxt.deserialize(obj, UndefPoint.class);
+ if (point != null) {
+ return point;
+ }
+ throw new JsonSyntaxException("unable to parse point WITHOUT a \"value\" element");
+ }
+
+ PointType pointType = PointType.UNDEFINED;
+
+ boolean valueIsPrimitive = value.isJsonPrimitive();
+
+ JsonElement rep = obj.get("rep");
+ if (rep != null && rep.isJsonPrimitive() && rep.getAsJsonPrimitive().isNumber()) {
+ /*
+ * full point lists have a "rep" element so we know explicitly the point class
+ */
+ int repValue = rep.getAsInt();
+ if (repValue == 0) {
+ pointType = PointType.STRING;
+ } else if (repValue < 4) {
+ pointType = valueIsPrimitive ? PointType.NUMBER : PointType.NESTED_NUMBER;
+ }
+ } else {
+ /*
+ * refresh point lists do NOT have a "rep" element so try to infer the point
+ * class
+ */
+ if (valueIsPrimitive) {
+ JsonPrimitive primitiveType = value.getAsJsonPrimitive();
+ pointType = primitiveType.isString() ? PointType.STRING : PointType.NUMBER;
+ } else
+ pointType = PointType.NESTED_NUMBER;
+ }
+
+ BasePoint point;
+ switch (pointType) {
+ case STRING: {
+ point = ctxt.deserialize(obj, StringPoint.class);
+ if (point != null) {
+ return point;
+ }
+ break;
+ }
+ case NESTED_NUMBER: {
+ point = ctxt.deserialize(obj, NestedNumberPoint.class);
+ if (point != null) {
+ return point;
+ }
+ break;
+ }
+ case NUMBER: {
+ point = ctxt.deserialize(obj, NumberPoint.class);
+ if (point != null) {
+ return point;
+ }
+ break;
+ }
+ default: {
+ point = ctxt.deserialize(obj, UndefPoint.class);
+ if (point != null) {
+ return point;
+ }
+ }
+ }
+ throw new JsonSyntaxException("unable to parse point with a \"value\" element");
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/StringPoint.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/StringPoint.java
new file mode 100644
index 0000000000000..9580fe2f58686
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/StringPoint.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.types.State;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * private class a data point where "value" is a JSON text element
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class StringPoint extends BasePoint {
+
+ @SerializedName("value")
+ private @Nullable String value;
+
+ @Override
+ public int asInt() {
+ try {
+ return Integer.parseInt(value);
+ } catch (Exception e) {
+ return UNDEFINED_VALUE;
+ }
+ }
+
+ @Override
+ public State getState() {
+ return new StringType(value);
+ }
+
+ @Override
+ public void refreshValueFrom(BasePoint from) {
+ super.refreshValueFrom(from);
+ if (from instanceof StringPoint) {
+ this.value = ((StringPoint) from).value;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/UndefPoint.java b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/UndefPoint.java
new file mode 100644
index 0000000000000..2f55c377147e1
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/main/java/org/openhab/binding/siemensrds/points/UndefPoint.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.points;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+
+/**
+ * private class a data point where "value" is unknown
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class UndefPoint extends BasePoint {
+
+ @Override
+ public State getState() {
+ return UnDefType.UNDEF;
+ }
+
+ @Override
+ public int asInt() {
+ return UNDEFINED_VALUE;
+ }
+
+ @Override
+ public void refreshValueFrom(BasePoint from) {
+ // do nothing
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.siemensrds/src/main/resources/ESH-INF/thing/thing-types.xml
index f181240ef2021..e5b7d69b1c6f0 100644
--- a/bundles/org.openhab.binding.siemensrds/src/main/resources/ESH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.siemensrds/src/main/resources/ESH-INF/thing/thing-types.xml
@@ -30,6 +30,7 @@
Time (seconds) between polling requests (min=8, max/default=60)
60
+ true
diff --git a/bundles/org.openhab.binding.siemensrds/src/test/java/org/openhab/binding/siemensrds/test/RdsTestData.java b/bundles/org.openhab.binding.siemensrds/src/test/java/org/openhab/binding/siemensrds/test/RdsTestData.java
new file mode 100644
index 0000000000000..ecf644f485f39
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/test/java/org/openhab/binding/siemensrds/test/RdsTestData.java
@@ -0,0 +1,513 @@
+/**
+ * Copyright (c) 2010-2020 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.siemensrds.test;
+
+import static org.junit.Assert.*;
+import static org.openhab.binding.siemensrds.internal.RdsBindingConstants.*;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.library.unit.ImperialUnits;
+import org.eclipse.smarthome.core.library.unit.SIUnits;
+import org.eclipse.smarthome.core.types.State;
+import org.junit.Test;
+import org.openhab.binding.siemensrds.internal.RdsAccessToken;
+import org.openhab.binding.siemensrds.internal.RdsCloudException;
+import org.openhab.binding.siemensrds.internal.RdsDataPoints;
+import org.openhab.binding.siemensrds.internal.RdsPlants;
+import org.openhab.binding.siemensrds.internal.RdsPlants.PlantInfo;
+import org.openhab.binding.siemensrds.points.BasePoint;
+
+import tec.uom.se.unit.Units;
+
+/**
+ * test suite
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RdsTestData {
+
+ private String load(String fileName) {
+ try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
+ BufferedReader reader = new BufferedReader(file)) {
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line).append("\n");
+ }
+ return builder.toString();
+ } catch (IOException e) {
+ fail(e.getMessage());
+ }
+ return "";
+ }
+
+ @Test
+ public void testRdsDataPointsFullNew() {
+ RdsDataPoints dataPoints = RdsDataPoints.createFromJson(load("datapoints_full_set_new"));
+ assertNotNull(dataPoints);
+ try {
+ assertEquals("Downstairs", dataPoints.getDescription());
+ } catch (RdsCloudException e) {
+ fail(e.getMessage());
+ }
+ @Nullable
+ Map points = dataPoints.points;
+ assertNotNull(points);
+ assertEquals(70, points.size());
+ }
+
+ @Test
+ public void confirmDegreeSymbolCodingNotTrashed() {
+ /*
+ * note: temperature symbols with a degree sign: the MVN Spotless trashes the
+ * "degree" (looks like *) symbol, so we must escape these symbols as octal \260
+ * or unicode \u00B00 - the following test will indicate is all is ok
+ */
+ assertTrue("\260C".equals(BasePoint.DEGREES_CELSIUS));
+ assertTrue("\u00B0C".equals(BasePoint.DEGREES_CELSIUS));
+ assertTrue("\260F".equals(BasePoint.DEGREES_FAHRENHEIT));
+ assertTrue("\u00B0F".equals(BasePoint.DEGREES_FAHRENHEIT));
+ assertTrue(BasePoint.DEGREES_FAHRENHEIT.startsWith(BasePoint.DEGREES_CELSIUS.substring(0, 1)));
+ }
+
+ @Test
+ public void testRdsDataPointsRefresh() {
+ RdsDataPoints refreshPoints = RdsDataPoints.createFromJson(load("datapoints_refresh_set"));
+ assertNotNull(refreshPoints);
+
+ assertNotNull(refreshPoints.points);
+ Map refreshMap = refreshPoints.points;
+ assertNotNull(refreshMap);
+
+ @Nullable
+ BasePoint point;
+ State state;
+
+ // check the parsed values
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!Online");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), DecimalType.class);
+ assertEquals(1, ((DecimalType) state).intValue());
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!00000000E000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(12.60, ((QuantityType>) state).floatValue(), 0.01);
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000083000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(16.0, ((QuantityType>) state).floatValue(), 0.01);
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000085000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(39.13, ((QuantityType>) state).floatValue(), 0.01);
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000086000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(21.51, ((QuantityType>) state).floatValue(), 0.01);
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000051000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(2, ((QuantityType>) state).intValue());
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000052000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(5, ((QuantityType>) state).intValue());
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000053000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(2, ((QuantityType>) state).intValue());
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000056000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(1, ((QuantityType>) state).intValue());
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!01300005A000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(2, ((QuantityType>) state).intValue());
+
+ point = refreshMap.get("Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000074000055");
+ assertTrue(point instanceof BasePoint);
+ state = point.getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(4, ((QuantityType>) state).intValue());
+
+ RdsDataPoints originalPoints = RdsDataPoints.createFromJson(load("datapoints_full_set"));
+ assertNotNull(originalPoints);
+ assertNotNull(originalPoints.points);
+
+ // check that the refresh point types match the originals
+ Map originalMap = originalPoints.points;
+ assertNotNull(originalMap);
+ @Nullable
+ BasePoint refreshPoint;
+ @Nullable
+ BasePoint originalPoint;
+ for (String key : refreshMap.keySet()) {
+ refreshPoint = refreshMap.get(key);
+ assertTrue(refreshPoint instanceof BasePoint);
+ originalPoint = originalMap.get(key);
+ assertTrue(originalPoint instanceof BasePoint);
+ assertEquals(refreshPoint.getState().getClass(), originalPoint.getState().getClass());
+ }
+ }
+
+ @Test
+ public void testAccessToken() {
+ RdsAccessToken accessToken = RdsAccessToken.createFromJson(load("access_token"));
+ assertNotNull(accessToken);
+ try {
+ assertEquals("this-is-not-a-valid-access_token", accessToken.getToken());
+ } catch (RdsCloudException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(accessToken.isExpired());
+ }
+
+ @Test
+ public void testRdsDataPointsFull() {
+ RdsDataPoints dataPoints = RdsDataPoints.createFromJson(load("datapoints_full_set"));
+ assertNotNull(dataPoints);
+ try {
+ assertEquals("Upstairs", dataPoints.getDescription());
+ } catch (RdsCloudException e) {
+ fail(e.getMessage());
+ }
+
+ @Nullable
+ Map points = dataPoints.points;
+ assertNotNull(points);
+ assertEquals(67, points.size());
+
+ try {
+ assertEquals("AAS-20:SU=SiUn;APT=HvacFnct18z_A;APTV=2.003;APS=1;",
+ dataPoints.getPointByClass("ApplicationSoftwareVersion").getState().toString());
+ assertEquals("Device object", dataPoints.getPointByClass("Device Description").getState().toString());
+ assertEquals("FW=02.32.02.27;SVS-300.1:SBC=13.22;I",
+ dataPoints.getPointByClass("FirmwareRevision").getState().toString());
+ assertEquals("RDS110", dataPoints.getPointByClass("ModelName").getState().toString());
+ assertEquals(0, dataPoints.getPointByClass("SystemStatus").asInt());
+ assertEquals(0, dataPoints.getPointByClass("UtcOffset").asInt());
+ assertEquals(19, dataPoints.getPointByClass("DatabaseRevision").asInt());
+ assertEquals(0, dataPoints.getPointByClass("LastRestartReason").asInt());
+ assertEquals("MDL:ASN= RDS110;HW=0.2.0;",
+ dataPoints.getPointByClass("ModelInformation").getState().toString());
+ assertEquals(1, dataPoints.getPointByClass("Active SystemLanguge").asInt());
+ assertEquals(26, dataPoints.getPointByClass("TimeZone").asInt());
+ assertEquals("160100096D", dataPoints.getPointByClass("SerialNumber").getState().toString());
+ assertEquals("'10010'B", dataPoints.getPointByClass("Device Features").getState().toString());
+ assertEquals("Upstairs", dataPoints.getPointByClass("'Description").getState().toString());
+ assertEquals("192.168.1.1", dataPoints.getPointByClass("'IP gefault gateway").getState().toString());
+ assertEquals("255.255.255.0", dataPoints.getPointByClass("'IP subnet mask").getState().toString());
+ assertEquals("192.168.1.42", dataPoints.getPointByClass("'IP address").getState().toString());
+ assertEquals(47808, dataPoints.getPointByClass("'UDP Port").asInt());
+ assertEquals("'F0C77F6C1895'H", dataPoints.getPointByClass("'BACnet MAC address").getState().toString());
+ assertEquals("sth.connectivity.ccl-siemens.com",
+ dataPoints.getPointByClass("'Connection URI").getState().toString());
+ assertEquals("this-is-not-a-valid-activation-key",
+ dataPoints.getPointByClass("'Activation Key").getState().toString());
+ assertEquals(60, dataPoints.getPointByClass("'Reconection delay").asInt());
+ assertEquals(0, dataPoints.getPointByClass("#Item Updates per Minute").asInt());
+ assertEquals(286849, dataPoints.getPointByClass("#Item Updates Total").asInt());
+ assertEquals("-;en", dataPoints.getPointByClass("#Languages").getState().toString());
+ assertEquals(1, dataPoints.getPointByClass("#Online").asInt());
+ assertEquals(1473, dataPoints.getPointByClass("#Traffic Inbound per Minute").asInt());
+ assertEquals(178130801, dataPoints.getPointByClass("#Traffic Inbound Total").asInt());
+ assertEquals(616, dataPoints.getPointByClass("#Traffic Outbound per Minute").asInt());
+ assertEquals(60624666, dataPoints.getPointByClass("#Traffic Outbound Total").asInt());
+ assertEquals(0, dataPoints.getPointByClass("#Item Updates per Minute").asInt());
+
+ State state;
+ QuantityType> celsius;
+
+ state = dataPoints.getPointByClass("'TOa").getState();
+ assertTrue(state instanceof QuantityType>);
+ celsius = ((QuantityType>) state).toUnit(SIUnits.CELSIUS);
+ assertNotNull(celsius);
+ assertEquals(18.55, celsius.floatValue(), 0.01);
+
+ assertEquals("0.0", dataPoints.getPointByClass("'HDevElLd").getState().toString());
+
+ state = dataPoints.getPointByClass("'SpHPcf").getState();
+ assertTrue(state instanceof QuantityType>);
+ QuantityType> fahrenheit = ((QuantityType>) state).toUnit(ImperialUnits.FAHRENHEIT);
+ assertNotNull(fahrenheit);
+ assertEquals(24.00, fahrenheit.floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass("'SpHEco").getState();
+ assertTrue(state instanceof QuantityType>);
+ celsius = ((QuantityType>) state).toUnit(SIUnits.CELSIUS);
+ assertNotNull(celsius);
+ assertEquals(16.00, celsius.floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass("'SpHPrt").getState();
+ assertTrue(state instanceof QuantityType>);
+ celsius = ((QuantityType>) state).toUnit(SIUnits.CELSIUS);
+ assertNotNull(celsius);
+ assertEquals(6.00, celsius.floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass("'SpTR").getState();
+ assertTrue(state instanceof QuantityType>);
+ celsius = ((QuantityType>) state).toUnit(SIUnits.CELSIUS);
+ assertNotNull(celsius);
+ assertEquals(24.00, celsius.floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass("'SpTRShft").getState();
+ assertTrue(state instanceof QuantityType>);
+ QuantityType> kelvin = ((QuantityType>) state).toUnit(Units.KELVIN);
+ assertNotNull(kelvin);
+ assertEquals(0, kelvin.floatValue(), 0.01);
+
+ assertEquals("46.86865", dataPoints.getPointByClass("'RHuRel").getState().toString());
+
+ state = dataPoints.getPointByClass("'RTemp").getState();
+ assertTrue(state instanceof QuantityType>);
+ celsius = ((QuantityType>) state).toUnit(SIUnits.CELSIUS);
+ assertNotNull(celsius);
+ assertEquals(23.76, celsius.floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass("'SpTRMaxHCmf").getState();
+ assertTrue(state instanceof QuantityType>);
+ celsius = ((QuantityType>) state).toUnit(SIUnits.CELSIUS);
+ assertNotNull(celsius);
+ assertEquals(35.00, celsius.floatValue(), 0.01);
+
+ assertEquals("30.0", dataPoints.getPointByClass("'WarmUpGrdnt").getState().toString());
+
+ state = dataPoints.getPointByClass("'TRBltnMsvAdj").getState();
+ assertTrue(state instanceof QuantityType>);
+ kelvin = ((QuantityType>) state).toUnit(Units.KELVIN);
+ assertNotNull(kelvin);
+ assertEquals(35.0, celsius.floatValue(), 0.01);
+
+ assertEquals("0.0", dataPoints.getPointByClass("'Q22Q24ElLd").getState().toString());
+ assertEquals("713.0", dataPoints.getPointByClass("'RAQual").getState().toString());
+ assertEquals("0.0", dataPoints.getPointByClass("'TmpCmfBtn").getState().toString());
+ assertEquals("0.0", dataPoints.getPointByClass("'CmfBtn").getState().toString());
+ assertEquals("0.0", dataPoints.getPointByClass("'RPscDet").getState().toString());
+ assertEquals("1.0", dataPoints.getPointByClass("'EnHCtl").getState().toString());
+ assertEquals("0.0", dataPoints.getPointByClass("'EnRPscDet").getState().toString());
+ assertEquals("2.0", dataPoints.getPointByClass("'OffPrtCnf").getState().toString());
+ assertEquals("3.0", dataPoints.getPointByClass("'OccMod").getState().toString());
+ assertEquals("5.0", dataPoints.getPointByClass("'REei").getState().toString());
+ assertEquals("2.0", dataPoints.getPointByClass("'DhwMod").getState().toString());
+ assertEquals("2.0", dataPoints.getPointByClass("'HCSta").getState().toString());
+ assertEquals("4.0", dataPoints.getPointByClass("'PrOpModRsn").getState().toString());
+ assertEquals("6.0", dataPoints.getPointByClass("'HCtrSet").getState().toString());
+ assertEquals("2.0", dataPoints.getPointByClass("'OsscSet").getState().toString());
+ assertEquals("4.0", dataPoints.getPointByClass("'RAQualInd").getState().toString());
+ assertEquals("500.0", dataPoints.getPointByClass("'KickCyc").getState().toString());
+ assertEquals("180000.0", dataPoints.getPointByClass("'BoDhwTiOnMin").getState().toString());
+ assertEquals("180000.0", dataPoints.getPointByClass("'BoDhwTiOffMin").getState().toString());
+ assertEquals("UNDEF", dataPoints.getPointByClass("'ROpModSched").getState().toString());
+ assertEquals("UNDEF", dataPoints.getPointByClass("'DhwSched").getState().toString());
+ assertEquals("UNDEF", dataPoints.getPointByClass("'ROpModSched").getState().toString());
+ assertEquals("UNDEF", dataPoints.getPointByClass("'DhwSched").getState().toString());
+ assertEquals("253140.0", dataPoints.getPointByClass("'OphH").getState().toString());
+ } catch (RdsCloudException e) {
+ fail(e.getMessage());
+ }
+
+ // test for a missing element
+ State test = null;
+ try {
+ test = dataPoints.getPointByClass("missing-element").getState();
+ fail("expected exception did not occur");
+ } catch (RdsCloudException e) {
+ assertEquals(null, test);
+ }
+
+ try {
+ // test the all-the-way-round lookup loop
+ assertNotNull(dataPoints.points);
+ Map pointsMap = dataPoints.points;
+ assertNotNull(pointsMap);
+ @Nullable
+ BasePoint point;
+ for (Entry entry : pointsMap.entrySet()) {
+ point = entry.getValue();
+ assertTrue(point instanceof BasePoint);
+ // ignore UNDEF points where all-the-way-round lookup fails
+ if (!"UNDEF".equals(point.getState().toString())) {
+ @Nullable
+ String x = entry.getKey();
+ assertNotNull(x);
+ String y = ((BasePoint) point).getPointClass();
+ String z = dataPoints.pointClassToId(y);
+ assertEquals(x, z);
+ }
+ }
+
+ State state = null;
+
+ // test the specific points that we use
+ state = dataPoints.getPointByClass(HIE_DESCRIPTION).getState();
+ assertEquals("Upstairs", state.toString());
+
+ state = dataPoints.getPointByClass(HIE_ROOM_TEMP).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(23.761879, ((QuantityType>) state).floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass(HIE_OUTSIDE_TEMP).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(18.55, ((QuantityType>) state).floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass(HIE_TARGET_TEMP).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(24, ((QuantityType>) state).floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass(HIE_ROOM_HUMIDITY).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(46.86, ((QuantityType>) state).floatValue(), 0.01);
+
+ state = dataPoints.getPointByClass(HIE_ROOM_AIR_QUALITY).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("Good", state.toString());
+ assertEquals("Good", dataPoints.getPointByClass(HIE_ROOM_AIR_QUALITY).getEnum().toString());
+
+ state = dataPoints.getPointByClass(HIE_ENERGY_SAVINGS_LEVEL).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("Excellent", state.toString());
+ assertEquals("Excellent", dataPoints.getPointByClass(HIE_ENERGY_SAVINGS_LEVEL).getEnum().toString());
+
+ state = dataPoints.getPointByClass(HIE_OUTPUT_STATE).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("Heating", state.toString());
+ assertEquals("Heating", dataPoints.getPointByClass(HIE_OUTPUT_STATE).getEnum().toString());
+
+ state = dataPoints.getPointByClass(HIE_STAT_OCC_MODE_PRESENT).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(3, ((QuantityType>) state).intValue());
+ assertEquals(3, dataPoints.getPointByClass(HIE_STAT_OCC_MODE_PRESENT).asInt());
+
+ state = dataPoints.getPointByClass(HIE_STAT_OCC_MODE_PRESENT).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("Present", state.toString());
+ assertEquals("Present", dataPoints.getPointByClass(HIE_STAT_OCC_MODE_PRESENT).getEnum().toString());
+
+ state = dataPoints.getPointByClass(HIE_DHW_OUTPUT_STATE).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(2, ((QuantityType>) state).intValue());
+ assertEquals(2, dataPoints.getPointByClass(HIE_DHW_OUTPUT_STATE).asInt());
+
+ state = dataPoints.getPointByClass(HIE_DHW_OUTPUT_STATE).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("On", state.toString());
+ assertEquals("On", dataPoints.getPointByClass(HIE_DHW_OUTPUT_STATE).getEnum().toString());
+
+ state = dataPoints.getPointByClass(HIE_PR_OP_MOD_RSN).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(4, ((QuantityType>) state).intValue());
+ assertEquals(4, dataPoints.getPointByClass(HIE_PR_OP_MOD_RSN).asInt());
+
+ state = dataPoints.getPointByClass(HIE_PR_OP_MOD_RSN).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("Comfort", state.toString());
+ assertEquals("Comfort", dataPoints.getPointByClass(HIE_PR_OP_MOD_RSN).getEnum().toString());
+
+ state = dataPoints.getPointByClass(HIE_STAT_CMF_BTN).getState();
+ assertEquals(state.getClass(), QuantityType.class);
+ assertEquals(0, ((QuantityType>) state).intValue());
+ assertEquals(0, dataPoints.getPointByClass(HIE_STAT_CMF_BTN).asInt());
+
+ state = dataPoints.getPointByClass(HIE_STAT_CMF_BTN).getEnum();
+ assertEquals(state.getClass(), StringType.class);
+ assertEquals("Inactive", state.toString());
+ assertEquals("Inactive", dataPoints.getPointByClass(HIE_STAT_CMF_BTN).getEnum().toString());
+
+ // test online code
+ assertTrue(dataPoints.isOnline());
+
+ // test present priority code
+ assertEquals(15, dataPoints.getPointByClass(HIE_TARGET_TEMP).getPresentPriority());
+
+ // test temperature units code (C)
+ BasePoint tempPoint = dataPoints.getPointByClass("'SpTR");
+ assertTrue(tempPoint instanceof BasePoint);
+ assertEquals(SIUnits.CELSIUS, ((BasePoint) tempPoint).getUnit());
+
+ // test temperature units code (F)
+ tempPoint = dataPoints.getPointByClass("'SpHPcf");
+ assertTrue(tempPoint instanceof BasePoint);
+ assertEquals(ImperialUnits.FAHRENHEIT, ((BasePoint) tempPoint).getUnit());
+
+ // test temperature units code (K)
+ tempPoint = dataPoints.getPointByClass("'SpHPcf");
+ assertTrue(tempPoint instanceof BasePoint);
+ assertEquals(ImperialUnits.FAHRENHEIT, ((BasePoint) tempPoint).getUnit());
+
+ tempPoint = dataPoints.getPointByClass("'SpTRShft");
+ assertTrue(tempPoint instanceof BasePoint);
+ assertEquals(Units.KELVIN, ((BasePoint) tempPoint).getUnit());
+ } catch (RdsCloudException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRdsPlants() {
+ try {
+ RdsPlants plants = RdsPlants.createFromJson(load("plants"));
+ assertNotNull(plants);
+
+ @Nullable
+ List plantList = plants.getPlants();
+ assertNotNull(plantList);
+
+ @Nullable
+ PlantInfo plant;
+ plant = plantList.get(0);
+ assertTrue(plant instanceof PlantInfo);
+ assertEquals("Pd1774247-7de7-4896-ac76-b7e0dd943c40", ((PlantInfo) plant).getId());
+ assertTrue(plant.isOnline());
+
+ plant = plantList.get(1);
+ assertTrue(plant instanceof PlantInfo);
+ assertEquals("Pfaf770c8-abeb-4742-ad65-ead39030d369", ((PlantInfo) plant).getId());
+ assertTrue(((PlantInfo) plant).isOnline());
+ } catch (RdsCloudException e) {
+ fail(e.getMessage());
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/test/resources/access_token.json b/bundles/org.openhab.binding.siemensrds/src/test/resources/access_token.json
new file mode 100644
index 0000000000000..e9607c8e77a19
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/test/resources/access_token.json
@@ -0,0 +1,8 @@
+{
+ "access_token": "this-is-not-a-valid-access_token",
+ "token_type": "bearer",
+ "expires_in": 1209599,
+ "userName": "software@whitebear.ch",
+ ".issued": "Thu, 06 Jun 2019 10:27:50 GMT",
+ ".expires": "Thu, 20 Jun 2019 10:27:50 GMT"
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_full_set.json b/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_full_set.json
new file mode 100644
index 0000000000000..6df106ccff901
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_full_set.json
@@ -0,0 +1,1535 @@
+{
+ "totalCount": 67,
+ "values": {
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF00000C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "AAS-20:SU=SiUn;APT=HvacFnct18z_A;APTV=2.003;APS=1;",
+ "descriptionName": "ApplicationSoftwareVersion",
+ "objectName": "ApplicationSoftwareVersion",
+ "memberName": "ApplicationSoftwareVersion",
+ "hierarchyName": "ApplicationSoftwareVersion",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF00001C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "Device object",
+ "descriptionName": "Device Description",
+ "objectName": "Device Description",
+ "memberName": "Description",
+ "hierarchyName": "Device Description",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF00002C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "FW=02.32.02.27;SVS-300.1:SBC=13.22;I",
+ "descriptionName": "FirmwareRevision",
+ "objectName": "FirmwareRevision",
+ "memberName": "FirmwareRevision",
+ "hierarchyName": "FirmwareRevision",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF000046": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "RDS110",
+ "descriptionName": "ModelName",
+ "objectName": "ModelName",
+ "memberName": "ModelName",
+ "hierarchyName": "ModelName",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF000070": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": 0,
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "operational*operational-read-only*download-required*download-in-progress*non-operational*backup-in-progress",
+ "descriptionName": "SystemStatus",
+ "objectName": "SystemStatus",
+ "memberName": "SystemStatus",
+ "hierarchyName": "SystemStatus",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF000077": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 0,
+ "descriptionName": "UtcOffset",
+ "objectName": "UtcOffset",
+ "memberName": "UtcOffset",
+ "hierarchyName": "UtcOffset",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF00009B": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 19,
+ "descriptionName": "DatabaseRevision",
+ "objectName": "DatabaseRevision",
+ "memberName": "DatabaseRevision",
+ "hierarchyName": "DatabaseRevision",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF0000C4": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": 0,
+ "limits": [
+ 0,
+ 7
+ ],
+ "descr": "unknown*coldstart*warmstart*detected-power-lost*detected-powered-off*hardware-watchdog*software-watchdog*suspended",
+ "descriptionName": "LastRestartReason",
+ "objectName": "LastRestartReason",
+ "memberName": "LastRestartReason",
+ "hierarchyName": "LastRestartReason",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF0012DB": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "MDL:ASN= RDS110;HW=0.2.0;",
+ "descriptionName": "ModelInformation",
+ "objectName": "ModelInformation",
+ "memberName": "4827",
+ "hierarchyName": "ModelInformation",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF001355": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": 1,
+ "limits": [
+ 0,
+ 26
+ ],
+ "descr": "-*en*de*fr*es*cs*da*nl*fi*it*hu*nb*pl*pt*ru*sk*sv*zh*zh*ko*ro*tr*en-US*fr-CA*es-mx*pt-BR",
+ "descriptionName": "Active SystemLanguge",
+ "objectName": "Active SystemLanguge",
+ "memberName": "4949",
+ "hierarchyName": "Active SystemLanguge",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF0013B0": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": 26,
+ "limits": [
+ 0,
+ 72
+ ],
+ "descr": "GMT-12:00 Kwajalein*GMT-11:00 Samoa, Midway*GMT-10:00 Hawaii*GMT-09:00 Alaska*GMT-08:00 Pacific Time*GMT-07:00 Arizona*GMT-07:00 Chihuahua*GMT-07:00 Mountain Time*GMT-06:00 Central America*GMT-06:00 Central Time*GMT-06:00 Mexico City*GMT-06:00 Saskatchewan*GMT-05:00 Bogota, Lima*GMT-05:00 Eastern Time*GMT-05:00 Indiana (USA)*GMT-04:00 Atlantic Time*GMT-04:00 Caracas, La Paz*GMT-04:00 Santiago*18*GMT-03:00 Brasilia*20*21*GMT-02:00 Mid-Atlantic*23*24*25*GMT London, Dublin, Lisbon*GMT+01:00 Berlin, Rome*GMT+01:00 Budapest, Prague*GMT+01:00 Paris, Madrid*GMT+01:00 Vienna, Warsaw*31*GMT+02:00 Athens, Istanbul*GMT+02:00 Bucharest*GMT+02:00 Cairo*GMT+02:00 Johannesburg, Harare*GMT+02:00 Helsinki, Riga*GMT+02:00 Jerusalem*38*GMT+03:00 Kuwait, Riyadh*GMT+04:00 Moscow*41*GMT+03:30 Tehran*GMT+04:00 Abu Dhabi, Muscat*GMT+04:00 Baku, Tbilisi*45*GMT+06:00 Ekaterinburg*47*GMT+05:30 New Delhi*49*GMT+07:00 Omsk, Novosibirsk *51*52*53*GMT+07:00 Bangkok, Jakarta*GMT+08:00 Krasnoyarsk*GMT+08:00 Beijing, Hong Kong*GMT+09:00 Irkutsk*GMT+08:00 Kuala Lumpur*GMT+08:00 Perth*GMT+08:00 Taipei*GMT+09:00 Tokyo, Osaka*GMT+09:00 Seoul*GMT+10:00 Yakutsk*GMT+09:30 Adelaide*GMT+09:30 Darwin*GMT+10:00 Brisbane*GMT+10:00 Melbourne, Sydney*68*69*GMT+11:00 Vladivostok*71*GMT+12:00 Auckland, Wellington",
+ "descriptionName": "TimeZone",
+ "objectName": "TimeZone",
+ "memberName": "5040",
+ "hierarchyName": "TimeZone",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF0013EC": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "160100096D",
+ "descriptionName": "SerialNumber",
+ "objectName": "SerialNumber",
+ "memberName": "5100",
+ "hierarchyName": "SerialNumber",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!0083FFFFF0013F4": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "'10010'B",
+ "descriptionName": "Device Features",
+ "objectName": "Device Features",
+ "memberName": "5108",
+ "hierarchyName": "Device Features",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!01D00000700001C": {
+ "rep": 0,
+ "type": 0,
+ "write": true,
+ "value": "Upstairs",
+ "descriptionName": "R(1)'Description",
+ "objectName": "R(1)'Description",
+ "memberName": "Description",
+ "hierarchyName": "R(1)'Description",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!10800000000130B": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "192.168.1.1",
+ "descriptionName": "NwkPortIP'IP gefault gateway",
+ "objectName": "NwkPortIP'IP gefault gateway",
+ "memberName": "4875",
+ "hierarchyName": "NwkPortIP'IP gefault gateway",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!10800000000130C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "255.255.255.0",
+ "descriptionName": "NwkPortIP'IP subnet mask",
+ "objectName": "NwkPortIP'IP subnet mask",
+ "memberName": "4876",
+ "hierarchyName": "NwkPortIP'IP subnet mask",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!10800000000130D": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "192.168.1.42",
+ "descriptionName": "NwkPortIP'IP address",
+ "objectName": "NwkPortIP'IP address",
+ "memberName": "4877",
+ "hierarchyName": "NwkPortIP'IP address",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!10800000000130E": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 47808,
+ "descriptionName": "NwkPortIP'UDP Port",
+ "objectName": "NwkPortIP'UDP Port",
+ "memberName": "4878",
+ "hierarchyName": "NwkPortIP'UDP Port",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!108000000001313": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "'F0C77F6C1895'H",
+ "descriptionName": "NwkPortIP'BACnet MAC address",
+ "objectName": "NwkPortIP'BACnet MAC address",
+ "memberName": "4883",
+ "hierarchyName": "NwkPortIP'BACnet MAC address",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!108000001001286": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "sth.connectivity.ccl-siemens.com",
+ "descriptionName": "NwkPortCCL'Connection URI",
+ "objectName": "NwkPortCCL'Connection URI",
+ "memberName": "4742",
+ "hierarchyName": "NwkPortCCL'Connection URI",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!108000001001287": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "this-is-not-a-valid-activation-key",
+ "descriptionName": "NwkPortCCL'Activation Key",
+ "objectName": "NwkPortCCL'Activation Key",
+ "memberName": "4743",
+ "hierarchyName": "NwkPortCCL'Activation Key",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!108000001001288": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 60,
+ "descriptionName": "NwkPortCCL'Reconection delay",
+ "objectName": "NwkPortCCL'Reconection delay",
+ "memberName": "4744",
+ "hierarchyName": "NwkPortCCL'Reconection delay",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!DPUpdPerMin": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 0,
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Item Updates per Minute",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!DPUpdTotal": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 286849,
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Item Updates Total",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!Languages": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "value": "-;en",
+ "limits": [
+ 0,
+ 1
+ ],
+ "objectName": "CSL-Config",
+ "memberName": "Languages",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!Online": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": 1,
+ "limits": [
+ 0,
+ 1
+ ],
+ "descr": "Offline*Online",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Online",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!TrfcInPerMin": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 1473,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Inbound per Minute",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!TrfcInTotal": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 178130801,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Inbound Total",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!TrfcOutPerMin": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 616,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Outbound per Minute",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!TrfcOutTotal": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "value": 60624666,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Outbound Total",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!00000000E000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 18.5519028,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": -50,
+ "maxValue": 80
+ },
+ "limits": [
+ -50,
+ 80
+ ],
+ "descr": "°C",
+ "descriptionName": "Outside air temperature",
+ "objectName": "R(1)'TOa",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'TOa",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000002000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 5
+ },
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "A",
+ "descriptionName": "Heating device electrical load",
+ "objectName": "R(1)'HDevElLd",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'HDevElLd",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!00200007F000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 24,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 17,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "°C",
+ "descriptionName": "Comfort heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHCmf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpHCmf",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000080000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 24,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "°F",
+ "descriptionName": "Pre-comfort heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHPcf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpHPcf",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000081000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 16,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "°C",
+ "descriptionName": "Economy heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHEco",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpHEco",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000082000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 6,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "°C",
+ "descriptionName": "Protection heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHPrt",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'SpHPrt",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000083000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 24,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0,
+ "minValue": 6,
+ "maxValue": 35
+ },
+ "limits": [
+ 12,
+ 35
+ ],
+ "descr": "°C",
+ "descriptionName": "Room temperature setpoint",
+ "objectName": "R(1)'RHvacCoo'SpTRDtr'SpTR",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpTR",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000084000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0,
+ "minValue": -18,
+ "maxValue": 11
+ },
+ "limits": [
+ -9,
+ 14
+ ],
+ "descr": "K",
+ "descriptionName": "Room temperature setpoint shift",
+ "objectName": "R(1)'RHvacCoo'SpTRDtr'SpTRShft",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpTRShft",
+ "translated": false,
+ "resolution": 0.10000000149011612
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000085000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 46.86865,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ "limits": [
+ 0,
+ 100
+ ],
+ "descr": "%r.H.",
+ "descriptionName": "Relative humidity for room",
+ "objectName": "R(1)'RHvacCoo'RHuRel",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RHuRel",
+ "translated": false,
+ "resolution": 1
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000086000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 23.761879,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "°C",
+ "descriptionName": "Room temperature",
+ "objectName": "R(1)'RHvacCoo'RTemp",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RTemp",
+ "translated": false,
+ "resolution": 0.10000000149011612
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!0020000B2000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 35,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "°C",
+ "descriptionName": "Max. heating setpoint",
+ "objectName": "R(1)'SpTRMaxHCmf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'SpTRMaxHCmf",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!0020000B4000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 30,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 120
+ },
+ "limits": [
+ 0,
+ 120
+ ],
+ "descr": "Unknown Unit(236)",
+ "descriptionName": "Warm-up gradient",
+ "objectName": "R(1)'WarmUpGrdnt",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'WarmUpGrdnt",
+ "translated": false,
+ "resolution": 5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!0020000B5000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 1,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": -5,
+ "maxValue": 5
+ },
+ "limits": [
+ -5,
+ 5
+ ],
+ "descr": "K",
+ "descriptionName": "Built-in temp. sensor adj.",
+ "objectName": "R(1)'TRBltnMsvAdj",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'TRBltnMsvAdj",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!0020000CB000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 5
+ },
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "A",
+ "descriptionName": "Q22/Q24 electrical load",
+ "objectName": "R(1)'Q22Q24ElLd",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'Q22Q24ElLd",
+ "translated": false,
+ "resolution": 0.5
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!0020000CD000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 713,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 5000
+ },
+ "limits": [
+ 0,
+ 5000
+ ],
+ "descr": "ppm",
+ "descriptionName": "Room air quality",
+ "objectName": "R(1)'RHvacCoo'RAQual",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RAQual",
+ "translated": false,
+ "resolution": 100
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!005000038000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Inactive*Active*Null",
+ "descriptionName": "Temporary comfort button",
+ "objectName": "R(1)'ROpModDtr'TmpCmfBtn",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'TmpCmfBtn",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!005000039000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Inactive*Active*Null",
+ "descriptionName": "Comfort button",
+ "objectName": "R(1)'ROpModDtr'CmfBtn",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'CmfBtn",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!00500003B000055": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Absent*Present*Null",
+ "descriptionName": "Room presence detection",
+ "objectName": "R(1)'RHvacCoo'RPscDet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RPscDet",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!00500003F000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 1,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 17,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "No*Yes*Null",
+ "descriptionName": "Enable heating control",
+ "objectName": "R(1)'RHvacCoo'TCtlH'EnHCtl",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'EnHCtl",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!005000054000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Inactive*Active*Null",
+ "descriptionName": "Room presence detector",
+ "objectName": "R(1)'EnRPscDet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'EnRPscDet",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!01300004C000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Null*Off*Protection",
+ "descriptionName": "Off/protection configuration",
+ "objectName": "R(1)'OffPrtCnf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'OffPrtCnf",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000051000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 3,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 3
+ ],
+ "descr": "Null*Off*Absent*Present",
+ "descriptionName": "Occupancy mode",
+ "objectName": "R(1)'ROpModDtr'OccMod",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'OccMod",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000052000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 5,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "Null*Undefined*Poor*Satisfactory*Good*Excellent",
+ "descriptionName": "Energy efficiency indication room",
+ "objectName": "R(1)'RGrnLf'REei",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'REei",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000053000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Null*Off*On",
+ "descriptionName": "Domestic hot water mode",
+ "objectName": "R(1)'RHvacCoo'DhwOp'DhwMod",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'DhwMod",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000056000055": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 3
+ ],
+ "descr": "Null*Neither*Heating*Cooling",
+ "descriptionName": "Heating/cooling state",
+ "objectName": "R(1)'RHvacCoo'HCStaDtr'HCSta",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'HCSta",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!01300005A000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 4,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 4
+ ],
+ "descr": "Null*Protection*Economy*Pre-Comfort*Comfort",
+ "descriptionName": "Present operating mode and reason",
+ "objectName": "R(1)'RHvacCoo'PrOpModRsn",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'PrOpModRsn",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000071000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 6,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 6
+ ],
+ "descr": "Null*Default*Slow*Medium*Fast*2-position*Self-adaptive",
+ "descriptionName": "Heating control loop",
+ "objectName": "R(1)'HCtrSet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'HCtrSet",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000072000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Null*Warm-up gradient*Self-adaptive",
+ "descriptionName": "Optimum start control setting",
+ "objectName": "R(1)'OsscSet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'OsscSet",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000074000055": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "value": {
+ "value": 4,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 4
+ ],
+ "descr": "Null*Undefined*Poor*Okay*Good",
+ "descriptionName": "Room air quality indication",
+ "objectName": "R(1)'RHvacCoo'RAQualInd",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RAQualInd",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!030000000000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 500,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 1,
+ "maxValue": 8760
+ },
+ "limits": [
+ 1,
+ 8760
+ ],
+ "descr": "h",
+ "descriptionName": "Pump/valve kick cycle",
+ "objectName": "R(1)'KickCyc",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'KickCyc",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!030000001000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 180000,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 3600000
+ },
+ "limits": [
+ 0,
+ 3600000
+ ],
+ "descr": "ms",
+ "descriptionName": "DHW min. ON time",
+ "objectName": "R(1)'BoDhwTiOnMin",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'BoDhwTiOnMin",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!030000002000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 180000,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 3600000
+ },
+ "limits": [
+ 0,
+ 3600000
+ ],
+ "descr": "ms",
+ "descriptionName": "DHW min. OFF time",
+ "objectName": "R(1)'BoDhwTiOffMin",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'BoDhwTiOffMin",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!030000007000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": 253140,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 4294967295
+ },
+ "limits": [
+ 0,
+ 4294967295
+ ],
+ "descr": "min",
+ "descriptionName": "Operating hours heating",
+ "objectName": "R(1)'RHvacCoo'OphHCDtr'OphH",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'OphH",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;2!011000004000055": {
+ "rep": 8,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": {
+ "cmd": "Command",
+ "cal": null,
+ "act": 0,
+ "null": 3,
+ "default": 0,
+ "schedStart": 4294967295,
+ "schedEnd": 4294967295,
+ "days": [
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ ]
+ ],
+ "timeTillNextValue": 632,
+ "nextValue": 3
+ },
+ "statusFlags": 0,
+ "reliability": 0
+ },
+ "limits": [
+ 0,
+ 4
+ ],
+ "descriptionName": "Room operating mode scheduler",
+ "objectName": "R(1)'CenOpMod'ROpModSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'ROpModSched",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;2!011000005000055": {
+ "rep": 8,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": {
+ "cmd": "Command",
+ "cal": null,
+ "act": 0,
+ "null": 3,
+ "default": 0,
+ "schedStart": 4294967295,
+ "schedEnd": 4294967295,
+ "days": [
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 8,
+ 2
+ ],
+ [
+ 11543,
+ 1
+ ]
+ ],
+ [
+ ]
+ ],
+ "timeTillNextValue": 767,
+ "nextValue": 1
+ },
+ "statusFlags": 0,
+ "reliability": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descriptionName": "Domestic hot water scheduler",
+ "objectName": "R(1)'CenDhw'DhwSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'DhwSched",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;3!01100000400004E": {
+ "rep": 9,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": {
+ "act": 0,
+ "rules": [
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ]
+ ]
+ },
+ "statusFlags": 0,
+ "reliability": 0
+ },
+ "descriptionName": "Room operating mode scheduler",
+ "objectName": "R(1)'CenOpMod'ROpModSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'ROpModSched",
+ "translated": false
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;3!01100000500004E": {
+ "rep": 9,
+ "type": 0,
+ "write": true,
+ "value": {
+ "value": {
+ "act": 0,
+ "rules": [
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ],
+ [
+ 3,
+ 4294967295,
+ 4294967295,
+ 4294967295
+ ]
+ ]
+ },
+ "statusFlags": 0,
+ "reliability": 0
+ },
+ "descriptionName": "Domestic hot water scheduler",
+ "objectName": "R(1)'CenDhw'DhwSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'DhwSched",
+ "translated": false
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_full_set_new.json b/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_full_set_new.json
new file mode 100644
index 0000000000000..89b4c92cbb725
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_full_set_new.json
@@ -0,0 +1,1625 @@
+{
+ "totalCount": 70,
+ "values": {
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF00000C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "AAS-20:SU=SiUn;APT=HvacFnct18z_A;APTV=2.003;APS=1;",
+ "descriptionName": "ApplicationSoftwareVersion",
+ "objectName": "ApplicationSoftwareVersion",
+ "memberName": "ApplicationSoftwareVersion",
+ "hierarchyName": "ApplicationSoftwareVersion",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF00001C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "Device object",
+ "descriptionName": "Device Description",
+ "objectName": "Device Description",
+ "memberName": "Description",
+ "hierarchyName": "Device Description",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF00002C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "FW=02.32.02.27;SVS-300.1:SBC=13.22;I",
+ "descriptionName": "FirmwareRevision",
+ "objectName": "FirmwareRevision",
+ "memberName": "FirmwareRevision",
+ "hierarchyName": "FirmwareRevision",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF000046": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "RDS110",
+ "descriptionName": "ModelName",
+ "objectName": "ModelName",
+ "memberName": "ModelName",
+ "hierarchyName": "ModelName",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF000070": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 0,
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "operational*operational-read-only*download-required*download-in-progress*non-operational*backup-in-progress",
+ "descriptionName": "SystemStatus",
+ "objectName": "SystemStatus",
+ "memberName": "SystemStatus",
+ "hierarchyName": "SystemStatus",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF000077": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 0,
+ "descriptionName": "UtcOffset",
+ "objectName": "UtcOffset",
+ "memberName": "UtcOffset",
+ "hierarchyName": "UtcOffset",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF00009B": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 29,
+ "descriptionName": "DatabaseRevision",
+ "objectName": "DatabaseRevision",
+ "memberName": "DatabaseRevision",
+ "hierarchyName": "DatabaseRevision",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF0000C4": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 4,
+ "limits": [
+ 0,
+ 7
+ ],
+ "descr": "unknown*coldstart*warmstart*detected-power-lost*detected-powered-off*hardware-watchdog*software-watchdog*suspended",
+ "descriptionName": "LastRestartReason",
+ "objectName": "LastRestartReason",
+ "memberName": "LastRestartReason",
+ "hierarchyName": "LastRestartReason",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF0012DB": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "MDL:ASN= RDS110;HW=0.2.0;",
+ "descriptionName": "ModelInformation",
+ "objectName": "ModelInformation",
+ "memberName": "4827",
+ "hierarchyName": "ModelInformation",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF001355": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 1,
+ "limits": [
+ 0,
+ 26
+ ],
+ "descr": "-*en*de*fr*es*cs*da*nl*fi*it*hu*nb*pl*pt*ru*sk*sv*zh*zh*ko*ro*tr*en-US*fr-CA*es-mx*pt-BR",
+ "descriptionName": "Active SystemLanguge",
+ "objectName": "Active SystemLanguge",
+ "memberName": "4949",
+ "hierarchyName": "Active SystemLanguge",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF0013B0": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 26,
+ "limits": [
+ 0,
+ 72
+ ],
+ "descr": "GMT-12:00 Kwajalein*GMT-11:00 Samoa, Midway*GMT-10:00 Hawaii*GMT-09:00 Alaska*GMT-08:00 Pacific Time*GMT-07:00 Arizona*GMT-07:00 Chihuahua*GMT-07:00 Mountain Time*GMT-06:00 Central America*GMT-06:00 Central Time*GMT-06:00 Mexico City*GMT-06:00 Saskatchewan*GMT-05:00 Bogota, Lima*GMT-05:00 Eastern Time*GMT-05:00 Indiana (USA)*GMT-04:00 Atlantic Time*GMT-04:00 Caracas, La Paz*GMT-04:00 Santiago*18*GMT-03:00 Brasilia*20*21*GMT-02:00 Mid-Atlantic*23*24*25*GMT London, Dublin, Lisbon*GMT+01:00 Berlin, Rome*GMT+01:00 Budapest, Prague*GMT+01:00 Paris, Madrid*GMT+01:00 Vienna, Warsaw*31*GMT+02:00 Athens, Istanbul*GMT+02:00 Bucharest*GMT+02:00 Cairo*GMT+02:00 Johannesburg, Harare*GMT+02:00 Helsinki, Riga*GMT+02:00 Jerusalem*38*GMT+03:00 Kuwait, Riyadh*GMT+04:00 Moscow*41*GMT+03:30 Tehran*GMT+04:00 Abu Dhabi, Muscat*GMT+04:00 Baku, Tbilisi*45*GMT+06:00 Ekaterinburg*47*GMT+05:30 New Delhi*49*GMT+07:00 Omsk, Novosibirsk *51*52*53*GMT+07:00 Bangkok, Jakarta*GMT+08:00 Krasnoyarsk*GMT+08:00 Beijing, Hong Kong*GMT+09:00 Irkutsk*GMT+08:00 Kuala Lumpur*GMT+08:00 Perth*GMT+08:00 Taipei*GMT+09:00 Tokyo, Osaka*GMT+09:00 Seoul*GMT+10:00 Yakutsk*GMT+09:30 Adelaide*GMT+09:30 Darwin*GMT+10:00 Brisbane*GMT+10:00 Melbourne, Sydney*68*69*GMT+11:00 Vladivostok*71*GMT+12:00 Auckland, Wellington",
+ "descriptionName": "TimeZone",
+ "objectName": "TimeZone",
+ "memberName": "5040",
+ "hierarchyName": "TimeZone",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF0013EC": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "160100078A",
+ "descriptionName": "SerialNumber",
+ "objectName": "SerialNumber",
+ "memberName": "5100",
+ "hierarchyName": "SerialNumber",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!0083FFFFF0013F4": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "'10010'B",
+ "descriptionName": "Device Features",
+ "objectName": "Device Features",
+ "memberName": "5108",
+ "hierarchyName": "Device Features",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!01D00000700001C": {
+ "rep": 0,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": "Downstairs",
+ "descriptionName": "R(1)'Description",
+ "objectName": "R(1)'Description",
+ "memberName": "Description",
+ "hierarchyName": "R(1)'Description",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!10800000000130B": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "192.168.1.1",
+ "descriptionName": "NwkPortIP'IP gefault gateway",
+ "objectName": "NwkPortIP'IP gefault gateway",
+ "memberName": "4875",
+ "hierarchyName": "NwkPortIP'IP gefault gateway",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!10800000000130C": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "255.255.255.0",
+ "descriptionName": "NwkPortIP'IP subnet mask",
+ "objectName": "NwkPortIP'IP subnet mask",
+ "memberName": "4876",
+ "hierarchyName": "NwkPortIP'IP subnet mask",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!10800000000130D": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "192.168.1.41",
+ "descriptionName": "NwkPortIP'IP address",
+ "objectName": "NwkPortIP'IP address",
+ "memberName": "4877",
+ "hierarchyName": "NwkPortIP'IP address",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!10800000000130E": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 47808,
+ "descriptionName": "NwkPortIP'UDP Port",
+ "objectName": "NwkPortIP'UDP Port",
+ "memberName": "4878",
+ "hierarchyName": "NwkPortIP'UDP Port",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!108000000001313": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "'F0C77F6C10A1'H",
+ "descriptionName": "NwkPortIP'BACnet MAC address",
+ "objectName": "NwkPortIP'BACnet MAC address",
+ "memberName": "4883",
+ "hierarchyName": "NwkPortIP'BACnet MAC address",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!108000001001286": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "sth.connectivity.ccl-siemens.com",
+ "descriptionName": "NwkPortCCL'Connection URI",
+ "objectName": "NwkPortCCL'Connection URI",
+ "memberName": "4742",
+ "hierarchyName": "NwkPortCCL'Connection URI",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!108000001001287": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "HABEAS-XXQFD-JRHUP-YNAFG-FNE4Q",
+ "descriptionName": "NwkPortCCL'Activation Key",
+ "objectName": "NwkPortCCL'Activation Key",
+ "memberName": "4743",
+ "hierarchyName": "NwkPortCCL'Activation Key",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!108000001001288": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 60,
+ "descriptionName": "NwkPortCCL'Reconection delay",
+ "objectName": "NwkPortCCL'Reconection delay",
+ "memberName": "4744",
+ "hierarchyName": "NwkPortCCL'Reconection delay",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!DPUpdCurMon": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 3685,
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Item Updates Current Month",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!DPUpdPerMin": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 1,
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Item Updates per Minute",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!DPUpdTotal": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 429857,
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Item Updates Total",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!Languages": {
+ "rep": 0,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": "-;en",
+ "limits": [
+ 0,
+ 1
+ ],
+ "objectName": "CSL-Config",
+ "memberName": "Languages",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!Online": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 1,
+ "limits": [
+ 0,
+ 1
+ ],
+ "descr": "Offline*Online",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Online",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!TrfcInCurMon": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 6836970,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Inbound Current Month",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!TrfcInPerMin": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 183,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Inbound per Minute",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!TrfcInTotal": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 361410891,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Inbound Total",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!TrfcOutCurMon": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 1641440,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Outbound Current Month",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!TrfcOutPerMin": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 62,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Outbound per Minute",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;0!TrfcOutTotal": {
+ "rep": 2,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": 105878276,
+ "descr": "bytes",
+ "descriptionName": "Target",
+ "objectName": "Target",
+ "memberName": "Traffic Outbound Total",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!00000000E000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 21.4816036,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": -50,
+ "maxValue": 80
+ },
+ "limits": [
+ -50,
+ 80
+ ],
+ "descr": "�C",
+ "descriptionName": "Outside air temperature",
+ "objectName": "R(1)'TOa",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'TOa",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!00200007F000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 24,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 17,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "�C",
+ "descriptionName": "Comfort heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHCmf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpHCmf",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000080000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 24,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "�C",
+ "descriptionName": "Economy heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHPcf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpHPcf",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000081000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 16,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "�C",
+ "descriptionName": "Unoccupied heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHEco",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpHEco",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000082000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 6,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "�C",
+ "descriptionName": "Protection heating setpoint",
+ "objectName": "R(1)'RHvacCoo'TCtlH'SpHPrt",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'SpHPrt",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000083000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 16,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0,
+ "minValue": 6,
+ "maxValue": 35
+ },
+ "limits": [
+ 6,
+ 35
+ ],
+ "descr": "�C",
+ "descriptionName": "Room temperature setpoint",
+ "objectName": "R(1)'RHvacCoo'SpTRDtr'SpTR",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpTR",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000084000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0,
+ "minValue": -10,
+ "maxValue": 19
+ },
+ "limits": [
+ -18,
+ 11
+ ],
+ "descr": "K",
+ "descriptionName": "Room temperature setpoint shift",
+ "objectName": "R(1)'RHvacCoo'SpTRDtr'SpTRShft",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'SpTRShft",
+ "translated": false,
+ "resolution": 0.10000000149011612,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000085000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 36.1050224,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 100
+ },
+ "limits": [
+ 0,
+ 100
+ ],
+ "descr": "%r.H.",
+ "descriptionName": "Relative humidity for room",
+ "objectName": "R(1)'RHvacCoo'RHuRel",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RHuRel",
+ "translated": false,
+ "resolution": 1,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!002000086000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 24.2482586,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "�C",
+ "descriptionName": "Room temperature",
+ "objectName": "R(1)'RHvacCoo'RTemp",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RTemp",
+ "translated": false,
+ "resolution": 0.10000000149011612,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!0020000B2000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 35,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ },
+ "limits": [
+ 0,
+ 50
+ ],
+ "descr": "�C",
+ "descriptionName": "Max. heating setpoint",
+ "objectName": "R(1)'SpTRMaxHCmf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'SpTRMaxHCmf",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!0020000B4000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 30,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 120
+ },
+ "limits": [
+ 0,
+ 120
+ ],
+ "descr": "Unknown Unit(236)",
+ "descriptionName": "Warm-up gradient",
+ "objectName": "R(1)'WarmUpGrdnt",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'WarmUpGrdnt",
+ "translated": false,
+ "resolution": 5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!0020000B5000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": -5,
+ "maxValue": 5
+ },
+ "limits": [
+ -5,
+ 5
+ ],
+ "descr": "K",
+ "descriptionName": "Built-in temp. sensor adj.",
+ "objectName": "R(1)'TRBltnMsvAdj",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'TRBltnMsvAdj",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!0020000C6000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 5
+ },
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "A",
+ "descriptionName": "Heating device electrical load",
+ "objectName": "R(1)'HDevElLd",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'HDevElLd",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!0020000CB000055": {
+ "rep": 1,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 5
+ },
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "A",
+ "descriptionName": "Q22/Q24 electrical load",
+ "objectName": "R(1)'Q22Q24ElLd",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'Q22Q24ElLd",
+ "translated": false,
+ "resolution": 0.5,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!0020000CD000055": {
+ "rep": 1,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 585.4,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 5000
+ },
+ "limits": [
+ 0,
+ 5000
+ ],
+ "descr": "ppm",
+ "descriptionName": "Room air quality",
+ "objectName": "R(1)'RHvacCoo'RAQual",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RAQual",
+ "translated": false,
+ "resolution": 100,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!005000038000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Inactive*Active*Null",
+ "descriptionName": "Temporary comfort button",
+ "objectName": "R(1)'ROpModDtr'TmpCmfBtn",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'TmpCmfBtn",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!005000039000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Inactive*Active*Null",
+ "descriptionName": "Comfort button",
+ "objectName": "R(1)'ROpModDtr'CmfBtn",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'CmfBtn",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!00500003B000055": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Absent*Present*Null",
+ "descriptionName": "Room presence detection",
+ "objectName": "R(1)'RHvacCoo'RPscDet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RPscDet",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!00500003F000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 1,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 17,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "No*Yes*Null",
+ "descriptionName": "Enable heating control",
+ "objectName": "R(1)'RHvacCoo'TCtlH'EnHCtl",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'EnHCtl",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!005000054000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 0,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Inactive*Active*Null",
+ "descriptionName": "Room presence detector",
+ "objectName": "R(1)'EnRPscDet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'EnRPscDet",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!01300004C000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Null*Off*Protection",
+ "descriptionName": "Off/protection configuration",
+ "objectName": "R(1)'OffPrtCnf",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'OffPrtCnf",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000051000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 3
+ ],
+ "descr": "Null*Off*Absent*Present",
+ "descriptionName": "Occupancy mode",
+ "objectName": "R(1)'ROpModDtr'OccMod",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'OccMod",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000052000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 5,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 5
+ ],
+ "descr": "Null*Undefined*Poor*Satisfactory*Good*Excellent",
+ "descriptionName": "Energy efficiency indication room",
+ "objectName": "R(1)'RGrnLf'REei",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'REei",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000053000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 1,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Null*Off*On",
+ "descriptionName": "Domestic hot water mode",
+ "objectName": "R(1)'RHvacCoo'DhwOp'DhwMod",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'DhwMod",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000056000055": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 1,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 3
+ ],
+ "descr": "Null*Neither*Heating*Cooling",
+ "descriptionName": "Heating/cooling state",
+ "objectName": "R(1)'RHvacCoo'HCStaDtr'HCSta",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'HCSta",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!01300005A000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 4
+ ],
+ "descr": "Null*Protection*Unoccupied*Economy*Comfort",
+ "descriptionName": "Present operating mode and reason",
+ "objectName": "R(1)'RHvacCoo'PrOpModRsn",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'PrOpModRsn",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000071000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 6,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 6
+ ],
+ "descr": "Null*Default*Slow*Medium*Fast*2-position*Self-adaptive",
+ "descriptionName": "Heating control loop",
+ "objectName": "R(1)'HCtrSet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'HCtrSet",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000072000055": {
+ "rep": 3,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descr": "Null*Warm-up gradient*Self-adaptive",
+ "descriptionName": "Optimum start control setting",
+ "objectName": "R(1)'OsscSet",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'OsscSet",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!013000074000055": {
+ "rep": 3,
+ "type": 0,
+ "write": false,
+ "webhookEnabled": false,
+ "value": {
+ "value": 4,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ },
+ "limits": [
+ 0,
+ 4
+ ],
+ "descr": "Null*Undefined*Poor*Okay*Good",
+ "descriptionName": "Room air quality indication",
+ "objectName": "R(1)'RHvacCoo'RAQualInd",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'RAQualInd",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!030000000000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 500,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 1,
+ "maxValue": 8760
+ },
+ "limits": [
+ 1,
+ 8760
+ ],
+ "descr": "h",
+ "descriptionName": "Pump/valve kick cycle",
+ "objectName": "R(1)'KickCyc",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'KickCyc",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!030000001000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 180000,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 3600000
+ },
+ "limits": [
+ 0,
+ 3600000
+ ],
+ "descr": "ms",
+ "descriptionName": "DHW min. ON time",
+ "objectName": "R(1)'BoDhwTiOnMin",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'BoDhwTiOnMin",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!030000002000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 180000,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 3600000
+ },
+ "limits": [
+ 0,
+ 3600000
+ ],
+ "descr": "ms",
+ "descriptionName": "DHW min. OFF time",
+ "objectName": "R(1)'BoDhwTiOffMin",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrAplSet'BoDhwTiOffMin",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;1!030000007000055": {
+ "rep": 2,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": 604800,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 4294967295
+ },
+ "limits": [
+ 0,
+ 4294967295
+ ],
+ "descr": "min",
+ "descriptionName": "Operating hours heating",
+ "objectName": "R(1)'RHvacCoo'OphHCDtr'OphH",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'OphH",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;2!011000004000055": {
+ "rep": 8,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": {
+ "cmd": "Command",
+ "cal": null,
+ "act": 0,
+ "null": 3,
+ "default": 0,
+ "schedStart": 4294967295,
+ "schedEnd": 4294967295,
+ "days": [
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ [
+ 0,
+ 3
+ ],
+ [
+ 7687,
+ 4
+ ],
+ [
+ 7701,
+ 3
+ ]
+ ],
+ [
+ ]
+ ],
+ "timeTillNextValue": 509,
+ "nextValue": 3
+ },
+ "statusFlags": 0,
+ "reliability": 0
+ },
+ "limits": [
+ 0,
+ 4
+ ],
+ "descriptionName": "Room operating mode scheduler",
+ "objectName": "R(1)'CenOpMod'ROpModSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'ROpModSched",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;2!011000005000055": {
+ "rep": 8,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "value": {
+ "value": {
+ "cmd": "Command",
+ "cal": null,
+ "act": 0,
+ "null": 3,
+ "default": 0,
+ "schedStart": 4294967295,
+ "schedEnd": 4294967295,
+ "days": [
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ [
+ 0,
+ 1
+ ],
+ [
+ 9,
+ 2
+ ],
+ [
+ 7689,
+ 1
+ ],
+ [
+ 21,
+ 2
+ ],
+ [
+ 7701,
+ 1
+ ]
+ ],
+ [
+ ]
+ ],
+ "timeTillNextValue": 479,
+ "nextValue": 2
+ },
+ "statusFlags": 0,
+ "reliability": 0
+ },
+ "limits": [
+ 0,
+ 2
+ ],
+ "descriptionName": "Domestic hot water scheduler",
+ "objectName": "R(1)'CenDhw'DhwSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'DhwSched",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;3!01100000400004E": {
+ "rep": 9,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "descriptionName": "Room operating mode scheduler",
+ "objectName": "R(1)'CenOpMod'ROpModSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'ROpModSched",
+ "translated": false,
+ "isVirtual": false
+ },
+ "Pfaf770c8-abeb-4742-ad65-ead39030d369;3!01100000500004E": {
+ "rep": 9,
+ "type": 0,
+ "write": true,
+ "webhookEnabled": false,
+ "descriptionName": "Domestic hot water scheduler",
+ "objectName": "R(1)'CenDhw'DhwSched",
+ "memberName": "PresentValue",
+ "hierarchyName": "R(1)'FvrBscOp'DhwSched",
+ "translated": false,
+ "isVirtual": false
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_refresh_set.json b/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_refresh_set.json
new file mode 100644
index 0000000000000..06af2d9dfd209
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/test/resources/datapoints_refresh_set.json
@@ -0,0 +1,101 @@
+{
+ "totalCount": 11,
+ "values": {
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;0!Online": {
+ "value": 1
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!00000000E000055": {
+ "value": {
+ "value": 12.6014862,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": -50,
+ "maxValue": 80
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000083000055": {
+ "value": {
+ "value": 16,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0,
+ "minValue": 6,
+ "maxValue": 35
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000085000055": {
+ "value": {
+ "value": 39.1304474,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 100
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!002000086000055": {
+ "value": {
+ "value": 21.51872,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0,
+ "minValue": 0,
+ "maxValue": 50
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000051000055": {
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000052000055": {
+ "value": {
+ "value": 5,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000053000055": {
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 15,
+ "eventState": 0
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000056000055": {
+ "value": {
+ "value": 1,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!01300005A000055": {
+ "value": {
+ "value": 2,
+ "statusFlags": 0,
+ "reliability": 0,
+ "presentPriority": 13,
+ "eventState": 0
+ }
+ },
+ "Pd1774247-7de7-4896-ac76-b7e0dd943c40;1!013000074000055": {
+ "value": {
+ "value": 4,
+ "statusFlags": 0,
+ "reliability": 0,
+ "eventState": 0
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.siemensrds/src/test/resources/plants.json b/bundles/org.openhab.binding.siemensrds/src/test/resources/plants.json
new file mode 100644
index 0000000000000..4558cc02faefa
--- /dev/null
+++ b/bundles/org.openhab.binding.siemensrds/src/test/resources/plants.json
@@ -0,0 +1,61 @@
+{
+ "totalCount": 2,
+ "items": [
+ {
+ "id": "Pd1774247-7de7-4896-ac76-b7e0dd943c40",
+ "activationKey": "this-is-not-a-valid-activation-key",
+ "address": "",
+ "alarmStatus": 0,
+ "applicationSetDescription": "Siemens Smart Thermostat\r\nRDS110 => Device ID 45\r\n",
+ "applicationSetId": "9964755b-6766-40bd-ba45-77b2446b71bb",
+ "applicationSetName": "STH-Default-RDS110",
+ "asn": "RDS110",
+ "assigned": true,
+ "city": "",
+ "country": "",
+ "description": "",
+ "energyIndicator": 0,
+ "isOnline": true,
+ "name": "this-is-not-a-valid-activation-key-RDS110",
+ "phone": "",
+ "serialNumber": "this-is-not-a-valid-activation-key",
+ "state": "",
+ "taskStatus": 0,
+ "tenant": "Siemens STH",
+ "tenantId": "T290ea1c1-902c-4c0b-9dce-f96119bc7fc1",
+ "timezone": "",
+ "zipCode": "",
+ "imsi": "",
+ "customerPlantId": null,
+ "enhancedPrivileges": false
+ },
+ {
+ "id": "Pfaf770c8-abeb-4742-ad65-ead39030d369",
+ "activationKey": "this-is-not-a-valid-activation-key",
+ "address": "",
+ "alarmStatus": 0,
+ "applicationSetDescription": "Siemens Smart Thermostat\r\nRDS110 => Device ID 45\r\n",
+ "applicationSetId": "9964755b-6766-40bd-ba45-77b2446b71bb",
+ "applicationSetName": "STH-Default-RDS110",
+ "asn": "RDS110",
+ "assigned": true,
+ "city": "",
+ "country": "",
+ "description": "",
+ "energyIndicator": 0,
+ "isOnline": true,
+ "name": "this-is-not-a-valid-activation-key-RDS110",
+ "phone": "",
+ "serialNumber": "this-is-not-a-valid-activation-key",
+ "state": "",
+ "taskStatus": 0,
+ "tenant": "Siemens STH",
+ "tenantId": "T290ea1c1-902c-4c0b-9dce-f96119bc7fc1",
+ "timezone": "",
+ "zipCode": "",
+ "imsi": "",
+ "customerPlantId": null,
+ "enhancedPrivileges": false
+ }
+ ]
+}