Skip to content

Commit

Permalink
Add dynamic state options to group scene channel
Browse files Browse the repository at this point in the history
Signed-off-by: leluna <[email protected]>
  • Loading branch information
leluna committed May 18, 2020
1 parent c1c6254 commit c207cda
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 11 deletions.
4 changes: 1 addition & 3 deletions bundles/org.openhab.binding.hue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,7 @@ The bridge itself supports a channel to activate scenes:
| --------------- | --------- | ----------- |
| scene | String | This channel activates the scene with the given ID String. |

The scenes are identified by an ID String that is assigned by the Hue bridge. These must be aquired directly from [Hue REST API](https://developers.meethue.com/develop/hue-api/).

This channel can then be used in the sitemap, for example as a switch or selection.
The scenes are identified by an ID String that is assigned by the Hue bridge.

## Rule Actions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ public class FullGroup extends Group {
private State state; // Will not be set by hue API

FullGroup() {
super();
}

/**
* Test constructor
*/
FullGroup(String id, String name, String type, State action, List<String> lights, State state) {
super(id, name, type);
this.action = action;
this.lights = lights;
this.state = state;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ public class Group {
this.type = "LightGroup";
}

/**
* Test constructor
*/
Group(String id, String name, String type) {
this.id = id;
this.name = name;
this.type = type;
}

void setName(String name) {
this.name = name;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -115,6 +116,17 @@ public HueBridge(String ip, int port, String protocol, String username, Schedule
authenticate(username);
}

/**
* Test constructor
*/
HueBridge(String ip, String baseUrl, String username, ScheduledExecutorService scheduler, HttpClient http) {
this.ip = ip;
this.baseUrl = baseUrl;
this.username = username;
this.scheduler = scheduler;
this.http = http;
}

/**
* Set the connect and read timeout for HTTP requests.
*
Expand Down Expand Up @@ -870,6 +882,7 @@ public List<Scene> getScenes() throws IOException, ApiException {
return e.getValue();
})//
.filter(scene -> !scene.isRecycle())//
.sorted(Comparator.comparing(Scene::getGroupId))//
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private ThingUID getThingUID(ThingTypeUID thingTypeUID, String id, @Nullable Thi
} else if (ClipHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new ClipHandler(thing);
} else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new HueGroupHandler(thing);
return new HueGroupHandler(thing, stateOptionProvider);
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ public class Scene {
private String groupId;
private boolean recycle;

/**
* Default constructor for GSon.
*/
public Scene() {
super();
}

/**
* Test constructor
*/
Scene(String id, String name, String groupId, List<String> lightIds, boolean recycle) {
this.id = id;
this.name = name;
this.groupId = groupId;
this.lightIds = lightIds;
this.recycle = recycle;
}

@NonNull
public String getId() {
return id;
Expand Down Expand Up @@ -89,15 +107,46 @@ public boolean isRecycle() {
return recycle;
}

/**
* Creates a {@link StateOption} to display this scene, including the group that it belongs to.
* <p>
* The display name is built with the following pattern:
* <ol>
* <li>Human readable name of the scene if set. Otherwise, the ID is displayed</li>
* <li>Group for which the scene is defined</li>
* </ol>
*/
public StateOption toStateOption(Map<String, String> groupNames) {
StringBuilder stateOptionLabel = new StringBuilder(name);
if (groupId != null && groupNames.containsKey(groupId)) {
stateOptionLabel.append(" (").append(groupNames.get(groupId)).append(")");
}
if (!id.contentEquals(name)) {
stateOptionLabel.append(" [").append(id).append("]");
}

return new StateOption(id, stateOptionLabel.toString());
}

/**
* Creates a {@link StateOption} to display this scene.
*/
public StateOption toStateOption() {
return new StateOption(id, name);
}

/**
* Returns whether the scene is applicable to the given group.
* <p>
* According to the hue API, a scene is applicable to a group if either
* <ol>
* <li>The scene is defined for the group</li>
* <li>All lights of the scene also belong to the group</li>
* </ol>
*/
public boolean isApplicableTo(FullGroup group) {
if (getGroupId() == null) {
return getLightIds().parallelStream()//
.allMatch(id -> group.getLights().contains(id));
} else {
return group.getId().contentEquals(getGroupId());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.openhab.binding.hue.internal.FullLight;
import org.openhab.binding.hue.internal.FullSensor;
import org.openhab.binding.hue.internal.HueBridge;
import org.openhab.binding.hue.internal.Scene;
import org.openhab.binding.hue.internal.handler.GroupStatusListener;
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
Expand Down Expand Up @@ -326,4 +327,9 @@ public void onGroupStateChanged(@Nullable HueBridge bridge, FullGroup group) {
// nothing to do
}

@Override
public void onScenesUpdated(@Nullable HueBridge bridge, List<Scene> scenes) {
// nothing to do
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
*/
package org.openhab.binding.hue.internal.handler;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.FullGroup;
import org.openhab.binding.hue.internal.HueBridge;
import org.openhab.binding.hue.internal.Scene;

/**
* The {@link GroupStatusListener} is notified when a group status has changed or a group has been removed or added.
Expand Down Expand Up @@ -57,4 +60,12 @@ public interface GroupStatusListener {
* @param group The added group
*/
void onGroupAdded(@Nullable HueBridge bridge, FullGroup group);

/**
* This method is called whenever the list of available scenes is updated.
*
* @param bridge The bridge on which all scenes is stored
* @param updatedScenes available scenes
*/
void onScenesUpdated(@Nullable HueBridge bridge, List<Scene> scenes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.openhab.binding.hue.internal.FullSensor;
import org.openhab.binding.hue.internal.HueBridge;
import org.openhab.binding.hue.internal.HueConfigStatusMessage;
import org.openhab.binding.hue.internal.Scene;
import org.openhab.binding.hue.internal.State;
import org.openhab.binding.hue.internal.StateUpdate;
import org.openhab.binding.hue.internal.config.HueBridgeConfig;
Expand Down Expand Up @@ -98,7 +99,7 @@ private static enum StatusType {
}

private final Logger logger = LoggerFactory.getLogger(HueBridgeHandler.class);
private final @NonNullByDefault({}) HueStateDescriptionOptionProvider stateDescriptionOptionProvider;
private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;

private final Map<String, FullLight> lastLightStates = new ConcurrentHashMap<>();
private final Map<String, FullSensor> lastSensorStates = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -322,9 +323,17 @@ private void updateGroups() throws IOException, ApiException {
private final Runnable scenePollingRunnable = new PollingRunnable() {
@Override
protected void doConnectedRun() throws IOException, ApiException {
Map<String, String> groupNames = lastGroupStates.entrySet().parallelStream()
List<Scene> scenes = hueBridge.getScenes();
logger.trace("Scenes detected: {}", scenes);

setBridgeSceneChannelStateOptions(scenes, lastGroupStates);
notifyGroupSceneUpdate(scenes);
}

private void setBridgeSceneChannelStateOptions(List<Scene> scenes, Map<String, FullGroup> groups) {
Map<String, String> groupNames = groups.entrySet().parallelStream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getName()));
List<StateOption> stateOptions = hueBridge.getScenes().parallelStream()//
List<StateOption> stateOptions = scenes.parallelStream()//
.map(scene -> scene.toStateOption(groupNames))//
.collect(Collectors.toList());
stateDescriptionOptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SCENE),
Expand Down Expand Up @@ -934,6 +943,10 @@ private void notifyGroupStatusListeners(final FullGroup fullGroup, StatusType ty
}
}

private void notifyGroupSceneUpdate(List<Scene> scenes) {
groupStatusListeners.forEach(l -> l.onScenesUpdated(hueBridge, scenes));
}

@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
// The bridge IP address to be used for checks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -34,9 +36,11 @@
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.StateOption;
import org.eclipse.smarthome.core.types.UnDefType;
import org.openhab.binding.hue.internal.FullGroup;
import org.openhab.binding.hue.internal.HueBridge;
import org.openhab.binding.hue.internal.Scene;
import org.openhab.binding.hue.internal.State;
import org.openhab.binding.hue.internal.State.ColorMode;
import org.openhab.binding.hue.internal.StateUpdate;
Expand All @@ -54,6 +58,7 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_GROUP);

private final Logger logger = LoggerFactory.getLogger(HueGroupHandler.class);
private final HueStateDescriptionOptionProvider stateDescriptionOptionProvider;

private @NonNullByDefault({}) String groupId;

Expand All @@ -64,8 +69,9 @@ public class HueGroupHandler extends BaseThingHandler implements GroupStatusList

private @Nullable HueClient hueClient;

public HueGroupHandler(Thing thing) {
public HueGroupHandler(Thing thing, HueStateDescriptionOptionProvider stateDescriptionOptionProvider) {
super(thing);
this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
}

@Override
Expand Down Expand Up @@ -376,4 +382,17 @@ public void onGroupGone(@Nullable HueBridge bridge, FullGroup group) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/offline.group-removed");
}
}

/**
* Sets the state options for applicable scenes.
*/
@Override
public void onScenesUpdated(@Nullable HueBridge bridge, List<Scene> updatedScenes) {
List<StateOption> stateOptions = updatedScenes.parallelStream()//
.filter(scene -> scene.isApplicableTo(getHueClient().getGroupById(groupId)))//
.map(Scene::toStateOption)//
.collect(Collectors.toList());
stateDescriptionOptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SCENE),
stateOptions);
}
}
Loading

0 comments on commit c207cda

Please sign in to comment.