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`.
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;
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
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 {
// 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";
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())) {
int hue;
int sat;
private float[] xy;
- private int ct;
+ int ct;
private String alert;
private String effect;
String colormode;
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.
/**
* 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));
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+}
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;
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;
*/
@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;
private @Nullable Integer lastSentColorTemp;
private @Nullable Integer lastSentBrightness;
+ private ColorTemperature colorTemperatureCapabilties = new ColorTemperature();
private long defaultFadeTime = 400;
private @Nullable HueClient hueClient;
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);
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);
}
}
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);
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;
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;
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;
}
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;
}
*/
@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);
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;
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;
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;
@NonNullByDefault
public class HueLightHandler extends BaseThingHandler implements LightStatusListener {
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream.of(THING_TYPE_COLOR_LIGHT,
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_COLOR_LIGHT,
THING_TYPE_COLOR_TEMPERATURE_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_EXTENDED_COLOR_LIGHT,
- THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG).collect(Collectors.toSet());
-
- // @formatter:off
- private static final Map<String, List<String>> VENDOR_MODEL_MAP = Stream.of(
- new SimpleEntry<>("Philips",
- Arrays.asList("LCT001", "LCT002", "LCT003", "LCT007", "LLC001", "LLC006", "LLC007", "LLC010",
- "LLC011", "LLC012", "LLC013", "LLC020", "LST001", "LST002", "LWB004", "LWB006", "LWB007",
- "LWL001")),
- new SimpleEntry<>("OSRAM",
- Arrays.asList("Classic_A60_RGBW", "PAR16_50_TW", "Surface_Light_TW", "Plug_01")))
- .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
- // @formatter:on
+ THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG);
+
+ private static final Map<String, List<String>> VENDOR_MODEL_MAP = Map.of( //
+ "Philips", List.of("LCT001", "LCT002", "LCT003", "LCT007", "LLC001", "LLC006", "LLC007", "LLC010", //
+ "LLC011", "LLC012", "LLC013", "LLC020", "LST001", "LST002", "LWB004", "LWB006", "LWB007", //
+ "LWL001"),
+ "OSRAM", List.of("Classic_A60_RGBW", "PAR16_50_TW", "Surface_Light_TW", "Plug_01"));
private static final String OSRAM_PAR16_50_TW_MODEL_ID = "PAR16_50_TW";
private final Logger logger = LoggerFactory.getLogger(HueLightHandler.class);
+ private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
private @NonNullByDefault({}) String lightId;
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
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);
}
}
+ 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)) {
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);
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) {
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;
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;
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;
}
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())) {
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
- return Collections.singletonList(LightActions.class);
+ return List.of(LightActions.class);
}
@Override
*/
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;
@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);
}
}
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;
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.
*/
* @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);
}
/**
* @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;
}
* @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.
<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"/>
<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>
<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"/>
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;
*/
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();
*/
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.*;
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;
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;
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;
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);
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;