Skip to content

Commit

Permalink
Add support for scene collections.
Browse files Browse the repository at this point in the history
Fixes openhab#11533

Signed-off-by: Jacob Laursen <[email protected]>
  • Loading branch information
jlaur committed Nov 6, 2021
1 parent d4510f3 commit 212aa4f
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*
* @author Andy Lintner - Initial contribution
* @author Andrew Fiddian-Green - Added support for secondary rail positions
* @author Jacob Laursen - Add support for scene collections
*/
@NonNullByDefault
public class HDPowerViewBindingConstants {
Expand All @@ -46,6 +47,7 @@ public class HDPowerViewBindingConstants {
public static final String CHANNEL_SHADE_SIGNAL_STRENGTH = "signalStrength";

public static final String CHANNELTYPE_SCENE_ACTIVATE = "scene-activate";
public static final String CHANNELTYPE_SCENE_COLLECTION_ACTIVATE = "scene-collection-activate";

public static final List<String> NETBIOS_NAMES = Arrays.asList("PDBU-Hub3.0", "PowerView-Hub");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeStop;
import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
Expand All @@ -42,6 +43,7 @@
*
* @author Andy Lintner - Initial contribution
* @author Andrew Fiddian-Green - Added support for secondary rail positions
* @author Jacob Laursen - Add support for scene collections
*/
@NonNullByDefault
public class HDPowerViewWebTargets {
Expand All @@ -61,6 +63,8 @@ public class HDPowerViewWebTargets {
private final String shades;
private final String sceneActivate;
private final String scenes;
private final String sceneCollectionActivate;
private final String sceneCollections;

private final Gson gson = new Gson();
private final HttpClient httpClient;
Expand Down Expand Up @@ -101,6 +105,8 @@ public HDPowerViewWebTargets(HttpClient httpClient, String ipAddress) {
shades = base + "shades/";
sceneActivate = base + "scenes";
scenes = base + "scenes/";
sceneCollectionActivate = base + "sceneCollections";
sceneCollections = base + "sceneCollections/";
this.httpClient = httpClient;
}

Expand Down Expand Up @@ -156,6 +162,33 @@ public void activateScene(int sceneId) throws HubProcessingException, HubMainten
invoke(HttpMethod.GET, sceneActivate, Query.of("sceneId", Integer.toString(sceneId)), null);
}

/**
* Fetches a JSON package that describes all scene collections in the hub, and wraps it in
* a SceneCollections class instance
*
* @return SceneCollections class instance
* @throws JsonParseException if there is a JSON parsing error
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public @Nullable SceneCollections getSceneCollections()
throws JsonParseException, HubProcessingException, HubMaintenanceException {
String json = invoke(HttpMethod.GET, sceneCollections, null, null);
return gson.fromJson(json, SceneCollections.class);
}

/**
* Instructs the hub to execute a specific scene collection
*
* @param sceneCollectionId id of the scene collection to be executed
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public void activateSceneCollection(int sceneCollectionId) throws HubProcessingException, HubMaintenanceException {
invoke(HttpMethod.GET, sceneCollectionActivate,
Query.of("sceneCollectionId", Integer.toString(sceneCollectionId)), null);
}

/**
* Invoke a call on the hub server to retrieve information or send a command
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2021 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.hdpowerview.internal.api.responses;

import java.util.Base64;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* State of all Scenes in an HD PowerView hub
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class SceneCollections {

public @Nullable List<SceneCollection> sceneCollectionData;
public @Nullable List<Integer> sceneCollectionIds;

/*
* the following SuppressWarnings annotation is because the Eclipse compiler
* does NOT expect a NonNullByDefault annotation on the inner class, since it is
* implicitly inherited from the outer class, whereas the Maven compiler always
* requires an explicit NonNullByDefault annotation on all classes
*/
@SuppressWarnings("null")
@NonNullByDefault
public static class SceneCollection {
public int id;
public @Nullable String name;
public int order;
public int colorId;
public int iconId;

public String getName() {
return new String(Base64.getDecoder().decode(name));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
import org.openhab.binding.hdpowerview.internal.HubProcessingException;
import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
Expand Down Expand Up @@ -59,6 +61,7 @@
*
* @author Andy Lintner - Initial contribution
* @author Andrew Fiddian-Green - Added support for secondary rail positions
* @author Jacob Laursen - Add support for scene collections
*/
@NonNullByDefault
public class HDPowerViewHubHandler extends BaseBridgeHandler {
Expand All @@ -78,6 +81,9 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);

private final ChannelTypeUID sceneCollectionChannelTypeUID = new ChannelTypeUID(
HDPowerViewBindingConstants.BINDING_ID, HDPowerViewBindingConstants.CHANNELTYPE_SCENE_COLLECTION_ACTIVATE);

public HDPowerViewHubHandler(Bridge bridge, HttpClient httpClient) {
super(bridge);
this.httpClient = httpClient;
Expand All @@ -90,21 +96,30 @@ public void handleCommand(ChannelUID channelUID, Command command) {
return;
}

if (!OnOffType.ON.equals(command)) {
return;
}

Channel channel = getThing().getChannel(channelUID.getId());
if (channel != null && sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
if (OnOffType.ON.equals(command)) {
try {
HDPowerViewWebTargets webTargets = this.webTargets;
if (webTargets == null) {
throw new ProcessingException("Web targets not initialized");
}
webTargets.activateScene(Integer.parseInt(channelUID.getId()));
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
} catch (NumberFormatException | HubProcessingException e) {
logger.debug("Unexpected error {}", e.getMessage());
}
if (channel == null) {
return;
}

try {
HDPowerViewWebTargets webTargets = this.webTargets;
if (webTargets == null) {
throw new ProcessingException("Web targets not initialized");
}
int id = Integer.parseInt(channelUID.getId());
if (sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
webTargets.activateScene(id);
} else if (sceneCollectionChannelTypeUID.equals(channel.getChannelTypeUID())) {
webTargets.activateSceneCollection(id);
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
} catch (NumberFormatException | HubProcessingException e) {
logger.debug("Unexpected error {}", e.getMessage());
}
}

Expand Down Expand Up @@ -196,6 +211,7 @@ private synchronized void poll() {
logger.debug("Polling for state");
pollShades();
pollScenes();
pollSceneCollections();
} catch (JsonParseException e) {
logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
} catch (HubProcessingException e) {
Expand Down Expand Up @@ -266,7 +282,7 @@ private void pollScenes() throws JsonParseException, HubProcessingException, Hub
}
logger.debug("Received data for {} scenes", sceneData.size());

Map<String, Channel> idChannelMap = getIdChannelMap();
Map<String, Channel> idChannelMap = getIdSceneChannelMap();
for (Scene scene : sceneData) {
// remove existing scene channel from the map
String sceneId = Integer.toString(scene.id);
Expand All @@ -292,6 +308,50 @@ private void pollScenes() throws JsonParseException, HubProcessingException, Hub
}
}

private void pollSceneCollections() throws JsonParseException, HubProcessingException, HubMaintenanceException {
HDPowerViewWebTargets webTargets = this.webTargets;
if (webTargets == null) {
throw new ProcessingException("Web targets not initialized");
}

SceneCollections sceneCollections = webTargets.getSceneCollections();
if (sceneCollections == null) {
throw new JsonParseException("Missing 'sceneCollections' element");
}

List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
if (sceneCollectionData == null) {
throw new JsonParseException("Missing 'sceneCollections.sceneCollectionData' element");
}
logger.debug("Received data for {} sceneCollections", sceneCollectionData.size());

Map<String, Channel> idChannelMap = getIdSceneCollectionChannelMap();
for (SceneCollection sceneCollection : sceneCollectionData) {
// remove existing scene channel from the map
String sceneCollectionId = Integer.toString(sceneCollection.id);
if (idChannelMap.containsKey(sceneCollectionId)) {
idChannelMap.remove(sceneCollectionId);
logger.debug("Keeping channel for existing scene collection '{}'", sceneCollectionId);
} else {
// create a new scene channel
ChannelUID channelUID = new ChannelUID(getThing().getUID(), sceneCollectionId);
Channel channel = ChannelBuilder.create(channelUID, "Switch").withType(sceneCollectionChannelTypeUID)
.withLabel(sceneCollection.getName())
.withDescription("Activates the scene collection " + sceneCollection.getName()).build();
updateThing(editThing().withChannel(channel).build());
logger.debug("Creating new channel for scene collection '{}'", sceneCollectionId);
}
}

// remove any previously created channels that no longer exist
if (!idChannelMap.isEmpty()) {
logger.debug("Removing {} orphan scene collection channels", idChannelMap.size());
List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
allChannels.removeAll(idChannelMap.values());
updateThing(editThing().withChannels(allChannels).build());
}
}

private Map<Thing, String> getThingIdMap() {
Map<Thing, String> ret = new HashMap<>();
for (Thing thing : getThing().getThings()) {
Expand All @@ -313,7 +373,7 @@ private Map<String, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
return ret;
}

private Map<String, Channel> getIdChannelMap() {
private Map<String, Channel> getIdSceneChannelMap() {
Map<String, Channel> ret = new HashMap<>();
for (Channel channel : getThing().getChannels()) {
if (sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
Expand All @@ -323,6 +383,16 @@ private Map<String, Channel> getIdChannelMap() {
return ret;
}

private Map<String, Channel> getIdSceneCollectionChannelMap() {
Map<String, Channel> ret = new HashMap<>();
for (Channel channel : getThing().getChannels()) {
if (sceneCollectionChannelTypeUID.equals(channel.getChannelTypeUID())) {
ret.put(channel.getUID().getId(), channel);
}
}
return ret;
}

private void requestRefreshShadePositions() {
Map<Thing, String> thingIdMap = getThingIdMap();
for (Entry<Thing, String> item : thingIdMap.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
<description>Activates the scene</description>
</channel-type>

<channel-type id="scene-collection-activate">
<item-type>Switch</item-type>
<label>Activate</label>
<description>Activates the scene collection</description>
</channel-type>

<channel-type id="battery-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Battery Voltage</label>
Expand Down

0 comments on commit 212aa4f

Please sign in to comment.