]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hue] Added support for different color temperature capabilities and added Channel...
authorChristoph Weitkamp <github@christophweitkamp.de>
Tue, 26 Jan 2021 17:27:14 +0000 (18:27 +0100)
committerGitHub <noreply@github.com>
Tue, 26 Jan 2021 17:27:14 +0000 (09:27 -0800)
* Added support for color temperature capabilities and set value in Kelvin
* Use system default channel type

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
19 files changed:
bundles/org.openhab.binding.hue/README.md
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Capabilities.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ColorTemperature.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Control.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueStateDescriptionOptionProvider.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/ColorTemperatureLight.xml
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/ExtendedColorLight.xml
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Group.xml
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java

index 332b25cb8a20569f564bba743c0982a30afa2196..9c2d07e512beb7ab094c5ea4a3ee25baaa5a25f3 100644 (file)
@@ -158,28 +158,29 @@ The group type also have an optional configuration value to specify the fade tim
 
 The devices support some of the following channels:
 
-| Channel Type ID   | Item Type          | Description                                                                                                                             | Thing types supporting this channel      |
-|-------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|
-| switch            | Switch             | This channel supports switching the device on and off.                                                                                  | 0000, 0010, group                        |
-| color             | Color              | This channel supports full color control with hue, saturation and brightness values.                                                    | 0200, 0210, group                        |
-| brightness        | Dimmer             | This channel supports adjusting the brightness value. Note that this is not available, if the color channel is supported.               | 0100, 0110, 0220, group                  |
-| color_temperature | Dimmer             | This channel supports adjusting the color temperature from cold (0%) to warm (100%).                                                    | 0210, 0220, group                        |
-| alert             | String             | This channel supports displaying alerts by flashing the bulb either once or multiple times. Valid values are: NONE, SELECT and LSELECT. | 0000, 0100, 0200, 0210, 0220, group      |
-| effect            | Switch             | This channel supports color looping.                                                                                                    | 0200, 0210, 0220                         |
-| dimmer_switch     | Number             | This channel shows which button was last pressed on the dimmer switch.                                                                  | 0820                                     |
-| illuminance       | Number:Illuminance | This channel shows the current illuminance measured by the sensor.                                                                      | 0106                                     |
-| light_level       | Number             | This channel shows the current light level measured by the sensor. **Advanced**                                                         | 0106                                     |
-| dark              | Switch             | This channel indicates whether the light level is below the darkness threshold or not.                                                  | 0106                                     |
-| daylight          | Switch             | This channel indicates whether the light level is below the daylight threshold or not.                                                  | 0106                                     |
-| presence          | Switch             | This channel indicates whether a motion is detected by the sensor or not.                                                               | 0107                                     |
-| enabled           | Switch             | This channel activated or deactivates the sensor                                                                                        | 0107                                     |
-| temperature       | Number:Temperature | This channel shows the current temperature measured by the sensor.                                                                      | 0302                                     |
-| flag              | Switch             | This channel save flag state for a CLIP sensor.                                                                                         | 0850                                     |
-| status            | Number             | This channel save status state for a CLIP sensor.                                                                                       | 0840                                     |
-| last_updated      | DateTime           | This channel the date and time when the sensor was last updated.                                                                        | 0820, 0830, 0840, 0850, 0106, 0107, 0302 |
-| battery_level     | Number             | This channel shows the battery level.                                                                                                   | 0820, 0106, 0107, 0302                   |
-| battery_low       | Switch             | This channel indicates whether the battery is low or not.                                                                               | 0820, 0106, 0107, 0302                   |
-| scene             | String             | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue bridge.                   | bridge, group                            |
+| Channel Type ID       | Item Type          | Description                                                                                                                             | Thing types supporting this channel      |
+|-----------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|
+| switch                | Switch             | This channel supports switching the device on and off.                                                                                  | 0000, 0010, group                        |
+| color                 | Color              | This channel supports full color control with hue, saturation and brightness values.                                                    | 0200, 0210, group                        |
+| brightness            | Dimmer             | This channel supports adjusting the brightness value. Note that this is not available, if the color channel is supported.               | 0100, 0110, 0220, group                  |
+| color_temperature     | Dimmer             | This channel supports adjusting the color temperature from cold (0%) to warm (100%).                                                    | 0210, 0220, group                        |
+| color_temperature_abs | Number             | This channel supports adjusting the color temperature in Kelvin. **Advanced**                                                           | 0210, 0220, group                        |
+| alert                 | String             | This channel supports displaying alerts by flashing the bulb either once or multiple times. Valid values are: NONE, SELECT and LSELECT. | 0000, 0100, 0200, 0210, 0220, group      |
+| effect                | Switch             | This channel supports color looping.                                                                                                    | 0200, 0210, 0220                         |
+| dimmer_switch         | Number             | This channel shows which button was last pressed on the dimmer switch.                                                                  | 0820                                     |
+| illuminance           | Number:Illuminance | This channel shows the current illuminance measured by the sensor.                                                                      | 0106                                     |
+| light_level           | Number             | This channel shows the current light level measured by the sensor. **Advanced**                                                         | 0106                                     |
+| dark                  | Switch             | This channel indicates whether the light level is below the darkness threshold or not.                                                  | 0106                                     |
+| daylight              | Switch             | This channel indicates whether the light level is below the daylight threshold or not.                                                  | 0106                                     |
+| presence              | Switch             | This channel indicates whether a motion is detected by the sensor or not.                                                               | 0107                                     |
+| enabled               | Switch             | This channel activated or deactivates the sensor                                                                                        | 0107                                     |
+| temperature           | Number:Temperature | This channel shows the current temperature measured by the sensor.                                                                      | 0302                                     |
+| flag                  | Switch             | This channel save flag state for a CLIP sensor.                                                                                         | 0850                                     |
+| status                | Number             | This channel save status state for a CLIP sensor.                                                                                       | 0840                                     |
+| last_updated          | DateTime           | This channel the date and time when the sensor was last updated.                                                                        | 0820, 0830, 0840, 0850, 0106, 0107, 0302 |
+| battery_level         | Number             | This channel shows the battery level.                                                                                                   | 0820, 0106, 0107, 0302                   |
+| battery_low           | Switch             | This channel indicates whether the battery is low or not.                                                                               | 0820, 0106, 0107, 0302                   |
+| scene                 | String             | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue bridge.                   | bridge, group                            |
 
 To load a hue scene inside a rule for example, the ID of the scene will be required.
 You can list all the scene IDs with the following console commands: `hue <bridgeUID> scenes` and `hue <groupThingUID> scenes`.
