* 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>
## 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
| 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`)
| 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.
```
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"]
}
```
```
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:
```
### `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
}
```
package org.openhab.binding.hdpowerview.internal;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
*
* @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 {
// 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";
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";
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);
}
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;
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;
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;
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;
sceneCollections = base + "scenecollections/";
scheduledEvents = base + "scheduledevents";
+
+ repeaters = base + "repeaters/";
+
this.httpClient = httpClient;
}
}
}
+ /**
+ * 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
*
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+}
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
public static final String ID = "id";
- public @Nullable String id;
+ public int id;
}
--- /dev/null
+/**
+ * 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());
+ }
+ }
+}
+++ /dev/null
-/**
- * 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();
- };
- }
-}
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);
}
}
- 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);
}
}
--- /dev/null
+/**
+ * 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);
+ }
+}
@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;
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
*/
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
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
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
+# 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}''
<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>