]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hdpowerview] Add support for repeaters (#12061)
authorJacob Laursen <jacob-github@vindvejr.dk>
Mon, 24 Jan 2022 21:42:53 +0000 (22:42 +0100)
committerGitHub <noreply@github.com>
Mon, 24 Jan 2022 21:42:53 +0000 (22:42 +0100)
* 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>
18 files changed:
bundles/org.openhab.binding.hdpowerview/README.md
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterBlinking.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeater.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/Repeaters.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewRepeaterConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewShadeConfiguration.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java [deleted file]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml

index 46f0e27e8926c36baeed8bb045f71ec68f612090..c777915c4681e4b23ac3c7a9746ff6d63d882b40 100644 (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
 }
 ```
index 371f63d7176359abeeebb7491001b0626c320587..0d45171d8adea05bb15b6eef7e6faaff9381a06a 100644 (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);
 }
index 819adc11df4aa7cede24356e801a611b279b6824..af8c201b99ff41908f53fa4f00610f6dde001704 100644 (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;
index 344dc6ab1e485337a5dcea92c7b1957801762df2..dd1748fafbfbd24a1635567a3a758166a0b41839 100644 (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
      *
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 (file)
index 0000000..ea3886a
--- /dev/null
@@ -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 (file)
index 0000000..55ba666
--- /dev/null
@@ -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 (file)
index 0000000..8b0c255
--- /dev/null
@@ -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 (file)
index 0000000..8b5f15b
--- /dev/null
@@ -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;
+}
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 (file)
index 0000000..29fd594
--- /dev/null
@@ -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;
+}
index 164d1d5a5a741c9a6e4971f3d366f1c6b801c40e..570aeeff14463a691c66f9345e36f9593afeb2a5 100644 (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;
 }
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java
new file mode 100644 (file)
index 0000000..f61be9f
--- /dev/null
@@ -0,0 +1,155 @@
+/**
+ * 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.discovery;
+
+import java.util.Collections;
+import java.util.List;
+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.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;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Discovers an HD PowerView Shade from an existing hub
+ *
+ * @author Andy Lintner - Initial contribution
+ */
+@NonNullByDefault
+public class HDPowerViewDeviceDiscoveryService extends AbstractDiscoveryService {
+
+    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 HDPowerViewDeviceDiscoveryService(HDPowerViewHubHandler hub) {
+        super(Collections.singleton(HDPowerViewBindingConstants.THING_TYPE_SHADE), 600, true);
+        this.hub = hub;
+        this.scanner = createScanner();
+    }
+
+    @Override
+    protected void startScan() {
+        scheduler.execute(scanner);
+    }
+
+    @Override
+    protected void startBackgroundDiscovery() {
+        ScheduledFuture<?> backgroundFuture = this.backgroundFuture;
+        if (backgroundFuture != null && !backgroundFuture.isDone()) {
+            backgroundFuture.cancel(true);
+        }
+        this.backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS);
+    }
+
+    @Override
+    protected void stopBackgroundDiscovery() {
+        ScheduledFuture<?> backgroundFuture = this.backgroundFuture;
+        if (backgroundFuture != null && !backgroundFuture.isDone()) {
+            backgroundFuture.cancel(true);
+            this.backgroundFuture = null;
+        }
+        super.stopBackgroundDiscovery();
+    }
+
+    private Runnable createScanner() {
+        return () -> {
+            try {
+                HDPowerViewWebTargets webTargets = hub.getWebTargets();
+                if (webTargets == null) {
+                    throw new HubProcessingException("Web targets not initialized");
+                }
+                discoverShades(webTargets);
+                discoverRepeaters(webTargets);
+            } catch (HubMaintenanceException e) {
+                // exceptions are logged in HDPowerViewWebTargets
+            } catch (HubException e) {
+                logger.warn("Unexpected error: {}", e.getMessage());
+            }
+            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());
+        }
+    }
+}
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/HDPowerViewShadeDiscoveryService.java
deleted file mode 100644 (file)
index 0eecec1..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * 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.discovery;
-
-import java.util.Collections;
-import java.util.List;
-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.HDPowerViewBindingConstants;
-import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
-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.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.HubMaintenanceException;
-import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
-import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewHubHandler;
-import org.openhab.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.thing.ThingUID;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Discovers an HD PowerView Shade from an existing hub
- *
- * @author Andy Lintner - Initial contribution
- */
-@NonNullByDefault
-public class HDPowerViewShadeDiscoveryService extends AbstractDiscoveryService {
-
-    private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeDiscoveryService.class);
-    private final HDPowerViewHubHandler hub;
-    private final Runnable scanner;
-    private @Nullable ScheduledFuture<?> backgroundFuture;
-    private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
-
-    public HDPowerViewShadeDiscoveryService(HDPowerViewHubHandler hub) {
-        super(Collections.singleton(HDPowerViewBindingConstants.THING_TYPE_SHADE), 600, true);
-        this.hub = hub;
-        this.scanner = createScanner();
-    }
-
-    @Override
-    protected void startScan() {
-        scheduler.execute(scanner);
-    }
-
-    @Override
-    protected void startBackgroundDiscovery() {
-        ScheduledFuture<?> backgroundFuture = this.backgroundFuture;
-        if (backgroundFuture != null && !backgroundFuture.isDone()) {
-            backgroundFuture.cancel(true);
-        }
-        this.backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS);
-    }
-
-    @Override
-    protected void stopBackgroundDiscovery() {
-        ScheduledFuture<?> backgroundFuture = this.backgroundFuture;
-        if (backgroundFuture != null && !backgroundFuture.isDone()) {
-            backgroundFuture.cancel(true);
-            this.backgroundFuture = null;
-        }
-        super.stopBackgroundDiscovery();
-    }
-
-    private Runnable createScanner() {
-        return () -> {
-            try {
-                HDPowerViewWebTargets webTargets = hub.getWebTargets();
-                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());
-                            }
-                        }
-                    }
-                }
-            } catch (HubMaintenanceException e) {
-                // exceptions are logged in HDPowerViewWebTargets
-            } catch (HubException e) {
-                logger.warn("Unexpected error: {}", e.getMessage());
-            }
-            stopScan();
-        };
-    }
-}
index b1f4de389ade6069fbc87f3f13803b21b685da83..ebe36df33a16293fcd4c2c6d99cd40b68590b49f 100644 (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);
             }
         }
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 (file)
index 0000000..36a8dad
--- /dev/null
@@ -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);
+    }
+}
index 6b42bf7d01319c05d20edbbe37e3e1be831b5fb6..30ab73e7008561660b1661754b468cae1a6df285 100644 (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
      */
index b9b8efc4c9cbc86657314b873ae3ce8eb12f0614..38a186b2589afe0671d02c5c7a9e35d1c92a2b2a 100644 (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
index 7f3a7fdf0c1f0d97b87d49ea341c348e51b52b7e..c245b59b96eb0a50702a4d365109a9425bee0d81 100644 (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}''
index 595392183ac75f4b9acee6a9c194a802ddf4e750..2a33e90d458ba77bb19dc4bdbc762afb2b869ef0 100644 (file)
                <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>
                <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>