2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.homematic.internal.communicator.parser;
15 import java.io.IOException;
16 import java.util.Objects;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.openhab.binding.homematic.internal.misc.MiscUtils;
20 import org.openhab.binding.homematic.internal.model.HmDatapoint;
21 import org.openhab.binding.homematic.internal.model.HmParamsetType;
22 import org.openhab.binding.homematic.internal.model.HmValueType;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * Abstract base class for all parsers with common methods.
29 * @author Gerhard Riegler - Initial contribution
31 public abstract class CommonRpcParser<M, R> implements RpcParser<M, R> {
33 private final Logger logger = LoggerFactory.getLogger(CommonRpcParser.class);
36 * Converts the object to a string.
38 protected String toString(Object object) {
39 String value = Objects.toString(object, "").trim();
40 return value.isEmpty() ? null : value;
44 * Converts the object to a integer.
46 protected Integer toInteger(Object object) {
47 if (object == null || object instanceof Integer) {
48 return (Integer) object;
51 return Double.valueOf(object.toString()).intValue();
52 } catch (NumberFormatException ex) {
53 logger.debug("Failed converting {} to a Double", object, ex);
59 * Converts the object to a double.
61 protected Double toDouble(Object object) {
62 if (object == null || object instanceof Double) {
63 return (Double) object;
66 return Double.valueOf(object.toString());
67 } catch (NumberFormatException ex) {
68 logger.debug("Failed converting {} to a Double", object, ex);
74 * Converts the object to a number.
76 protected Number toNumber(Object object) {
77 if (object == null || object instanceof Number) {
78 return (Number) object;
81 String value = object.toString();
82 if (value.contains(".")) {
83 return Float.parseFloat(value);
85 return Integer.parseInt(value);
87 } catch (NumberFormatException ex) {
88 logger.debug("Failed converting {} to a Number", object, ex);
94 * Converts the object to a boolean.
96 protected Boolean toBoolean(Object object) {
97 if (object == null || object instanceof Boolean) {
98 return (Boolean) object;
100 return "true".equals(object.toString().toLowerCase());
104 * Converts the object to a string array.
106 protected String[] toOptionList(Object optionList) {
107 if (optionList != null && optionList instanceof Object[]) {
108 Object[] vl = (Object[]) optionList;
109 String[] stringArray = new String[vl.length];
110 for (int i = 0; i < vl.length; i++) {
111 stringArray[i] = vl[i].toString();
119 * Returns the address of a device, replacing group address identifier and illegal characters.
122 protected String getSanitizedAddress(Object object) {
123 String address = Objects.toString(object, "").trim().replaceFirst("\\*", "T-");
124 return MiscUtils.validateCharacters(address.isEmpty() ? null : address, "Address", "_");
128 * Adjust uninitialized rssi values to zero.
130 protected void adjustRssiValue(HmDatapoint dp) {
131 if (dp.getValue() != null && dp.getName().startsWith("RSSI_") && dp.isIntegerType()) {
132 int rssiValue = ((Number) dp.getValue()).intValue();
133 dp.setValue(getAdjustedRssiValue(rssiValue));
138 * Adjust a rssi value if it is out of range.
140 protected Integer getAdjustedRssiValue(Integer rssiValue) {
141 if (rssiValue == null || rssiValue >= 255 || rssiValue <= -255) {
148 * Converts the value to the correct type if necessary.
150 protected Object convertToType(HmDatapoint dp, Object value) {
153 } else if (dp.isBooleanType()) {
154 return toBoolean(value);
155 } else if (dp.isIntegerType()) {
156 return toInteger(value);
157 } else if (dp.isFloatType()) {
158 return toNumber(value);
159 } else if (dp.isStringType()) {
160 return toString(value);
167 * Assembles a datapoint with the given parameters.
169 protected HmDatapoint assembleDatapoint(String name, String unit, String type, String[] options, Object min,
170 Object max, Integer operations, Object defaultValue, HmParamsetType paramsetType, boolean isHmIpDevice)
172 HmDatapoint dp = new HmDatapoint();
174 dp.setDescription(name);
176 unit = unit.trim().replace("\ufffd", "°");
178 dp.setUnit(unit == null || unit.isEmpty() ? null : unit);
179 if (dp.getUnit() == null && dp.getName() != null && dp.getName().startsWith("RSSI_")) {
183 HmValueType valueType = HmValueType.parse(type);
184 if (valueType == null || valueType == HmValueType.UNKNOWN) {
185 throw new IOException("Unknown datapoint type: " + type);
186 } else if (valueType == HmValueType.FLOAT && dp.getUnit() == null
187 && dp.getName().matches("\\w*_TEMPERATURE(_\\w.*|$)")) {
188 logger.debug("No unit information found for temperature datapoint {}, assuming Number:Temperature",
190 dp.setUnit("°C"); // Bypass for a problem with HMIP devices where unit of temperature channels is sometimes
193 dp.setType(valueType);
195 dp.setOptions(options);
196 if (dp.isNumberType() || dp.isEnumType()) {
197 if (isHmIpDevice && dp.isEnumType()) {
198 dp.setMinValue(dp.getOptionIndex(toString(min)));
199 dp.setMaxValue(dp.getOptionIndex(toString(max)));
201 dp.setMinValue(toNumber(min));
202 dp.setMaxValue(toNumber(max));
205 dp.setReadOnly((operations & 2) != 2);
206 dp.setReadable((operations & 1) == 1);
207 dp.setParamsetType(paramsetType);
208 if (isHmIpDevice && dp.isEnumType()) {
209 dp.setDefaultValue(dp.getOptionIndex(toString(defaultValue)));
211 dp.setDefaultValue(convertToType(dp, defaultValue));
213 dp.setValue(dp.getDefaultValue());
218 * Converts a string value to the type.
220 protected Object convertToType(String value) {
221 if (value == null || value.isBlank()) {
224 if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("on")) {
225 return (Boolean.TRUE);
226 } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("off")) {
227 return (Boolean.FALSE);
228 } else if (value.matches("(-|\\+)?[0-9]+")) {
229 return (Integer.valueOf(value));
230 } else if (value.matches("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?")) {
231 return (Double.valueOf(value));