index cab424c29960d88f2ae7584e04b9e82d93ae239d..ee2fad7fdd994a981296f05b252c177f8bd23526 100644 (file)
@@ -17,6 +17,8 @@ import java.time.Duration;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.hue.internal.dto.Capabilities;
 
 import com.google.gson.reflect.TypeToken;
 
@@ -33,6 +35,7 @@ public class FullLight extends FullHueObject {
     public static final Type GSON_TYPE = new TypeToken<Map<String, FullLight>>() {
     }.getType();
 
+    public @Nullable Capabilities capabilities;
     private @NonNullByDefault({}) State state;
     private final long fadetime = 400; // milliseconds
 
index f4adb97523fd561323769ef0090a4dc7d9f5b6fe..8f02a02d6ddd1877f35c3174c0893b99d29bba8c 100644 (file)
@@ -93,7 +93,8 @@ public class HttpClient {
         synchronized (commandsQueue) {
             if (commandsQueue.isEmpty()) {
                 commandsQueue.offer(asyncPutParameters);
-                if (job == null || job.isDone()) {
+                Future<?> localJob = job;
+                if (localJob == null || localJob.isDone()) {
                     job = scheduler.submit(this::executeCommands);
                 }
             } else {
index a914cf14e53f95ddcecbd35e90001d49575874c2..8f38f52b9ee291b8caf59eb6fb58d4d1dfb3ca32 100644 (file)
@@ -58,6 +58,7 @@ public class HueBindingConstants {
 
     // List all channels
     public static final String CHANNEL_COLORTEMPERATURE = "color_temperature";
+    public static final String CHANNEL_COLORTEMPERATURE_ABS = "color_temperature_abs";
     public static final String CHANNEL_COLOR = "color";
     public static final String CHANNEL_BRIGHTNESS = "brightness";
     public static final String CHANNEL_ALERT = "alert";
index 4bc07f45bc5635c54d26b3cf740e188df6390a4d..8644676a73a72c606cf716f8a1ea42ba0b1d112d 100644 (file)
@@ -144,7 +144,7 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
         if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
             return new HueBridgeHandler((Bridge) thing, stateOptionProvider);
         } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new HueLightHandler(thing);
+            return new HueLightHandler(thing, stateOptionProvider);
         } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
             return new DimmerSwitchHandler(thing);
         } else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
index 7e116f5bf62e99632dc2d2d23235fefe05241988..7ce101bdcf31fcb488bbca4fee368bba1dd9c8aa 100644 (file)
@@ -27,7 +27,7 @@ public class State {
     int hue;
     int sat;
     private float[] xy;
-    private int ct;
+    int ct;
     private String alert;
     private String effect;
     String colormode;
index 55f8958cf0acf35676bb5995383429645ff0ca85..28617b3459933f7fee729778ce04ed4628563b73 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.hue.internal;
 
 import org.openhab.binding.hue.internal.State.AlertMode;
 import org.openhab.binding.hue.internal.State.Effect;
+import org.openhab.binding.hue.internal.dto.ColorTemperature;
 
 /**
  * Collection of updates to the state of a light.
@@ -139,12 +140,13 @@ public class StateUpdate extends ConfigUpdate {
     /**
      * Switch to CT color mode and set color temperature in mired.
      *
-     * @param colorTemperature color temperature [153..500]
+     * @param colorTemperature color temperature
      * @return this object for chaining calls
      */
-    public StateUpdate setColorTemperature(int colorTemperature) {
-        if (colorTemperature < 153 || colorTemperature > 500) {
-            throw new IllegalArgumentException("Color temperature out of range");
+    public StateUpdate setColorTemperature(int colorTemperature, ColorTemperature capabilities) {
+        if (colorTemperature < capabilities.min || colorTemperature > capabilities.max) {
+            throw new IllegalArgumentException(String.format("Color temperature %d is out of range [%d..%d]",
+                    colorTemperature, capabilities.min, capabilities.max));
         }
 
         commands.add(new Command("ct", colorTemperature));
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Capabilities.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Capabilities.java
new file mode 100644 (file)
index 0000000..7b544e9
--- /dev/null
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hue.internal.dto;
+
+/**
+ * Collection of capabilities for lights.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class Capabilities {
+    public Control control;
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ColorTemperature.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ColorTemperature.java
new file mode 100644 (file)
index 0000000..655a150
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hue.internal.dto;
+
+/**
+ * Collection of color temperature capabilities to control lights.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class ColorTemperature {
+    public int max = 500;
+    public int min = 153;
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Control.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Control.java
new file mode 100644 (file)
index 0000000..ce2817c
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hue.internal.dto;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Collection of capabilities to control lights.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class Control {
+    public @Nullable ColorTemperature ct;
+}
index b6139627aeee304cd9a80b54fbe0c82aec6343fc..a8e87730afe9b50958c9e97235a8d87cc6516e1a 100644 (file)
@@ -15,8 +15,6 @@ package org.openhab.binding.hue.internal.handler;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -31,6 +29,8 @@ import org.openhab.binding.hue.internal.Scene;
 import org.openhab.binding.hue.internal.State;
 import org.openhab.binding.hue.internal.State.ColorMode;
 import org.openhab.binding.hue.internal.StateUpdate;
+import org.openhab.binding.hue.internal.dto.ColorTemperature;
+import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
 import org.openhab.core.library.types.OnOffType;
@@ -59,7 +59,7 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class HueGroupHandler extends BaseThingHandler implements GroupStatusListener {
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_GROUP);
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GROUP);
 
     private final Logger logger = LoggerFactory.getLogger(HueGroupHandler.class);
     private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
@@ -69,6 +69,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
     private @Nullable Integer lastSentColorTemp;
     private @Nullable Integer lastSentBrightness;
 
+    private ColorTemperature colorTemperatureCapabilties = new ColorTemperature();
     private long defaultFadeTime = 400;
 
     private @Nullable HueClient hueClient;
@@ -76,7 +77,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
     private @Nullable ScheduledFuture<?> scheduledFuture;
     private @Nullable FullGroup lastFullGroup;
 
-    private List<String> consoleScenesList = new ArrayList<>();
+    private List<String> consoleScenesList = List.of();
 
     public HueGroupHandler(Thing thing, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) {
         super(thing);
@@ -201,7 +202,8 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
                 break;
             case CHANNEL_COLORTEMPERATURE:
                 if (command instanceof PercentType) {
-                    newState = LightStateConverter.toColorTemperatureLightState((PercentType) command);
+                    newState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command,
+                            colorTemperatureCapabilties);
                     newState.setTransitionTime(fadeTime);
                 } else if (command instanceof OnOffType) {
                     newState = LightStateConverter.toOnOffLightState((OnOffType) command);
@@ -212,6 +214,13 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
                     }
                 }
                 break;
+            case CHANNEL_COLORTEMPERATURE_ABS:
+                if (command instanceof DecimalType) {
+                    newState = LightStateConverter.toColorTemperatureLightState((DecimalType) command,
+                            colorTemperatureCapabilties);
+                    newState.setTransitionTime(fadeTime);
+                }
+                break;
             case CHANNEL_BRIGHTNESS:
                 if (command instanceof PercentType) {
                     newState = LightStateConverter.toBrightnessLightState((PercentType) command);
@@ -228,7 +237,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
                 if (newState != null && lastColorTemp != null) {
                     // make sure that the light also has the latest color temp
                     // this might not have been yet set in the light, if it was off
-                    newState.setColorTemperature(lastColorTemp);
+                    newState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
                     newState.setTransitionTime(fadeTime);
                 }
                 break;
@@ -240,7 +249,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
                 if (newState != null && lastColorTemp != null) {
                     // make sure that the light also has the latest color temp
                     // this might not have been yet set in the light, if it was off
-                    newState.setColorTemperature(lastColorTemp);
+                    newState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
                     newState.setTransitionTime(fadeTime);
                 }
                 break;
@@ -296,8 +305,9 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
         StateUpdate stateUpdate = null;
         Integer currentColorTemp = getCurrentColorTemp(group.getState());
         if (currentColorTemp != null) {
-            int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp);
-            stateUpdate = new StateUpdate().setColorTemperature(newColorTemp);
+            int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp,
+                    colorTemperatureCapabilties);
+            stateUpdate = new StateUpdate().setColorTemperature(newColorTemp, colorTemperatureCapabilties);
         }
         return stateUpdate;
     }
@@ -380,25 +390,27 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
 
         HSBType hsbType = LightStateConverter.toHSBType(state);
         if (!state.isOn()) {
-            hsbType = new HSBType(hsbType.getHue(), hsbType.getSaturation(), new PercentType(0));
+            hsbType = new HSBType(hsbType.getHue(), hsbType.getSaturation(), PercentType.ZERO);
         }
         updateState(CHANNEL_COLOR, hsbType);
 
         ColorMode colorMode = state.getColorMode();
         if (ColorMode.CT.equals(colorMode)) {
-            PercentType colorTempPercentType = LightStateConverter.toColorTemperaturePercentType(state);
-            updateState(CHANNEL_COLORTEMPERATURE, colorTempPercentType);
+            updateState(CHANNEL_COLORTEMPERATURE,
+                    LightStateConverter.toColorTemperaturePercentType(state, colorTemperatureCapabilties));
+            updateState(CHANNEL_COLORTEMPERATURE_ABS, LightStateConverter.toColorTemperature(state));
         } else {
-            updateState(CHANNEL_COLORTEMPERATURE, UnDefType.NULL);
+            updateState(CHANNEL_COLORTEMPERATURE, UnDefType.UNDEF);
+            updateState(CHANNEL_COLORTEMPERATURE_ABS, UnDefType.UNDEF);
         }
 
         PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state);
         if (!state.isOn()) {
-            brightnessPercentType = new PercentType(0);
+            brightnessPercentType = PercentType.ZERO;
         }
         updateState(CHANNEL_BRIGHTNESS, brightnessPercentType);
 
-        updateState(CHANNEL_SWITCH, state.isOn() ? OnOffType.ON : OnOffType.OFF);
+        updateState(CHANNEL_SWITCH, OnOffType.from(state.isOn()));
 
         return true;
     }
@@ -423,8 +435,8 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
      */
     @Override
     public void onScenesUpdated(List<Scene> updatedScenes) {
-        List<StateOption> stateOptions = Collections.emptyList();
-        consoleScenesList = new ArrayList<>();
+        List<StateOption> stateOptions = List.of();
+        consoleScenesList = List.of();
         HueClient handler = getHueClient();
         if (handler != null) {
             FullGroup group = handler.getGroupById(groupId);
index 410fd55395366056601a25d4abc44bf6a1f1fe6e..336791740865f659c8eeef057d3a7be43961df40 100644 (file)
@@ -16,18 +16,13 @@ import static org.openhab.binding.hue.internal.HueBindingConstants.*;
 import static org.openhab.core.thing.Thing.*;
 
 import java.math.BigDecimal;
-import java.util.AbstractMap.SimpleEntry;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -36,6 +31,9 @@ import org.openhab.binding.hue.internal.State;
 import org.openhab.binding.hue.internal.State.ColorMode;
 import org.openhab.binding.hue.internal.StateUpdate;
 import org.openhab.binding.hue.internal.action.LightActions;
+import org.openhab.binding.hue.internal.dto.Capabilities;
+import org.openhab.binding.hue.internal.dto.ColorTemperature;
+import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
 import org.openhab.core.library.types.OnOffType;
@@ -52,6 +50,8 @@ import org.openhab.core.thing.binding.BaseThingHandler;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
+import org.openhab.core.types.StateDescription;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
 import org.openhab.core.types.UnDefType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -77,24 +77,20 @@ import org.slf4j.LoggerFactory;
 @NonNullByDefault
 public class HueLightHandler extends BaseThingHandler implements LightStatusListener {
 
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream.of(THING_TYPE_COLOR_LIGHT,
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_COLOR_LIGHT,
             THING_TYPE_COLOR_TEMPERATURE_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_EXTENDED_COLOR_LIGHT,
-            THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG).collect(Collectors.toSet());
-
-    // @formatter:off
-    private static final Map<String, List<String>> VENDOR_MODEL_MAP = Stream.of(
-            new SimpleEntry<>("Philips",
-                    Arrays.asList("LCT001", "LCT002", "LCT003", "LCT007", "LLC001", "LLC006", "LLC007", "LLC010",
-                            "LLC011", "LLC012", "LLC013", "LLC020", "LST001", "LST002", "LWB004", "LWB006", "LWB007",
-                            "LWL001")),
-            new SimpleEntry<>("OSRAM",
-                    Arrays.asList("Classic_A60_RGBW", "PAR16_50_TW", "Surface_Light_TW", "Plug_01")))
-        .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
-    // @formatter:on
+            THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG);
+
+    private static final Map<String, List<String>> VENDOR_MODEL_MAP = Map.of( //
+            "Philips", List.of("LCT001", "LCT002", "LCT003", "LCT007", "LLC001", "LLC006", "LLC007", "LLC010", //
+                    "LLC011", "LLC012", "LLC013", "LLC020", "LST001", "LST002", "LWB004", "LWB006", "LWB007", //
+                    "LWL001"),
+            "OSRAM", List.of("Classic_A60_RGBW", "PAR16_50_TW", "Surface_Light_TW", "Plug_01"));
 
     private static final String OSRAM_PAR16_50_TW_MODEL_ID = "PAR16_50_TW";
 
     private final Logger logger = LoggerFactory.getLogger(HueLightHandler.class);
+    private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
 
     private @NonNullByDefault({}) String lightId;
 
@@ -108,14 +104,17 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
     private boolean isOsramPar16 = false;
 
     private boolean propertiesInitializedSuccessfully = false;
+    private boolean capabilitiesInitializedSuccessfully = false;
+    private ColorTemperature colorTemperatureCapabilties = new ColorTemperature();
     private long defaultFadeTime = 400;
 
     private @Nullable HueClient hueClient;
 
     private @Nullable ScheduledFuture<?> scheduledFuture;
 
-    public HueLightHandler(Thing hueLight) {
+    public HueLightHandler(Thing hueLight, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) {
         super(hueLight);
+        this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
     }
 
     @Override
@@ -145,7 +144,9 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
             HueClient bridgeHandler = getHueClient();
             if (bridgeHandler != null) {
                 if (bridgeStatus == ThingStatus.ONLINE) {
-                    initializeProperties(bridgeHandler.getLightById(lightId));
+                    FullLight fullLight = bridgeHandler.getLightById(lightId);
+                    initializeProperties(fullLight);
+                    initializeCapabilities(fullLight);
                     updateStatus(ThingStatus.ONLINE);
                 } else {
                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
@@ -187,6 +188,33 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
         }
     }
 
+    private void initializeCapabilities(@Nullable FullLight fullLight) {
+        if (!capabilitiesInitializedSuccessfully && fullLight != null) {
+            Capabilities capabilities = fullLight.capabilities;
+            if (capabilities != null) {
+                ColorTemperature ct = capabilities.control.ct;
+                if (ct != null) {
+                    colorTemperatureCapabilties = ct;
+
+                    // minimum and maximum are inverted due to mired/Kelvin conversion!
+                    StateDescription stateDescription = StateDescriptionFragmentBuilder.create()
+                            .withMinimum(new BigDecimal(LightStateConverter.miredToKelvin(ct.max))) //
+                            .withMaximum(new BigDecimal(LightStateConverter.miredToKelvin(ct.min))) //
+                            .withStep(new BigDecimal(100)) //
+                            .withPattern("%.0f K") //
+                            .build().toStateDescription();
+                    if (stateDescription != null) {
+                        stateDescriptionOptionProvider.setDescription(
+                                new ChannelUID(thing.getUID(), CHANNEL_COLORTEMPERATURE_ABS), stateDescription);
+                    } else {
+                        logger.warn("Failed to create state description in thing {}", thing.getUID());
+                    }
+                }
+            }
+            capabilitiesInitializedSuccessfully = true;
+        }
+    }
+
     private @Nullable String getVendor(String modelId) {
         for (String vendor : VENDOR_MODEL_MAP.keySet()) {
             if (VENDOR_MODEL_MAP.get(vendor).contains(modelId)) {
@@ -235,7 +263,8 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
         switch (channel) {
             case CHANNEL_COLORTEMPERATURE:
                 if (command instanceof PercentType) {
-                    lightState = LightStateConverter.toColorTemperatureLightState((PercentType) command);
+                    lightState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command,
+                            colorTemperatureCapabilties);
                     lightState.setTransitionTime(fadeTime);
                 } else if (command instanceof OnOffType) {
                     lightState = LightStateConverter.toOnOffLightState((OnOffType) command);
@@ -248,7 +277,13 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
                         lightState.setTransitionTime(fadeTime);
                     }
                 }
-
+                break;
+            case CHANNEL_COLORTEMPERATURE_ABS:
+                if (command instanceof DecimalType) {
+                    lightState = LightStateConverter.toColorTemperatureLightState((DecimalType) command,
+                            colorTemperatureCapabilties);
+                    lightState.setTransitionTime(fadeTime);
+                }
                 break;
             case CHANNEL_BRIGHTNESS:
                 if (command instanceof PercentType) {
@@ -269,7 +304,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
                 if (lightState != null && lastColorTemp != null) {
                     // make sure that the light also has the latest color temp
                     // this might not have been yet set in the light, if it was off
-                    lightState.setColorTemperature(lastColorTemp);
+                    lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
                     lightState.setTransitionTime(fadeTime);
                 }
                 break;
@@ -285,7 +320,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
                 if (lightState != null && lastColorTemp != null) {
                     // make sure that the light also has the latest color temp
                     // this might not have been yet set in the light, if it was off
-                    lightState.setColorTemperature(lastColorTemp);
+                    lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
                     lightState.setTransitionTime(fadeTime);
                 }
                 break;
@@ -365,8 +400,9 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
         StateUpdate stateUpdate = null;
         Integer currentColorTemp = getCurrentColorTemp(light.getState());
         if (currentColorTemp != null) {
-            int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp);
-            stateUpdate = new StateUpdate().setColorTemperature(newColorTemp);
+            int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp,
+                    colorTemperatureCapabilties);
+            stateUpdate = new StateUpdate().setColorTemperature(newColorTemp, colorTemperatureCapabilties);
         }
         return stateUpdate;
     }
@@ -482,29 +518,27 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
 
         HSBType hsbType = LightStateConverter.toHSBType(state);
         if (!state.isOn()) {
-            hsbType = new HSBType(hsbType.getHue(), hsbType.getSaturation(), new PercentType(0));
+            hsbType = new HSBType(hsbType.getHue(), hsbType.getSaturation(), PercentType.ZERO);
         }
         updateState(CHANNEL_COLOR, hsbType);
 
         ColorMode colorMode = state.getColorMode();
         if (ColorMode.CT.equals(colorMode)) {
-            PercentType colorTempPercentType = LightStateConverter.toColorTemperaturePercentType(state);
-            updateState(CHANNEL_COLORTEMPERATURE, colorTempPercentType);
+            updateState(CHANNEL_COLORTEMPERATURE,
+                    LightStateConverter.toColorTemperaturePercentType(state, colorTemperatureCapabilties));
+            updateState(CHANNEL_COLORTEMPERATURE_ABS, LightStateConverter.toColorTemperature(state));
         } else {
-            updateState(CHANNEL_COLORTEMPERATURE, UnDefType.NULL);
+            updateState(CHANNEL_COLORTEMPERATURE, UnDefType.UNDEF);
+            updateState(CHANNEL_COLORTEMPERATURE_ABS, UnDefType.UNDEF);
         }
 
         PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state);
         if (!state.isOn()) {
-            brightnessPercentType = new PercentType(0);
+            brightnessPercentType = PercentType.ZERO;
         }
         updateState(CHANNEL_BRIGHTNESS, brightnessPercentType);
 
-        if (state.isOn()) {
-            updateState(CHANNEL_SWITCH, OnOffType.ON);
-        } else {
-            updateState(CHANNEL_SWITCH, OnOffType.OFF);
-        }
+        updateState(CHANNEL_SWITCH, OnOffType.from(state.isOn()));
 
         StringType stringType = LightStateConverter.toAlertStringType(state);
         if (!"NULL".equals(stringType.toString())) {
@@ -609,7 +643,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
 
     @Override
     public Collection<Class<? extends ThingHandlerService>> getServices() {
-        return Collections.singletonList(LightActions.class);
+        return List.of(LightActions.class);
     }
 
     @Override
index 2f43f88b79f5110a280d3dd9706ca5435d23dcec..4801c59d60a843e6e8af34ec96f2a75dc2da5017 100644 (file)
  */
 package org.openhab.binding.hue.internal.handler;
 
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
 import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
 import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.openhab.core.types.StateDescription;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
@@ -29,14 +38,23 @@ import org.osgi.service.component.annotations.Reference;
 @NonNullByDefault
 public class HueStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
 
-    @Reference
-    protected void setChannelTypeI18nLocalizationService(
-            final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+    private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
+
+    @Activate
+    public HueStateDescriptionOptionProvider(
+            final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
         this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
     }
 
-    protected void unsetChannelTypeI18nLocalizationService(
-            final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
-        this.channelTypeI18nLocalizationService = null;
+    public void setDescription(ChannelUID channelUID, StateDescription description) {
+        descriptions.put(channelUID, description);
+    }
+
+    @Override
+    public @Nullable StateDescription getStateDescription(Channel channel,
+            @Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
+        StateDescription stateDescription = descriptions.get(channel.getUID());
+        return stateDescription != null ? stateDescription
+                : super.getStateDescription(channel, originalStateDescription, locale);
     }
 }
index 3a45f732fe56794a286490e3a7367f79b1a462fb..4057df83ae7bdcb8c22350dd06ad983bc1287e29 100644 (file)
@@ -19,6 +19,7 @@ import org.openhab.binding.hue.internal.State.AlertMode;
 import org.openhab.binding.hue.internal.State.ColorMode;
 import org.openhab.binding.hue.internal.State.Effect;
 import org.openhab.binding.hue.internal.StateUpdate;
+import org.openhab.binding.hue.internal.dto.ColorTemperature;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
@@ -44,10 +45,6 @@ public class LightStateConverter {
     private static final double SATURATION_FACTOR = 2.54;
     private static final double BRIGHTNESS_FACTOR = 2.54;
 
-    private static final int MIN_COLOR_TEMPERATURE = 153;
-    private static final int MAX_COLOR_TEMPERATURE = 500;
-    private static final int COLOR_TEMPERATURE_RANGE = MAX_COLOR_TEMPERATURE - MIN_COLOR_TEMPERATURE;
-
     /**
      * {@value #ALERT_MODE_NONE}. The light is not performing an alert effect.
      */
@@ -150,10 +147,26 @@ public class LightStateConverter {
      * @param percentType color temperature represented as {@link PercentType}
      * @return light state containing the color temperature
      */
-    public static StateUpdate toColorTemperatureLightState(PercentType percentType) {
-        int colorTemperature = MIN_COLOR_TEMPERATURE
-                + Math.round((COLOR_TEMPERATURE_RANGE * percentType.floatValue()) / 100);
-        return new StateUpdate().setColorTemperature(colorTemperature);
+    public static StateUpdate toColorTemperatureLightStateFromPercentType(PercentType percentType,
+            ColorTemperature capabilities) {
+        int colorTemperature = capabilities.min
+                + Math.round(((capabilities.max - capabilities.min) * percentType.floatValue()) / 100);
+        return new StateUpdate().setColorTemperature(colorTemperature, capabilities);
+    }
+
+    public static int kelvinToMired(int kelvinValue) {
+        return (int) (1000000.0 / kelvinValue);
+    }
+
+    /**
+     * Transforms the given {@link DecimalType} into a light state containing
+     * the color temperature in Kelvin.
+     *
+     * @param decimalType color temperature in Kelvin
+     * @return light state containing the color temperature
+     */
+    public static StateUpdate toColorTemperatureLightState(DecimalType decimalType, ColorTemperature capabilities) {
+        return new StateUpdate().setColorTemperature(kelvinToMired(decimalType.intValue()), capabilities);
     }
 
     /**
@@ -163,12 +176,13 @@ public class LightStateConverter {
      * @param currentColorTemp The current color temperature
      * @return The adjusted color temperature value
      */
-    public static int toAdjustedColorTemp(IncreaseDecreaseType type, int currentColorTemp) {
+    public static int toAdjustedColorTemp(IncreaseDecreaseType type, int currentColorTemp,
+            ColorTemperature capabilities) {
         int newColorTemp;
         if (type == IncreaseDecreaseType.DECREASE) {
-            newColorTemp = Math.max(currentColorTemp - DIM_STEPSIZE, MIN_COLOR_TEMPERATURE);
+            newColorTemp = Math.max(currentColorTemp - DIM_STEPSIZE, capabilities.min);
         } else {
-            newColorTemp = Math.min(currentColorTemp + DIM_STEPSIZE, MAX_COLOR_TEMPERATURE);
+            newColorTemp = Math.min(currentColorTemp + DIM_STEPSIZE, capabilities.max);
         }
         return newColorTemp;
     }
@@ -180,12 +194,27 @@ public class LightStateConverter {
      * @param lightState light state
      * @return percent type representing the color temperature
      */
-    public static PercentType toColorTemperaturePercentType(State lightState) {
-        int percent = (int) Math
-                .round(((lightState.getColorTemperature() - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE);
+    public static PercentType toColorTemperaturePercentType(State lightState, ColorTemperature capabilities) {
+        int percent = (int) Math.round(((lightState.getColorTemperature() - capabilities.min) * 100.0)
+                / (capabilities.max - capabilities.min));
         return new PercentType(restrictToBounds(percent));
     }
 
+    public static int miredToKelvin(int miredValue) {
+        return (int) (1000000.0 / miredValue);
+    }
+
+    /**
+     * Transforms Hue Light {@link State} into {@link DecimalType} representing
+     * the color temperature in Kelvin.
+     *
+     * @param lightState light state
+     * @return percent type representing the color temperature in Kelvin
+     */
+    public static DecimalType toColorTemperature(State lightState) {
+        return new DecimalType(miredToKelvin(lightState.getColorTemperature()));
+    }
+
     /**
      * Transforms Hue Light {@link State} into {@link PercentType} representing
      * the brightness.
index e1b759eb77f59f250af5603bc19f392c68b86b46..7bd7ea70f3eb090f71acb6db20006de4631edd7f 100644 (file)
@@ -14,6 +14,7 @@
 
                <channels>
                        <channel id="color_temperature" typeId="system.color-temperature"/>
+                       <channel id="color_temperature_abs" typeId="system.color-temperature-abs"/>
                        <channel id="brightness" typeId="system.brightness"/>
                        <channel id="alert" typeId="alert"/>
                        <channel id="effect" typeId="effect"/>
index a4b4363df58d160b06f28c3f644fe446a86d75e3..6d64a96ee451e3f2a679d05a0f85834e8645dd80 100644 (file)
@@ -15,6 +15,7 @@
                <channels>
                        <channel id="color" typeId="system.color"/>
                        <channel id="color_temperature" typeId="system.color-temperature"/>
+                       <channel id="color_temperature_abs" typeId="system.color-temperature-abs"/>
                        <channel id="alert" typeId="alert"/>
                        <channel id="effect" typeId="effect"/>
                </channels>
index a863b9d27c1bd50fbc1d5333e061ea44e68a4bed..3078bd66480f7496a2ff7a0fc3d8837106247edb 100644 (file)
@@ -15,6 +15,7 @@
                <channels>
                        <channel id="switch" typeId="system.power"/>
                        <channel id="color_temperature" typeId="system.color-temperature"/>
+                       <channel id="color_temperature_abs" typeId="system.color-temperature-abs"/>
                        <channel id="brightness" typeId="system.brightness"/>
                        <channel id="color" typeId="system.color"/>
                        <channel id="alert" typeId="alert"/>
index 7d7cfcf367df1f0fe1a07d597130e6d6b3971544..9d151d7eb7bf30d837b380b9f0784a0ad17c5939 100644 (file)
@@ -14,10 +14,12 @@ package org.openhab.binding.hue.internal;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.hue.internal.State.ColorMode;
+import org.openhab.binding.hue.internal.dto.ColorTemperature;
 import org.openhab.binding.hue.internal.handler.LightStateConverter;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
@@ -31,6 +33,38 @@ import org.openhab.core.library.types.PercentType;
  */
 public class LightStateConverterTest {
 
+    @Test
+    public void colorTemperatureLightStateConverterConversionIsBijectiveDefaultColorTemperatureCapabilities() {
+        final State lightState = new State();
+        final ColorTemperature colorTemperature = new ColorTemperature();
+        for (int percent = 1; percent <= 100; ++percent) {
+            StateUpdate stateUpdate = LightStateConverter
+                    .toColorTemperatureLightStateFromPercentType(new PercentType(percent), colorTemperature);
+            assertThat(stateUpdate.commands, hasSize(1));
+            assertThat(stateUpdate.commands.get(0).key, is("ct"));
+            lightState.ct = Integer.parseInt(stateUpdate.commands.get(0).value.toString());
+            assertThat(LightStateConverter.toColorTemperaturePercentType(lightState, colorTemperature).intValue(),
+                    is(percent));
+        }
+    }
+
+    @Test
+    public void colorTemperatureLightStateConverterConversionIsBijectiveIndividualColorTemperatureCapabilities() {
+        final State lightState = new State();
+        final ColorTemperature colorTemperature = new ColorTemperature();
+        colorTemperature.min = 250;
+        colorTemperature.max = 454;
+        for (int percent = 1; percent <= 100; ++percent) {
+            StateUpdate stateUpdate = LightStateConverter
+                    .toColorTemperatureLightStateFromPercentType(new PercentType(percent), colorTemperature);
+            assertThat(stateUpdate.commands, hasSize(1));
+            assertThat(stateUpdate.commands.get(0).key, is("ct"));
+            lightState.ct = Integer.parseInt(stateUpdate.commands.get(0).value.toString());
+            assertThat(LightStateConverter.toColorTemperaturePercentType(lightState, colorTemperature).intValue(),
+                    is(percent));
+        }
+    }
+
     @Test
     public void brightnessOfZeroIsZero() {
         final State lightState = new State();
index 0a21801e113744fc290a7035d9fc4cc87854875d..2d62bbe8a5506087637821468c11a2650434572b 100644 (file)
@@ -12,7 +12,7 @@
  */
 package org.openhab.binding.hue.internal.handler;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
@@ -27,6 +27,7 @@ import org.openhab.binding.hue.internal.FullLight;
 import org.openhab.binding.hue.internal.State.ColorMode;
 import org.openhab.binding.hue.internal.StateUpdate;
 import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
 import org.openhab.core.library.types.OnOffType;
@@ -37,6 +38,7 @@ import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
 import org.openhab.core.types.Command;
 
 import com.google.gson.Gson;
@@ -136,6 +138,24 @@ public class HueLightHandlerTest {
         assertSendCommandForColorTemp(new PercentType(100), new HueLightState(), expectedReply);
     }
 
+    @Test
+    public void assertCommandForColorTemperatureAbsChannel6500Kelvin() {
+        String expectedReply = "{\"ct\" : 153, \"transitiontime\" : 4}";
+        assertSendCommandForColorTempAbs(new DecimalType(6500), new HueLightState(), expectedReply);
+    }
+
+    @Test
+    public void assertCommandForColorTemperatureAbsChannel4500Kelvin() {
+        String expectedReply = "{\"ct\" : 222, \"transitiontime\" : 4}";
+        assertSendCommandForColorTempAbs(new DecimalType(4500), new HueLightState(), expectedReply);
+    }
+
+    @Test
+    public void assertCommandForColorTemperatureAbsChannel2000Kelvin() {
+        String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
+        assertSendCommandForColorTempAbs(new DecimalType(2000), new HueLightState(), expectedReply);
+    }
+
     @Test
     public void assertPercentageValueOfColorTemperatureWhenCt153() {
         int expectedReply = 0;
@@ -337,6 +357,10 @@ public class HueLightHandlerTest {
         assertSendCommand(CHANNEL_COLORTEMPERATURE, command, currentState, expectedReply);
     }
 
+    private void assertSendCommandForColorTempAbs(Command command, HueLightState currentState, String expectedReply) {
+        assertSendCommand(CHANNEL_COLORTEMPERATURE_ABS, command, currentState, expectedReply);
+    }
+
     private void asserttoColorTemperaturePercentType(int ctValue, int expectedPercent) {
         int percent = (int) Math.round(((ctValue - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE);
         assertEquals(percent, expectedPercent);
@@ -373,7 +397,8 @@ public class HueLightHandlerTest {
 
         long fadeTime = 400;
 
-        HueLightHandler hueLightHandler = new HueLightHandler(mockThing) {
+        HueLightHandler hueLightHandler = new HueLightHandler(mockThing,
+                new HueStateDescriptionOptionProvider(mock(ChannelTypeI18nLocalizationService.class))) {
             @Override
             protected synchronized HueClient getHueClient() {
                 return mockClient;