[hue] Added support for different color temperature capabilities and added Channel to set value in Kelvin (#9939)

* Added support for color temperature capabilities and set value in Kelvin
* Use system default channel type

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp
2021-01-26 18:27:14 +01:00
committed by GitHub
parent 7ce7228a85
commit 7141a091ff
19 changed files with 336 additions and 104 deletions

View 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: The devices support some of the following channels:
| Channel Type ID | Item Type | Description | Thing types supporting this channel | | 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 | | 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 | | 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 | | 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 | 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 | | color_temperature_abs | Number | This channel supports adjusting the color temperature in Kelvin. **Advanced** | 0210, 0220, group |
| effect | Switch | This channel supports color looping. | 0200, 0210, 0220 | | 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 |
| dimmer_switch | Number | This channel shows which button was last pressed on the dimmer switch. | 0820 | | effect | Switch | This channel supports color looping. | 0200, 0210, 0220 |
| illuminance | Number:Illuminance | This channel shows the current illuminance measured by the sensor. | 0106 | | dimmer_switch | Number | This channel shows which button was last pressed on the dimmer switch. | 0820 |
| light_level | Number | This channel shows the current light level measured by the sensor. **Advanced** | 0106 | | illuminance | Number:Illuminance | This channel shows the current illuminance measured by the sensor. | 0106 |
| dark | Switch | This channel indicates whether the light level is below the darkness threshold or not. | 0106 | | light_level | Number | This channel shows the current light level measured by the sensor. **Advanced** | 0106 |
| daylight | Switch | This channel indicates whether the light level is below the daylight threshold or not. | 0106 | | dark | Switch | This channel indicates whether the light level is below the darkness threshold or not. | 0106 |
| presence | Switch | This channel indicates whether a motion is detected by the sensor or not. | 0107 | | daylight | Switch | This channel indicates whether the light level is below the daylight threshold or not. | 0106 |
| enabled | Switch | This channel activated or deactivates the sensor | 0107 | | presence | Switch | This channel indicates whether a motion is detected by the sensor or not. | 0107 |
| temperature | Number:Temperature | This channel shows the current temperature measured by the sensor. | 0302 | | enabled | Switch | This channel activated or deactivates the sensor | 0107 |
| flag | Switch | This channel save flag state for a CLIP sensor. | 0850 | | temperature | Number:Temperature | This channel shows the current temperature measured by the sensor. | 0302 |
| status | Number | This channel save status state for a CLIP sensor. | 0840 | | flag | Switch | This channel save flag state for a CLIP sensor. | 0850 |
| last_updated | DateTime | This channel the date and time when the sensor was last updated. | 0820, 0830, 0840, 0850, 0106, 0107, 0302 | | status | Number | This channel save status state for a CLIP sensor. | 0840 |
| battery_level | Number | This channel shows the battery level. | 0820, 0106, 0107, 0302 | | last_updated | DateTime | This channel the date and time when the sensor was last updated. | 0820, 0830, 0840, 0850, 0106, 0107, 0302 |
| battery_low | Switch | This channel indicates whether the battery is low or not. | 0820, 0106, 0107, 0302 | | battery_level | Number | This channel shows the battery level. | 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 | | 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. 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`. You can list all the scene IDs with the following console commands: `hue <bridgeUID> scenes` and `hue <groupThingUID> scenes`.

View File

@@ -17,6 +17,8 @@ import java.time.Duration;
import java.util.Map; import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; 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; 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>>() { public static final Type GSON_TYPE = new TypeToken<Map<String, FullLight>>() {
}.getType(); }.getType();
public @Nullable Capabilities capabilities;
private @NonNullByDefault({}) State state; private @NonNullByDefault({}) State state;
private final long fadetime = 400; // milliseconds private final long fadetime = 400; // milliseconds

View File

@@ -93,7 +93,8 @@ public class HttpClient {
synchronized (commandsQueue) { synchronized (commandsQueue) {
if (commandsQueue.isEmpty()) { if (commandsQueue.isEmpty()) {
commandsQueue.offer(asyncPutParameters); commandsQueue.offer(asyncPutParameters);
if (job == null || job.isDone()) { Future<?> localJob = job;
if (localJob == null || localJob.isDone()) {
job = scheduler.submit(this::executeCommands); job = scheduler.submit(this::executeCommands);
} }
} else { } else {

View File

@@ -58,6 +58,7 @@ public class HueBindingConstants {
// List all channels // List all channels
public static final String CHANNEL_COLORTEMPERATURE = "color_temperature"; 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_COLOR = "color";
public static final String CHANNEL_BRIGHTNESS = "brightness"; public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_ALERT = "alert"; public static final String CHANNEL_ALERT = "alert";

View File

@@ -144,7 +144,7 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new HueBridgeHandler((Bridge) thing, stateOptionProvider); return new HueBridgeHandler((Bridge) thing, stateOptionProvider);
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { } 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())) { } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new DimmerSwitchHandler(thing); return new DimmerSwitchHandler(thing);
} else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { } else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {

View File

@@ -27,7 +27,7 @@ public class State {
int hue; int hue;
int sat; int sat;
private float[] xy; private float[] xy;
private int ct; int ct;
private String alert; private String alert;
private String effect; private String effect;
String colormode; String colormode;

View 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.AlertMode;
import org.openhab.binding.hue.internal.State.Effect; 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. * 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. * 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 * @return this object for chaining calls
*/ */
public StateUpdate setColorTemperature(int colorTemperature) { public StateUpdate setColorTemperature(int colorTemperature, ColorTemperature capabilities) {
if (colorTemperature < 153 || colorTemperature > 500) { if (colorTemperature < capabilities.min || colorTemperature > capabilities.max) {
throw new IllegalArgumentException("Color temperature out of range"); 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)); commands.add(new Command("ct", colorTemperature));

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -15,8 +15,6 @@ package org.openhab.binding.hue.internal.handler;
import static org.openhab.binding.hue.internal.HueBindingConstants.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; 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;
import org.openhab.binding.hue.internal.State.ColorMode; import org.openhab.binding.hue.internal.State.ColorMode;
import org.openhab.binding.hue.internal.StateUpdate; 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.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@@ -59,7 +59,7 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class HueGroupHandler extends BaseThingHandler implements GroupStatusListener { 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 Logger logger = LoggerFactory.getLogger(HueGroupHandler.class);
private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider; private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
@@ -69,6 +69,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
private @Nullable Integer lastSentColorTemp; private @Nullable Integer lastSentColorTemp;
private @Nullable Integer lastSentBrightness; private @Nullable Integer lastSentBrightness;
private ColorTemperature colorTemperatureCapabilties = new ColorTemperature();
private long defaultFadeTime = 400; private long defaultFadeTime = 400;
private @Nullable HueClient hueClient; private @Nullable HueClient hueClient;
@@ -76,7 +77,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
private @Nullable ScheduledFuture<?> scheduledFuture; private @Nullable ScheduledFuture<?> scheduledFuture;
private @Nullable FullGroup lastFullGroup; private @Nullable FullGroup lastFullGroup;
private List<String> consoleScenesList = new ArrayList<>(); private List<String> consoleScenesList = List.of();
public HueGroupHandler(Thing thing, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) { public HueGroupHandler(Thing thing, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) {
super(thing); super(thing);
@@ -201,7 +202,8 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
break; break;
case CHANNEL_COLORTEMPERATURE: case CHANNEL_COLORTEMPERATURE:
if (command instanceof PercentType) { if (command instanceof PercentType) {
newState = LightStateConverter.toColorTemperatureLightState((PercentType) command); newState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command,
colorTemperatureCapabilties);
newState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} else if (command instanceof OnOffType) { } else if (command instanceof OnOffType) {
newState = LightStateConverter.toOnOffLightState((OnOffType) command); newState = LightStateConverter.toOnOffLightState((OnOffType) command);
@@ -212,6 +214,13 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
} }
} }
break; break;
case CHANNEL_COLORTEMPERATURE_ABS:
if (command instanceof DecimalType) {
newState = LightStateConverter.toColorTemperatureLightState((DecimalType) command,
colorTemperatureCapabilties);
newState.setTransitionTime(fadeTime);
}
break;
case CHANNEL_BRIGHTNESS: case CHANNEL_BRIGHTNESS:
if (command instanceof PercentType) { if (command instanceof PercentType) {
newState = LightStateConverter.toBrightnessLightState((PercentType) command); newState = LightStateConverter.toBrightnessLightState((PercentType) command);
@@ -228,7 +237,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
if (newState != null && lastColorTemp != null) { if (newState != null && lastColorTemp != null) {
// make sure that the light also has the latest color temp // 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 // this might not have been yet set in the light, if it was off
newState.setColorTemperature(lastColorTemp); newState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
newState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
break; break;
@@ -240,7 +249,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
if (newState != null && lastColorTemp != null) { if (newState != null && lastColorTemp != null) {
// make sure that the light also has the latest color temp // 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 // this might not have been yet set in the light, if it was off
newState.setColorTemperature(lastColorTemp); newState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
newState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
break; break;
@@ -296,8 +305,9 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
StateUpdate stateUpdate = null; StateUpdate stateUpdate = null;
Integer currentColorTemp = getCurrentColorTemp(group.getState()); Integer currentColorTemp = getCurrentColorTemp(group.getState());
if (currentColorTemp != null) { if (currentColorTemp != null) {
int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp); int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp,
stateUpdate = new StateUpdate().setColorTemperature(newColorTemp); colorTemperatureCapabilties);
stateUpdate = new StateUpdate().setColorTemperature(newColorTemp, colorTemperatureCapabilties);
} }
return stateUpdate; return stateUpdate;
} }
@@ -380,25 +390,27 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
HSBType hsbType = LightStateConverter.toHSBType(state); HSBType hsbType = LightStateConverter.toHSBType(state);
if (!state.isOn()) { 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); updateState(CHANNEL_COLOR, hsbType);
ColorMode colorMode = state.getColorMode(); ColorMode colorMode = state.getColorMode();
if (ColorMode.CT.equals(colorMode)) { if (ColorMode.CT.equals(colorMode)) {
PercentType colorTempPercentType = LightStateConverter.toColorTemperaturePercentType(state); updateState(CHANNEL_COLORTEMPERATURE,
updateState(CHANNEL_COLORTEMPERATURE, colorTempPercentType); LightStateConverter.toColorTemperaturePercentType(state, colorTemperatureCapabilties));
updateState(CHANNEL_COLORTEMPERATURE_ABS, LightStateConverter.toColorTemperature(state));
} else { } else {
updateState(CHANNEL_COLORTEMPERATURE, UnDefType.NULL); updateState(CHANNEL_COLORTEMPERATURE, UnDefType.UNDEF);
updateState(CHANNEL_COLORTEMPERATURE_ABS, UnDefType.UNDEF);
} }
PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state); PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state);
if (!state.isOn()) { if (!state.isOn()) {
brightnessPercentType = new PercentType(0); brightnessPercentType = PercentType.ZERO;
} }
updateState(CHANNEL_BRIGHTNESS, brightnessPercentType); updateState(CHANNEL_BRIGHTNESS, brightnessPercentType);
updateState(CHANNEL_SWITCH, state.isOn() ? OnOffType.ON : OnOffType.OFF); updateState(CHANNEL_SWITCH, OnOffType.from(state.isOn()));
return true; return true;
} }
@@ -423,8 +435,8 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
*/ */
@Override @Override
public void onScenesUpdated(List<Scene> updatedScenes) { public void onScenesUpdated(List<Scene> updatedScenes) {
List<StateOption> stateOptions = Collections.emptyList(); List<StateOption> stateOptions = List.of();
consoleScenesList = new ArrayList<>(); consoleScenesList = List.of();
HueClient handler = getHueClient(); HueClient handler = getHueClient();
if (handler != null) { if (handler != null) {
FullGroup group = handler.getGroupById(groupId); FullGroup group = handler.getGroupById(groupId);

View File

@@ -16,18 +16,13 @@ import static org.openhab.binding.hue.internal.HueBindingConstants.*;
import static org.openhab.core.thing.Thing.*; import static org.openhab.core.thing.Thing.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.State.ColorMode;
import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.StateUpdate;
import org.openhab.binding.hue.internal.action.LightActions; 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.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType; 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.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; 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.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -77,24 +77,20 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class HueLightHandler extends BaseThingHandler implements LightStatusListener { 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_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()); THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG);
// @formatter:off private static final Map<String, List<String>> VENDOR_MODEL_MAP = Map.of( //
private static final Map<String, List<String>> VENDOR_MODEL_MAP = Stream.of( "Philips", List.of("LCT001", "LCT002", "LCT003", "LCT007", "LLC001", "LLC006", "LLC007", "LLC010", //
new SimpleEntry<>("Philips", "LLC011", "LLC012", "LLC013", "LLC020", "LST001", "LST002", "LWB004", "LWB006", "LWB007", //
Arrays.asList("LCT001", "LCT002", "LCT003", "LCT007", "LLC001", "LLC006", "LLC007", "LLC010", "LWL001"),
"LLC011", "LLC012", "LLC013", "LLC020", "LST001", "LST002", "LWB004", "LWB006", "LWB007", "OSRAM", List.of("Classic_A60_RGBW", "PAR16_50_TW", "Surface_Light_TW", "Plug_01"));
"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
private static final String OSRAM_PAR16_50_TW_MODEL_ID = "PAR16_50_TW"; private static final String OSRAM_PAR16_50_TW_MODEL_ID = "PAR16_50_TW";
private final Logger logger = LoggerFactory.getLogger(HueLightHandler.class); private final Logger logger = LoggerFactory.getLogger(HueLightHandler.class);
private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
private @NonNullByDefault({}) String lightId; private @NonNullByDefault({}) String lightId;
@@ -108,14 +104,17 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
private boolean isOsramPar16 = false; private boolean isOsramPar16 = false;
private boolean propertiesInitializedSuccessfully = false; private boolean propertiesInitializedSuccessfully = false;
private boolean capabilitiesInitializedSuccessfully = false;
private ColorTemperature colorTemperatureCapabilties = new ColorTemperature();
private long defaultFadeTime = 400; private long defaultFadeTime = 400;
private @Nullable HueClient hueClient; private @Nullable HueClient hueClient;
private @Nullable ScheduledFuture<?> scheduledFuture; private @Nullable ScheduledFuture<?> scheduledFuture;
public HueLightHandler(Thing hueLight) { public HueLightHandler(Thing hueLight, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) {
super(hueLight); super(hueLight);
this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
} }
@Override @Override
@@ -145,7 +144,9 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
HueClient bridgeHandler = getHueClient(); HueClient bridgeHandler = getHueClient();
if (bridgeHandler != null) { if (bridgeHandler != null) {
if (bridgeStatus == ThingStatus.ONLINE) { if (bridgeStatus == ThingStatus.ONLINE) {
initializeProperties(bridgeHandler.getLightById(lightId)); FullLight fullLight = bridgeHandler.getLightById(lightId);
initializeProperties(fullLight);
initializeCapabilities(fullLight);
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); 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) { private @Nullable String getVendor(String modelId) {
for (String vendor : VENDOR_MODEL_MAP.keySet()) { for (String vendor : VENDOR_MODEL_MAP.keySet()) {
if (VENDOR_MODEL_MAP.get(vendor).contains(modelId)) { if (VENDOR_MODEL_MAP.get(vendor).contains(modelId)) {
@@ -235,7 +263,8 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
switch (channel) { switch (channel) {
case CHANNEL_COLORTEMPERATURE: case CHANNEL_COLORTEMPERATURE:
if (command instanceof PercentType) { if (command instanceof PercentType) {
lightState = LightStateConverter.toColorTemperatureLightState((PercentType) command); lightState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command,
colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); lightState.setTransitionTime(fadeTime);
} else if (command instanceof OnOffType) { } else if (command instanceof OnOffType) {
lightState = LightStateConverter.toOnOffLightState((OnOffType) command); lightState = LightStateConverter.toOnOffLightState((OnOffType) command);
@@ -248,7 +277,13 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
lightState.setTransitionTime(fadeTime); lightState.setTransitionTime(fadeTime);
} }
} }
break;
case CHANNEL_COLORTEMPERATURE_ABS:
if (command instanceof DecimalType) {
lightState = LightStateConverter.toColorTemperatureLightState((DecimalType) command,
colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime);
}
break; break;
case CHANNEL_BRIGHTNESS: case CHANNEL_BRIGHTNESS:
if (command instanceof PercentType) { if (command instanceof PercentType) {
@@ -269,7 +304,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
if (lightState != null && lastColorTemp != null) { if (lightState != null && lastColorTemp != null) {
// make sure that the light also has the latest color temp // 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 // this might not have been yet set in the light, if it was off
lightState.setColorTemperature(lastColorTemp); lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); lightState.setTransitionTime(fadeTime);
} }
break; break;
@@ -285,7 +320,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
if (lightState != null && lastColorTemp != null) { if (lightState != null && lastColorTemp != null) {
// make sure that the light also has the latest color temp // 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 // this might not have been yet set in the light, if it was off
lightState.setColorTemperature(lastColorTemp); lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); lightState.setTransitionTime(fadeTime);
} }
break; break;
@@ -365,8 +400,9 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
StateUpdate stateUpdate = null; StateUpdate stateUpdate = null;
Integer currentColorTemp = getCurrentColorTemp(light.getState()); Integer currentColorTemp = getCurrentColorTemp(light.getState());
if (currentColorTemp != null) { if (currentColorTemp != null) {
int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp); int newColorTemp = LightStateConverter.toAdjustedColorTemp(command, currentColorTemp,
stateUpdate = new StateUpdate().setColorTemperature(newColorTemp); colorTemperatureCapabilties);
stateUpdate = new StateUpdate().setColorTemperature(newColorTemp, colorTemperatureCapabilties);
} }
return stateUpdate; return stateUpdate;
} }
@@ -482,29 +518,27 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
HSBType hsbType = LightStateConverter.toHSBType(state); HSBType hsbType = LightStateConverter.toHSBType(state);
if (!state.isOn()) { 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); updateState(CHANNEL_COLOR, hsbType);
ColorMode colorMode = state.getColorMode(); ColorMode colorMode = state.getColorMode();
if (ColorMode.CT.equals(colorMode)) { if (ColorMode.CT.equals(colorMode)) {
PercentType colorTempPercentType = LightStateConverter.toColorTemperaturePercentType(state); updateState(CHANNEL_COLORTEMPERATURE,
updateState(CHANNEL_COLORTEMPERATURE, colorTempPercentType); LightStateConverter.toColorTemperaturePercentType(state, colorTemperatureCapabilties));
updateState(CHANNEL_COLORTEMPERATURE_ABS, LightStateConverter.toColorTemperature(state));
} else { } else {
updateState(CHANNEL_COLORTEMPERATURE, UnDefType.NULL); updateState(CHANNEL_COLORTEMPERATURE, UnDefType.UNDEF);
updateState(CHANNEL_COLORTEMPERATURE_ABS, UnDefType.UNDEF);
} }
PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state); PercentType brightnessPercentType = LightStateConverter.toBrightnessPercentType(state);
if (!state.isOn()) { if (!state.isOn()) {
brightnessPercentType = new PercentType(0); brightnessPercentType = PercentType.ZERO;
} }
updateState(CHANNEL_BRIGHTNESS, brightnessPercentType); updateState(CHANNEL_BRIGHTNESS, brightnessPercentType);
if (state.isOn()) { updateState(CHANNEL_SWITCH, OnOffType.from(state.isOn()));
updateState(CHANNEL_SWITCH, OnOffType.ON);
} else {
updateState(CHANNEL_SWITCH, OnOffType.OFF);
}
StringType stringType = LightStateConverter.toAlertStringType(state); StringType stringType = LightStateConverter.toAlertStringType(state);
if (!"NULL".equals(stringType.toString())) { if (!"NULL".equals(stringType.toString())) {
@@ -609,7 +643,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
@Override @Override
public Collection<Class<? extends ThingHandlerService>> getServices() { public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(LightActions.class); return List.of(LightActions.class);
} }
@Override @Override

View File

@@ -12,10 +12,19 @@
*/ */
package org.openhab.binding.hue.internal.handler; 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.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.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider; 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.Component;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
@@ -29,14 +38,23 @@ import org.osgi.service.component.annotations.Reference;
@NonNullByDefault @NonNullByDefault
public class HueStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider { public class HueStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
@Reference private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
protected void setChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { @Activate
public HueStateDescriptionOptionProvider(
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
} }
protected void unsetChannelTypeI18nLocalizationService( public void setDescription(ChannelUID channelUID, StateDescription description) {
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { descriptions.put(channelUID, description);
this.channelTypeI18nLocalizationService = null; }
@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);
} }
} }

View 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.ColorMode;
import org.openhab.binding.hue.internal.State.Effect; import org.openhab.binding.hue.internal.State.Effect;
import org.openhab.binding.hue.internal.StateUpdate; 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.DecimalType;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; 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 SATURATION_FACTOR = 2.54;
private static final double BRIGHTNESS_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. * {@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} * @param percentType color temperature represented as {@link PercentType}
* @return light state containing the color temperature * @return light state containing the color temperature
*/ */
public static StateUpdate toColorTemperatureLightState(PercentType percentType) { public static StateUpdate toColorTemperatureLightStateFromPercentType(PercentType percentType,
int colorTemperature = MIN_COLOR_TEMPERATURE ColorTemperature capabilities) {
+ Math.round((COLOR_TEMPERATURE_RANGE * percentType.floatValue()) / 100); int colorTemperature = capabilities.min
return new StateUpdate().setColorTemperature(colorTemperature); + 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 * @param currentColorTemp The current color temperature
* @return The adjusted color temperature value * @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; int newColorTemp;
if (type == IncreaseDecreaseType.DECREASE) { if (type == IncreaseDecreaseType.DECREASE) {
newColorTemp = Math.max(currentColorTemp - DIM_STEPSIZE, MIN_COLOR_TEMPERATURE); newColorTemp = Math.max(currentColorTemp - DIM_STEPSIZE, capabilities.min);
} else { } else {
newColorTemp = Math.min(currentColorTemp + DIM_STEPSIZE, MAX_COLOR_TEMPERATURE); newColorTemp = Math.min(currentColorTemp + DIM_STEPSIZE, capabilities.max);
} }
return newColorTemp; return newColorTemp;
} }
@@ -180,12 +194,27 @@ public class LightStateConverter {
* @param lightState light state * @param lightState light state
* @return percent type representing the color temperature * @return percent type representing the color temperature
*/ */
public static PercentType toColorTemperaturePercentType(State lightState) { public static PercentType toColorTemperaturePercentType(State lightState, ColorTemperature capabilities) {
int percent = (int) Math int percent = (int) Math.round(((lightState.getColorTemperature() - capabilities.min) * 100.0)
.round(((lightState.getColorTemperature() - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE); / (capabilities.max - capabilities.min));
return new PercentType(restrictToBounds(percent)); 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 * Transforms Hue Light {@link State} into {@link PercentType} representing
* the brightness. * the brightness.

View File

@@ -14,6 +14,7 @@
<channels> <channels>
<channel id="color_temperature" typeId="system.color-temperature"/> <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="brightness" typeId="system.brightness"/>
<channel id="alert" typeId="alert"/> <channel id="alert" typeId="alert"/>
<channel id="effect" typeId="effect"/> <channel id="effect" typeId="effect"/>

View File

@@ -15,6 +15,7 @@
<channels> <channels>
<channel id="color" typeId="system.color"/> <channel id="color" typeId="system.color"/>
<channel id="color_temperature" typeId="system.color-temperature"/> <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="alert" typeId="alert"/>
<channel id="effect" typeId="effect"/> <channel id="effect" typeId="effect"/>
</channels> </channels>

View File

@@ -15,6 +15,7 @@
<channels> <channels>
<channel id="switch" typeId="system.power"/> <channel id="switch" typeId="system.power"/>
<channel id="color_temperature" typeId="system.color-temperature"/> <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="brightness" typeId="system.brightness"/>
<channel id="color" typeId="system.color"/> <channel id="color" typeId="system.color"/>
<channel id="alert" typeId="alert"/> <channel id="alert" typeId="alert"/>

View File

@@ -14,10 +14,12 @@ package org.openhab.binding.hue.internal;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openhab.binding.hue.internal.State.ColorMode; 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.binding.hue.internal.handler.LightStateConverter;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;
@@ -31,6 +33,38 @@ import org.openhab.core.library.types.PercentType;
*/ */
public class LightStateConverterTest { 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 @Test
public void brightnessOfZeroIsZero() { public void brightnessOfZeroIsZero() {
final State lightState = new State(); final State lightState = new State();

View File

@@ -12,7 +12,7 @@
*/ */
package org.openhab.binding.hue.internal.handler; 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.ArgumentMatchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.openhab.binding.hue.internal.HueBindingConstants.*; 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.State.ColorMode;
import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.StateUpdate;
import org.openhab.core.config.core.Configuration; 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.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType; 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.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -136,6 +138,24 @@ public class HueLightHandlerTest {
assertSendCommandForColorTemp(new PercentType(100), new HueLightState(), expectedReply); 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 @Test
public void assertPercentageValueOfColorTemperatureWhenCt153() { public void assertPercentageValueOfColorTemperatureWhenCt153() {
int expectedReply = 0; int expectedReply = 0;
@@ -337,6 +357,10 @@ public class HueLightHandlerTest {
assertSendCommand(CHANNEL_COLORTEMPERATURE, command, currentState, expectedReply); 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) { private void asserttoColorTemperaturePercentType(int ctValue, int expectedPercent) {
int percent = (int) Math.round(((ctValue - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE); int percent = (int) Math.round(((ctValue - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE);
assertEquals(percent, expectedPercent); assertEquals(percent, expectedPercent);
@@ -373,7 +397,8 @@ public class HueLightHandlerTest {
long fadeTime = 400; long fadeTime = 400;
HueLightHandler hueLightHandler = new HueLightHandler(mockThing) { HueLightHandler hueLightHandler = new HueLightHandler(mockThing,
new HueStateDescriptionOptionProvider(mock(ChannelTypeI18nLocalizationService.class))) {
@Override @Override
protected synchronized HueClient getHueClient() { protected synchronized HueClient getHueClient() {
return mockClient; return mockClient;