]> git.basschouten.com Git - openhab-addons.git/blob
127c72a9048972ab687f9ae5cd510beaec4a1820
[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 import java.util.Objects;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
25 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
26 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput;
27 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
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 import com.google.gson.JsonSyntaxException;
34
35 /**
36  * The {@link ShellyDeviceProfile} creates a device profile based on the settings returned from the API's /settings
37  * call. This is used to be more dynamic in controlling the device, but also to overcome some issues in the API (e.g.
38  * RGBW2 returns "no meter" even it has one)
39  *
40  * @author Markus Michels - Initial contribution
41  */
42 @NonNullByDefault
43 public class ShellyDeviceProfile {
44     private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class);
45
46     public boolean initialized = false; // true when initialized
47
48     public String thingName = "";
49     public String deviceType = "";
50
51     public String settingsJson = "";
52     public ShellySettingsGlobal settings = new ShellySettingsGlobal();
53     public ShellySettingsStatus status = new ShellySettingsStatus();
54
55     public String hostname = "";
56     public String mode = "";
57     public boolean discoverable = true;
58
59     public String hwRev = "";
60     public String hwBatchId = "";
61     public String mac = "";
62     public String fwId = "";
63     public String fwVersion = "";
64     public String fwDate = "";
65
66     public boolean hasRelays = false; // true if it has at least 1 power meter
67     public int numRelays = 0; // number of relays/outputs
68     public int numRollers = 0; // number of Rollers, usually 1
69     public boolean isRoller = false; // true for Shelly2 in roller mode
70     public boolean isDimmer = false; // true for a Shelly Dimmer (SHDM-1)
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         try {
105             initFromThingType(thingType);
106             settingsJson = json;
107             settings = Objects.requireNonNull(gson.fromJson(json, ShellySettingsGlobal.class));
108         } catch (IllegalArgumentException | JsonSyntaxException e) {
109             throw new ShellyApiException(
110                     thingName + ": Unable to transform settings JSON " + e.toString() + ", json='" + json + "'", e);
111         }
112
113         // General settings
114         deviceType = getString(settings.device.type);
115         mac = getString(settings.device.mac);
116         hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty()
117                 ? settings.device.hostname.toLowerCase()
118                 : "shelly-" + mac.toUpperCase().substring(6, 11);
119         mode = !getString(settings.mode).isEmpty() ? getString(settings.mode).toLowerCase() : "";
120         hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : "";
121         hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : "";
122         fwDate = substringBefore(settings.fw, "/");
123         fwVersion = substringBetween(settings.fw, "/", "@");
124         fwId = substringAfter(settings.fw, "@");
125         discoverable = (settings.discoverable == null) || settings.discoverable;
126
127         inColor = isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
128
129         numRelays = !isLight ? getInteger(settings.device.numOutputs) : 0;
130         if ((numRelays > 0) && (settings.relays == null)) {
131             numRelays = 0;
132         }
133         isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
134         hasRelays = (numRelays > 0) || isDimmer;
135         numRollers = getInteger(settings.device.numRollers);
136
137         isEMeter = settings.emeters != null;
138         numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters);
139         if ((numMeters == 0) && isLight) {
140             // RGBW2 doesn't report, but has one
141             numMeters = inColor ? 1 : getInteger(settings.device.numOutputs);
142         }
143         isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
144
145         if (settings.sleepMode != null) {
146             // Sensor, usally 12h
147             updatePeriod = getString(settings.sleepMode.unit).equalsIgnoreCase("m") ? settings.sleepMode.period * 60 // minutes
148                     : settings.sleepMode.period * 3600; // hours
149             updatePeriod += 600; // give 10min extra
150         } else if ((settings.coiot != null) && (settings.coiot.updatePeriod != null)) {
151             // Derive from CoAP update interval, usually 2*15+5s=50sec -> 70sec
152             updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 3 * getInteger(settings.coiot.updatePeriod)) + 10;
153         } else {
154             updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
155         }
156
157         initialized = true;
158         return this;
159     }
160
161     public boolean containsEventUrl(String eventType) {
162         return containsEventUrl(settingsJson, eventType);
163     }
164
165     public boolean containsEventUrl(String json, String eventType) {
166         String settings = json.toLowerCase();
167         return settings.contains((eventType + SHELLY_EVENTURL_SUFFIX).toLowerCase());
168     }
169
170     public boolean isInitialized() {
171         return initialized;
172     }
173
174     public void initFromThingType(String name) {
175         String thingType = (name.contains("-") ? substringBefore(name, "-") : name).toLowerCase().trim();
176         if (thingType.isEmpty()) {
177             return;
178         }
179
180         isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
181         isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR);
182         isRGBW2 = thingType.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX);
183         isLight = isBulb || isDuo || isRGBW2;
184         if (isLight) {
185             minTemp = isBulb ? MIN_COLOR_TEMP_BULB : MIN_COLOR_TEMP_DUO;
186             maxTemp = isBulb ? MAX_COLOR_TEMP_BULB : MAX_COLOR_TEMP_DUO;
187         }
188
189         boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
190         boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
191         boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_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 || isSense;
198         hasBattery = isHT || isFlood || isDW || isSmoke || isButton; // we assume that Sense is connected to // the
199                                                                      // charger
200     }
201
202     public String getControlGroup(int i) {
203         if (i < 0) {
204             logger.debug("{}: Invalid index {} for getControlGroup()", thingName, i);
205             return "";
206         }
207         int idx = i + 1;
208         if (isDimmer) {
209             return CHANNEL_GROUP_DIMMER_CONTROL;
210         } else if (isRoller) {
211             return numRollers == 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
212         } else if (hasRelays) {
213             return numRelays == 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
214         } else if (isLight) {
215             return numRelays == 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
216         } else if (isButton) {
217             return CHANNEL_GROUP_STATUS;
218         } else if (isSensor) {
219             return CHANNEL_GROUP_SENSOR;
220         }
221
222         // e.g. ix3
223         return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx;
224     }
225
226     public String getInputGroup(int i) {
227         int idx = i + 1; // group names are 1-based
228         if (isRGBW2) {
229             return CHANNEL_GROUP_LIGHT_CONTROL;
230         } else if (isIX3) {
231             return CHANNEL_GROUP_STATUS + idx;
232         } else if (isButton) {
233             return CHANNEL_GROUP_STATUS;
234         } else if (isRoller) {
235             return numRelays <= 2 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
236         } else {
237             // Device has 1 input per relay: 0=off, 1+2 depend on switch mode
238             return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
239         }
240     }
241
242     public String getInputChannel(int i) {
243         int idx = i + 1; // channel names are 1-based
244         if (isRGBW2 || isIX3) {
245             return CHANNEL_INPUT; // RGBW2 has only 1 channel
246         } else if (hasRelays) {
247             return CHANNEL_INPUT + idx;
248         }
249         return CHANNEL_INPUT;
250     }
251
252     public boolean inButtonMode(int idx) {
253         if (idx < 0) {
254             logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
255             return false;
256         }
257         String btnType = "";
258         if (isButton) {
259             return true;
260         } else if (isIX3) {
261             if ((settings.inputs != null) && (idx >= 0) && (idx < settings.inputs.size())) {
262                 ShellySettingsInput input = settings.inputs.get(idx);
263                 btnType = input.btnType;
264             }
265         } else if (isDimmer) {
266             if ((settings.dimmers != null) && (idx >= 0) && (idx < settings.dimmers.size())) {
267                 ShellySettingsDimmer dimmer = settings.dimmers.get(idx);
268                 btnType = dimmer.btnType;
269             }
270         } else if ((settings.relays != null) && (idx >= 0) && (idx < settings.relays.size())) {
271             ShellySettingsRelay relay = settings.relays.get(idx);
272             btnType = relay.btnType;
273         }
274
275         if (btnType.equals(SHELLY_BTNT_MOMENTARY) || btnType.equals(SHELLY_BTNT_MOM_ON_RELEASE)
276                 || btnType.equals(SHELLY_BTNT_DETACHED) || btnType.equals(SHELLY_BTNT_ONE_BUTTON)) {
277             return true;
278         }
279         return false;
280     }
281 }