]> git.basschouten.com Git - openhab-addons.git/blob
796067fb061a3b29e76e8b12b7d16b4b41e82016
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.handler;
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.io.IOException;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.openhab.binding.shelly.internal.api.ShellyApiException;
23 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
24 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
25 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
26 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
27 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.OpenClosedType;
30 import org.openhab.core.library.unit.ImperialUnits;
31 import org.openhab.core.library.unit.SIUnits;
32 import org.openhab.core.library.unit.SmartHomeUnits;
33
34 import tec.uom.se.unit.Units;
35
36 /***
37  * The{@link ShellyComponents} implements updates for supplemental components
38  * Meter will be used by Relay + Light; Sensor is part of H&T, Flood, Door Window, Sense
39  *
40  * @author Markus Michels - Initial contribution
41  */
42 @NonNullByDefault
43 public class ShellyComponents {
44
45     /**
46      * Update device status
47      *
48      * @param th Thing Handler instance
49      * @param profile ShellyDeviceProfile
50      */
51     public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
52         if (!thingHandler.areChannelsCreated()) {
53             thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
54                     .createDeviceChannels(thingHandler.getThing(), thingHandler.getProfile(), status));
55         }
56
57         Integer rssi = getInteger(status.wifiSta.rssi);
58         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
59                 toQuantityType(new Double(getLong(status.uptime)), DIGITS_NONE, SmartHomeUnits.SECOND));
60         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
61         if (status.tmp != null) {
62             thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
63                     toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
64         } else if (status.temperature != null) {
65             thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
66                     toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS));
67         }
68         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
69
70         return false; // device status never triggers update
71     }
72
73     /**
74      * Update Meter channel
75      *
76      * @param th Thing Handler instance
77      * @param profile ShellyDeviceProfile
78      * @param status Last ShellySettingsStatus
79      */
80     public static boolean updateMeters(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
81         ShellyDeviceProfile profile = thingHandler.getProfile();
82
83         double accumulatedWatts = 0.0;
84         double accumulatedTotal = 0.0;
85         double accumulatedReturned = 0.0;
86
87         boolean updated = false;
88         // Devices without power meters get no updates
89         // We need to differ
90         // Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device
91         // Meter and EMeter have a different set of channels
92         if ((profile.numMeters > 0) && ((status.meters != null) || (status.emeters != null))) {
93             if (!profile.isRoller && !profile.isRGBW2) {
94                 thingHandler.logger.trace("{}: Updating {} {}meter(s)", thingHandler.thingName, profile.numMeters,
95                         !profile.isEMeter ? "standard " : "e-");
96
97                 // In Relay mode we map eacher meter to the matching channel group
98                 int m = 0;
99                 if (!profile.isEMeter) {
100                     for (ShellySettingsMeter meter : status.meters) {
101                         Integer meterIndex = m + 1;
102                         if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag
103                                                                          // correctly in white mode
104                             String groupName = "";
105                             if (profile.numMeters > 1) {
106                                 groupName = CHANNEL_GROUP_METER + meterIndex.toString();
107                             } else {
108                                 groupName = CHANNEL_GROUP_METER;
109                             }
110
111                             if (!thingHandler.areChannelsCreated()) {
112                                 // skip for Shelly Bulb: JSON has a meter, but values don't get updated
113                                 if (!profile.isBulb) {
114                                     thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
115                                             .createMeterChannels(thingHandler.getThing(), meter, groupName));
116                                 }
117                             }
118
119                             updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
120                                     toQuantityType(getDouble(meter.power), DIGITS_WATT, SmartHomeUnits.WATT));
121                             accumulatedWatts += getDouble(meter.power);
122
123                             // convert Watt/Min to kw/h
124                             if (meter.total != null) {
125                                 double kwh = getDouble(meter.total) / 60 / 1000;
126                                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
127                                         toQuantityType(kwh, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
128                                 accumulatedTotal += kwh;
129                             }
130                             if (meter.counters != null) {
131                                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
132                                         toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, SmartHomeUnits.WATT));
133                             }
134                             thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
135                                     getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp)));
136                         }
137                         m++;
138                     }
139                 } else {
140                     for (ShellySettingsEMeter emeter : status.emeters) {
141                         Integer meterIndex = m + 1;
142                         if (getBool(emeter.isValid)) {
143                             String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString()
144                                     : CHANNEL_GROUP_METER;
145                             if (!thingHandler.areChannelsCreated()) {
146                                 thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
147                                         .createEMeterChannels(thingHandler.getThing(), emeter, groupName));
148                             }
149
150                             // convert Watt/Hour tok w/h
151                             updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
152                                     toQuantityType(getDouble(emeter.power), DIGITS_WATT, SmartHomeUnits.WATT));
153                             updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH, toQuantityType(
154                                     getDouble(emeter.total) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
155                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_TOTALRET, toQuantityType(
156                                     getDouble(emeter.totalReturned) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
157                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_REACTWATTS,
158                                     toQuantityType(getDouble(emeter.reactive), DIGITS_WATT, SmartHomeUnits.WATT));
159                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_VOLTAGE,
160                                     toQuantityType(getDouble(emeter.voltage), DIGITS_VOLT, SmartHomeUnits.VOLT));
161
162                             if (emeter.current != null) {
163                                 // Shelly
164                                 updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_CURRENT,
165                                         toQuantityType(getDouble(emeter.current), DIGITS_VOLT, SmartHomeUnits.AMPERE));
166                                 updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_PFACTOR,
167                                         getDecimal(emeter.pf));
168                             }
169
170                             accumulatedWatts += getDouble(emeter.power);
171                             accumulatedTotal += getDouble(emeter.total) / 1000;
172                             accumulatedReturned += getDouble(emeter.totalReturned) / 1000;
173                             if (updated) {
174                                 thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, getTimestamp());
175                             }
176                         }
177                         m++;
178                     }
179                 }
180             } else {
181                 // In Roller Mode we accumulate all meters to a single set of meters
182                 thingHandler.logger.trace("{}: Updating Meter (accumulated)", thingHandler.thingName);
183                 double currentWatts = 0.0;
184                 double totalWatts = 0.0;
185                 double lastMin1 = 0.0;
186                 long timestamp = 0l;
187                 String groupName = CHANNEL_GROUP_METER;
188                 for (ShellySettingsMeter meter : status.meters) {
189                     if (meter.isValid) {
190                         currentWatts += getDouble(meter.power);
191                         totalWatts += getDouble(meter.total);
192                         if (meter.counters != null) {
193                             lastMin1 += getDouble(meter.counters[0]);
194                         }
195                         if (getLong(meter.timestamp) > timestamp) {
196                             timestamp = getLong(meter.timestamp); // newest one
197                         }
198                     }
199                 }
200                 // Create channels for 1 Meter
201                 if (!thingHandler.areChannelsCreated()) {
202                     thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
203                             .createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
204                 }
205
206                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
207                         toQuantityType(getDouble(lastMin1), DIGITS_WATT, SmartHomeUnits.WATT));
208
209                 // convert totalWatts into kw/h
210                 totalWatts = totalWatts / (60.0 * 10000.0);
211                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
212                         toQuantityType(getDouble(currentWatts), DIGITS_WATT, SmartHomeUnits.WATT));
213                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
214                         toQuantityType(getDouble(totalWatts), DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
215
216                 if (updated) {
217                     thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
218                             getTimestamp(getString(profile.settings.timezone), timestamp));
219                 }
220             }
221
222             if (updated && !profile.isRoller && !profile.isRGBW2) {
223                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS,
224                         toQuantityType(accumulatedWatts, DIGITS_WATT, SmartHomeUnits.WATT));
225                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUTOTAL,
226                         toQuantityType(accumulatedTotal, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
227                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCURETURNED,
228                         toQuantityType(accumulatedReturned, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
229             }
230         }
231
232         return updated;
233     }
234
235     /**
236      * Update Sensor channel
237      *
238      * @param th Thing Handler instance
239      * @param profile ShellyDeviceProfile
240      * @param status Last ShellySettingsStatus
241      *
242      * @throws IOException
243      */
244     public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status)
245             throws ShellyApiException {
246         ShellyDeviceProfile profile = thingHandler.getProfile();
247
248         boolean updated = false;
249         if (profile.isSensor || profile.hasBattery || profile.isSense) {
250             ShellyStatusSensor sdata = thingHandler.api.getSensorStatus();
251
252             if (!thingHandler.areChannelsCreated()) {
253                 thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
254                 thingHandler.updateChannelDefinitions(
255                         ShellyChannelDefinitionsDTO.createSensorChannels(thingHandler.getThing(), profile, sdata));
256             }
257
258             updated |= thingHandler.updateWakeupReason(sdata.actReasons);
259
260             if ((sdata.contact != null) && sdata.contact.isValid) {
261                 // Shelly DW: “sensor”:{“state”:“open”, “is_valid”:true},
262                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
263                         getString(sdata.contact.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
264                                 : OpenClosedType.CLOSED);
265                 boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
266                         getStringType(sdata.sensorError));
267                 if (changed) {
268                     thingHandler.postEvent(sdata.sensorError, true);
269                 }
270                 updated |= changed;
271             }
272             if ((sdata.tmp != null) && getBool(sdata.tmp.isValid)) {
273                 thingHandler.logger.trace("{}: Updating temperature", thingHandler.thingName);
274                 Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS)
275                         ? getDouble(sdata.tmp.tC)
276                         : getDouble(sdata.tmp.tF);
277                 if (getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_FAHRENHEIT)) {
278                     // convert Fahrenheit to Celsius
279                     temp = ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(temp).doubleValue();
280                 }
281                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
282                         toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS));
283             }
284             if (sdata.hum != null) {
285                 thingHandler.logger.trace("{}: Updating humidity", thingHandler.thingName);
286                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
287                         toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
288             }
289             if ((sdata.lux != null) && getBool(sdata.lux.isValid)) {
290                 // “lux”:{“value”:30, “illumination”: “dark”, “is_valid”:true},
291                 thingHandler.logger.trace("{}: Updating lux", thingHandler.thingName);
292                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
293                         toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, SmartHomeUnits.LUX));
294                 if (sdata.lux.illumination != null) {
295                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM,
296                             getStringType(sdata.lux.illumination));
297                 }
298             }
299             if (sdata.accel != null) {
300                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT, toQuantityType(
301                         getDouble(sdata.accel.tilt.doubleValue()), DIGITS_NONE, SmartHomeUnits.DEGREE_ANGLE));
302                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
303                         getInteger(sdata.accel.vibration) == 1 ? OnOffType.ON : OnOffType.OFF);
304             }
305             if (sdata.flood != null) {
306                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
307                         getOnOff(sdata.flood));
308             }
309             if (sdata.smoke != null) {
310                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SMOKE,
311                         getOnOff(sdata.smoke));
312             }
313             if (sdata.gasSensor != null) {
314                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST,
315                         getStringType(sdata.gasSensor.selfTestState));
316                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE,
317                         getStringType(sdata.gasSensor.alarmState));
318                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE,
319                         getStringType(sdata.gasSensor.sensorState));
320             }
321             if ((sdata.concentration != null) && sdata.concentration.isValid) {
322                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM,
323                         getDecimal(sdata.concentration.ppm));
324             }
325
326             if (sdata.bat != null) { // no update for Sense
327                 thingHandler.logger.trace("{}: Updating battery", thingHandler.thingName);
328                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
329                         toQuantityType(getDouble(sdata.bat.value), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
330                 boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW,
331                         getDouble(sdata.bat.value) < thingHandler.config.lowBattery ? OnOffType.ON : OnOffType.OFF);
332                 updated |= changed;
333                 if (changed && getDouble(sdata.bat.value) < thingHandler.config.lowBattery) {
334                     thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
335                 }
336             }
337             if (sdata.motion != null) {
338                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
339                         getOnOff(sdata.motion));
340             }
341             if (sdata.charger != null) {
342                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
343                         getOnOff(sdata.charger));
344             }
345
346             updated |= thingHandler.updateInputs(status);
347
348             if (updated) {
349                 thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
350             }
351         }
352         return updated;
353     }
354 }