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