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