[hue] Support timed effects (#15408)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>pull/15756/head
parent
4b0c551065
commit
247c0973b6
|
@ -87,7 +87,13 @@ Device things support some of the following channels:
|
||||||
The exact list of channels in a given device is determined at run time when the system is started.
|
The exact list of channels in a given device is determined at run time when the system is started.
|
||||||
Each device reports its own live list of capabilities, and the respective list of channels is created accordingly.
|
Each device reports its own live list of capabilities, and the respective list of channels is created accordingly.
|
||||||
|
|
||||||
The channels `color-xy-only`, `dimming-only` and `on-off-only` are *advanced* channels - see [below](###advanced-channels-for-devices-,-rooms-and-zones) for more details.
|
The channels `color-xy-only`, `dimming-only` and `on-off-only` are *advanced* channels - see [below](#advanced-channels-for-devices-rooms-and-zones) for more details.
|
||||||
|
|
||||||
|
The `effect` channel is an amalgamation of 'normal' and 'timed' effects.
|
||||||
|
To activate a 'normal' effect, the binding sends a single command to activate the respective effect.
|
||||||
|
To activate a 'timed' effect, the binding sends a first command to set the timing followed a second command to activate the effect.
|
||||||
|
You can explicitly send the timing command via the [dynamics channel](#the-dynamics-channel) before you send the effect command.
|
||||||
|
Or otherwise the binding will send a default timing command of 15 minutes.
|
||||||
|
|
||||||
The `button-last-event` channel is a trigger channel.
|
The `button-last-event` channel is a trigger channel.
|
||||||
When the button is pressed the channel receives a number as calculated by the following formula:
|
When the button is pressed the channel receives a number as calculated by the following formula:
|
||||||
|
@ -140,6 +146,7 @@ When you set a value for the `dynamics` channel (e.g. 2000 milliseconds) and the
|
||||||
When the `dynamics` channel value is changed, it triggers a time window of ten seconds during which the value is active.
|
When the `dynamics` channel value is changed, it triggers a time window of ten seconds during which the value is active.
|
||||||
If the second command is sent within the active time window, it will be executed gradually according to the `dynamics` channel value.
|
If the second command is sent within the active time window, it will be executed gradually according to the `dynamics` channel value.
|
||||||
However, if the second command is sent after the active time window has expired, then it will be executed immediately.
|
However, if the second command is sent after the active time window has expired, then it will be executed immediately.
|
||||||
|
If the second command is a 'timed' effect, then the dynamics duration will be applied to that effect.
|
||||||
|
|
||||||
### Advanced Channels for Devices, Rooms and Zones
|
### Advanced Channels for Devices, Rooms and Zones
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,7 @@ public class HueBindingConstants {
|
||||||
|
|
||||||
// channel IDs that (optionally) support dynamics
|
// channel IDs that (optionally) support dynamics
|
||||||
public static final Set<String> DYNAMIC_CHANNELS = Set.of(CHANNEL_2_BRIGHTNESS, CHANNEL_2_COLOR,
|
public static final Set<String> DYNAMIC_CHANNELS = Set.of(CHANNEL_2_BRIGHTNESS, CHANNEL_2_COLOR,
|
||||||
CHANNEL_2_COLOR_TEMP_PERCENT, CHANNEL_2_COLOR_TEMP_ABSOLUTE, CHANNEL_2_SCENE);
|
CHANNEL_2_COLOR_TEMP_PERCENT, CHANNEL_2_COLOR_TEMP_ABSOLUTE, CHANNEL_2_SCENE, CHANNEL_2_EFFECT);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Map of API v1 channel IDs against API v2 channel IDs where, if the v1 channel exists in the system, then we
|
* Map of API v1 channel IDs against API v2 channel IDs where, if the v1 channel exists in the system, then we
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Optional;
|
||||||
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.dto.clip2.enums.ActionType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
||||||
|
@ -320,13 +321,33 @@ public class Resource {
|
||||||
return UnDefType.NULL;
|
return UnDefType.NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Effects getEffects() {
|
public @Nullable Effects getFixedEffects() {
|
||||||
return effects;
|
return effects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amalgamated effect state. The result may be either from an 'effects' field or from a 'timedEffects'
|
||||||
|
* field. If both fields are missing it returns UnDefType.NULL, otherwise if either field is present and has an
|
||||||
|
* active value (other than EffectType.NO_EFFECT) it returns a StringType of the name of the respective active
|
||||||
|
* effect; and if none of the above apply, it returns a StringType of 'NO_EFFECT'.
|
||||||
|
*
|
||||||
|
* @return either a StringType value or UnDefType.NULL
|
||||||
|
*/
|
||||||
public State getEffectState() {
|
public State getEffectState() {
|
||||||
Effects effects = this.effects;
|
Effects effects = this.effects;
|
||||||
return Objects.nonNull(effects) ? new StringType(effects.getStatus().name()) : UnDefType.NULL;
|
TimedEffects timedEffects = this.timedEffects;
|
||||||
|
if (Objects.isNull(effects) && Objects.isNull(timedEffects)) {
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
EffectType effect = Objects.nonNull(effects) ? effects.getStatus() : null;
|
||||||
|
if (Objects.nonNull(effect) && effect != EffectType.NO_EFFECT) {
|
||||||
|
return new StringType(effect.name());
|
||||||
|
}
|
||||||
|
EffectType timedEffect = Objects.nonNull(timedEffects) ? timedEffects.getStatus() : null;
|
||||||
|
if (Objects.nonNull(timedEffect) && timedEffect != EffectType.NO_EFFECT) {
|
||||||
|
return new StringType(timedEffect.name());
|
||||||
|
}
|
||||||
|
return new StringType(EffectType.NO_EFFECT.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Boolean getEnabled() {
|
public @Nullable Boolean getEnabled() {
|
||||||
|
@ -517,7 +538,7 @@ public class Resource {
|
||||||
return Objects.nonNull(temperature) ? temperature.getTemperatureValidState() : UnDefType.NULL;
|
return Objects.nonNull(temperature) ? temperature.getTemperatureValidState() : UnDefType.NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Effects getTimedEffects() {
|
public @Nullable TimedEffects getTimedEffects() {
|
||||||
return timedEffects;
|
return timedEffects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +598,7 @@ public class Resource {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource setEffects(Effects effect) {
|
public Resource setFixedEffects(Effects effect) {
|
||||||
this.effects = effect;
|
this.effects = effect;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -640,6 +661,19 @@ public class Resource {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Resource setTimedEffects(TimedEffects timedEffects) {
|
||||||
|
this.timedEffects = timedEffects;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setTimedEffectsDuration(Duration dynamicsDuration) {
|
||||||
|
TimedEffects timedEffects = this.timedEffects;
|
||||||
|
if (Objects.nonNull(timedEffects)) {
|
||||||
|
timedEffects.setDuration(dynamicsDuration);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Resource setType(ResourceType resourceType) {
|
public Resource setType(ResourceType resourceType) {
|
||||||
this.type = resourceType.name().toLowerCase();
|
this.type = resourceType.name().toLowerCase();
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -25,11 +25,13 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class TimedEffects extends Effects {
|
public class TimedEffects extends Effects {
|
||||||
|
public static final Duration DEFAULT_DURATION = Duration.ofMinutes(15);
|
||||||
|
|
||||||
private @Nullable Long duration;
|
private @Nullable Long duration;
|
||||||
|
|
||||||
public @Nullable Duration getDuration() {
|
public @Nullable Duration getDuration() {
|
||||||
Long duration = this.duration;
|
Long duration = this.duration;
|
||||||
return Objects.nonNull(duration) ? Duration.ofMillis(duration) : Duration.ZERO;
|
return Objects.nonNull(duration) ? Duration.ofMillis(duration) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimedEffects setDuration(Duration duration) {
|
public TimedEffects setDuration(Duration duration) {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
package org.openhab.binding.hue.internal.dto.clip2.helper;
|
package org.openhab.binding.hue.internal.dto.clip2.helper;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
|
import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.OnState;
|
import org.openhab.binding.hue.internal.dto.clip2.OnState;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
@ -198,9 +200,9 @@ public class Setters {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for Effect field:
|
* Setter for fixed or timed effect field:
|
||||||
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
* Use the given command value to set the target fixed or timed effects resource DTO value based on the attributes
|
||||||
* (if any).
|
* of the source resource (if any).
|
||||||
*
|
*
|
||||||
* @param target the target resource.
|
* @param target the target resource.
|
||||||
* @param command the new state command should be a StringType.
|
* @param command the new state command should be a StringType.
|
||||||
|
@ -210,12 +212,16 @@ public class Setters {
|
||||||
*/
|
*/
|
||||||
public static Resource setEffect(Resource target, Command command, @Nullable Resource source) {
|
public static Resource setEffect(Resource target, Command command, @Nullable Resource source) {
|
||||||
if ((command instanceof StringType) && Objects.nonNull(source)) {
|
if ((command instanceof StringType) && Objects.nonNull(source)) {
|
||||||
Effects otherEffects = source.getEffects();
|
EffectType commandEffectType = EffectType.of(((StringType) command).toString());
|
||||||
if (Objects.nonNull(otherEffects)) {
|
Effects sourceFixedEffects = source.getFixedEffects();
|
||||||
EffectType effectType = EffectType.of(((StringType) command).toString());
|
if (Objects.nonNull(sourceFixedEffects) && sourceFixedEffects.allows(commandEffectType)) {
|
||||||
if (otherEffects.allows(effectType)) {
|
target.setFixedEffects(new Effects().setEffect(commandEffectType));
|
||||||
target.setEffects(new Effects().setEffect(effectType));
|
}
|
||||||
}
|
TimedEffects sourceTimedEffects = source.getTimedEffects();
|
||||||
|
if (Objects.nonNull(sourceTimedEffects) && sourceTimedEffects.allows(commandEffectType)) {
|
||||||
|
Duration duration = sourceTimedEffects.getDuration();
|
||||||
|
target.setTimedEffects(((TimedEffects) new TimedEffects().setEffect(commandEffectType))
|
||||||
|
.setDuration(Objects.nonNull(duration) ? duration : TimedEffects.DEFAULT_DURATION));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
|
@ -239,6 +245,7 @@ public class Setters {
|
||||||
if (Objects.isNull(targetOnOff) && Objects.nonNull(sourceOnOff)) {
|
if (Objects.isNull(targetOnOff) && Objects.nonNull(sourceOnOff)) {
|
||||||
target.setOnState(sourceOnOff);
|
target.setOnState(sourceOnOff);
|
||||||
}
|
}
|
||||||
|
|
||||||
// dimming
|
// dimming
|
||||||
Dimming targetDimming = target.getDimming();
|
Dimming targetDimming = target.getDimming();
|
||||||
Dimming sourceDimming = source.getDimming();
|
Dimming sourceDimming = source.getDimming();
|
||||||
|
@ -246,13 +253,15 @@ public class Setters {
|
||||||
target.setDimming(sourceDimming);
|
target.setDimming(sourceDimming);
|
||||||
targetDimming = target.getDimming();
|
targetDimming = target.getDimming();
|
||||||
}
|
}
|
||||||
|
|
||||||
// minimum dimming level
|
// minimum dimming level
|
||||||
Double targetMinDimmingLevel = Objects.nonNull(targetDimming) ? targetDimming.getMinimumDimmingLevel() : null;
|
if (Objects.nonNull(targetDimming)) {
|
||||||
Double sourceMinDimmingLevel = Objects.nonNull(sourceDimming) ? sourceDimming.getMinimumDimmingLevel() : null;
|
Double sourceMinDimLevel = Objects.isNull(sourceDimming) ? null : sourceDimming.getMinimumDimmingLevel();
|
||||||
if (Objects.isNull(targetMinDimmingLevel) && Objects.nonNull(sourceMinDimmingLevel)) {
|
if (Objects.nonNull(sourceMinDimLevel)) {
|
||||||
targetDimming = Objects.nonNull(targetDimming) ? targetDimming : new Dimming();
|
targetDimming.setMinimumDimmingLevel(sourceMinDimLevel);
|
||||||
targetDimming.setMinimumDimmingLevel(sourceMinDimmingLevel);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// color
|
// color
|
||||||
ColorXy targetColor = target.getColorXy();
|
ColorXy targetColor = target.getColorXy();
|
||||||
ColorXy sourceColor = source.getColorXy();
|
ColorXy sourceColor = source.getColorXy();
|
||||||
|
@ -260,13 +269,13 @@ public class Setters {
|
||||||
target.setColorXy(sourceColor);
|
target.setColorXy(sourceColor);
|
||||||
targetColor = target.getColorXy();
|
targetColor = target.getColorXy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// color gamut
|
// color gamut
|
||||||
Gamut targetGamut = Objects.nonNull(targetColor) ? targetColor.getGamut() : null;
|
Gamut sourceGamut = Objects.isNull(sourceColor) ? null : sourceColor.getGamut();
|
||||||
Gamut sourceGamut = Objects.nonNull(sourceColor) ? sourceColor.getGamut() : null;
|
if (Objects.nonNull(targetColor) && Objects.nonNull(sourceGamut)) {
|
||||||
if (Objects.isNull(targetGamut) && Objects.nonNull(sourceGamut)) {
|
|
||||||
targetColor = Objects.nonNull(targetColor) ? targetColor : new ColorXy();
|
|
||||||
targetColor.setGamut(sourceGamut);
|
targetColor.setGamut(sourceGamut);
|
||||||
}
|
}
|
||||||
|
|
||||||
// color temperature
|
// color temperature
|
||||||
ColorTemperature targetColorTemp = target.getColorTemperature();
|
ColorTemperature targetColorTemp = target.getColorTemperature();
|
||||||
ColorTemperature sourceColorTemp = source.getColorTemperature();
|
ColorTemperature sourceColorTemp = source.getColorTemperature();
|
||||||
|
@ -274,40 +283,65 @@ public class Setters {
|
||||||
target.setColorTemperature(sourceColorTemp);
|
target.setColorTemperature(sourceColorTemp);
|
||||||
targetColorTemp = target.getColorTemperature();
|
targetColorTemp = target.getColorTemperature();
|
||||||
}
|
}
|
||||||
|
|
||||||
// mirek schema
|
// mirek schema
|
||||||
MirekSchema targetMirekSchema = Objects.nonNull(targetColorTemp) ? targetColorTemp.getMirekSchema() : null;
|
if (Objects.nonNull(targetColorTemp)) {
|
||||||
MirekSchema sourceMirekSchema = Objects.nonNull(sourceColorTemp) ? sourceColorTemp.getMirekSchema() : null;
|
MirekSchema sourceMirekSchema = Objects.isNull(sourceColorTemp) ? null : sourceColorTemp.getMirekSchema();
|
||||||
if (Objects.isNull(targetMirekSchema) && Objects.nonNull(sourceMirekSchema)) {
|
if (Objects.nonNull(sourceMirekSchema)) {
|
||||||
targetColorTemp = Objects.nonNull(targetColorTemp) ? targetColorTemp : new ColorTemperature();
|
targetColorTemp.setMirekSchema(sourceMirekSchema);
|
||||||
targetColorTemp.setMirekSchema(sourceMirekSchema);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// metadata
|
// metadata
|
||||||
MetaData targetMetaData = target.getMetaData();
|
MetaData targetMetaData = target.getMetaData();
|
||||||
MetaData sourceMetaData = source.getMetaData();
|
MetaData sourceMetaData = source.getMetaData();
|
||||||
if (Objects.isNull(targetMetaData) && Objects.nonNull(sourceMetaData)) {
|
if (Objects.isNull(targetMetaData) && Objects.nonNull(sourceMetaData)) {
|
||||||
target.setMetadata(sourceMetaData);
|
target.setMetadata(sourceMetaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// alerts
|
// alerts
|
||||||
Alerts targetAlerts = target.getAlerts();
|
Alerts targetAlerts = target.getAlerts();
|
||||||
Alerts sourceAlerts = source.getAlerts();
|
Alerts sourceAlerts = source.getAlerts();
|
||||||
if (Objects.isNull(targetAlerts) && Objects.nonNull(sourceAlerts)) {
|
if (Objects.isNull(targetAlerts) && Objects.nonNull(sourceAlerts)) {
|
||||||
target.setAlerts(sourceAlerts);
|
target.setAlerts(sourceAlerts);
|
||||||
}
|
}
|
||||||
// effects
|
|
||||||
Effects targetEffects = target.getEffects();
|
// fixed effects
|
||||||
Effects sourceEffects = source.getEffects();
|
Effects targetFixedEffects = target.getFixedEffects();
|
||||||
if (Objects.isNull(targetEffects) && Objects.nonNull(sourceEffects)) {
|
Effects sourceFixedEffects = source.getFixedEffects();
|
||||||
targetEffects = sourceEffects;
|
if (Objects.isNull(targetFixedEffects) && Objects.nonNull(sourceFixedEffects)) {
|
||||||
target.setEffects(sourceEffects);
|
target.setFixedEffects(sourceFixedEffects);
|
||||||
targetEffects = target.getEffects();
|
targetFixedEffects = target.getFixedEffects();
|
||||||
}
|
}
|
||||||
// effects values
|
|
||||||
List<String> targetStatusValues = Objects.nonNull(targetEffects) ? targetEffects.getStatusValues() : null;
|
// fixed effects allowed values
|
||||||
List<String> sourceStatusValues = Objects.nonNull(sourceEffects) ? sourceEffects.getStatusValues() : null;
|
if (Objects.nonNull(targetFixedEffects)) {
|
||||||
if (Objects.isNull(targetStatusValues) && Objects.nonNull(sourceStatusValues)) {
|
List<String> values = Objects.isNull(sourceFixedEffects) ? List.of() : sourceFixedEffects.getStatusValues();
|
||||||
targetEffects = Objects.nonNull(targetEffects) ? targetEffects : new Effects();
|
if (!values.isEmpty()) {
|
||||||
targetEffects.setStatusValues(sourceStatusValues);
|
targetFixedEffects.setStatusValues(values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timed effects
|
||||||
|
TimedEffects targetTimedEffects = target.getTimedEffects();
|
||||||
|
TimedEffects sourceTimedEffects = source.getTimedEffects();
|
||||||
|
if (Objects.isNull(targetTimedEffects) && Objects.nonNull(sourceTimedEffects)) {
|
||||||
|
target.setTimedEffects(sourceTimedEffects);
|
||||||
|
targetTimedEffects = target.getTimedEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// timed effects allowed values and duration
|
||||||
|
if (Objects.nonNull(targetTimedEffects)) {
|
||||||
|
List<String> values = Objects.isNull(sourceTimedEffects) ? List.of() : sourceTimedEffects.getStatusValues();
|
||||||
|
if (!values.isEmpty()) {
|
||||||
|
targetTimedEffects.setStatusValues(values);
|
||||||
|
}
|
||||||
|
Duration duration = Objects.isNull(sourceTimedEffects) ? null : sourceTimedEffects.getDuration();
|
||||||
|
if (Objects.nonNull(duration)) {
|
||||||
|
targetTimedEffects.setDuration(duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
@ -45,6 +46,7 @@ import org.openhab.binding.hue.internal.dto.clip2.ProductData;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Resources;
|
import org.openhab.binding.hue.internal.dto.clip2.Resources;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
||||||
|
@ -337,8 +339,7 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CHANNEL_2_EFFECT:
|
case CHANNEL_2_EFFECT:
|
||||||
putResource = Setters.setEffect(new Resource(lightResourceType), command, cache);
|
putResource = Setters.setEffect(new Resource(lightResourceType), command, cache).setOnOff(OnOffType.ON);
|
||||||
putResource.setOnOff(OnOffType.ON);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CHANNEL_2_COLOR_TEMP_PERCENT:
|
case CHANNEL_2_COLOR_TEMP_PERCENT:
|
||||||
|
@ -487,6 +488,8 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
&& !dynamicsDuration.isNegative()) {
|
&& !dynamicsDuration.isNegative()) {
|
||||||
if (ResourceType.SCENE == putResource.getType()) {
|
if (ResourceType.SCENE == putResource.getType()) {
|
||||||
putResource.setRecallDuration(dynamicsDuration);
|
putResource.setRecallDuration(dynamicsDuration);
|
||||||
|
} else if (CHANNEL_2_EFFECT == channelId) {
|
||||||
|
putResource.setTimedEffectsDuration(dynamicsDuration);
|
||||||
} else {
|
} else {
|
||||||
putResource.setDynamicsDuration(dynamicsDuration);
|
putResource.setDynamicsDuration(dynamicsDuration);
|
||||||
}
|
}
|
||||||
|
@ -945,21 +948,23 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the incoming Resource to initialize the effects channel.
|
* Process the incoming Resource to initialize the fixed resp. timed effects channel.
|
||||||
*
|
*
|
||||||
* @param resource a Resource possibly with an Effects element.
|
* @param resource a Resource possibly containing a fixed and/or timed effects element.
|
||||||
*/
|
*/
|
||||||
public void updateEffectChannel(Resource resource) {
|
public void updateEffectChannel(Resource resource) {
|
||||||
Effects effects = resource.getEffects();
|
Effects fixedEffects = resource.getFixedEffects();
|
||||||
if (Objects.nonNull(effects)) {
|
TimedEffects timedEffects = resource.getTimedEffects();
|
||||||
List<StateOption> stateOptions = effects.getStatusValues().stream()
|
List<StateOption> stateOptions = Stream
|
||||||
.map(effect -> EffectType.of(effect).name()).map(effectId -> new StateOption(effectId, effectId))
|
.concat(Objects.nonNull(fixedEffects) ? fixedEffects.getStatusValues().stream() : Stream.empty(),
|
||||||
.collect(Collectors.toList());
|
Objects.nonNull(timedEffects) ? timedEffects.getStatusValues().stream() : Stream.empty())
|
||||||
if (!stateOptions.isEmpty()) {
|
.map(effect -> {
|
||||||
stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_2_EFFECT),
|
String effectName = EffectType.of(effect).name();
|
||||||
stateOptions);
|
return new StateOption(effectName, effectName);
|
||||||
logger.debug("{} -> updateEffects() found {} effects", resourceId, stateOptions.size());
|
}).distinct().collect(Collectors.toList());
|
||||||
}
|
if (!stateOptions.isEmpty()) {
|
||||||
|
stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_2_EFFECT), stateOptions);
|
||||||
|
logger.debug("{} -> updateEffects() found {} effects", resourceId, stateOptions.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.io.BufferedReader;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -28,6 +29,7 @@ import org.openhab.binding.hue.internal.dto.clip2.ActionEntry;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Alerts;
|
import org.openhab.binding.hue.internal.dto.clip2.Alerts;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Button;
|
import org.openhab.binding.hue.internal.dto.clip2.Button;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Dimming;
|
import org.openhab.binding.hue.internal.dto.clip2.Dimming;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Effects;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Event;
|
import org.openhab.binding.hue.internal.dto.clip2.Event;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.LightLevel;
|
import org.openhab.binding.hue.internal.dto.clip2.LightLevel;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
|
@ -42,11 +44,13 @@ import org.openhab.binding.hue.internal.dto.clip2.Resources;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Rotation;
|
import org.openhab.binding.hue.internal.dto.clip2.Rotation;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
|
import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Temperature;
|
import org.openhab.binding.hue.internal.dto.clip2.Temperature;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
||||||
|
@ -597,4 +601,115 @@ class Clip2DtoTest {
|
||||||
assertEquals("db4fd630-3798-40de-b642-c1ef464bf770", service.getId());
|
assertEquals("db4fd630-3798-40de-b642-c1ef464bf770", service.getId());
|
||||||
assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
|
assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFixedEffectSetter() {
|
||||||
|
Resource source;
|
||||||
|
Resource target;
|
||||||
|
Effects resultEffect;
|
||||||
|
|
||||||
|
// no source effects
|
||||||
|
source = new Resource(ResourceType.LIGHT);
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
assertNull(target.getFixedEffects());
|
||||||
|
|
||||||
|
// valid source fixed effects
|
||||||
|
source = new Resource(ResourceType.LIGHT).setFixedEffects(
|
||||||
|
new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getFixedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
|
||||||
|
assertEquals(3, resultEffect.getStatusValues().size());
|
||||||
|
|
||||||
|
// valid but different source and target fixed effects
|
||||||
|
source = new Resource(ResourceType.LIGHT).setFixedEffects(
|
||||||
|
new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
|
||||||
|
target = new Resource(ResourceType.LIGHT).setFixedEffects(
|
||||||
|
new Effects().setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getFixedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
|
||||||
|
assertEquals(3, resultEffect.getStatusValues().size());
|
||||||
|
|
||||||
|
// partly valid source fixed effects
|
||||||
|
source = new Resource(ResourceType.LIGHT).setFixedEffects(new Effects().setStatusValues(List.of("SPARKLE"))
|
||||||
|
.setEffect(EffectType.SPARKLE).setStatusValues(List.of()));
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getFixedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
|
||||||
|
assertEquals(0, resultEffect.getStatusValues().size());
|
||||||
|
assertFalse(resultEffect.allows(EffectType.SPARKLE));
|
||||||
|
assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTimedEffectSetter() {
|
||||||
|
Resource source;
|
||||||
|
Resource target;
|
||||||
|
Effects resultEffect;
|
||||||
|
|
||||||
|
// no source effects
|
||||||
|
source = new Resource(ResourceType.LIGHT);
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
assertNull(target.getTimedEffects());
|
||||||
|
|
||||||
|
// valid source timed effects
|
||||||
|
source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
|
||||||
|
.setStatusValues(List.of("NO_EFFECT", "SUNRISE")).setEffect(EffectType.NO_EFFECT));
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getTimedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertEquals(EffectType.NO_EFFECT, resultEffect.getEffect());
|
||||||
|
assertEquals(2, resultEffect.getStatusValues().size());
|
||||||
|
|
||||||
|
// valid but different source and target timed effects
|
||||||
|
source = new Resource(ResourceType.LIGHT)
|
||||||
|
.setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMinutes(11))
|
||||||
|
.setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
|
||||||
|
target = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
|
||||||
|
.setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getTimedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
|
||||||
|
assertEquals(3, resultEffect.getStatusValues().size());
|
||||||
|
assertTrue(resultEffect instanceof TimedEffects);
|
||||||
|
assertEquals(Duration.ofMinutes(11), ((TimedEffects) resultEffect).getDuration());
|
||||||
|
|
||||||
|
// partly valid source timed effects
|
||||||
|
source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
|
||||||
|
.setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getTimedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertEquals(EffectType.SUNRISE, resultEffect.getEffect());
|
||||||
|
assertEquals(0, resultEffect.getStatusValues().size());
|
||||||
|
assertFalse(resultEffect.allows(EffectType.SPARKLE));
|
||||||
|
assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
|
||||||
|
assertTrue(resultEffect instanceof TimedEffects);
|
||||||
|
assertNull(((TimedEffects) resultEffect).getDuration());
|
||||||
|
|
||||||
|
target.setTimedEffectsDuration(Duration.ofSeconds(22));
|
||||||
|
assertEquals(Duration.ofSeconds(22), ((TimedEffects) resultEffect).getDuration());
|
||||||
|
|
||||||
|
// source timed effect with duration
|
||||||
|
source = new Resource(ResourceType.LIGHT)
|
||||||
|
.setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMillis(44))
|
||||||
|
.setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
|
||||||
|
target = new Resource(ResourceType.LIGHT);
|
||||||
|
Setters.setResource(target, source);
|
||||||
|
resultEffect = target.getTimedEffects();
|
||||||
|
assertNotNull(resultEffect);
|
||||||
|
assertTrue(resultEffect instanceof TimedEffects);
|
||||||
|
assertEquals(Duration.ofMillis(44), ((TimedEffects) resultEffect).getDuration());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue