[hdpowerview] Add support for repeaters (#12061)

* Add support for repeaters.

Fixes #12060

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Simplify thing type filtering.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Improve robustness of configuration ID validation/initialization.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Convert repeater-identify to command channel.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Fix logged warning.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Skip unneeded bridge status logic.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Skip redundant logging.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>

* Fix chanenl type label for blinking enabled.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-01-24 22:42:53 +01:00 committed by GitHub
parent 3d24cfc535
commit 9d361c1fa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 682 additions and 96 deletions

View File

@ -13,10 +13,11 @@ By using a scene to control multiple shades at once, the shades will all begin m
## Supported Things
| Thing | Thing Type | Description |
|-------|------------|-------------|
| hub | Bridge | The PowerView hub provides the interface between your network and the shade's radio network. It also contains channels used to interact with scenes. |
| shade | Thing | A motorized shade. |
| Thing | Thing Type | Description |
|----------|------------|-------------|
| hub | Bridge | The PowerView hub provides the interface between your network and the shade's radio network. It also contains channels used to interact with scenes. |
| shade | Thing | A motorized shade. |
| repeater | Thing | A PowerView signal repeater. |
## Discovery
@ -46,16 +47,25 @@ If in the future, you add additional shades or scenes to your system, the bindin
| hardRefresh | The number of minutes between hard refreshes of the PowerView hub's shade state (default 180 three hours). See [Refreshing the PowerView Hub Cache](#Refreshing-the-PowerView-Hub-Cache). |
| hardRefreshBatteryLevel | The number of hours between hard refreshes of battery levels from the PowerView Hub (or 0 to disable, defaulting to weekly). See [Refreshing the PowerView Hub Cache](#Refreshing-the-PowerView-Hub-Cache). |
### Thing Configuration for PowerView Shades
### Thing Configuration for PowerView Shades and Accessories
PowerView shades should preferably be configured via the automatic discovery process.
It is quite difficult to configure manually as the `id` of the shade is not exposed in the PowerView app.
However, the configuration parameters are described below:
PowerView shades and repeaters should preferably be configured via the automatic discovery process.
It is quite difficult to configure manually as the `id` of the shade or repeater is not exposed in the
PowerView app. However, the configuration parameters are described below.
#### Thing Configuration for PowerView Shades
| Configuration Parameter | Description |
|-------------------------|-------------|
| id | The ID of the PowerView shade in the app. Must be an integer. |
#### Thing Configuration for PowerView Repeaters
| Configuration Parameter | Description |
|-------------------------|-------------|
| id | The ID of the PowerView repeater in the app. Must be an integer. |
## Channels
### Channels for Hub (Thing type `hub`)
@ -88,6 +98,13 @@ All of these channels appear in the binding, but only those which have a physica
| batteryVoltage | Number:ElectricPotential | Battery voltage reported by the shade. |
| signalStrength | Number | Signal strength (0 for no or unknown signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
### Channels for Repeaters (Thing type `repeater`)
| Channel | Item Type | Description |
|-----------------|-----------|-------------------------------|
| identify | String | Flash repeater to identify. Valid values are: IDENTIFY |
| blinkingEnabled | Switch | Blink during commands. |
### Roller Shutter Up/Down Position vs. Open/Close State
The `position` and `secondary` channels are Rollershutter types.
@ -191,6 +208,7 @@ For single shades the refresh takes the item's channel into consideration:
```
Bridge hdpowerview:hub:g24 "Luxaflex Hub" @ "Living Room" [host="192.168.1.123"] {
Thing shade s50150 "Living Room Shade" @ "Living Room" [id="50150"]
Thing repeater r16384 "Bedroom Repeater" @ "Bedroom" [id="16384"]
}
```
@ -200,16 +218,19 @@ Shade items:
```
Rollershutter Living_Room_Shade_Position "Living Room Shade Position [%.0f %%]" {channel="hdpowerview:shade:g24:s50150:position"}
Rollershutter Living_Room_Shade_Secondary "Living Room Shade Secondary Position [%.0f %%]" {channel="hdpowerview:shade:g24:s50150:secondary"}
Dimmer Living_Room_Shade_Vane "Living Room Shade Vane [%.0f %%]" {channel="hdpowerview:shade:g24:s50150:vane"}
Switch Living_Room_Shade_Battery_Low_Alarm "Living Room Shade Battery Low Alarm [%s]" {channel="hdpowerview:shade:g24:s50150:lowBattery"}
Switch Living_Room_Shade_Calibrate "Living Room Shade Calibrate" {channel="hdpowerview:shade:g24:s50150:calibrate", autoupdate="false"}
```
Repeater items:
```
String Bedroom_Repeater_Identify "Bedroom Repeater Identify" {channel="hdpowerview:repeater:g24:r16384:identify"}
Switch Bedroom_Repeater_BlinkingEnabled "Bedroom Repeater Blinking Enabled [%s]" {channel="hdpowerview:repeater:g24:r16384:blinkingEnabled"}
```
Scene items:
```
@ -219,8 +240,12 @@ Switch Living_Room_Shades_Scene_Heart "Living Room Shades Scene Heart" <blinds>
### `demo.sitemap` File
```
Frame label="Living Room Shades" {
Switch item=Living_Room_Shades_Scene_Open
Slider item=Living_Room_Shade_1_Position 
Frame label="Living Room Shades" {
Switch item=Living_Room_Shades_Scene_Open
Slider item=Living_Room_Shade_1_Position
}
Frame label="Bedroom" {
Switch item=Bedroom_Repeater_Identify mappings=[IDENTIFY="Identify"]
Switch item=Bedroom_Repeater_BlinkingEnabled
}
```

View File

@ -13,7 +13,6 @@
package org.openhab.binding.hdpowerview.internal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -26,7 +25,7 @@ import org.openhab.core.thing.ThingTypeUID;
*
* @author Andy Lintner - Initial contribution
* @author Andrew Fiddian-Green - Added support for secondary rail positions
* @author Jacob Laursen - Add support for scene groups and automations
* @author Jacob Laursen - Added support for scene groups, automations and repeaters
*/
@NonNullByDefault
public class HDPowerViewBindingConstants {
@ -36,6 +35,7 @@ public class HDPowerViewBindingConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_HUB = new ThingTypeUID(BINDING_ID, "hub");
public static final ThingTypeUID THING_TYPE_SHADE = new ThingTypeUID(BINDING_ID, "shade");
public static final ThingTypeUID THING_TYPE_REPEATER = new ThingTypeUID(BINDING_ID, "repeater");
// List of all Channel ids
public static final String CHANNEL_SHADE_POSITION = "position";
@ -47,6 +47,9 @@ public class HDPowerViewBindingConstants {
public static final String CHANNEL_SHADE_BATTERY_VOLTAGE = "batteryVoltage";
public static final String CHANNEL_SHADE_SIGNAL_STRENGTH = "signalStrength";
public static final String CHANNEL_REPEATER_IDENTIFY = "identify";
public static final String CHANNEL_REPEATER_BLINKING_ENABLED = "blinkingEnabled";
public static final String CHANNEL_GROUP_SCENES = "scenes";
public static final String CHANNEL_GROUP_SCENE_GROUPS = "sceneGroups";
public static final String CHANNEL_GROUP_AUTOMATIONS = "automations";
@ -68,10 +71,6 @@ public class HDPowerViewBindingConstants {
public static final List<String> NETBIOS_NAMES = Arrays.asList("PDBU-Hub3.0", "PowerView-Hub");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
static {
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_HUB);
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_SHADE);
}
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_HUB, THING_TYPE_SHADE,
THING_TYPE_REPEATER);
}

View File

@ -17,8 +17,9 @@ import java.util.Hashtable;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.hdpowerview.internal.discovery.HDPowerViewShadeDiscoveryService;
import org.openhab.binding.hdpowerview.internal.discovery.HDPowerViewDeviceDiscoveryService;
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewHubHandler;
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewRepeaterHandler;
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewShadeHandler;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocaleProvider;
@ -67,12 +68,14 @@ public class HDPowerViewHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(HDPowerViewBindingConstants.THING_TYPE_HUB)) {
if (HDPowerViewBindingConstants.THING_TYPE_HUB.equals(thingTypeUID)) {
HDPowerViewHubHandler handler = new HDPowerViewHubHandler((Bridge) thing, httpClient, translationProvider);
registerService(new HDPowerViewShadeDiscoveryService(handler));
registerService(new HDPowerViewDeviceDiscoveryService(handler));
return handler;
} else if (thingTypeUID.equals(HDPowerViewBindingConstants.THING_TYPE_SHADE)) {
} else if (HDPowerViewBindingConstants.THING_TYPE_SHADE.equals(thingTypeUID)) {
return new HDPowerViewShadeHandler(thing);
} else if (HDPowerViewBindingConstants.THING_TYPE_REPEATER.equals(thingTypeUID)) {
return new HDPowerViewRepeaterHandler(thing);
}
return null;

View File

@ -27,11 +27,15 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
import org.openhab.binding.hdpowerview.internal.api.requests.RepeaterBlinking;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeCalibrate;
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.FirmwareVersion;
import org.openhab.binding.hdpowerview.internal.api.responses.FirmwareVersions;
import org.openhab.binding.hdpowerview.internal.api.responses.Repeater;
import org.openhab.binding.hdpowerview.internal.api.responses.RepeaterData;
import org.openhab.binding.hdpowerview.internal.api.responses.Repeaters;
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;
@ -83,6 +87,7 @@ public class HDPowerViewWebTargets {
private final String sceneCollectionActivate;
private final String sceneCollections;
private final String scheduledEvents;
private final String repeaters;
private final Gson gson = new Gson();
private final HttpClient httpClient;
@ -135,6 +140,9 @@ public class HDPowerViewWebTargets {
sceneCollections = base + "scenecollections/";
scheduledEvents = base + "scheduledevents";
repeaters = base + "repeaters/";
this.httpClient = httpClient;
}
@ -385,6 +393,98 @@ public class HDPowerViewWebTargets {
}
}
/**
* Fetches a JSON package that describes all repeaters in the hub, and wraps it in
* a Repeaters class instance
*
* @return Repeaters class instance
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public Repeaters getRepeaters()
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
String json = invoke(HttpMethod.GET, repeaters, null, null);
try {
Repeaters repeaters = gson.fromJson(json, Repeaters.class);
if (repeaters == null) {
throw new HubInvalidResponseException("Missing repeaters response");
}
List<RepeaterData> repeaterData = repeaters.repeaterData;
if (repeaterData == null) {
throw new HubInvalidResponseException("Missing 'repeaters.repeaterData' element");
}
return repeaters;
} catch (JsonParseException e) {
throw new HubInvalidResponseException("Error parsing repeaters response", e);
}
}
/**
* Fetches a JSON package that describes a specific repeater in the hub, and wraps it
* in a RepeaterData class instance
*
* @param repeaterId id of the repeater to be fetched
* @return RepeaterData class instance
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public RepeaterData getRepeater(int repeaterId)
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
String jsonResponse = invoke(HttpMethod.GET, repeaters + Integer.toString(repeaterId), null, null);
return repeaterDataFromJson(jsonResponse);
}
private RepeaterData repeaterDataFromJson(String json) throws HubInvalidResponseException {
try {
Repeater repeater = gson.fromJson(json, Repeater.class);
if (repeater == null) {
throw new HubInvalidResponseException("Missing repeater response");
}
RepeaterData repeaterData = repeater.repeater;
if (repeaterData == null) {
throw new HubInvalidResponseException("Missing 'repeater.repeater' element");
}
return repeaterData;
} catch (JsonParseException e) {
throw new HubInvalidResponseException("Error parsing repeater response", e);
}
}
/**
* Instructs the hub to identify a specific repeater by blinking
*
* @param repeaterId id of the repeater to be identified
* @return RepeaterData class instance
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public RepeaterData identifyRepeater(int repeaterId)
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
String jsonResponse = invoke(HttpMethod.GET, repeaters + repeaterId,
Query.of("identify", Boolean.toString(true)), null);
return repeaterDataFromJson(jsonResponse);
}
/**
* Enables or disables blinking for a repeater
*
* @param repeaterId id of the repeater for which to be enable or disable blinking
* @param enable true to enable blinking, false to disable
* @return RepeaterData class instance
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public RepeaterData enableRepeaterBlinking(int repeaterId, boolean enable)
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
String jsonRequest = gson.toJson(new RepeaterBlinking(repeaterId, enable));
String jsonResponse = invoke(HttpMethod.PUT, repeaters + repeaterId, null, jsonRequest);
return repeaterDataFromJson(jsonResponse);
}
/**
* Invoke a call on the hub server to retrieve information or send a command
*

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2022 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.requests;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* State of a single Repeater for being updated by an HD PowerView Hub
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class RepeaterBlinking {
public Repeater repeater;
public class Repeater {
public int id;
public boolean blinkEnabled;
public Repeater(int id, boolean enable) {
this.id = id;
this.blinkEnabled = enable;
}
}
public RepeaterBlinking(int id, boolean enable) {
repeater = new Repeater(id, enable);
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2022 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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* State of a single Repeater, as returned by an HD PowerView Hub
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class Repeater {
public @Nullable RepeaterData repeater;
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2022 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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hdpowerview.internal.api.Firmware;
/**
* Repeater data for a single Repeater, as returned by an HD PowerView Hub
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class RepeaterData {
public int id;
public @Nullable String name;
public int roomId;
public int groupId;
public boolean blinkEnabled;
public @Nullable Firmware firmware;
public String getName() {
return new String(Base64.getDecoder().decode(name));
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2022 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.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Repeaters connected to an HD PowerView Hub
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class Repeaters {
public @Nullable List<RepeaterData> repeaterData;
public @Nullable List<Integer> repeaterIds;
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2022 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Basic configuration for an HD PowerView Repeater
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class HDPowerViewRepeaterConfiguration {
public static final String ID = "id";
public int id;
}

View File

@ -13,7 +13,6 @@
package org.openhab.binding.hdpowerview.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Basic configuration for an HD PowerView Shade
@ -25,5 +24,5 @@ public class HDPowerViewShadeConfiguration {
public static final String ID = "id";
public @Nullable String id;
public int id;
}

View File

@ -21,12 +21,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants;
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
import org.openhab.binding.hdpowerview.internal.api.responses.RepeaterData;
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewRepeaterConfiguration;
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase;
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewHubHandler;
@ -42,15 +45,15 @@ import org.slf4j.LoggerFactory;
* @author Andy Lintner - Initial contribution
*/
@NonNullByDefault
public class HDPowerViewShadeDiscoveryService extends AbstractDiscoveryService {
public class HDPowerViewDeviceDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeDiscoveryService.class);
private final Logger logger = LoggerFactory.getLogger(HDPowerViewDeviceDiscoveryService.class);
private final HDPowerViewHubHandler hub;
private final Runnable scanner;
private @Nullable ScheduledFuture<?> backgroundFuture;
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
public HDPowerViewShadeDiscoveryService(HDPowerViewHubHandler hub) {
public HDPowerViewDeviceDiscoveryService(HDPowerViewHubHandler hub) {
super(Collections.singleton(HDPowerViewBindingConstants.THING_TYPE_SHADE), 600, true);
this.hub = hub;
this.scanner = createScanner();
@ -87,34 +90,8 @@ public class HDPowerViewShadeDiscoveryService extends AbstractDiscoveryService {
if (webTargets == null) {
throw new HubProcessingException("Web targets not initialized");
}
Shades shades = webTargets.getShades();
if (shades.shadeData != null) {
ThingUID bridgeUID = hub.getThing().getUID();
List<ShadeData> shadesData = shades.shadeData;
if (shadesData != null) {
for (ShadeData shadeData : shadesData) {
if (shadeData.id != 0) {
String id = Integer.toString(shadeData.id);
ThingUID thingUID = new ThingUID(HDPowerViewBindingConstants.THING_TYPE_SHADE,
bridgeUID, id);
Integer caps = shadeData.capabilities;
Capabilities capabilities = db.getCapabilities((caps != null) ? caps.intValue() : -1);
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID)
.withLabel(shadeData.getName()).withBridge(bridgeUID)
.withProperty(HDPowerViewShadeConfiguration.ID, id)
.withProperty(HDPowerViewBindingConstants.PROPERTY_SHADE_TYPE,
db.getType(shadeData.type).toString())
.withProperty(HDPowerViewBindingConstants.PROPERTY_SHADE_CAPABILITIES,
capabilities.toString())
.withRepresentationProperty(HDPowerViewShadeConfiguration.ID);
logger.debug("Hub discovered shade '{}'", id);
thingDiscovered(builder.build());
}
}
}
}
discoverShades(webTargets);
discoverRepeaters(webTargets);
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
} catch (HubException e) {
@ -123,4 +100,56 @@ public class HDPowerViewShadeDiscoveryService extends AbstractDiscoveryService {
stopScan();
};
}
private void discoverShades(HDPowerViewWebTargets webTargets)
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
Shades shades = webTargets.getShades();
List<ShadeData> shadesData = shades.shadeData;
if (shadesData == null) {
return;
}
ThingUID bridgeUid = hub.getThing().getUID();
for (ShadeData shadeData : shadesData) {
if (shadeData.id == 0) {
continue;
}
String id = Integer.toString(shadeData.id);
ThingUID thingUID = new ThingUID(HDPowerViewBindingConstants.THING_TYPE_SHADE, bridgeUid, id);
Integer caps = shadeData.capabilities;
Capabilities capabilities = db.getCapabilities((caps != null) ? caps.intValue() : -1);
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withLabel(shadeData.getName())
.withBridge(bridgeUid).withProperty(HDPowerViewShadeConfiguration.ID, id)
.withProperty(HDPowerViewBindingConstants.PROPERTY_SHADE_TYPE,
db.getType(shadeData.type).toString())
.withProperty(HDPowerViewBindingConstants.PROPERTY_SHADE_CAPABILITIES, capabilities.toString())
.withRepresentationProperty(HDPowerViewShadeConfiguration.ID);
logger.debug("Hub discovered shade '{}'", id);
thingDiscovered(builder.build());
}
}
private void discoverRepeaters(HDPowerViewWebTargets webTargets)
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
List<RepeaterData> repeaters = webTargets.getRepeaters().repeaterData;
if (repeaters == null) {
return;
}
ThingUID bridgeUid = hub.getThing().getUID();
for (RepeaterData repeaterData : repeaters) {
if (repeaterData.id == 0) {
continue;
}
String id = Integer.toString(repeaterData.id);
ThingUID thingUid = new ThingUID(HDPowerViewBindingConstants.THING_TYPE_REPEATER, bridgeUid, id);
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUid).withLabel(repeaterData.getName())
.withBridge(bridgeUid).withProperty(HDPowerViewRepeaterConfiguration.ID, id)
.withRepresentationProperty(HDPowerViewRepeaterConfiguration.ID);
logger.debug("Hub discovered repeater '{}'", id);
thingDiscovered(builder.build());
}
}
}

View File

@ -325,17 +325,17 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
updateStatus(ThingStatus.ONLINE);
logger.debug("Received data for {} shades", shadesData.size());
Map<String, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
Map<Thing, String> thingIdMap = getThingIdMap();
for (Entry<Thing, String> item : thingIdMap.entrySet()) {
Map<Integer, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
String shadeId = item.getValue();
int shadeId = item.getValue();
ShadeData shadeData = idShadeDataMap.get(shadeId);
updateShadeThing(shadeId, thing, shadeData);
}
}
private void updateShadeThing(String shadeId, Thing thing, @Nullable ShadeData shadeData) {
private void updateShadeThing(int shadeId, Thing thing, @Nullable ShadeData shadeData) {
HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
if (thingHandler == null) {
logger.debug("Shade '{}' handler not initialized", shadeId);
@ -533,50 +533,52 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
}
}
private Map<Thing, String> getThingIdMap() {
Map<Thing, String> ret = new HashMap<>();
for (Thing thing : getThing().getThings()) {
String id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
if (id != null && !id.isEmpty()) {
ret.put(thing, id);
}
}
private Map<Thing, Integer> getShadeThingIdMap() {
Map<Thing, Integer> ret = new HashMap<>();
getThing().getThings().stream()
.filter(thing -> HDPowerViewBindingConstants.THING_TYPE_SHADE.equals(thing.getThingTypeUID()))
.forEach(thing -> {
int id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
if (id > 0) {
ret.put(thing, id);
}
});
return ret;
}
private Map<String, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
Map<String, ShadeData> ret = new HashMap<>();
private Map<Integer, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
Map<Integer, ShadeData> ret = new HashMap<>();
for (ShadeData shade : shadeData) {
if (shade.id != 0) {
ret.put(Integer.toString(shade.id), shade);
if (shade.id > 0) {
ret.put(shade.id, shade);
}
}
return ret;
}
private void requestRefreshShadePositions() {
Map<Thing, String> thingIdMap = getThingIdMap();
for (Entry<Thing, String> item : thingIdMap.entrySet()) {
Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
} else {
String shadeId = item.getValue();
int shadeId = item.getValue();
logger.debug("Shade '{}' handler not initialized", shadeId);
}
}
}
private void requestRefreshShadeBatteryLevels() {
Map<Thing, String> thingIdMap = getThingIdMap();
for (Entry<Thing, String> item : thingIdMap.entrySet()) {
Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
} else {
String shadeId = item.getValue();
int shadeId = item.getValue();
logger.debug("Shade '{}' handler not initialized", shadeId);
}
}

View File

@ -0,0 +1,212 @@
/**
* Copyright (c) 2010-2022 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.handler;
import static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
import org.openhab.binding.hdpowerview.internal.api.Firmware;
import org.openhab.binding.hdpowerview.internal.api.responses.RepeaterData;
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewRepeaterConfiguration;
import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles commands for an HD PowerView Repeater
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class HDPowerViewRepeaterHandler extends AbstractHubbedThingHandler {
private final Logger logger = LoggerFactory.getLogger(HDPowerViewRepeaterHandler.class);
private static final int REFRESH_INTERVAL_MINUTES = 5;
private static final int IDENTITY_PERIOD_SECONDS = 3;
private static final String COMMAND_IDENTIFY = "IDENTIFY";
private @Nullable ScheduledFuture<?> refreshStatusFuture = null;
private @Nullable ScheduledFuture<?> resetIdentifyStateFuture = null;
private int repeaterId;
public HDPowerViewRepeaterHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
repeaterId = getConfigAs(HDPowerViewRepeaterConfiguration.class).id;
logger.debug("Initializing repeater handler for repeater {}", repeaterId);
if (repeaterId <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error.invalid-id");
return;
}
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
return;
}
if (!(bridge.getHandler() instanceof HDPowerViewHubHandler)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"@text/offline.conf-error.invalid-bridge-handler");
return;
}
updateStatus(ThingStatus.UNKNOWN);
scheduleRefreshJob();
}
@Override
public void dispose() {
logger.debug("Disposing repeater handler for repeater {}", repeaterId);
cancelRefreshJob();
cancelResetIdentifyStateJob();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
HDPowerViewHubHandler bridge = getBridgeHandler();
if (bridge == null) {
logger.warn("Missing bridge handler");
return;
}
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
if (webTargets == null) {
logger.warn("Web targets not initialized");
return;
}
try {
RepeaterData repeaterData;
switch (channelUID.getId()) {
case CHANNEL_REPEATER_IDENTIFY:
if (command instanceof StringType) {
if (COMMAND_IDENTIFY.equals(((StringType) command).toString())) {
repeaterData = webTargets.identifyRepeater(repeaterId);
scheduler.submit(() -> updatePropertyAndState(repeaterData));
cancelResetIdentifyStateJob();
resetIdentifyStateFuture = scheduler.schedule(() -> {
updateState(CHANNEL_REPEATER_IDENTIFY, UnDefType.UNDEF);
}, IDENTITY_PERIOD_SECONDS, TimeUnit.SECONDS);
} else {
logger.warn("Unsupported command: {}. Supported commands are: " + COMMAND_IDENTIFY,
command);
}
}
break;
case CHANNEL_REPEATER_BLINKING_ENABLED:
repeaterData = webTargets.enableRepeaterBlinking(repeaterId, OnOffType.ON == command);
scheduler.submit(() -> updatePropertyAndState(repeaterData));
break;
}
} catch (HubInvalidResponseException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
} else {
logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
} catch (HubException e) {
logger.warn("Unexpected error: {}", e.getMessage());
}
}
private void cancelResetIdentifyStateJob() {
ScheduledFuture<?> scheduledJob = resetIdentifyStateFuture;
if (scheduledJob != null) {
scheduledJob.cancel(true);
}
resetIdentifyStateFuture = null;
}
private void scheduleRefreshJob() {
cancelRefreshJob();
logger.debug("Scheduling poll for repeater {} now, then every {} minutes", repeaterId,
REFRESH_INTERVAL_MINUTES);
this.refreshStatusFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, REFRESH_INTERVAL_MINUTES,
TimeUnit.MINUTES);
}
private void cancelRefreshJob() {
ScheduledFuture<?> future = this.refreshStatusFuture;
if (future != null) {
future.cancel(false);
}
this.refreshStatusFuture = null;
}
private synchronized void poll() {
HDPowerViewHubHandler bridge = getBridgeHandler();
if (bridge == null) {
logger.warn("Missing bridge handler");
return;
}
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
if (webTargets == null) {
logger.warn("Web targets not initialized");
return;
}
try {
logger.debug("Polling for status information");
RepeaterData repeaterData = webTargets.getRepeater(repeaterId);
updatePropertyAndState(repeaterData);
} catch (HubInvalidResponseException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
} else {
logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
} catch (HubException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
}
}
private void updatePropertyAndState(RepeaterData repeaterData) {
updateStatus(ThingStatus.ONLINE);
Firmware firmware = repeaterData.firmware;
if (firmware != null) {
logger.debug("Repeater firmware version received: {}", firmware.toString());
updateProperty(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
} else {
logger.warn("Repeater firmware version missing in response");
}
updateState(CHANNEL_REPEATER_BLINKING_ENABLED, repeaterData.blinkEnabled ? OnOffType.ON : OnOffType.OFF);
}
}

View File

@ -86,11 +86,10 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
@Override
public void initialize() {
logger.debug("Initializing shade handler");
isDisposing = false;
try {
shadeId = getShadeId();
} catch (NumberFormatException e) {
shadeId = getConfigAs(HDPowerViewShadeConfiguration.class).id;
logger.debug("Initializing shade handler for shade {}", shadeId);
if (shadeId <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error.invalid-id");
return;
@ -459,14 +458,6 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
updatePositionStates(shadePosition);
}
private int getShadeId() throws NumberFormatException {
String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
if (str == null) {
throw new NumberFormatException("null input string");
}
return Integer.parseInt(str);
}
/**
* Request that the shade shall undergo a 'hard' refresh for querying its current position
*/

View File

@ -7,6 +7,8 @@ binding.hdpowerview.description = The Hunter Douglas PowerView binding provides
thing-type.hdpowerview.hub.label = PowerView Hub
thing-type.hdpowerview.hub.description = Hunter Douglas (Luxaflex) PowerView Hub
thing-type.hdpowerview.repeater.label = PowerView Repeater
thing-type.hdpowerview.repeater.description = Hunter Douglas (Luxaflex) PowerView Repeater
thing-type.hdpowerview.shade.label = PowerView Shade
thing-type.hdpowerview.shade.description = Hunter Douglas (Luxaflex) PowerView Shade
thing-type.hdpowerview.shade.channel.secondary.label = Secondary Position
@ -22,6 +24,8 @@ thing-type.config.hdpowerview.hub.host.label = Host
thing-type.config.hdpowerview.hub.host.description = The Host address of the PowerView Hub
thing-type.config.hdpowerview.hub.refresh.label = Refresh Interval
thing-type.config.hdpowerview.hub.refresh.description = The number of milliseconds between fetches of the PowerView Hub shade state
thing-type.config.hdpowerview.repeater.id.label = ID
thing-type.config.hdpowerview.repeater.id.description = The numeric ID of the PowerView Repeater in the Hub
thing-type.config.hdpowerview.shade.id.label = ID
thing-type.config.hdpowerview.shade.id.description = The numeric ID of the PowerView Shade in the Hub
@ -29,6 +33,11 @@ thing-type.config.hdpowerview.shade.id.description = The numeric ID of the Power
channel-type.hdpowerview.battery-voltage.label = Battery Voltage
channel-type.hdpowerview.battery-voltage.description = Battery voltage reported by the shade
channel-type.hdpowerview.repeater-blinking-enabled.label = Blinking Enabled
channel-type.hdpowerview.repeater-blinking-enabled.description = Blink during commands
channel-type.hdpowerview.repeater-identify.label = Identify
channel-type.hdpowerview.repeater-identify.description = Flash repeater to identify
channel-type.hdpowerview.repeater-identify.command.option.IDENTIFY = Identify
channel-type.hdpowerview.shade-calibrate.label = Calibrate
channel-type.hdpowerview.shade-calibrate.description = Perform calibration of the shade
channel-type.hdpowerview.shade-position.label = Position

View File

@ -1,3 +1,16 @@
# thing types
thing-type.hdpowerview.repeater.label = PowerView Repeater
thing-type.hdpowerview.repeater.description = Hunter Douglas (Luxaflex) PowerView Repeater
# channel types
channel-type.hdpowerview.repeater-blinking-enabled.label = Blink aktiveret
channel-type.hdpowerview.repeater-blinking-enabled.description = Blink når kommandoer aktiveres
channel-type.hdpowerview.repeater-identify.label = Identificer
channel-type.hdpowerview.repeater-identify.description = Identificer ved at lade repeater blinke
channel-type.hdpowerview.repeater-identify.command.option.IDENTIFY = Identificer
# dynamic channels
dynamic-channel.scene-activate.description = Aktiverer scenen ''{0}''

View File

@ -75,13 +75,40 @@
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="text" required="true">
<parameter name="id" type="integer" min="1" required="true">
<label>ID</label>
<description>The numeric ID of the PowerView Shade in the Hub</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="repeater">
<supported-bridge-type-refs>
<bridge-type-ref id="hub"/>
</supported-bridge-type-refs>
<label>PowerView Repeater</label>
<description>Hunter Douglas (Luxaflex) PowerView Repeater</description>
<channels>
<channel id="identify" typeId="repeater-identify"/>
<channel id="blinkingEnabled" typeId="repeater-blinking-enabled"/>
</channels>
<properties>
<property name="vendor">Hunter Douglas (Luxaflex)</property>
<property name="modelId">PowerView Repeater</property>
</properties>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="integer" min="1" required="true">
<label>ID</label>
<description>The numeric ID of the PowerView Repeater in the Hub</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="shade-position">
<item-type>Rollershutter</item-type>
<label>Position</label>
@ -122,6 +149,23 @@
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="repeater-identify">
<item-type>String</item-type>
<label>Identify</label>
<description>Flash repeater to identify</description>
<command>
<options>
<option value="IDENTIFY">Identify</option>
</options>
</command>
</channel-type>
<channel-type id="repeater-blinking-enabled">
<item-type>Switch</item-type>
<label>Blinking Enabled</label>
<description>Blink during commands</description>
</channel-type>
<channel-group-type id="scenes">
<label>Scenes</label>
</channel-group-type>