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