]> git.basschouten.com Git - openhab-addons.git/blob
7846afde876a33c60103e10c5e1a92b083ef4a1f
[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.handler;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
17 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.shelly.internal.api.ShellyApiException;
22 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
23 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
24 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
25 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
26 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
27 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
28 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
29 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyADC;
30 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature.ShellyShortTemp;
31 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyThermnostat;
32 import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.library.unit.ImperialUnits;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.library.unit.Units;
37 import org.openhab.core.types.UnDefType;
38
39 /***
40  * The{@link ShellyComponents} implements updates for supplemental components
41  * Meter will be used by Relay + Light; Sensor is part of H&T, Flood, Door Window, Sense
42  *
43  * @author Markus Michels - Initial contribution
44  */
45 @NonNullByDefault
46 public class ShellyComponents {
47
48     /**
49      * Update device status
50      *
51      * @param th Thing Handler instance
52      * @param profile ShellyDeviceProfile
53      */
54     public static boolean updateDeviceStatus(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
55         ShellyDeviceProfile profile = thingHandler.getProfile();
56
57         if (!thingHandler.areChannelsCreated()) {
58             thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(),
59                     thingHandler.getProfile(), status));
60         }
61
62         if (getLong(status.uptime) > 10) {
63             thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
64                     toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND));
65         }
66
67         Integer rssi = getInteger(status.wifiSta.rssi);
68         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
69         if (getDouble(status.temperature) != SHELLY_API_INVTEMP) {
70             if (status.tmp != null && !thingHandler.getProfile().isSensor) {
71                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
72                         toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
73             } else if (status.temperature != null) {
74                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
75                         toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS));
76             }
77         }
78         thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SLEEPTIME,
79                 toQuantityType(getInteger(status.sleepTime), Units.SECOND));
80
81         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
82
83         if (profile.settings.calibrated != null) {
84             thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CALIBRATED,
85                     getOnOff(profile.settings.calibrated));
86         }
87
88         return false; // device status never triggers update
89     }
90
91     public static boolean updateRelay(ShellyBaseHandler thingHandler, ShellySettingsStatus status, int id) {
92         ShellyDeviceProfile profile = thingHandler.getProfile();
93         ShellySettingsRelay relay = status.relays.get(id);
94         ShellySettingsRelay rsettings;
95         if (profile.settings.relays != null) {
96             rsettings = profile.settings.relays.get(id);
97         } else {
98             throw new IllegalArgumentException("No relay settings");
99         }
100
101         boolean updated = false;
102         if (relay.isValid == null || relay.isValid) {
103             String groupName = profile.getControlGroup(id);
104             updated |= thingHandler.updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(rsettings.name));
105
106             if (getBool(relay.overpower)) {
107                 thingHandler.postEvent(ALARM_TYPE_OVERPOWER, false);
108             }
109
110             updated |= thingHandler.updateChannel(groupName, CHANNEL_OUTPUT, getOnOff(relay.ison));
111             updated |= thingHandler.updateChannel(groupName, CHANNEL_TIMER_ACTIVE, getOnOff(relay.hasTimer));
112             if (status.extSwitch != null) {
113                 if (status.extSwitch.input0 != null) {
114                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENSOR_INPUT1,
115                             getOpenClosed(getInteger(status.extSwitch.input0.input) == 1));
116                 }
117             }
118             if (status.extTemperature != null) {
119                 // Shelly 1/1PM support up to 3 external sensors
120                 // for whatever reason those are not represented as an array, but 3 elements
121                 updated |= updateTempChannel(status.extTemperature.sensor1, thingHandler, CHANNEL_ESENSOR_TEMP1);
122                 updated |= updateTempChannel(status.extTemperature.sensor2, thingHandler, CHANNEL_ESENSOR_TEMP2);
123                 updated |= updateTempChannel(status.extTemperature.sensor3, thingHandler, CHANNEL_ESENSOR_TEMP3);
124                 updated |= updateTempChannel(status.extTemperature.sensor4, thingHandler, CHANNEL_ESENSOR_TEMP4);
125                 updated |= updateTempChannel(status.extTemperature.sensor5, thingHandler, CHANNEL_ESENSOR_TEMP5);
126             }
127             if ((status.extHumidity != null) && (status.extHumidity.sensor1 != null)) {
128                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENSOR_HUMIDITY,
129                         toQuantityType(getDouble(status.extHumidity.sensor1.hum), DIGITS_PERCENT, Units.PERCENT));
130             }
131             if ((status.extVoltage != null) && (status.extVoltage.sensor1 != null)) {
132                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENSOR_VOLTAGE,
133                         toQuantityType(getDouble(status.extVoltage.sensor1.voltage), 4, Units.VOLT));
134             }
135             if ((status.extDigitalInput != null) && (status.extDigitalInput.sensor1 != null)) {
136                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENSOR_DIGITALINPUT,
137                         getOnOff(status.extDigitalInput.sensor1.state));
138             }
139             if ((status.extAnalogInput != null) && (status.extAnalogInput.sensor1 != null)) {
140                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENSOR_ANALOGINPUT, toQuantityType(
141                         getDouble(status.extAnalogInput.sensor1.percent), DIGITS_PERCENT, Units.PERCENT));
142             }
143
144             // Update Auto-ON/OFF timer
145             updated |= thingHandler.updateChannel(groupName, CHANNEL_TIMER_AUTOON,
146                     toQuantityType(getDouble(rsettings.autoOn), Units.SECOND));
147             updated |= thingHandler.updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
148                     toQuantityType(getDouble(rsettings.autoOff), Units.SECOND));
149         }
150         return updated;
151     }
152
153     public static boolean updateRoller(ShellyBaseHandler thingHandler, ShellyRollerStatus control, int id)
154             throws ShellyApiException {
155         ShellyDeviceProfile profile = thingHandler.getProfile();
156         boolean updated = false;
157         if (getBool(control.isValid)) {
158             String groupName = profile.getControlGroup(id);
159             if (control.name != null) {
160                 updated |= thingHandler.updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(control.name));
161             }
162
163             String state = getString(control.state);
164             int pos = -1;
165             switch (state) {
166                 case SHELLY_ALWD_ROLLER_TURN_OPEN:
167                     pos = SHELLY_MAX_ROLLER_POS;
168                     break;
169                 case SHELLY_ALWD_ROLLER_TURN_CLOSE:
170                     pos = SHELLY_MIN_ROLLER_POS;
171                     break;
172                 case SHELLY_ALWD_ROLLER_TURN_STOP:
173                     if (control.currentPos != null) {
174                         // only valid in stop state
175                         pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(control.currentPos, SHELLY_MAX_ROLLER_POS));
176                     }
177                     break;
178             }
179             if (pos != -1) {
180                 updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
181                         toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
182                 updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
183                         toQuantityType((double) pos, Units.PERCENT));
184             }
185
186             updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_STATE, new StringType(state));
187             updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_STOPR,
188                     getStringType(control.stopReason));
189             updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_SAFETY,
190                     getOnOff(control.safetySwitch));
191         }
192         return updated;
193     }
194
195     /**
196      * Update Meter channel
197      *
198      * @param th Thing Handler instance
199      * @param profile ShellyDeviceProfile
200      * @param status Last ShellySettingsStatus
201      */
202     public static boolean updateMeters(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
203         ShellyDeviceProfile profile = thingHandler.getProfile();
204
205         double accumulatedWatts = 0.0;
206         double accumulatedTotal = 0.0;
207         double accumulatedReturned = 0.0;
208
209         boolean updated = false;
210         // Devices without power meters get no updates
211         // We need to differ
212         // Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device
213         // Meter and EMeter have a different set of channels
214         if (status.meters != null || status.emeters != null) {
215             if (!profile.isRoller && !profile.isRGBW2) {
216                 // In Relay mode we map eacher meter to the matching channel group
217                 int m = 0;
218                 if (!profile.isEMeter) {
219                     for (ShellySettingsMeter meter : status.meters) {
220                         if (m >= profile.numMeters) {
221                             // Shelly1: reports status.meters[0].is_valid = true, but even doesn't have a meter
222                             meter.isValid = false;
223                         }
224                         if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag
225                             // correctly in white mode
226                             String groupName = profile.getMeterGroup(m);
227                             if (!thingHandler.areChannelsCreated()) {
228                                 // skip for Shelly Bulb: JSON has a meter, but values don't get updated
229                                 if (!profile.isBulb) {
230                                     thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
231                                             .createMeterChannels(thingHandler.getThing(), meter, groupName));
232                                 }
233                             }
234
235                             updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
236                                     toQuantityType(getDouble(meter.power), DIGITS_WATT, Units.WATT));
237                             accumulatedWatts += getDouble(meter.power);
238
239                             // convert Watt/Min to kw/h
240                             if (meter.total != null) {
241                                 double kwh = getDouble(meter.total) / 60 / 1000;
242                                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
243                                         toQuantityType(kwh, DIGITS_KWH, Units.KILOWATT_HOUR));
244                                 accumulatedTotal += kwh;
245                             }
246                             if (meter.counters != null) {
247                                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
248                                         toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, Units.WATT));
249                             }
250                             if (meter.timestamp != null) {
251                                 thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
252                                         getTimestamp(getString(profile.settings.timezone), meter.timestamp));
253                             }
254                         }
255                         m++;
256                     }
257                 } else {
258                     for (ShellySettingsEMeter emeter : status.emeters) {
259                         if (getBool(emeter.isValid)) {
260                             String groupName = profile.getMeterGroup(m);
261                             if (!thingHandler.areChannelsCreated()) {
262                                 thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
263                                         .createEMeterChannels(thingHandler.getThing(), profile, emeter, groupName));
264                             }
265
266                             // convert Watt/Hour tok w/h
267                             updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
268                                     toQuantityType(getDouble(emeter.power), DIGITS_WATT, Units.WATT));
269                             updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
270                                     toQuantityType(getDouble(emeter.total) / 1000, DIGITS_KWH, Units.KILOWATT_HOUR));
271                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_TOTALRET, toQuantityType(
272                                     getDouble(emeter.totalReturned) / 1000, DIGITS_KWH, Units.KILOWATT_HOUR));
273                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_REACTWATTS,
274                                     toQuantityType(getDouble(emeter.reactive), DIGITS_WATT, Units.WATT));
275                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_VOLTAGE,
276                                     toQuantityType(getDouble(emeter.voltage), DIGITS_VOLT, Units.VOLT));
277                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_CURRENT,
278                                     toQuantityType(getDouble(emeter.current), DIGITS_AMPERE, Units.AMPERE));
279                             updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_PFACTOR,
280                                     toQuantityType(computePF(emeter), Units.PERCENT));
281
282                             accumulatedWatts += getDouble(emeter.power);
283                             accumulatedTotal += getDouble(emeter.total) / 1000;
284                             accumulatedReturned += getDouble(emeter.totalReturned) / 1000;
285                             if (updated) {
286                                 thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, getTimestamp());
287                             }
288                         }
289                         m++;
290                     }
291                 }
292             } else {
293                 // In Roller Mode we accumulate all meters to a single set of meters
294                 double currentWatts = 0.0;
295                 double totalWatts = 0.0;
296                 double lastMin1 = 0.0;
297                 long timestamp = 0l;
298                 String groupName = CHANNEL_GROUP_METER;
299
300                 if (!thingHandler.areChannelsCreated()) {
301                     ShellySettingsMeter m = status.meters.get(0);
302                     if (getBool(m.isValid)) {
303                         // Create channels for 1 Meter
304                         thingHandler.updateChannelDefinitions(
305                                 ShellyChannelDefinitions.createMeterChannels(thingHandler.getThing(), m, groupName));
306                     }
307                 }
308
309                 for (ShellySettingsMeter meter : status.meters) {
310                     if (getBool(meter.isValid)) {
311                         currentWatts += getDouble(meter.power);
312                         totalWatts += getDouble(meter.total);
313                         if (meter.counters != null) {
314                             lastMin1 += getDouble(meter.counters[0]);
315                         }
316                         if (getLong(meter.timestamp) > timestamp) {
317                             timestamp = getLong(meter.timestamp); // newest one
318                         }
319                     }
320                 }
321
322                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
323                         toQuantityType(getDouble(lastMin1), DIGITS_WATT, Units.WATT));
324
325                 // convert totalWatts into kw/h
326                 totalWatts = totalWatts / (60.0 * 1000.0);
327                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
328                         toQuantityType(getDouble(currentWatts), DIGITS_WATT, Units.WATT));
329                 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
330                         toQuantityType(getDouble(totalWatts), DIGITS_KWH, Units.KILOWATT_HOUR));
331
332                 if (updated && timestamp > 0) {
333                     thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
334                             getTimestamp(getString(profile.settings.timezone), timestamp));
335                 }
336             }
337
338             if (!profile.isRoller && !profile.isRGBW2) {
339                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS,
340                         toQuantityType(accumulatedWatts, DIGITS_WATT, Units.WATT));
341                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUTOTAL,
342                         toQuantityType(accumulatedTotal, DIGITS_KWH, Units.KILOWATT_HOUR));
343                 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCURETURNED,
344                         toQuantityType(accumulatedReturned, DIGITS_KWH, Units.KILOWATT_HOUR));
345             }
346         }
347
348         return updated;
349     }
350
351     private static Double computePF(ShellySettingsEMeter emeter) {
352         if (emeter.pf != null) { // EM3
353             return emeter.pf; // take device value
354         }
355
356         // EM: compute from provided values
357         if (emeter.reactive != null && Math.abs(emeter.power) + Math.abs(emeter.reactive) > 1.5) {
358             double pf = emeter.power / Math.sqrt(emeter.power * emeter.power + emeter.reactive * emeter.reactive);
359             return pf;
360         }
361         return 0.0;
362     }
363
364     /**
365      * Update Sensor channel
366      *
367      * @param th Thing Handler instance
368      * @param profile ShellyDeviceProfile
369      * @param status Last ShellySettingsStatus
370      *
371      * @throws ShellyApiException
372      */
373     public static boolean updateSensors(ShellyThingInterface thingHandler, ShellySettingsStatus status)
374             throws ShellyApiException {
375         ShellyDeviceProfile profile = thingHandler.getProfile();
376
377         boolean updated = false;
378         if (profile.isSensor || profile.hasBattery) {
379             ShellyStatusSensor sdata = thingHandler.getApi().getSensorStatus();
380             if (!thingHandler.areChannelsCreated()) {
381                 thingHandler.updateChannelDefinitions(
382                         ShellyChannelDefinitions.createSensorChannels(thingHandler.getThing(), profile, sdata));
383             }
384
385             updated |= thingHandler.updateWakeupReason(sdata.actReasons);
386
387             if ((sdata.sensor != null) && sdata.sensor.isValid) {
388                 // Shelly DW: “sensor”:{“state”:“open”, “is_valid”:true},
389                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE,
390                         getOpenClosed(getString(sdata.sensor.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN)));
391                 String sensorError = sdata.sensorError;
392                 boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
393                         getStringType(sensorError));
394                 if (changed && !"0".equals(sensorError)) {
395                     thingHandler.postEvent(getString(sdata.sensorError), true);
396                 }
397                 updated |= changed;
398             }
399             if (sdata.tmp != null && getBool(sdata.tmp.isValid)) {
400                 Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS)
401                         ? getDouble(sdata.tmp.tC)
402                         : getDouble(sdata.tmp.tF);
403                 updated |= updateTempChannel(thingHandler, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
404                         temp.doubleValue(), getString(sdata.tmp.units));
405             } else if (status.thermostats != null) {
406                 // Shelly TRV
407                 if (profile.settings.thermostats != null) {
408                     ShellyThermnostat ps = profile.settings.thermostats.get(0);
409                     ShellyThermnostat t = status.thermostats.get(0);
410                     int bminutes = getInteger(t.boostMinutes) >= 0 ? getInteger(t.boostMinutes)
411                             : getInteger(ps.boostMinutes);
412                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BCONTROL,
413                             getOnOff(getInteger(t.boostMinutes) > 0));
414                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BTIMER,
415                             toQuantityType((double) bminutes, DIGITS_NONE, Units.MINUTE));
416                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE, getStringType(
417                             getBool(t.targetTemp.enabled) ? SHELLY_TRV_MODE_AUTO : SHELLY_TRV_MODE_MANUAL));
418                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_OPEN,
419                             getOpenClosed(t.windowOpen));
420
421                     int pid = getBool(t.schedule) ? getInteger(t.profile) : 0;
422                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE,
423                             getOnOff(t.schedule));
424                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE,
425                             getStringType(profile.getValueProfile(0, pid)));
426                     if (t.tmp != null) {
427                         updated |= updateTempChannel(thingHandler, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
428                                 t.tmp.value, t.tmp.units);
429                         updated |= updateTempChannel(thingHandler, CHANNEL_GROUP_SENSOR, CHANNEL_CONTROL_SETTEMP,
430                                 t.targetTemp.value, t.targetTemp.unit);
431                     }
432                     if (t.pos != null) {
433                         updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION,
434                                 t.pos != -1 ? toQuantityType(t.pos, DIGITS_NONE, Units.PERCENT) : UnDefType.UNDEF);
435                         updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE,
436                                 getOpenClosed(getDouble(t.pos) > 0));
437                     }
438                 }
439             }
440
441             if (sdata.hum != null) {
442                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
443                         toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, Units.PERCENT));
444             }
445             if ((sdata.lux != null) && getBool(sdata.lux.isValid)) {
446                 // “lux”:{“value”:30, “illumination”: “dark”, “is_valid”:true},
447                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
448                         toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, Units.LUX));
449                 if (sdata.lux.illumination != null) {
450                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM,
451                             getStringType(sdata.lux.illumination));
452                 }
453             }
454             if (sdata.accel != null) {
455                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT,
456                         toQuantityType(getDouble(sdata.accel.tilt.doubleValue()), DIGITS_NONE, Units.DEGREE_ANGLE));
457             }
458             if (sdata.flood != null) {
459                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
460                         getOnOff(sdata.flood));
461             }
462             if (sdata.smoke != null) {
463                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SMOKE,
464                         getOnOff(sdata.smoke));
465             }
466             if (sdata.mute != null) {
467                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MUTE, getOnOff(sdata.mute));
468             }
469
470             if (sdata.gasSensor != null) {
471                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST,
472                         getStringType(sdata.gasSensor.selfTestState));
473                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE,
474                         getStringType(sdata.gasSensor.alarmState));
475                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE,
476                         getStringType(sdata.gasSensor.sensorState));
477             }
478             if ((sdata.concentration != null) && sdata.concentration.isValid) {
479                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, toQuantityType(
480                         getInteger(sdata.concentration.ppm).doubleValue(), DIGITS_NONE, Units.PARTS_PER_MILLION));
481             }
482             if ((sdata.adcs != null) && (sdata.adcs.size() > 0)) {
483                 ShellyADC adc = sdata.adcs.get(0);
484                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE,
485                         toQuantityType(getDouble(adc.voltage), 2, Units.VOLT));
486             }
487
488             boolean charger = (getInteger(profile.settings.externalPower) == 1) || getBool(sdata.charger);
489             if ((profile.settings.externalPower != null) || (sdata.charger != null)) {
490                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
491                         getOnOff(charger));
492             }
493             if (sdata.bat != null) { // no update for Sense
494                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
495                         toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT));
496
497                 int lowBattery = thingHandler.getThingConfig().lowBattery;
498                 boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW,
499                         getOnOff(!charger && getDouble(sdata.bat.value) < lowBattery));
500                 updated |= changed;
501                 if (!charger && changed && getDouble(sdata.bat.value) < lowBattery) {
502                     thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
503                 }
504             }
505
506             if (sdata.motion != null) { // Shelly Sense
507                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
508                         getOnOff(sdata.motion));
509             }
510             if (sdata.sensor != null) { // Shelly Motion
511                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_ACT,
512                         getOnOff(sdata.sensor.motionActive));
513                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
514                         getOnOff(sdata.sensor.motion));
515                 long timestamp = getLong(sdata.sensor.motionTimestamp);
516                 if (timestamp != 0) {
517                     updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
518                             getTimestamp(getString(profile.settings.timezone), timestamp));
519                 }
520             }
521
522             updated |= thingHandler.updateInputs(status);
523
524             if (updated) {
525                 thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
526             }
527         }
528         return updated;
529     }
530
531     public static boolean updateTempChannel(@Nullable ShellyShortTemp sensor, ShellyThingInterface thingHandler,
532             String channel) {
533         return sensor != null ? updateTempChannel(thingHandler, CHANNEL_GROUP_SENSOR, channel, sensor.tC, "") : false;
534     }
535
536     public static boolean updateTempChannel(ShellyThingInterface thingHandler, String group, String channel,
537             @Nullable Double temp, @Nullable String unit) {
538         if (temp == null || temp == SHELLY_API_INVTEMP) {
539             return false;
540         }
541         return thingHandler.updateChannel(group, channel,
542                 toQuantityType(convertToC(temp, unit), DIGITS_TEMP, SIUnits.CELSIUS));
543     }
544
545     private static Double convertToC(@Nullable Double temp, @Nullable String unit) {
546         if (temp == null || temp == SHELLY_API_INVTEMP) {
547             return SHELLY_API_INVTEMP;
548         }
549         if (SHELLY_TEMP_FAHRENHEIT.equalsIgnoreCase(getString(unit))) {
550             // convert Fahrenheit to Celsius
551             return ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(temp).doubleValue();
552         }
553         return temp;
554     }
555 }