]> git.basschouten.com Git - openhab-addons.git/blob
fcd6d37f43035249843deefd232ce32e3e91b8cb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.hue.internal.handler;
14
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
16 import static org.openhab.core.thing.Thing.*;
17
18 import java.math.BigDecimal;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Objects;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.hue.internal.FullLight;
30 import org.openhab.binding.hue.internal.State;
31 import org.openhab.binding.hue.internal.State.ColorMode;
32 import org.openhab.binding.hue.internal.StateUpdate;
33 import org.openhab.binding.hue.internal.action.LightActions;
34 import org.openhab.binding.hue.internal.dto.Capabilities;
35 import org.openhab.binding.hue.internal.dto.ColorTemperature;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.HSBType;
38 import org.openhab.core.library.types.IncreaseDecreaseType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingStatusInfo;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.binding.ThingHandler;
51 import org.openhab.core.thing.binding.ThingHandlerService;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.StateDescription;
54 import org.openhab.core.types.StateDescriptionFragmentBuilder;
55 import org.openhab.core.types.UnDefType;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * {@link HueLightHandler} is the handler for a hue light. It uses the {@link HueClient} to execute the actual
61  * command.
62  *
63  * @author Dennis Nobel - Initial contribution
64  * @author Oliver Libutzki - Adjustments
65  * @author Kai Kreuzer - stabilized code
66  * @author Andre Fuechsel - implemented switch off when brightness == 0, changed to support generic thing types, changed
67  *         the initialization of properties
68  * @author Thomas Höfer - added thing properties
69  * @author Jochen Hiller - fixed status updates for reachable=true/false
70  * @author Markus Mazurczak - added code for command handling of OSRAM PAR16 50
71  *         bulbs
72  * @author Yordan Zhelev - added alert and effect functions
73  * @author Denis Dudnik - switched to internally integrated source of Jue library
74  * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only
75  * @author Jochen Leopold - Added support for custom fade times
76  */
77 @NonNullByDefault
78 public class HueLightHandler extends BaseThingHandler implements LightStatusListener {
79
80     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_COLOR_LIGHT,
81             THING_TYPE_COLOR_TEMPERATURE_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_EXTENDED_COLOR_LIGHT,
82             THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG);
83
84     public static final String OSRAM_PAR16_50_TW_MODEL_ID = "PAR16_50_TW";
85
86     private final Logger logger = LoggerFactory.getLogger(HueLightHandler.class);
87
88     private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
89
90     private @NonNullByDefault({}) String lightId;
91
92     private @Nullable FullLight lastFullLight;
93     private long endBypassTime = 0L;
94
95     private @Nullable Integer lastSentColorTemp;
96     private @Nullable Integer lastSentBrightness;
97
98     // Flag to indicate whether the bulb is of type Osram par16 50 TW or not
99     private boolean isOsramPar16 = false;
100
101     private boolean propertiesInitializedSuccessfully = false;
102     private boolean capabilitiesInitializedSuccessfully = false;
103     private ColorTemperature colorTemperatureCapabilties = new ColorTemperature();
104     private long defaultFadeTime = 400;
105
106     private @Nullable HueClient hueClient;
107
108     private @Nullable ScheduledFuture<?> scheduledFuture;
109
110     public HueLightHandler(Thing hueLight, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) {
111         super(hueLight);
112         this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
113     }
114
115     @Override
116     public void initialize() {
117         logger.debug("Initializing hue light handler.");
118         Bridge bridge = getBridge();
119         initializeThing((bridge == null) ? null : bridge.getStatus());
120     }
121
122     @Override
123     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
124         logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
125         initializeThing(bridgeStatusInfo.getStatus());
126     }
127
128     private void initializeThing(@Nullable ThingStatus bridgeStatus) {
129         logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
130         final String configLightId = (String) getConfig().get(LIGHT_ID);
131         if (configLightId != null) {
132             BigDecimal time = (BigDecimal) getConfig().get(FADETIME);
133             if (time != null) {
134                 defaultFadeTime = time.longValueExact();
135             }
136
137             lightId = configLightId;
138             // note: this call implicitly registers our handler as a listener on the bridge
139             HueClient bridgeHandler = getHueClient();
140             if (bridgeHandler != null) {
141                 if (bridgeStatus == ThingStatus.ONLINE) {
142                     FullLight fullLight = bridgeHandler.getLightById(lightId);
143                     initializeProperties(fullLight);
144                     initializeCapabilities(fullLight);
145                     updateStatus(ThingStatus.ONLINE);
146                 } else {
147                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
148                 }
149             } else {
150                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
151             }
152         } else {
153             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
154                     "@text/offline.conf-error-no-light-id");
155         }
156     }
157
158     private synchronized void initializeProperties(@Nullable FullLight fullLight) {
159         if (!propertiesInitializedSuccessfully && fullLight != null) {
160             Map<String, String> properties = editProperties();
161             String softwareVersion = fullLight.getSoftwareVersion();
162             if (softwareVersion != null) {
163                 properties.put(PROPERTY_FIRMWARE_VERSION, softwareVersion);
164             }
165             String modelId = fullLight.getNormalizedModelID();
166             if (modelId != null) {
167                 properties.put(PROPERTY_MODEL_ID, modelId);
168             }
169             properties.put(PROPERTY_VENDOR, fullLight.getManufacturerName());
170             properties.put(PRODUCT_NAME, fullLight.getProductName());
171             String uniqueID = fullLight.getUniqueID();
172             if (uniqueID != null) {
173                 properties.put(UNIQUE_ID, uniqueID);
174             }
175             updateProperties(properties);
176             isOsramPar16 = OSRAM_PAR16_50_TW_MODEL_ID.equals(modelId);
177             propertiesInitializedSuccessfully = true;
178         }
179     }
180
181     private void initializeCapabilities(@Nullable FullLight fullLight) {
182         if (!capabilitiesInitializedSuccessfully && fullLight != null) {
183             Capabilities capabilities = fullLight.capabilities;
184             if (capabilities != null) {
185                 ColorTemperature ct = capabilities.control.ct;
186                 if (ct != null) {
187                     colorTemperatureCapabilties = ct;
188
189                     // minimum and maximum are inverted due to mired/Kelvin conversion!
190                     StateDescription stateDescription = StateDescriptionFragmentBuilder.create()
191                             .withMinimum(new BigDecimal(LightStateConverter.miredToKelvin(ct.max))) //
192                             .withMaximum(new BigDecimal(LightStateConverter.miredToKelvin(ct.min))) //
193                             .withStep(new BigDecimal(100)) //
194                             .withPattern("%.0f K") //
195                             .build().toStateDescription();
196                     if (stateDescription != null) {
197                         stateDescriptionOptionProvider.setDescription(
198                                 new ChannelUID(thing.getUID(), CHANNEL_COLORTEMPERATURE_ABS), stateDescription);
199                     } else {
200                         logger.warn("Failed to create state description in thing {}", thing.getUID());
201                     }
202                 }
203             }
204             capabilitiesInitializedSuccessfully = true;
205         }
206     }
207
208     @Override
209     public void dispose() {
210         logger.debug("Hue light handler disposes. Unregistering listener.");
211         cancelScheduledFuture();
212         if (lightId != null) {
213             HueClient bridgeHandler = getHueClient();
214             if (bridgeHandler != null) {
215                 bridgeHandler.unregisterLightStatusListener(this);
216                 hueClient = null;
217             }
218             lightId = null;
219         }
220     }
221
222     @Override
223     public void handleCommand(ChannelUID channelUID, Command command) {
224         handleCommand(channelUID.getId(), command, defaultFadeTime);
225     }
226
227     public void handleCommand(String channel, Command command, long fadeTime) {
228         HueClient bridgeHandler = getHueClient();
229         if (bridgeHandler == null) {
230             logger.warn("hue bridge handler not found. Cannot handle command without bridge.");
231             return;
232         }
233
234         final FullLight light = lastFullLight == null ? bridgeHandler.getLightById(lightId) : lastFullLight;
235         if (light == null) {
236             logger.debug("hue light not known on bridge. Cannot handle command.");
237             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
238                     "@text/offline.conf-error-wrong-light-id");
239             return;
240         }
241
242         Integer lastColorTemp;
243         StateUpdate lightState = null;
244         switch (channel) {
245             case CHANNEL_COLORTEMPERATURE:
246                 if (command instanceof PercentType) {
247                     lightState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command,
248                             colorTemperatureCapabilties);
249                     lightState.setTransitionTime(fadeTime);
250                 } else if (command instanceof OnOffType) {
251                     lightState = LightStateConverter.toOnOffLightState((OnOffType) command);
252                     if (isOsramPar16) {
253                         lightState = addOsramSpecificCommands(lightState, (OnOffType) command);
254                     }
255                 } else if (command instanceof IncreaseDecreaseType) {
256                     lightState = convertColorTempChangeToStateUpdate((IncreaseDecreaseType) command, light);
257                     if (lightState != null) {
258                         lightState.setTransitionTime(fadeTime);
259                     }
260                 }
261                 break;
262             case CHANNEL_COLORTEMPERATURE_ABS:
263                 if (command instanceof DecimalType) {
264                     lightState = LightStateConverter.toColorTemperatureLightState((DecimalType) command,
265                             colorTemperatureCapabilties);
266                     lightState.setTransitionTime(fadeTime);
267                 }
268                 break;
269             case CHANNEL_BRIGHTNESS:
270                 if (command instanceof PercentType) {
271                     lightState = LightStateConverter.toBrightnessLightState((PercentType) command);
272                     lightState.setTransitionTime(fadeTime);
273                 } else if (command instanceof OnOffType) {
274                     lightState = LightStateConverter.toOnOffLightState((OnOffType) command);
275                     if (isOsramPar16) {
276                         lightState = addOsramSpecificCommands(lightState, (OnOffType) command);
277                     }
278                 } else if (command instanceof IncreaseDecreaseType) {
279                     lightState = convertBrightnessChangeToStateUpdate((IncreaseDecreaseType) command, light);
280                     if (lightState != null) {
281                         lightState.setTransitionTime(fadeTime);
282                     }
283                 }
284                 lastColorTemp = lastSentColorTemp;
285                 if (lightState != null && lastColorTemp != null) {
286                     // make sure that the light also has the latest color temp
287                     // this might not have been yet set in the light, if it was off
288                     lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
289                     lightState.setTransitionTime(fadeTime);
290                 }
291                 break;
292             case CHANNEL_SWITCH:
293                 logger.trace("CHANNEL_SWITCH handling command {}", command);
294                 if (command instanceof OnOffType) {
295                     lightState = LightStateConverter.toOnOffLightState((OnOffType) command);
296                     if (isOsramPar16) {
297                         lightState = addOsramSpecificCommands(lightState, (OnOffType) command);
298                     }
299                 }
300                 lastColorTemp = lastSentColorTemp;
301                 if (lightState != null && lastColorTemp != null) {
302                     // make sure that the light also has the latest color temp
303                     // this might not have been yet set in the light, if it was off
304                     lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
305                     lightState.setTransitionTime(fadeTime);
306                 }
307                 break;
308             case CHANNEL_COLOR:
309                 if (command instanceof HSBType) {
310                     HSBType hsbCommand = (HSBType) command;
311                     if (hsbCommand.getBrightness().intValue() == 0) {
312                         lightState = LightStateConverter.toOnOffLightState(OnOffType.OFF);
313                     } else {
314                         lightState = LightStateConverter.toColorLightState(hsbCommand, light.getState());
315                         lightState.setTransitionTime(fadeTime);
316                     }
317                 } else if (command instanceof PercentType) {
318                     lightState = LightStateConverter.toBrightnessLightState((PercentType) command);
319                     lightState.setTransitionTime(fadeTime);
320                 } else if (command instanceof OnOffType) {
321                     lightState = LightStateConverter.toOnOffLightState((OnOffType) command);
322                 } else if (command instanceof IncreaseDecreaseType) {
323                     lightState = convertBrightnessChangeToStateUpdate((IncreaseDecreaseType) command, light);
324                     if (lightState != null) {
325                         lightState.setTransitionTime(fadeTime);
326                     }
327                 }
328                 break;
329             case CHANNEL_ALERT:
330                 if (command instanceof StringType) {
331                     lightState = LightStateConverter.toAlertState((StringType) command);
332                     if (lightState == null) {
333                         // Unsupported StringType is passed. Log a warning
334                         // message and return.
335                         logger.warn("Unsupported String command: {}. Supported commands are: {}, {}, {} ", command,
336                                 LightStateConverter.ALERT_MODE_NONE, LightStateConverter.ALERT_MODE_SELECT,
337                                 LightStateConverter.ALERT_MODE_LONG_SELECT);
338                         return;
339                     } else {
340                         scheduleAlertStateRestore(command);
341                     }
342                 }
343                 break;
344             case CHANNEL_EFFECT:
345                 if (command instanceof OnOffType) {
346                     lightState = LightStateConverter.toOnOffEffectState((OnOffType) command);
347                 }
348                 break;
349         }
350         if (lightState != null) {
351             // Cache values which we have sent
352             Integer tmpBrightness = lightState.getBrightness();
353             if (tmpBrightness != null) {
354                 lastSentBrightness = tmpBrightness;
355             }
356             Integer tmpColorTemp = lightState.getColorTemperature();
357             if (tmpColorTemp != null) {
358                 lastSentColorTemp = tmpColorTemp;
359             }
360             bridgeHandler.updateLightState(this, light, lightState, fadeTime);
361         } else {
362             logger.warn("Command sent to an unknown channel id: {}:{}", getThing().getUID(), channel);
363         }
364     }
365
366     /*
367      * Applies additional {@link StateUpdate} commands as a workaround for Osram
368      * Lightify PAR16 TW firmware bug. Also see
369      * http://www.everyhue.com/vanilla/discussion/1756/solved-lightify-turning-off
370      */
371     private StateUpdate addOsramSpecificCommands(StateUpdate lightState, OnOffType actionType) {
372         if (actionType.equals(OnOffType.ON)) {
373             lightState.setBrightness(254);
374         } else {
375             lightState.setTransitionTime(0);
376         }
377         return lightState;
378     }
379
380     private @Nullable StateUpdate convertColorTempChangeToStateUpdate(IncreaseDecreaseType command, FullLight light) {
381         StateUpdate stateUpdate = null;
382         Integer currentColorTemp = getCurrentColorTemp(light.getState());
383         if (currentColorTemp != null) {
384             int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp,
385                     colorTemperatureCapabilties);
386             stateUpdate = new StateUpdate().setColorTemperature(newColorTemp, colorTemperatureCapabilties);
387         }
388         return stateUpdate;
389     }
390
391     private @Nullable Integer getCurrentColorTemp(@Nullable State lightState) {
392         Integer colorTemp = lastSentColorTemp;
393         if (colorTemp == null && lightState != null) {
394             colorTemp = lightState.getColorTemperature();
395         }
396         return colorTemp;
397     }
398
399     private @Nullable StateUpdate convertBrightnessChangeToStateUpdate(IncreaseDecreaseType command, FullLight light) {
400         StateUpdate stateUpdate = null;
401         Integer currentBrightness = getCurrentBrightness(light.getState());
402         if (currentBrightness != null) {
403             int newBrightness = LightStateConverter.toAdjustedBrightness(command, currentBrightness);
404             stateUpdate = createBrightnessStateUpdate(currentBrightness, newBrightness);
405         }
406         return stateUpdate;
407     }
408
409     private @Nullable Integer getCurrentBrightness(@Nullable State lightState) {
410         Integer brightness = lastSentBrightness;
411         if (brightness == null && lightState != null) {
412             if (!lightState.isOn()) {
413                 brightness = 0;
414             } else {
415                 brightness = lightState.getBrightness();
416             }
417         }
418         return brightness;
419     }
420
421     private StateUpdate createBrightnessStateUpdate(int currentBrightness, int newBrightness) {
422         StateUpdate lightUpdate = new StateUpdate();
423         if (newBrightness == 0) {
424             lightUpdate.turnOff();
425         } else {
426             lightUpdate.setBrightness(newBrightness);
427             if (currentBrightness == 0) {
428                 lightUpdate.turnOn();
429             }
430         }
431         return lightUpdate;
432     }
433
434     protected synchronized @Nullable HueClient getHueClient() {
435         if (hueClient == null) {
436             Bridge bridge = getBridge();
437             if (bridge == null) {
438                 return null;
439             }
440             ThingHandler handler = bridge.getHandler();
441             if (handler instanceof HueClient) {
442                 HueClient bridgeHandler = (HueClient) handler;
443                 hueClient = bridgeHandler;
444                 bridgeHandler.registerLightStatusListener(this);
445             } else {
446                 return null;
447             }
448         }
449         return hueClient;
450     }
451
452     @Override
453     public void setPollBypass(long bypassTime) {
454         endBypassTime = System.currentTimeMillis() + bypassTime;
455     }
456
457     @Override
458     public void unsetPollBypass() {
459         endBypassTime = 0L;
460     }
461
462     @Override
463     public boolean onLightStateChanged(FullLight fullLight) {
464         logger.trace("onLightStateChanged() was called");
465
466         if (System.currentTimeMillis() <= endBypassTime) {
467             logger.debug("Bypass light update after command ({}).", lightId);
468             return false;
469         }
470
471         State state = fullLight.getState();
472
473         final FullLight lastState = lastFullLight;
474         if (lastState == null || !Objects.equals(lastState.getState(), state)) {
475             lastFullLight = fullLight;
476         } else {
477             return true;
478         }
479
480         logger.trace("New state for light {}", lightId);
481
482         initializeProperties(fullLight);
483
484         lastSentColorTemp = null;
485         lastSentBrightness = null;
486
487         // update status (ONLINE, OFFLINE)
488         if (state.isReachable()) {
489             updateStatus(ThingStatus.ONLINE);
490         } else {
491             // we assume OFFLINE without any error (NONE), as this is an
492             // expected state (when bulb powered off)
493             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.light-not-reachable");
494         }
495
496         logger.debug("onLightStateChanged Light {}: on {} bri {} hue {} sat {} temp {} mode {} XY {}",
497                 fullLight.getName(), state.isOn(), state.getBrightness(), state.getHue(), state.getSaturation(),
498                 state.getColorTemperature(), state.getColorMode(), state.getXY());
499
500         HSBType hsbType = LightStateConverter.toHSBType(state);
501         if (!state.isOn()) {
502             hsbType = new HSBType(hsbType.getHue(), hsbType.getSaturation(), PercentType.ZERO);
503         }
504         updateState(CHANNEL_COLOR, hsbType);
505
506         ColorMode colorMode = state.getColorMode();
507         if (ColorMode.CT.equals(colorMode)) {
508             updateState(CHANNEL_COLORTEMPERATURE,
509                     LightStateConverter.toColorTemperaturePercentType(state, colorTemperatureCapabilties));
510             updateState(CHANNEL_COLORTEMPERATURE_ABS, LightStateConverter.toColorTemperature(state));
511         } else {
512             updateState(CHANNEL_COLORTEMPERATURE, UnDefType.UNDEF);
513             updateState(CHANNEL_COLORTEMPERATURE_ABS, UnDefType.UNDEF);
514         }
515
516         PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state);
517         if (!state.isOn()) {
518             brightnessPercentType = PercentType.ZERO;
519         }
520         updateState(CHANNEL_BRIGHTNESS, brightnessPercentType);
521
522         updateState(CHANNEL_SWITCH, OnOffType.from(state.isOn()));
523
524         StringType stringType = LightStateConverter.toAlertStringType(state);
525         if (!"NULL".equals(stringType.toString())) {
526             updateState(CHANNEL_ALERT, stringType);
527             scheduleAlertStateRestore(stringType);
528         }
529
530         return true;
531     }
532
533     @Override
534     public void channelLinked(ChannelUID channelUID) {
535         HueClient handler = getHueClient();
536         if (handler != null) {
537             FullLight light = handler.getLightById(lightId);
538             if (light != null) {
539                 onLightStateChanged(light);
540             }
541         }
542     }
543
544     @Override
545     public void onLightRemoved() {
546         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.light-removed");
547     }
548
549     @Override
550     public void onLightGone() {
551         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/offline.light-not-reachable");
552     }
553
554     @Override
555     public void onLightAdded(FullLight light) {
556         onLightStateChanged(light);
557     }
558
559     /**
560      * Schedules restoration of the alert item state to {@link LightStateConverter#ALERT_MODE_NONE} after a given time.
561      * <br>
562      * Based on the initial command:
563      * <ul>
564      * <li>For {@link LightStateConverter#ALERT_MODE_SELECT} restoration will be triggered after <strong>2
565      * seconds</strong>.
566      * <li>For {@link LightStateConverter#ALERT_MODE_LONG_SELECT} restoration will be triggered after <strong>15
567      * seconds</strong>.
568      * </ul>
569      * This method also cancels any previously scheduled restoration.
570      *
571      * @param command The {@link Command} sent to the item
572      */
573     private void scheduleAlertStateRestore(Command command) {
574         cancelScheduledFuture();
575         int delay = getAlertDuration(command);
576
577         if (delay > 0) {
578             scheduledFuture = scheduler.schedule(() -> {
579                 updateState(CHANNEL_ALERT, new StringType(LightStateConverter.ALERT_MODE_NONE));
580             }, delay, TimeUnit.MILLISECONDS);
581         }
582     }
583
584     /**
585      * This method will cancel previously scheduled alert item state
586      * restoration.
587      */
588     private void cancelScheduledFuture() {
589         ScheduledFuture<?> scheduledJob = scheduledFuture;
590         if (scheduledJob != null) {
591             scheduledJob.cancel(true);
592             scheduledFuture = null;
593         }
594     }
595
596     /**
597      * This method returns the time in <strong>milliseconds</strong> after
598      * which, the state of the alert item has to be restored to {@link LightStateConverter#ALERT_MODE_NONE}.
599      *
600      * @param command The initial command sent to the alert item.
601      * @return Based on the initial command will return:
602      *         <ul>
603      *         <li><strong>2000</strong> for {@link LightStateConverter#ALERT_MODE_SELECT}.
604      *         <li><strong>15000</strong> for {@link LightStateConverter#ALERT_MODE_LONG_SELECT}.
605      *         <li><strong>-1</strong> for any command different from the previous two.
606      *         </ul>
607      */
608     private int getAlertDuration(Command command) {
609         int delay;
610         switch (command.toString()) {
611             case LightStateConverter.ALERT_MODE_LONG_SELECT:
612                 delay = 15000;
613                 break;
614             case LightStateConverter.ALERT_MODE_SELECT:
615                 delay = 2000;
616                 break;
617             default:
618                 delay = -1;
619                 break;
620         }
621
622         return delay;
623     }
624
625     @Override
626     public Collection<Class<? extends ThingHandlerService>> getServices() {
627         return List.of(LightActions.class);
628     }
629
630     @Override
631     public String getLightId() {
632         return lightId;
633     }
634 }