]> git.basschouten.com Git - openhab-addons.git/blob
0f5a08a97a8e163cdb059304413374424a0d3f72
[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.jetty.client.HttpClient;
21 import org.openhab.binding.shelly.internal.api.ShellyApiException;
22 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO;
23 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
24 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
25 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
26 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRoller;
27 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
28 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
29 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortStatusRelay;
30 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
31 import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
32 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
33 import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
34 import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.IncreaseDecreaseType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.PercentType;
39 import org.openhab.core.library.types.StopMoveType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.library.types.UpDownType;
42 import org.openhab.core.library.unit.SIUnits;
43 import org.openhab.core.library.unit.Units;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.types.Command;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import com.google.gson.Gson;
51
52 /***
53  * The{@link ShellyRelayHandler} handles light (bulb+rgbw2) specific commands and status. All other commands will be
54  * handled by the generic thing handler.
55  *
56  * @author Markus Michels - Initial contribution
57  */
58 @NonNullByDefault
59 public class ShellyRelayHandler extends ShellyBaseHandler {
60     private final Logger logger = LoggerFactory.getLogger(ShellyRelayHandler.class);
61
62     /**
63      * Constructor
64      *
65      * @param thing The thing passed by the HandlerFactory
66      * @param bindingConfig configuration of the binding
67      * @param coapServer coap server instance
68      * @param localIP local IP of the openHAB host
69      * @param httpPort port of the openHAB HTTP API
70      */
71     public ShellyRelayHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
72             final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
73             int httpPort, final HttpClient httpClient) {
74         super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
75     }
76
77     @Override
78     public void initialize() {
79         super.initialize();
80     }
81
82     @Override
83     public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throws ShellyApiException {
84         // Process command
85         String groupName = getString(channelUID.getGroupId());
86         Integer rIndex = 0;
87         if (groupName.startsWith(CHANNEL_GROUP_RELAY_CONTROL)
88                 && groupName.length() > CHANNEL_GROUP_RELAY_CONTROL.length()) {
89             rIndex = Integer.parseInt(substringAfter(channelUID.getGroupId(), CHANNEL_GROUP_RELAY_CONTROL)) - 1;
90         } else if (groupName.startsWith(CHANNEL_GROUP_ROL_CONTROL)
91                 && groupName.length() > CHANNEL_GROUP_ROL_CONTROL.length()) {
92             rIndex = Integer.parseInt(substringAfter(channelUID.getGroupId(), CHANNEL_GROUP_ROL_CONTROL)) - 1;
93         }
94
95         switch (channelUID.getIdWithoutGroup()) {
96             default:
97                 return false;
98
99             case CHANNEL_OUTPUT:
100                 if (!profile.isRoller) {
101                     // extract relay number of group name (relay0->0, relay1->1...)
102                     logger.debug("{}: Set relay output to {}", thingName, command);
103                     api.setRelayTurn(rIndex, command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
104                 } else {
105                     logger.debug("{}: Device is in roller mode, channel command {} ignored", thingName, channelUID);
106                 }
107                 break;
108             case CHANNEL_BRIGHTNESS: // e.g.Dimmer, Duo
109                 handleBrightness(command, rIndex);
110                 break;
111
112             case CHANNEL_ROL_CONTROL_POS:
113             case CHANNEL_ROL_CONTROL_CONTROL:
114                 logger.debug("{}: Roller command/position {}", thingName, command);
115                 handleRoller(command, groupName, rIndex,
116                         channelUID.getIdWithoutGroup().equals(CHANNEL_ROL_CONTROL_CONTROL));
117
118                 // request updates the next 45sec to update roller position after it stopped
119                 requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
120                 break;
121
122             case CHANNEL_ROL_CONTROL_FAV:
123                 if (command instanceof Number) {
124                     int id = ((Number) command).intValue() - 1;
125                     int pos = profile.getRollerFav(id);
126                     if (pos > 0) {
127                         logger.debug("{}: Selecting favorite {}, position = {}", thingName, id, pos);
128                         api.setRollerPos(rIndex, pos);
129                         break;
130                     }
131                 }
132                 logger.debug("{}: Invalid favorite index: {}", thingName, command);
133                 break;
134
135             case CHANNEL_TIMER_AUTOON:
136                 logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
137                 api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command).intValue());
138                 break;
139             case CHANNEL_TIMER_AUTOOFF:
140                 logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
141                 api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command).intValue());
142                 break;
143         }
144         return true;
145     }
146
147     /**
148      * Brightness channel has 2 functions: Switch On/Off (OnOnType) and setting brightness (PercentType)
149      * There is some more logic in the control. When brightness is set to 0 the control sends also an OFF command
150      * When current brightness is 0 and slider will be moved the new brightness will be set, but also a ON command is
151      * send.
152      *
153      * @param command
154      * @param index
155      * @throws ShellyApiException
156      */
157     private void handleBrightness(Command command, Integer index) throws ShellyApiException {
158         Integer value = -1;
159         if (command instanceof PercentType) { // Dimmer
160             value = ((PercentType) command).intValue();
161         } else if (command instanceof DecimalType) { // Number
162             value = ((DecimalType) command).intValue();
163         } else if (command instanceof OnOffType) { // Switch
164             logger.debug("{}: Switch output {}", thingName, command);
165             updateBrightnessChannel(index, (OnOffType) command, value);
166             return;
167         } else if (command instanceof IncreaseDecreaseType) {
168             ShellyShortLightStatus light = api.getLightStatus(index);
169             if (command == IncreaseDecreaseType.INCREASE) {
170                 value = Math.min(light.brightness + DIM_STEPSIZE, 100);
171             } else {
172                 value = Math.max(light.brightness - DIM_STEPSIZE, 0);
173             }
174             logger.debug("{}: Increase/Decrease brightness from {} to {}", thingName, light.brightness, value);
175         }
176         validateRange("brightness", value, 0, 100);
177
178         // Switch light off on brightness = 0
179         if (value == 0) {
180             logger.debug("{}: Brightness=0 -> switch output OFF", thingName);
181             updateBrightnessChannel(index, OnOffType.OFF, 0);
182         } else {
183             logger.debug("{}: Setting dimmer brightness to {}", thingName, value);
184             updateBrightnessChannel(index, OnOffType.ON, value);
185         }
186     }
187
188     private void updateBrightnessChannel(int lightId, OnOffType power, int brightness) throws ShellyApiException {
189         updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
190         if (brightness > 0) {
191             api.setBrightness(lightId, brightness, config.brightnessAutoOn);
192         } else {
193             api.setRelayTurn(lightId, power == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
194             if (brightness >= 0) { // ignore -1
195                 updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
196                         toQuantityType((double) (power == OnOffType.ON ? brightness : 0), DIGITS_NONE, Units.PERCENT));
197             }
198         }
199     }
200
201     @Override
202     public boolean updateDeviceStatus(ShellySettingsStatus status) throws ShellyApiException {
203         // map status to channels
204         boolean updated = false;
205         updated |= updateRelays(status);
206         updated |= updateDimmers(status);
207         updated |= updateLed(status);
208         return updated;
209     }
210
211     /**
212      * Handle Roller Commands
213      *
214      * @param command from handleCommand()
215      * @param groupName relay, roller...
216      * @param index relay number
217      * @param isControl true: is the Rollershutter channel, false: rollerpos channel
218      * @throws ShellyApiException
219      */
220     private void handleRoller(Command command, String groupName, Integer index, boolean isControl)
221             throws ShellyApiException {
222         int position = -1;
223
224         if ((command instanceof UpDownType) || (command instanceof OnOffType)) {
225             ShellyControlRoller rstatus = api.getRollerStatus(index);
226
227             if (!getString(rstatus.state).isEmpty() && !getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_STOP)) {
228                 if ((command == UpDownType.UP && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
229                         || (command == UpDownType.DOWN
230                                 && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
231                     logger.debug("{}: Roller is already moving ({}), ignore command {}", thingName,
232                             getString(rstatus.state), command);
233                     requestUpdates(1, false);
234                     return;
235                 }
236             }
237
238             if (command == UpDownType.UP || command == OnOffType.ON
239                     || ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 100))) {
240                 logger.debug("{}: Open roller", thingName);
241                 int shpos = profile.getRollerFav(config.favoriteUP - 1);
242                 if (shpos > 0) {
243                     logger.debug("{}: Use favoriteUP id {} for positioning roller({}%)", thingName, config.favoriteUP,
244                             shpos);
245                     api.setRollerPos(index, shpos);
246                     position = shpos;
247                 } else {
248                     api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
249                     position = SHELLY_MIN_ROLLER_POS;
250                 }
251             } else if (command == UpDownType.DOWN || command == OnOffType.OFF
252                     || ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 0))) {
253                 logger.debug("{}: Closing roller", thingName);
254                 int shpos = profile.getRollerFav(config.favoriteDOWN - 1);
255                 if (shpos > 0) {
256                     // use favorite position
257                     logger.debug("{}: Use favoriteDOWN id {} for positioning roller ({}%)", thingName,
258                             config.favoriteDOWN, shpos);
259                     api.setRollerPos(index, shpos);
260                     position = shpos;
261                 } else {
262                     api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
263                     position = SHELLY_MAX_ROLLER_POS;
264                 }
265             }
266         } else if (command == StopMoveType.STOP) {
267             logger.debug("{}: Stop roller", thingName);
268             api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_STOP);
269         } else {
270             logger.debug("{}: Set roller to position {}", thingName, command);
271             if (command instanceof PercentType) {
272                 PercentType p = (PercentType) command;
273                 position = p.intValue();
274             } else if (command instanceof DecimalType) {
275                 DecimalType d = (DecimalType) command;
276                 position = d.intValue();
277             } else {
278                 throw new IllegalArgumentException(
279                         "Invalid value type for roller control/position" + command.getClass().toString());
280             }
281
282             // take position from RollerShutter control and map to Shelly positon
283             // OH: 0=closed, 100=open; Shelly 0=open, 100=closed)
284             // take position 1:1 from position channel
285             position = isControl ? SHELLY_MAX_ROLLER_POS - position : position;
286             validateRange("roller position", position, SHELLY_MIN_ROLLER_POS, SHELLY_MAX_ROLLER_POS);
287
288             logger.debug("{}: Changing roller position to {}", thingName, position);
289             api.setRollerPos(index, position);
290         }
291
292         if (position != -1) {
293             // make sure both are in sync
294             if (isControl) {
295                 int pos = SHELLY_MAX_ROLLER_POS - Math.max(0, Math.min(position, SHELLY_MAX_ROLLER_POS));
296                 logger.debug("{}: Set roller position for control channel to {}", thingName, pos);
297                 updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL, new PercentType(pos));
298             } else {
299                 logger.debug("{}: Set roller position channel to {}", thingName, position);
300                 updateChannel(groupName, CHANNEL_ROL_CONTROL_POS, new PercentType(position));
301             }
302         }
303     }
304
305     /**
306      * Auto-create relay channels depending on relay type/mode
307      */
308     private void createRelayChannels(ShellyStatusRelay relay, int idx) {
309         if (!areChannelsCreated()) {
310             updateChannelDefinitions(ShellyChannelDefinitions.createRelayChannels(getThing(), profile, relay, idx));
311         }
312     }
313
314     private void createDimmerChannels(ShellySettingsStatus dstatus, int idx) {
315         if (!areChannelsCreated()) {
316             updateChannelDefinitions(ShellyChannelDefinitions.createDimmerChannels(getThing(), profile, dstatus, idx));
317         }
318     }
319
320     private void createRollerChannels(ShellyControlRoller roller) {
321         if (!areChannelsCreated()) {
322             updateChannelDefinitions(ShellyChannelDefinitions.createRollerChannels(getThing(), roller));
323         }
324     }
325
326     /**
327      * Update Relay/Roller channels
328      *
329      * @param th Thing Handler instance
330      * @param profile ShellyDeviceProfile
331      * @param status Last ShellySettingsStatus
332      *
333      * @throws ShellyApiException
334      */
335     public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
336         boolean updated = false;
337         // Check for Relay in Standard Mode
338         if (profile.hasRelays && !profile.isRoller && !profile.isDimmer) {
339             double voltage = -1;
340             if (status.voltage == null && profile.settings.supplyVoltage != null) {
341                 // Shelly 1PM/1L (fix)
342                 voltage = profile.settings.supplyVoltage == 0 ? 110.0 : 220.0;
343             } else {
344                 // Shelly 2.5 (measured)
345                 voltage = getDouble(status.voltage);
346             }
347             if (voltage > 0) {
348                 updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_VOLTAGE,
349                         toQuantityType(voltage, DIGITS_VOLT, Units.VOLT));
350             }
351
352             logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
353             int i = 0;
354             ShellyStatusRelay rstatus = api.getRelayStatus(i);
355             for (ShellyShortStatusRelay relay : rstatus.relays) {
356                 createRelayChannels(rstatus, i);
357                 if ((relay.isValid == null) || relay.isValid) {
358                     String groupName = profile.getControlGroup(i);
359                     ShellySettingsRelay rs = profile.settings.relays.get(i);
360                     updated |= updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(rs.name));
361
362                     if (getBool(relay.overpower)) {
363                         postEvent(ALARM_TYPE_OVERPOWER, false);
364                     }
365
366                     updated |= updateChannel(groupName, CHANNEL_OUTPUT, getOnOff(relay.ison));
367                     updated |= updateChannel(groupName, CHANNEL_TIMER_ACTIVE, getOnOff(relay.hasTimer));
368                     if (rstatus.extTemperature != null) {
369                         // Shelly 1/1PM support up to 3 external sensors
370                         // for whatever reason those are not represented as an array, but 3 elements
371                         if (rstatus.extTemperature.sensor1 != null) {
372                             updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP1, toQuantityType(
373                                     getDouble(rstatus.extTemperature.sensor1.tC), DIGITS_TEMP, SIUnits.CELSIUS));
374                         }
375                         if (rstatus.extTemperature.sensor2 != null) {
376                             updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP2, toQuantityType(
377                                     getDouble(rstatus.extTemperature.sensor2.tC), DIGITS_TEMP, SIUnits.CELSIUS));
378                         }
379                         if (rstatus.extTemperature.sensor3 != null) {
380                             updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP3, toQuantityType(
381                                     getDouble(rstatus.extTemperature.sensor3.tC), DIGITS_TEMP, SIUnits.CELSIUS));
382                         }
383                     }
384                     if ((rstatus.extHumidity != null) && (rstatus.extHumidity.sensor1 != null)) {
385                         updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM, toQuantityType(
386                                 getDouble(rstatus.extHumidity.sensor1.hum), DIGITS_PERCENT, Units.PERCENT));
387                     }
388
389                     // Update Auto-ON/OFF timer
390                     ShellySettingsRelay rsettings = profile.settings.relays.get(i);
391                     if (rsettings != null) {
392                         updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
393                                 toQuantityType(getDouble(rsettings.autoOn), Units.SECOND));
394                         updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
395                                 toQuantityType(getDouble(rsettings.autoOff), Units.SECOND));
396                     }
397                 }
398                 i++;
399             }
400         } else if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
401             // Check for Relay in Roller Mode
402             logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
403             int i = 0;
404
405             for (ShellySettingsRoller roller : status.rollers) {
406                 if (roller.isValid) {
407                     ShellyControlRoller control = api.getRollerStatus(i);
408                     Integer relayIndex = i + 1;
409                     String groupName = profile.numRollers > 1 ? CHANNEL_GROUP_ROL_CONTROL + relayIndex.toString()
410                             : CHANNEL_GROUP_ROL_CONTROL;
411
412                     createRollerChannels(control);
413
414                     if (control.name != null) {
415                         updated |= updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(control.name));
416                     }
417
418                     String state = getString(control.state);
419                     if (state.equals(SHELLY_ALWD_ROLLER_TURN_STOP)) { // only valid in stop state
420                         int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(control.currentPos, SHELLY_MAX_ROLLER_POS));
421                         logger.debug("{}: REST Update roller position: control={}, position={}", thingName,
422                                 SHELLY_MAX_ROLLER_POS - pos, pos);
423                         updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
424                                 toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
425                         updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
426                                 toQuantityType((double) pos, Units.PERCENT));
427                         scheduledUpdates = 1; // one more poll and then stop
428                     }
429
430                     updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STATE, new StringType(state));
431                     updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STOPR, getStringType(control.stopReason));
432                     updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_SAFETY, getOnOff(control.safetySwitch));
433
434                     i++;
435                 }
436             }
437         }
438         return updated;
439     }
440
441     /**
442      * Update Relay/Roller channels
443      *
444      * @param th Thing Handler instance
445      * @param profile ShellyDeviceProfile
446      * @param status Last ShellySettingsStatus
447      *
448      * @throws ShellyApiException
449      */
450     public boolean updateDimmers(ShellySettingsStatus orgStatus) throws ShellyApiException {
451         boolean updated = false;
452         if (profile.isDimmer) {
453             // We need to fixup the returned Json: The dimmer returns light[] element, which is ok, but it doesn't have
454             // the same structure as lights[] from Bulb,RGBW2 and Duo. The tag gets replaced by dimmers[] so that Gson
455             // maps to a different structure (ShellyShortLight).
456             Gson gson = new Gson();
457             ShellySettingsStatus dstatus = fromJson(gson, ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
458                     ShellySettingsStatus.class);
459
460             logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size());
461             int l = 0;
462             for (ShellyShortLightStatus dimmer : dstatus.dimmers) {
463                 Integer r = l + 1;
464                 String groupName = profile.numRelays <= 1 ? CHANNEL_GROUP_DIMMER_CONTROL
465                         : CHANNEL_GROUP_DIMMER_CONTROL + r.toString();
466
467                 createDimmerChannels(dstatus, l);
468
469                 // On a status update we map a dimmer.ison = false to brightness 0 rather than the device's brightness
470                 // and send a OFF status to the same channel.
471                 // When the device's brightness is > 0 we send the new value to the channel and a ON command
472                 if (dimmer.ison) {
473                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.ON);
474                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
475                             toQuantityType((double) getInteger(dimmer.brightness), DIGITS_NONE, Units.PERCENT));
476                 } else {
477                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.OFF);
478                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
479                             toQuantityType(0.0, DIGITS_NONE, Units.PERCENT));
480                 }
481
482                 ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l);
483                 if (dsettings != null) {
484                     updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
485                             toQuantityType(getDouble(dsettings.autoOn), Units.SECOND));
486                     updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
487                             toQuantityType(getDouble(dsettings.autoOff), Units.SECOND));
488                 }
489
490                 l++;
491             }
492         }
493         return updated;
494     }
495
496     /**
497      * Update LED channels
498      *
499      * @param th Thing Handler instance
500      * @param profile ShellyDeviceProfile
501      * @param status Last ShellySettingsStatus
502      */
503     public boolean updateLed(ShellySettingsStatus status) {
504         boolean updated = false;
505         updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_LED_STATUS_DISABLE,
506                 getOnOff(profile.settings.ledStatusDisable));
507         updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_LED_POWER_DISABLE,
508                 getOnOff(profile.settings.ledPowerDisable));
509         return updated;
510     }
511 }