[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>
pull/9977/head
Christoph Weitkamp 2021-01-26 18:27:14 +01:00 committed by GitHub
parent 7ce7228a85
commit 7141a091ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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:
| 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`.

View 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

View 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 {

View 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";

View 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())) {

View 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;

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.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));

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 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);

View 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());
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 = 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
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

View File

@ -12,10 +12,19 @@
*/
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);
}
}

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.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.

View 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"/>

View 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>

View 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"/>

View 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();

View 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;