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