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