Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hue] Add support for enabling automations (API v2) #16980

Merged
merged 21 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bundles/org.openhab.binding.hue/doc/readme_v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

Device things support some of the following channels:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;

/**
* The {@link HueBindingConstants} class defines common constants, which are
Expand Down Expand Up @@ -200,4 +201,7 @@ public class HueBindingConstants {
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 CHANNEL_GROUP_AUTOMATION = "automation";
public static final ChannelTypeUID CHANNEL_TYPE_AUTOMATION = new ChannelTypeUID(BINDING_ID, "automation-enable");
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
package org.openhab.binding.hue.internal.api.dto.clip2;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import org.eclipse.jdt.annotation.NonNullByDefault;
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;

/**
Expand All @@ -32,7 +33,13 @@ public class Event {
public static final Type EVENT_LIST_TYPE = new TypeToken<List<Event>>() {
}.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() {
List<Resource> data = this.data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
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.CategoryType;

import com.google.gson.annotations.SerializedName;

Expand All @@ -28,6 +29,7 @@ public class MetaData {
private @Nullable String archetype;
private @Nullable String name;
private @Nullable @SerializedName("control_id") Integer controlId;
private @Nullable String category;

public Archetype getArchetype() {
return Archetype.of(archetype);
Expand All @@ -37,6 +39,10 @@ public Archetype getArchetype() {
return name;
}

public CategoryType getCategory() {
return CategoryType.of(category);
}

public int getControlId() {
Integer controlId = this.controlId;
return controlId != null ? controlId.intValue() : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
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.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.ContentType;
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.SceneRecallAction;
Expand All @@ -55,6 +57,7 @@

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.SerializedName;

/**
Expand All @@ -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
* 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.
* <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 id;
Expand Down Expand Up @@ -107,14 +118,23 @@ public class Resource {
private @Nullable Dynamics dynamics;
private @Nullable @SerializedName("contact_report") ContactReport contactReport;
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
*
* @param resourceType
*/
public Resource(@Nullable ResourceType resourceType) {
this();
if (Objects.nonNull(resourceType)) {
setType(resourceType);
}
Expand Down Expand Up @@ -343,6 +363,14 @@ public State getColorTemperaturePercentState() {
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%
*
Expand Down Expand Up @@ -375,6 +403,10 @@ public State getContactState() {
: OpenClosedType.OPEN;
}

public ContentType getContentType() {
return contentType;
}

public int getControlId() {
MetaData metadata = this.metadata;
return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
Expand Down Expand Up @@ -648,6 +680,13 @@ public Optional<Boolean> getSceneActive() {
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
* present and 'true' (i.e. the scene is active) return the scene name. Or finally (the optional result is present
Expand All @@ -661,13 +700,14 @@ public State getSceneState() {

/**
* 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.
*/
public Optional<Boolean> getSmartSceneActive() {
if (ResourceType.SMART_SCENE == getType()) {
String state = this.state;
if (ResourceType.SMART_SCENE == getType() && (state instanceof JsonPrimitive statePrimitive)) {
String state = statePrimitive.getAsString();
if (Objects.nonNull(state)) {
return Optional.of(SmartSceneState.ACTIVE == SmartSceneState.of(state));
}
Expand Down Expand Up @@ -785,17 +825,12 @@ public State getZigbeeState() {
}

public boolean hasFullState() {
return !hasSparseData;
return ContentType.FULL_STATE == contentType;
}

/**
* Mark that the resource has sparse data.
*
* @return this instance.
*/
public Resource markAsSparse() {
hasSparseData = true;
return this;
public boolean hasName() {
MetaData metaData = getMetaData();
return Objects.nonNull(metaData) && Objects.nonNull(metaData.getName());
}

public Resource setAlerts(Alerts alert) {
Expand All @@ -818,6 +853,11 @@ public Resource setContactReport(ContactReport contactReport) {
return this;
}

public Resource setContentType(ContentType contentType) {
this.contentType = contentType;
return this;
}

public Resource setDimming(@Nullable Dimming dimming) {
this.dimming = dimming;
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,15 @@ protected void onEventData(String data) {
return;
}
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()) {
LOGGER.debug("onEventData() resource list is empty");
return;
}
resources.forEach(resource -> resource.markAsSparse());
bridgeHandler.onResourcesEvent(resources);
}

Expand Down
Loading