Skip to content

Commit 77561d5

Browse files
authored
[hue] Add support for enabling automations (openhab#16980)
Signed-off-by: Andrew Fiddian-Green <[email protected]>
1 parent 43cc09a commit 77561d5

File tree

15 files changed

+652
-122
lines changed

15 files changed

+652
-122
lines changed

bundles/org.openhab.binding.hue/doc/readme_v2.md

+11
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ See [console command](#console-command-for-finding-resourceids)
5555

5656
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.
5757

58+
### Channels for Bridges
59+
60+
Bridge Things support the following channels:
61+
62+
| Channel ID | Item Type | Description |
63+
|-------------------------------------------------|--------------------|---------------------------------------------|
64+
| automation#11111111-2222-3333-4444-555555555555 | Switch | Enable / disable the respective automation. |
65+
66+
The Bridge dynamically creates `automation` channels corresponding to the automations in the Hue App;
67+
the '11111111-2222-3333-4444-555555555555' is the unique id of the respective automation.
68+
5869
### Channels for Devices
5970

6071
Device things support some of the following channels:

bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.eclipse.jdt.annotation.NonNullByDefault;
1919
import org.openhab.core.thing.ThingTypeUID;
20+
import org.openhab.core.thing.type.ChannelTypeUID;
2021

2122
/**
2223
* The {@link HueBindingConstants} class defines common constants, which are
@@ -200,4 +201,7 @@ public class HueBindingConstants {
200201
Map.entry(CHANNEL_LAST_UPDATED, CHANNEL_2_LAST_UPDATED));
201202

202203
public static final String ALL_LIGHTS_KEY = "discovery.group.all-lights.label";
204+
205+
public static final String CHANNEL_GROUP_AUTOMATION = "automation";
206+
public static final ChannelTypeUID CHANNEL_TYPE_AUTOMATION = new ChannelTypeUID(BINDING_ID, "automation-enable");
203207
}

bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Event.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
package org.openhab.binding.hue.internal.api.dto.clip2;
1414

1515
import java.lang.reflect.Type;
16-
import java.util.ArrayList;
1716
import java.util.List;
1817
import java.util.Objects;
1918

2019
import org.eclipse.jdt.annotation.NonNullByDefault;
2120
import org.eclipse.jdt.annotation.Nullable;
21+
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContentType;
2222

23+
import com.google.gson.annotations.SerializedName;
2324
import com.google.gson.reflect.TypeToken;
2425

2526
/**
@@ -32,7 +33,13 @@ public class Event {
3233
public static final Type EVENT_LIST_TYPE = new TypeToken<List<Event>>() {
3334
}.getType();
3435

35-
private @Nullable List<Resource> data = new ArrayList<>();
36+
private @Nullable List<Resource> data;
37+
private @Nullable @SerializedName("type") ContentType contentType; // content type of resources
38+
39+
public ContentType getContentType() {
40+
ContentType contentType = this.contentType;
41+
return Objects.nonNull(contentType) ? contentType : ContentType.ERROR;
42+
}
3643

3744
public List<Resource> getData() {
3845
List<Resource> data = this.data;

bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MetaData.java

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.eclipse.jdt.annotation.NonNullByDefault;
1616
import org.eclipse.jdt.annotation.Nullable;
1717
import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
18+
import org.openhab.binding.hue.internal.api.dto.clip2.enums.CategoryType;
1819

1920
import com.google.gson.annotations.SerializedName;
2021

@@ -28,6 +29,7 @@ public class MetaData {
2829
private @Nullable String archetype;
2930
private @Nullable String name;
3031
private @Nullable @SerializedName("control_id") Integer controlId;
32+
private @Nullable String category;
3133

3234
public Archetype getArchetype() {
3335
return Archetype.of(archetype);
@@ -37,6 +39,10 @@ public Archetype getArchetype() {
3739
return name;
3840
}
3941

42+
public CategoryType getCategory() {
43+
return CategoryType.of(category);
44+
}
45+
4046
public int getControlId() {
4147
Integer controlId = this.controlId;
4248
return controlId != null ? controlId.intValue() : 0;

bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java

+54-14
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import org.eclipse.jdt.annotation.Nullable;
2929
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
3030
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType;
31+
import org.openhab.binding.hue.internal.api.dto.clip2.enums.CategoryType;
3132
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContactStateType;
33+
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContentType;
3234
import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
3335
import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
3436
import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction;
@@ -55,6 +57,7 @@
5557

5658
import com.google.gson.JsonElement;
5759
import com.google.gson.JsonObject;
60+
import com.google.gson.JsonPrimitive;
5861
import com.google.gson.annotations.SerializedName;
5962

6063
/**
@@ -74,8 +77,16 @@ public class Resource {
7477
* values have changed. A sparse resource does not contain the full state of the resource. And the absence of any
7578
* field from such a resource does not indicate that the field value is UNDEF, but rather that the value is the same
7679
* as what it was previously set to by the last non-sparse resource.
80+
* <p>
81+
* The following content types are defined:
82+
*
83+
* <li><b>ADD</b> resource being added; contains (assumed) all fields</li>
84+
* <li><b>DELETE</b> resource being deleted; contains id and type only</li>
85+
* <li><b>UPDATE</b> resource being updated; contains id, type and changed fields</li>
86+
* <li><b>ERROR</b> resource with error; contents unknown</li>
87+
* <li><b>FULL_STATE</b> existing resource being downloaded; contains all fields</li>
7788
*/
78-
private transient boolean hasSparseData;
89+
private transient ContentType contentType;
7990

8091
private @Nullable String type;
8192
private @Nullable String id;
@@ -107,14 +118,23 @@ public class Resource {
107118
private @Nullable Dynamics dynamics;
108119
private @Nullable @SerializedName("contact_report") ContactReport contactReport;
109120
private @Nullable @SerializedName("tamper_reports") List<TamperReport> tamperReports;
110-
private @Nullable String state;
121+
private @Nullable JsonElement state;
122+
private @Nullable @SerializedName("script_id") String scriptId;
123+
124+
/**
125+
* Constructor
126+
*/
127+
public Resource() {
128+
contentType = ContentType.FULL_STATE;
129+
}
111130

112131
/**
113132
* Constructor
114133
*
115134
* @param resourceType
116135
*/
117136
public Resource(@Nullable ResourceType resourceType) {
137+
this();
118138
if (Objects.nonNull(resourceType)) {
119139
setType(resourceType);
120140
}
@@ -343,6 +363,14 @@ public State getColorTemperaturePercentState() {
343363
return color;
344364
}
345365

366+
/**
367+
* Return the resource's metadata category.
368+
*/
369+
public CategoryType getCategory() {
370+
MetaData metaData = getMetaData();
371+
return Objects.nonNull(metaData) ? metaData.getCategory() : CategoryType.NULL;
372+
}
373+
346374
/**
347375
* Return an HSB where the HS part is derived from the color xy JSON element (only), so the B part is 100%
348376
*
@@ -375,6 +403,10 @@ public State getContactState() {
375403
: OpenClosedType.OPEN;
376404
}
377405

406+
public ContentType getContentType() {
407+
return contentType;
408+
}
409+
378410
public int getControlId() {
379411
MetaData metadata = this.metadata;
380412
return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
@@ -648,6 +680,13 @@ public Optional<Boolean> getSceneActive() {
648680
return Optional.empty();
649681
}
650682

683+
/**
684+
* Return the scriptId if any.
685+
*/
686+
public @Nullable String getScriptId() {
687+
return scriptId;
688+
}
689+
651690
/**
652691
* If the getSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result is
653692
* 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 State getSceneState() {
661700

662701
/**
663702
* Check if the smart scene resource contains a 'state' element. If such an element is present, returns a Boolean
664-
* Optional whose value depends on the value of that element, or an empty Optional if it is not.
703+
* Optional whose value depends on the value of that element, or an empty Optional if it is not. Note that in some
704+
* resource types the 'state' element is not a String primitive.
665705
*
666706
* @return true, false, or empty.
667707
*/
668708
public Optional<Boolean> getSmartSceneActive() {
669-
if (ResourceType.SMART_SCENE == getType()) {
670-
String state = this.state;
709+
if (ResourceType.SMART_SCENE == getType() && (state instanceof JsonPrimitive statePrimitive)) {
710+
String state = statePrimitive.getAsString();
671711
if (Objects.nonNull(state)) {
672712
return Optional.of(SmartSceneState.ACTIVE == SmartSceneState.of(state));
673713
}
@@ -785,17 +825,12 @@ public State getZigbeeState() {
785825
}
786826

787827
public boolean hasFullState() {
788-
return !hasSparseData;
828+
return ContentType.FULL_STATE == contentType;
789829
}
790830

791-
/**
792-
* Mark that the resource has sparse data.
793-
*
794-
* @return this instance.
795-
*/
796-
public Resource markAsSparse() {
797-
hasSparseData = true;
798-
return this;
831+
public boolean hasName() {
832+
MetaData metaData = getMetaData();
833+
return Objects.nonNull(metaData) && Objects.nonNull(metaData.getName());
799834
}
800835

801836
public Resource setAlerts(Alerts alert) {
@@ -818,6 +853,11 @@ public Resource setContactReport(ContactReport contactReport) {
818853
return this;
819854
}
820855

856+
public Resource setContentType(ContentType contentType) {
857+
this.contentType = contentType;
858+
return this;
859+
}
860+
821861
public Resource setDimming(@Nullable Dimming dimming) {
822862
this.dimming = dimming;
823863
return this;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.hue.internal.api.dto.clip2.enums;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
import org.eclipse.jdt.annotation.Nullable;
17+
18+
/**
19+
* Enum for 'category' fields.
20+
*
21+
* @author Andrew Fiddian-Green - Initial contribution
22+
*/
23+
@NonNullByDefault
24+
public enum CategoryType {
25+
ACCESSORY,
26+
AUTOMATION,
27+
ENTERTAINMENT,
28+
NULL,
29+
UNDEF;
30+
31+
public static CategoryType of(@Nullable String value) {
32+
if (value != null) {
33+
try {
34+
return valueOf(value.toUpperCase());
35+
} catch (IllegalArgumentException e) {
36+
return UNDEF;
37+
}
38+
}
39+
return NULL;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.hue.internal.api.dto.clip2.enums;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
17+
import com.google.gson.annotations.SerializedName;
18+
19+
/**
20+
* Enum for content type of Resource instances
21+
*
22+
* @author Andrew Fiddian-Green - Initial contribution
23+
*/
24+
@NonNullByDefault
25+
public enum ContentType {
26+
@SerializedName("add") // resource being added; contains (maybe) all fields
27+
ADD,
28+
@SerializedName("delete") // resource being deleted; contains id and type only
29+
DELETE,
30+
@SerializedName("update") // resource being updated; contains id, type and updated fields
31+
UPDATE,
32+
@SerializedName("error") // resource error event
33+
ERROR,
34+
// existing resource being downloaded; contains all fields; excluded from (de-)serialization
35+
FULL_STATE
36+
}

bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -921,12 +921,15 @@ protected void onEventData(String data) {
921921
return;
922922
}
923923
List<Resource> resources = new ArrayList<>();
924-
events.forEach(event -> resources.addAll(event.getData()));
924+
events.forEach(event -> {
925+
List<Resource> eventResources = event.getData();
926+
eventResources.forEach(resource -> resource.setContentType(event.getContentType()));
927+
resources.addAll(eventResources);
928+
});
925929
if (resources.isEmpty()) {
926930
LOGGER.debug("onEventData() resource list is empty");
927931
return;
928932
}
929-
resources.forEach(resource -> resource.markAsSparse());
930933
bridgeHandler.onResourcesEvent(resources);
931934
}
932935

0 commit comments

Comments
 (0)