added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
38
bundles/org.openhab.binding.hdpowerview/.classpath
Normal file
38
bundles/org.openhab.binding.hdpowerview/.classpath
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.hdpowerview/.project
Normal file
23
bundles/org.openhab.binding.hdpowerview/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.hdpowerview</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
21
bundles/org.openhab.binding.hdpowerview/NOTICE
Normal file
21
bundles/org.openhab.binding.hdpowerview/NOTICE
Normal file
@@ -0,0 +1,21 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
jcifs
|
||||
* License: LGPL v2.1 License
|
||||
* License: BSD License
|
||||
* Project: https://www.jcifs.org
|
||||
* Source: https://www.jcifs.org/src/src/jcifs
|
||||
185
bundles/org.openhab.binding.hdpowerview/README.md
Normal file
185
bundles/org.openhab.binding.hdpowerview/README.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Hunter Douglas (Luxaflex) PowerView Binding
|
||||
|
||||
This is an openHAB binding for [Hunter Douglas PowerView](https://www.hunterdouglas.com/operating-systems/motorized/powerview-motorization/overview) motorized shades via their PowerView hub.
|
||||
In some countries the PowerView system is sold under the brand name [Luxaflex](https://www.luxaflex.com/)
|
||||
|
||||

|
||||
|
||||
PowerView shades have motorization control for their vertical position, and some also have vane controls to change the angle of their slats.
|
||||
|
||||
This binding also supports scenes that are defined in the PowerView app.
|
||||
This helps to work around a limitation of the hub; commands are executed serially with a several second delay between executions.
|
||||
By using a scene to control multiple shades at once, the shades will all begin moving at the same time.
|
||||
|
||||
## Supported Things
|
||||
|
||||
| Thing | Thing Type | Description |
|
||||
|-----------------|------------|--------------------|
|
||||
| PowerView 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. |
|
||||
| PowerView Shade | Thing | A motorized shade. |
|
||||
|
||||
## Discovery
|
||||
|
||||
Make sure your shades are visible in the PowerView app before attempting discovery.
|
||||
|
||||
The binding can automatically discover the PowerView hub.
|
||||
The discovery process can be started by pressing the refresh button in the Main Configuration UI Inbox.
|
||||
However you can also manually create a (bridge) thing for the hub, and enter the required configuration parameters (see Thing Configuration below).
|
||||
If the configuration parameters are all valid, the binding will then automatically attempt to connect to the hub.
|
||||
If the connection succeeds, the hub will indicate its status as Online, otherwise it will show an error status.
|
||||
|
||||
Once the hub thing has been created and successfully connected, the binding will automatically discover all shades and scenes that are in it.
|
||||
|
||||
- For each shade discovered: the binding will create a new dedicated thing with its own channels.
|
||||
- For each scene discovered: the binding will create a new channel dynamically within the hub thing.
|
||||
|
||||
If in the future, you add additional shades or scenes to your system, the binding will discover them too.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### Thing Configuration for PowerView Hub
|
||||
|
||||
| Configuration Parameter | Description |
|
||||
|-------------------------|---------------|
|
||||
| host | The host name or IP address of the hub on your network. |
|
||||
| refresh | The number of milli-seconds between fetches of the PowerView hub's shade state (default 60'000 one minute). |
|
||||
| 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). |
|
||||
|
||||
### Thing Configuration for PowerView Shades
|
||||
|
||||
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:
|
||||
|
||||
| Configuration Parameter | Description |
|
||||
|-------------------------|---------------------------------------------------------------|
|
||||
| id | The ID of the PowerView shade in the app. Must be an integer. |
|
||||
|
||||
## Channels
|
||||
|
||||
### Channels for PowerView Hub
|
||||
|
||||
Scene channels will be added dynamically to the binding as they are discovered in the hub.
|
||||
Each scene channel will have an entry in the hub as shown below, whereby different scenes have different `id` values:
|
||||
|
||||
| Channel | Item Type | Description |
|
||||
|----------|-----------| ------------|
|
||||
| id | Switch | Turning this to ON will activate the scene. Scenes are stateless in the PowerView hub; they have no on/off state. Note: include `{autoupdate="false"}` in the item configuration to avoid having to reset it to off after use. |
|
||||
|
||||
### Channels for PowerView Shade
|
||||
|
||||
A shade always implements a roller shutter channel `position` which controls the vertical position of the shade's (primary) rail.
|
||||
If the shade has slats or rotatable vanes, there is also a dimmer channel `vane` which controls the slat / vane position.
|
||||
If it is a dual action (top-down plus bottom-up) shade, there is also a roller shutter channel `secondary` which controls the vertical position of the secondary rail.
|
||||
All of these channels appear in the binding, but only those which have a physical implementation in the shade, will have any physical effect.
|
||||
|
||||
| Channel | Item Type | Description |
|
||||
|------------|---------------|------------|
|
||||
| position | Rollershutter | The vertical position of the shade's rail -- see [next chapter](#Roller-Shutter-Up/Down-Position-vs.-Open/Close-State). Up/Down commands will move the rail completely up or completely down. Percentage commands will move the rail to an intermediate position. Stop commands will halt any current movement of the rail. |
|
||||
| secondary | Rollershutter | The vertical position of the secondary rail (if any). Its function is basically identical to the `position` channel above -- but see [next chapter](#Roller-Shutter-Up/Down-Position-vs.-Open/Close-State). |
|
||||
| vane | Dimmer | The degree of opening of the slats or vanes. Setting this to a non-zero value will first move the shade `position` fully down, since the slats or vanes can only have a defined state if the shade is in its down position -- see [Interdependency between Channel positions](#Interdependency-between-Channel-positions). |
|
||||
| batteryLow | Switch | Indicates ON when the battery level of the shade is low, as determined by the hub's internal rules. |
|
||||
|
||||
### Roller Shutter Up/Down Position vs. Open/Close State
|
||||
|
||||
The `position` and `secondary` channels are Rollershutter types.
|
||||
For vertical shades, the binding maps the vertical position of the "rail" to the Rollershutter ▲ / ▼ commands, and its respective percent value.
|
||||
And for horizontal shades, it maps the horizontal position of the "truck" to the Rollershutter ▲ / ▼ commands, and its respective percent value.
|
||||
|
||||
Depending on whether the shade is a top-down, bottom-up, left-right, right-left, or dual action shade, the `OPEN` and `CLOSED` position of the shades may differ from the ▲ / ▼ commands follows..
|
||||
|
||||
| Type of Shade | Channel | Rollershutter Command | Motion direction | Shade State | Percent |
|
||||
|--------------------------|-------------------|-----------------------|------------------|----------------|---------|
|
||||
| Single action bottom-up | `position` | ▲ | Up | `OPEN` | 0% |
|
||||
| | | ▼ | Down | `CLOSED` | 100% |
|
||||
| Single action top-down | `position` | ▲ | Up | ***`CLOSED`*** | 0% |
|
||||
| | | ▼ | Down | ***`OPEN`*** | 100% |
|
||||
| Single action right-left | `position` | ▲ | ***Left*** | `OPEN` | 0% |
|
||||
| | | ▼ | ***Right*** | `CLOSED` | 100% |
|
||||
| Single action left-right | `position` | ▲ | ***Right*** | `OPEN` | 0% |
|
||||
| | | ▼ | ***Left*** | `CLOSED` | 100% |
|
||||
| Dual action (lower rail) | `position` | ▲ | Up | `OPEN` | 0% |
|
||||
| | | ▼ | Down | `CLOSED` | 100% |
|
||||
| Dual action (upper rail) | ***`secondary`*** | ▲ | ***Down*** | `OPEN` | 0% |
|
||||
| | | ▼ | ***Up*** | `CLOSED` | 100% |
|
||||
|
||||
### Interdependency between Channel positions
|
||||
|
||||
On some types of shades with movable vanes, the vanes cannot be moved unless the shade is down.
|
||||
So there is an interdependency between the value of `vane` and the value of `position` as follows..
|
||||
|
||||
| Case | State of `position` | State of `vane` |
|
||||
|----------------------------|---------------------|-----------------|
|
||||
| Shade up | 0% = `UP` | `UNDEFINED` |
|
||||
| Shade 50% down | 50% | `UNDEFINED` |
|
||||
| Shade 100% down, Vane 0% | 100% = `DOWN` | 0% |
|
||||
| Shade 100% down, Vane 50% | 100% = `DOWN` | 50% |
|
||||
| Shade 100% down, Vane 100% | 100% = `DOWN` | 100% |
|
||||
|
||||
On dual action shades, the top rail cannot move below the position of the bottom rail.
|
||||
So the value of `secondary` may be constrained by the value of `position`.
|
||||
|
||||
## Refreshing the PowerView Hub Cache
|
||||
|
||||
The hub maintains a cache of the last known state of its shades, and this binding delivers those values.
|
||||
Usually the shades will be moved by this binding, so since the hub is always involved in the moving process, it updates this cache accordingly.
|
||||
|
||||
However shades can also be moved manually without the hub’s knowledge.
|
||||
A person can manually move a shade by pressing a button on the side of the shade or via a remote control.
|
||||
In neither case will the hub be aware of the shade’s new position.
|
||||
|
||||
The hub periodically does a _**"hard refresh"**_ in order to overcome this issue.
|
||||
The time interval between hard refreshes is set in the `hardRefresh` configuration parameter.
|
||||
To disable periodic hard refreshes, set `hardRefresh` to zero.
|
||||
|
||||
Note: You can also force the hub to refresh itself by sending a `REFRESH` command in a rule to an item that is connected to a channel in the hub as follows:
|
||||
|
||||
```
|
||||
rule "Hub Refresh (every 20 minutes)"
|
||||
when
|
||||
Time cron "0 1/20 0 ? * * *"
|
||||
then
|
||||
sendCommand(HUB_ITEM_NAME, "REFRESH") // refresh all shades in HUB
|
||||
|
||||
sendCommand(SHADE_ITEM_NAME, "REFRESH") // refresh single shade that ITEM is bound to
|
||||
end
|
||||
```
|
||||
|
||||
## Full Example
|
||||
|
||||
### `demo.things` File
|
||||
|
||||
```
|
||||
Bridge hdpowerview:hub:g24 "Luxaflex Hub" @ "Living Room" [host="192.168.1.123"] {
|
||||
Thing shade s50150 "Living Room Shade" @ "Living Room" [id="50150"]
|
||||
}
|
||||
```
|
||||
|
||||
### `demo.items` File
|
||||
|
||||
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"}
|
||||
```
|
||||
|
||||
Scene items:
|
||||
|
||||
```
|
||||
Switch Living_Room_Shades_Scene_Heart "Living Room Shades Scene Heart" <blinds> (g_Shades_Scene_Trigger) {channel="hdpowerview:hub:g24:22663", autoupdate="false"}
|
||||
```
|
||||
|
||||
### `demo.sitemap` File
|
||||
|
||||
```
|
||||
Frame label="Living Room Shades" {
|
||||
Switch item=Living_Room_Shades_Scene_Open
|
||||
Slider item=Living_Room_Shade_1_Position
|
||||
}
|
||||
```
|
||||
BIN
bundles/org.openhab.binding.hdpowerview/doc/hdpowerview.png
Normal file
BIN
bundles/org.openhab.binding.hdpowerview/doc/hdpowerview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
26
bundles/org.openhab.binding.hdpowerview/pom.xml
Normal file
26
bundles/org.openhab.binding.hdpowerview/pom.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.hdpowerview</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Hunter Douglas PowerView Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.samba.jcifs</groupId>
|
||||
<artifactId>jcifs</artifactId>
|
||||
<version>1.3.17</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.hdpowerview-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-hdpowerview" description="HD PowerView Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle dependency="true">mvn:org.samba.jcifs/jcifs/1.3.17</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.hdpowerview/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link HDPowerViewBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Added support for secondary rail positions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "hdpowerview";
|
||||
|
||||
// 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");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_SHADE_POSITION = "position";
|
||||
public static final String CHANNEL_SHADE_VANE = "vane";
|
||||
public static final String CHANNEL_SHADE_LOW_BATTERY = "lowBattery";
|
||||
public static final String CHANNEL_SHADE_SECONDARY_POSITION = "secondary";
|
||||
|
||||
public static final String CHANNELTYPE_SCENE_ACTIVATE = "scene-activate";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.discovery.HDPowerViewShadeDiscoveryService;
|
||||
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewHubHandler;
|
||||
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewShadeHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link HDPowerViewHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hdpowerview")
|
||||
public class HDPowerViewHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return HDPowerViewBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(HDPowerViewBindingConstants.THING_TYPE_HUB)) {
|
||||
HDPowerViewHubHandler handler = new HDPowerViewHubHandler((Bridge) thing);
|
||||
registerService(new HDPowerViewShadeDiscoveryService(handler));
|
||||
return handler;
|
||||
} else if (thingTypeUID.equals(HDPowerViewBindingConstants.THING_TYPE_SHADE)) {
|
||||
return new HDPowerViewShadeHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void registerService(DiscoveryService discoveryService) {
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.Invocation;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove;
|
||||
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeStop;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* JAX-RS targets for communicating with an HD PowerView hub
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Added support for secondary rail positions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewWebTargets {
|
||||
|
||||
private static final String PUT = "PUT";
|
||||
private static final String GET = "GET";
|
||||
private static final String SCENE_ID = "sceneId";
|
||||
private static final String ID = "id";
|
||||
private static final String REFRESH = "refresh";
|
||||
private static final String CONN_HDR = "Connection";
|
||||
private static final String CONN_VAL = "close"; // versus "keep-alive"
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HDPowerViewWebTargets.class);
|
||||
|
||||
/*
|
||||
* the hub returns a 423 error (resource locked) daily just after midnight;
|
||||
* which means it is temporarily undergoing maintenance; so we use "soft"
|
||||
* exception handling during the five minute maintenance period after a 423
|
||||
* error is received
|
||||
*/
|
||||
private final int maintenancePeriod = 300;
|
||||
private Instant maintenanceScheduledEnd = Instant.now().minusSeconds(2 * maintenancePeriod);
|
||||
|
||||
private WebTarget base;
|
||||
private WebTarget shades;
|
||||
private WebTarget shade;
|
||||
private WebTarget sceneActivate;
|
||||
private WebTarget scenes;
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Initialize the web targets
|
||||
*
|
||||
* @param client the Javax RS client (the binding)
|
||||
* @param ipAddress the IP address of the server (the hub)
|
||||
*/
|
||||
public HDPowerViewWebTargets(Client client, String ipAddress) {
|
||||
base = client.target("http://" + ipAddress + "/api");
|
||||
shades = base.path("shades/");
|
||||
shade = base.path("shades/{id}");
|
||||
sceneActivate = base.path("scenes");
|
||||
scenes = base.path("scenes/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a JSON package that describes all shades in the hub, and wraps it in
|
||||
* a Shades class instance
|
||||
*
|
||||
* @return Shades class instance
|
||||
* @throws JsonParseException if there is a JSON parsing error
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public @Nullable Shades getShades() throws JsonParseException, ProcessingException, HubMaintenanceException {
|
||||
String json = invoke(shades.request().header(CONN_HDR, CONN_VAL).buildGet(), shades, null);
|
||||
return gson.fromJson(json, Shades.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the hub to move a specific shade
|
||||
*
|
||||
* @param shadeId id of the shade to be moved
|
||||
* @param position instance of ShadePosition containing the new position
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public void moveShade(int shadeId, ShadePosition position) throws ProcessingException, HubMaintenanceException {
|
||||
WebTarget target = shade.resolveTemplate(ID, shadeId);
|
||||
String json = gson.toJson(new ShadeMove(shadeId, position));
|
||||
invoke(target.request().header(CONN_HDR, CONN_VAL)
|
||||
.buildPut(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)), target, json);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a JSON package that describes all scenes in the hub, and wraps it in
|
||||
* a Scenes class instance
|
||||
*
|
||||
* @return Scenes class instance
|
||||
* @throws JsonParseException if there is a JSON parsing error
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public @Nullable Scenes getScenes() throws JsonParseException, ProcessingException, HubMaintenanceException {
|
||||
String json = invoke(scenes.request().header(CONN_HDR, CONN_VAL).buildGet(), scenes, null);
|
||||
return gson.fromJson(json, Scenes.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the hub to execute a specific scene
|
||||
*
|
||||
* @param sceneId id of the scene to be executed
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public void activateScene(int sceneId) throws ProcessingException, HubMaintenanceException {
|
||||
WebTarget target = sceneActivate.queryParam(SCENE_ID, sceneId);
|
||||
invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null);
|
||||
}
|
||||
|
||||
private synchronized String invoke(Invocation invocation, WebTarget target, @Nullable String jsonCommand)
|
||||
throws ProcessingException, HubMaintenanceException {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("API command {} {}", jsonCommand == null ? GET : PUT, target.getUri());
|
||||
if (jsonCommand != null) {
|
||||
logger.trace("JSON command = {}", jsonCommand);
|
||||
}
|
||||
}
|
||||
Response response;
|
||||
try {
|
||||
response = invocation.invoke();
|
||||
} catch (ProcessingException e) {
|
||||
if (Instant.now().isBefore(maintenanceScheduledEnd)) {
|
||||
// throw "softer" exception during maintenance window
|
||||
logger.debug("Hub still undergoing maintenance");
|
||||
throw new HubMaintenanceException("Hub still undergoing maintenance");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
int statusCode = response.getStatus();
|
||||
if (statusCode == 423) {
|
||||
// set end of maintenance window, and throw a "softer" exception
|
||||
maintenanceScheduledEnd = Instant.now().plusSeconds(maintenancePeriod);
|
||||
logger.debug("Hub undergoing maintenance");
|
||||
if (response.hasEntity()) {
|
||||
response.readEntity(String.class);
|
||||
}
|
||||
response.close();
|
||||
throw new HubMaintenanceException("Hub undergoing maintenance");
|
||||
}
|
||||
if (statusCode != 200) {
|
||||
logger.warn("Hub returned HTTP error '{}'", statusCode);
|
||||
if (response.hasEntity()) {
|
||||
response.readEntity(String.class);
|
||||
}
|
||||
response.close();
|
||||
throw new ProcessingException(String.format("HTTP %d error", statusCode));
|
||||
}
|
||||
if (!response.hasEntity()) {
|
||||
logger.warn("Hub returned no content");
|
||||
response.close();
|
||||
throw new ProcessingException("Missing response entity");
|
||||
}
|
||||
String jsonResponse = response.readEntity(String.class);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("JSON response = {}", jsonResponse);
|
||||
}
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a JSON package that describes a specific shade in the hub, and wraps it
|
||||
* in a Shade class instance
|
||||
*
|
||||
* @param shadeId id of the shade to be fetched
|
||||
* @return Shade class instance
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public @Nullable Shade getShade(int shadeId) throws ProcessingException, HubMaintenanceException {
|
||||
WebTarget target = shade.resolveTemplate(ID, shadeId);
|
||||
String json = invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null);
|
||||
return gson.fromJson(json, Shade.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
|
||||
* a specific shade; fetches a JSON package that describes that shade, and wraps
|
||||
* it in a Shade class instance
|
||||
*
|
||||
* @param shadeId id of the shade to be refreshed
|
||||
* @return Shade class instance
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public @Nullable Shade refreshShade(int shadeId) throws ProcessingException, HubMaintenanceException {
|
||||
WebTarget target = shade.resolveTemplate(ID, shadeId).queryParam(REFRESH, true);
|
||||
String json = invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null);
|
||||
return gson.fromJson(json, Shade.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the hub to stop movement of a specific shade
|
||||
*
|
||||
* @param shadeId id of the shade to be stopped
|
||||
* @throws ProcessingException if there is any processing error
|
||||
* @throws HubMaintenanceException if the hub is down for maintenance
|
||||
*/
|
||||
public void stopShade(int shadeId) throws ProcessingException, HubMaintenanceException {
|
||||
WebTarget target = shade.resolveTemplate(ID, shadeId);
|
||||
String json = gson.toJson(new ShadeStop(shadeId));
|
||||
invoke(target.request().header(CONN_HDR, CONN_VAL)
|
||||
.buildPut(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)), target, json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HubMaintenanceException} is a custom exception for the HD PowerView hub
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HubMaintenanceException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -708582495003057343L;
|
||||
|
||||
public HubMaintenanceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Actuator class; all shades have a PRIMARY class actuator, plus double action
|
||||
* shades also have a SECONDARY class actuator
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ActuatorClass {
|
||||
PRIMARY_ACTUATOR,
|
||||
SECONDARY_ACTUATOR;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Shade coordinate system, as returned by the HD PowerView hub
|
||||
*
|
||||
* @param ZERO_IS_CLOSED coordinate value 0 means shade is closed
|
||||
* @param ZERO_IS_OPEN coordinate value 0 means shade is open
|
||||
* @param VANE_COORDS coordinate system for vanes
|
||||
* @param ERROR_UNKNOWN unsupported coordinate system
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution of the original enum called
|
||||
* ShadePositionKind
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Rewritten as a new enum called
|
||||
* CoordinateSystem to support secondary rail positions and be more
|
||||
* explicit on coordinate directions and ranges
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum CoordinateSystem {
|
||||
/*-
|
||||
* Specifies the coordinate system used for the position of the shade. Top-down
|
||||
* shades are in the same coordinate space as bottom-up shades. Shade position
|
||||
* values for top-down shades would be reversed for bottom-up shades. For
|
||||
* example, since 65535 is the open value for a bottom-up shade, it is the
|
||||
* closed value for a top-down shade. The top-down/bottom-up shade is different
|
||||
* in that instead of the top and bottom rail operating in one coordinate space
|
||||
* like the top-down and the bottom-up, it operates in two where the top
|
||||
* (middle) rail closed value is 0 and the bottom (primary) rail closed position
|
||||
* is also 0 and fully open for both is 65535
|
||||
*
|
||||
* The position element can take on multiple states depending on the family of
|
||||
* shade under control.
|
||||
*
|
||||
* The ranges of position integer values are
|
||||
* shades: 0..65535
|
||||
* vanes: 0..32767
|
||||
*
|
||||
* Shade fully up: (top-down: open, bottom-up: closed)
|
||||
* posKind: 1 {ZERO_IS_CLOSED}
|
||||
* position: 65535
|
||||
*
|
||||
* Shade and vane fully down: (top-down: closed, bottom-up: open)
|
||||
* posKind: 1 {ZERO_IS_CLOSED}
|
||||
* position1: 0
|
||||
*
|
||||
* ALTERNATE: Shade and vane fully down: (top-down: closed, bottom-up: open)
|
||||
* posKind: 3 {VANE_COORDS}
|
||||
* position: 0
|
||||
*
|
||||
* Shade fully down (closed) and vane fully up (open):
|
||||
* posKind: 3 {VANE_COORDS}
|
||||
* position: 32767
|
||||
*
|
||||
* Dual action, secondary top-down shade fully up (closed):
|
||||
* posKind: 2 {ZERO_IS_OPEN}
|
||||
* position: 0
|
||||
*
|
||||
* Dual action, secondary top-down shade fully down (open):
|
||||
* posKind: 2 {ZERO_IS_OPEN}
|
||||
* position: 65535
|
||||
*
|
||||
*/
|
||||
ZERO_IS_CLOSED,
|
||||
ZERO_IS_OPEN,
|
||||
VANE_COORDS,
|
||||
ERROR_UNKNOWN;
|
||||
|
||||
public static final int MAX_SHADE = 65535;
|
||||
public static final int MAX_VANE = 32767;
|
||||
|
||||
/**
|
||||
* Converts an HD PowerView posKind integer value to a CoordinateSystem enum value
|
||||
*
|
||||
* @param posKind input integer value
|
||||
* @return corresponding CoordinateSystem enum
|
||||
*/
|
||||
public static CoordinateSystem fromPosKind(int posKind) {
|
||||
switch (posKind) {
|
||||
case 1:
|
||||
return ZERO_IS_CLOSED;
|
||||
case 2:
|
||||
return ZERO_IS_OPEN;
|
||||
case 3:
|
||||
return VANE_COORDS;
|
||||
}
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a CoordinateSystem enum to an HD PowerView posKind integer value
|
||||
*
|
||||
* @return the posKind integer value
|
||||
*/
|
||||
public int toPosKind() {
|
||||
return ordinal() + 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.*;
|
||||
|
||||
/**
|
||||
* The position of a single shade, as returned by the HD PowerView hub
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Added support for secondary rail positions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadePosition {
|
||||
/**
|
||||
* Primary actuator position
|
||||
*/
|
||||
private int posKind1;
|
||||
private int position1;
|
||||
|
||||
/**
|
||||
* Secondary actuator position
|
||||
*
|
||||
* here we have to use Integer objects rather than just int primitives because
|
||||
* these are secondary optional position elements in the JSON payload, so the
|
||||
* GSON de-serializer might leave them as null
|
||||
*/
|
||||
private @Nullable Integer posKind2 = null;
|
||||
private @Nullable Integer position2 = null;
|
||||
|
||||
/**
|
||||
* Create a ShadePosition position instance with just a primary actuator
|
||||
* position
|
||||
*
|
||||
* @param coordSys the Coordinate System to be used
|
||||
* @param percent the percentage position within that Coordinate System
|
||||
* @return the ShadePosition instance
|
||||
*/
|
||||
public static ShadePosition create(CoordinateSystem coordSys, int percent) {
|
||||
return new ShadePosition(coordSys, percent, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ShadePosition position instance with both a primary and a secondary
|
||||
* actuator position
|
||||
*
|
||||
* @param primaryCoordSys the Coordinate System to be used for the primary
|
||||
* position
|
||||
* @param primaryPercent the percentage position for primary position
|
||||
* @param secondaryCoordSys the Coordinate System to be used for the secondary
|
||||
* position
|
||||
* @param secondaryPercent the percentage position for secondary position
|
||||
* @return the ShadePosition instance
|
||||
*/
|
||||
public static ShadePosition create(CoordinateSystem primaryCoordSys, int primaryPercent,
|
||||
@Nullable CoordinateSystem secondaryCoordSys, @Nullable Integer secondaryPercent) {
|
||||
return new ShadePosition(primaryCoordSys, primaryPercent, secondaryCoordSys, secondaryPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for ShadePosition position with both a primary and a secondary
|
||||
* actuator position
|
||||
*
|
||||
* @param primaryCoordSys the Coordinate System to be used for the primary
|
||||
* position
|
||||
* @param primaryPercent the percentage position for primary position
|
||||
* @param secondaryCoordSys the Coordinate System to be used for the secondary
|
||||
* position
|
||||
* @param secondaryPercent the percentage position for secondary position
|
||||
*/
|
||||
ShadePosition(CoordinateSystem primaryCoordSys, int primaryPercent, @Nullable CoordinateSystem secondaryCoordSys,
|
||||
@Nullable Integer secondaryPercent) {
|
||||
setPosition1(primaryCoordSys, primaryPercent);
|
||||
setPosition2(secondaryCoordSys, secondaryPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given Actuator Class and Coordinate System, map the ShadePosition's
|
||||
* state to an OpenHAB State
|
||||
*
|
||||
* @param actuatorClass the requested Actuator Class
|
||||
* @param coordSys the requested Coordinate System
|
||||
* @return the corresponding OpenHAB State
|
||||
*/
|
||||
public State getState(ActuatorClass actuatorClass, CoordinateSystem coordSys) {
|
||||
switch (actuatorClass) {
|
||||
case PRIMARY_ACTUATOR:
|
||||
return getPosition1(coordSys);
|
||||
case SECONDARY_ACTUATOR:
|
||||
return getPosition2(coordSys);
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the Coordinate System used for the given Actuator Class (if any)
|
||||
*
|
||||
* @param actuatorClass the requested Actuator Class
|
||||
* @return the Coordinate System used for that Actuator Class, or ERROR_UNKNOWN
|
||||
* if the Actuator Class is not implemented
|
||||
*/
|
||||
public CoordinateSystem getCoordinateSystem(ActuatorClass actuatorClass) {
|
||||
switch (actuatorClass) {
|
||||
case PRIMARY_ACTUATOR:
|
||||
return fromPosKind(posKind1);
|
||||
case SECONDARY_ACTUATOR:
|
||||
Integer posKind2 = this.posKind2;
|
||||
if (posKind2 != null) {
|
||||
return fromPosKind(posKind2.intValue());
|
||||
}
|
||||
default:
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPosition1(CoordinateSystem coordSys, int percent) {
|
||||
posKind1 = coordSys.toPosKind();
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
/*-
|
||||
* Primary rail of a single action bottom-up shade, or
|
||||
* Primary, lower, bottom-up, rail of a dual action shade
|
||||
*/
|
||||
case ZERO_IS_OPEN:
|
||||
/*-
|
||||
* Primary rail of a single action top-down shade
|
||||
*
|
||||
* All these types use the same coordinate system; which is inverted in relation
|
||||
* to that of OpenHAB
|
||||
*/
|
||||
position1 = MAX_SHADE - (int) Math.round(percent / 100d * MAX_SHADE);
|
||||
break;
|
||||
case VANE_COORDS:
|
||||
/*
|
||||
* Vane angle of the primary rail of a bottom-up single action shade
|
||||
*/
|
||||
position1 = (int) Math.round(percent / 100d * MAX_VANE);
|
||||
break;
|
||||
default:
|
||||
position1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private State getPosition1(CoordinateSystem coordSys) {
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
/*-
|
||||
* Primary rail of a single action bottom-up shade, or
|
||||
* Primary, lower, bottom-up, rail of a dual action shade
|
||||
*/
|
||||
case ZERO_IS_OPEN:
|
||||
/*
|
||||
* Primary rail of a single action top-down shade
|
||||
*
|
||||
* All these types use the same coordinate system; which is inverted in relation
|
||||
* to that of OpenHAB
|
||||
*
|
||||
* If the slats have a defined position then the shade position must by
|
||||
* definition be 100%
|
||||
*/
|
||||
return posKind1 == 3 ? PercentType.HUNDRED
|
||||
: new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
|
||||
|
||||
case VANE_COORDS:
|
||||
/*
|
||||
* Vane angle of the primary rail of a bottom-up single action shade
|
||||
*
|
||||
* If the shades are not open, the vane position is undefined; if the the shades
|
||||
* are exactly open then the vanes are at zero; otherwise return the actual vane
|
||||
* position itself
|
||||
*
|
||||
* note: sometimes the hub may return a value of position1 > MAX_VANE (seems to
|
||||
* be a bug in the hub) so we avoid an out of range exception via the Math.min()
|
||||
* function below..
|
||||
*/
|
||||
return posKind1 != 3 ? (position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO)
|
||||
: new PercentType((int) Math.round((double) Math.min(position1, MAX_VANE) / MAX_VANE * 100));
|
||||
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPosition2(@Nullable CoordinateSystem coordSys, @Nullable Integer percent) {
|
||||
if (coordSys == null || percent == null) {
|
||||
return;
|
||||
}
|
||||
posKind2 = Integer.valueOf(coordSys.toPosKind());
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
case ZERO_IS_OPEN:
|
||||
/*
|
||||
* Secondary, upper, top-down rail of a dual action shade
|
||||
*
|
||||
* Uses a coordinate system that is NOT inverted in relation to OpenHAB
|
||||
*/
|
||||
position2 = Integer.valueOf((int) Math.round(percent.doubleValue() / 100 * MAX_SHADE));
|
||||
break;
|
||||
default:
|
||||
position2 = Integer.valueOf(0);
|
||||
}
|
||||
}
|
||||
|
||||
private State getPosition2(CoordinateSystem coordSys) {
|
||||
Integer posKind2 = this.posKind2;
|
||||
Integer position2 = this.position2;
|
||||
if (position2 == null || posKind2 == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
/*
|
||||
* This case should never occur; but return a value anyway just in case
|
||||
*/
|
||||
case ZERO_IS_OPEN:
|
||||
/*
|
||||
* Secondary, upper, top-down rail of a dual action shade
|
||||
*
|
||||
* Uses a coordinate system that is NOT inverted in relation to OpenHAB
|
||||
*/
|
||||
if (posKind2.intValue() != 3) {
|
||||
return new PercentType(100 - (int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
|
||||
}
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
|
||||
/**
|
||||
* The position of a shade to set
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ShadeIdPosition {
|
||||
|
||||
int id;
|
||||
public @Nullable ShadePosition positions;
|
||||
|
||||
public ShadeIdPosition(int id, ShadePosition position) {
|
||||
this.id = id;
|
||||
this.positions = position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The motion "stop" directive for a shade
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ShadeIdStop {
|
||||
|
||||
int id;
|
||||
public @Nullable String motion;
|
||||
|
||||
public ShadeIdStop(int id) {
|
||||
this.id = id;
|
||||
this.motion = "stop";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
|
||||
/**
|
||||
* A request to set the position of a shade
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadeMove {
|
||||
|
||||
public @Nullable ShadeIdPosition shade;
|
||||
|
||||
public ShadeMove(int id, ShadePosition position) {
|
||||
this.shade = new ShadeIdPosition(id, position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A request to stop the movement of a shade
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadeStop {
|
||||
|
||||
public @Nullable ShadeIdStop shade;
|
||||
|
||||
public ShadeStop(int id) {
|
||||
this.shade = new ShadeIdStop(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.hdpowerview.internal.api.responses;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* State of all Scenes in an HD PowerView hub
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Scenes {
|
||||
|
||||
public @Nullable List<Scene> sceneData;
|
||||
public @Nullable List<Integer> sceneIds;
|
||||
|
||||
/*
|
||||
* the following SuppressWarnings annotation is because the Eclipse compiler
|
||||
* does NOT expect a NonNullByDefault annotation on the inner class, since it is
|
||||
* implicitly inherited from the outer class, whereas the Maven compiler always
|
||||
* requires an explicit NonNullByDefault annotation on all classes
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@NonNullByDefault
|
||||
public static class Scene {
|
||||
public int id;
|
||||
public @Nullable String name;
|
||||
public int roomId;
|
||||
public int order;
|
||||
public int colorId;
|
||||
public int iconId;
|
||||
|
||||
public String getName() {
|
||||
return new String(Base64.getDecoder().decode(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
|
||||
/**
|
||||
* State of a single Shade, as returned by an HD PowerView hub
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Shade {
|
||||
|
||||
public @Nullable ShadeData shade;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.hdpowerview.internal.api.responses;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
|
||||
/**
|
||||
* State of all Shades, as returned by an HD PowerView hub
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Shades {
|
||||
|
||||
public @Nullable List<ShadeData> shadeData;
|
||||
public @Nullable List<Integer> shadeIds;
|
||||
|
||||
/*
|
||||
* the following SuppressWarnings annotation is because the Eclipse compiler
|
||||
* does NOT expect a NonNullByDefault annotation on the inner class, since it is
|
||||
* implicitly inherited from the outer class, whereas the Maven compiler always
|
||||
* requires an explicit NonNullByDefault annotation on all classes
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@NonNullByDefault
|
||||
public static class ShadeData {
|
||||
public int id;
|
||||
public @Nullable String name;
|
||||
public int roomId;
|
||||
public int groupId;
|
||||
public int order;
|
||||
public int type;
|
||||
public double batteryStrength;
|
||||
public int batteryStatus;
|
||||
public boolean batteryIsLow;
|
||||
public @Nullable ShadePosition positions;
|
||||
public @Nullable Boolean timedOut;
|
||||
|
||||
public String getName() {
|
||||
return new String(Base64.getDecoder().decode(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.hdpowerview.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Basic configuration for the HD PowerView hub
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewHubConfiguration {
|
||||
|
||||
public static final String HOST = "host";
|
||||
|
||||
public @Nullable String host;
|
||||
|
||||
public long refresh;
|
||||
public long hardRefresh;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 Scene
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewSceneConfiguration {
|
||||
|
||||
public static final String ID = "id";
|
||||
|
||||
public int id;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.hdpowerview.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Basic configuration for an HD PowerView Shade
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewShadeConfiguration {
|
||||
|
||||
public static final String ID = "id";
|
||||
|
||||
public @Nullable String id;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Discovers HD PowerView hubs by means of mDNS
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
public class HDPowerViewHubDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubDiscoveryParticipant.class);
|
||||
|
||||
private static final Pattern VALID_IP_V4_ADDRESS = Pattern
|
||||
.compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(THING_TYPE_HUB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return "_powerview._tcp.local.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||
for (String host : service.getHostAddresses()) {
|
||||
if (VALID_IP_V4_ADDRESS.matcher(host).matches()) {
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_HUB, host.replace('.', '_'));
|
||||
DiscoveryResult hub = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(HDPowerViewHubConfiguration.HOST, host)
|
||||
.withRepresentationProperty(HDPowerViewHubConfiguration.HOST)
|
||||
.withLabel("PowerView Hub (" + host + ")").build();
|
||||
logger.debug("mDNS discovered hub on host '{}'", host);
|
||||
return hub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
for (String host : service.getHostAddresses()) {
|
||||
return new ThingUID(THING_TYPE_HUB, host.replace('.', '_'));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
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.config.HDPowerViewHubConfiguration;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jcifs.netbios.NbtAddress;
|
||||
|
||||
/**
|
||||
* Discovers HD PowerView hubs by means of NetBios
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.hdpowerview")
|
||||
public class HDPowerViewHubDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubDiscoveryService.class);
|
||||
|
||||
private final Runnable scanner;
|
||||
private @Nullable ScheduledFuture<?> backgroundFuture;
|
||||
|
||||
public HDPowerViewHubDiscoveryService() {
|
||||
super(Collections.singleton(THING_TYPE_HUB), 600, true);
|
||||
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 () -> {
|
||||
for (String netBiosName : NETBIOS_NAMES) {
|
||||
try {
|
||||
NbtAddress address = NbtAddress.getByName(netBiosName);
|
||||
if (address != null) {
|
||||
String host = address.getInetAddress().getHostAddress();
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_HUB, host.replace('.', '_'));
|
||||
DiscoveryResult hub = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(HDPowerViewHubConfiguration.HOST, host)
|
||||
.withRepresentationProperty(HDPowerViewHubConfiguration.HOST)
|
||||
.withLabel("PowerView Hub (" + host + ")").build();
|
||||
logger.debug("NetBios discovered hub on host '{}'", host);
|
||||
thingDiscovered(hub);
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
// Nothing to do here - the host couldn't be found, likely because it doesn't
|
||||
// exist
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 javax.ws.rs.ProcessingException;
|
||||
|
||||
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.HubMaintenanceException;
|
||||
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.handler.HDPowerViewHubHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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 ProcessingException("Web targets not initialized");
|
||||
}
|
||||
Shades shades = webTargets.getShades();
|
||||
if (shades != null && 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);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(HDPowerViewShadeConfiguration.ID, id)
|
||||
.withRepresentationProperty(HDPowerViewShadeConfiguration.ID)
|
||||
.withLabel(shadeData.getName()).withBridge(bridgeUID).build();
|
||||
logger.debug("Hub discovered shade '{}'", id);
|
||||
thingDiscovered(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ProcessingException | JsonParseException e) {
|
||||
logger.warn("Unexpected error: {}", e.getMessage());
|
||||
} catch (HubMaintenanceException e) {
|
||||
// exceptions are logged in HDPowerViewWebTargets
|
||||
}
|
||||
stopScan();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract class for Things that are managed through an HD PowerView hub
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class AbstractHubbedThingHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractHubbedThingHandler.class);
|
||||
|
||||
public AbstractHubbedThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
protected @Nullable HDPowerViewHubHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.error("Thing {} must belong to a hub", getThing().getThingTypeUID().getId());
|
||||
return null;
|
||||
}
|
||||
ThingHandler handler = bridge.getHandler();
|
||||
if (!(handler instanceof HDPowerViewHubHandler)) {
|
||||
logger.debug("Thing {} belongs to the wrong hub type", getThing().getThingTypeUID().getId());
|
||||
return null;
|
||||
}
|
||||
return (HDPowerViewHubHandler) handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
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.HubMaintenanceException;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
|
||||
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The {@link HDPowerViewHubHandler} is responsible for handling commands, which
|
||||
* are sent to one of the channels.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Added support for secondary rail positions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewHubHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubHandler.class);
|
||||
|
||||
private long refreshInterval;
|
||||
private long hardRefreshInterval;
|
||||
|
||||
private final Client client = ClientBuilder.newClient();
|
||||
private @Nullable HDPowerViewWebTargets webTargets;
|
||||
private @Nullable ScheduledFuture<?> pollFuture;
|
||||
private @Nullable ScheduledFuture<?> hardRefreshFuture;
|
||||
|
||||
private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
|
||||
HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);
|
||||
|
||||
public HDPowerViewHubHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (RefreshType.REFRESH.equals(command)) {
|
||||
requestRefreshShades();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
if (channel != null && sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
try {
|
||||
HDPowerViewWebTargets webTargets = this.webTargets;
|
||||
if (webTargets == null) {
|
||||
throw new ProcessingException("Web targets not initialized");
|
||||
}
|
||||
webTargets.activateScene(Integer.parseInt(channelUID.getId()));
|
||||
} catch (HubMaintenanceException e) {
|
||||
// exceptions are logged in HDPowerViewWebTargets
|
||||
} catch (NumberFormatException | ProcessingException e) {
|
||||
logger.debug("Unexpected error {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing hub");
|
||||
HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
|
||||
String host = config.host;
|
||||
|
||||
if (host == null || host.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
|
||||
return;
|
||||
}
|
||||
|
||||
webTargets = new HDPowerViewWebTargets(client, host);
|
||||
refreshInterval = config.refresh;
|
||||
hardRefreshInterval = config.hardRefresh;
|
||||
schedulePoll();
|
||||
}
|
||||
|
||||
public @Nullable HDPowerViewWebTargets getWebTargets() {
|
||||
return webTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRemoval() {
|
||||
super.handleRemoval();
|
||||
stopPoll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
stopPoll();
|
||||
}
|
||||
|
||||
private void schedulePoll() {
|
||||
ScheduledFuture<?> future = this.pollFuture;
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
logger.debug("Scheduling poll for 5000ms out, then every {}ms", refreshInterval);
|
||||
this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 5000, refreshInterval, TimeUnit.MILLISECONDS);
|
||||
|
||||
future = this.hardRefreshFuture;
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
if (hardRefreshInterval > 0) {
|
||||
logger.debug("Scheduling hard refresh every {}minutes", hardRefreshInterval);
|
||||
this.hardRefreshFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShades, 1,
|
||||
hardRefreshInterval, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stopPoll() {
|
||||
ScheduledFuture<?> future = this.pollFuture;
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
this.pollFuture = null;
|
||||
|
||||
future = this.hardRefreshFuture;
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
this.hardRefreshFuture = null;
|
||||
}
|
||||
|
||||
private synchronized void poll() {
|
||||
try {
|
||||
logger.debug("Polling for state");
|
||||
pollShades();
|
||||
pollScenes();
|
||||
} catch (JsonParseException e) {
|
||||
logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
|
||||
} catch (ProcessingException e) {
|
||||
logger.warn("Error connecting to bridge: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
|
||||
} catch (HubMaintenanceException e) {
|
||||
// exceptions are logged in HDPowerViewWebTargets
|
||||
}
|
||||
}
|
||||
|
||||
private void pollShades() throws JsonParseException, ProcessingException, HubMaintenanceException {
|
||||
HDPowerViewWebTargets webTargets = this.webTargets;
|
||||
if (webTargets == null) {
|
||||
throw new ProcessingException("Web targets not initialized");
|
||||
}
|
||||
|
||||
Shades shades = webTargets.getShades();
|
||||
if (shades == null) {
|
||||
throw new JsonParseException("Missing 'shades' element");
|
||||
}
|
||||
|
||||
List<ShadeData> shadesData = shades.shadeData;
|
||||
if (shadesData == null) {
|
||||
throw new JsonParseException("Missing 'shades.shadeData' element");
|
||||
}
|
||||
|
||||
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()) {
|
||||
Thing thing = item.getKey();
|
||||
String shadeId = item.getValue();
|
||||
ShadeData shadeData = idShadeDataMap.get(shadeId);
|
||||
updateShadeThing(shadeId, thing, shadeData);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShadeThing(String shadeId, Thing thing, @Nullable ShadeData shadeData) {
|
||||
HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
|
||||
if (thingHandler == null) {
|
||||
logger.debug("Shade '{}' handler not initialized", shadeId);
|
||||
return;
|
||||
}
|
||||
if (shadeData == null) {
|
||||
logger.debug("Shade '{}' has no data in hub", shadeId);
|
||||
} else {
|
||||
logger.debug("Updating shade '{}'", shadeId);
|
||||
}
|
||||
thingHandler.onReceiveUpdate(shadeData);
|
||||
}
|
||||
|
||||
private void pollScenes() throws JsonParseException, ProcessingException, HubMaintenanceException {
|
||||
HDPowerViewWebTargets webTargets = this.webTargets;
|
||||
if (webTargets == null) {
|
||||
throw new ProcessingException("Web targets not initialized");
|
||||
}
|
||||
|
||||
Scenes scenes = webTargets.getScenes();
|
||||
if (scenes == null) {
|
||||
throw new JsonParseException("Missing 'scenes' element");
|
||||
}
|
||||
|
||||
List<Scene> sceneData = scenes.sceneData;
|
||||
if (sceneData == null) {
|
||||
throw new JsonParseException("Missing 'scenes.sceneData' element");
|
||||
}
|
||||
logger.debug("Received data for {} scenes", sceneData.size());
|
||||
|
||||
Map<String, Channel> idChannelMap = getIdChannelMap();
|
||||
for (Scene scene : sceneData) {
|
||||
// remove existing scene channel from the map
|
||||
String sceneId = Integer.toString(scene.id);
|
||||
if (idChannelMap.containsKey(sceneId)) {
|
||||
idChannelMap.remove(sceneId);
|
||||
logger.debug("Keeping channel for existing scene '{}'", sceneId);
|
||||
} else {
|
||||
// create a new scene channel
|
||||
ChannelUID channelUID = new ChannelUID(getThing().getUID(), sceneId);
|
||||
Channel channel = ChannelBuilder.create(channelUID, "Switch").withType(sceneChannelTypeUID)
|
||||
.withLabel(scene.getName()).withDescription("Activates the scene " + scene.getName()).build();
|
||||
updateThing(editThing().withChannel(channel).build());
|
||||
logger.debug("Creating new channel for scene '{}'", sceneId);
|
||||
}
|
||||
}
|
||||
|
||||
// remove any previously created channels that no longer exist
|
||||
if (!idChannelMap.isEmpty()) {
|
||||
logger.debug("Removing {} orphan scene channels", idChannelMap.size());
|
||||
List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
|
||||
allChannels.removeAll(idChannelMap.values());
|
||||
updateThing(editThing().withChannels(allChannels).build());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Thing, String> getThingIdMap() {
|
||||
Map<Thing, String> ret = new HashMap<>();
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
String id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
|
||||
if (id != null && !id.isEmpty()) {
|
||||
ret.put(thing, id);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Map<String, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
|
||||
Map<String, ShadeData> ret = new HashMap<>();
|
||||
for (ShadeData shade : shadeData) {
|
||||
if (shade.id != 0) {
|
||||
ret.put(Integer.toString(shade.id), shade);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Map<String, Channel> getIdChannelMap() {
|
||||
Map<String, Channel> ret = new HashMap<>();
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
if (sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
|
||||
ret.put(channel.getUID().getId(), channel);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void requestRefreshShades() {
|
||||
Map<Thing, String> thingIdMap = getThingIdMap();
|
||||
for (Entry<Thing, String> item : thingIdMap.entrySet()) {
|
||||
Thing thing = item.getKey();
|
||||
ThingHandler handler = thing.getHandler();
|
||||
if (handler instanceof HDPowerViewShadeHandler) {
|
||||
((HDPowerViewShadeHandler) handler).requestRefreshShade();
|
||||
} else {
|
||||
String shadeId = item.getValue();
|
||||
logger.debug("Shade '{}' handler not initialized", shadeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.hdpowerview.internal.api.ActuatorClass.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
|
||||
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.HubMaintenanceException;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ActuatorClass;
|
||||
import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
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.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles commands for an HD PowerView Shade
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Added support for secondary rail positions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
|
||||
|
||||
private static final int REFRESH_DELAY_SEC = 10;
|
||||
private @Nullable ScheduledFuture<?> refreshFuture = null;
|
||||
|
||||
public HDPowerViewShadeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
try {
|
||||
getShadeId();
|
||||
} catch (NumberFormatException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Configuration 'id' not a valid integer");
|
||||
return;
|
||||
}
|
||||
if (getBridgeHandler() == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (RefreshType.REFRESH.equals(command)) {
|
||||
requestRefreshShade();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SHADE_POSITION:
|
||||
if (command instanceof PercentType) {
|
||||
moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, ((PercentType) command).intValue());
|
||||
} else if (command instanceof UpDownType) {
|
||||
moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
if (StopMoveType.STOP.equals(command)) {
|
||||
stopShade();
|
||||
} else {
|
||||
logger.warn("Unexpected StopMoveType command");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANNEL_SHADE_VANE:
|
||||
if (command instanceof PercentType) {
|
||||
moveShade(PRIMARY_ACTUATOR, VANE_COORDS, ((PercentType) command).intValue());
|
||||
} else if (command instanceof OnOffType) {
|
||||
moveShade(PRIMARY_ACTUATOR, VANE_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANNEL_SHADE_SECONDARY_POSITION:
|
||||
if (command instanceof PercentType) {
|
||||
moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, ((PercentType) command).intValue());
|
||||
} else if (command instanceof UpDownType) {
|
||||
moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
if (StopMoveType.STOP.equals(command)) {
|
||||
stopShade();
|
||||
} else {
|
||||
logger.warn("Unexpected StopMoveType command");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the channels based on the ShadeData provided
|
||||
*
|
||||
* @param shadeData the ShadeData to be used; may be null
|
||||
*/
|
||||
protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
|
||||
if (shadeData != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
updateBindingStates(shadeData.positions);
|
||||
updateState(CHANNEL_SHADE_LOW_BATTERY, shadeData.batteryStatus < 2 ? OnOffType.ON : OnOffType.OFF);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBindingStates(@Nullable ShadePosition shadePos) {
|
||||
if (shadePos != null) {
|
||||
updateState(CHANNEL_SHADE_POSITION, shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED));
|
||||
updateState(CHANNEL_SHADE_VANE, shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS));
|
||||
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN));
|
||||
} else {
|
||||
updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
|
||||
try {
|
||||
HDPowerViewHubHandler bridge;
|
||||
if ((bridge = getBridgeHandler()) == null) {
|
||||
throw new ProcessingException("Missing bridge handler");
|
||||
}
|
||||
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
|
||||
if (webTargets == null) {
|
||||
throw new ProcessingException("Web targets not initialized");
|
||||
}
|
||||
int shadeId = getShadeId();
|
||||
|
||||
switch (actuatorClass) {
|
||||
case PRIMARY_ACTUATOR:
|
||||
// write the new primary position
|
||||
webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
|
||||
break;
|
||||
case SECONDARY_ACTUATOR:
|
||||
// read the current primary position; default value 100%
|
||||
int primaryPercent = 100;
|
||||
Shade shade = webTargets.getShade(shadeId);
|
||||
if (shade != null) {
|
||||
ShadeData shadeData = shade.shade;
|
||||
if (shadeData != null) {
|
||||
ShadePosition shadePos = shadeData.positions;
|
||||
if (shadePos != null) {
|
||||
State primaryState = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
if (primaryState instanceof PercentType) {
|
||||
primaryPercent = ((PercentType) primaryState).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// write the current primary position, plus the new secondary position
|
||||
webTargets.moveShade(shadeId,
|
||||
ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent));
|
||||
}
|
||||
} catch (ProcessingException | NumberFormatException e) {
|
||||
logger.warn("Unexpected error: {}", e.getMessage());
|
||||
return;
|
||||
} catch (HubMaintenanceException e) {
|
||||
// exceptions are logged in HDPowerViewWebTargets
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private int getShadeId() throws NumberFormatException {
|
||||
return Integer.parseInt(getConfigAs(HDPowerViewShadeConfiguration.class).id);
|
||||
}
|
||||
|
||||
private void stopShade() {
|
||||
try {
|
||||
HDPowerViewHubHandler bridge;
|
||||
if ((bridge = getBridgeHandler()) == null) {
|
||||
throw new ProcessingException("Missing bridge handler");
|
||||
}
|
||||
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
|
||||
if (webTargets == null) {
|
||||
throw new ProcessingException("Web targets not initialized");
|
||||
}
|
||||
int shadeId = getShadeId();
|
||||
webTargets.stopShade(shadeId);
|
||||
requestRefreshShade();
|
||||
} catch (ProcessingException | NumberFormatException e) {
|
||||
logger.warn("Unexpected error: {}", e.getMessage());
|
||||
return;
|
||||
} catch (HubMaintenanceException e) {
|
||||
// exceptions are logged in HDPowerViewWebTargets
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the shade shall undergo a 'hard' refresh
|
||||
*/
|
||||
protected synchronized void requestRefreshShade() {
|
||||
if (refreshFuture == null) {
|
||||
refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void doRefreshShade() {
|
||||
try {
|
||||
HDPowerViewHubHandler bridge;
|
||||
if ((bridge = getBridgeHandler()) == null) {
|
||||
throw new ProcessingException("Missing bridge handler");
|
||||
}
|
||||
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
|
||||
if (webTargets == null) {
|
||||
throw new ProcessingException("Web targets not initialized");
|
||||
}
|
||||
int shadeId = getShadeId();
|
||||
Shade shade = webTargets.refreshShade(shadeId);
|
||||
if (shade != null) {
|
||||
ShadeData shadeData = shade.shade;
|
||||
if (shadeData != null) {
|
||||
if (Boolean.TRUE.equals(shadeData.timedOut)) {
|
||||
logger.warn("Shade {} wireless refresh time out", shadeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ProcessingException | NumberFormatException e) {
|
||||
logger.warn("Unexpected error: {}", e.getMessage());
|
||||
} catch (HubMaintenanceException e) {
|
||||
// exceptions are logged in HDPowerViewWebTargets
|
||||
}
|
||||
refreshFuture = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="hdpowerview" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Hunter Douglas PowerView Binding</name>
|
||||
<description>The Hunter Douglas PowerView binding provides access to the Hunter Douglas line of PowerView shades.</description>
|
||||
<author>Andy Lintner</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="hdpowerview"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="hub">
|
||||
<label>PowerView Hub</label>
|
||||
<description>Hunter Douglas (Luxaflex) PowerView Hub</description>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Hunter Douglas (Luxaflex)</property>
|
||||
<property name="modelId">PowerView Hub</property>
|
||||
</properties>
|
||||
<representation-property>host</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host</label>
|
||||
<description>The Host address of the PowerView Hub</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" required="false">
|
||||
<label>Refresh Interval</label>
|
||||
<description>The number of milliseconds between fetches of the PowerView Hub shade state</description>
|
||||
<default>60000</default>
|
||||
</parameter>
|
||||
<parameter name="hardRefresh" type="integer" required="false">
|
||||
<label>Hard Refresh Interval</label>
|
||||
<description>The number of minutes between hard refreshes of the PowerView Hub (or 0 to disable)</description>
|
||||
<default>180</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="shade">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="hub"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>PowerView Shade</label>
|
||||
<description>Hunter Douglas (Luxaflex) PowerView Shade</description>
|
||||
|
||||
<channels>
|
||||
<channel id="position" typeId="shade-position"/>
|
||||
<channel id="secondary" typeId="shade-position">
|
||||
<label>Secondary Position</label>
|
||||
<description>The secondary vertical position (on top-down/bottom-up shades)</description>
|
||||
</channel>
|
||||
<channel id="vane" typeId="shade-vane"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Hunter Douglas (Luxaflex)</property>
|
||||
<property name="modelId">PowerView Motorized Shade</property>
|
||||
</properties>
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="id" type="text" required="true">
|
||||
<label>ID</label>
|
||||
<description>The numeric ID of the PowerView Shade in the Hub</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="shade-position">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Position</label>
|
||||
<description>The vertical position of the shade</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="shade-vane">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Vane</label>
|
||||
<description>The opening of the slats in the shade</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="scene-activate">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Activate</label>
|
||||
<description>Activates the scene</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.ActuatorClass.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
|
||||
import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
|
||||
import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* Unit tests for HD PowerView binding
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HDPowerViewJUnitTests {
|
||||
|
||||
private static final Pattern VALID_IP_V4_ADDRESS = Pattern
|
||||
.compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
|
||||
|
||||
/*
|
||||
* load a test JSON string from a file
|
||||
*/
|
||||
private String loadJson(String fileName) {
|
||||
try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
|
||||
BufferedReader reader = new BufferedReader(file)) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line).append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of ONLINE tests on the communication with a hub
|
||||
*
|
||||
* @param hubIPAddress must be a valid hub IP address to run the
|
||||
* tests on; or an INVALID IP address to
|
||||
* suppress the tests
|
||||
* @param allowShadeMovementCommands set to true if you accept that the tests
|
||||
* shall physically move the shades
|
||||
*/
|
||||
@Test
|
||||
public void testOnlineCommunication() {
|
||||
/*
|
||||
* NOTE: in order to actually run these tests you must have a hub physically
|
||||
* available, and its IP address must be correctly configured in the
|
||||
* "hubIPAddress" string constant e.g. "192.168.1.123"
|
||||
*/
|
||||
String hubIPAddress = "192.168.1.xxx";
|
||||
|
||||
/*
|
||||
* NOTE: set allowShadeMovementCommands = true if you accept physically moving
|
||||
* the shades during these tests
|
||||
*/
|
||||
boolean allowShadeMovementCommands = false;
|
||||
|
||||
if (VALID_IP_V4_ADDRESS.matcher(hubIPAddress).matches()) {
|
||||
// initialize stuff
|
||||
Client client = ClientBuilder.newClient();
|
||||
assertNotNull(client);
|
||||
// client.register(new Logger());
|
||||
HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress);
|
||||
assertNotNull(webTargets);
|
||||
|
||||
// ==== exercise some code ====
|
||||
ShadePosition test;
|
||||
State pos;
|
||||
|
||||
// shade fully up
|
||||
test = ShadePosition.create(ZERO_IS_CLOSED, 0);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(0, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertTrue(UnDefType.UNDEF.equals(pos));
|
||||
|
||||
// shade fully down (method 1)
|
||||
test = ShadePosition.create(ZERO_IS_CLOSED, 100);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(0, ((PercentType) pos).intValue());
|
||||
|
||||
// shade fully down (method 2)
|
||||
test = ShadePosition.create(VANE_COORDS, 0);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(0, ((PercentType) pos).intValue());
|
||||
|
||||
// shade fully down (method 2) and vane fully open
|
||||
test = ShadePosition.create(VANE_COORDS, 100);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
|
||||
int shadeId = 0;
|
||||
@Nullable
|
||||
ShadePosition shadePos = null;
|
||||
@Nullable
|
||||
Shades shadesX = null;
|
||||
|
||||
// ==== get all shades ====
|
||||
try {
|
||||
shadesX = webTargets.getShades();
|
||||
assertNotNull(shadesX);
|
||||
@Nullable
|
||||
List<ShadeData> shadesData = shadesX.shadeData;
|
||||
assertNotNull(shadesData);
|
||||
assertTrue(shadesData.size() > 0);
|
||||
@Nullable
|
||||
ShadeData shadeData;
|
||||
shadeData = shadesData.get(0);
|
||||
assertNotNull(shadeData);
|
||||
assertTrue(shadeData.getName().length() > 0);
|
||||
shadePos = shadeData.positions;
|
||||
assertNotNull(shadePos);
|
||||
@Nullable
|
||||
ShadeData shadeZero = shadesData.get(0);
|
||||
assertNotNull(shadeZero);
|
||||
shadeId = shadeZero.id;
|
||||
assertNotEquals(0, shadeId);
|
||||
|
||||
for (ShadeData shadexData : shadesData) {
|
||||
String shadeName = shadexData.getName();
|
||||
assertNotNull(shadeName);
|
||||
}
|
||||
} catch (JsonParseException | ProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// ==== get all scenes ====
|
||||
int sceneId = 0;
|
||||
try {
|
||||
Scenes scenes = webTargets.getScenes();
|
||||
assertNotNull(scenes);
|
||||
@Nullable
|
||||
List<Scene> scenesData = scenes.sceneData;
|
||||
assertNotNull(scenesData);
|
||||
assertTrue(scenesData.size() > 0);
|
||||
@Nullable
|
||||
Scene sceneZero = scenesData.get(0);
|
||||
assertNotNull(sceneZero);
|
||||
sceneId = sceneZero.id;
|
||||
assertTrue(sceneId > 0);
|
||||
|
||||
for (Scene scene : scenesData) {
|
||||
String sceneName = scene.getName();
|
||||
assertNotNull(sceneName);
|
||||
}
|
||||
} catch (JsonParseException | ProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// ==== refresh a specific shade ====
|
||||
@Nullable
|
||||
Shade shade = null;
|
||||
try {
|
||||
assertNotEquals(0, shadeId);
|
||||
shade = webTargets.refreshShade(shadeId);
|
||||
assertNotNull(shade);
|
||||
} catch (ProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// ==== move a specific shade ====
|
||||
try {
|
||||
assertNotEquals(0, shadeId);
|
||||
assertNotNull(shade);
|
||||
@Nullable
|
||||
ShadeData shadeData = shade.shade;
|
||||
assertNotNull(shadeData);
|
||||
ShadePosition positions = shadeData.positions;
|
||||
assertNotNull(positions);
|
||||
CoordinateSystem coordSys = positions.getCoordinateSystem(PRIMARY_ACTUATOR);
|
||||
assertNotNull(coordSys);
|
||||
|
||||
pos = positions.getState(PRIMARY_ACTUATOR, coordSys);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
|
||||
pos = positions.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
|
||||
int position = ((PercentType) pos).intValue();
|
||||
position = position + ((position <= 10) ? 5 : -5);
|
||||
|
||||
ShadePosition newPos = ShadePosition.create(ZERO_IS_CLOSED, position);
|
||||
assertNotNull(newPos);
|
||||
|
||||
if (allowShadeMovementCommands) {
|
||||
webTargets.moveShade(shadeId, newPos);
|
||||
}
|
||||
} catch (ProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// ==== activate a specific scene ====
|
||||
if (allowShadeMovementCommands) {
|
||||
try {
|
||||
assertNotNull(sceneId);
|
||||
webTargets.activateScene(sceneId);
|
||||
} catch (ProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of OFFLINE tests on the JSON parsing machinery
|
||||
*/
|
||||
@Test
|
||||
public void testOfflineJsonParsing() {
|
||||
final Gson gson = new Gson();
|
||||
|
||||
@Nullable
|
||||
Shades shades;
|
||||
// test generic JSON shades response
|
||||
try {
|
||||
@Nullable
|
||||
String json = loadJson("shades");
|
||||
assertNotNull(json);
|
||||
assertNotEquals("", json);
|
||||
shades = gson.fromJson(json, Shades.class);
|
||||
assertNotNull(shades);
|
||||
} catch (JsonParseException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// test generic JSON scenes response
|
||||
try {
|
||||
@Nullable
|
||||
String json = loadJson("scenes");
|
||||
assertNotNull(json);
|
||||
assertNotEquals("", json);
|
||||
@Nullable
|
||||
Scenes scenes = gson.fromJson(json, Scenes.class);
|
||||
assertNotNull(scenes);
|
||||
} catch (JsonParseException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// test the JSON parsing for a duette top down bottom up shade
|
||||
try {
|
||||
@Nullable
|
||||
ShadeData shadeData = null;
|
||||
String json = loadJson("duette");
|
||||
assertNotNull(json);
|
||||
assertNotEquals("", json);
|
||||
|
||||
shades = gson.fromJson(json, Shades.class);
|
||||
assertNotNull(shades);
|
||||
@Nullable
|
||||
List<ShadeData> shadesData = shades.shadeData;
|
||||
assertNotNull(shadesData);
|
||||
|
||||
assertEquals(1, shadesData.size());
|
||||
shadeData = shadesData.get(0);
|
||||
assertNotNull(shadeData);
|
||||
|
||||
assertEquals("Gardin 1", shadeData.getName());
|
||||
assertEquals(63778, shadeData.id);
|
||||
|
||||
ShadePosition shadePos = shadeData.positions;
|
||||
assertNotNull(shadePos);
|
||||
assertEquals(ZERO_IS_CLOSED, shadePos.getCoordinateSystem(PRIMARY_ACTUATOR));
|
||||
|
||||
State pos = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(59, ((PercentType) pos).intValue());
|
||||
|
||||
pos = shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(65, ((PercentType) pos).intValue());
|
||||
|
||||
pos = shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(UnDefType.class, pos.getClass());
|
||||
} catch (JsonParseException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"shadeIds": [
|
||||
63778
|
||||
],
|
||||
"shadeData": [
|
||||
{
|
||||
"id": 63778,
|
||||
"type": 8,
|
||||
"batteryStatus": 0,
|
||||
"batteryStrength": 0,
|
||||
"roomId": 891,
|
||||
"firmware": {
|
||||
"revision": 1,
|
||||
"subRevision": 8,
|
||||
"build": 1944
|
||||
},
|
||||
"motor": {
|
||||
"revision": 0,
|
||||
"subRevision": 0,
|
||||
"build": 267
|
||||
},
|
||||
"name": "R2FyZGluIDE=",
|
||||
"groupId": 18108,
|
||||
"positions": {
|
||||
"posKind2": 2,
|
||||
"position2": 23194,
|
||||
"posKind1": 1,
|
||||
"position1": 26566
|
||||
},
|
||||
"signalStrength": 4,
|
||||
"aid": 2,
|
||||
"capabilities": 7,
|
||||
"batteryKind": "unassigned"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"sceneIds": [
|
||||
18097,
|
||||
22663,
|
||||
35821,
|
||||
6551
|
||||
],
|
||||
"sceneData": [
|
||||
{
|
||||
"roomId": 59611,
|
||||
"name": "RG9vciBPcGVu",
|
||||
"colorId": 6,
|
||||
"iconId": 160,
|
||||
"networkNumber": 19,
|
||||
"id": 18097,
|
||||
"order": 3,
|
||||
"hkAssist": false
|
||||
},
|
||||
{
|
||||
"roomId": 59611,
|
||||
"name": "SGVhcnQ=",
|
||||
"colorId": 15,
|
||||
"iconId": 49,
|
||||
"networkNumber": 3,
|
||||
"id": 22663,
|
||||
"order": 0,
|
||||
"hkAssist": false
|
||||
},
|
||||
{
|
||||
"roomId": 59611,
|
||||
"name": "Q2xvc2U=",
|
||||
"colorId": 5,
|
||||
"iconId": 31,
|
||||
"networkNumber": 8,
|
||||
"id": 35821,
|
||||
"order": 2,
|
||||
"hkAssist": false
|
||||
},
|
||||
{
|
||||
"roomId": 59611,
|
||||
"name": "T3Blbg==",
|
||||
"colorId": 10,
|
||||
"iconId": 95,
|
||||
"networkNumber": 20,
|
||||
"id": 6551,
|
||||
"order": 1,
|
||||
"hkAssist": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"shadeIds": [
|
||||
25206,
|
||||
55854,
|
||||
50150
|
||||
],
|
||||
"shadeData": [
|
||||
{
|
||||
"id": 25206,
|
||||
"type": 44,
|
||||
"batteryStatus": 3,
|
||||
"batteryStrength": 179,
|
||||
"roomId": 59611,
|
||||
"firmware": {
|
||||
"revision": 1,
|
||||
"subRevision": 8,
|
||||
"build": 1944
|
||||
},
|
||||
"name": "U2hhZGUgMg==",
|
||||
"motor": {
|
||||
"revision": 51,
|
||||
"subRevision": 51,
|
||||
"build": 11825
|
||||
},
|
||||
"groupId": 64003,
|
||||
"aid": 2,
|
||||
"signalStrength": 4,
|
||||
"capabilities": 0,
|
||||
"batteryKind": "unassigned",
|
||||
"positions": {
|
||||
"posKind1": 3,
|
||||
"position1": 32579
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55854,
|
||||
"type": 44,
|
||||
"batteryStatus": 3,
|
||||
"batteryStrength": 187,
|
||||
"roomId": 59611,
|
||||
"firmware": {
|
||||
"revision": 1,
|
||||
"subRevision": 8,
|
||||
"build": 1944
|
||||
},
|
||||
"motor": {
|
||||
"revision": 51,
|
||||
"subRevision": 51,
|
||||
"build": 11825
|
||||
},
|
||||
"name": "U2hhZGUgMw==",
|
||||
"groupId": 64003,
|
||||
"aid": 3,
|
||||
"signalStrength": 4,
|
||||
"capabilities": 0,
|
||||
"positions": {
|
||||
"posKind1": 1,
|
||||
"position1": 65534
|
||||
},
|
||||
"batteryKind": "unassigned"
|
||||
},
|
||||
{
|
||||
"id": 50150,
|
||||
"type": 44,
|
||||
"batteryStatus": 3,
|
||||
"batteryStrength": 181,
|
||||
"roomId": 59611,
|
||||
"name": "U2hhZGUgMQ==",
|
||||
"firmware": {
|
||||
"revision": 1,
|
||||
"subRevision": 8,
|
||||
"build": 1944
|
||||
},
|
||||
"motor": {
|
||||
"revision": 51,
|
||||
"subRevision": 51,
|
||||
"build": 11825
|
||||
},
|
||||
"groupId": 64003,
|
||||
"aid": 4,
|
||||
"signalStrength": 4,
|
||||
"capabilities": 0,
|
||||
"batteryKind": "unassigned",
|
||||
"positions": {
|
||||
"posKind1": 1,
|
||||
"position1": 1040
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user