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