2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.shelly.internal.handler;
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.*;
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;
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
44 * @author Markus Michels - Initial contribution
47 public class ShellyComponents {
50 * Update device status
52 * @param th Thing Handler instance
53 * @param profile ShellyDeviceProfile
55 public static boolean updateDeviceStatus(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
56 ShellyDeviceProfile profile = thingHandler.getProfile();
58 if (!thingHandler.areChannelsCreated()) {
59 thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(),
60 thingHandler.getProfile(), status));
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));
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));
79 thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SLEEPTIME,
80 toQuantityType(getInteger(status.sleepTime), Units.SECOND));
82 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
84 if (profile.settings.calibrated != null) {
85 thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CALIBRATED,
86 getOnOff(profile.settings.calibrated));
89 return false; // device status never triggers update
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);
99 throw new IllegalArgumentException("No relay settings");
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));
107 if (getBool(relay.overpower)) {
108 thingHandler.postEvent(ALARM_TYPE_OVERPOWER, false);
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);
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
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));
180 String state = getString(control.state);
183 case SHELLY_ALWD_ROLLER_TURN_OPEN:
184 pos = SHELLY_MAX_ROLLER_POS;
186 case SHELLY_ALWD_ROLLER_TURN_CLOSE:
187 pos = SHELLY_MIN_ROLLER_POS;
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));
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));
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));
213 * Update Meter channel
215 * @param th Thing Handler instance
216 * @param profile ShellyDeviceProfile
217 * @param status Last ShellySettingsStatus
219 public static boolean updateMeters(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
220 ShellyDeviceProfile profile = thingHandler.getProfile();
222 double accumulatedWatts = 0.0;
223 double accumulatedTotal = 0.0;
224 double accumulatedReturned = 0.0;
226 boolean updated = false;
227 // Devices without power meters get no updates
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
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;
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));
252 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
253 toQuantityType(getDouble(meter.power), DIGITS_WATT, Units.WATT));
254 accumulatedWatts += getDouble(meter.power);
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;
263 if (meter.counters != null) {
264 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
265 toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, Units.WATT));
267 if (meter.timestamp != null) {
268 thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
269 getTimestamp(getString(profile.settings.timezone), meter.timestamp));
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));
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));
299 accumulatedWatts += getDouble(emeter.power);
300 accumulatedTotal += getDouble(emeter.total) / 1000;
301 accumulatedReturned += getDouble(emeter.totalReturned) / 1000;
303 thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, getTimestamp());
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;
315 String groupName = CHANNEL_GROUP_METER;
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));
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]);
333 if (getLong(meter.timestamp) > timestamp) {
334 timestamp = getLong(meter.timestamp); // newest one
339 updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
340 toQuantityType(getDouble(lastMin1), DIGITS_WATT, Units.WATT));
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));
349 if (updated && timestamp > 0) {
350 thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
351 getTimestamp(getString(profile.settings.timezone), timestamp));
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));
368 private static Double computePF(ShellySettingsEMeter emeter) {
369 if (emeter.pf != null) { // EM3
370 return emeter.pf; // take device value
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);
382 * Update Sensor channel
384 * @param th Thing Handler instance
385 * @param profile ShellyDeviceProfile
386 * @param status Last ShellySettingsStatus
388 * @throws ShellyApiException
390 public static boolean updateSensors(ShellyThingInterface thingHandler, ShellySettingsStatus status)
391 throws ShellyApiException {
392 ShellyDeviceProfile profile = thingHandler.getProfile();
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));
402 updated |= thingHandler.updateWakeupReason(sdata.actReasons);
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);
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();
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) {
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)));
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));
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);
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));
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));
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));
480 if (sdata.flood != null) {
481 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
482 getOnOff(sdata.flood));
484 if (sdata.smoke != null) {
485 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SMOKE,
486 getOnOff(sdata.smoke));
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));
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));
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));
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);
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));
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);
519 if (!charger && changed && getDouble(sdata.bat.value) < lowBattery) {
520 thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
524 if (sdata.motion != null) { // Shelly Sense
525 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
526 getOnOff(sdata.motion));
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));
540 updated |= thingHandler.updateInputs(status);
543 thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
549 private static Double convertToC(@Nullable Double temp, String unit) {
553 if (SHELLY_TEMP_FAHRENHEIT.equalsIgnoreCase(unit)) {
554 // convert Fahrenheit to Celsius
555 return ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(temp).doubleValue();