]> git.basschouten.com Git - openhab-addons.git/blob
00cdb38fa12f9ccd4b1568578206b4fcb55d7e45
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.api;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
17 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
18
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
24 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
25 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput;
26 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
27 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
28 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import com.google.gson.Gson;
33
34 /**
35  * The {@link ShellyDeviceProfile} creates a device profile based on the settings returned from the API's /settings
36  * call. This is used to be more dynamic in controlling the device, but also to overcome some issues in the API (e.g.
37  * RGBW2 returns "no meter" even it has one)
38  *
39  * @author Markus Michels - Initial contribution
40  */
41 @NonNullByDefault
42 public class ShellyDeviceProfile {
43     private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class);
44
45     public boolean initialized = false; // true when initialized
46
47     public String thingName = "";
48     public String deviceType = "";
49
50     public String settingsJson = "";
51     public ShellySettingsGlobal settings = new ShellySettingsGlobal();
52     public ShellySettingsStatus status = new ShellySettingsStatus();
53
54     public String hostname = "";
55     public String mode = "";
56     public boolean discoverable = true;
57
58     public String hwRev = "";
59     public String hwBatchId = "";
60     public String mac = "";
61     public String fwId = "";
62     public String fwVersion = "";
63     public String fwDate = "";
64
65     public boolean hasRelays = false; // true if it has at least 1 power meter
66     public int numRelays = 0; // number of relays/outputs
67     public int numRollers = 0; // number of Rollers, usually 1
68     public boolean isRoller = false; // true for Shelly2 in roller mode
69     public boolean isDimmer = false; // true for a Shelly Dimmer (SHDM-1)
70     public int numInputs = 0; // number of inputs
71
72     public int numMeters = 0;
73     public boolean isEMeter = false; // true for ShellyEM/3EM
74
75     public boolean isLight = false; // true if it is a Shelly Bulb/RGBW2
76     public boolean isBulb = false; // true only if it is a Bulb
77     public boolean isDuo = false; // true only if it is a Duo
78     public boolean isRGBW2 = false; // true only if it a a RGBW2
79     public boolean inColor = false; // true if bulb/rgbw2 is in color mode
80
81     public boolean isSensor = false; // true for HT & Smoke
82     public boolean hasBattery = false; // true if battery device
83     public boolean isSense = false; // true if thing is a Shelly Sense
84     public boolean isHT = false; // true for H&T
85     public boolean isDW = false; // true for Door Window sensor
86     public boolean isButton = false; // true for a Shelly Button 1
87     public boolean isIX3 = false; // true for a Shelly IX
88
89     public int minTemp = 0; // Bulb/Duo: Min Light Temp
90     public int maxTemp = 0; // Bulb/Duo: Max Light Temp
91
92     public int updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
93
94     public Map<String, String> irCodes = new HashMap<>(); // Sense: list of stored IR codes
95
96     public ShellyDeviceProfile() {
97     }
98
99     public ShellyDeviceProfile initialize(String thingType, String json) throws ShellyApiException {
100         Gson gson = new Gson();
101
102         initialized = false;
103
104         initFromThingType(thingType);
105         settingsJson = json;
106         ShellySettingsGlobal gs = fromJson(gson, json, ShellySettingsGlobal.class);
107         settings = gs; // only update when no exception
108
109         // General settings
110         deviceType = getString(settings.device.type);
111         mac = getString(settings.device.mac);
112         hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty()
113                 ? settings.device.hostname.toLowerCase()
114                 : "shelly-" + mac.toUpperCase().substring(6, 11);
115         mode = !getString(settings.mode).isEmpty() ? getString(settings.mode).toLowerCase() : "";
116         hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : "";
117         hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : "";
118         fwDate = substringBefore(settings.fw, "/");
119         fwVersion = substringBetween(settings.fw, "/", "@");
120         fwId = substringAfter(settings.fw, "@");
121         discoverable = (settings.discoverable == null) || settings.discoverable;
122
123         inColor = isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
124
125         numRelays = !isLight ? getInteger(settings.device.numOutputs) : 0;
126         if ((numRelays > 0) && (settings.relays == null)) {
127             numRelays = 0;
128         }
129         isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
130         isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
131         hasRelays = (numRelays > 0) || isDimmer;
132         numRollers = getInteger(settings.device.numRollers);
133         numInputs = settings.inputs != null ? settings.inputs.size() : hasRelays ? isRoller ? 2 : 1 : 0;
134
135         isEMeter = settings.emeters != null;
136         numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters);
137         if ((numMeters == 0) && isLight) {
138             // RGBW2 doesn't report, but has one
139             numMeters = inColor ? 1 : getInteger(settings.device.numOutputs);
140         }
141
142         if (settings.sleepMode != null) {
143             // Sensor, usually 12h, H&T in USB mode 10min
144             updatePeriod = getString(settings.sleepMode.unit).equalsIgnoreCase("m") ? settings.sleepMode.period * 60 // minutes
145                     : settings.sleepMode.period * 3600; // hours
146             updatePeriod += 60; // give 1min extra
147         } else if ((settings.coiot != null) && (settings.coiot.updatePeriod != null)) {
148             // Derive from CoAP update interval, usually 2*15+10s=40sec -> 70sec
149             updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 2 * getInteger(settings.coiot.updatePeriod)) + 10;
150         } else {
151             updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
152         }
153
154         initialized = true;
155         return this;
156     }
157
158     public boolean containsEventUrl(String eventType) {
159         return containsEventUrl(settingsJson, eventType);
160     }
161
162     public boolean containsEventUrl(String json, String eventType) {
163         String settings = json.toLowerCase();
164         return settings.contains((eventType + SHELLY_EVENTURL_SUFFIX).toLowerCase());
165     }
166
167     public boolean isInitialized() {
168         return initialized;
169     }
170
171     public void initFromThingType(String name) {
172         String thingType = (name.contains("-") ? substringBefore(name, "-") : name).toLowerCase().trim();
173         if (thingType.isEmpty()) {
174             return;
175         }
176
177         isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
178         isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)
179                 || thingType.equals(THING_TYPE_SHELLYDUORGBW_STR);
180         isRGBW2 = thingType.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX);
181         isLight = isBulb || isDuo || isRGBW2;
182         if (isLight) {
183             minTemp = isBulb ? MIN_COLOR_TEMP_BULB : MIN_COLOR_TEMP_DUO;
184             maxTemp = isBulb ? MAX_COLOR_TEMP_BULB : MAX_COLOR_TEMP_DUO;
185         }
186
187         boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
188         boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
189         boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
190         boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR);
191         boolean isMotion = thingType.equals(THING_TYPE_SHELLYMOTION_STR);
192         isHT = thingType.equals(THING_TYPE_SHELLYHT_STR);
193         isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR);
194         isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR);
195         isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR);
196         isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR);
197         isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isSense;
198         hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion; // we assume that Sense is connected to
199                                                                                  // the charger
200     }
201
202     public void updateFromStatus(ShellySettingsStatus status) {
203         if (hasRelays) {
204             // Dimmer-2 doesn't report inputs under /settings, only on /status, we need to update that info after
205             // initialization
206             if (status.inputs != null) {
207                 numInputs = status.inputs.size();
208             }
209         } else if (status.input != null) {
210             // RGBW2
211             numInputs = 1;
212         }
213     }
214
215     public String getControlGroup(int i) {
216         if (i < 0) {
217             logger.debug("{}: Invalid index {} for getControlGroup()", thingName, i);
218             return "";
219         }
220         int idx = i + 1;
221         if (isDimmer) {
222             return CHANNEL_GROUP_DIMMER_CONTROL;
223         } else if (isRoller) {
224             return numRollers <= 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
225         } else if (isDimmer) {
226             return CHANNEL_GROUP_RELAY_CONTROL;
227         } else if (hasRelays) {
228             return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
229         } else if (isLight) {
230             return numRelays <= 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
231         } else if (isButton) {
232             return CHANNEL_GROUP_STATUS;
233         } else if (isSensor) {
234             return CHANNEL_GROUP_SENSOR;
235         }
236
237         // e.g. ix3
238         return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx;
239     }
240
241     public String getInputGroup(int i) {
242         int idx = i + 1; // group names are 1-based
243         if (isRGBW2) {
244             return CHANNEL_GROUP_LIGHT_CONTROL;
245         } else if (isIX3) {
246             return CHANNEL_GROUP_STATUS + idx;
247         } else if (isButton) {
248             return CHANNEL_GROUP_STATUS;
249         } else if (isRoller) {
250             return numRelays <= 2 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
251         } else {
252             // Device has 1 input per relay: 0=off, 1+2 depend on switch mode
253             return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
254         }
255     }
256
257     public String getInputSuffix(int i) {
258         int idx = i + 1; // channel names are 1-based
259         if (isRGBW2 || isIX3) {
260             return ""; // RGBW2 has only 1 channel
261         } else if (isRoller || isDimmer) {
262             // Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs
263             return String.valueOf(idx);
264         } else if (hasRelays) {
265             return (numRelays) == 1 && (numInputs >= 2) ? String.valueOf(idx) : "";
266         }
267         return "";
268     }
269
270     public boolean inButtonMode(int idx) {
271         if (idx < 0) {
272             logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
273             return false;
274         }
275         String btnType = "";
276         if (isButton) {
277             return true;
278         } else if (isIX3 && (settings.inputs != null) && (idx < settings.inputs.size())) {
279             ShellySettingsInput input = settings.inputs.get(idx);
280             btnType = getString(input.btnType);
281         } else if (isDimmer) {
282             if (settings.dimmers != null) {
283                 ShellySettingsDimmer dimmer = settings.dimmers.get(0);
284                 btnType = dimmer.btnType;
285             }
286         } else if (settings.relays != null) {
287             if (numRelays == 1) {
288                 ShellySettingsRelay relay = settings.relays.get(0);
289                 if (relay.btnType != null) {
290                     btnType = getString(relay.btnType);
291                 } else {
292                     // Shelly 1L has 2 inputs
293                     btnType = idx == 0 ? getString(relay.btnType1) : getString(relay.btnType2);
294                 }
295             } else if (idx < settings.relays.size()) {
296                 // only one input channel
297                 ShellySettingsRelay relay = settings.relays.get(idx);
298                 btnType = getString(relay.btnType);
299             }
300         } else if (isRGBW2 && (settings.lights != null) && (idx < settings.lights.size())) {
301             ShellySettingsRgbwLight light = settings.lights.get(idx);
302             btnType = light.btnType;
303         }
304
305         logger.trace("{}: Checking for trigger, button-type[{}] is {}", thingName, idx, btnType);
306         return btnType.equalsIgnoreCase(SHELLY_BTNT_MOMENTARY) || btnType.equalsIgnoreCase(SHELLY_BTNT_MOM_ON_RELEASE)
307                 || btnType.equalsIgnoreCase(SHELLY_BTNT_ONE_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_TWO_BUTTON);
308     }
309
310     public int getRollerFav(int id) {
311         if ((id >= 0) && getBool(settings.favoritesEnabled) && (settings.favorites != null)
312                 && (id < settings.favorites.size())) {
313             return settings.favorites.get(id).pos;
314         }
315         return -1;
316     }
317 }