]> git.basschouten.com Git - openhab-addons.git/blob
1fdc36b6c3cb3f9993b0bcc028c7cfb5c3a797e8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.shelly.internal.util;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16
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.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.time.DateTimeException;
25 import java.time.Instant;
26 import java.time.LocalDateTime;
27 import java.time.ZoneId;
28 import java.time.ZonedDateTime;
29 import java.time.format.DateTimeFormatter;
30
31 import javax.measure.Unit;
32
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.shelly.internal.api.ShellyApiException;
36 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
37 import org.openhab.core.library.types.DateTimeType;
38 import org.openhab.core.library.types.DecimalType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.OpenClosedType;
41 import org.openhab.core.library.types.PercentType;
42 import org.openhab.core.library.types.QuantityType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47
48 import com.google.gson.Gson;
49 import com.google.gson.JsonSyntaxException;
50
51 /**
52  * {@link ShellyUtils} provides general utility functions
53  *
54  * @author Markus Michels - Initial contribution
55  */
56 @NonNullByDefault
57 public class ShellyUtils {
58     private static final String PRE = "Unable to create object of type ";
59     public static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern(DateTimeType.DATE_PATTERN);
60
61     public static <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT) throws ShellyApiException {
62         @Nullable
63         T o = fromJson(gson, json, classOfT, true);
64         if (o == null) {
65             throw new ShellyApiException("Unable to create JSON object");
66         }
67         return o;
68     }
69
70     public static @Nullable <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT, boolean exceptionOnNull)
71             throws ShellyApiException {
72         String className = substringAfter(classOfT.getName(), "$");
73
74         if (json == null) {
75             if (exceptionOnNull) {
76                 throw new IllegalArgumentException(PRE + className + ": json is null!");
77             } else {
78                 return null;
79             }
80         }
81
82         if (classOfT.isInstance(json)) {
83             return wrap(classOfT).cast(json);
84         } else if (json.isEmpty()) { // update GSON might return null
85             throw new ShellyApiException(PRE + className + " from empty JSON");
86         } else {
87             try {
88                 @Nullable
89                 T obj = gson.fromJson(json, classOfT);
90                 if ((obj == null) && exceptionOnNull) { // new in OH3: fromJson may return null
91                     throw new ShellyApiException(PRE + className + " from JSON: " + json);
92                 }
93                 return obj;
94             } catch (JsonSyntaxException e) {
95                 throw new ShellyApiException(
96                         PRE + className + " from JSON (syntax/format error: " + e.getMessage() + "): " + json, e);
97             } catch (RuntimeException e) {
98                 throw new ShellyApiException(
99                         PRE + className + " from JSON (" + getString(e.getMessage() + "), JSON=" + json), e);
100             }
101         }
102     }
103
104     @SuppressWarnings("unchecked")
105     private static <T> Class<T> wrap(Class<T> type) {
106         if (type == int.class) {
107             return (Class<T>) Integer.class;
108         }
109         if (type == float.class) {
110             return (Class<T>) Float.class;
111         }
112         if (type == byte.class) {
113             return (Class<T>) Byte.class;
114         }
115         if (type == double.class) {
116             return (Class<T>) Double.class;
117         }
118         if (type == long.class) {
119             return (Class<T>) Long.class;
120         }
121         if (type == char.class) {
122             return (Class<T>) Character.class;
123         }
124         if (type == boolean.class) {
125             return (Class<T>) Boolean.class;
126         }
127         if (type == short.class) {
128             return (Class<T>) Short.class;
129         }
130         if (type == void.class) {
131             return (Class<T>) Void.class;
132         }
133         return type;
134     }
135
136     public static String mkChannelId(String group, String channel) {
137         return group + "#" + channel;
138     }
139
140     public static String getString(@Nullable String value) {
141         return value != null ? value : "";
142     }
143
144     public static String substringBefore(@Nullable String string, String pattern) {
145         if (string != null) {
146             int pos = string.indexOf(pattern);
147             if (pos > 0) {
148                 return string.substring(0, pos);
149             }
150         }
151         return "";
152     }
153
154     public static String substringBeforeLast(@Nullable String string, String pattern) {
155         if (string != null) {
156             int pos = string.lastIndexOf(pattern);
157             if (pos > 0) {
158                 return string.substring(0, pos);
159             }
160         }
161         return "";
162     }
163
164     public static String substringAfter(@Nullable String string, String pattern) {
165         if (string != null) {
166             int pos = string.indexOf(pattern);
167             if (pos != -1) {
168                 return string.substring(pos + pattern.length());
169             }
170         }
171         return "";
172     }
173
174     public static String substringAfterLast(@Nullable String string, String pattern) {
175         if (string == null) {
176             return "";
177         }
178         int pos = string.lastIndexOf(pattern);
179         if (pos != -1) {
180             return string.substring(pos + pattern.length());
181         }
182         return string;
183     }
184
185     public static String substringBetween(@Nullable String string, String begin, String end) {
186         if (string != null) {
187             int s = string.indexOf(begin);
188             if (s != -1) {
189                 // The end tag might be included before the start tag, e.g.
190                 // when using "http://" and ":" to get the IP from http://192.168.1.1:8081/xxx
191                 // therefore make it 2 steps
192                 String result = string.substring(s + begin.length());
193                 return substringBefore(result, end);
194             }
195         }
196         return "";
197     }
198
199     public static String getMessage(Exception e) {
200         String message = e.getMessage();
201         return message != null ? message : "";
202     }
203
204     public static Integer getInteger(@Nullable Integer value) {
205         return (value != null ? (Integer) value : 0);
206     }
207
208     public static Long getLong(@Nullable Long value) {
209         return (value != null ? (Long) value : 0);
210     }
211
212     public static Double getDouble(@Nullable Double value) {
213         return (value != null ? (Double) value : 0);
214     }
215
216     public static Boolean getBool(@Nullable Boolean value) {
217         return (value != null ? (Boolean) value : false);
218     }
219
220     // as State
221
222     public static StringType getStringType(@Nullable String value) {
223         return new StringType(value != null ? value : "");
224     }
225
226     public static DecimalType getDecimal(@Nullable Double value) {
227         return new DecimalType((value != null ? value : 0));
228     }
229
230     public static DecimalType getDecimal(@Nullable Integer value) {
231         return new DecimalType((value != null ? value : 0));
232     }
233
234     public static DecimalType getDecimal(@Nullable Long value) {
235         return new DecimalType((value != null ? value : 0));
236     }
237
238     public static Double getNumber(Command command) throws IllegalArgumentException {
239         if (command instanceof DecimalType decimalCommand) {
240             return decimalCommand.doubleValue();
241         }
242         if (command instanceof QuantityType quantityCommand) {
243             return quantityCommand.doubleValue();
244         }
245         throw new IllegalArgumentException("Unable to convert number");
246     }
247
248     public static OnOffType getOnOff(@Nullable Boolean value) {
249         return OnOffType.from(value != null && value);
250     }
251
252     public static OpenClosedType getOpenClosed(@Nullable Boolean value) {
253         return (value != null && value ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
254     }
255
256     public static OnOffType getOnOff(int value) {
257         return OnOffType.from(value != 0);
258     }
259
260     public static State toQuantityType(@Nullable Double value, int digits, Unit<?> unit) {
261         if (value == null) {
262             return UnDefType.NULL;
263         }
264         BigDecimal bd = new BigDecimal(value.doubleValue());
265         return toQuantityType(bd.setScale(digits, RoundingMode.HALF_UP), unit);
266     }
267
268     public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
269         return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
270     }
271
272     public static State toQuantityType(@Nullable PercentType value, Unit<?> unit) {
273         return value == null ? UnDefType.NULL : toQuantityType(value.toBigDecimal(), unit);
274     }
275
276     public static void validateRange(String name, Integer value, int min, int max) {
277         if ((value < min) || (value > max)) {
278             throw new IllegalArgumentException("Value " + name + " is out of range (" + min + "-" + max + ")");
279         }
280     }
281
282     public static String urlEncode(String input) {
283         try {
284             return URLEncoder.encode(input, StandardCharsets.UTF_8.toString());
285         } catch (UnsupportedEncodingException e) {
286             return input;
287         }
288     }
289
290     public static Long now() {
291         return System.currentTimeMillis() / 1000L;
292     }
293
294     public static DateTimeType getTimestamp() {
295         return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(now()), ZoneId.systemDefault()));
296     }
297
298     public static DateTimeType getTimestamp(String zone, long timestamp) {
299         try {
300             ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault();
301             ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId);
302             int delta = zdt.getOffset().getTotalSeconds();
303             return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp - delta), zoneId));
304         } catch (DateTimeException e) {
305             // Unable to convert device's timezone, use system one
306             return getTimestamp();
307         }
308     }
309
310     public static String getTimestamp(DateTimeType dt) {
311         return dt.getZonedDateTime().toString().replace('T', ' ').replace('-', '/');
312     }
313
314     public static String convertTimestamp(long ts) {
315         if (ts == 0) {
316             return "";
317         }
318         String time = DATE_TIME.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(ts), ZoneId.systemDefault()));
319         return time.replace('T', ' ').replace('-', '/');
320     }
321
322     public static Integer getLightIdFromGroup(String groupName) {
323         if (groupName.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
324             return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_CHANNEL)) - 1;
325         }
326         return 0; // only 1 light, e.g. bulb or rgbw2 in color mode
327     }
328
329     public static String buildControlGroupName(ShellyDeviceProfile profile, Integer channelId) {
330         return !profile.isRGBW2 || profile.inColor ? CHANNEL_GROUP_LIGHT_CONTROL
331                 : CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
332     }
333
334     public static String buildWhiteGroupName(ShellyDeviceProfile profile, Integer channelId) {
335         return profile.isBulb || profile.isDuo ? CHANNEL_GROUP_WHITE_CONTROL
336                 : CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
337     }
338
339     public static DecimalType mapSignalStrength(int dbm) {
340         int strength = -1;
341         if (dbm > -60) {
342             strength = 4;
343         } else if (dbm > -70) {
344             strength = 3;
345         } else if (dbm > -80) {
346             strength = 2;
347         } else if (dbm > -90) {
348             strength = 1;
349         } else {
350             strength = 0;
351         }
352         return new DecimalType(strength);
353     }
354
355     public static boolean isDigit(char c) {
356         return c >= '0' && c <= '9';
357     }
358
359     public static char lastChar(String s) {
360         return s.length() > 1 ? s.charAt(s.length() - 1) : '*';
361     }
362
363     public static String sha256(String string) throws ShellyApiException {
364         try {
365             MessageDigest digest = MessageDigest.getInstance("SHA-256");
366             final byte[] hashbytes = digest.digest(string.getBytes(StandardCharsets.UTF_8));
367             return bytesToHex(hashbytes);
368         } catch (NoSuchAlgorithmException e) {
369             throw new ShellyApiException("SHA256 can't be initialzed", e);
370         }
371     }
372
373     public static String bytesToHex(byte[] bytes) {
374         StringBuilder hexString = new StringBuilder(2 * bytes.length);
375         for (int i = 0; i < bytes.length; i++) {
376             String hex = Integer.toHexString(0xff & bytes[i]);
377             if (hex.length() == 1) {
378                 hexString.append('0');
379             }
380             hexString.append(hex);
381         }
382         return hexString.toString();
383     }
384 }