[hue] Support smart scenes (#15388)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
a8c1d0927f
commit
a0bc1e0f8b
|
@ -16,10 +16,11 @@ import java.time.Duration;
|
||||||
|
|
||||||
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.RecallAction;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO for scene recall.
|
* DTO for scene and smart scene recall.
|
||||||
*
|
*
|
||||||
* @author Andrew Fiddian-Green - Initial contribution
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@ -29,7 +30,12 @@ public class Recall {
|
||||||
private @Nullable @SuppressWarnings("unused") String status;
|
private @Nullable @SuppressWarnings("unused") String status;
|
||||||
private @Nullable @SuppressWarnings("unused") Long duration;
|
private @Nullable @SuppressWarnings("unused") Long duration;
|
||||||
|
|
||||||
public Recall setAction(RecallAction action) {
|
public Recall setAction(SceneRecallAction action) {
|
||||||
|
this.action = action.name().toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Recall setAction(SmartSceneRecallAction action) {
|
||||||
this.action = action.name().toLowerCase();
|
this.action = action.name().toLowerCase();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,10 @@ 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.EffectType;
|
||||||
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.SceneRecallAction;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneState;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
||||||
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
@ -94,6 +96,7 @@ public class Resource {
|
||||||
private @Nullable List<ResourceReference> children;
|
private @Nullable List<ResourceReference> children;
|
||||||
private @Nullable JsonElement status;
|
private @Nullable JsonElement status;
|
||||||
private @Nullable @SuppressWarnings("unused") Dynamics dynamics;
|
private @Nullable @SuppressWarnings("unused") Dynamics dynamics;
|
||||||
|
private @Nullable String state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -507,8 +510,34 @@ public class Resource {
|
||||||
* @return either 'UnDefType.NULL', a StringType containing the (active) scene name, or 'UnDefType.UNDEF'.
|
* @return either 'UnDefType.NULL', a StringType containing the (active) scene name, or 'UnDefType.UNDEF'.
|
||||||
*/
|
*/
|
||||||
public State getSceneState() {
|
public State getSceneState() {
|
||||||
Optional<Boolean> active = getSceneActive();
|
return getSceneActive().map(a -> a ? new StringType(getName()) : UnDefType.UNDEF).orElse(UnDefType.NULL);
|
||||||
return active.isEmpty() ? UnDefType.NULL : active.get() ? new StringType(getName()) : UnDefType.UNDEF;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the smart scene resource contains a 'state' element. If such an element is present, returns a Boolean
|
||||||
|
* Optional whose value depends on the value of that element, or an empty Optional if it is not.
|
||||||
|
*
|
||||||
|
* @return true, false, or empty.
|
||||||
|
*/
|
||||||
|
public Optional<Boolean> getSmartSceneActive() {
|
||||||
|
if (ResourceType.SMART_SCENE == getType()) {
|
||||||
|
String state = this.state;
|
||||||
|
if (Objects.nonNull(state)) {
|
||||||
|
return Optional.of(SmartSceneState.ACTIVE == SmartSceneState.of(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the getSmartSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result
|
||||||
|
* is present and 'true' (i.e. the scene is active) return the smart scene name. Or finally (the optional result is
|
||||||
|
* present and 'false') return 'UnDefType.UNDEF'.
|
||||||
|
*
|
||||||
|
* @return either 'UnDefType.NULL', a StringType containing the (active) scene name, or 'UnDefType.UNDEF'.
|
||||||
|
*/
|
||||||
|
public State getSmartSceneState() {
|
||||||
|
return getSmartSceneActive().map(a -> a ? new StringType(getName()) : UnDefType.UNDEF).orElse(UnDefType.NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceReference> getServiceReferences() {
|
public List<ResourceReference> getServiceReferences() {
|
||||||
|
@ -649,7 +678,13 @@ public class Resource {
|
||||||
this.on = on;
|
this.on = on;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource setRecallAction(RecallAction recallAction) {
|
public Resource setRecallAction(SceneRecallAction recallAction) {
|
||||||
|
Recall recall = this.recall;
|
||||||
|
this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setAction(recallAction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setRecallAction(SmartSceneRecallAction recallAction) {
|
||||||
Recall recall = this.recall;
|
Recall recall = this.recall;
|
||||||
this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setAction(recallAction);
|
this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setAction(recallAction);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -47,6 +47,7 @@ public enum ResourceType {
|
||||||
ROOM,
|
ROOM,
|
||||||
RELATIVE_ROTARY,
|
RELATIVE_ROTARY,
|
||||||
SCENE,
|
SCENE,
|
||||||
|
SMART_SCENE,
|
||||||
TEMPERATURE,
|
TEMPERATURE,
|
||||||
ZGP_CONNECTIVITY,
|
ZGP_CONNECTIVITY,
|
||||||
ZIGBEE_CONNECTIVITY,
|
ZIGBEE_CONNECTIVITY,
|
||||||
|
|
|
@ -21,12 +21,13 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||||
* @author Andrew Fiddian-Green - Initial contribution
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public enum RecallAction {
|
public enum SceneRecallAction {
|
||||||
ACTIVE,
|
ACTIVE,
|
||||||
DYNAMIC_PALETTE,
|
DYNAMIC_PALETTE,
|
||||||
STATIC;
|
STATIC,
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
public static RecallAction of(@Nullable String value) {
|
public static SceneRecallAction of(@Nullable String value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
try {
|
try {
|
||||||
return valueOf(value.toUpperCase());
|
return valueOf(value.toUpperCase());
|
||||||
|
@ -34,6 +35,6 @@ public enum RecallAction {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ACTIVE;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for smart scene recall actions.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum SmartSceneRecallAction {
|
||||||
|
ACTIVATE,
|
||||||
|
DEACTIVATE,
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
|
public static SmartSceneRecallAction of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for 'smart_scene' states.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum SmartSceneState {
|
||||||
|
INACTIVE,
|
||||||
|
ACTIVE,
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
|
public static SmartSceneState of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,6 +91,7 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||||
private static final ResourceReference BRIDGE = new ResourceReference().setType(ResourceType.BRIDGE);
|
private static final ResourceReference BRIDGE = new ResourceReference().setType(ResourceType.BRIDGE);
|
||||||
private static final ResourceReference BRIDGE_HOME = new ResourceReference().setType(ResourceType.BRIDGE_HOME);
|
private static final ResourceReference BRIDGE_HOME = new ResourceReference().setType(ResourceType.BRIDGE_HOME);
|
||||||
private static final ResourceReference SCENE = new ResourceReference().setType(ResourceType.SCENE);
|
private static final ResourceReference SCENE = new ResourceReference().setType(ResourceType.SCENE);
|
||||||
|
private static final ResourceReference SMART_SCENE = new ResourceReference().setType(ResourceType.SMART_SCENE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of resource references that need to be mass down loaded.
|
* List of resource references that need to be mass down loaded.
|
||||||
|
@ -729,9 +730,19 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||||
for (ResourceReference reference : MASS_DOWNLOAD_RESOURCE_REFERENCES) {
|
for (ResourceReference reference : MASS_DOWNLOAD_RESOURCE_REFERENCES) {
|
||||||
ResourceType resourceType = reference.getType();
|
ResourceType resourceType = reference.getType();
|
||||||
List<Resource> resourceList = bridge.getResources(reference).getResources();
|
List<Resource> resourceList = bridge.getResources(reference).getResources();
|
||||||
if (resourceType == ResourceType.ZONE) {
|
switch (resourceType) {
|
||||||
|
case ZONE:
|
||||||
// add special 'All Lights' zone to the zone resource list
|
// add special 'All Lights' zone to the zone resource list
|
||||||
resourceList.addAll(bridge.getResources(BRIDGE_HOME).getResources());
|
resourceList.addAll(bridge.getResources(BRIDGE_HOME).getResources());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCENE:
|
||||||
|
// add 'smart scenes' to the scene resource list
|
||||||
|
resourceList.addAll(bridge.getResources(SMART_SCENE).getResources());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
getThing().getThings().forEach(thing -> {
|
getThing().getThings().forEach(thing -> {
|
||||||
ThingHandler handler = thing.getHandler();
|
ThingHandler handler = thing.getHandler();
|
||||||
|
|
|
@ -17,6 +17,7 @@ import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -49,8 +50,9 @@ 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.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.ResourceType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
|
import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
|
||||||
import org.openhab.binding.hue.internal.exceptions.ApiException;
|
import org.openhab.binding.hue.internal.exceptions.ApiException;
|
||||||
|
@ -99,6 +101,8 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM,
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM,
|
||||||
THING_TYPE_ZONE);
|
THING_TYPE_ZONE);
|
||||||
|
|
||||||
|
private static final Set<ResourceType> SUPPORTED_SCENE_TYPES = Set.of(ResourceType.SCENE, ResourceType.SMART_SCENE);
|
||||||
|
|
||||||
private static final Duration DYNAMICS_ACTIVE_WINDOW = Duration.ofSeconds(10);
|
private static final Duration DYNAMICS_ACTIVE_WINDOW = Duration.ofSeconds(10);
|
||||||
|
|
||||||
private static final String LK_WISER_DIMMER_MODEL_ID = "LK Dimmer";
|
private static final String LK_WISER_DIMMER_MODEL_ID = "LK Dimmer";
|
||||||
|
@ -136,16 +140,16 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
private final Set<String> supportedChannelIdSet = new HashSet<>();
|
private final Set<String> supportedChannelIdSet = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of scene IDs and respective scene Resources for the scenes that contribute to and command this thing. It is
|
* A map of scene IDs versus scene Resources for the scenes that contribute to and command this thing. It is a map
|
||||||
* a map between the resource ID (string) and a Resource object containing the scene's last known state.
|
* between the resource ID (string) and a Resource object containing the scene's last known state.
|
||||||
*/
|
*/
|
||||||
private final Map<String, Resource> sceneContributorsCache = new ConcurrentHashMap<>();
|
private final Map<String, Resource> sceneContributorsCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of scene names versus Resource IDs for the scenes that contribute to and command this thing. e.g. a command
|
* A map of scene names versus scene Resources for the scenes that contribute to and command this thing. e.g. a
|
||||||
* for a scene named 'Energize' shall be sent to the respective SCENE resource ID.
|
* command for a scene named 'Energize' shall be sent to the respective SCENE resource ID.
|
||||||
*/
|
*/
|
||||||
private final Map<String, String> sceneResourceIds = new ConcurrentHashMap<>();
|
private final Map<String, Resource> sceneResourceEntries = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of API v1 thing channel UIDs that are linked to items. It is used in the process of replicating the
|
* A list of API v1 thing channel UIDs that are linked to items. It is used in the process of replicating the
|
||||||
|
@ -248,7 +252,7 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
updateServiceContributorsTask = null;
|
updateServiceContributorsTask = null;
|
||||||
legacyLinkedChannelUIDs.clear();
|
legacyLinkedChannelUIDs.clear();
|
||||||
sceneContributorsCache.clear();
|
sceneContributorsCache.clear();
|
||||||
sceneResourceIds.clear();
|
sceneResourceEntries.clear();
|
||||||
supportedChannelIdSet.clear();
|
supportedChannelIdSet.clear();
|
||||||
commandResourceIds.clear();
|
commandResourceIds.clear();
|
||||||
serviceContributorsCache.clear();
|
serviceContributorsCache.clear();
|
||||||
|
@ -426,9 +430,23 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
|
|
||||||
case CHANNEL_2_SCENE:
|
case CHANNEL_2_SCENE:
|
||||||
if (command instanceof StringType) {
|
if (command instanceof StringType) {
|
||||||
putResourceId = sceneResourceIds.get(((StringType) command).toString());
|
Resource scene = sceneResourceEntries.get(((StringType) command).toString());
|
||||||
if (Objects.nonNull(putResourceId)) {
|
if (Objects.nonNull(scene)) {
|
||||||
putResource = new Resource(ResourceType.SCENE).setRecallAction(RecallAction.ACTIVE);
|
ResourceType putResourceType = scene.getType();
|
||||||
|
putResource = new Resource(putResourceType);
|
||||||
|
switch (putResourceType) {
|
||||||
|
case SCENE:
|
||||||
|
putResource.setRecallAction(SceneRecallAction.ACTIVE);
|
||||||
|
break;
|
||||||
|
case SMART_SCENE:
|
||||||
|
putResource.setRecallAction(SmartSceneRecallAction.ACTIVATE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.debug("{} -> handleCommand() type '{}' is not a supported scene type",
|
||||||
|
resourceId, putResourceType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
putResourceId = scene.getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -624,7 +642,7 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
cancelTask(updateDependenciesTask, false);
|
cancelTask(updateDependenciesTask, false);
|
||||||
updateDependenciesTask = scheduler.submit(() -> updateDependencies());
|
updateDependenciesTask = scheduler.submit(() -> updateDependencies());
|
||||||
}
|
}
|
||||||
} else if (ResourceType.SCENE == resource.getType()) {
|
} else if (SUPPORTED_SCENE_TYPES.contains(resource.getType())) {
|
||||||
Resource cachedScene = sceneContributorsCache.get(incomingResourceId);
|
Resource cachedScene = sceneContributorsCache.get(incomingResourceId);
|
||||||
if (Objects.nonNull(cachedScene)) {
|
if (Objects.nonNull(cachedScene)) {
|
||||||
Setters.setResource(resource, cachedScene);
|
Setters.setResource(resource, cachedScene);
|
||||||
|
@ -876,6 +894,10 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
updateState(CHANNEL_2_SCENE, resource.getSceneState(), fullUpdate);
|
updateState(CHANNEL_2_SCENE, resource.getSceneState(), fullUpdate);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SMART_SCENE:
|
||||||
|
updateState(CHANNEL_2_SCENE, resource.getSmartSceneState(), fullUpdate);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1096,7 +1118,8 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the full list of scenes from the bridge, and call {@code updateSceneContributors(List<Resource> allScenes)}
|
* Fetch the full list of normal resp. smart scenes from the bridge, and call
|
||||||
|
* {@code updateSceneContributors(List<Resource> allScenes)}
|
||||||
*
|
*
|
||||||
* @throws ApiException if a communication error occurred.
|
* @throws ApiException if a communication error occurred.
|
||||||
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
||||||
|
@ -1104,22 +1127,26 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
*/
|
*/
|
||||||
public boolean updateSceneContributors() throws ApiException, AssetNotLoadedException, InterruptedException {
|
public boolean updateSceneContributors() throws ApiException, AssetNotLoadedException, InterruptedException {
|
||||||
if (!disposing && !updateSceneContributorsDone) {
|
if (!disposing && !updateSceneContributorsDone) {
|
||||||
ResourceReference scenesReference = new ResourceReference().setType(ResourceType.SCENE);
|
List<Resource> allScenes = new ArrayList<>();
|
||||||
updateSceneContributors(getBridgeHandler().getResources(scenesReference).getResources());
|
for (ResourceType type : SUPPORTED_SCENE_TYPES) {
|
||||||
|
allScenes.addAll(getBridgeHandler().getResources(new ResourceReference().setType(type)).getResources());
|
||||||
|
}
|
||||||
|
updateSceneContributors(allScenes);
|
||||||
}
|
}
|
||||||
return updateSceneContributorsDone;
|
return updateSceneContributorsDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the incoming list of scene resources to find those scenes which contribute to this thing. And if there
|
* Process the incoming list of normal resp. smart scene resources to find those which contribute to this thing. And
|
||||||
* are any, include a scene channel in the supported channel list, and populate its respective state options.
|
* if there are any, include a scene channel in the supported channel list, and populate its respective state
|
||||||
|
* options.
|
||||||
*
|
*
|
||||||
* @param allScenes the full list of scene resources.
|
* @param allScenes the full list of normal resp. smart scene resources.
|
||||||
*/
|
*/
|
||||||
public synchronized boolean updateSceneContributors(List<Resource> allScenes) {
|
public synchronized boolean updateSceneContributors(List<Resource> allScenes) {
|
||||||
if (!disposing && !updateSceneContributorsDone) {
|
if (!disposing && !updateSceneContributorsDone) {
|
||||||
sceneContributorsCache.clear();
|
sceneContributorsCache.clear();
|
||||||
sceneResourceIds.clear();
|
sceneResourceEntries.clear();
|
||||||
|
|
||||||
ResourceReference thisReference = getResourceReference();
|
ResourceReference thisReference = getResourceReference();
|
||||||
List<Resource> scenes = allScenes.stream().filter(s -> thisReference.equals(s.getGroup()))
|
List<Resource> scenes = allScenes.stream().filter(s -> thisReference.equals(s.getGroup()))
|
||||||
|
@ -1127,16 +1154,18 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
||||||
|
|
||||||
if (!scenes.isEmpty()) {
|
if (!scenes.isEmpty()) {
|
||||||
sceneContributorsCache.putAll(scenes.stream().collect(Collectors.toMap(s -> s.getId(), s -> s)));
|
sceneContributorsCache.putAll(scenes.stream().collect(Collectors.toMap(s -> s.getId(), s -> s)));
|
||||||
sceneResourceIds.putAll(scenes.stream().collect(Collectors.toMap(s -> s.getName(), s -> s.getId())));
|
sceneResourceEntries.putAll(scenes.stream().collect(Collectors.toMap(s -> s.getName(), s -> s)));
|
||||||
|
|
||||||
State state = scenes.stream().filter(s -> s.getSceneActive().orElse(false)).map(s -> s.getSceneState())
|
State state = scenes.stream().filter(s -> s.getSceneActive().orElse(false)).map(s -> s.getSceneState())
|
||||||
.findAny().orElse(UnDefType.UNDEF);
|
.findAny().orElse(UnDefType.UNDEF);
|
||||||
|
|
||||||
updateState(CHANNEL_2_SCENE, state, true);
|
updateState(CHANNEL_2_SCENE, state, true);
|
||||||
|
|
||||||
stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_2_SCENE), scenes
|
stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_2_SCENE), scenes
|
||||||
.stream().map(s -> s.getName()).map(n -> new StateOption(n, n)).collect(Collectors.toList()));
|
.stream().map(s -> s.getName()).map(n -> new StateOption(n, n)).collect(Collectors.toList()));
|
||||||
|
|
||||||
logger.debug("{} -> updateSceneContributors() found {} scenes", resourceId, scenes.size());
|
logger.debug("{} -> updateSceneContributors() found {} normal resp. smart scenes", resourceId,
|
||||||
|
scenes.size());
|
||||||
}
|
}
|
||||||
updateSceneContributorsDone = true;
|
updateSceneContributorsDone = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,6 +458,28 @@ class Clip2DtoTest {
|
||||||
assertEquals(OnOffType.ON, action.getOnOffState());
|
assertEquals(OnOffType.ON, action.getOnOffState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSmartScene() {
|
||||||
|
String json = load(ResourceType.SMART_SCENE.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
ResourceReference group = item.getGroup();
|
||||||
|
assertNotNull(group);
|
||||||
|
String groupId = group.getId();
|
||||||
|
assertNotNull(groupId);
|
||||||
|
assertFalse(groupId.isBlank());
|
||||||
|
ResourceType type = group.getType();
|
||||||
|
assertNotNull(type);
|
||||||
|
assertEquals(ResourceType.ROOM, type);
|
||||||
|
Optional<Boolean> state = item.getSmartSceneActive();
|
||||||
|
assertTrue(state.isPresent());
|
||||||
|
assertFalse(state.get());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSensor2Motion() {
|
void testSensor2Motion() {
|
||||||
String json = load(ResourceType.MOTION.name().toLowerCase());
|
String json = load(ResourceType.MOTION.name().toLowerCase());
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
{
|
||||||
|
"errors": [
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "0707ec71-8d7a-4bf8-91ca-71db273ddfe9",
|
||||||
|
"type": "smart_scene",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Natural light",
|
||||||
|
"image": {
|
||||||
|
"rid": "eb014820-a902-4652-8ca7-6e29c03b87a1",
|
||||||
|
"rtype": "public_image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"rid": "1166743f-fe3d-47d1-bd7d-b5bb378098cc",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"week_timeslots": [
|
||||||
|
{
|
||||||
|
"timeslots": [
|
||||||
|
{
|
||||||
|
"start_time": {
|
||||||
|
"kind": "time",
|
||||||
|
"time": {
|
||||||
|
"hour": 7,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"rid": "9c633a2d-f7bf-4bba-b5ee-a4d69ce6e050",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_time": {
|
||||||
|
"kind": "time",
|
||||||
|
"time": {
|
||||||
|
"hour": 10,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"rid": "7616d9fe-4889-472b-93bf-5090c19fb902",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_time": {
|
||||||
|
"kind": "sunset",
|
||||||
|
"time": {
|
||||||
|
"hour": 0,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"rid": "eeabcfdb-97db-4e4a-a94a-fe1fea8e6c2c",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_time": {
|
||||||
|
"kind": "time",
|
||||||
|
"time": {
|
||||||
|
"hour": 20,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"rid": "2f474323-0c6b-4361-a8c7-b52da902cd7b",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_time": {
|
||||||
|
"kind": "time",
|
||||||
|
"time": {
|
||||||
|
"hour": 22,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"rid": "6fa2a278-43ec-4586-bdd8-2fe6f4ea5f85",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_time": {
|
||||||
|
"kind": "time",
|
||||||
|
"time": {
|
||||||
|
"hour": 0,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"rid": "6ca12be2-c547-4722-8db0-6e12f20688f3",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recurrence": [
|
||||||
|
"sunday",
|
||||||
|
"monday",
|
||||||
|
"tuesday",
|
||||||
|
"wednesday",
|
||||||
|
"thursday",
|
||||||
|
"friday",
|
||||||
|
"saturday"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transition_duration": 60000,
|
||||||
|
"active_timeslot": {
|
||||||
|
"timeslot_id": 1,
|
||||||
|
"weekday": "wednesday"
|
||||||
|
},
|
||||||
|
"state": "inactive"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue