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.shelly.internal.util;
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
17 import java.io.UnsupportedEncodingException;
18 import java.math.BigDecimal;
19 import java.math.RoundingMode;
20 import java.net.URLEncoder;
21 import java.nio.charset.StandardCharsets;
22 import java.time.DateTimeException;
23 import java.time.Instant;
24 import java.time.LocalDateTime;
25 import java.time.ZoneId;
26 import java.time.ZonedDateTime;
28 import javax.measure.Unit;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.shelly.internal.api.ShellyApiException;
33 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.PercentType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.State;
42 import org.openhab.core.types.UnDefType;
44 import com.google.gson.Gson;
45 import com.google.gson.JsonSyntaxException;
46 import com.google.gson.internal.Primitives;
49 * {@link ShellyUtils} provides general utility functions
51 * @author Markus Michels - Initial contribution
54 public class ShellyUtils {
55 private final static String PRE = "Unable to create object of type ";
57 public static <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT) throws ShellyApiException {
59 T o = fromJson(gson, json, classOfT, true);
61 throw new ShellyApiException("Unable to create JSON object");
66 public static @Nullable <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT, boolean exceptionOnNull)
67 throws ShellyApiException {
68 String className = substringAfter(classOfT.getName(), "$");
71 if (exceptionOnNull) {
72 throw new IllegalArgumentException(PRE + className + ": json is null!");
78 if (classOfT.isInstance(json)) {
79 return Primitives.wrap(classOfT).cast(json);
80 } else if (json.isEmpty()) { // update GSON might return null
81 throw new ShellyApiException(PRE + className + "from empty JSON");
85 T obj = gson.fromJson(json, classOfT);
86 if ((obj == null) && exceptionOnNull) { // new in OH3: fromJson may return null
87 throw new ShellyApiException(PRE + className + "from JSON: " + json);
90 } catch (JsonSyntaxException e) {
91 throw new ShellyApiException(PRE + className + "from JSON (syntax/format error): " + json, e);
92 } catch (RuntimeException e) {
93 throw new ShellyApiException(PRE + className + "from JSON: " + json, e);
98 public static String mkChannelId(String group, String channel) {
99 return group + "#" + channel;
102 public static String getString(@Nullable String value) {
103 return value != null ? value : "";
106 public static String substringBefore(@Nullable String string, String pattern) {
107 if (string != null) {
108 int pos = string.indexOf(pattern);
110 return string.substring(0, pos);
116 public static String substringBeforeLast(@Nullable String string, String pattern) {
117 if (string != null) {
118 int pos = string.lastIndexOf(pattern);
120 return string.substring(0, pos);
126 public static String substringAfter(@Nullable String string, String pattern) {
127 if (string != null) {
128 int pos = string.indexOf(pattern);
130 return string.substring(pos + pattern.length());
136 public static String substringAfterLast(@Nullable String string, String pattern) {
137 if (string == null) {
140 int pos = string.lastIndexOf(pattern);
142 return string.substring(pos + pattern.length());
147 public static String substringBetween(@Nullable String string, String begin, String end) {
148 if (string != null) {
149 int s = string.indexOf(begin);
151 // The end tag might be included before the start tag, e.g.
152 // when using "http://" and ":" to get the IP from http://192.168.1.1:8081/xxx
153 // therefore make it 2 steps
154 String result = string.substring(s + begin.length());
155 return substringBefore(result, end);
161 public static String getMessage(Exception e) {
162 String message = e.getMessage();
163 return message != null ? message : "";
166 public static Integer getInteger(@Nullable Integer value) {
167 return (value != null ? (Integer) value : 0);
170 public static Long getLong(@Nullable Long value) {
171 return (value != null ? (Long) value : 0);
174 public static Double getDouble(@Nullable Double value) {
175 return (value != null ? (Double) value : 0);
178 public static Boolean getBool(@Nullable Boolean value) {
179 return (value != null ? (Boolean) value : false);
184 public static StringType getStringType(@Nullable String value) {
185 return new StringType(value != null ? value : "");
188 public static DecimalType getDecimal(@Nullable Double value) {
189 return new DecimalType((value != null ? value : 0));
192 public static DecimalType getDecimal(@Nullable Integer value) {
193 return new DecimalType((value != null ? value : 0));
196 public static DecimalType getDecimal(@Nullable Long value) {
197 return new DecimalType((value != null ? value : 0));
200 public static Double getNumber(Command command) throws IllegalArgumentException {
201 if (command instanceof DecimalType) {
202 return ((DecimalType) command).doubleValue();
204 if (command instanceof QuantityType) {
205 return ((QuantityType<?>) command).doubleValue();
207 throw new IllegalArgumentException("Unable to convert number");
210 public static OnOffType getOnOff(@Nullable Boolean value) {
211 return (value != null ? value ? OnOffType.ON : OnOffType.OFF : OnOffType.OFF);
214 public static OnOffType getOnOff(int value) {
215 return value == 0 ? OnOffType.OFF : OnOffType.ON;
218 public static State toQuantityType(@Nullable Double value, int digits, Unit<?> unit) {
220 return UnDefType.NULL;
222 BigDecimal bd = new BigDecimal(value.doubleValue());
223 return toQuantityType(bd.setScale(digits, RoundingMode.HALF_UP), unit);
226 public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
227 return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
230 public static State toQuantityType(@Nullable PercentType value, Unit<?> unit) {
231 return value == null ? UnDefType.NULL : toQuantityType(value.toBigDecimal(), unit);
234 public static void validateRange(String name, Integer value, int min, int max) {
235 if ((value < min) || (value > max)) {
236 throw new IllegalArgumentException("Value " + name + " is out of range (" + min + "-" + max + ")");
240 public static String urlEncode(String input) {
242 return URLEncoder.encode(input, StandardCharsets.UTF_8.toString());
243 } catch (UnsupportedEncodingException e) {
248 public static Long now() {
249 return System.currentTimeMillis() / 1000L;
252 public static DateTimeType getTimestamp() {
253 return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(now()), ZoneId.systemDefault()));
256 public static DateTimeType getTimestamp(String zone, long timestamp) {
258 if (timestamp == 0) {
259 return getTimestamp();
261 ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault();
262 ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId);
263 int delta = zdt.getOffset().getTotalSeconds();
264 return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp - delta), zoneId));
265 } catch (DateTimeException e) {
266 // Unable to convert device's timezone, use system one
267 return getTimestamp();
271 public static Integer getLightIdFromGroup(String groupName) {
272 if (groupName.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
273 return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_CHANNEL)) - 1;
275 return 0; // only 1 light, e.g. bulb or rgbw2 in color mode
278 public static String buildControlGroupName(ShellyDeviceProfile profile, Integer channelId) {
279 return profile.isBulb || profile.isDuo || profile.inColor ? CHANNEL_GROUP_LIGHT_CONTROL
280 : CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
283 public static String buildWhiteGroupName(ShellyDeviceProfile profile, Integer channelId) {
284 return profile.isBulb || profile.isDuo ? CHANNEL_GROUP_WHITE_CONTROL
285 : CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
288 public static DecimalType mapSignalStrength(int dbm) {
292 } else if (dbm > -70) {
294 } else if (dbm > -80) {
296 } else if (dbm > -90) {
301 return new DecimalType(strength);