mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-26 15:21:41 +01:00
[hue] Add support for enabling automations (#16980)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
43cc09a2ce
commit
77561d5d8f
@ -55,6 +55,17 @@ See [console command](#console-command-for-finding-resourceids)
|
|||||||
|
|
||||||
The configuration of all things (as described above) is the same regardless of whether it is a device containing a light, a button, or (one or more) sensors, or whether it is a room or zone.
|
The configuration of all things (as described above) is the same regardless of whether it is a device containing a light, a button, or (one or more) sensors, or whether it is a room or zone.
|
||||||
|
|
||||||
|
### Channels for Bridges
|
||||||
|
|
||||||
|
Bridge Things support the following channels:
|
||||||
|
|
||||||
|
| Channel ID | Item Type | Description |
|
||||||
|
|-------------------------------------------------|--------------------|---------------------------------------------|
|
||||||
|
| automation#11111111-2222-3333-4444-555555555555 | Switch | Enable / disable the respective automation. |
|
||||||
|
|
||||||
|
The Bridge dynamically creates `automation` channels corresponding to the automations in the Hue App;
|
||||||
|
the '11111111-2222-3333-4444-555555555555' is the unique id of the respective automation.
|
||||||
|
|
||||||
### Channels for Devices
|
### Channels for Devices
|
||||||
|
|
||||||
Device things support some of the following channels:
|
Device things support some of the following channels:
|
||||||
|
@ -17,6 +17,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HueBindingConstants} class defines common constants, which are
|
* The {@link HueBindingConstants} class defines common constants, which are
|
||||||
@ -200,4 +201,7 @@ public class HueBindingConstants {
|
|||||||
Map.entry(CHANNEL_LAST_UPDATED, CHANNEL_2_LAST_UPDATED));
|
Map.entry(CHANNEL_LAST_UPDATED, CHANNEL_2_LAST_UPDATED));
|
||||||
|
|
||||||
public static final String ALL_LIGHTS_KEY = "discovery.group.all-lights.label";
|
public static final String ALL_LIGHTS_KEY = "discovery.group.all-lights.label";
|
||||||
|
|
||||||
|
public static final String CHANNEL_GROUP_AUTOMATION = "automation";
|
||||||
|
public static final ChannelTypeUID CHANNEL_TYPE_AUTOMATION = new ChannelTypeUID(BINDING_ID, "automation-enable");
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,14 @@
|
|||||||
package org.openhab.binding.hue.internal.api.dto.clip2;
|
package org.openhab.binding.hue.internal.api.dto.clip2;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
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.api.dto.clip2.enums.ContentType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +33,13 @@ public class Event {
|
|||||||
public static final Type EVENT_LIST_TYPE = new TypeToken<List<Event>>() {
|
public static final Type EVENT_LIST_TYPE = new TypeToken<List<Event>>() {
|
||||||
}.getType();
|
}.getType();
|
||||||
|
|
||||||
private @Nullable List<Resource> data = new ArrayList<>();
|
private @Nullable List<Resource> data;
|
||||||
|
private @Nullable @SerializedName("type") ContentType contentType; // content type of resources
|
||||||
|
|
||||||
|
public ContentType getContentType() {
|
||||||
|
ContentType contentType = this.contentType;
|
||||||
|
return Objects.nonNull(contentType) ? contentType : ContentType.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Resource> getData() {
|
public List<Resource> getData() {
|
||||||
List<Resource> data = this.data;
|
List<Resource> data = this.data;
|
||||||
|
@ -15,6 +15,7 @@ package org.openhab.binding.hue.internal.api.dto.clip2;
|
|||||||
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.api.dto.clip2.enums.Archetype;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
|
||||||
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.CategoryType;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ public class MetaData {
|
|||||||
private @Nullable String archetype;
|
private @Nullable String archetype;
|
||||||
private @Nullable String name;
|
private @Nullable String name;
|
||||||
private @Nullable @SerializedName("control_id") Integer controlId;
|
private @Nullable @SerializedName("control_id") Integer controlId;
|
||||||
|
private @Nullable String category;
|
||||||
|
|
||||||
public Archetype getArchetype() {
|
public Archetype getArchetype() {
|
||||||
return Archetype.of(archetype);
|
return Archetype.of(archetype);
|
||||||
@ -37,6 +39,10 @@ public class MetaData {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CategoryType getCategory() {
|
||||||
|
return CategoryType.of(category);
|
||||||
|
}
|
||||||
|
|
||||||
public int getControlId() {
|
public int getControlId() {
|
||||||
Integer controlId = this.controlId;
|
Integer controlId = this.controlId;
|
||||||
return controlId != null ? controlId.intValue() : 0;
|
return controlId != null ? controlId.intValue() : 0;
|
||||||
|
@ -28,7 +28,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType;
|
||||||
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.CategoryType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContactStateType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContactStateType;
|
||||||
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContentType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction;
|
||||||
@ -55,6 +57,7 @@ import org.openhab.core.util.ColorUtil.Gamut;
|
|||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,8 +77,16 @@ public class Resource {
|
|||||||
* values have changed. A sparse resource does not contain the full state of the resource. And the absence of any
|
* values have changed. A sparse resource does not contain the full state of the resource. And the absence of any
|
||||||
* field from such a resource does not indicate that the field value is UNDEF, but rather that the value is the same
|
* field from such a resource does not indicate that the field value is UNDEF, but rather that the value is the same
|
||||||
* as what it was previously set to by the last non-sparse resource.
|
* as what it was previously set to by the last non-sparse resource.
|
||||||
|
* <p>
|
||||||
|
* The following content types are defined:
|
||||||
|
*
|
||||||
|
* <li><b>ADD</b> resource being added; contains (assumed) all fields</li>
|
||||||
|
* <li><b>DELETE</b> resource being deleted; contains id and type only</li>
|
||||||
|
* <li><b>UPDATE</b> resource being updated; contains id, type and changed fields</li>
|
||||||
|
* <li><b>ERROR</b> resource with error; contents unknown</li>
|
||||||
|
* <li><b>FULL_STATE</b> existing resource being downloaded; contains all fields</li>
|
||||||
*/
|
*/
|
||||||
private transient boolean hasSparseData;
|
private transient ContentType contentType;
|
||||||
|
|
||||||
private @Nullable String type;
|
private @Nullable String type;
|
||||||
private @Nullable String id;
|
private @Nullable String id;
|
||||||
@ -107,7 +118,15 @@ public class Resource {
|
|||||||
private @Nullable Dynamics dynamics;
|
private @Nullable Dynamics dynamics;
|
||||||
private @Nullable @SerializedName("contact_report") ContactReport contactReport;
|
private @Nullable @SerializedName("contact_report") ContactReport contactReport;
|
||||||
private @Nullable @SerializedName("tamper_reports") List<TamperReport> tamperReports;
|
private @Nullable @SerializedName("tamper_reports") List<TamperReport> tamperReports;
|
||||||
private @Nullable String state;
|
private @Nullable JsonElement state;
|
||||||
|
private @Nullable @SerializedName("script_id") String scriptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public Resource() {
|
||||||
|
contentType = ContentType.FULL_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -115,6 +134,7 @@ public class Resource {
|
|||||||
* @param resourceType
|
* @param resourceType
|
||||||
*/
|
*/
|
||||||
public Resource(@Nullable ResourceType resourceType) {
|
public Resource(@Nullable ResourceType resourceType) {
|
||||||
|
this();
|
||||||
if (Objects.nonNull(resourceType)) {
|
if (Objects.nonNull(resourceType)) {
|
||||||
setType(resourceType);
|
setType(resourceType);
|
||||||
}
|
}
|
||||||
@ -343,6 +363,14 @@ public class Resource {
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the resource's metadata category.
|
||||||
|
*/
|
||||||
|
public CategoryType getCategory() {
|
||||||
|
MetaData metaData = getMetaData();
|
||||||
|
return Objects.nonNull(metaData) ? metaData.getCategory() : CategoryType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an HSB where the HS part is derived from the color xy JSON element (only), so the B part is 100%
|
* Return an HSB where the HS part is derived from the color xy JSON element (only), so the B part is 100%
|
||||||
*
|
*
|
||||||
@ -375,6 +403,10 @@ public class Resource {
|
|||||||
: OpenClosedType.OPEN;
|
: OpenClosedType.OPEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ContentType getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
public int getControlId() {
|
public int getControlId() {
|
||||||
MetaData metadata = this.metadata;
|
MetaData metadata = this.metadata;
|
||||||
return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
|
return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
|
||||||
@ -648,6 +680,13 @@ public class Resource {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the scriptId if any.
|
||||||
|
*/
|
||||||
|
public @Nullable String getScriptId() {
|
||||||
|
return scriptId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the getSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result is
|
* If the getSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result is
|
||||||
* present and 'true' (i.e. the scene is active) return the scene name. Or finally (the optional result is present
|
* present and 'true' (i.e. the scene is active) return the scene name. Or finally (the optional result is present
|
||||||
@ -661,13 +700,14 @@ public class Resource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the smart scene resource contains a 'state' element. If such an element is present, returns a Boolean
|
* 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.
|
* Optional whose value depends on the value of that element, or an empty Optional if it is not. Note that in some
|
||||||
|
* resource types the 'state' element is not a String primitive.
|
||||||
*
|
*
|
||||||
* @return true, false, or empty.
|
* @return true, false, or empty.
|
||||||
*/
|
*/
|
||||||
public Optional<Boolean> getSmartSceneActive() {
|
public Optional<Boolean> getSmartSceneActive() {
|
||||||
if (ResourceType.SMART_SCENE == getType()) {
|
if (ResourceType.SMART_SCENE == getType() && (state instanceof JsonPrimitive statePrimitive)) {
|
||||||
String state = this.state;
|
String state = statePrimitive.getAsString();
|
||||||
if (Objects.nonNull(state)) {
|
if (Objects.nonNull(state)) {
|
||||||
return Optional.of(SmartSceneState.ACTIVE == SmartSceneState.of(state));
|
return Optional.of(SmartSceneState.ACTIVE == SmartSceneState.of(state));
|
||||||
}
|
}
|
||||||
@ -785,17 +825,12 @@ public class Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasFullState() {
|
public boolean hasFullState() {
|
||||||
return !hasSparseData;
|
return ContentType.FULL_STATE == contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean hasName() {
|
||||||
* Mark that the resource has sparse data.
|
MetaData metaData = getMetaData();
|
||||||
*
|
return Objects.nonNull(metaData) && Objects.nonNull(metaData.getName());
|
||||||
* @return this instance.
|
|
||||||
*/
|
|
||||||
public Resource markAsSparse() {
|
|
||||||
hasSparseData = true;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource setAlerts(Alerts alert) {
|
public Resource setAlerts(Alerts alert) {
|
||||||
@ -818,6 +853,11 @@ public class Resource {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Resource setContentType(ContentType contentType) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Resource setDimming(@Nullable Dimming dimming) {
|
public Resource setDimming(@Nullable Dimming dimming) {
|
||||||
this.dimming = dimming;
|
this.dimming = dimming;
|
||||||
return this;
|
return this;
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.api.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for 'category' fields.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum CategoryType {
|
||||||
|
ACCESSORY,
|
||||||
|
AUTOMATION,
|
||||||
|
ENTERTAINMENT,
|
||||||
|
NULL,
|
||||||
|
UNDEF;
|
||||||
|
|
||||||
|
public static CategoryType of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return UNDEF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.api.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for content type of Resource instances
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum ContentType {
|
||||||
|
@SerializedName("add") // resource being added; contains (maybe) all fields
|
||||||
|
ADD,
|
||||||
|
@SerializedName("delete") // resource being deleted; contains id and type only
|
||||||
|
DELETE,
|
||||||
|
@SerializedName("update") // resource being updated; contains id, type and updated fields
|
||||||
|
UPDATE,
|
||||||
|
@SerializedName("error") // resource error event
|
||||||
|
ERROR,
|
||||||
|
// existing resource being downloaded; contains all fields; excluded from (de-)serialization
|
||||||
|
FULL_STATE
|
||||||
|
}
|
@ -921,12 +921,15 @@ public class Clip2Bridge implements Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<Resource> resources = new ArrayList<>();
|
List<Resource> resources = new ArrayList<>();
|
||||||
events.forEach(event -> resources.addAll(event.getData()));
|
events.forEach(event -> {
|
||||||
|
List<Resource> eventResources = event.getData();
|
||||||
|
eventResources.forEach(resource -> resource.setContentType(event.getContentType()));
|
||||||
|
resources.addAll(eventResources);
|
||||||
|
});
|
||||||
if (resources.isEmpty()) {
|
if (resources.isEmpty()) {
|
||||||
LOGGER.debug("onEventData() resource list is empty");
|
LOGGER.debug("onEventData() resource list is empty");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resources.forEach(resource -> resource.markAsSparse());
|
|
||||||
bridgeHandler.onResourcesEvent(resources);
|
bridgeHandler.onResourcesEvent(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@ -36,6 +38,7 @@ import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
|
|||||||
import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference;
|
import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.Resources;
|
import org.openhab.binding.hue.internal.api.dto.clip2.Resources;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
|
||||||
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.CategoryType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters;
|
import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters;
|
||||||
import org.openhab.binding.hue.internal.config.Clip2BridgeConfig;
|
import org.openhab.binding.hue.internal.config.Clip2BridgeConfig;
|
||||||
@ -50,7 +53,10 @@ import org.openhab.core.i18n.LocaleProvider;
|
|||||||
import org.openhab.core.i18n.TranslationProvider;
|
import org.openhab.core.i18n.TranslationProvider;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.io.net.http.TlsTrustManagerProvider;
|
import org.openhab.core.io.net.http.TlsTrustManagerProvider;
|
||||||
|
import org.openhab.core.library.CoreItemFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.Channel;
|
||||||
|
import org.openhab.core.thing.ChannelGroupUID;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingRegistry;
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
@ -62,6 +68,7 @@ import org.openhab.core.thing.binding.BaseBridgeHandler;
|
|||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
import org.openhab.core.thing.binding.builder.BridgeBuilder;
|
import org.openhab.core.thing.binding.builder.BridgeBuilder;
|
||||||
|
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
@ -93,6 +100,11 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
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);
|
private static final ResourceReference SMART_SCENE = new ResourceReference().setType(ResourceType.SMART_SCENE);
|
||||||
|
private static final ResourceReference SCRIPT = new ResourceReference().setType(ResourceType.BEHAVIOR_SCRIPT);
|
||||||
|
private static final ResourceReference BEHAVIOR = new ResourceReference().setType(ResourceType.BEHAVIOR_INSTANCE);
|
||||||
|
|
||||||
|
private static final String AUTOMATION_CHANNEL_LABEL_KEY = "dynamic-channel.automation-enable.label";
|
||||||
|
private static final String AUTOMATION_CHANNEL_DESCRIPTION_KEY = "dynamic-channel.automation-enable.description";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of resource references that need to be mass down loaded.
|
* List of resource references that need to be mass down loaded.
|
||||||
@ -107,11 +119,15 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
private final Bundle bundle;
|
private final Bundle bundle;
|
||||||
private final LocaleProvider localeProvider;
|
private final LocaleProvider localeProvider;
|
||||||
private final TranslationProvider translationProvider;
|
private final TranslationProvider translationProvider;
|
||||||
|
private final Map<String, Resource> automationsCache = new ConcurrentHashMap<>();;
|
||||||
|
private final Set<String> automationScriptIds = ConcurrentHashMap.newKeySet();
|
||||||
|
private final ChannelGroupUID automationChannelGroupUID;
|
||||||
|
|
||||||
private @Nullable Clip2Bridge clip2Bridge;
|
private @Nullable Clip2Bridge clip2Bridge;
|
||||||
private @Nullable ServiceRegistration<?> trustManagerRegistration;
|
private @Nullable ServiceRegistration<?> trustManagerRegistration;
|
||||||
private @Nullable Clip2ThingDiscoveryService discoveryService;
|
private @Nullable Clip2ThingDiscoveryService discoveryService;
|
||||||
|
|
||||||
|
private @Nullable Future<?> updateAutomationChannelsTask;
|
||||||
private @Nullable Future<?> checkConnectionTask;
|
private @Nullable Future<?> checkConnectionTask;
|
||||||
private @Nullable Future<?> updateOnlineStateTask;
|
private @Nullable Future<?> updateOnlineStateTask;
|
||||||
private @Nullable ScheduledFuture<?> scheduledUpdateTask;
|
private @Nullable ScheduledFuture<?> scheduledUpdateTask;
|
||||||
@ -129,6 +145,7 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
this.bundle = FrameworkUtil.getBundle(getClass());
|
this.bundle = FrameworkUtil.getBundle(getClass());
|
||||||
this.localeProvider = localeProvider;
|
this.localeProvider = localeProvider;
|
||||||
this.translationProvider = translationProvider;
|
this.translationProvider = translationProvider;
|
||||||
|
this.automationChannelGroupUID = new ChannelGroupUID(thing.getUID(), CHANNEL_GROUP_AUTOMATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -265,9 +282,11 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
logger.debug("disposeAssets() {}", this);
|
logger.debug("disposeAssets() {}", this);
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
assetsLoaded = false;
|
assetsLoaded = false;
|
||||||
|
cancelTask(updateAutomationChannelsTask, true);
|
||||||
cancelTask(checkConnectionTask, true);
|
cancelTask(checkConnectionTask, true);
|
||||||
cancelTask(updateOnlineStateTask, true);
|
cancelTask(updateOnlineStateTask, true);
|
||||||
cancelTask(scheduledUpdateTask, true);
|
cancelTask(scheduledUpdateTask, true);
|
||||||
|
updateAutomationChannelsTask = null;
|
||||||
checkConnectionTask = null;
|
checkConnectionTask = null;
|
||||||
updateOnlineStateTask = null;
|
updateOnlineStateTask = null;
|
||||||
scheduledUpdateTask = null;
|
scheduledUpdateTask = null;
|
||||||
@ -418,10 +437,25 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (RefreshType.REFRESH.equals(command)) {
|
if (CHANNEL_GROUP_AUTOMATION.equals(channelUID.getGroupId())) {
|
||||||
return;
|
try {
|
||||||
|
if (RefreshType.REFRESH.equals(command)) {
|
||||||
|
updateAutomationChannelsNow();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Resources resources = getClip2Bridge().putResource(new Resource(ResourceType.BEHAVIOR_INSTANCE)
|
||||||
|
.setId(channelUID.getIdWithoutGroup()).setEnabled(command));
|
||||||
|
if (resources.hasErrors()) {
|
||||||
|
logger.warn("handleCommand({}, {}) succeeded with errors: {}", channelUID, command,
|
||||||
|
String.join("; ", resources.getErrors()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
logger.warn("handleCommand({}, {}) error {}", channelUID, command, e.getMessage(),
|
||||||
|
logger.isDebugEnabled() ? e : null);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.warn("Bridge thing '{}' has no channels, only REFRESH command supported.", thing.getUID());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -533,6 +567,9 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
if (numberOfResources != resources.size()) {
|
if (numberOfResources != resources.size()) {
|
||||||
logger.debug("onResourcesEventTask() merged to {} resources", resources.size());
|
logger.debug("onResourcesEventTask() merged to {} resources", resources.size());
|
||||||
}
|
}
|
||||||
|
if (onResources(resources)) {
|
||||||
|
updateAutomationChannelsNow();
|
||||||
|
}
|
||||||
getThing().getThings().forEach(thing -> {
|
getThing().getThings().forEach(thing -> {
|
||||||
if (thing.getHandler() instanceof Clip2ThingHandler clip2ThingHandler) {
|
if (thing.getHandler() instanceof Clip2ThingHandler clip2ThingHandler) {
|
||||||
clip2ThingHandler.onResources(resources);
|
clip2ThingHandler.onResources(resources);
|
||||||
@ -598,6 +635,8 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
logger.debug("updateOnlineState()");
|
logger.debug("updateOnlineState()");
|
||||||
connectRetriesRemaining = RECONNECT_MAX_TRIES;
|
connectRetriesRemaining = RECONNECT_MAX_TRIES;
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
loadAutomationScriptIds();
|
||||||
|
updateAutomationChannelsNow();
|
||||||
updateThingsScheduled(500);
|
updateThingsScheduled(500);
|
||||||
Clip2ThingDiscoveryService discoveryService = this.discoveryService;
|
Clip2ThingDiscoveryService discoveryService = this.discoveryService;
|
||||||
if (Objects.nonNull(discoveryService)) {
|
if (Objects.nonNull(discoveryService)) {
|
||||||
@ -775,4 +814,124 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
scheduledUpdateTask = scheduler.schedule(() -> updateThingsNow(), delayMilliSeconds, TimeUnit.MILLISECONDS);
|
scheduledUpdateTask = scheduler.schedule(() -> updateThingsNow(), delayMilliSeconds, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the set of automation script ids.
|
||||||
|
*/
|
||||||
|
private void loadAutomationScriptIds() {
|
||||||
|
try {
|
||||||
|
synchronized (automationScriptIds) {
|
||||||
|
automationScriptIds.clear();
|
||||||
|
automationScriptIds.addAll(getClip2Bridge().getResources(SCRIPT).getResources().stream()
|
||||||
|
.filter(r -> CategoryType.AUTOMATION == r.getCategory()).map(r -> r.getId())
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
logger.warn("loadAutomationScriptIds() unexpected exception {}", e.getMessage(),
|
||||||
|
logger.isDebugEnabled() ? e : null);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create resp. update the automation channels
|
||||||
|
*/
|
||||||
|
private void updateAutomationChannels() {
|
||||||
|
List<Resource> automations;
|
||||||
|
try {
|
||||||
|
automations = getClip2Bridge().getResources(BEHAVIOR).getResources().stream()
|
||||||
|
.filter(r -> automationScriptIds.contains(r.getScriptId())).toList();
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
logger.warn("Unexpected exception '{}' while updating channels.", e.getMessage(),
|
||||||
|
logger.isDebugEnabled() ? e : null);
|
||||||
|
return;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (automations.size() != automationsCache.size() || automations.stream().anyMatch(automation -> {
|
||||||
|
Resource cachedAutomation = automationsCache.get(automation.getId());
|
||||||
|
return Objects.isNull(cachedAutomation) || !automation.getName().equals(cachedAutomation.getName());
|
||||||
|
})) {
|
||||||
|
|
||||||
|
synchronized (automationsCache) {
|
||||||
|
automationsCache.clear();
|
||||||
|
automationsCache.putAll(automations.stream().collect(Collectors.toMap(a -> a.getId(), a -> a)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Channel> newChannels = automations.stream().map(a -> createAutomationChannel(a));
|
||||||
|
Stream<Channel> oldchannels = thing.getChannels().stream()
|
||||||
|
.filter(c -> !CHANNEL_TYPE_AUTOMATION.equals(c.getChannelTypeUID()));
|
||||||
|
|
||||||
|
updateThing(editThing().withChannels(Stream.concat(oldchannels, newChannels).toList()).build());
|
||||||
|
onResources(automations);
|
||||||
|
|
||||||
|
logger.debug("Bridge created {} automation channels", automations.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a task to update the automation channels
|
||||||
|
*/
|
||||||
|
private void updateAutomationChannelsNow() {
|
||||||
|
cancelTask(updateAutomationChannelsTask, false);
|
||||||
|
updateAutomationChannelsTask = scheduler.submit(() -> updateAutomationChannels());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an automation channel from an automation resource
|
||||||
|
*/
|
||||||
|
private Channel createAutomationChannel(Resource automation) {
|
||||||
|
String label = Objects.requireNonNullElse(translationProvider.getText(bundle, AUTOMATION_CHANNEL_LABEL_KEY,
|
||||||
|
AUTOMATION_CHANNEL_LABEL_KEY, localeProvider.getLocale(), automation.getName()),
|
||||||
|
AUTOMATION_CHANNEL_LABEL_KEY);
|
||||||
|
|
||||||
|
String description = Objects.requireNonNullElse(
|
||||||
|
translationProvider.getText(bundle, AUTOMATION_CHANNEL_DESCRIPTION_KEY,
|
||||||
|
AUTOMATION_CHANNEL_DESCRIPTION_KEY, localeProvider.getLocale(), automation.getName()),
|
||||||
|
AUTOMATION_CHANNEL_DESCRIPTION_KEY);
|
||||||
|
|
||||||
|
return ChannelBuilder
|
||||||
|
.create(new ChannelUID(automationChannelGroupUID, automation.getId()), CoreItemFactory.SWITCH)
|
||||||
|
.withLabel(label).withDescription(description).withType(CHANNEL_TYPE_AUTOMATION).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process event resources list
|
||||||
|
*
|
||||||
|
* @return true if the automation channels require updating
|
||||||
|
*/
|
||||||
|
public boolean onResources(List<Resource> resources) {
|
||||||
|
boolean requireUpdateChannels = false;
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
if (ResourceType.BEHAVIOR_INSTANCE != resource.getType()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String resourceId = resource.getId();
|
||||||
|
switch (resource.getContentType()) {
|
||||||
|
case ADD:
|
||||||
|
requireUpdateChannels |= automationScriptIds.contains(resource.getScriptId());
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
requireUpdateChannels |= automationsCache.containsKey(resourceId);
|
||||||
|
break;
|
||||||
|
case UPDATE:
|
||||||
|
case FULL_STATE:
|
||||||
|
Resource cachedAutomation = automationsCache.get(resourceId);
|
||||||
|
if (Objects.isNull(cachedAutomation)) {
|
||||||
|
requireUpdateChannels |= automationScriptIds.contains(resource.getScriptId());
|
||||||
|
} else {
|
||||||
|
if (resource.hasName() && !resource.getName().equals(cachedAutomation.getName())) {
|
||||||
|
requireUpdateChannels = true;
|
||||||
|
} else if (Objects.nonNull(resource.getEnabled())) {
|
||||||
|
updateState(new ChannelUID(automationChannelGroupUID, resourceId),
|
||||||
|
resource.getEnabledState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requireUpdateChannels;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,10 @@ thing-type.config.hue.room.resourceId.description = Unique Resource ID of the ro
|
|||||||
thing-type.config.hue.zone.resourceId.label = Resource ID
|
thing-type.config.hue.zone.resourceId.label = Resource ID
|
||||||
thing-type.config.hue.zone.resourceId.description = Unique Resource ID of the zone in the Hue bridge
|
thing-type.config.hue.zone.resourceId.description = Unique Resource ID of the zone in the Hue bridge
|
||||||
|
|
||||||
|
# channel group types
|
||||||
|
|
||||||
|
channel-group-type.hue.automation.label = Automations
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
channel-type.hue.advanced-brightness.label = Dimming Only
|
channel-type.hue.advanced-brightness.label = Dimming Only
|
||||||
@ -144,6 +148,7 @@ channel-type.hue.alert.description = The alert channel allows a temporary change
|
|||||||
channel-type.hue.alert.state.option.NONE = None
|
channel-type.hue.alert.state.option.NONE = None
|
||||||
channel-type.hue.alert.state.option.SELECT = Alert
|
channel-type.hue.alert.state.option.SELECT = Alert
|
||||||
channel-type.hue.alert.state.option.LSELECT = Long Alert
|
channel-type.hue.alert.state.option.LSELECT = Long Alert
|
||||||
|
channel-type.hue.automation-enable.label = Enable
|
||||||
channel-type.hue.button-last-event.label = Button Last Event
|
channel-type.hue.button-last-event.label = Button Last Event
|
||||||
channel-type.hue.button-last-event.description = Numeric code (e.g. 1003) representing the last push button event.
|
channel-type.hue.button-last-event.description = Numeric code (e.g. 1003) representing the last push button event.
|
||||||
channel-type.hue.dark.label = Dark
|
channel-type.hue.dark.label = Dark
|
||||||
@ -292,3 +297,8 @@ dynamics.command.label = Target Command
|
|||||||
dynamics.command.description = The target command state for the light(s) to transition to.
|
dynamics.command.description = The target command state for the light(s) to transition to.
|
||||||
dynamics.duration.label = Duration
|
dynamics.duration.label = Duration
|
||||||
dynamics.duration.description = The dynamic transition duration in ms.
|
dynamics.duration.description = The dynamic transition duration in ms.
|
||||||
|
|
||||||
|
# dynamic channels
|
||||||
|
|
||||||
|
dynamic-channel.automation-enable.label = Enable ''{0}''
|
||||||
|
dynamic-channel.automation-enable.description = Enable the ''{0}'' automation
|
||||||
|
@ -67,6 +67,10 @@
|
|||||||
<label>Hue API v2 Bridge</label>
|
<label>Hue API v2 Bridge</label>
|
||||||
<description>The Hue Bridge represents a Philips Hue Bridge supporting API v2.</description>
|
<description>The Hue Bridge represents a Philips Hue Bridge supporting API v2.</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="automation" typeId="automation"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
<representation-property>serialNumber</representation-property>
|
<representation-property>serialNumber</representation-property>
|
||||||
|
|
||||||
<config-description>
|
<config-description>
|
||||||
|
@ -286,4 +286,14 @@
|
|||||||
<category>Siren</category>
|
<category>Siren</category>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="automation-enable">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Enable</label>
|
||||||
|
<category>Switch</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-group-type id="automation">
|
||||||
|
<label>Automations</label>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -905,4 +905,14 @@ class Clip2DtoTest {
|
|||||||
assertTrue(resultEffect instanceof TimedEffects);
|
assertTrue(resultEffect instanceof TimedEffects);
|
||||||
assertEquals(Duration.ofMillis(44), ((TimedEffects) resultEffect).getDuration());
|
assertEquals(Duration.ofMillis(44), ((TimedEffects) resultEffect).getDuration());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBehaviorInstance() {
|
||||||
|
String json = load(ResourceType.BEHAVIOR_INSTANCE.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(2, list.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,14 @@ import org.openhab.binding.hue.internal.api.dto.clip2.Dimming;
|
|||||||
import org.openhab.binding.hue.internal.api.dto.clip2.Effects;
|
import org.openhab.binding.hue.internal.api.dto.clip2.Effects;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.OnState;
|
import org.openhab.binding.hue.internal.api.dto.clip2.OnState;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
|
import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContentType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
|
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
|
||||||
import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters;
|
import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters;
|
||||||
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link Setters}.
|
* Tests for {@link Setters}.
|
||||||
*
|
*
|
||||||
* @author Jacob Laursen - Initial contribution
|
* @author Jacob Laursen - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@ -51,7 +52,7 @@ public class SettersTest {
|
|||||||
*
|
*
|
||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light/grouped_light, sparse, id=1, on=on, dimming=50
|
* - Resource 1: type=light/grouped_light, sparse, id=1, on=on, dimming=50
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ -100,7 +101,7 @@ public class SettersTest {
|
|||||||
*
|
*
|
||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light, sparse, id=1, dimming=50
|
* - Resource 1: type=light, sparse, id=1, dimming=50
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -137,7 +138,7 @@ public class SettersTest {
|
|||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light, sparse, id=1, on=on, dimming=50
|
* - Resource 1: type=light, sparse, id=1, on=on, dimming=50
|
||||||
* - Resource 2: type=light, sparse, id=1, effect=xxx
|
* - Resource 2: type=light, sparse, id=1, effect=xxx
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -185,7 +186,7 @@ public class SettersTest {
|
|||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light, sparse, id=1, on=on
|
* - Resource 1: type=light, sparse, id=1, on=on
|
||||||
* - Resource 2: type=light, sparse, id=2, dimming=50
|
* - Resource 2: type=light, sparse, id=2, dimming=50
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -228,7 +229,7 @@ public class SettersTest {
|
|||||||
*
|
*
|
||||||
* Expected output:
|
* Expected output:
|
||||||
* - Exception thrown, full state is not supported/expected.
|
* - Exception thrown, full state is not supported/expected.
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -254,7 +255,7 @@ public class SettersTest {
|
|||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light, sparse, id=1, on=on
|
* - Resource 1: type=light, sparse, id=1, on=on
|
||||||
* - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
|
* - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -301,7 +302,7 @@ public class SettersTest {
|
|||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light, sparse, id=1, on=on, dimming=50
|
* - Resource 1: type=light, sparse, id=1, on=on, dimming=50
|
||||||
* - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
|
* - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -352,7 +353,7 @@ public class SettersTest {
|
|||||||
*
|
*
|
||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=light, sparse, id=1, on=on, color temperature=370
|
* - Resource 1: type=light, sparse, id=1, on=on, color temperature=370
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -389,7 +390,7 @@ public class SettersTest {
|
|||||||
*
|
*
|
||||||
* Expected output:
|
* Expected output:
|
||||||
* - Resource 1: type=motion, sparse, id=1
|
* - Resource 1: type=motion, sparse, id=1
|
||||||
*
|
*
|
||||||
* @throws DTOPresentButEmptyException
|
* @throws DTOPresentButEmptyException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ -431,7 +432,7 @@ public class SettersTest {
|
|||||||
private Resource createResource(ResourceType resourceType, String id) {
|
private Resource createResource(ResourceType resourceType, String id) {
|
||||||
Resource resource = new Resource(resourceType);
|
Resource resource = new Resource(resourceType);
|
||||||
resource.setId(id);
|
resource.setId(id);
|
||||||
resource.markAsSparse();
|
resource.setContentType(ContentType.UPDATE);
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
@ -1,91 +1,279 @@
|
|||||||
{
|
{
|
||||||
"errors": [],
|
"errors": [
|
||||||
"data": [
|
],
|
||||||
{
|
"data": [
|
||||||
"configuration": {
|
{
|
||||||
"what": [
|
"id": "042284f9-eeae-4f1e-9560-cc73750c7d28",
|
||||||
{
|
"type": "behavior_instance",
|
||||||
"group": {
|
"script_id": "67d9395b-4403-42cc-b5f0-740b699d67c6",
|
||||||
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
"enabled": true,
|
||||||
"rtype": "room"
|
"state": {
|
||||||
},
|
"model_id": "RWL021",
|
||||||
"recall": {
|
"source_type": "device"
|
||||||
"rid": "11ac9c82-d031-43a6-a8d5-b6efdee72fe6",
|
},
|
||||||
"rtype": "scene"
|
"configuration": {
|
||||||
}
|
"buttons": {
|
||||||
},
|
"6615f1f1-f3f1-4a05-b8f7-581097458e34": {
|
||||||
{
|
"on_repeat": {
|
||||||
"group": {
|
"action": "dim_down"
|
||||||
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
}
|
||||||
"rtype": "room"
|
},
|
||||||
},
|
"91ba8839-2bac-4175-9f8c-ed192842d549": {
|
||||||
"recall": {
|
"on_long_press": {
|
||||||
"rid": "8b65c749-3ad8-435e-a7ed-94e3cc99e9d7",
|
"action": "do_nothing"
|
||||||
"rtype": "scene"
|
},
|
||||||
}
|
"on_short_release": {
|
||||||
}
|
"time_based_extended": {
|
||||||
],
|
"slots": [
|
||||||
"when_constrained": {
|
{
|
||||||
"type": "nighttime"
|
"actions": [
|
||||||
},
|
{
|
||||||
"where": [
|
"action": {
|
||||||
{
|
"recall": {
|
||||||
"group": {
|
"rid": "f021deb5-5104-4752-aab3-2849f84da690",
|
||||||
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
"rtype": "scene"
|
||||||
"rtype": "room"
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
],
|
||||||
"group": {
|
"start_time": {
|
||||||
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
"hour": 7,
|
||||||
"rtype": "room"
|
"minute": 0
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
},
|
"actions": [
|
||||||
"dependees": [
|
{
|
||||||
{
|
"action": {
|
||||||
"level": "critical",
|
"recall": {
|
||||||
"target": {
|
"rid": "4ddd4f8b-428c-4089-a9a1-c27df5259b9a",
|
||||||
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
"rtype": "scene"
|
||||||
"rtype": "room"
|
}
|
||||||
},
|
}
|
||||||
"type": "ResourceDependee"
|
}
|
||||||
},
|
],
|
||||||
{
|
"start_time": {
|
||||||
"level": "critical",
|
"hour": 20,
|
||||||
"target": {
|
"minute": 0
|
||||||
"rid": "11ac9c82-d031-43a6-a8d5-b6efdee72fe6",
|
}
|
||||||
"rtype": "scene"
|
},
|
||||||
},
|
{
|
||||||
"type": "ResourceDependee"
|
"actions": [
|
||||||
},
|
{
|
||||||
{
|
"action": {
|
||||||
"level": "critical",
|
"recall": {
|
||||||
"target": {
|
"rid": "af0c88c4-9dae-4767-8475-a3cca906390d",
|
||||||
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
"rtype": "scene"
|
||||||
"rtype": "room"
|
}
|
||||||
},
|
}
|
||||||
"type": "ResourceDependee"
|
}
|
||||||
},
|
],
|
||||||
{
|
"start_time": {
|
||||||
"level": "critical",
|
"hour": 23,
|
||||||
"target": {
|
"minute": 0
|
||||||
"rid": "8b65c749-3ad8-435e-a7ed-94e3cc99e9d7",
|
}
|
||||||
"rtype": "scene"
|
}
|
||||||
},
|
],
|
||||||
"type": "ResourceDependee"
|
"with_off": {
|
||||||
}
|
"enabled": false
|
||||||
],
|
}
|
||||||
"enabled": true,
|
}
|
||||||
"id": "8d0ffbee-e24e-4d3e-b91a-5adc9ef5d49c",
|
}
|
||||||
"last_error": "",
|
},
|
||||||
"metadata": {
|
"b0d5a0af-31fd-4189-9150-c551ff9033d7": {
|
||||||
"name": "Coming home"
|
"on_long_press": {
|
||||||
},
|
"action": "do_nothing"
|
||||||
"script_id": "fd60fcd1-4809-4813-b510-4a18856a595c",
|
},
|
||||||
"status": "running",
|
"on_short_release": {
|
||||||
"type": "behavior_instance"
|
"action": "all_off"
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
}
|
"f95addfc-2f7c-453f-924d-ba496e07e5f9": {
|
||||||
|
"on_repeat": {
|
||||||
|
"action": "dim_up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"model_id": "RWL021",
|
||||||
|
"where": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "bdc282b3-750d-45dd-b6c4-12a2927d8951",
|
||||||
|
"rtype": "zone"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependees": [
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "bdc282b3-750d-45dd-b6c4-12a2927d8951",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "f021deb5-5104-4752-aab3-2849f84da690",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "4ddd4f8b-428c-4089-a9a1-c27df5259b9a",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "af0c88c4-9dae-4767-8475-a3cca906390d",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "91ba8839-2bac-4175-9f8c-ed192842d549",
|
||||||
|
"rtype": "button"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "f95addfc-2f7c-453f-924d-ba496e07e5f9",
|
||||||
|
"rtype": "button"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "6615f1f1-f3f1-4a05-b8f7-581097458e34",
|
||||||
|
"rtype": "button"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"rid": "b0d5a0af-31fd-4189-9150-c551ff9033d7",
|
||||||
|
"rtype": "button"
|
||||||
|
},
|
||||||
|
"level": "critical",
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "running",
|
||||||
|
"last_error": "",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Worktops Dimmer Pad Right"
|
||||||
|
},
|
||||||
|
"migrated_from": "/resourcelinks/5338"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration": {
|
||||||
|
"what": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"recall": {
|
||||||
|
"rid": "11ac9c82-d031-43a6-a8d5-b6efdee72fe6",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"recall": {
|
||||||
|
"rid": "8b65c749-3ad8-435e-a7ed-94e3cc99e9d7",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"when_constrained": {
|
||||||
|
"type": "nighttime"
|
||||||
|
},
|
||||||
|
"where": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependees": [
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "11ac9c82-d031-43a6-a8d5-b6efdee72fe6",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "8b65c749-3ad8-435e-a7ed-94e3cc99e9d7",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enabled": true,
|
||||||
|
"id": "8d0ffbee-e24e-4d3e-b91a-5adc9ef5d49c",
|
||||||
|
"last_error": "",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Coming home"
|
||||||
|
},
|
||||||
|
"script_id": "fd60fcd1-4809-4813-b510-4a18856a595c",
|
||||||
|
"status": "running",
|
||||||
|
"type": "behavior_instance"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user