[hue] Support timed effects (#15408)

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
pull/15756/head
Andrew Fiddian-Green 2023-10-14 18:30:15 +01:00 committed by GitHub
parent 4b0c551065
commit 247c0973b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 254 additions and 57 deletions

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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