2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.lifx.internal.handler;
15 import static org.openhab.binding.lifx.internal.LifxBindingConstants.*;
16 import static org.openhab.binding.lifx.internal.LifxProduct.Feature.*;
17 import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.*;
19 import java.net.InetSocketAddress;
20 import java.time.Duration;
21 import java.time.LocalDateTime;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.concurrent.locks.ReentrantLock;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.lifx.internal.LifxBindingConstants;
32 import org.openhab.binding.lifx.internal.LifxChannelFactory;
33 import org.openhab.binding.lifx.internal.LifxLightCommunicationHandler;
34 import org.openhab.binding.lifx.internal.LifxLightConfig;
35 import org.openhab.binding.lifx.internal.LifxLightContext;
36 import org.openhab.binding.lifx.internal.LifxLightCurrentStateUpdater;
37 import org.openhab.binding.lifx.internal.LifxLightOnlineStateUpdater;
38 import org.openhab.binding.lifx.internal.LifxLightPropertiesUpdater;
39 import org.openhab.binding.lifx.internal.LifxLightState;
40 import org.openhab.binding.lifx.internal.LifxLightStateChanger;
41 import org.openhab.binding.lifx.internal.LifxProduct;
42 import org.openhab.binding.lifx.internal.dto.Effect;
43 import org.openhab.binding.lifx.internal.dto.GetLightInfraredRequest;
44 import org.openhab.binding.lifx.internal.dto.GetLightPowerRequest;
45 import org.openhab.binding.lifx.internal.dto.GetRequest;
46 import org.openhab.binding.lifx.internal.dto.GetTileEffectRequest;
47 import org.openhab.binding.lifx.internal.dto.GetWifiInfoRequest;
48 import org.openhab.binding.lifx.internal.dto.Packet;
49 import org.openhab.binding.lifx.internal.dto.PowerState;
50 import org.openhab.binding.lifx.internal.dto.SignalStrength;
51 import org.openhab.binding.lifx.internal.fields.HSBK;
52 import org.openhab.binding.lifx.internal.fields.MACAddress;
53 import org.openhab.core.config.core.Configuration;
54 import org.openhab.core.library.types.DecimalType;
55 import org.openhab.core.library.types.HSBType;
56 import org.openhab.core.library.types.IncreaseDecreaseType;
57 import org.openhab.core.library.types.OnOffType;
58 import org.openhab.core.library.types.PercentType;
59 import org.openhab.core.library.types.StringType;
60 import org.openhab.core.thing.Channel;
61 import org.openhab.core.thing.ChannelUID;
62 import org.openhab.core.thing.Thing;
63 import org.openhab.core.thing.ThingStatus;
64 import org.openhab.core.thing.ThingStatusDetail;
65 import org.openhab.core.thing.ThingStatusInfo;
66 import org.openhab.core.thing.binding.BaseThingHandler;
67 import org.openhab.core.types.Command;
68 import org.openhab.core.types.RefreshType;
69 import org.openhab.core.types.State;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 * The {@link LifxLightHandler} is responsible for handling commands, which are
75 * sent to one of the light channels.
77 * @author Dennis Nobel - Initial contribution
78 * @author Stefan Bußweiler - Added new thing status handling
79 * @author Karel Goderis - Rewrite for Firmware V2, and remove dependency on external libraries
80 * @author Kai Kreuzer - Added configurable transition time and small fixes
81 * @author Wouter Born - Decomposed class into separate objects
82 * @author Pauli Anttila - Added power on temperature and color features.
85 public class LifxLightHandler extends BaseThingHandler {
87 private final Logger logger = LoggerFactory.getLogger(LifxLightHandler.class);
89 private static final Duration MIN_STATUS_INFO_UPDATE_INTERVAL = Duration.ofSeconds(1);
90 private static final Duration MAX_STATE_CHANGE_DURATION = Duration.ofSeconds(4);
92 private final LifxChannelFactory channelFactory;
93 private @NonNullByDefault({}) LifxProduct product;
95 private @Nullable PercentType powerOnBrightness;
96 private @Nullable HSBType powerOnColor;
97 private @Nullable PercentType powerOnTemperature;
98 private Double effectMorphSpeed = 3.0;
99 private Double effectFlameSpeed = 4.0;
101 private @NonNullByDefault({}) String logId;
103 private final ReentrantLock lock = new ReentrantLock();
105 private @NonNullByDefault({}) CurrentLightState currentLightState;
106 private @NonNullByDefault({}) LifxLightState pendingLightState;
108 private Map<String, @Nullable State> channelStates = new HashMap<>();
109 private @Nullable ThingStatusInfo statusInfo;
110 private LocalDateTime lastStatusInfoUpdate = LocalDateTime.MIN;
112 private @NonNullByDefault({}) LifxLightCommunicationHandler communicationHandler;
113 private @NonNullByDefault({}) LifxLightCurrentStateUpdater currentStateUpdater;
114 private @NonNullByDefault({}) LifxLightStateChanger lightStateChanger;
115 private @NonNullByDefault({}) LifxLightOnlineStateUpdater onlineStateUpdater;
116 private @NonNullByDefault({}) LifxLightPropertiesUpdater propertiesUpdater;
118 public class CurrentLightState extends LifxLightState {
120 public boolean isOnline() {
121 return thing.getStatus() == ThingStatus.ONLINE;
124 public boolean isOffline() {
125 return thing.getStatus() == ThingStatus.OFFLINE;
128 public void setOnline() {
129 updateStatusIfChanged(ThingStatus.ONLINE);
132 public void setOnline(MACAddress macAddress) {
133 updateStatusIfChanged(ThingStatus.ONLINE);
134 Configuration configuration = editConfiguration();
135 configuration.put(LifxBindingConstants.CONFIG_PROPERTY_DEVICE_ID, macAddress.getAsLabel());
136 updateConfiguration(configuration);
139 public void setOffline() {
140 updateStatusIfChanged(ThingStatus.OFFLINE);
143 public void setOfflineByCommunicationError() {
144 updateStatusIfChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
148 public void setColors(HSBK[] colors) {
149 if (!isStateChangePending() || isPendingColorStateChangesApplied(getPowerState(), colors)) {
150 PowerState powerState = isStateChangePending() ? pendingLightState.getPowerState() : getPowerState();
151 updateColorChannels(powerState, colors);
153 super.setColors(colors);
157 public void setPowerState(PowerState powerState) {
158 if (!isStateChangePending() || isPendingColorStateChangesApplied(powerState, getColors())) {
159 HSBK[] colors = isStateChangePending() ? pendingLightState.getColors() : getColors();
160 updateColorChannels(powerState, colors);
162 super.setPowerState(powerState);
165 private boolean isPendingColorStateChangesApplied(@Nullable PowerState powerState, HSBK[] colors) {
166 return powerState != null && powerState.equals(pendingLightState.getPowerState())
167 && Arrays.equals(colors, pendingLightState.getColors());
170 private void updateColorChannels(@Nullable PowerState powerState, HSBK[] colors) {
171 HSBK color = colors.length > 0 ? colors[0] : null;
172 HSBK updateColor = nullSafeUpdateColor(powerState, color);
173 HSBType hsb = updateColor.getHSB();
175 updateStateIfChanged(CHANNEL_COLOR, hsb);
176 updateStateIfChanged(CHANNEL_BRIGHTNESS, hsb.getBrightness());
177 updateStateIfChanged(CHANNEL_TEMPERATURE,
178 kelvinToPercentType(updateColor.getKelvin(), product.getTemperatureRange()));
180 updateZoneChannels(powerState, colors);
183 private HSBK nullSafeUpdateColor(@Nullable PowerState powerState, @Nullable HSBK color) {
184 HSBK updateColor = color != null ? color : DEFAULT_COLOR;
185 if (powerState == PowerState.OFF) {
186 updateColor = new HSBK(updateColor);
187 updateColor.setBrightness(PercentType.ZERO);
193 public void setInfrared(PercentType infrared) {
194 if (!isStateChangePending() || infrared.equals(pendingLightState.getInfrared())) {
195 updateStateIfChanged(CHANNEL_INFRARED, infrared);
197 super.setInfrared(infrared);
201 public void setSignalStrength(SignalStrength signalStrength) {
202 updateStateIfChanged(CHANNEL_SIGNAL_STRENGTH, new DecimalType(signalStrength.toQualityRating()));
203 super.setSignalStrength(signalStrength);
207 public void setTileEffect(Effect effect) {
208 updateStateIfChanged(CHANNEL_EFFECT, new StringType(effect.getType().stringValue()));
209 super.setTileEffect(effect);
212 private void updateZoneChannels(@Nullable PowerState powerState, HSBK[] colors) {
213 if (!product.hasFeature(MULTIZONE) || colors.length == 0) {
217 int oldZones = getColors().length;
218 int newZones = colors.length;
219 if (oldZones != newZones) {
220 addRemoveZoneChannels(newZones);
223 for (int i = 0; i < colors.length; i++) {
224 HSBK color = colors[i];
225 HSBK updateColor = nullSafeUpdateColor(powerState, color);
226 updateStateIfChanged(CHANNEL_COLOR_ZONE + i, updateColor.getHSB());
227 updateStateIfChanged(CHANNEL_TEMPERATURE_ZONE + i,
228 kelvinToPercentType(updateColor.getKelvin(), product.getTemperatureRange()));
233 public LifxLightHandler(Thing thing, LifxChannelFactory channelFactory) {
235 this.channelFactory = channelFactory;
239 public void initialize() {
243 LifxLightConfig configuration = getConfigAs(LifxLightConfig.class);
245 logId = getLogId(configuration.getMACAddress(), configuration.getHost());
246 product = getProduct();
248 logger.debug("{} : Initializing handler for product {}", logId, product.getName());
250 powerOnBrightness = getPowerOnBrightness();
251 powerOnColor = getPowerOnColor();
252 powerOnTemperature = getPowerOnTemperature();
253 Double speed = getEffectSpeed(LifxBindingConstants.CONFIG_PROPERTY_EFFECT_MORPH_SPEED);
255 effectMorphSpeed = speed;
257 speed = getEffectSpeed(LifxBindingConstants.CONFIG_PROPERTY_EFFECT_FLAME_SPEED);
259 effectFlameSpeed = speed;
261 channelStates.clear();
262 currentLightState = new CurrentLightState();
263 pendingLightState = new LifxLightState();
265 LifxLightContext context = new LifxLightContext(logId, product, configuration, currentLightState,
266 pendingLightState, scheduler);
268 communicationHandler = new LifxLightCommunicationHandler(context);
269 currentStateUpdater = new LifxLightCurrentStateUpdater(context, communicationHandler);
270 onlineStateUpdater = new LifxLightOnlineStateUpdater(context, communicationHandler);
271 propertiesUpdater = new LifxLightPropertiesUpdater(context, communicationHandler);
272 propertiesUpdater.addPropertiesUpdateListener(this::updateProperties);
273 lightStateChanger = new LifxLightStateChanger(context, communicationHandler);
275 if (configuration.getMACAddress() != null || configuration.getHost() != null) {
276 communicationHandler.start();
277 currentStateUpdater.start();
278 onlineStateUpdater.start();
279 propertiesUpdater.start();
280 lightStateChanger.start();
281 startOrStopSignalStrengthUpdates();
283 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
284 "Configure a Device ID or Host");
286 } catch (Exception e) {
287 logger.debug("{} : Error occurred while initializing handler: {}", logId, e.getMessage(), e);
288 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
295 public void dispose() {
299 logger.debug("{} : Disposing handler", logId);
301 if (communicationHandler != null) {
302 communicationHandler.stop();
303 communicationHandler = null;
306 if (currentStateUpdater != null) {
307 currentStateUpdater.stop();
308 currentStateUpdater = null;
311 if (onlineStateUpdater != null) {
312 onlineStateUpdater.stop();
313 onlineStateUpdater = null;
316 if (propertiesUpdater != null) {
317 propertiesUpdater.stop();
318 propertiesUpdater.removePropertiesUpdateListener(this::updateProperties);
319 propertiesUpdater = null;
322 if (lightStateChanger != null) {
323 lightStateChanger.stop();
324 lightStateChanger = null;
327 currentLightState = null;
328 pendingLightState = null;
334 public String getLogId(@Nullable MACAddress macAddress, @Nullable InetSocketAddress host) {
335 return (macAddress != null ? macAddress.getHex() : (host != null ? host.getHostString() : "Unknown"));
338 private @Nullable PercentType getPowerOnBrightness() {
339 Channel channel = null;
341 if (product.hasFeature(COLOR)) {
342 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
343 channel = getThing().getChannel(channelUID.getId());
345 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_BRIGHTNESS);
346 channel = getThing().getChannel(channelUID.getId());
349 if (channel == null) {
353 Configuration configuration = channel.getConfiguration();
354 Object powerOnBrightness = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_BRIGHTNESS);
355 return powerOnBrightness == null ? null : new PercentType(powerOnBrightness.toString());
358 private @Nullable HSBType getPowerOnColor() {
359 Channel channel = null;
361 if (product.hasFeature(COLOR)) {
362 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
363 channel = getThing().getChannel(channelUID.getId());
366 if (channel == null) {
370 Configuration configuration = channel.getConfiguration();
371 Object powerOnColor = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_COLOR);
372 return powerOnColor == null ? null : new HSBType(powerOnColor.toString());
375 private @Nullable PercentType getPowerOnTemperature() {
376 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_TEMPERATURE);
377 Channel channel = getThing().getChannel(channelUID.getId());
379 if (channel == null) {
383 Configuration configuration = channel.getConfiguration();
384 Object powerOnTemperature = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_TEMPERATURE);
385 if (powerOnTemperature != null) {
386 return new PercentType(powerOnTemperature.toString());
391 private @Nullable Double getEffectSpeed(String parameter) {
392 Channel channel = null;
394 if (product.hasFeature(TILE_EFFECT)) {
395 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_EFFECT);
396 channel = getThing().getChannel(channelUID.getId());
399 if (channel == null) {
403 Configuration configuration = channel.getConfiguration();
404 Object speed = configuration.get(parameter);
405 return speed == null ? null : new Double(speed.toString());
408 private LifxProduct getProduct() {
409 Object propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_PRODUCT_ID);
410 if (propertyValue == null) {
411 return LifxProduct.getLikelyProduct(getThing().getThingTypeUID());
414 // Without first conversion to double, on a very first thing creation from discovery inbox,
415 // the product type is incorrectly parsed, as framework passed it as a floating point number
416 // (e.g. 50.0 instead of 50)
417 Double d = Double.parseDouble((String) propertyValue);
418 long productID = d.longValue();
419 return LifxProduct.getProductFromProductID(productID);
420 } catch (IllegalArgumentException e) {
421 return LifxProduct.getLikelyProduct(getThing().getThingTypeUID());
425 private void addRemoveZoneChannels(int zones) {
426 List<Channel> newChannels = new ArrayList<>();
428 // retain non-zone channels
429 for (Channel channel : getThing().getChannels()) {
430 String channelId = channel.getUID().getId();
431 if (!channelId.startsWith(CHANNEL_COLOR_ZONE) && !channelId.startsWith(CHANNEL_TEMPERATURE_ZONE)) {
432 newChannels.add(channel);
437 for (int i = 0; i < zones; i++) {
438 newChannels.add(channelFactory.createColorZoneChannel(getThing().getUID(), i));
439 newChannels.add(channelFactory.createTemperatureZoneChannel(getThing().getUID(), i));
442 updateThing(editThing().withChannels(newChannels).build());
444 Map<String, String> properties = editProperties();
445 properties.put(LifxBindingConstants.PROPERTY_ZONES, Integer.toString(zones));
446 updateProperties(properties);
450 public void channelLinked(ChannelUID channelUID) {
451 super.channelLinked(channelUID);
452 startOrStopSignalStrengthUpdates();
456 public void channelUnlinked(ChannelUID channelUID) {
457 startOrStopSignalStrengthUpdates();
460 private void startOrStopSignalStrengthUpdates() {
461 currentStateUpdater.setUpdateSignalStrength(isLinked(CHANNEL_SIGNAL_STRENGTH));
464 private void sendPacket(Packet packet) {
465 communicationHandler.sendPacket(packet);
469 public void handleCommand(ChannelUID channelUID, Command command) {
470 if (command instanceof RefreshType) {
471 channelStates.remove(channelUID.getId());
472 switch (channelUID.getId()) {
474 case CHANNEL_BRIGHTNESS:
475 sendPacket(new GetLightPowerRequest());
476 sendPacket(new GetRequest());
478 case CHANNEL_TEMPERATURE:
479 sendPacket(new GetRequest());
481 case CHANNEL_INFRARED:
482 sendPacket(new GetLightInfraredRequest());
484 case CHANNEL_SIGNAL_STRENGTH:
485 sendPacket(new GetWifiInfoRequest());
488 if (product.hasFeature(TILE_EFFECT)) {
489 sendPacket(new GetTileEffectRequest());
496 boolean supportedCommand = true;
497 switch (channelUID.getId()) {
499 if (command instanceof HSBType) {
500 handleHSBCommand((HSBType) command);
501 } else if (command instanceof PercentType) {
502 handlePercentCommand((PercentType) command);
503 } else if (command instanceof OnOffType) {
504 handleOnOffCommand((OnOffType) command);
505 } else if (command instanceof IncreaseDecreaseType) {
506 handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
508 supportedCommand = false;
511 case CHANNEL_BRIGHTNESS:
512 if (command instanceof PercentType) {
513 handlePercentCommand((PercentType) command);
514 } else if (command instanceof OnOffType) {
515 handleOnOffCommand((OnOffType) command);
516 } else if (command instanceof IncreaseDecreaseType) {
517 handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
519 supportedCommand = false;
522 case CHANNEL_TEMPERATURE:
523 if (command instanceof PercentType) {
524 handleTemperatureCommand((PercentType) command);
525 } else if (command instanceof IncreaseDecreaseType) {
526 handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command);
528 supportedCommand = false;
531 case CHANNEL_INFRARED:
532 if (command instanceof PercentType) {
533 handleInfraredCommand((PercentType) command);
534 } else if (command instanceof IncreaseDecreaseType) {
535 handleIncreaseDecreaseInfraredCommand((IncreaseDecreaseType) command);
537 supportedCommand = false;
541 if (command instanceof StringType && product.hasFeature(TILE_EFFECT)) {
542 handleTileEffectCommand((StringType) command);
544 supportedCommand = false;
549 if (channelUID.getId().startsWith(CHANNEL_COLOR_ZONE)) {
550 int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_COLOR_ZONE, ""));
551 if (command instanceof HSBType) {
552 handleHSBCommand((HSBType) command, zoneIndex);
553 } else if (command instanceof PercentType) {
554 handlePercentCommand((PercentType) command, zoneIndex);
555 } else if (command instanceof IncreaseDecreaseType) {
556 handleIncreaseDecreaseCommand((IncreaseDecreaseType) command, zoneIndex);
558 supportedCommand = false;
560 } else if (channelUID.getId().startsWith(CHANNEL_TEMPERATURE_ZONE)) {
561 int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_TEMPERATURE_ZONE, ""));
562 if (command instanceof PercentType) {
563 handleTemperatureCommand((PercentType) command, zoneIndex);
564 } else if (command instanceof IncreaseDecreaseType) {
565 handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command, zoneIndex);
567 supportedCommand = false;
570 supportedCommand = false;
572 } catch (NumberFormatException e) {
573 logger.error("Failed to parse zone index for a command of a light ({}) : {}", logId,
575 supportedCommand = false;
580 if (supportedCommand && !(command instanceof OnOffType) && !CHANNEL_INFRARED.equals(channelUID.getId())) {
581 getLightStateForCommand().setPowerState(PowerState.ON);
586 private LifxLightState getLightStateForCommand() {
587 if (!isStateChangePending()) {
588 pendingLightState.copy(currentLightState);
590 return pendingLightState;
593 private boolean isStateChangePending() {
594 return pendingLightState.getDurationSinceLastChange().minus(MAX_STATE_CHANGE_DURATION).isNegative();
597 private void handleTemperatureCommand(PercentType temperature) {
598 HSBK newColor = getLightStateForCommand().getColor();
599 newColor.setSaturation(PercentType.ZERO);
600 newColor.setKelvin(percentTypeToKelvin(temperature, product.getTemperatureRange()));
601 getLightStateForCommand().setColor(newColor);
604 private void handleTemperatureCommand(PercentType temperature, int zoneIndex) {
605 HSBK newColor = getLightStateForCommand().getColor(zoneIndex);
606 newColor.setSaturation(PercentType.ZERO);
607 newColor.setKelvin(percentTypeToKelvin(temperature, product.getTemperatureRange()));
608 getLightStateForCommand().setColor(newColor, zoneIndex);
611 private void handleHSBCommand(HSBType hsb) {
612 getLightStateForCommand().setColor(hsb);
615 private void handleHSBCommand(HSBType hsb, int zoneIndex) {
616 getLightStateForCommand().setColor(hsb, zoneIndex);
619 private void handlePercentCommand(PercentType brightness) {
620 getLightStateForCommand().setBrightness(brightness);
623 private void handlePercentCommand(PercentType brightness, int zoneIndex) {
624 getLightStateForCommand().setBrightness(brightness, zoneIndex);
627 private void handleOnOffCommand(OnOffType onOff) {
628 HSBType localPowerOnColor = powerOnColor;
629 if (localPowerOnColor != null && onOff == OnOffType.ON) {
630 getLightStateForCommand().setColor(localPowerOnColor);
633 PercentType localPowerOnTemperature = powerOnTemperature;
634 if (localPowerOnTemperature != null && onOff == OnOffType.ON) {
635 getLightStateForCommand()
636 .setTemperature(percentTypeToKelvin(localPowerOnTemperature, product.getTemperatureRange()));
639 PercentType powerOnBrightness = this.powerOnBrightness;
640 if (powerOnBrightness != null) {
641 PercentType newBrightness = onOff == OnOffType.ON ? powerOnBrightness : new PercentType(0);
642 getLightStateForCommand().setBrightness(newBrightness);
644 getLightStateForCommand().setPowerState(onOff);
647 private void handleIncreaseDecreaseCommand(IncreaseDecreaseType increaseDecrease) {
648 HSBK baseColor = getLightStateForCommand().getColor();
649 PercentType newBrightness = increaseDecreasePercentType(increaseDecrease, baseColor.getHSB().getBrightness());
650 handlePercentCommand(newBrightness);
653 private void handleIncreaseDecreaseCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
654 HSBK baseColor = getLightStateForCommand().getColor(zoneIndex);
655 PercentType newBrightness = increaseDecreasePercentType(increaseDecrease, baseColor.getHSB().getBrightness());
656 handlePercentCommand(newBrightness, zoneIndex);
659 private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
660 PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor().getKelvin(),
661 product.getTemperatureRange());
662 PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
663 handleTemperatureCommand(newTemperature);
666 private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
667 PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor(zoneIndex).getKelvin(),
668 product.getTemperatureRange());
669 PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
670 handleTemperatureCommand(newTemperature, zoneIndex);
673 private void handleInfraredCommand(PercentType infrared) {
674 getLightStateForCommand().setInfrared(infrared);
677 private void handleIncreaseDecreaseInfraredCommand(IncreaseDecreaseType increaseDecrease) {
678 PercentType baseInfrared = getLightStateForCommand().getInfrared();
679 if (baseInfrared != null) {
680 PercentType newInfrared = increaseDecreasePercentType(increaseDecrease, baseInfrared);
681 handleInfraredCommand(newInfrared);
685 private void handleTileEffectCommand(StringType type) {
686 logger.debug("handleTileEffectCommand mode={}", type);
687 Double morphSpeedInMSecs = effectMorphSpeed * 1000.0;
688 Double flameSpeedInMSecs = effectFlameSpeed * 1000.0;
690 Effect effect = Effect.createDefault(type.toString(), morphSpeedInMSecs.longValue(),
691 flameSpeedInMSecs.longValue());
692 getLightStateForCommand().setTileEffect(effect);
693 } catch (IllegalArgumentException e) {
694 logger.debug("Wrong effect type received as command: {}", type);
698 private void updateStateIfChanged(String channel, State newState) {
699 State oldState = channelStates.get(channel);
700 if (oldState == null || !oldState.equals(newState)) {
701 updateState(channel, newState);
702 channelStates.put(channel, newState);
706 private void updateStatusIfChanged(ThingStatus status) {
707 updateStatusIfChanged(status, ThingStatusDetail.NONE);
710 private void updateStatusIfChanged(ThingStatus status, ThingStatusDetail statusDetail) {
711 ThingStatusInfo newStatusInfo = new ThingStatusInfo(status, statusDetail, null);
712 Duration durationSinceLastUpdate = Duration.between(lastStatusInfoUpdate, LocalDateTime.now());
713 boolean intervalElapsed = MIN_STATUS_INFO_UPDATE_INTERVAL.minus(durationSinceLastUpdate).isNegative();
715 ThingStatusInfo oldStatusInfo = statusInfo;
716 if (oldStatusInfo == null || !oldStatusInfo.equals(newStatusInfo) || intervalElapsed) {
717 statusInfo = newStatusInfo;
718 lastStatusInfoUpdate = LocalDateTime.now();
719 updateStatus(status, statusDetail);