Skip to content

Commit

Permalink
[hue] Add support for enabling automations (openhab#16980)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Fiddian-Green <[email protected]>
  • Loading branch information
andrewfg authored and matchews committed Oct 18, 2024
1 parent bd6311f commit a2c36c4
Show file tree
Hide file tree
Showing 15 changed files with 652 additions and 122 deletions.
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

0 comments on commit a2c36c4

Please sign in to comment.