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