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 + } + ] +}