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