diff --git a/bundles/org.openhab.binding.hdpowerview/README.md b/bundles/org.openhab.binding.hdpowerview/README.md index 46f0e27e8926c..08086ca2a35e2 100644 --- a/bundles/org.openhab.binding.hdpowerview/README.md +++ b/bundles/org.openhab.binding.hdpowerview/README.md @@ -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 | Switch | Flash repeater to identify. Setting this to ON will identify the repeater by blinking. Note: include `{autoupdate="false"}` in the item configuration to avoid having to reset it to off after use. | +| 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: + +``` +Switch Bedroom_Repeater_Identify "Bedroom Repeater Identify" {channel="hdpowerview:repeater:g24:r16384:identify", autoupdate="false"} +Switch Bedroom_Repeater_BlinkingEnabled "Bedroom Repeater Blinking Enabled [%s]" {channel="hdpowerview:repeater:g24:r16384:blinkingEnabled"} +``` + Scene items: ``` diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java index 371f63d717635..0d45171d8adea 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java @@ -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 @@ * * @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 NETBIOS_NAMES = Arrays.asList("PDBU-Hub3.0", "PowerView-Hub"); - public static final Set 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 SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_HUB, THING_TYPE_SHADE, + THING_TYPE_REPEATER); } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java index 819adc11df4aa..af8c201b99ff4 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java @@ -17,8 +17,9 @@ 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 boolean supportsThingType(ThingTypeUID thingTypeUID) { 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; diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java index 344dc6ab1e485..dd1748fafbfbd 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java @@ -27,11 +27,15 @@ 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 HDPowerViewWebTargets(HttpClient httpClient, String ipAddress) { sceneCollections = base + "scenecollections/"; scheduledEvents = base + "scheduledevents"; + + repeaters = base + "repeaters/"; + this.httpClient = httpClient; } @@ -385,6 +393,98 @@ public void enableScheduledEvent(int scheduledEventId, boolean enable) } } + /** + * 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 = 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 * diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterBlinking.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterBlinking.java new file mode 100644 index 0000000000000..ea3886af1f1c4 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterBlinking.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeater.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeater.java new file mode 100644 index 0000000000000..55ba66665b14f --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeater.java @@ -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; +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java new file mode 100644 index 0000000000000..8b0c255309ec3 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java @@ -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)); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeaters.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeaters.java new file mode 100644 index 0000000000000..8b5f15b12fd69 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeaters.java @@ -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; + public @Nullable List repeaterIds; +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewRepeaterConfiguration.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewRepeaterConfiguration.java new file mode 100644 index 0000000000000..c67b128a27679 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewRepeaterConfiguration.java @@ -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.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Basic configuration for an HD PowerView Repeater + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class HDPowerViewRepeaterConfiguration { + + public static final String ID = "id"; + + public @Nullable String id; +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java similarity index 54% rename from bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java rename to bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java index 0eecec1379444..f61be9f9a9b62 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java @@ -21,12 +21,15 @@ 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 @@ * @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 @@ private Runnable createScanner() { if (webTargets == null) { throw new HubProcessingException("Web targets not initialized"); } - Shades shades = webTargets.getShades(); - if (shades.shadeData != null) { - ThingUID bridgeUID = hub.getThing().getUID(); - List 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 @@ private Runnable createScanner() { stopScan(); }; } + + private void discoverShades(HDPowerViewWebTargets webTargets) + throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException { + Shades shades = webTargets.getShades(); + List 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 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()); + } + } } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java index 8b3d2c60e844f..176ba8f677996 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java @@ -328,7 +328,7 @@ private void pollShades() throws HubInvalidResponseException, HubProcessingExcep logger.debug("Received data for {} shades", shadesData.size()); Map idShadeDataMap = getIdShadeDataMap(shadesData); - Map thingIdMap = getThingIdMap(); + Map thingIdMap = getShadeThingIdMap(); for (Entry item : thingIdMap.entrySet()) { Thing thing = item.getKey(); String shadeId = item.getValue(); @@ -656,9 +656,12 @@ private void updateScheduledEventStates(List scheduledEvents) { } } - private Map getThingIdMap() { + private Map getShadeThingIdMap() { Map ret = new HashMap<>(); for (Thing thing : getThing().getThings()) { + if (!HDPowerViewBindingConstants.THING_TYPE_SHADE.equals(thing.getThingTypeUID())) { + continue; + } String id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id; if (id != null && !id.isEmpty()) { ret.put(thing, id); @@ -678,7 +681,7 @@ private Map getIdShadeDataMap(List shadeData) { } private void requestRefreshShadePositions() { - Map thingIdMap = getThingIdMap(); + Map thingIdMap = getShadeThingIdMap(); for (Entry item : thingIdMap.entrySet()) { Thing thing = item.getKey(); ThingHandler handler = thing.getHandler(); @@ -692,7 +695,7 @@ private void requestRefreshShadePositions() { } private void requestRefreshShadeBatteryLevels() { - Map thingIdMap = getThingIdMap(); + Map thingIdMap = getShadeThingIdMap(); for (Entry item : thingIdMap.entrySet()) { Thing thing = item.getKey(); ThingHandler handler = thing.getHandler(); diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java new file mode 100644 index 0000000000000..eecd78d477bdc --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java @@ -0,0 +1,202 @@ +/** + * 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.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.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 @Nullable ScheduledFuture refreshStatusFuture = null; + private int repeaterId; + + public HDPowerViewRepeaterHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + logger.debug("Initializing repeater handler"); + try { + repeaterId = getRepeaterId(); + } catch (NumberFormatException e) { + 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; + } + ThingStatus bridgeStatus = bridge.getStatus(); + if (bridgeStatus == ThingStatus.ONLINE) { + updateStatus(ThingStatus.UNKNOWN); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + scheduleJob(); + } + + @Override + public void dispose() { + logger.debug("Disposing repeater handler for repeater {}", repeaterId); + cancelJob(); + } + + private int getRepeaterId() throws NumberFormatException { + String str = getConfigAs(HDPowerViewRepeaterConfiguration.class).id; + if (str == null) { + throw new NumberFormatException("null input string"); + } + return Integer.parseInt(str); + } + + @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: + repeaterData = webTargets.identifyRepeater(repeaterId); + scheduler.submit(() -> updatePropertyAndState(repeaterData)); + 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 scheduleJob() { + cancelJob(); + 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 cancelJob() { + 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) { + logger.warn("Error connecting to bridge: {}", e.getMessage()); + 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); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties index 4a6ec1312e3d6..bff2a95cd26c5 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties @@ -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,10 @@ 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 = Enables/disables blinking +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.shade-calibrate.label = Calibrate channel-type.hdpowerview.shade-calibrate.description = Perform calibration of the shade channel-type.hdpowerview.shade-position.label = Position diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties index 6b4483f261e17..e2b6838b28f49 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties @@ -1,3 +1,15 @@ +# 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 = Aktiverer/deaktiverer blink +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 + # dynamic channels dynamic-channel.scene-activate.description = Aktiverer scenen ''{0}'' diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml index 80b128b60278b..1bc7332b727ec 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml @@ -85,6 +85,33 @@ + + + + + + Hunter Douglas (Luxaflex) PowerView Repeater + + + + + + + + Hunter Douglas (Luxaflex) + PowerView Repeater + + + id + + + + + The numeric ID of the PowerView Repeater in the Hub + + + + Rollershutter @@ -125,6 +152,18 @@ + + Switch + + Flash repeater to identify + + + + Switch + + Blink during commands + +