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