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.Objects;
28 import java.util.concurrent.locks.ReentrantLock;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.lifx.internal.LifxBindingConstants;
33 import org.openhab.binding.lifx.internal.LifxChannelFactory;
34 import org.openhab.binding.lifx.internal.LifxLightCommunicationHandler;
35 import org.openhab.binding.lifx.internal.LifxLightConfig;
36 import org.openhab.binding.lifx.internal.LifxLightContext;
37 import org.openhab.binding.lifx.internal.LifxLightCurrentStateUpdater;
38 import org.openhab.binding.lifx.internal.LifxLightOnlineStateUpdater;
39 import org.openhab.binding.lifx.internal.LifxLightPropertiesUpdater;
40 import org.openhab.binding.lifx.internal.LifxLightState;
41 import org.openhab.binding.lifx.internal.LifxLightStateChanger;
42 import org.openhab.binding.lifx.internal.LifxProduct;
43 import org.openhab.binding.lifx.internal.LifxProduct.Features;
44 import org.openhab.binding.lifx.internal.dto.Effect;
45 import org.openhab.binding.lifx.internal.dto.GetLightInfraredRequest;
46 import org.openhab.binding.lifx.internal.dto.GetLightPowerRequest;
47 import org.openhab.binding.lifx.internal.dto.GetRequest;
48 import org.openhab.binding.lifx.internal.dto.GetTileEffectRequest;
49 import org.openhab.binding.lifx.internal.dto.GetWifiInfoRequest;
50 import org.openhab.binding.lifx.internal.dto.Packet;
51 import org.openhab.binding.lifx.internal.dto.PowerState;
52 import org.openhab.binding.lifx.internal.dto.SignalStrength;
53 import org.openhab.binding.lifx.internal.fields.HSBK;
54 import org.openhab.binding.lifx.internal.fields.MACAddress;
55 import org.openhab.core.config.core.Configuration;
56 import org.openhab.core.library.types.DecimalType;
57 import org.openhab.core.library.types.HSBType;
58 import org.openhab.core.library.types.IncreaseDecreaseType;
59 import org.openhab.core.library.types.OnOffType;
60 import org.openhab.core.library.types.PercentType;
61 import org.openhab.core.library.types.StringType;
62 import org.openhab.core.thing.Channel;
63 import org.openhab.core.thing.ChannelUID;
64 import org.openhab.core.thing.Thing;
65 import org.openhab.core.thing.ThingStatus;
66 import org.openhab.core.thing.ThingStatusDetail;
67 import org.openhab.core.thing.ThingStatusInfo;
68 import org.openhab.core.thing.binding.BaseThingHandler;
69 import org.openhab.core.types.Command;
70 import org.openhab.core.types.RefreshType;
71 import org.openhab.core.types.State;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
76 * The {@link LifxLightHandler} is responsible for handling commands, which are
77 * sent to one of the light channels.
79 * @author Dennis Nobel - Initial contribution
80 * @author Stefan Bußweiler - Added new thing status handling
81 * @author Karel Goderis - Rewrite for Firmware V2, and remove dependency on external libraries
82 * @author Kai Kreuzer - Added configurable transition time and small fixes
83 * @author Wouter Born - Decomposed class into separate objects
84 * @author Pauli Anttila - Added power on temperature and color features.
87 public class LifxLightHandler extends BaseThingHandler {
89 private final Logger logger = LoggerFactory.getLogger(LifxLightHandler.class);
91 private static final Duration MIN_STATUS_INFO_UPDATE_INTERVAL = Duration.ofSeconds(1);
92 private static final Duration MAX_STATE_CHANGE_DURATION = Duration.ofSeconds(4);
94 private final LifxChannelFactory channelFactory;
95 private @NonNullByDefault({}) Features features;
97 private @Nullable PercentType powerOnBrightness;
98 private @Nullable HSBType powerOnColor;
99 private @Nullable PercentType powerOnTemperature;
100 private Double effectMorphSpeed = 3.0;
101 private Double effectFlameSpeed = 4.0;
103 private @NonNullByDefault({}) String logId;
105 private final ReentrantLock lock = new ReentrantLock();
107 private @NonNullByDefault({}) CurrentLightState currentLightState;
108 private @NonNullByDefault({}) LifxLightState pendingLightState;
110 private Map<String, @Nullable State> channelStates = new HashMap<>();
111 private @Nullable ThingStatusInfo statusInfo;
112 private LocalDateTime lastStatusInfoUpdate = LocalDateTime.MIN;
114 private @NonNullByDefault({}) LifxLightCommunicationHandler communicationHandler;
115 private @NonNullByDefault({}) LifxLightCurrentStateUpdater currentStateUpdater;
116 private @NonNullByDefault({}) LifxLightStateChanger lightStateChanger;
117 private @NonNullByDefault({}) LifxLightOnlineStateUpdater onlineStateUpdater;
118 private @NonNullByDefault({}) LifxLightPropertiesUpdater propertiesUpdater;
120 public class CurrentLightState extends LifxLightState {
122 public boolean isOnline() {
123 return thing.getStatus() == ThingStatus.ONLINE;
126 public boolean isOffline() {
127 return thing.getStatus() == ThingStatus.OFFLINE;
130 public void setOnline() {
131 updateStatusIfChanged(ThingStatus.ONLINE);
134 public void setOnline(MACAddress macAddress) {
135 updateStatusIfChanged(ThingStatus.ONLINE);
136 Configuration configuration = editConfiguration();
137 configuration.put(LifxBindingConstants.CONFIG_PROPERTY_DEVICE_ID, macAddress.getAsLabel());
138 updateConfiguration(configuration);
141 public void setOffline() {
142 updateStatusIfChanged(ThingStatus.OFFLINE);
145 public void setOfflineByCommunicationError() {
146 updateStatusIfChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
150 public void setColors(HSBK[] colors) {
151 if (!isStateChangePending() || isPendingColorStateChangesApplied(getPowerState(), colors)) {
152 PowerState powerState = isStateChangePending() ? pendingLightState.getPowerState() : getPowerState();
153 updateColorChannels(powerState, colors);
155 super.setColors(colors);
159 public void setPowerState(PowerState powerState) {
160 if (!isStateChangePending() || isPendingColorStateChangesApplied(powerState, getColors())) {
161 HSBK[] colors = isStateChangePending() ? pendingLightState.getColors() : getColors();
162 updateColorChannels(powerState, colors);
164 super.setPowerState(powerState);
167 private boolean isPendingColorStateChangesApplied(@Nullable PowerState powerState, HSBK[] colors) {
168 return powerState != null && powerState.equals(pendingLightState.getPowerState())
169 && Arrays.equals(colors, pendingLightState.getColors());
172 private void updateColorChannels(@Nullable PowerState powerState, HSBK[] colors) {
173 HSBK color = colors.length > 0 ? colors[0] : null;
174 HSBK updateColor = nullSafeUpdateColor(powerState, color);
175 HSBType hsb = updateColor.getHSB();
177 updateStateIfChanged(CHANNEL_COLOR, hsb);
178 updateStateIfChanged(CHANNEL_BRIGHTNESS, hsb.getBrightness());
179 updateStateIfChanged(CHANNEL_TEMPERATURE,
180 kelvinToPercentType(updateColor.getKelvin(), features.getTemperatureRange()));
182 updateZoneChannels(powerState, colors);
185 private HSBK nullSafeUpdateColor(@Nullable PowerState powerState, @Nullable HSBK color) {
186 HSBK updateColor = color != null ? color : DEFAULT_COLOR;
187 if (powerState == PowerState.OFF) {
188 updateColor = new HSBK(updateColor);
189 updateColor.setBrightness(PercentType.ZERO);
195 public void setInfrared(PercentType infrared) {
196 if (!isStateChangePending() || infrared.equals(pendingLightState.getInfrared())) {
197 updateStateIfChanged(CHANNEL_INFRARED, infrared);
199 super.setInfrared(infrared);
203 public void setSignalStrength(SignalStrength signalStrength) {
204 updateStateIfChanged(CHANNEL_SIGNAL_STRENGTH, new DecimalType(signalStrength.toQualityRating()));
205 super.setSignalStrength(signalStrength);
209 public void setTileEffect(Effect effect) {
210 updateStateIfChanged(CHANNEL_EFFECT, new StringType(effect.getType().stringValue()));
211 super.setTileEffect(effect);
214 private void updateZoneChannels(@Nullable PowerState powerState, HSBK[] colors) {
215 if (!features.hasFeature(MULTIZONE) || colors.length == 0) {
219 int oldZones = getColors().length;
220 int newZones = colors.length;
221 if (oldZones != newZones) {
222 addRemoveZoneChannels(newZones);
225 for (int i = 0; i < colors.length; i++) {
226 HSBK color = colors[i];
227 HSBK updateColor = nullSafeUpdateColor(powerState, color);
228 updateStateIfChanged(CHANNEL_COLOR_ZONE + i, updateColor.getHSB());
229 updateStateIfChanged(CHANNEL_TEMPERATURE_ZONE + i,
230 kelvinToPercentType(updateColor.getKelvin(), features.getTemperatureRange()));
235 public LifxLightHandler(Thing thing, LifxChannelFactory channelFactory) {
237 this.channelFactory = channelFactory;
241 public void initialize() {
245 LifxLightConfig configuration = getConfigAs(LifxLightConfig.class);
247 logId = getLogId(configuration.getMACAddress(), configuration.getHost());
249 if (logger.isDebugEnabled()) {
250 logger.debug("{} : Initializing handler for product {}", logId, getProduct().getName());
253 features = getFeatures();
255 powerOnBrightness = getPowerOnBrightness();
256 powerOnColor = getPowerOnColor();
257 powerOnTemperature = getPowerOnTemperature();
258 Double speed = getEffectSpeed(LifxBindingConstants.CONFIG_PROPERTY_EFFECT_MORPH_SPEED);
260 effectMorphSpeed = speed;
262 speed = getEffectSpeed(LifxBindingConstants.CONFIG_PROPERTY_EFFECT_FLAME_SPEED);
264 effectFlameSpeed = speed;
266 channelStates.clear();
267 currentLightState = new CurrentLightState();
268 pendingLightState = new LifxLightState();
270 LifxLightContext context = new LifxLightContext(logId, features, configuration, currentLightState,
271 pendingLightState, scheduler);
273 communicationHandler = new LifxLightCommunicationHandler(context);
274 currentStateUpdater = new LifxLightCurrentStateUpdater(context, communicationHandler);
275 onlineStateUpdater = new LifxLightOnlineStateUpdater(context, communicationHandler);
276 propertiesUpdater = new LifxLightPropertiesUpdater(context, communicationHandler);
277 propertiesUpdater.addPropertiesUpdateListener(this::updateProperties);
278 lightStateChanger = new LifxLightStateChanger(context, communicationHandler);
280 if (configuration.getMACAddress() != null || configuration.getHost() != null) {
281 communicationHandler.start();
282 currentStateUpdater.start();
283 onlineStateUpdater.start();
284 propertiesUpdater.start();
285 lightStateChanger.start();
286 startOrStopSignalStrengthUpdates();
288 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
289 "Configure a Device ID or Host");
291 } catch (Exception e) {
292 logger.debug("{} : Error occurred while initializing handler: {}", logId, e.getMessage(), e);
293 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
300 public void dispose() {
304 logger.debug("{} : Disposing handler", logId);
306 if (communicationHandler != null) {
307 communicationHandler.stop();
308 communicationHandler = null;
311 if (currentStateUpdater != null) {
312 currentStateUpdater.stop();
313 currentStateUpdater = null;
316 if (onlineStateUpdater != null) {
317 onlineStateUpdater.stop();
318 onlineStateUpdater = null;
321 if (propertiesUpdater != null) {
322 propertiesUpdater.stop();
323 propertiesUpdater.removePropertiesUpdateListener(this::updateProperties);
324 propertiesUpdater = null;
327 if (lightStateChanger != null) {
328 lightStateChanger.stop();
329 lightStateChanger = null;
332 currentLightState = null;
333 pendingLightState = null;
339 public String getLogId(@Nullable MACAddress macAddress, @Nullable InetSocketAddress host) {
340 return (macAddress != null ? macAddress.getHex() : (host != null ? host.getHostString() : "Unknown"));
343 private @Nullable PercentType getPowerOnBrightness() {
344 Channel channel = null;
346 if (features.hasFeature(COLOR)) {
347 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
348 channel = getThing().getChannel(channelUID.getId());
350 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_BRIGHTNESS);
351 channel = getThing().getChannel(channelUID.getId());
354 if (channel == null) {
358 Configuration configuration = channel.getConfiguration();
359 Object powerOnBrightness = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_BRIGHTNESS);
360 return powerOnBrightness == null ? null : new PercentType(powerOnBrightness.toString());
363 private @Nullable HSBType getPowerOnColor() {
364 Channel channel = null;
366 if (features.hasFeature(COLOR)) {
367 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
368 channel = getThing().getChannel(channelUID.getId());
371 if (channel == null) {
375 Configuration configuration = channel.getConfiguration();
376 Object powerOnColor = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_COLOR);
377 return powerOnColor == null ? null : new HSBType(powerOnColor.toString());
380 private @Nullable PercentType getPowerOnTemperature() {
381 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_TEMPERATURE);
382 Channel channel = getThing().getChannel(channelUID.getId());
384 if (channel == null) {
388 Configuration configuration = channel.getConfiguration();
389 Object powerOnTemperature = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_TEMPERATURE);
390 if (powerOnTemperature != null) {
391 return new PercentType(powerOnTemperature.toString());
396 private @Nullable Double getEffectSpeed(String parameter) {
397 Channel channel = null;
399 if (features.hasFeature(TILE_EFFECT)) {
400 ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_EFFECT);
401 channel = getThing().getChannel(channelUID.getId());
404 if (channel == null) {
408 Configuration configuration = channel.getConfiguration();
409 Object speed = configuration.get(parameter);
410 return speed == null ? null : Double.valueOf(speed.toString());
413 private Features getFeatures() {
414 LifxProduct product = getProduct();
416 String propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_HOST_VERSION);
417 if (propertyValue == null) {
418 logger.debug("{} : Using features of initial firmware version", logId);
419 return product.getFeatures();
422 logger.debug("{} : Using features of firmware version {}", logId, propertyValue);
423 return product.getFeatures(propertyValue);
426 private LifxProduct getProduct() {
427 String propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_PRODUCT_ID);
428 if (propertyValue == null) {
429 return LifxProduct.getLikelyProduct(getThing().getThingTypeUID());
432 // Without first conversion to double, on a very first thing creation from discovery inbox,
433 // the product type is incorrectly parsed, as framework passed it as a floating point number
434 // (e.g. 50.0 instead of 50)
435 Double d = Double.valueOf(propertyValue);
436 long productID = d.longValue();
437 return LifxProduct.getProductFromProductID(productID);
438 } catch (IllegalArgumentException e) {
439 return LifxProduct.getLikelyProduct(getThing().getThingTypeUID());
443 private void addRemoveZoneChannels(int zones) {
444 List<Channel> newChannels = new ArrayList<>();
446 // retain non-zone channels
447 for (Channel channel : getThing().getChannels()) {
448 String channelId = channel.getUID().getId();
449 if (!channelId.startsWith(CHANNEL_COLOR_ZONE) && !channelId.startsWith(CHANNEL_TEMPERATURE_ZONE)) {
450 newChannels.add(channel);
455 for (int i = 0; i < zones; i++) {
456 newChannels.add(channelFactory.createColorZoneChannel(getThing().getUID(), i));
457 newChannels.add(channelFactory.createTemperatureZoneChannel(getThing().getUID(), i));
460 updateThing(editThing().withChannels(newChannels).build());
462 Map<String, String> properties = editProperties();
463 properties.put(LifxBindingConstants.PROPERTY_ZONES, Integer.toString(zones));
464 updateProperties(properties);
468 public void channelLinked(ChannelUID channelUID) {
469 super.channelLinked(channelUID);
470 startOrStopSignalStrengthUpdates();
474 public void channelUnlinked(ChannelUID channelUID) {
475 startOrStopSignalStrengthUpdates();
478 private void startOrStopSignalStrengthUpdates() {
479 currentStateUpdater.setUpdateSignalStrength(isLinked(CHANNEL_SIGNAL_STRENGTH));
482 private void sendPacket(Packet packet) {
483 communicationHandler.sendPacket(packet);
487 public void handleCommand(ChannelUID channelUID, Command command) {
488 if (command instanceof RefreshType) {
489 channelStates.remove(channelUID.getId());
490 switch (channelUID.getId()) {
492 case CHANNEL_BRIGHTNESS:
493 sendPacket(new GetLightPowerRequest());
494 sendPacket(new GetRequest());
496 case CHANNEL_TEMPERATURE:
497 sendPacket(new GetRequest());
499 case CHANNEL_INFRARED:
500 sendPacket(new GetLightInfraredRequest());
502 case CHANNEL_SIGNAL_STRENGTH:
503 sendPacket(new GetWifiInfoRequest());
506 if (features.hasFeature(TILE_EFFECT)) {
507 sendPacket(new GetTileEffectRequest());
514 boolean supportedCommand = true;
515 switch (channelUID.getId()) {
517 if (command instanceof HSBType) {
518 handleHSBCommand((HSBType) command);
519 } else if (command instanceof PercentType) {
520 handlePercentCommand((PercentType) command);
521 } else if (command instanceof OnOffType) {
522 handleOnOffCommand((OnOffType) command);
523 } else if (command instanceof IncreaseDecreaseType) {
524 handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
526 supportedCommand = false;
529 case CHANNEL_BRIGHTNESS:
530 if (command instanceof PercentType) {
531 handlePercentCommand((PercentType) command);
532 } else if (command instanceof OnOffType) {
533 handleOnOffCommand((OnOffType) command);
534 } else if (command instanceof IncreaseDecreaseType) {
535 handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
537 supportedCommand = false;
540 case CHANNEL_TEMPERATURE:
541 if (command instanceof PercentType) {
542 handleTemperatureCommand((PercentType) command);
543 } else if (command instanceof IncreaseDecreaseType) {
544 handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command);
546 supportedCommand = false;
549 case CHANNEL_INFRARED:
550 if (command instanceof PercentType) {
551 handleInfraredCommand((PercentType) command);
552 } else if (command instanceof IncreaseDecreaseType) {
553 handleIncreaseDecreaseInfraredCommand((IncreaseDecreaseType) command);
555 supportedCommand = false;
559 if (command instanceof StringType && features.hasFeature(TILE_EFFECT)) {
560 handleTileEffectCommand((StringType) command);
562 supportedCommand = false;
567 if (channelUID.getId().startsWith(CHANNEL_COLOR_ZONE)) {
568 int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_COLOR_ZONE, ""));
569 if (command instanceof HSBType) {
570 handleHSBCommand((HSBType) command, zoneIndex);
571 } else if (command instanceof PercentType) {
572 handlePercentCommand((PercentType) command, zoneIndex);
573 } else if (command instanceof IncreaseDecreaseType) {
574 handleIncreaseDecreaseCommand((IncreaseDecreaseType) command, zoneIndex);
576 supportedCommand = false;
578 } else if (channelUID.getId().startsWith(CHANNEL_TEMPERATURE_ZONE)) {
579 int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_TEMPERATURE_ZONE, ""));
580 if (command instanceof PercentType) {
581 handleTemperatureCommand((PercentType) command, zoneIndex);
582 } else if (command instanceof IncreaseDecreaseType) {
583 handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command, zoneIndex);
585 supportedCommand = false;
588 supportedCommand = false;
590 } catch (NumberFormatException e) {
591 logger.error("Failed to parse zone index for a command of a light ({}) : {}", logId,
593 supportedCommand = false;
598 if (supportedCommand && !(command instanceof OnOffType) && !CHANNEL_INFRARED.equals(channelUID.getId())) {
599 getLightStateForCommand().setPowerState(PowerState.ON);
604 private LifxLightState getLightStateForCommand() {
605 if (!isStateChangePending()) {
606 pendingLightState.copy(currentLightState);
608 return pendingLightState;
611 private boolean isStateChangePending() {
612 return pendingLightState.getDurationSinceLastChange().minus(MAX_STATE_CHANGE_DURATION).isNegative();
615 private void handleTemperatureCommand(PercentType temperature) {
616 HSBK newColor = getLightStateForCommand().getColor();
617 newColor.setSaturation(PercentType.ZERO);
618 newColor.setKelvin(percentTypeToKelvin(temperature, features.getTemperatureRange()));
619 getLightStateForCommand().setColor(newColor);
622 private void handleTemperatureCommand(PercentType temperature, int zoneIndex) {
623 HSBK newColor = getLightStateForCommand().getColor(zoneIndex);
624 newColor.setSaturation(PercentType.ZERO);
625 newColor.setKelvin(percentTypeToKelvin(temperature, features.getTemperatureRange()));
626 getLightStateForCommand().setColor(newColor, zoneIndex);
629 private void handleHSBCommand(HSBType hsb) {
630 getLightStateForCommand().setColor(hsb);
633 private void handleHSBCommand(HSBType hsb, int zoneIndex) {
634 getLightStateForCommand().setColor(hsb, zoneIndex);
637 private void handlePercentCommand(PercentType brightness) {
638 getLightStateForCommand().setBrightness(brightness);
641 private void handlePercentCommand(PercentType brightness, int zoneIndex) {
642 getLightStateForCommand().setBrightness(brightness, zoneIndex);
645 private void handleOnOffCommand(OnOffType onOff) {
646 HSBType localPowerOnColor = powerOnColor;
647 if (localPowerOnColor != null && onOff == OnOffType.ON) {
648 getLightStateForCommand().setColor(localPowerOnColor);
651 PercentType localPowerOnTemperature = powerOnTemperature;
652 if (localPowerOnTemperature != null && onOff == OnOffType.ON) {
653 getLightStateForCommand()
654 .setTemperature(percentTypeToKelvin(localPowerOnTemperature, features.getTemperatureRange()));
657 PercentType powerOnBrightness = this.powerOnBrightness;
658 if (powerOnBrightness != null) {
659 PercentType newBrightness = onOff == OnOffType.ON ? powerOnBrightness : new PercentType(0);
660 getLightStateForCommand().setBrightness(newBrightness);
662 getLightStateForCommand().setPowerState(onOff);
665 private void handleIncreaseDecreaseCommand(IncreaseDecreaseType increaseDecrease) {
666 HSBK baseColor = getLightStateForCommand().getColor();
667 PercentType newBrightness = increaseDecreasePercentType(increaseDecrease, baseColor.getHSB().getBrightness());
668 handlePercentCommand(newBrightness);
671 private void handleIncreaseDecreaseCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
672 HSBK baseColor = getLightStateForCommand().getColor(zoneIndex);
673 PercentType newBrightness = increaseDecreasePercentType(increaseDecrease, baseColor.getHSB().getBrightness());
674 handlePercentCommand(newBrightness, zoneIndex);
677 private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
678 PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor().getKelvin(),
679 features.getTemperatureRange());
680 PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
681 handleTemperatureCommand(newTemperature);
684 private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
685 PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor(zoneIndex).getKelvin(),
686 features.getTemperatureRange());
687 PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
688 handleTemperatureCommand(newTemperature, zoneIndex);
691 private void handleInfraredCommand(PercentType infrared) {
692 getLightStateForCommand().setInfrared(infrared);
695 private void handleIncreaseDecreaseInfraredCommand(IncreaseDecreaseType increaseDecrease) {
696 PercentType baseInfrared = getLightStateForCommand().getInfrared();
697 if (baseInfrared != null) {
698 PercentType newInfrared = increaseDecreasePercentType(increaseDecrease, baseInfrared);
699 handleInfraredCommand(newInfrared);
703 private void handleTileEffectCommand(StringType type) {
704 logger.debug("handleTileEffectCommand mode={}", type);
705 Double morphSpeedInMSecs = effectMorphSpeed * 1000.0;
706 Double flameSpeedInMSecs = effectFlameSpeed * 1000.0;
708 Effect effect = Effect.createDefault(type.toString(), morphSpeedInMSecs.longValue(),
709 flameSpeedInMSecs.longValue());
710 getLightStateForCommand().setTileEffect(effect);
711 } catch (IllegalArgumentException e) {
712 logger.debug("{} : Wrong effect type received as command: {}", logId, type);
717 protected void updateProperties(Map<String, String> properties) {
718 String oldHostVersion = getThing().getProperties().get(LifxBindingConstants.PROPERTY_HOST_VERSION);
719 super.updateProperties(properties);
720 String newHostVersion = getThing().getProperties().get(LifxBindingConstants.PROPERTY_HOST_VERSION);
722 if (!Objects.equals(oldHostVersion, newHostVersion)) {
723 features.update(getFeatures());
727 private void updateStateIfChanged(String channel, State newState) {
728 State oldState = channelStates.get(channel);
729 if (oldState == null || !oldState.equals(newState)) {
730 updateState(channel, newState);
731 channelStates.put(channel, newState);
735 private void updateStatusIfChanged(ThingStatus status) {
736 updateStatusIfChanged(status, ThingStatusDetail.NONE);
739 private void updateStatusIfChanged(ThingStatus status, ThingStatusDetail statusDetail) {
740 ThingStatusInfo newStatusInfo = new ThingStatusInfo(status, statusDetail, null);
741 Duration durationSinceLastUpdate = Duration.between(lastStatusInfoUpdate, LocalDateTime.now());
742 boolean intervalElapsed = MIN_STATUS_INFO_UPDATE_INTERVAL.minus(durationSinceLastUpdate).isNegative();
744 ThingStatusInfo oldStatusInfo = statusInfo;
745 if (oldStatusInfo == null || !oldStatusInfo.equals(newStatusInfo) || intervalElapsed) {
746 statusInfo = newStatusInfo;
747 lastStatusInfoUpdate = LocalDateTime.now();
748 updateStatus(status, statusDetail);