[hue] Added LightActions to Hue light groups (#11452)

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2021-10-26 19:08:47 +02:00 committed by GitHub
parent a1b3f27964
commit 4d5fd84c49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 73 deletions

View File

@ -227,7 +227,7 @@ The `tap_switch_event` can trigger one of the following events:
## Rule Actions ## Rule Actions
This binding includes a rule action, which allows to change a light channel with a specific fading time from within rules. This binding includes a rule action, which allows to change a light channel with a specific fading time from within rules.
There is a separate instance for each light, which can be retrieved e.g. through There is a separate instance for each light or light group, which can be retrieved e.g. through
```php ```php
val hueActions = getActions("hue","hue:0210:00178810d0dc:1") val hueActions = getActions("hue","hue:0210:00178810d0dc:1")
@ -244,7 +244,7 @@ hueActions.fadingLightCommand("color", new PercentType(100), new DecimalType(100
|-----------|--------------------------------------------------------------------------------------------------| |-----------|--------------------------------------------------------------------------------------------------|
| channel | The following channels have fade time support: **brightness, color, color_temperature, switch** | | channel | The following channels have fade time support: **brightness, color, color_temperature, switch** |
| command | All commands supported by the channel can be used | | command | All commands supported by the channel can be used |
| fadeTime | Fade time in Milliseconds to a new light value (min="0", step="100") | | fadeTime | Fade time in milliseconds to a new light value (min="0", step="100") |
## Full Example ## Full Example

View File

@ -14,7 +14,7 @@ package org.openhab.binding.hue.internal.action;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.handler.HueLightHandler; import org.openhab.binding.hue.internal.handler.HueLightActionsHandler;
import org.openhab.core.automation.annotation.ActionInput; import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction; import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@ -26,20 +26,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* The {@link LightActions} defines the thing actions for the hue binding. * The {@link LightActions} defines {@link ThingActions} for the hue lights.
* *
* @author Jochen Leopold - Initial contribution * @author Jochen Leopold - Initial contribution
* @author Laurent Garnier - new method invokeMethodOf + interface ILightActions
*/ */
@ThingActionsScope(name = "hue") @ThingActionsScope(name = "hue")
@NonNullByDefault @NonNullByDefault
public class LightActions implements ThingActions { public class LightActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(LightActions.class); private final Logger logger = LoggerFactory.getLogger(LightActions.class);
private @Nullable HueLightHandler handler; private @Nullable HueLightActionsHandler handler;
@Override @Override
public void setThingHandler(@Nullable ThingHandler handler) { public void setThingHandler(@Nullable ThingHandler handler) {
this.handler = (HueLightHandler) handler; this.handler = (HueLightActionsHandler) handler;
} }
@Override @Override
@ -52,8 +51,8 @@ public class LightActions implements ThingActions {
@ActionInput(name = "channel", label = "@text/actionInputChannelLabel", description = "@text/actionInputChannelDesc") @Nullable String channel, @ActionInput(name = "channel", label = "@text/actionInputChannelLabel", description = "@text/actionInputChannelDesc") @Nullable String channel,
@ActionInput(name = "command", label = "@text/actionInputCommandLabel", description = "@text/actionInputCommandDesc") @Nullable Command command, @ActionInput(name = "command", label = "@text/actionInputCommandLabel", description = "@text/actionInputCommandDesc") @Nullable Command command,
@ActionInput(name = "fadeTime", label = "@text/actionInputFadeTimeLabel", description = "@text/actionInputFadeTimeDesc") @Nullable DecimalType fadeTime) { @ActionInput(name = "fadeTime", label = "@text/actionInputFadeTimeLabel", description = "@text/actionInputFadeTimeDesc") @Nullable DecimalType fadeTime) {
HueLightHandler lightHandler = handler; HueLightActionsHandler lightActionsHandler = handler;
if (lightHandler == null) { if (lightActionsHandler == null) {
logger.warn("Hue Action service ThingHandler is null!"); logger.warn("Hue Action service ThingHandler is null!");
return; return;
} }
@ -62,7 +61,6 @@ public class LightActions implements ThingActions {
logger.debug("skipping Hue fadingLightCommand to channel '{}' due to null value.", channel); logger.debug("skipping Hue fadingLightCommand to channel '{}' due to null value.", channel);
return; return;
} }
if (command == null) { if (command == null) {
logger.debug("skipping Hue fadingLightCommand to command '{}' due to null value.", command); logger.debug("skipping Hue fadingLightCommand to command '{}' due to null value.", command);
return; return;
@ -72,8 +70,8 @@ public class LightActions implements ThingActions {
return; return;
} }
lightHandler.handleCommand(channel, command, fadeTime.longValue()); lightActionsHandler.handleCommand(channel, command, fadeTime.longValue());
logger.debug("send LightAction to {} with {}ms of fadeTime", channel, fadeTime); logger.debug("send fadingLightCommand to channel '{}' with fadeTime of {}ms.", channel, fadeTime);
} }
public static void fadingLightCommand(ThingActions actions, @Nullable String channel, @Nullable Command command, public static void fadingLightCommand(ThingActions actions, @Nullable String channel, @Nullable Command command,

View File

@ -57,7 +57,8 @@ import org.slf4j.LoggerFactory;
* @author Laurent Garnier - Initial contribution * @author Laurent Garnier - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class HueGroupHandler extends BaseThingHandler implements GroupStatusListener { public class HueGroupHandler extends BaseThingHandler implements HueLightActionsHandler, GroupStatusListener {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GROUP); public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GROUP);
public static final String PROPERTY_MEMBERS = "members"; public static final String PROPERTY_MEMBERS = "members";
@ -168,6 +169,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
handleCommand(channelUID.getId(), command, defaultFadeTime); handleCommand(channelUID.getId(), command, defaultFadeTime);
} }
@Override
public void handleCommand(String channel, Command command, long fadeTime) { public void handleCommand(String channel, Command command, long fadeTime) {
HueClient bridgeHandler = getHueClient(); HueClient bridgeHandler = getHueClient();
if (bridgeHandler == null) { if (bridgeHandler == null) {

View File

@ -0,0 +1,45 @@
/**
* 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.handler;
import java.util.Collection;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.action.LightActions;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
/**
* The {@link HueLightActionsHandler} defines interface handlers to handle {@link LightActions}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public interface HueLightActionsHandler extends ThingHandler {
@Override
default Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(LightActions.class);
}
/**
* Handles a command for a given channel.
*
* @param channel the id of the channel to which the command was sent
* @param command the {@link Command}
* @param fadeTime duration for execution of the command
*/
void handleCommand(String channel, Command command, long fadeTime);
}

View File

@ -16,8 +16,6 @@ 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.Collection;
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;
@ -29,7 +27,6 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.FullLight; import org.openhab.binding.hue.internal.FullLight;
import org.openhab.binding.hue.internal.State; import org.openhab.binding.hue.internal.State;
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.dto.Capabilities; import org.openhab.binding.hue.internal.dto.Capabilities;
import org.openhab.binding.hue.internal.dto.ColorTemperature; import org.openhab.binding.hue.internal.dto.ColorTemperature;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@ -47,7 +44,6 @@ import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler; 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.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateDescriptionFragment; import org.openhab.core.types.StateDescriptionFragment;
import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.StateDescriptionFragmentBuilder;
@ -73,7 +69,7 @@ import org.slf4j.LoggerFactory;
* @author Jochen Leopold - Added support for custom fade times * @author Jochen Leopold - Added support for custom fade times
*/ */
@NonNullByDefault @NonNullByDefault
public class HueLightHandler extends BaseThingHandler implements LightStatusListener { public class HueLightHandler extends BaseThingHandler implements HueLightActionsHandler, LightStatusListener {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.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,
@ -218,6 +214,7 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
handleCommand(channelUID.getId(), command, defaultFadeTime); handleCommand(channelUID.getId(), command, defaultFadeTime);
} }
@Override
public void handleCommand(String channel, Command command, long fadeTime) { public void handleCommand(String channel, Command command, long fadeTime) {
HueClient bridgeHandler = getHueClient(); HueClient bridgeHandler = getHueClient();
if (bridgeHandler == null) { if (bridgeHandler == null) {
@ -234,95 +231,95 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
} }
Integer lastColorTemp; Integer lastColorTemp;
StateUpdate lightState = null; StateUpdate newState = null;
switch (channel) { switch (channel) {
case CHANNEL_COLORTEMPERATURE: case CHANNEL_COLORTEMPERATURE:
if (command instanceof PercentType) { if (command instanceof PercentType) {
lightState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command, newState = LightStateConverter.toColorTemperatureLightStateFromPercentType((PercentType) command,
colorTemperatureCapabilties); colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} else if (command instanceof OnOffType) { } else if (command instanceof OnOffType) {
lightState = LightStateConverter.toOnOffLightState((OnOffType) command); newState = LightStateConverter.toOnOffLightState((OnOffType) command);
if (isOsramPar16) { if (isOsramPar16) {
lightState = addOsramSpecificCommands(lightState, (OnOffType) command); newState = addOsramSpecificCommands(newState, (OnOffType) command);
} }
} else if (command instanceof IncreaseDecreaseType) { } else if (command instanceof IncreaseDecreaseType) {
lightState = convertColorTempChangeToStateUpdate((IncreaseDecreaseType) command, light); newState = convertColorTempChangeToStateUpdate((IncreaseDecreaseType) command, light);
if (lightState != null) { if (newState != null) {
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
} }
break; break;
case CHANNEL_COLORTEMPERATURE_ABS: case CHANNEL_COLORTEMPERATURE_ABS:
if (command instanceof DecimalType) { if (command instanceof DecimalType) {
lightState = LightStateConverter.toColorTemperatureLightState((DecimalType) command, newState = LightStateConverter.toColorTemperatureLightState((DecimalType) command,
colorTemperatureCapabilties); colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
break; break;
case CHANNEL_BRIGHTNESS: case CHANNEL_BRIGHTNESS:
if (command instanceof PercentType) { if (command instanceof PercentType) {
lightState = LightStateConverter.toBrightnessLightState((PercentType) command); newState = LightStateConverter.toBrightnessLightState((PercentType) command);
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} else if (command instanceof OnOffType) { } else if (command instanceof OnOffType) {
lightState = LightStateConverter.toOnOffLightState((OnOffType) command); newState = LightStateConverter.toOnOffLightState((OnOffType) command);
if (isOsramPar16) { if (isOsramPar16) {
lightState = addOsramSpecificCommands(lightState, (OnOffType) command); newState = addOsramSpecificCommands(newState, (OnOffType) command);
} }
} else if (command instanceof IncreaseDecreaseType) { } else if (command instanceof IncreaseDecreaseType) {
lightState = convertBrightnessChangeToStateUpdate((IncreaseDecreaseType) command, light); newState = convertBrightnessChangeToStateUpdate((IncreaseDecreaseType) command, light);
if (lightState != null) { if (newState != null) {
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
} }
lastColorTemp = lastSentColorTemp; lastColorTemp = lastSentColorTemp;
if (lightState != 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
lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties); newState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
break; break;
case CHANNEL_SWITCH: case CHANNEL_SWITCH:
if (command instanceof OnOffType) { if (command instanceof OnOffType) {
lightState = LightStateConverter.toOnOffLightState((OnOffType) command); newState = LightStateConverter.toOnOffLightState((OnOffType) command);
if (isOsramPar16) { if (isOsramPar16) {
lightState = addOsramSpecificCommands(lightState, (OnOffType) command); newState = addOsramSpecificCommands(newState, (OnOffType) command);
} }
} }
lastColorTemp = lastSentColorTemp; lastColorTemp = lastSentColorTemp;
if (lightState != 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
lightState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties); newState.setColorTemperature(lastColorTemp, colorTemperatureCapabilties);
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
break; break;
case CHANNEL_COLOR: case CHANNEL_COLOR:
if (command instanceof HSBType) { if (command instanceof HSBType) {
HSBType hsbCommand = (HSBType) command; HSBType hsbCommand = (HSBType) command;
if (hsbCommand.getBrightness().intValue() == 0) { if (hsbCommand.getBrightness().intValue() == 0) {
lightState = LightStateConverter.toOnOffLightState(OnOffType.OFF); newState = LightStateConverter.toOnOffLightState(OnOffType.OFF);
} else { } else {
lightState = LightStateConverter.toColorLightState(hsbCommand, light.getState()); newState = LightStateConverter.toColorLightState(hsbCommand, light.getState());
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
} else if (command instanceof PercentType) { } else if (command instanceof PercentType) {
lightState = LightStateConverter.toBrightnessLightState((PercentType) command); newState = LightStateConverter.toBrightnessLightState((PercentType) command);
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} else if (command instanceof OnOffType) { } else if (command instanceof OnOffType) {
lightState = LightStateConverter.toOnOffLightState((OnOffType) command); newState = LightStateConverter.toOnOffLightState((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) { } else if (command instanceof IncreaseDecreaseType) {
lightState = convertBrightnessChangeToStateUpdate((IncreaseDecreaseType) command, light); newState = convertBrightnessChangeToStateUpdate((IncreaseDecreaseType) command, light);
if (lightState != null) { if (newState != null) {
lightState.setTransitionTime(fadeTime); newState.setTransitionTime(fadeTime);
} }
} }
break; break;
case CHANNEL_ALERT: case CHANNEL_ALERT:
if (command instanceof StringType) { if (command instanceof StringType) {
lightState = LightStateConverter.toAlertState((StringType) command); newState = LightStateConverter.toAlertState((StringType) command);
if (lightState == null) { if (newState == null) {
// Unsupported StringType is passed. Log a warning // Unsupported StringType is passed. Log a warning
// message and return. // message and return.
logger.warn("Unsupported String command: {}. Supported commands are: {}, {}, {} ", command, logger.warn("Unsupported String command: {}. Supported commands are: {}, {}, {} ", command,
@ -336,21 +333,21 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
break; break;
case CHANNEL_EFFECT: case CHANNEL_EFFECT:
if (command instanceof OnOffType) { if (command instanceof OnOffType) {
lightState = LightStateConverter.toOnOffEffectState((OnOffType) command); newState = LightStateConverter.toOnOffEffectState((OnOffType) command);
} }
break; break;
} }
if (lightState != null) { if (newState != null) {
// Cache values which we have sent // Cache values which we have sent
Integer tmpBrightness = lightState.getBrightness(); Integer tmpBrightness = newState.getBrightness();
if (tmpBrightness != null) { if (tmpBrightness != null) {
lastSentBrightness = tmpBrightness; lastSentBrightness = tmpBrightness;
} }
Integer tmpColorTemp = lightState.getColorTemperature(); Integer tmpColorTemp = newState.getColorTemperature();
if (tmpColorTemp != null) { if (tmpColorTemp != null) {
lastSentColorTemp = tmpColorTemp; lastSentColorTemp = tmpColorTemp;
} }
bridgeHandler.updateLightState(this, light, lightState, fadeTime); bridgeHandler.updateLightState(this, light, newState, fadeTime);
} else { } else {
logger.warn("Command sent to an unknown channel id: {}:{}", getThing().getUID(), channel); logger.warn("Command sent to an unknown channel id: {}:{}", getThing().getUID(), channel);
} }
@ -601,11 +598,6 @@ public class HueLightHandler extends BaseThingHandler implements LightStatusList
return delay; return delay;
} }
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return List.of(LightActions.class);
}
@Override @Override
public String getLightId() { public String getLightId() {
return lightId; return lightId;

View File

@ -19,7 +19,8 @@ import static org.openhab.binding.hue.internal.HueBindingConstants.*;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.BeforeEach; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.openhab.binding.hue.internal.FullConfig; import org.openhab.binding.hue.internal.FullConfig;
@ -56,6 +57,7 @@ import com.google.gson.JsonParser;
* @author Simon Kaufmann - migrated to plain Java test * @author Simon Kaufmann - migrated to plain Java test
* @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only
*/ */
@NonNullByDefault
public class HueLightHandlerTest { public class HueLightHandlerTest {
private static final int MIN_COLOR_TEMPERATURE = 153; private static final int MIN_COLOR_TEMPERATURE = 153;
@ -66,12 +68,7 @@ public class HueLightHandlerTest {
private static final String OSRAM_MODEL_TYPE = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID; private static final String OSRAM_MODEL_TYPE = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID;
private static final String OSRAM_MODEL_TYPE_ID = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID; private static final String OSRAM_MODEL_TYPE_ID = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID;
private Gson gson; private final Gson gson = new Gson();
@BeforeEach
public void setUp() {
gson = new Gson();
}
@Test @Test
public void assertCommandForOsramPar1650ForColorTemperatureChannelOn() { public void assertCommandForOsramPar1650ForColorTemperatureChannelOn() {
@ -402,12 +399,12 @@ public class HueLightHandlerTest {
HueLightHandler hueLightHandler = new HueLightHandler(mockThing, mock(HueStateDescriptionProvider.class)) { HueLightHandler hueLightHandler = new HueLightHandler(mockThing, mock(HueStateDescriptionProvider.class)) {
@Override @Override
protected synchronized HueClient getHueClient() { protected synchronized @Nullable HueClient getHueClient() {
return mockClient; return mockClient;
} }
@Override @Override
protected Bridge getBridge() { protected @Nullable Bridge getBridge() {
return mockBridge; return mockBridge;
} }
}; };