mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[bondhome] Initial contribution (#13459)
* First commit on newly created branch, taking code from c8b8e210dfd23f98526763782eadbca49509baf9 * [bondhome] update snapshot version, and some typos * [bondhome] Address (most) comments from prior review from #7260 * [bondhome] simplify channels * lastUpdate is unnecessary; turn on persistence or add a rule on update if you care to keep track of it * use a single string command channel for all shoot-and-forget commands, instead of multiple switch channels * use a rollershutter channel for shades (accepting UP, DOWN, STOP, 0%, and 100%) * on all dimmer channels, accept ON and OFF, as well as 0% to imply OFF, instead of having to write rules to control ON/OFF state separately. * if the dimmer channel exists, prune the corresponding power channel, since the dimmer channel is now a pure superset of its functionality * overload fan#speed to be ceiling fan or a fireplace's fan, depending on the device type * [bondhome] add bundle to the BOM pom * [bondhome] clean up BondDeviceHandler a bit * there's no need to delay initialization; ThingManager won't even attempt to initialize a child thing until its bridge is online * Remove some extra initialization checks that can never be false * slightly refactor some methods to return early, rather than nest a giant `else` * remove some info logging that will get triggered in normal usage * [bondhome] fix bridge discovery * Bridge property and config serial number need to be the same name * Don't arbitrarily delay the BPUPListener * Automatically update the IP if the BPUPListener finds it * Provide the new bridge with its discovered IP to avoid an additional DNS query * Don't get the bridge version after every keep-alive response * [bondhome] trigger end-device discovery as soon as the bridge comes online * [bondhome] remove internal binding version * [bondhome] change addr property to string Certain values seen in the wild when interpreted as a long are too big for that storage. Also, the Bond API documentation describes the addr property on a device to be a string. OpenHAB already has infrastructure to have things update their channel definitions when a binding is updated. * [bondhome] ignore any device that starts with _ In v3 of their API, Bond added a new special entry of __. Because no valid device id would start with an underscore, ignore everything that starts with an underscore to fix v3 and maybe futureproof. * address review comments mostly adding i18n to error states, and cleaning up error handling of HTTP requests. * use builtin translation services instead of plumbing our own provider through * use System.nanoTime instead of currentTimeMillis so that it will be a monotonic clock, not (as) susceptible to the clock changing * [bondhome] ignore BPUP messasges that aren't state In recent firmware, bond is now sending action messages via BPUP as well as state. This change ignores all messages that aren't state. * [bondhome] Improve error handling, and remove dummy constants Just use a single BondException class to communicate any sort of error from within bond, and avoid throwing, catching, and re-throwing the same (or slightly modified) exception. Also remove dummy constants that might give the wrong impression of the details of your Bond device. Then implement proper null checks, especially setting a configuration error if key config properties aren't set on the thing. * [bondhome] avoid setting device status when bridge just went offline * address static analysis tool problems Also-by: Sara Damiano <sdamiano@stroudcenter.org> Also-by: Keith T. Garner <kgarner@kgarner.com> Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
dd8b7c8b65
commit
52b7b7981f
@ -44,6 +44,7 @@
|
|||||||
/bundles/org.openhab.binding.bluetooth.govee/ @cpmeister
|
/bundles/org.openhab.binding.bluetooth.govee/ @cpmeister
|
||||||
/bundles/org.openhab.binding.bluetooth.roaming/ @cpmeister
|
/bundles/org.openhab.binding.bluetooth.roaming/ @cpmeister
|
||||||
/bundles/org.openhab.binding.bluetooth.ruuvitag/ @ssalonen
|
/bundles/org.openhab.binding.bluetooth.ruuvitag/ @ssalonen
|
||||||
|
/bundles/org.openhab.binding.bondhome/ @ccutrer
|
||||||
/bundles/org.openhab.binding.boschindego/ @jofleck @jlaur
|
/bundles/org.openhab.binding.boschindego/ @jofleck @jlaur
|
||||||
/bundles/org.openhab.binding.boschshc/ @stefan-kaestle @coeing @GerdZanker
|
/bundles/org.openhab.binding.boschshc/ @stefan-kaestle @coeing @GerdZanker
|
||||||
/bundles/org.openhab.binding.bosesoundtouch/ @marvkis @tratho
|
/bundles/org.openhab.binding.bosesoundtouch/ @marvkis @tratho
|
||||||
|
@ -211,6 +211,11 @@
|
|||||||
<artifactId>org.openhab.binding.bluetooth.ruuvitag</artifactId>
|
<artifactId>org.openhab.binding.bluetooth.ruuvitag</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.bondhome</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.boschindego</artifactId>
|
<artifactId>org.openhab.binding.boschindego</artifactId>
|
||||||
|
13
bundles/org.openhab.binding.bondhome/NOTICE
Normal file
13
bundles/org.openhab.binding.bondhome/NOTICE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
105
bundles/org.openhab.binding.bondhome/README.md
Normal file
105
bundles/org.openhab.binding.bondhome/README.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Bond Home Binding
|
||||||
|
|
||||||
|
This binding connects the [Bond Home](https://bondhome.io/) Bridge to openHAB using the [BOND V2 Local HTTP API](http://docs-local.appbond.com).
|
||||||
|
You'll need to acquire your [Local Token](http://docs-local.appbond.com/#section/Getting-Started/Getting-the-Bond-Token).
|
||||||
|
The easiest way is to open the Bond Home app on your mobile device, tap on your bridge device, open the Advanced Settings, and copy it from the Local Token entry.
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
| Thing Type | Description |
|
||||||
|
|------------------|-------------------------------------------------------------------|
|
||||||
|
| bondBridge | The RF/IR/WiFi Bridge |
|
||||||
|
| bondFan | An RF or IR remote controlled ceiling fan with or without a light |
|
||||||
|
| bondFireplace | An RF or IR remote controlled fireplace with or without a fan |
|
||||||
|
| bondGenericThing | A generic RF or IR remote controlled device |
|
||||||
|
| bondShades | An RF or IR remote controlled motorized shade |
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Once the bridge has been added, individual devices will be auto-discovered and added to the inbox.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
### bondBridge
|
||||||
|
|
||||||
|
| Parameter | Description | Required |
|
||||||
|
|--------------------|-----------------------------------------------------------------------|----------|
|
||||||
|
| bondId | The Bond ID of the bridge from the Bond Home app. | Yes |
|
||||||
|
| localToken | The authentication token for the local API. | Yes |
|
||||||
|
| bondIpAddress | The exact IP address to connect to the Bond Hub on the local network | No |
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
Not all channels will be available for every device.
|
||||||
|
They are dependent on how the device is configured in the Bond Home app.
|
||||||
|
|
||||||
|
### `common` Group
|
||||||
|
|
||||||
|
| Channel | Type | Description |
|
||||||
|
|------------|----------|-----------------------------------------------------------------|
|
||||||
|
| power | Switch | Device Power |
|
||||||
|
| command | String | Send a command to the device |
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
| Command | Description |
|
||||||
|
|---------------------------|---------------------------------------------------|
|
||||||
|
| STOP | Stop any in-progress dimming operation |
|
||||||
|
| PRESET | Move a shade to a preset |
|
||||||
|
| DIM_START_STOP | Dim the fan light (cyclically) |
|
||||||
|
| DIM_INCREASE | Start increasing the brightness of the fan light |
|
||||||
|
| DIM_DECREASE | Start decreasing the brightness of the fan light |
|
||||||
|
| UP_LIGHT_DIM_START_STOP | Dim the fan light (cyclically) |
|
||||||
|
| UP_LIGHT_DIM_INCREASE | Start increasing the brightness of the up light |
|
||||||
|
| UP_LIGHT_DIM_DECREASE | Start decreasing the brightness of the up light |
|
||||||
|
| DOWN_LIGHT_DIM_START_STOP | Dim the fan light (cyclically) |
|
||||||
|
| DOWN_LIGHT_DIM_INCREASE | Start increasing the brightness of the down light |
|
||||||
|
| DOWN_LIGHT_DIM_DECREASE | Start decreasing the brightness of the down light |
|
||||||
|
|
||||||
|
### `fan` Group
|
||||||
|
|
||||||
|
| Channel | Type | Description |
|
||||||
|
|-------------------|----------|---------------------------------------------------|
|
||||||
|
| power | Switch | Fan power (only applicable to fireplace fans) |
|
||||||
|
| speed | Dimmer | Sets the fan speed. The 0-100% value will be scaled to however many speeds the fan actually has. Note that you cannot set the fan to speed 0 - you must turn `OFF` the power channel instead. |
|
||||||
|
| breezeState | Switch | Enables or disables breeze mode |
|
||||||
|
| breezeMean | Dimmer | Sets the average speed in breeze mode |
|
||||||
|
| breezeVariability | Dimmer | Sets the variability of the speed in breeze mode. |
|
||||||
|
| direction | String | Sets the fan direction - "Summer" or "Winter" |
|
||||||
|
| timer | Number | Sets an automatic off timer for s seconds (turning on the fan if necessary) |
|
||||||
|
|
||||||
|
### `light`, `upLight`, `downLight` Groups
|
||||||
|
|
||||||
|
| Channel | Type | Description |
|
||||||
|
|-----------------|--------|--------------------------------------------------------|
|
||||||
|
| power | Switch | Turns the light on or off |
|
||||||
|
| brightness | Dimmer | Adjusts the brightness of the light |
|
||||||
|
|
||||||
|
### `fireplace` Group
|
||||||
|
|
||||||
|
| Channel | Type | Description |
|
||||||
|
|----------|--------|----------------------------------------|
|
||||||
|
| flame | Dimmer | Adjust the flame level |
|
||||||
|
|
||||||
|
### `shade` Group
|
||||||
|
|
||||||
|
| Channel | Type | Description |
|
||||||
|
|---------------|---------------|--------------------------------------------------|
|
||||||
|
| rollershutter | Rollershutter | Only UP, DOWN, STOP, 0%, and 100% are supported. |
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
### `bond.things` File
|
||||||
|
|
||||||
|
```
|
||||||
|
bondhome:bondBridge:BD123456 "Bond Bridge" [ ipAddress="192.168.0.10", localToken="abc123", serialNumber="BD123456" ]
|
||||||
|
bondhome:bondFan:BD123456:0d11f00 "Living Room Fan" (bondhome:bondBridge:BD123456) [ deviceId="0d11f00" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bond.items` File
|
||||||
|
|
||||||
|
```
|
||||||
|
Switch GreatFan_Switch "Great Room Fan" { channel="bondhome:bondFan:BD123456:0d11f00:common#power" }
|
||||||
|
Dimmer GreatFan_Dimmer "Great Room Fan" { channel="bondhome:bondFan:BD123456:0d11f00:fan#speed" }
|
||||||
|
String GreatFan_Rotation "Great Room Fan Rotation" { channel="bondhome:bondFan:BD123456:0d11f00:fan#direction" }
|
||||||
|
Switch GreatFanLight_Switch "Great Room Fan Light" { channel="bondhome:bondFan:BD123456:0d11f00:light#power" }
|
||||||
|
```
|
17
bundles/org.openhab.binding.bondhome/pom.xml
Normal file
17
bundles/org.openhab.binding.bondhome/pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?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 https://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.4.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.bondhome</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: Bond Home Binding</name>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.binding.bondhome-${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-bondhome" description="BondHome Binding" version="${project.version}">
|
||||||
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bondhome/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown for various API issues.
|
||||||
|
*
|
||||||
|
* @author Cody Cutrer - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondException extends Exception {
|
||||||
|
private boolean wasBridgeSetOffline;
|
||||||
|
|
||||||
|
public BondException(String message) {
|
||||||
|
this(message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BondException(String message, boolean wasBridgeSetOffline) {
|
||||||
|
super(message);
|
||||||
|
this.wasBridgeSetOffline = wasBridgeSetOffline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasBridgeSetOffline() {
|
||||||
|
return wasBridgeSetOffline;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BondHomeBindingConstants} class defines common constants, which are
|
||||||
|
* used across the whole binding.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondHomeBindingConstants {
|
||||||
|
|
||||||
|
public static final String BINDING_ID = "bondhome";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all Thing Type UIDs.
|
||||||
|
*/
|
||||||
|
public static final ThingTypeUID THING_TYPE_BOND_BRIDGE = new ThingTypeUID(BINDING_ID, "bondBridge");
|
||||||
|
public static final ThingTypeUID THING_TYPE_BOND_FAN = new ThingTypeUID(BINDING_ID, "bondFan");
|
||||||
|
public static final ThingTypeUID THING_TYPE_BOND_SHADES = new ThingTypeUID(BINDING_ID, "bondShades");
|
||||||
|
public static final ThingTypeUID THING_TYPE_BOND_FIREPLACE = new ThingTypeUID(BINDING_ID, "bondFireplace");
|
||||||
|
public static final ThingTypeUID THING_TYPE_BOND_GENERIC = new ThingTypeUID(BINDING_ID, "bondGenericThing");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supported thing types.
|
||||||
|
*/
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BOND_FAN, THING_TYPE_BOND_SHADES,
|
||||||
|
THING_TYPE_BOND_FIREPLACE, THING_TYPE_BOND_GENERIC);
|
||||||
|
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_TYPES = Set.of(THING_TYPE_BOND_BRIDGE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all Channel ids - these match the id fields in the OH-INF xml files
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Universal channels
|
||||||
|
public static final String CHANNEL_GROUP_COMMON = "common";
|
||||||
|
public static final String CHANNEL_POWER = CHANNEL_GROUP_COMMON + "#power";
|
||||||
|
public static final String CHANNEL_COMMAND = CHANNEL_GROUP_COMMON + "command";
|
||||||
|
|
||||||
|
// Ceiling fan channels
|
||||||
|
public static final String CHANNEL_GROUP_FAN = "fan";
|
||||||
|
public static final String CHANNEL_FAN_POWER = CHANNEL_GROUP_FAN + "#power";
|
||||||
|
public static final String CHANNEL_FAN_SPEED = CHANNEL_GROUP_FAN + "#speed";
|
||||||
|
public static final String CHANNEL_FAN_BREEZE_STATE = CHANNEL_GROUP_FAN + "#breezeState";
|
||||||
|
public static final String CHANNEL_FAN_BREEZE_MEAN = CHANNEL_GROUP_FAN + "#breezeMean";
|
||||||
|
public static final String CHANNEL_FAN_BREEZE_VAR = CHANNEL_GROUP_FAN + "#breezeVariability";
|
||||||
|
public static final String CHANNEL_FAN_DIRECTION = CHANNEL_GROUP_FAN + "#direction";
|
||||||
|
public static final String CHANNEL_FAN_TIMER = CHANNEL_GROUP_FAN + "#timer";
|
||||||
|
|
||||||
|
// Fan light channels
|
||||||
|
public static final String CHANNEL_GROUP_LIGHT = "light";
|
||||||
|
public static final String CHANNEL_LIGHT_POWER = CHANNEL_GROUP_LIGHT + "#power";
|
||||||
|
public static final String CHANNEL_LIGHT_BRIGHTNESS = CHANNEL_GROUP_LIGHT + "#brightness";
|
||||||
|
|
||||||
|
public static final String CHANNEL_GROUP_UP_LIGHT = "upLight";
|
||||||
|
public static final String CHANNEL_UP_LIGHT_POWER = CHANNEL_GROUP_UP_LIGHT + "#power";
|
||||||
|
public static final String CHANNEL_UP_LIGHT_ENABLE = CHANNEL_GROUP_UP_LIGHT + "#enable";
|
||||||
|
public static final String CHANNEL_UP_LIGHT_BRIGHTNESS = CHANNEL_GROUP_UP_LIGHT + "#brightness";
|
||||||
|
|
||||||
|
public static final String CHANNEL_GROUP_DOWN_LIGHT = "downLight";
|
||||||
|
public static final String CHANNEL_DOWN_LIGHT_POWER = CHANNEL_GROUP_DOWN_LIGHT + "#power";
|
||||||
|
public static final String CHANNEL_DOWN_LIGHT_ENABLE = CHANNEL_GROUP_DOWN_LIGHT + "#enable";
|
||||||
|
public static final String CHANNEL_DOWN_LIGHT_BRIGHTNESS = CHANNEL_GROUP_DOWN_LIGHT + "#brightness";
|
||||||
|
|
||||||
|
// Fireplace channels
|
||||||
|
public static final String CHANNEL_GROUP_FIREPLACE = "fireplace";
|
||||||
|
public static final String CHANNEL_FLAME = CHANNEL_GROUP_FIREPLACE + "#flame";
|
||||||
|
|
||||||
|
// Motorize shade channels
|
||||||
|
public static final String CHANNEL_GROUP_SHADES = "shade";
|
||||||
|
public static final String CHANNEL_ROLLERSHUTTER = CHANNEL_GROUP_SHADES + "#rollershutter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration arguments
|
||||||
|
*/
|
||||||
|
public static final String CONFIG_SERIAL_NUMBER = "serialNumber";
|
||||||
|
public static final String CONFIG_IP_ADDRESS = "ipAddress";
|
||||||
|
public static final String CONFIG_LOCAL_TOKEN = "localToken";
|
||||||
|
public static final String CONFIG_DEVICE_ID = "deviceId";
|
||||||
|
public static final String CONFIG_LATEST_HASH = "lastDeviceConfigurationHash";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device Properties
|
||||||
|
*/
|
||||||
|
public static final String PROPERTIES_DEVICE_NAME = "deviceName";
|
||||||
|
public static final String PROPERTIES_TEMPLATE_NAME = "template";
|
||||||
|
public static final String PROPERTIES_MAX_SPEED = "maxSpeed";
|
||||||
|
public static final String PROPERTIES_TRUST_STATE = "trustState";
|
||||||
|
public static final String PROPERTIES_ADDRESS = "addr";
|
||||||
|
public static final String PROPERTIES_RF_FREQUENCY = "freq";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
public static final int BOND_BPUP_PORT = 30007;
|
||||||
|
public static final int BOND_API_TIMEOUT_MS = 3000;
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.bondhome.internal.handler.BondBridgeHandler;
|
||||||
|
import org.openhab.binding.bondhome.internal.handler.BondDeviceHandler;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.osgi.framework.ServiceRegistration;
|
||||||
|
import org.osgi.service.component.ComponentContext;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BondHomeHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.bondhome", service = ThingHandlerFactory.class)
|
||||||
|
public class BondHomeHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public BondHomeHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||||
|
ComponentContext componentContext) {
|
||||||
|
super.activate(componentContext);
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
|
return SUPPORTED_BRIDGE_TYPES.contains(thingTypeUID) || SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
|
if (THING_TYPE_BOND_BRIDGE.equals(thingTypeUID)) {
|
||||||
|
final BondBridgeHandler handler = new BondBridgeHandler((Bridge) thing, httpClientFactory);
|
||||||
|
return handler;
|
||||||
|
} else if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
|
return new BondDeviceHandler(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||||
|
if (thingHandler instanceof BondBridgeHandler) {
|
||||||
|
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
|
||||||
|
if (serviceReg != null) {
|
||||||
|
serviceReg.unregister();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,282 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.bondhome.internal.handler.BondBridgeHandler;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Thread is responsible maintaining the Bond Push UDP Protocol
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BPUPListener implements Runnable {
|
||||||
|
|
||||||
|
private static final int SOCKET_TIMEOUT_MILLISECONDS = 3000;
|
||||||
|
private static final int SOCKET_RETRY_TIMEOUT_MILLISECONDS = 3000;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(BPUPListener.class);
|
||||||
|
|
||||||
|
// To parse the JSON responses
|
||||||
|
private final Gson gsonBuilder;
|
||||||
|
|
||||||
|
// Used for callbacks to handler
|
||||||
|
private final BondBridgeHandler bridgeHandler;
|
||||||
|
|
||||||
|
// UDP socket used to receive status events
|
||||||
|
private @Nullable DatagramSocket socket;
|
||||||
|
|
||||||
|
public @Nullable String lastRequestId;
|
||||||
|
private long timeOfLastKeepAlivePacket;
|
||||||
|
private boolean shutdown;
|
||||||
|
|
||||||
|
private int numberOfKeepAliveTimeouts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of the receiver runnable thread.
|
||||||
|
*
|
||||||
|
* @param address The address of the Bond Bridge
|
||||||
|
* @throws SocketException is some problem occurs opening the socket.
|
||||||
|
*/
|
||||||
|
public BPUPListener(BondBridgeHandler bridgeHandler) {
|
||||||
|
logger.debug("Starting BPUP Listener...");
|
||||||
|
|
||||||
|
this.bridgeHandler = bridgeHandler;
|
||||||
|
this.timeOfLastKeepAlivePacket = -1;
|
||||||
|
this.numberOfKeepAliveTimeouts = 0;
|
||||||
|
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
gsonBuilder.excludeFieldsWithoutExposeAnnotation();
|
||||||
|
Gson gson = gsonBuilder.create();
|
||||||
|
this.gsonBuilder = gson;
|
||||||
|
this.shutdown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return !shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(Executor executor) {
|
||||||
|
shutdown = false;
|
||||||
|
executor.execute(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send keep-alive as necessary and listen for push messages
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
receivePackets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gracefully shutdown thread. Worst case takes TIMEOUT_TO_DATAGRAM_RECEPTION to
|
||||||
|
* shutdown.
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
this.shutdown = true;
|
||||||
|
DatagramSocket s = this.socket;
|
||||||
|
if (s != null) {
|
||||||
|
s.close();
|
||||||
|
logger.debug("Listener closed socket");
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendBPUPKeepAlive() {
|
||||||
|
// Create a buffer and packet for the response
|
||||||
|
byte[] buffer = new byte[256];
|
||||||
|
DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length);
|
||||||
|
|
||||||
|
DatagramSocket sock = this.socket;
|
||||||
|
if (sock != null) {
|
||||||
|
logger.trace("Sending keep-alive request ('\\n')");
|
||||||
|
try {
|
||||||
|
byte[] outBuffer = { (byte) '\n' };
|
||||||
|
InetAddress inetAddress = InetAddress.getByName(bridgeHandler.getBridgeIpAddress());
|
||||||
|
DatagramPacket outPacket = new DatagramPacket(outBuffer, 1, inetAddress, BOND_BPUP_PORT);
|
||||||
|
sock.send(outPacket);
|
||||||
|
sock.receive(inPacket);
|
||||||
|
BPUPUpdate response = transformUpdatePacket(inPacket);
|
||||||
|
if (response != null) {
|
||||||
|
if (!response.bondId.equalsIgnoreCase(bridgeHandler.getBridgeId())) {
|
||||||
|
logger.warn("Response isn't from expected Bridge! Expected: {} Got: {}",
|
||||||
|
bridgeHandler.getBridgeId(), response.bondId);
|
||||||
|
} else {
|
||||||
|
bridgeHandler.setBridgeOnline(inPacket.getAddress().getHostAddress());
|
||||||
|
numberOfKeepAliveTimeouts = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
numberOfKeepAliveTimeouts++;
|
||||||
|
logger.trace("BPUP Socket timeout, number of timeouts: {}", numberOfKeepAliveTimeouts);
|
||||||
|
if (numberOfKeepAliveTimeouts > 10) {
|
||||||
|
bridgeHandler.setBridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.comm-error.timeout");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("One exception has occurred", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receivePackets() {
|
||||||
|
try {
|
||||||
|
DatagramSocket s = new DatagramSocket(null);
|
||||||
|
s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
|
||||||
|
s.bind(null);
|
||||||
|
socket = s;
|
||||||
|
logger.debug("Listener created UDP socket on port {} with timeout {}", s.getPort(),
|
||||||
|
SOCKET_TIMEOUT_MILLISECONDS);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
logger.debug("Listener got SocketException", e);
|
||||||
|
datagramSocketHealthRoutine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a buffer and packet for the response
|
||||||
|
byte[] buffer = new byte[256];
|
||||||
|
DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length);
|
||||||
|
|
||||||
|
DatagramSocket sock = this.socket;
|
||||||
|
while (sock != null && !this.shutdown) {
|
||||||
|
// Check if we're due to send something to keep the connection
|
||||||
|
long now = System.nanoTime() / 1000000L;
|
||||||
|
long timePassedFromLastKeepAlive = now - timeOfLastKeepAlivePacket;
|
||||||
|
|
||||||
|
if (timeOfLastKeepAlivePacket == -1 || timePassedFromLastKeepAlive >= 60000L) {
|
||||||
|
sendBPUPKeepAlive();
|
||||||
|
timeOfLastKeepAlivePacket = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
sock.receive(inPacket);
|
||||||
|
processPacket(inPacket);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// Ignore. Means there was no updates while we waited.
|
||||||
|
// We'll just loop around and try again after sending a keep alive.
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Listener got IOException waiting for datagram: {}", e.getMessage());
|
||||||
|
datagramSocketHealthRoutine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Listener exiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPacket(DatagramPacket packet) {
|
||||||
|
logger.trace("Got datagram of length {} from {}", packet.getLength(), packet.getAddress().getHostAddress());
|
||||||
|
|
||||||
|
BPUPUpdate update = transformUpdatePacket(packet);
|
||||||
|
if (update != null) {
|
||||||
|
if (!update.bondId.equalsIgnoreCase(bridgeHandler.getBridgeId())) {
|
||||||
|
logger.warn("Response isn't from expected Bridge! Expected: {} Got: {}", bridgeHandler.getBridgeId(),
|
||||||
|
update.bondId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate packet
|
||||||
|
if (isDuplicate(update)) {
|
||||||
|
logger.trace("Dropping duplicate packet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the update the the bridge for it to pass on to the devices
|
||||||
|
if (update.topic != null) {
|
||||||
|
logger.trace("Forwarding message to bridge handler");
|
||||||
|
bridgeHandler.forwardUpdateToThing(update);
|
||||||
|
} else {
|
||||||
|
logger.debug("No topic in incoming message!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that transforms {@link DatagramPacket} to a {@link BPUPUpdate} Object
|
||||||
|
*
|
||||||
|
* @param packet the {@link DatagramPacket}
|
||||||
|
* @return the {@link BPUPUpdate}
|
||||||
|
*/
|
||||||
|
public @Nullable BPUPUpdate transformUpdatePacket(final DatagramPacket packet) {
|
||||||
|
String responseJson = new String(packet.getData(), 0, packet.getLength(), UTF_8);
|
||||||
|
logger.debug("Message from {}:{} -> {}", packet.getAddress().getHostAddress(), packet.getPort(), responseJson);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
BPUPUpdate response = null;
|
||||||
|
try {
|
||||||
|
response = this.gsonBuilder.fromJson(responseJson, BPUPUpdate.class);
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
logger.warn("Error parsing json! {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDuplicate(BPUPUpdate update) {
|
||||||
|
boolean packetIsDuplicate = false;
|
||||||
|
String newReqestId = update.requestId;
|
||||||
|
String lastRequestId = this.lastRequestId;
|
||||||
|
if (lastRequestId != null && newReqestId != null) {
|
||||||
|
if (lastRequestId.equalsIgnoreCase(newReqestId)) {
|
||||||
|
packetIsDuplicate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remember this packet for duplicate check
|
||||||
|
lastRequestId = newReqestId;
|
||||||
|
return packetIsDuplicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void datagramSocketHealthRoutine() {
|
||||||
|
@Nullable
|
||||||
|
DatagramSocket datagramSocket = this.socket;
|
||||||
|
if (datagramSocket == null || (datagramSocket.isClosed() || !datagramSocket.isConnected())) {
|
||||||
|
logger.trace("Datagram Socket is disconnected or has been closed, reconnecting...");
|
||||||
|
try {
|
||||||
|
// close the socket before trying to reopen
|
||||||
|
if (datagramSocket != null) {
|
||||||
|
datagramSocket.close();
|
||||||
|
}
|
||||||
|
logger.trace("Old socket closed.");
|
||||||
|
try {
|
||||||
|
Thread.sleep(SOCKET_RETRY_TIMEOUT_MILLISECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
DatagramSocket s = new DatagramSocket(null);
|
||||||
|
s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
|
||||||
|
s.bind(null);
|
||||||
|
this.socket = s;
|
||||||
|
logger.trace("Datagram Socket reconnected using port {}.", s.getPort());
|
||||||
|
} catch (SocketException exception) {
|
||||||
|
logger.warn("Problem creating new socket : {}", exception.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This POJO represents update datagram sent by the Bond Push UDP Protocol
|
||||||
|
*
|
||||||
|
* The incoming JSON looks like this:
|
||||||
|
*
|
||||||
|
* {"B": "ZZBL12345", "t": "devices/aabbccdd/state", "i": "00112233bbeeeeff", "s" :200, "m": 0, "f": 255, "b": {"_":
|
||||||
|
* "ab9284ef", "power": 1, "speed": 2}}
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BPUPUpdate {
|
||||||
|
// The Bond ID
|
||||||
|
@SerializedName("B")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String bondId;
|
||||||
|
// The topic (the path from HTTP URL)
|
||||||
|
@SerializedName("t")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String topic;
|
||||||
|
// The request ID
|
||||||
|
@SerializedName("i")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String requestId;
|
||||||
|
// The HTTP status code
|
||||||
|
@SerializedName("s")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int statusCode;
|
||||||
|
// HTTP method (0=GET, 1=POST, 2=PUT, 3=DELETE, 4=PATCH)
|
||||||
|
@SerializedName("m")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int method;
|
||||||
|
// flags (Olibra-internal use)
|
||||||
|
@SerializedName("f")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int flag;
|
||||||
|
// HTTP response body
|
||||||
|
@SerializedName("b")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable BondDeviceState deviceState;
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This POJO represents a bond device
|
||||||
|
*
|
||||||
|
* The incoming JSON looks like this:
|
||||||
|
*
|
||||||
|
* {"name": "My Fan", "type": "CF", "template": "A1", "location": "Kitchen",
|
||||||
|
* "actions": {"_": "7fc1e84b"}, "properties": {"_": "84cd8a43"}, "state": {"_":
|
||||||
|
* "ad9bcde4"}, "commands": {"_": "ad9bcde4" }}
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondDevice {
|
||||||
|
// The current device hash
|
||||||
|
@SerializedName("_")
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable String hash;
|
||||||
|
// The name associated with the device in the bond app
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String name;
|
||||||
|
// The device type
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public BondDeviceType type = BondDeviceType.GENERIC_DEVICE;
|
||||||
|
// The remote control template being used
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String template;
|
||||||
|
// A list of the available actions
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public List<BondDeviceAction> actions = Arrays.asList(BondDeviceAction.TURN_ON);
|
||||||
|
// The current hash of the properties object
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable BondHash properties;
|
||||||
|
// The current hash of the state object
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable BondHash state;
|
||||||
|
// The current hash of the commands object - only applies to a bridge
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable BondHash commands;
|
||||||
|
}
|
@ -0,0 +1,317 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum represents the possible device actions
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum BondDeviceAction {
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// power: (integer) 1 = on, 0 = off
|
||||||
|
// Actions
|
||||||
|
@SerializedName("TurnOn")
|
||||||
|
TURN_ON("TurnOn", CHANNEL_GROUP_COMMON, CHANNEL_POWER),
|
||||||
|
// ^^ Turn device power on.
|
||||||
|
@SerializedName("TurnOff")
|
||||||
|
TURN_OFF("TurnOff", CHANNEL_GROUP_COMMON, CHANNEL_POWER),
|
||||||
|
// ^^ Turn device power off.
|
||||||
|
@SerializedName("TogglePower")
|
||||||
|
TOGGLE_POWER("TogglePower", CHANNEL_GROUP_COMMON, CHANNEL_POWER),
|
||||||
|
// ^^ Change device power from on to off, or off to on.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// timer: (integer) seconds remaining on timer, or 0 meaning no timer running
|
||||||
|
// Actions
|
||||||
|
@SerializedName("SetTimer")
|
||||||
|
SET_TIMER("SetTimer", CHANNEL_GROUP_COMMON, CHANNEL_FAN_TIMER),
|
||||||
|
// ^^ Start timer for s seconds. If power if off, device is implicitly turned
|
||||||
|
// on. If argument is zero, the timer is
|
||||||
|
// canceled without turning off the device.
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
// max_speed: (integer) highest speed available
|
||||||
|
// State Variables
|
||||||
|
// speed: (integer) value from 1 to max_speed. If power=0, speed represents the
|
||||||
|
// last speed setting and the speed to
|
||||||
|
// which the device resumes when user asks to turn on.
|
||||||
|
// Actions
|
||||||
|
@SerializedName("SetSpeed")
|
||||||
|
SET_SPEED("SetSpeed", CHANNEL_GROUP_FAN, CHANNEL_FAN_SPEED),
|
||||||
|
// ^^ Set speed and turn on. If speed>max_speed, max_speed is assumed. If the
|
||||||
|
// fan is off, implicitly turn on the
|
||||||
|
// power. Setting speed to zero or a negative value is ignored.
|
||||||
|
@SerializedName("IncreaseSpeed")
|
||||||
|
INCREASE_SPEED("IncreaseSpeed", CHANNEL_GROUP_FAN, CHANNEL_FAN_SPEED),
|
||||||
|
// ^^ Increase speed of fan by specified number of speeds. If the fan is off,
|
||||||
|
// implicitly turn on the power.
|
||||||
|
@SerializedName("DecreaseSpeed")
|
||||||
|
DECREASE_SPEED("DecreaseSpeed", CHANNEL_GROUP_FAN, CHANNEL_FAN_SPEED),
|
||||||
|
// ^^ Decrease fan speed by specified number of speeds. If attempting to
|
||||||
|
// decrease fan speed below 1, the fan will
|
||||||
|
// remain at speed 1. That is, power will not be implicitly turned off. If the
|
||||||
|
// power is already off, DecreaseSpeed
|
||||||
|
// is ignored.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// breeze: (array) array of the form [ <mode>, <mean>, <var> ]:
|
||||||
|
// mode: (integer) 0 = breeze mode disabled, 1 = breeze mode enabled
|
||||||
|
// mean: (integer) sets the average speed. 0 = minimum average speed (calm), 100
|
||||||
|
// = maximum average speed (storm)
|
||||||
|
// var: (integer) sets the variability of the speed. 0 = minimum variation
|
||||||
|
// (steady), 100 = maximum variation (gusty)
|
||||||
|
// Actions
|
||||||
|
@SerializedName("BreezeOn")
|
||||||
|
BREEZE_ON("BreezeOn", CHANNEL_GROUP_FAN, CHANNEL_FAN_BREEZE_STATE),
|
||||||
|
// ^^ Enable breeze with remembered parameters. Defaults to [50,50].
|
||||||
|
@SerializedName("BreezeOff")
|
||||||
|
BREEZE_OFF("BreezeOff", CHANNEL_GROUP_FAN, CHANNEL_FAN_BREEZE_STATE),
|
||||||
|
// ^^ Stop breeze. Fan remains on at current speed.
|
||||||
|
@SerializedName("SetBreeze")
|
||||||
|
SET_BREEZE("SetBreeze", CHANNEL_GROUP_FAN, CHANNEL_FAN_BREEZE_MEAN),
|
||||||
|
// ^^ Enable breeze with specified parameters (same as breeze state variable).
|
||||||
|
// Example SetBreeze([1, 20, 90]).
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// direction: (integer) 1 = forward, -1 = reverse.
|
||||||
|
// The forward and reverse modes are sometimes called Summer and Winter,
|
||||||
|
// respectively.
|
||||||
|
// Actions
|
||||||
|
@SerializedName("SetDirection")
|
||||||
|
SET_DIRECTION("SetDirection", CHANNEL_GROUP_FAN, CHANNEL_FAN_DIRECTION),
|
||||||
|
// ^^ Control forward and reverse.
|
||||||
|
@SerializedName("ToggleDirection")
|
||||||
|
TOGGLE_DIRECTION("ToggleDirection", CHANNEL_GROUP_FAN, CHANNEL_FAN_DIRECTION),
|
||||||
|
// ^^ Reverse the direction of the fan.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// light: (integer) 1 = light on, 0 = light off
|
||||||
|
// Actions
|
||||||
|
@SerializedName("TurnLightOn")
|
||||||
|
TURN_LIGHT_ON("TurnLightOn", CHANNEL_GROUP_LIGHT, CHANNEL_LIGHT_POWER),
|
||||||
|
// ^^ Turn light on.
|
||||||
|
@SerializedName("TurnLightOff")
|
||||||
|
TURN_LIGHT_OFF("TurnLightOff", CHANNEL_GROUP_LIGHT, CHANNEL_LIGHT_POWER),
|
||||||
|
// ^^ Turn off light.
|
||||||
|
@SerializedName("ToggleLight")
|
||||||
|
TOGGLE_LIGHT("ToggleLight", CHANNEL_GROUP_LIGHT, CHANNEL_LIGHT_POWER),
|
||||||
|
// ^^ Change light from on to off, or off to on.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// up_light: (integer) 1 = up light enabled, 0 = up light disabled
|
||||||
|
// down_light: (integer) 1 = down light enabled, 0 = down light disabled
|
||||||
|
// If both up_light and light are 1, then the up light will be on, and similar
|
||||||
|
// for down light.
|
||||||
|
// Note that both up_light and down_light may not be simultaneously zero, so
|
||||||
|
// that the device is always ready to
|
||||||
|
// respond to a TurnLightOn request.
|
||||||
|
// Actions
|
||||||
|
@SerializedName("TurnUpLightOn")
|
||||||
|
TURN_UP_LIGHT_ON("TurnUpLightOn", CHANNEL_GROUP_UP_LIGHT, CHANNEL_UP_LIGHT_ENABLE),
|
||||||
|
// ^^ Turn up light on.
|
||||||
|
@SerializedName("TurnDownLightOn")
|
||||||
|
TURN_DOWN_LIGHT_ON("TurnDownLightOn", CHANNEL_GROUP_DOWN_LIGHT, CHANNEL_DOWN_LIGHT_ENABLE),
|
||||||
|
// ^^ Turn down light on.
|
||||||
|
@SerializedName("TurnUpLightOff")
|
||||||
|
TURN_UP_LIGHT_OFF("TurnUpLightOff", CHANNEL_GROUP_UP_LIGHT, CHANNEL_UP_LIGHT_POWER),
|
||||||
|
// ^^ Turn off up light.
|
||||||
|
@SerializedName("TurnDownLightOff")
|
||||||
|
TURN_DOWN_LIGHT_OFF("TurnDownLightOff", CHANNEL_GROUP_DOWN_LIGHT, CHANNEL_DOWN_LIGHT_POWER),
|
||||||
|
// ^^ Turn off down light.
|
||||||
|
@SerializedName("ToggleUpLight")
|
||||||
|
TOGGLE_UP_LIGHT("ToggleUpLight", CHANNEL_GROUP_UP_LIGHT, CHANNEL_UP_LIGHT_POWER),
|
||||||
|
// ^^ Change up light from on to off, or off to on.
|
||||||
|
@SerializedName("ToggleDownLight")
|
||||||
|
TOGGLE_DOWN_LIGHT("ToggleDownLight", CHANNEL_GROUP_DOWN_LIGHT, CHANNEL_DOWN_LIGHT_POWER),
|
||||||
|
// ^^ Change down light from on to off, or off to on.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// brightness: (integer) percentage value of brightness, 1-100. If light=0,
|
||||||
|
// brightness represents the last
|
||||||
|
// brightness setting and the brightness to resume when user turns on light. If
|
||||||
|
// fan has no dimmer or a non-stateful
|
||||||
|
// dimmer, brightness is always 100.
|
||||||
|
// Actions
|
||||||
|
@SerializedName("SetBrightness")
|
||||||
|
SET_BRIGHTNESS("SetBrightness", CHANNEL_GROUP_LIGHT, CHANNEL_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Set the brightness of the light to specified percentage. Value of 0 is
|
||||||
|
// ignored, use TurnLightOff instead.
|
||||||
|
@SerializedName("IncreaseBrightness")
|
||||||
|
INCREASE_BRIGHTNESS("IncreaseBrightness", CHANNEL_GROUP_LIGHT, CHANNEL_LIGHT_BRIGHTNESS),
|
||||||
|
// will be turned on at (0 + amount).
|
||||||
|
DECREASE_BRIGHTNESS("DecreaseBrightness", CHANNEL_GROUP_LIGHT, CHANNEL_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Decrease light brightness by specified percentage. If attempting to
|
||||||
|
// decrease brightness below 1%, light will
|
||||||
|
// remain at 1%. Use TurnLightOff to turn off the light. If the light is off,
|
||||||
|
// the light will remain off but the
|
||||||
|
// remembered brightness will be decreased.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// up_light_brightness: (integer) percentage value of up light brightness,
|
||||||
|
// 1-100.
|
||||||
|
// down_light_brightness: (integer) percentage value of down light brightness,
|
||||||
|
// 1-100.
|
||||||
|
// Actions
|
||||||
|
@SerializedName("SetUpLightBrightness")
|
||||||
|
SET_UP_LIGHT_BRIGHTNESS("SetUpLightBrightness", CHANNEL_GROUP_UP_LIGHT, CHANNEL_UP_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Similar to SetBrightness but only for the up light.
|
||||||
|
@SerializedName("SetDownLightBrightness")
|
||||||
|
SET_DOWN_LIGHT_BRIGHTNESS("SetDownLightBrightness", CHANNEL_GROUP_DOWN_LIGHT, CHANNEL_DOWN_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Similar to SetBrightness but only for the down light.
|
||||||
|
@SerializedName("IncreaseUpLightBrightness")
|
||||||
|
INCREASE_UP_LIGHT_BRIGHTNESS("IncreaseUpLightBrightness", CHANNEL_GROUP_UP_LIGHT, CHANNEL_UP_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Similar to IncreaseBrightness but only for the up light.
|
||||||
|
@SerializedName("InreaseDownLightBrightness")
|
||||||
|
INCREASE_DOWN_LIGHT_BRIGHTNESS("IncreaseDownLightBrightness", CHANNEL_GROUP_DOWN_LIGHT,
|
||||||
|
CHANNEL_DOWN_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Similar to IncreaseBrightness but only for the down light.
|
||||||
|
@SerializedName("DecreaseUpLightBrightness")
|
||||||
|
DECREASE_UP_LIGHT_BRIGHTNESS("DecreaseUpLightBrightness", CHANNEL_GROUP_UP_LIGHT, CHANNEL_UP_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Similar to DecreaseBrightness but only for the up light.
|
||||||
|
@SerializedName("DecreaseDownLightBrightness")
|
||||||
|
DECREASE_DOWN_LIGHT_BRIGHTNESS("DecreaseDownLightBrightness", CHANNEL_GROUP_DOWN_LIGHT,
|
||||||
|
CHANNEL_DOWN_LIGHT_BRIGHTNESS),
|
||||||
|
// ^^ Similar to DecreaseBrightness but only for the down light.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// flame: (integer) value from 1 to 100. If power=0, flame represents the last
|
||||||
|
// flame setting and the flame to which
|
||||||
|
// the device resumes when user asks to turn on.
|
||||||
|
// Actions
|
||||||
|
@SerializedName("SetFlame")
|
||||||
|
SET_FLAME("SetFlame", CHANNEL_GROUP_FIREPLACE, CHANNEL_FLAME),
|
||||||
|
// ^^ Set flame and turn on. If flame>100, 100 is assumed. If the fireplace is
|
||||||
|
// off, implicitly turn on the power.
|
||||||
|
// Setting flame to zero or a negative value is ignored.
|
||||||
|
@SerializedName("IncreaseFlame")
|
||||||
|
INCREASE_FLAME("IncreaseFlame", CHANNEL_GROUP_FIREPLACE, CHANNEL_FLAME),
|
||||||
|
// ^^ Increase flame level of fireplace by specified number of flames. If the
|
||||||
|
// fireplace is off, implicitly turn on
|
||||||
|
// the power.
|
||||||
|
@SerializedName("DecreaseFlame")
|
||||||
|
DECREASE_FLAME("DecreaseFlame", CHANNEL_GROUP_FIREPLACE, CHANNEL_FLAME),
|
||||||
|
// ^^ Decrease flame level by specified number of flames. If attempting to
|
||||||
|
// decrease fireplace flame below 1, the
|
||||||
|
// fireplace will remain at flame 1. That is, power will not be implicitly
|
||||||
|
// turned off. If the power is already off,
|
||||||
|
// DecreaseFlame is ignored.
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// fpfan_power: (integer) 1 = on, 0 = off
|
||||||
|
// fpfan_speed: (integer) from 1-100
|
||||||
|
// Actions
|
||||||
|
@SerializedName("TurnFpFanOff")
|
||||||
|
TURN_FP_FAN_OFF("TurnFpFanOff", CHANNEL_GROUP_FAN, CHANNEL_FAN_SPEED),
|
||||||
|
// ^^ Turn the fireplace fan off
|
||||||
|
@SerializedName("TurnFpFanOn")
|
||||||
|
TURN_FP_FAN_ON("TurnFpFanOn", CHANNEL_GROUP_FAN, CHANNEL_FAN_POWER),
|
||||||
|
// ^^ Turn the fireplace fan on, restoring the previous speed
|
||||||
|
@SerializedName("SetFpFan")
|
||||||
|
SET_FP_FAN("SetFpFan", CHANNEL_GROUP_FAN, CHANNEL_FAN_SPEED),
|
||||||
|
// ^^ Sets the speed of the fireplace fan
|
||||||
|
|
||||||
|
// State Variables
|
||||||
|
// open: (integer) 1 = open, 0 = closed
|
||||||
|
// Actions
|
||||||
|
@SerializedName("Open")
|
||||||
|
OPEN("Open", CHANNEL_GROUP_SHADES, CHANNEL_ROLLERSHUTTER),
|
||||||
|
// ^^ Open the device.
|
||||||
|
@SerializedName("Close")
|
||||||
|
CLOSE("Close", CHANNEL_GROUP_SHADES, CHANNEL_ROLLERSHUTTER),
|
||||||
|
// ^^ Close the device.
|
||||||
|
@SerializedName("ToggleOpen")
|
||||||
|
TOGGLE_OPEN("ToggleOpen", CHANNEL_GROUP_SHADES, CHANNEL_ROLLERSHUTTER),
|
||||||
|
// ^^ Close the device if it's open, open it if it's closed
|
||||||
|
@SerializedName("Preset")
|
||||||
|
PRESET("Preset", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
// ^^ Sets a shade to a preset level
|
||||||
|
|
||||||
|
// Other actions
|
||||||
|
@SerializedName("Stop")
|
||||||
|
STOP("Stop", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
// ^^ This action tells the Bond to stop any in-progress transmission and empty
|
||||||
|
// its transmission queue.
|
||||||
|
@SerializedName("Hold")
|
||||||
|
HOLD("Hold", CHANNEL_GROUP_SHADES, CHANNEL_COMMAND),
|
||||||
|
// ^^ Can be used when a signal is required to tell a device to stop moving or
|
||||||
|
// the like, since Stop is a special
|
||||||
|
// "stop transmitting" action
|
||||||
|
@SerializedName("Pair")
|
||||||
|
PAIR("Pair", CHANNEL_GROUP_COMMON, null),
|
||||||
|
// ^^ Used in devices that need to be paired with a receiver.
|
||||||
|
@SerializedName("StartDimmer")
|
||||||
|
START_DIMMER("StartDimmer", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
// ^^ Start dimming. The Bond should time out its transmission after 30 seconds,
|
||||||
|
// or when the Stop action is called.
|
||||||
|
@SerializedName("StartUpLightDimmer")
|
||||||
|
START_UP_LIGHT_DIMMER("StartUpLightDimmer", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
// ^^ Use this and the StartDownLightDimmer instead of StartDimmer if your
|
||||||
|
// device has two dimmable lights.
|
||||||
|
@SerializedName("StartDownLightDimmer")
|
||||||
|
START_DOWN_LIGHT_DIMMER("StartDownLightDimmer", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
// ^^ The counterpart to StartUpLightDimmer
|
||||||
|
@SerializedName("StartIncreasingBrightness")
|
||||||
|
START_INCREASING_BRIGHTNESS("StartIncreasingBrightness", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
@SerializedName("StartDecreasingBrightness")
|
||||||
|
START_DECREASING_BRIGHTNESS("StartDecreasingBrightness", CHANNEL_GROUP_COMMON, CHANNEL_COMMAND),
|
||||||
|
|
||||||
|
// More actions
|
||||||
|
@SerializedName("OEMRandom")
|
||||||
|
OEM_RANDOM("OEMRandom", CHANNEL_GROUP_COMMON, null),
|
||||||
|
@SerializedName("OEMTimer")
|
||||||
|
OEM_TIMER("OEMTimer", CHANNEL_GROUP_COMMON, null),
|
||||||
|
@SerializedName("Unknown")
|
||||||
|
UNKNOWN("Unknown", CHANNEL_GROUP_COMMON, null);
|
||||||
|
|
||||||
|
private String actionId;
|
||||||
|
private String channelGroupTypeId;
|
||||||
|
private @Nullable String channelTypeId;
|
||||||
|
|
||||||
|
private BondDeviceAction(final String actionId, String channelGroupTypeId, @Nullable String channelTypeId) {
|
||||||
|
this.actionId = actionId;
|
||||||
|
this.channelGroupTypeId = channelGroupTypeId;
|
||||||
|
this.channelTypeId = channelTypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the actionId
|
||||||
|
*/
|
||||||
|
public String getActionId() {
|
||||||
|
return actionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the channelGroupTypeId
|
||||||
|
*/
|
||||||
|
public String getChannelGroupTypeId() {
|
||||||
|
return channelGroupTypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the channelTypeId
|
||||||
|
*/
|
||||||
|
public @Nullable String getChannelTypeId() {
|
||||||
|
return channelTypeId;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This POJO represents the properties of a Bond device
|
||||||
|
*
|
||||||
|
* The incoming JSON looks like this:
|
||||||
|
*
|
||||||
|
* {"max_speed": 3, "trust_state": false, "addr": "10101", "freq": 434300, "bps": 3000, "zero_gap": 30}
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondDeviceProperties {
|
||||||
|
// The current properties hash
|
||||||
|
@SerializedName("_")
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable String hash;
|
||||||
|
// The maximum speed of a fan
|
||||||
|
@SerializedName("max_speed")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int maxSpeed;
|
||||||
|
// Whether or not to "trust" that the device state remembered by the bond bridge is
|
||||||
|
// correct for toggle switches
|
||||||
|
@SerializedName("trust_state")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public boolean trustState;
|
||||||
|
// The device address
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public String addr = "";
|
||||||
|
// The fan radio frequency
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int freq;
|
||||||
|
// Undocumented
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int bps;
|
||||||
|
// Undocumented
|
||||||
|
@SerializedName("zero_gap")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int zeroGap;
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This POJO represents the Bond Device state
|
||||||
|
*
|
||||||
|
* The incoming JSON looks like this:
|
||||||
|
*
|
||||||
|
* { "breeze": [ 1, 0.2, 0.9 ], "brightness": 75, "light": 1, "power": 0,
|
||||||
|
* "speed": 2, "timer": 3599 }
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondDeviceState {
|
||||||
|
// The current state hash
|
||||||
|
@SerializedName("_")
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable String hash;
|
||||||
|
|
||||||
|
// The device power state 1 = on, 0 = off
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int power;
|
||||||
|
|
||||||
|
// The seconds remaining on timer, or 0 meaning no timer running
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int timer;
|
||||||
|
|
||||||
|
// The fan speed - value from 1 to max_speed. If power=0, speed represents the
|
||||||
|
// last speed setting and the speed to which the device resumes when user asks
|
||||||
|
// to turn on.
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int speed;
|
||||||
|
|
||||||
|
// The current breeze setting (for a ceiling fan)
|
||||||
|
// array of the form[<mode>,<mean>,<var>]:
|
||||||
|
// mode: (integer) 0 = breeze mode disabled, 1 = breeze mode enabled
|
||||||
|
// mean: (integer) sets the average speed. 0 = minimum average speed (calm), 100 = maximum average speed (storm)
|
||||||
|
// var: (integer) sets the variability of the speed. 0 = minimum variation (steady), 100 = maximum variation (gusty)
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int[] breeze = { 0, 50, 50 };
|
||||||
|
|
||||||
|
// The direction of a fan with a reversible motor 1 = forward, -1 = reverse.
|
||||||
|
// The forward and reverse modes are sometimes called Summer and Winter, respectively.
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int direction;
|
||||||
|
|
||||||
|
// The fan light state 1 = light on, 0 = light off
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int light;
|
||||||
|
|
||||||
|
// Whether separate up and down lights are enabled, if applicable
|
||||||
|
// 1 = enabled, 0 = disabled
|
||||||
|
// If both up_light and light are 1, then the up light will be on, and similar for down light.
|
||||||
|
@SerializedName("up_light")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int upLight;
|
||||||
|
|
||||||
|
@SerializedName("down_light")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int downLight;
|
||||||
|
|
||||||
|
// The brightness of a fan light or lights
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int brightness;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int upLightBrightness;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int downLightBrightness;
|
||||||
|
|
||||||
|
// The flame level of a fireplace - value from 1 to 100. If power=0, flame represents the last flame setting and
|
||||||
|
// the flame to which the device resumes when user asks to turn on
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int flame;
|
||||||
|
|
||||||
|
// Whether a device is open or closed (for motorized shades and garage doors)
|
||||||
|
// 1 = open, 0 = closed
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int open;
|
||||||
|
|
||||||
|
// Fan settings for a fireplace fan
|
||||||
|
// fpfan_power: (integer) 1 = on, 0 = off
|
||||||
|
// fpfan_speed: (integer) from 1-100
|
||||||
|
@SerializedName("fpfan_power")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int fpfanPower;
|
||||||
|
|
||||||
|
@SerializedName("fpfan_speed")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int fpfanSpeed;
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum represents the possible device types
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum BondDeviceType {
|
||||||
|
@SerializedName("CF")
|
||||||
|
CEILING_FAN(THING_TYPE_BOND_FAN),
|
||||||
|
@SerializedName("MS")
|
||||||
|
MOTORIZED_SHADES(THING_TYPE_BOND_SHADES),
|
||||||
|
@SerializedName("FP")
|
||||||
|
FIREPLACE(THING_TYPE_BOND_FIREPLACE),
|
||||||
|
@SerializedName("GX")
|
||||||
|
GENERIC_DEVICE(THING_TYPE_BOND_GENERIC);
|
||||||
|
|
||||||
|
private ThingTypeUID deviceTypeUid;
|
||||||
|
|
||||||
|
private BondDeviceType(final ThingTypeUID deviceTypeUid) {
|
||||||
|
this.deviceTypeUid = deviceTypeUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the device type name for request deviceType
|
||||||
|
*
|
||||||
|
* @return the deviceType name
|
||||||
|
*/
|
||||||
|
public ThingTypeUID getThingTypeUID() {
|
||||||
|
return deviceTypeUid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This POJO represents a bond tree node hash state
|
||||||
|
*
|
||||||
|
* The incoming JSON looks like this:
|
||||||
|
*
|
||||||
|
* { "_": "b32ae527" }
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondHash {
|
||||||
|
// The name associated with the device in the bond app
|
||||||
|
@SerializedName("_")
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable String hash;
|
||||||
|
}
|
@ -0,0 +1,260 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.openhab.binding.bondhome.internal.BondException;
|
||||||
|
import org.openhab.binding.bondhome.internal.handler.BondBridgeHandler;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BondHttpApi} wraps the Bond REST API and provides various low
|
||||||
|
* level function to access the device api (not cloud api).
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondHttpApi {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(BondHttpApi.class);
|
||||||
|
private final BondBridgeHandler bridgeHandler;
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
public BondHttpApi(BondBridgeHandler bridgeHandler, final HttpClientFactory httpClientFactory) {
|
||||||
|
this.bridgeHandler = bridgeHandler;
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets version information about the Bond bridge
|
||||||
|
*
|
||||||
|
* @return the {@link BondSysVersion}
|
||||||
|
* @throws BondException
|
||||||
|
*/
|
||||||
|
public BondSysVersion getBridgeVersion() throws BondException {
|
||||||
|
String json = request("/v2/sys/version");
|
||||||
|
logger.trace("BondHome device info : {}", json);
|
||||||
|
try {
|
||||||
|
return Objects.requireNonNull(gson.fromJson(json, BondSysVersion.class));
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
logger.debug("Could not parse sys/version JSON '{}'", json, e);
|
||||||
|
throw new BondException("@text/offline.comm-error.unparseable-response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of the attached devices
|
||||||
|
*
|
||||||
|
* @return an array of device id's
|
||||||
|
* @throws BondException
|
||||||
|
*/
|
||||||
|
public List<String> getDevices() throws BondException {
|
||||||
|
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
String json = request("/v2/devices/");
|
||||||
|
try {
|
||||||
|
JsonParser parser = new JsonParser();
|
||||||
|
JsonElement element = parser.parse(json);
|
||||||
|
JsonObject obj = element.getAsJsonObject();
|
||||||
|
Set<Map.Entry<String, JsonElement>> entries = obj.entrySet();
|
||||||
|
for (Map.Entry<String, JsonElement> entry : entries) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
if (!key.startsWith("_")) {
|
||||||
|
list.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
logger.debug("Could not parse devices JSON '{}'", json, e);
|
||||||
|
throw new BondException("@text/offline.comm-error.unparseable-response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets basic device information
|
||||||
|
*
|
||||||
|
* @param deviceId The ID of the device
|
||||||
|
* @return the {@link BondDevice}
|
||||||
|
* @throws BondException
|
||||||
|
*/
|
||||||
|
public BondDevice getDevice(String deviceId) throws BondException {
|
||||||
|
String json = request("/v2/devices/" + deviceId);
|
||||||
|
logger.trace("BondHome device info : {}", json);
|
||||||
|
try {
|
||||||
|
return Objects.requireNonNull(gson.fromJson(json, BondDevice.class));
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
logger.debug("Could not parse device {}'s JSON '{}'", deviceId, json, e);
|
||||||
|
throw new BondException("@text/offline.comm-error.unparseable-response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current state of a device
|
||||||
|
*
|
||||||
|
* @param deviceId The ID of the device
|
||||||
|
* @return the {@link BondDeviceState}
|
||||||
|
* @throws BondException
|
||||||
|
*/
|
||||||
|
public BondDeviceState getDeviceState(String deviceId) throws BondException {
|
||||||
|
String json = request("/v2/devices/" + deviceId + "/state");
|
||||||
|
logger.trace("BondHome device state : {}", json);
|
||||||
|
try {
|
||||||
|
return Objects.requireNonNull(gson.fromJson(json, BondDeviceState.class));
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
logger.debug("Could not parse device {}'s state JSON '{}'", deviceId, json, e);
|
||||||
|
throw new BondException("@text/offline.comm-error.unparseable-response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current properties of a device
|
||||||
|
*
|
||||||
|
* @param deviceId The ID of the device
|
||||||
|
* @return the {@link BondDeviceProperties}
|
||||||
|
* @throws BondException
|
||||||
|
*/
|
||||||
|
public BondDeviceProperties getDeviceProperties(String deviceId) throws BondException {
|
||||||
|
String json = request("/v2/devices/" + deviceId + "/properties");
|
||||||
|
logger.trace("BondHome device properties : {}", json);
|
||||||
|
try {
|
||||||
|
return Objects.requireNonNull(gson.fromJson(json, BondDeviceProperties.class));
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
logger.debug("Could not parse device {}'s property JSON '{}'", deviceId, json, e);
|
||||||
|
throw new BondException("@text/offline.comm-error.unparseable-response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a device action
|
||||||
|
*
|
||||||
|
* @param deviceId The ID of the device
|
||||||
|
* @param actionId The Bond action
|
||||||
|
* @param argument An additional argument for the actions (such as the fan speed)
|
||||||
|
*/
|
||||||
|
public synchronized void executeDeviceAction(String deviceId, BondDeviceAction action, @Nullable Integer argument) {
|
||||||
|
String url = "http://" + bridgeHandler.getBridgeIpAddress() + "/v2/devices/" + deviceId + "/actions/"
|
||||||
|
+ action.getActionId();
|
||||||
|
String payload = "{}";
|
||||||
|
if (argument != null) {
|
||||||
|
payload = "{\"argument\":" + argument + "}";
|
||||||
|
}
|
||||||
|
InputStream content = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
logger.debug("HTTP PUT to {} with content {}", url, payload);
|
||||||
|
|
||||||
|
final HttpClient httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
|
final Request request = httpClient.newRequest(url).method(HttpMethod.PUT)
|
||||||
|
.header("BOND-Token", bridgeHandler.getBridgeToken())
|
||||||
|
.timeout(BOND_API_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
try (final InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(content)) {
|
||||||
|
request.content(inputStreamContentProvider, "application/json");
|
||||||
|
}
|
||||||
|
ContentResponse response;
|
||||||
|
try {
|
||||||
|
response = request.send();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Unable to execute device action {} against device {}: {}", deviceId, action, e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("HTTP response from {}: {}", deviceId, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit GET request and return response, check for invalid responses
|
||||||
|
*
|
||||||
|
* @param uri: URI (e.g. "/settings")
|
||||||
|
*/
|
||||||
|
private synchronized String request(String uri) throws BondException {
|
||||||
|
String httpResponse = "ERROR";
|
||||||
|
String url = "http://" + bridgeHandler.getBridgeIpAddress() + uri;
|
||||||
|
int numRetriesRemaining = 3;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
logger.debug("HTTP GET to {}", url);
|
||||||
|
|
||||||
|
final HttpClient httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
|
final Request request = httpClient.newRequest(url).method(HttpMethod.GET).header("BOND-Token",
|
||||||
|
bridgeHandler.getBridgeToken());
|
||||||
|
ContentResponse response;
|
||||||
|
response = request.send();
|
||||||
|
String encoding = response.getEncoding() != null ? response.getEncoding().replaceAll("\"", "").trim()
|
||||||
|
: StandardCharsets.UTF_8.name();
|
||||||
|
try {
|
||||||
|
httpResponse = new String(response.getContent(), encoding);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new BondException("@text/offline.comm-error.no-response");
|
||||||
|
}
|
||||||
|
// handle known errors
|
||||||
|
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
|
||||||
|
// Don't retry or throw an exception if we get unauthorized, just set the bridge offline
|
||||||
|
bridgeHandler.setBridgeOffline(ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.incorrect-local-token");
|
||||||
|
throw new BondException("@text/offline.conf-error.incorrect-local-token", true);
|
||||||
|
}
|
||||||
|
if (response.getStatus() == HttpStatus.NOT_FOUND_404) {
|
||||||
|
throw new BondException("@text/offline.comm-error.device-not-found");
|
||||||
|
}
|
||||||
|
// all api responses return Json. If we get something else it must
|
||||||
|
// be an error message, e.g. http result code
|
||||||
|
if (!httpResponse.startsWith("{") && !httpResponse.startsWith("[")) {
|
||||||
|
throw new BondException("@text/offline.comm-error.unexpected-response");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("HTTP response from request to {}: {}", uri, httpResponse);
|
||||||
|
return httpResponse;
|
||||||
|
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||||
|
logger.debug("Last request to Bond Bridge failed; {} retries remaining: {}", numRetriesRemaining,
|
||||||
|
e.getMessage());
|
||||||
|
numRetriesRemaining--;
|
||||||
|
if (numRetriesRemaining == 0) {
|
||||||
|
logger.debug("Repeated Bond API calls to {} failed.", uri);
|
||||||
|
bridgeHandler.setBridgeOffline(ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.comm-error.api-call-failed");
|
||||||
|
throw new BondException("@text/offline.conf-error.api-call-failed", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.api;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This POJO represents the version information of the bond bridge
|
||||||
|
*
|
||||||
|
* The incoming JSON looks like this:
|
||||||
|
*
|
||||||
|
* {"target": "snowbird", "fw_ver": "v2.5.2", "fw_date": "Fri Feb 22 14:13:25
|
||||||
|
* -03 2019", "make": "Olibra LLC", "model": "model", "branding_profile":
|
||||||
|
* "O_SNOWBIRD", "uptime_s": 380, "_": "c342ae74"}
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondSysVersion {
|
||||||
|
// The current state hash
|
||||||
|
@SerializedName("_")
|
||||||
|
@Expose(serialize = false, deserialize = true)
|
||||||
|
public @Nullable String hash;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String target;
|
||||||
|
|
||||||
|
@SerializedName("fw_ver")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String firmwareVersion;
|
||||||
|
|
||||||
|
@SerializedName("fw_date")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String firmwareDate;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String make;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String model;
|
||||||
|
|
||||||
|
@SerializedName("branding_profile")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String brandingProfile;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable String bondid;
|
||||||
|
|
||||||
|
@SerializedName("upgrade_http")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public @Nullable Boolean upgradeHttp;
|
||||||
|
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int api;
|
||||||
|
|
||||||
|
@SerializedName("uptime_s")
|
||||||
|
@Expose(serialize = true, deserialize = true)
|
||||||
|
public int uptimeSeconds;
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.config;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BondBridgeConfiguration} class contains fields mapping thing
|
||||||
|
* configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondBridgeConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for a Bond Bridge
|
||||||
|
*/
|
||||||
|
public @Nullable String serialNumber;
|
||||||
|
public @Nullable String localToken;
|
||||||
|
public @Nullable String ipAddress;
|
||||||
|
|
||||||
|
public @Nullable String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(String ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.config;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BondHomeConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondDeviceConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for a Bond Device
|
||||||
|
*/
|
||||||
|
public @Nullable String deviceId;
|
||||||
|
public @Nullable String lastDeviceConfigurationHash;
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.discovery;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.bondhome.internal.BondException;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDevice;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondHttpApi;
|
||||||
|
import org.openhab.binding.bondhome.internal.handler.BondBridgeHandler;
|
||||||
|
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.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class does discovery of discoverable things
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
||||||
|
private static final long REFRESH_INTERVAL_MINUTES = 60;
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(BondDiscoveryService.class);
|
||||||
|
private @Nullable ScheduledFuture<?> discoveryJob;
|
||||||
|
private @Nullable BondBridgeHandler bridgeHandler;
|
||||||
|
private @Nullable BondHttpApi api;
|
||||||
|
|
||||||
|
public BondDiscoveryService() {
|
||||||
|
super(SUPPORTED_THING_TYPES, 10);
|
||||||
|
this.discoveryJob = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deactivate() {
|
||||||
|
super.deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||||
|
if (handler instanceof BondBridgeHandler) {
|
||||||
|
bridgeHandler = (BondBridgeHandler) handler;
|
||||||
|
bridgeHandler.setDiscoveryService(this);
|
||||||
|
api = bridgeHandler.getBridgeAPI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingHandler getThingHandler() {
|
||||||
|
return bridgeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startBackgroundDiscovery() {
|
||||||
|
discoverNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void discoverNow() {
|
||||||
|
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
|
||||||
|
if (localDiscoveryJob != null) {
|
||||||
|
localDiscoveryJob.cancel(true);
|
||||||
|
}
|
||||||
|
discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 0, REFRESH_INTERVAL_MINUTES, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void startScan() {
|
||||||
|
logger.debug("Start scan for Bond devices.");
|
||||||
|
try {
|
||||||
|
final ThingUID bridgeUid = bridgeHandler.getThing().getUID();
|
||||||
|
api = bridgeHandler.getBridgeAPI();
|
||||||
|
List<String> deviceList = api.getDevices();
|
||||||
|
if (deviceList != null) {
|
||||||
|
for (final String deviceId : deviceList) {
|
||||||
|
BondDevice thisDevice = api.getDevice(deviceId);
|
||||||
|
String deviceName;
|
||||||
|
if (thisDevice != null && (deviceName = thisDevice.name) != null) {
|
||||||
|
final ThingUID deviceUid = new ThingUID(thisDevice.type.getThingTypeUID(), bridgeUid, deviceId);
|
||||||
|
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(deviceUid)
|
||||||
|
.withBridge(bridgeUid).withLabel(thisDevice.name)
|
||||||
|
.withProperty(CONFIG_DEVICE_ID, deviceId)
|
||||||
|
.withProperty(PROPERTIES_DEVICE_NAME, deviceName)
|
||||||
|
.withRepresentationProperty(CONFIG_DEVICE_ID).build();
|
||||||
|
thingDiscovered(discoveryResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (BondException ignored) {
|
||||||
|
logger.warn("Error getting devices for discovery: {}", ignored.getMessage());
|
||||||
|
} finally {
|
||||||
|
removeOlderResults(getTimestampOfLastScan());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void stopBackgroundDiscovery() {
|
||||||
|
stopScan();
|
||||||
|
ScheduledFuture<?> discoveryJob = this.discoveryJob;
|
||||||
|
if (discoveryJob != null) {
|
||||||
|
discoveryJob.cancel(true);
|
||||||
|
this.discoveryJob = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.discovery;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
import static org.openhab.core.thing.Thing.*;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class identifies Bond Bridges by their mDNS service information.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@Component(service = MDNSDiscoveryParticipant.class, configurationPid = "discovery.mdns.bondhome")
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(BondMDNSDiscoveryParticipant.class);
|
||||||
|
|
||||||
|
private static final String SERVICE_TYPE = "_bond._tcp.local.";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||||
|
return SUPPORTED_BRIDGE_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceType() {
|
||||||
|
return SERVICE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingUID getThingUID(@Nullable ServiceInfo service) {
|
||||||
|
if (service != null) {
|
||||||
|
return new ThingUID(THING_TYPE_BOND_BRIDGE, service.getName());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||||
|
ThingUID thingUID = getThingUID(service);
|
||||||
|
if (thingUID != null) {
|
||||||
|
logger.debug("Discovered Bond Bridge: {}", service);
|
||||||
|
return DiscoveryResultBuilder.create(thingUID).withProperty(PROPERTY_SERIAL_NUMBER, service.getName())
|
||||||
|
.withLabel("@text/discovery.bridge.label").withRepresentationProperty(PROPERTY_SERIAL_NUMBER)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,343 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
import static org.openhab.core.thing.Thing.*;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
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.bondhome.internal.BondException;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BPUPListener;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BPUPUpdate;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDeviceState;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondHttpApi;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondSysVersion;
|
||||||
|
import org.openhab.binding.bondhome.internal.config.BondBridgeConfiguration;
|
||||||
|
import org.openhab.binding.bondhome.internal.discovery.BondDiscoveryService;
|
||||||
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BondBridgeHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondBridgeHandler extends BaseBridgeHandler {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(BondBridgeHandler.class);
|
||||||
|
|
||||||
|
// Get a dedicated threadpool for the long-running listener thread.
|
||||||
|
// Intent is to not permanently tie up the common scheduler pool.
|
||||||
|
private final ScheduledExecutorService bondScheduler = ThreadPoolManager.getScheduledPool("bondBridgeHandler");
|
||||||
|
private final BPUPListener udpListener;
|
||||||
|
private final BondHttpApi api;
|
||||||
|
|
||||||
|
private @Nullable BondBridgeConfiguration config;
|
||||||
|
|
||||||
|
private @Nullable BondDiscoveryService discoveryService;
|
||||||
|
|
||||||
|
private final Set<BondDeviceHandler> handlers = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
|
private @Nullable ScheduledFuture<?> initializer;
|
||||||
|
|
||||||
|
public BondBridgeHandler(Bridge bridge, final HttpClientFactory httpClientFactory) {
|
||||||
|
super(bridge);
|
||||||
|
udpListener = new BPUPListener(this);
|
||||||
|
api = new BondHttpApi(this, httpClientFactory);
|
||||||
|
logger.debug("Created a BondBridgeHandler for thing '{}'", getThing().getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
// Not needed, all commands are handled in the {@link BondDeviceHandler}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
config = getConfigAs(BondBridgeConfiguration.class);
|
||||||
|
|
||||||
|
// set the thing status to UNKNOWN temporarily
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
|
||||||
|
this.initializer = scheduler.schedule(this::initializeThing, 0L, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeThing() {
|
||||||
|
BondBridgeConfiguration localConfig = config;
|
||||||
|
if (localConfig.localToken == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.incorrect-local-token");
|
||||||
|
this.initializer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (localConfig.ipAddress == null) {
|
||||||
|
try {
|
||||||
|
String lookupAddress = localConfig.serialNumber + ".local";
|
||||||
|
logger.debug("Attempting to get IP address for Bond Bridge {}", lookupAddress);
|
||||||
|
InetAddress ia = InetAddress.getByName(lookupAddress);
|
||||||
|
String ip = ia.getHostAddress();
|
||||||
|
Configuration c = editConfiguration();
|
||||||
|
c.put(CONFIG_IP_ADDRESS, ip);
|
||||||
|
updateConfiguration(c);
|
||||||
|
config = getConfigAs(BondBridgeConfiguration.class);
|
||||||
|
} catch (UnknownHostException ignored) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.unknown-host");
|
||||||
|
this.initializer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
InetAddress.getByName(localConfig.ipAddress);
|
||||||
|
} catch (UnknownHostException ignored) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.invalid-host");
|
||||||
|
this.initializer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the bridge its current status and update the properties with the info
|
||||||
|
// This will also set the thing status to online/offline based on whether it
|
||||||
|
// succeeds in getting the properties from the bridge.
|
||||||
|
updateBridgeProperties();
|
||||||
|
this.initializer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
// The listener should already have been stopped when the last child was
|
||||||
|
// disposed,
|
||||||
|
// but we'll call the stop here for good measure.
|
||||||
|
stopUDPListenerJob();
|
||||||
|
ScheduledFuture<?> localInitializer = initializer;
|
||||||
|
if (localInitializer != null) {
|
||||||
|
localInitializer.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void startUDPListenerJob() {
|
||||||
|
if (udpListener.isRunning()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("Started listener job");
|
||||||
|
udpListener.start(bondScheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void stopUDPListenerJob() {
|
||||||
|
logger.trace("Stopping UDP listener job");
|
||||||
|
if (udpListener.isRunning()) {
|
||||||
|
udpListener.shutdown();
|
||||||
|
logger.debug("UDP listener job stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||||
|
super.childHandlerInitialized(childHandler, childThing);
|
||||||
|
if (childHandler instanceof BondDeviceHandler) {
|
||||||
|
BondDeviceHandler handler = (BondDeviceHandler) childHandler;
|
||||||
|
synchronized (handlers) {
|
||||||
|
// Start the BPUP update service after the first child device is added
|
||||||
|
startUDPListenerJob();
|
||||||
|
if (!handlers.contains(handler)) {
|
||||||
|
handlers.add(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||||
|
if (childHandler instanceof BondDeviceHandler) {
|
||||||
|
BondDeviceHandler handler = (BondDeviceHandler) childHandler;
|
||||||
|
synchronized (handlers) {
|
||||||
|
handlers.remove(handler);
|
||||||
|
if (handlers.isEmpty()) {
|
||||||
|
// Stop the update service when the last child is removed
|
||||||
|
stopUDPListenerJob();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.childHandlerDisposed(childHandler, childThing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards a push update to a device
|
||||||
|
*
|
||||||
|
* @param the {@link BPUPUpdate object}
|
||||||
|
*/
|
||||||
|
public void forwardUpdateToThing(BPUPUpdate pushUpdate) {
|
||||||
|
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
|
||||||
|
BondDeviceState updateState = pushUpdate.deviceState;
|
||||||
|
String topic = pushUpdate.topic;
|
||||||
|
String deviceId = null;
|
||||||
|
String topicType = null;
|
||||||
|
if (topic != null) {
|
||||||
|
String parts[] = topic.split("/");
|
||||||
|
deviceId = parts[1];
|
||||||
|
topicType = parts[2];
|
||||||
|
}
|
||||||
|
// We can't use getThingByUID because we don't know the type of device and thus
|
||||||
|
// don't know the full uid (that is we cannot tell a fan from a fireplace, etc,
|
||||||
|
// from the contents of the update)
|
||||||
|
if (deviceId != null) {
|
||||||
|
if (topicType != null && "state".equals(topicType)) {
|
||||||
|
synchronized (handlers) {
|
||||||
|
for (BondDeviceHandler handler : handlers) {
|
||||||
|
String handlerDeviceId = handler.getDeviceId();
|
||||||
|
if (handlerDeviceId.equalsIgnoreCase(deviceId)) {
|
||||||
|
handler.updateChannelsFromState(updateState);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.trace("could not read topic type from push update or type was not state.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("Can not read device Id from push update.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Id of the bridge associated with the handler
|
||||||
|
*/
|
||||||
|
public String getBridgeId() {
|
||||||
|
String serialNumber = config.serialNumber;
|
||||||
|
return serialNumber == null ? "" : serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Ip Address of the bridge associated with the handler as a string
|
||||||
|
*/
|
||||||
|
public @Nullable String getBridgeIpAddress() {
|
||||||
|
return config.ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local token of the bridge associated with the handler as a string
|
||||||
|
*/
|
||||||
|
public String getBridgeToken() {
|
||||||
|
String localToken = config.localToken;
|
||||||
|
return localToken == null ? "" : localToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the api instance
|
||||||
|
*/
|
||||||
|
public BondHttpApi getBridgeAPI() {
|
||||||
|
return this.api;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the bridge status offline.
|
||||||
|
*
|
||||||
|
* Called by the dependents to set the bridge offline when repeated requests
|
||||||
|
* fail.
|
||||||
|
*
|
||||||
|
* NOTE: This does NOT stop the UDP listener job, which will keep pinging the
|
||||||
|
* bridge's IP once a minute. The listener job will set the bridge back online
|
||||||
|
* if it receives a proper response from the bridge.
|
||||||
|
*/
|
||||||
|
public void setBridgeOffline(ThingStatusDetail detail, String description) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, detail, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the bridge status back online.
|
||||||
|
*
|
||||||
|
* Called by the UDP listener when it gets a proper response.
|
||||||
|
*/
|
||||||
|
public void setBridgeOnline(String bridgeAddress) {
|
||||||
|
BondBridgeConfiguration localConfig = config;
|
||||||
|
if (localConfig.ipAddress == null || !localConfig.ipAddress.equals(bridgeAddress)) {
|
||||||
|
logger.debug("IP address of Bond {} has changed to {}", localConfig.serialNumber, bridgeAddress);
|
||||||
|
Configuration c = editConfiguration();
|
||||||
|
c.put(CONFIG_IP_ADDRESS, bridgeAddress);
|
||||||
|
updateConfiguration(c);
|
||||||
|
updateBridgeProperties();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// don't bother updating on every keepalive packet
|
||||||
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
|
updateBridgeProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thingProperties.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBridgeProperties() {
|
||||||
|
BondSysVersion myVersion;
|
||||||
|
try {
|
||||||
|
myVersion = api.getBridgeVersion();
|
||||||
|
} catch (BondException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Update all the thing properties based on the result
|
||||||
|
Map<String, String> thingProperties = editProperties();
|
||||||
|
updateProperty(thingProperties, PROPERTY_VENDOR, myVersion.make);
|
||||||
|
updateProperty(thingProperties, PROPERTY_MODEL_ID, myVersion.model);
|
||||||
|
updateProperty(thingProperties, PROPERTY_SERIAL_NUMBER, myVersion.bondid);
|
||||||
|
updateProperty(thingProperties, PROPERTY_FIRMWARE_VERSION, myVersion.firmwareVersion);
|
||||||
|
updateProperties(thingProperties);
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
BondDiscoveryService localDiscoveryService = discoveryService;
|
||||||
|
if (localDiscoveryService != null) {
|
||||||
|
localDiscoveryService.discoverNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||||
|
return Collections.singleton(BondDiscoveryService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDiscoveryService(BondDiscoveryService discoveryService) {
|
||||||
|
this.discoveryService = discoveryService;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,756 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bondhome.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
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.bondhome.internal.BondException;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDevice;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDeviceAction;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDeviceProperties;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDeviceState;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondDeviceType;
|
||||||
|
import org.openhab.binding.bondhome.internal.api.BondHttpApi;
|
||||||
|
import org.openhab.binding.bondhome.internal.config.BondDeviceConfiguration;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||||
|
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.StringType;
|
||||||
|
import org.openhab.core.library.types.UpDownType;
|
||||||
|
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.ThingStatusInfo;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BondDeviceHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Sara Geleskie Damiano - Initial contribution
|
||||||
|
* @author Cody Cutrer - Significant rework on channels to more closely match openHAB's model.
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BondDeviceHandler extends BaseThingHandler {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(BondDeviceHandler.class);
|
||||||
|
|
||||||
|
private @NonNullByDefault({}) BondDeviceConfiguration config;
|
||||||
|
private @Nullable BondHttpApi api;
|
||||||
|
|
||||||
|
private @Nullable BondDevice deviceInfo;
|
||||||
|
private @Nullable BondDeviceProperties deviceProperties;
|
||||||
|
private @Nullable BondDeviceState deviceState;
|
||||||
|
|
||||||
|
private @Nullable ScheduledFuture<?> pollingJob;
|
||||||
|
|
||||||
|
private volatile boolean disposed;
|
||||||
|
private volatile boolean fullyInitialized;
|
||||||
|
|
||||||
|
public BondDeviceHandler(Thing thing) {
|
||||||
|
super(thing);
|
||||||
|
disposed = true;
|
||||||
|
fullyInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
if (hasConfigurationError() || !fullyInitialized) {
|
||||||
|
logger.trace(
|
||||||
|
"Bond device handler for {} received command {} on channel {} but is not yet prepared to handle it.",
|
||||||
|
config.deviceId, command, channelUID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String deviceId = Objects.requireNonNull(config.deviceId);
|
||||||
|
|
||||||
|
logger.trace("Bond device handler for {} received command {} on channel {}", config.deviceId, command,
|
||||||
|
channelUID);
|
||||||
|
final BondHttpApi api = this.api;
|
||||||
|
if (api == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error.no-api");
|
||||||
|
// Re-attempt initialization
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
logger.trace("Re-attempting initialization");
|
||||||
|
initialize();
|
||||||
|
}, 30, TimeUnit.SECONDS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command instanceof RefreshType) {
|
||||||
|
logger.trace("Executing refresh command");
|
||||||
|
try {
|
||||||
|
deviceState = api.getDeviceState(deviceId);
|
||||||
|
updateChannelsFromState(deviceState);
|
||||||
|
} catch (BondException e) {
|
||||||
|
if (!e.wasBridgeSetOffline()) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BondDeviceAction action = null;
|
||||||
|
@Nullable
|
||||||
|
Integer value = null;
|
||||||
|
final BondDevice devInfo = Objects.requireNonNull(this.deviceInfo);
|
||||||
|
switch (channelUID.getId()) {
|
||||||
|
case CHANNEL_POWER:
|
||||||
|
logger.trace("Power state command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF, null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_COMMAND:
|
||||||
|
logger.trace("{} command", command.toString());
|
||||||
|
try {
|
||||||
|
action = BondDeviceAction.valueOf(command.toString());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.warn("Received unknown command {}.", command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devInfo.actions.contains(action)) {
|
||||||
|
api.executeDeviceAction(deviceId, action, null);
|
||||||
|
} else {
|
||||||
|
logger.warn("Device {} does not support command {}.", config.deviceId, command);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FAN_POWER:
|
||||||
|
logger.trace("Fan power state command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_FP_FAN_ON : BondDeviceAction.TURN_FP_FAN_OFF,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FAN_SPEED:
|
||||||
|
logger.trace("Fan speed command");
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
if (devInfo.actions.contains(BondDeviceAction.SET_FP_FAN)) {
|
||||||
|
value = ((PercentType) command).intValue();
|
||||||
|
if (value == 0) {
|
||||||
|
action = BondDeviceAction.TURN_FP_FAN_OFF;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
action = BondDeviceAction.SET_FP_FAN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BondDeviceProperties devProperties = this.deviceProperties;
|
||||||
|
if (devProperties != null) {
|
||||||
|
int maxSpeed = devProperties.maxSpeed;
|
||||||
|
value = (int) Math.ceil(((PercentType) command).intValue() * maxSpeed / 100);
|
||||||
|
} else {
|
||||||
|
value = 1;
|
||||||
|
}
|
||||||
|
if (value == 0) {
|
||||||
|
action = BondDeviceAction.TURN_OFF;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
action = BondDeviceAction.SET_SPEED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.trace("Fan speed command with speed set as {}", value);
|
||||||
|
api.executeDeviceAction(deviceId, action, value);
|
||||||
|
} else if (command instanceof IncreaseDecreaseType) {
|
||||||
|
logger.trace("Fan increase/decrease speed command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
|
||||||
|
? BondDeviceAction.INCREASE_SPEED
|
||||||
|
: BondDeviceAction.DECREASE_SPEED),
|
||||||
|
null);
|
||||||
|
} else if (command instanceof OnOffType) {
|
||||||
|
logger.trace("Fan speed command {}", command);
|
||||||
|
if (devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_ON)) {
|
||||||
|
action = command == OnOffType.ON ? BondDeviceAction.TURN_FP_FAN_ON
|
||||||
|
: BondDeviceAction.TURN_FP_FAN_OFF;
|
||||||
|
} else if (devInfo.actions.contains(BondDeviceAction.TURN_ON)) {
|
||||||
|
action = command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF;
|
||||||
|
}
|
||||||
|
if (action != null) {
|
||||||
|
api.executeDeviceAction(deviceId, action, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Unsupported command on fan speed channel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FAN_BREEZE_STATE:
|
||||||
|
logger.trace("Fan enable/disable breeze command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.BREEZE_ON : BondDeviceAction.BREEZE_OFF, null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FAN_BREEZE_MEAN:
|
||||||
|
// TODO(SRGDamia1): write array command fxn
|
||||||
|
logger.trace("Support for fan breeze settings not yet available");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FAN_BREEZE_VAR:
|
||||||
|
// TODO(SRGDamia1): write array command fxn
|
||||||
|
logger.trace("Support for fan breeze settings not yet available");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FAN_DIRECTION:
|
||||||
|
logger.trace("Fan direction command {}", command.toString());
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
api.executeDeviceAction(deviceId, BondDeviceAction.SET_DIRECTION,
|
||||||
|
command.toString().equals("winter") ? -1 : 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_LIGHT_POWER:
|
||||||
|
logger.trace("Fan light state command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_LIGHT_BRIGHTNESS:
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
PercentType pctCommand = (PercentType) command;
|
||||||
|
value = pctCommand.intValue();
|
||||||
|
if (value == 0) {
|
||||||
|
action = BondDeviceAction.TURN_LIGHT_OFF;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
action = BondDeviceAction.SET_BRIGHTNESS;
|
||||||
|
}
|
||||||
|
logger.trace("Fan light brightness command with value of {}", value);
|
||||||
|
api.executeDeviceAction(deviceId, action, value);
|
||||||
|
} else if (command instanceof IncreaseDecreaseType) {
|
||||||
|
logger.trace("Fan light brightness increase/decrease command {}", command);
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
|
||||||
|
? BondDeviceAction.INCREASE_BRIGHTNESS
|
||||||
|
: BondDeviceAction.DECREASE_BRIGHTNESS),
|
||||||
|
null);
|
||||||
|
} else if (command instanceof OnOffType) {
|
||||||
|
logger.trace("Fan light brightness command {}", command);
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
|
||||||
|
null);
|
||||||
|
} else {
|
||||||
|
logger.info("Unsupported command on fan light brightness channel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_UP_LIGHT_ENABLE:
|
||||||
|
api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_UP_LIGHT_ON
|
||||||
|
: BondDeviceAction.TURN_UP_LIGHT_OFF, null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_UP_LIGHT_POWER:
|
||||||
|
// To turn on the up light, we first have to enable it and then turn on the lights
|
||||||
|
enableUpLight();
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_UP_LIGHT_BRIGHTNESS:
|
||||||
|
enableUpLight();
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
PercentType pctCommand = (PercentType) command;
|
||||||
|
value = pctCommand.intValue();
|
||||||
|
if (value == 0) {
|
||||||
|
action = BondDeviceAction.TURN_LIGHT_OFF;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
action = BondDeviceAction.SET_UP_LIGHT_BRIGHTNESS;
|
||||||
|
}
|
||||||
|
logger.trace("Fan up light brightness command with value of {}", value);
|
||||||
|
api.executeDeviceAction(deviceId, action, value);
|
||||||
|
} else if (command instanceof IncreaseDecreaseType) {
|
||||||
|
logger.trace("Fan uplight brightness increase/decrease command {}", command);
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
|
||||||
|
? BondDeviceAction.INCREASE_UP_LIGHT_BRIGHTNESS
|
||||||
|
: BondDeviceAction.DECREASE_UP_LIGHT_BRIGHTNESS),
|
||||||
|
null);
|
||||||
|
} else if (command instanceof OnOffType) {
|
||||||
|
logger.trace("Fan up light brightness command {}", command);
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
|
||||||
|
null);
|
||||||
|
} else {
|
||||||
|
logger.info("Unsupported command on fan up light brightness channel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_DOWN_LIGHT_ENABLE:
|
||||||
|
api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_DOWN_LIGHT_ON
|
||||||
|
: BondDeviceAction.TURN_DOWN_LIGHT_OFF, null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_DOWN_LIGHT_POWER:
|
||||||
|
// To turn on the down light, we first have to enable it and then turn on the lights
|
||||||
|
api.executeDeviceAction(deviceId, BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_DOWN_LIGHT_BRIGHTNESS:
|
||||||
|
enableDownLight();
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
PercentType pctCommand = (PercentType) command;
|
||||||
|
value = pctCommand.intValue();
|
||||||
|
if (value == 0) {
|
||||||
|
action = BondDeviceAction.TURN_LIGHT_OFF;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
action = BondDeviceAction.SET_DOWN_LIGHT_BRIGHTNESS;
|
||||||
|
}
|
||||||
|
logger.trace("Fan down light brightness command with value of {}", value);
|
||||||
|
api.executeDeviceAction(deviceId, action, value);
|
||||||
|
} else if (command instanceof IncreaseDecreaseType) {
|
||||||
|
logger.trace("Fan down light brightness increase/decrease command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
|
||||||
|
? BondDeviceAction.INCREASE_DOWN_LIGHT_BRIGHTNESS
|
||||||
|
: BondDeviceAction.DECREASE_DOWN_LIGHT_BRIGHTNESS),
|
||||||
|
null);
|
||||||
|
} else if (command instanceof OnOffType) {
|
||||||
|
logger.trace("Fan down light brightness command {}", command);
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
|
||||||
|
null);
|
||||||
|
} else {
|
||||||
|
logger.debug("Unsupported command on fan down light brightness channel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_FLAME:
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
PercentType pctCommand = (PercentType) command;
|
||||||
|
value = pctCommand.intValue();
|
||||||
|
if (value == 0) {
|
||||||
|
action = BondDeviceAction.TURN_OFF;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
action = BondDeviceAction.SET_FLAME;
|
||||||
|
}
|
||||||
|
logger.trace("Fireplace flame command with value of {}", value);
|
||||||
|
api.executeDeviceAction(deviceId, action, value);
|
||||||
|
} else if (command instanceof IncreaseDecreaseType) {
|
||||||
|
logger.trace("Fireplace flame increase/decrease command");
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
|
||||||
|
? BondDeviceAction.INCREASE_FLAME
|
||||||
|
: BondDeviceAction.DECREASE_FLAME),
|
||||||
|
null);
|
||||||
|
} else if (command instanceof OnOffType) {
|
||||||
|
api.executeDeviceAction(deviceId,
|
||||||
|
command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF, null);
|
||||||
|
} else {
|
||||||
|
logger.info("Unsupported command on flame channel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHANNEL_ROLLERSHUTTER:
|
||||||
|
logger.trace("Rollershutter command {}", command);
|
||||||
|
if (command.equals(PercentType.ZERO)) {
|
||||||
|
command = UpDownType.UP;
|
||||||
|
} else if (command.equals(PercentType.HUNDRED)) {
|
||||||
|
command = UpDownType.DOWN;
|
||||||
|
}
|
||||||
|
if (command == UpDownType.UP) {
|
||||||
|
action = BondDeviceAction.OPEN;
|
||||||
|
} else if (command == UpDownType.DOWN) {
|
||||||
|
action = BondDeviceAction.CLOSE;
|
||||||
|
} else if (command == StopMoveType.STOP) {
|
||||||
|
action = BondDeviceAction.HOLD;
|
||||||
|
}
|
||||||
|
if (action != null) {
|
||||||
|
api.executeDeviceAction(deviceId, action, null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.info("Command {} on unknown channel {}, {}", command.toFullString(), channelUID.getId(),
|
||||||
|
channelUID.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableUpLight() {
|
||||||
|
Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
|
||||||
|
BondDeviceAction.TURN_UP_LIGHT_ON, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableDownLight() {
|
||||||
|
Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
|
||||||
|
BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
config = getConfigAs(BondDeviceConfiguration.class);
|
||||||
|
logger.trace("Starting initialization for Bond device with device id {}.", config.deviceId);
|
||||||
|
fullyInitialized = false;
|
||||||
|
disposed = false;
|
||||||
|
|
||||||
|
// set the thing status to UNKNOWN temporarily
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
|
||||||
|
scheduler.execute(this::initializeThing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void dispose() {
|
||||||
|
logger.debug("Disposing thing handler for {}.", this.getThing().getUID());
|
||||||
|
// Mark handler as disposed as soon as possible to halt updates
|
||||||
|
disposed = true;
|
||||||
|
fullyInitialized = false;
|
||||||
|
|
||||||
|
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||||
|
if (pollingJob != null && !pollingJob.isCancelled()) {
|
||||||
|
pollingJob.cancel(true);
|
||||||
|
}
|
||||||
|
this.pollingJob = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeThing() {
|
||||||
|
String deviceId = config.deviceId;
|
||||||
|
|
||||||
|
if (deviceId == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.no-device-id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getBridgeAndAPI()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BondHttpApi api = this.api;
|
||||||
|
if (api == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error.no-api");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.trace("Getting device information for {} ({})", config.deviceId, this.getThing().getLabel());
|
||||||
|
deviceInfo = api.getDevice(deviceId);
|
||||||
|
logger.trace("Getting device properties for {} ({})", config.deviceId, this.getThing().getLabel());
|
||||||
|
deviceProperties = api.getDeviceProperties(deviceId);
|
||||||
|
} catch (BondException e) {
|
||||||
|
if (!e.wasBridgeSetOffline()) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BondDevice devInfo = this.deviceInfo;
|
||||||
|
final BondDeviceProperties devProperties = this.deviceProperties;
|
||||||
|
BondDeviceType devType;
|
||||||
|
String devHash;
|
||||||
|
if (devInfo == null || devProperties == null || (devType = devInfo.type) == null
|
||||||
|
|| (devHash = devInfo.hash) == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.no-device-properties");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anytime the configuration has changed or the binding has been updated,
|
||||||
|
// recreate the thing to make sure all possible channels are available
|
||||||
|
// NOTE: This will cause the thing to be disposed and re-initialized
|
||||||
|
if (wasThingUpdatedExternally(devInfo)) {
|
||||||
|
recreateAllChannels(devType, devHash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDevicePropertiesFromBond(devInfo, devProperties);
|
||||||
|
|
||||||
|
deleteExtraChannels(devInfo.actions);
|
||||||
|
|
||||||
|
startPollingJob();
|
||||||
|
|
||||||
|
// Now we're online!
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
fullyInitialized = true;
|
||||||
|
logger.debug("Finished initializing device!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thingProperties.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDevicePropertiesFromBond(BondDevice devInfo, BondDeviceProperties devProperties) {
|
||||||
|
// Update all the thing properties based on the result
|
||||||
|
Map<String, String> thingProperties = new HashMap<String, String>();
|
||||||
|
updateProperty(thingProperties, CONFIG_DEVICE_ID, config.deviceId);
|
||||||
|
logger.trace("Updating device name to {}", devInfo.name);
|
||||||
|
updateProperty(thingProperties, PROPERTIES_DEVICE_NAME, devInfo.name);
|
||||||
|
logger.trace("Updating other device properties for {} ({})", config.deviceId, this.getThing().getLabel());
|
||||||
|
updateProperty(thingProperties, PROPERTIES_TEMPLATE_NAME, devInfo.template);
|
||||||
|
thingProperties.put(PROPERTIES_MAX_SPEED, String.valueOf(devProperties.maxSpeed));
|
||||||
|
thingProperties.put(PROPERTIES_TRUST_STATE, String.valueOf(devProperties.trustState));
|
||||||
|
thingProperties.put(PROPERTIES_ADDRESS, String.valueOf(devProperties.addr));
|
||||||
|
thingProperties.put(PROPERTIES_RF_FREQUENCY, String.valueOf(devProperties.freq));
|
||||||
|
logger.trace("Saving properties for {} ({})", config.deviceId, this.getThing().getLabel());
|
||||||
|
updateProperties(thingProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void recreateAllChannels(BondDeviceType currentType, String currentHash) {
|
||||||
|
if (hasConfigurationError()) {
|
||||||
|
logger.trace("Don't recreate channels, I've been disposed!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Recreating all possible channels for a {} for {} ({})",
|
||||||
|
currentType.getThingTypeUID().getAsString(), config.deviceId, this.getThing().getLabel());
|
||||||
|
|
||||||
|
// Create a new configuration
|
||||||
|
final Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put(CONFIG_DEVICE_ID, Objects.requireNonNull(config.deviceId));
|
||||||
|
map.put(CONFIG_LATEST_HASH, currentHash);
|
||||||
|
Configuration newConfiguration = new Configuration(map);
|
||||||
|
|
||||||
|
// Change the thing type back to itself to force all channels to be re-created from XML
|
||||||
|
changeThingType(currentType.getThingTypeUID(), newConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void deleteExtraChannels(List<BondDeviceAction> currentActions) {
|
||||||
|
logger.trace("Deleting channels based on the available actions");
|
||||||
|
// Get the thing to edit
|
||||||
|
ThingBuilder thingBuilder = editThing();
|
||||||
|
|
||||||
|
// Now, look at the whole list of possible channels
|
||||||
|
List<Channel> possibleChannels = this.getThing().getChannels();
|
||||||
|
Set<String> availableChannelIds = new HashSet<>();
|
||||||
|
|
||||||
|
for (BondDeviceAction action : currentActions) {
|
||||||
|
String actionType = action.getChannelTypeId();
|
||||||
|
if (actionType != null) {
|
||||||
|
availableChannelIds.add(actionType);
|
||||||
|
logger.trace(" Action: {}, Relevant Channel Type Id: {}", action.getActionId(), actionType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove power channels if we have a dimmer channel for them;
|
||||||
|
// the dimmer channel already covers the power case
|
||||||
|
if (availableChannelIds.contains(CHANNEL_FAN_SPEED)) {
|
||||||
|
availableChannelIds.remove(CHANNEL_POWER);
|
||||||
|
availableChannelIds.remove(CHANNEL_FAN_POWER);
|
||||||
|
}
|
||||||
|
if (availableChannelIds.contains(CHANNEL_LIGHT_BRIGHTNESS)) {
|
||||||
|
availableChannelIds.remove(CHANNEL_LIGHT_POWER);
|
||||||
|
}
|
||||||
|
if (availableChannelIds.contains(CHANNEL_UP_LIGHT_BRIGHTNESS)) {
|
||||||
|
availableChannelIds.remove(CHANNEL_UP_LIGHT_POWER);
|
||||||
|
}
|
||||||
|
if (availableChannelIds.contains(CHANNEL_DOWN_LIGHT_BRIGHTNESS)) {
|
||||||
|
availableChannelIds.remove(CHANNEL_DOWN_LIGHT_POWER);
|
||||||
|
}
|
||||||
|
if (availableChannelIds.contains(CHANNEL_FLAME)) {
|
||||||
|
availableChannelIds.remove(CHANNEL_POWER);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Channel channel : possibleChannels) {
|
||||||
|
if (availableChannelIds.contains(channel.getUID().getId())) {
|
||||||
|
logger.trace(" ++++ Keeping: {}", channel.getUID().getId());
|
||||||
|
} else {
|
||||||
|
thingBuilder.withoutChannel(channel.getUID());
|
||||||
|
logger.trace(" ---- Dropping: {}", channel.getUID().getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all the channels
|
||||||
|
logger.trace("Saving the thing with extra channels removed");
|
||||||
|
updateThing(thingBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceId() {
|
||||||
|
String deviceId = config.deviceId;
|
||||||
|
return deviceId == null ? "" : deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void updateChannelsFromState(@Nullable BondDeviceState updateState) {
|
||||||
|
if (hasConfigurationError()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateState == null) {
|
||||||
|
logger.debug("No state information provided to update channels with");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Updating channels from state for {} ({})", config.deviceId, this.getThing().getLabel());
|
||||||
|
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
|
||||||
|
updateState(CHANNEL_POWER, updateState.power == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||||
|
boolean fanOn;
|
||||||
|
final BondDevice devInfo = this.deviceInfo;
|
||||||
|
if (devInfo != null && devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_OFF)) {
|
||||||
|
fanOn = updateState.fpfanPower != 0;
|
||||||
|
updateState(CHANNEL_FAN_POWER, fanOn ? OnOffType.OFF : OnOffType.ON);
|
||||||
|
updateState(CHANNEL_FAN_SPEED, new PercentType(updateState.fpfanSpeed));
|
||||||
|
} else {
|
||||||
|
fanOn = updateState.power != 0;
|
||||||
|
int value = 1;
|
||||||
|
BondDeviceProperties devProperties = this.deviceProperties;
|
||||||
|
if (devProperties != null) {
|
||||||
|
double maxSpeed = devProperties.maxSpeed;
|
||||||
|
value = (int) (((double) updateState.speed / maxSpeed) * 100);
|
||||||
|
logger.trace("Raw fan speed: {}, Percent: {}", updateState.speed, value);
|
||||||
|
} else if (updateState.speed != 0 && this.getThing().getThingTypeUID().equals(THING_TYPE_BOND_FAN)) {
|
||||||
|
logger.info("Unable to convert fan speed to a percent for {}!", this.getThing().getLabel());
|
||||||
|
}
|
||||||
|
updateState(CHANNEL_FAN_SPEED, formPercentType(fanOn, value));
|
||||||
|
}
|
||||||
|
updateState(CHANNEL_FAN_BREEZE_STATE, updateState.breeze[0] == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||||
|
updateState(CHANNEL_FAN_BREEZE_MEAN, new PercentType(updateState.breeze[1]));
|
||||||
|
updateState(CHANNEL_FAN_BREEZE_VAR, new PercentType(updateState.breeze[2]));
|
||||||
|
updateState(CHANNEL_FAN_DIRECTION,
|
||||||
|
updateState.direction == 1 ? new StringType("summer") : new StringType("winter"));
|
||||||
|
updateState(CHANNEL_FAN_TIMER, new DecimalType(updateState.timer));
|
||||||
|
|
||||||
|
updateState(CHANNEL_LIGHT_POWER, updateState.light == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||||
|
updateState(CHANNEL_LIGHT_BRIGHTNESS, formPercentType(updateState.light != 0, updateState.brightness));
|
||||||
|
|
||||||
|
updateState(CHANNEL_UP_LIGHT_ENABLE, updateState.upLight == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||||
|
updateState(CHANNEL_UP_LIGHT_POWER,
|
||||||
|
(updateState.upLight == 1 && updateState.light == 1) ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
updateState(CHANNEL_UP_LIGHT_BRIGHTNESS,
|
||||||
|
formPercentType((updateState.upLight == 1 && updateState.light == 1), updateState.upLightBrightness));
|
||||||
|
|
||||||
|
updateState(CHANNEL_DOWN_LIGHT_ENABLE, updateState.downLight == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||||
|
updateState(CHANNEL_DOWN_LIGHT_POWER,
|
||||||
|
(updateState.downLight == 1 && updateState.light == 1) ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
updateState(CHANNEL_DOWN_LIGHT_BRIGHTNESS, formPercentType(
|
||||||
|
(updateState.downLight == 1 && updateState.light == 1), updateState.downLightBrightness));
|
||||||
|
|
||||||
|
updateState(CHANNEL_FLAME, formPercentType(updateState.power != 0, updateState.flame));
|
||||||
|
|
||||||
|
updateState(CHANNEL_ROLLERSHUTTER, formPercentType(updateState.open != 0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PercentType formPercentType(boolean isOn, int value) {
|
||||||
|
if (!isOn) {
|
||||||
|
return PercentType.ZERO;
|
||||||
|
} else {
|
||||||
|
return new PercentType(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasConfigurationError() {
|
||||||
|
final ThingStatusInfo statusInfo = getThing().getStatusInfo();
|
||||||
|
return statusInfo.getStatus() == ThingStatus.OFFLINE
|
||||||
|
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR || disposed
|
||||||
|
|| config.deviceId == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean wasThingUpdatedExternally(BondDevice devInfo) {
|
||||||
|
// Check if the Bond hash tree has changed
|
||||||
|
final String lastDeviceConfigurationHash = config.lastDeviceConfigurationHash;
|
||||||
|
boolean updatedHashTree = !devInfo.hash.equals(lastDeviceConfigurationHash);
|
||||||
|
if (updatedHashTree) {
|
||||||
|
logger.debug("Hash tree of device has been updated by Bond.");
|
||||||
|
logger.debug("Current state is {}, prior state was {}.", devInfo.hash, lastDeviceConfigurationHash);
|
||||||
|
}
|
||||||
|
return updatedHashTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getBridgeAndAPI() {
|
||||||
|
Bridge myBridge = this.getBridge();
|
||||||
|
if (myBridge == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.no-bridge");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
BondBridgeHandler myBridgeHandler = (BondBridgeHandler) myBridge.getHandler();
|
||||||
|
if (myBridgeHandler != null) {
|
||||||
|
this.api = myBridgeHandler.getBridgeAPI();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error.no-bridge");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start polling for state
|
||||||
|
private synchronized void startPollingJob() {
|
||||||
|
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||||
|
if (pollingJob == null || pollingJob.isCancelled()) {
|
||||||
|
Runnable pollingCommand = () -> {
|
||||||
|
BondHttpApi api = this.api;
|
||||||
|
if (api == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.comm-error.no-api");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String deviceId = Objects.requireNonNull(config.deviceId);
|
||||||
|
logger.trace("Polling for current state for {} ({})", deviceId, this.getThing().getLabel());
|
||||||
|
try {
|
||||||
|
deviceState = api.getDeviceState(deviceId);
|
||||||
|
updateChannelsFromState(deviceState);
|
||||||
|
} catch (BondException e) {
|
||||||
|
if (!e.wasBridgeSetOffline()) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.pollingJob = scheduler.scheduleWithFixedDelay(pollingCommand, 60, 300, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||||
|
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
|
||||||
|
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
||||||
|
if (!fullyInitialized) {
|
||||||
|
scheduler.execute(this::initializeThing);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||||
|
// restart the polling job when the bridge goes back online
|
||||||
|
startPollingJob();
|
||||||
|
}
|
||||||
|
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||||
|
// stop the polling job when the bridge goes offline
|
||||||
|
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||||
|
if (pollingJob != null) {
|
||||||
|
pollingJob.cancel(true);
|
||||||
|
this.pollingJob = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<binding:binding id="bondhome" 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>Bond Home Binding</name>
|
||||||
|
<description>This is the binding for the Bond Bridge for Ceiling Fans and and other RF devices.</description>
|
||||||
|
<author>Sara Geleskie Damiano</author>
|
||||||
|
|
||||||
|
</binding:binding>
|
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config-description:config-descriptions
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<config-description uri="thing-type:bondhome:bondbridge">
|
||||||
|
<parameter name="serialNumber" type="text" required="true">
|
||||||
|
<label>Bond Serial Number</label>
|
||||||
|
<description>The Bond ID (serial number) of the Bond Bridge. Viewable in the Bond Home app or on the bottom of the
|
||||||
|
bridge itself.</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="localToken" type="text" required="true">
|
||||||
|
<label>Bond Bridge Local Token</label>
|
||||||
|
<description>The local token associated with the Bond Bridge. This is viewable in the Bond Home app.</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="ipAddress" type="text" required="false">
|
||||||
|
<context>network-address</context>
|
||||||
|
<label>Bond Bridge IP Address</label>
|
||||||
|
<description>The IP Address of the Bond Bridge.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
<config-description uri="thing-type:bondhome:bonddevice">
|
||||||
|
<parameter name="deviceId" type="text" required="true">
|
||||||
|
<label>Device ID</label>
|
||||||
|
<description>The device ID assigned to the fan. Available in the Bond Home app.</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="lastDeviceConfigurationHash" type="text" required="false">
|
||||||
|
<label>Bond Device Hash State</label>
|
||||||
|
<description>The current hash value of the device.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
</config-description:config-descriptions>
|
@ -0,0 +1,101 @@
|
|||||||
|
# binding
|
||||||
|
|
||||||
|
binding.bondhome.name = Bond Home Binding
|
||||||
|
binding.bondhome.description = This is the binding for the Bond Bridge for Ceiling Fans and and other RF devices.
|
||||||
|
|
||||||
|
# thing types
|
||||||
|
|
||||||
|
thing-type.bondhome.bondBridge.label = Bond Home Bridge
|
||||||
|
thing-type.bondhome.bondBridge.description = The RF/IR/Wifi Bridge
|
||||||
|
thing-type.bondhome.bondFan.label = Bond Home Ceiling Fan
|
||||||
|
thing-type.bondhome.bondFan.description = An RF or IR remote controlled ceiling fan with or without a light
|
||||||
|
thing-type.bondhome.bondFireplace.label = Bond Home Fireplace
|
||||||
|
thing-type.bondhome.bondFireplace.description = An RF or IR remote controlled fireplace with or without a fan
|
||||||
|
thing-type.bondhome.bondGenericThing.label = Bond Home Generic Remote
|
||||||
|
thing-type.bondhome.bondGenericThing.description = A generic RF or IR remote controlled device
|
||||||
|
thing-type.bondhome.bondShades.label = Bond Home Motorized Shades
|
||||||
|
thing-type.bondhome.bondShades.description = An RF or IR remote controlled motorized shade
|
||||||
|
|
||||||
|
# thing types config
|
||||||
|
|
||||||
|
thing-type.config.bondhome.bondbridge.ipAddress.label = Bond Bridge IP Address
|
||||||
|
thing-type.config.bondhome.bondbridge.ipAddress.description = The IP Address of the Bond Bridge.
|
||||||
|
thing-type.config.bondhome.bondbridge.localToken.label = Bond Bridge Local Token
|
||||||
|
thing-type.config.bondhome.bondbridge.localToken.description = The local token associated with the Bond Bridge. This is viewable in the Bond Home app.
|
||||||
|
thing-type.config.bondhome.bondbridge.serialNumber.label = Bond Serial Number
|
||||||
|
thing-type.config.bondhome.bondbridge.serialNumber.description = The Bond ID (serial number) of the Bond Bridge. Viewable in the Bond Home app or on the bottom of the bridge itself.
|
||||||
|
thing-type.config.bondhome.bonddevice.deviceId.label = Device ID
|
||||||
|
thing-type.config.bondhome.bonddevice.deviceId.description = The device ID assigned to the fan. Available in the Bond Home app.
|
||||||
|
thing-type.config.bondhome.bonddevice.lastDeviceConfigurationHash.label = Bond Device Hash State
|
||||||
|
thing-type.config.bondhome.bonddevice.lastDeviceConfigurationHash.description = The current hash value of the device.
|
||||||
|
|
||||||
|
# channel group types
|
||||||
|
|
||||||
|
channel-group-type.bondhome.ceilingFanChannelGroup.label = Ceiling Fan
|
||||||
|
channel-group-type.bondhome.commonChannelGroup.label = Common
|
||||||
|
channel-group-type.bondhome.downLightChannelGroup.label = Down Light
|
||||||
|
channel-group-type.bondhome.fireplaceChannelGroup.label = Fireplace
|
||||||
|
channel-group-type.bondhome.lightChannelGroup.label = Light
|
||||||
|
channel-group-type.bondhome.shadeChannelGroup.label = Motorized Shades
|
||||||
|
channel-group-type.bondhome.upLightChannelGroup.label = Up Light
|
||||||
|
|
||||||
|
# channel types
|
||||||
|
|
||||||
|
channel-type.bondhome.breezeMeanChannelType.label = Mean Breeze Speed
|
||||||
|
channel-type.bondhome.breezeMeanChannelType.description = Sets the average speed in breeze mode. 0 = minimum average speed (calm), 100 = maximum average speed (storm)
|
||||||
|
channel-type.bondhome.breezeStateChannelType.label = Breeze Mode
|
||||||
|
channel-type.bondhome.breezeStateChannelType.description = Enables or disables breeze mode
|
||||||
|
channel-type.bondhome.breezeVariabilityChannelType.label = Breeze Variability
|
||||||
|
channel-type.bondhome.breezeVariabilityChannelType.description = Sets the variability of the speed in breeze mode. 0 = minimum variation (steady), 100 = maximum variation (gusty)
|
||||||
|
channel-type.bondhome.commandChannelType.label = Command
|
||||||
|
channel-type.bondhome.commandChannelType.description = Sends a command to a device
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.STOP = Stop Dimming
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.HOLD = Hold Rollershutter
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.PRESET = Send shade to preset
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.DIM_START_STOP = Start/Stop Dimming
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.DIM_INCREASE = Increase Brightness Until Stopped
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.DIM_DECREASE = Decrease Brightness Until Stopped
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.UP_LIGHT_DIM_START_STOP = Start/Stop Dimming
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.UP_LIGHT_DIM_INCREASE = Increase Brightness Until Stopped
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.UP_LIGHT_DIM_DECREASE = Decrease Brightness Until Stopped
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.DOWN_LIGHT_DIM_START_STOP = Start/Stop Dimming
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.DOWN_LIGHT_DIM_INCREASE = Increase Brightness Until Stopped
|
||||||
|
channel-type.bondhome.commandChannelType.command.option.DOWN_LIGHT_DIM_DECREASE = Decrease Brightness Until Stopped
|
||||||
|
channel-type.bondhome.directionChannelType.label = Fan Direction
|
||||||
|
channel-type.bondhome.directionChannelType.description = Sets the fan direction; forward or reverse. The forward and reverse modes are sometimes called Summer and Winter
|
||||||
|
channel-type.bondhome.directionChannelType.state.option.summer = Summer
|
||||||
|
channel-type.bondhome.directionChannelType.state.option.winter = Winter
|
||||||
|
channel-type.bondhome.enableChannelType.label = Enable Up or Down Light
|
||||||
|
channel-type.bondhome.enableChannelType.description = Enables or disables the up or down light of the ceiling fan. The light must also be on to turn on the up light.
|
||||||
|
channel-type.bondhome.fanSpeedChannelType.label = Fan Speed
|
||||||
|
channel-type.bondhome.fanSpeedChannelType.description = Sets fan speed
|
||||||
|
channel-type.bondhome.flameChannelType.label = Flame Level
|
||||||
|
channel-type.bondhome.flameChannelType.description = Turns on or adjust the flame level
|
||||||
|
channel-type.bondhome.fpFanSpeedChannelType.label = Fireplace Fan Speed
|
||||||
|
channel-type.bondhome.fpFanSpeedChannelType.description = Adjusts the speed of the fireplace fan
|
||||||
|
channel-type.bondhome.lightChannelType.label = Light
|
||||||
|
channel-type.bondhome.lightChannelType.description = Turns the light on the ceiling fan on or off
|
||||||
|
channel-type.bondhome.rollershutterChannelType.label = Shade
|
||||||
|
channel-type.bondhome.rollershutterChannelType.description = Opens, closes, or stops motorized shades
|
||||||
|
channel-type.bondhome.timerChannelType.label = Timer
|
||||||
|
channel-type.bondhome.timerChannelType.description = Starts a timer for s seconds. If power if off, device is implicitly turned on
|
||||||
|
|
||||||
|
# thing status descriptions
|
||||||
|
|
||||||
|
offline.comm-error.api-call-failed = Bond API call to {} failed: {}
|
||||||
|
offline.comm-error.device-not-found = No Bond device found with the given device id.
|
||||||
|
offline.comm-error.no-api = Bond Bridge API not available.
|
||||||
|
offline.comm-error.no-response = No response received!
|
||||||
|
offline.comm-error.timeout = Repeated timeouts attempting to reach bridge.
|
||||||
|
offline.comm-error.unexpected-response = Unexpected HTTP response: {}
|
||||||
|
offline.comm-error.unparseable-response = Unable to parse JSON response.
|
||||||
|
offline.conf-error.no-device-id = No device ID set.
|
||||||
|
offline.conf-error.incorrect-local-token = Incorrect local token for Bond Bridge.
|
||||||
|
offline.conf-error.invalid-host = IP Address or host name for Bond Bridge is not valid.
|
||||||
|
offline.conf-error.no-bridge = No Bond Bridge is associated with this Bond device.
|
||||||
|
offline.conf-error.no-device-properties = Unable to get device properties from Bond.
|
||||||
|
offline.conf-error.unknown-host = Unable to get an IP Address for Bond Bridge
|
||||||
|
|
||||||
|
# discovery result
|
||||||
|
|
||||||
|
discovery.bridge.label = Bond Bridge
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- The Bond Bridge -->
|
||||||
|
<bridge-type id="bondBridge">
|
||||||
|
<label>Bond Home Bridge</label>
|
||||||
|
<description>The RF/IR/Wifi Bridge</description>
|
||||||
|
|
||||||
|
<representation-property>serialNumber</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:bondhome:bondbridge"/>
|
||||||
|
|
||||||
|
</bridge-type>
|
||||||
|
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- A Ceiling Fan Thing -->
|
||||||
|
<thing-type id="bondFan">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bondBridge"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Bond Home Ceiling Fan</label>
|
||||||
|
<description>An RF or IR remote controlled ceiling fan with or without a light</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="common" typeId="commonChannelGroup"/>
|
||||||
|
<channel-group id="fan" typeId="ceilingFanChannelGroup"/>
|
||||||
|
<channel-group id="light" typeId="lightChannelGroup"/>
|
||||||
|
<channel-group id="upLight" typeId="upLightChannelGroup"/>
|
||||||
|
<channel-group id="downLight" typeId="downLightChannelGroup"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>deviceId</representation-property>
|
||||||
|
|
||||||
|
<config-description-ref uri="thing-type:bondhome:bonddevice"/>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- Channels Groups -->
|
||||||
|
|
||||||
|
<channel-group-type id="commonChannelGroup">
|
||||||
|
<label>Common</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="power" typeId="system.power"/>
|
||||||
|
<channel id="command" typeId="commandChannelType"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="ceilingFanChannelGroup">
|
||||||
|
<label>Ceiling Fan</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="speed" typeId="fanSpeedChannelType"/>
|
||||||
|
<channel id="breezeState" typeId="breezeStateChannelType"/>
|
||||||
|
<channel id="breezeMean" typeId="breezeMeanChannelType"/>
|
||||||
|
<channel id="breezeVariability" typeId="breezeVariabilityChannelType"/>
|
||||||
|
<channel id="direction" typeId="directionChannelType"/>
|
||||||
|
<channel id="timer" typeId="timerChannelType"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="lightChannelGroup">
|
||||||
|
<label>Light</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="power" typeId="lightChannelType"/>
|
||||||
|
<channel id="brightness" typeId="system.brightness"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="upLightChannelGroup">
|
||||||
|
<label>Up Light</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="power" typeId="system.power"/>
|
||||||
|
<channel id="enable" typeId="enableChannelType"/>
|
||||||
|
<channel id="brightness" typeId="system.brightness"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="downLightChannelGroup">
|
||||||
|
<label>Down Light</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="power" typeId="system.power"/>
|
||||||
|
<channel id="enable" typeId="enableChannelType"/>
|
||||||
|
<channel id="brightness" typeId="system.brightness"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="fireplaceChannelGroup">
|
||||||
|
<label>Fireplace</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="flame" typeId="flameChannelType"/>
|
||||||
|
<channel id="fanPower" typeId="system.power"/>
|
||||||
|
<channel id="fanSpeed" typeId="fpFanSpeedChannelType"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="shadeChannelGroup">
|
||||||
|
<label>Motorized Shades</label>
|
||||||
|
<channels>
|
||||||
|
<channel id="rollershutter" typeId="rollershutterChannelType"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- Individual Channels -->
|
||||||
|
|
||||||
|
<channel-type id="commandChannelType">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Command</label>
|
||||||
|
<description>Sends a command to a device</description>
|
||||||
|
<command>
|
||||||
|
<options>
|
||||||
|
<option value="STOP">Stop Dimming</option>
|
||||||
|
<option value="HOLD">Hold Rollershutter</option>
|
||||||
|
<option value="PRESET">Send shade to preset</option>
|
||||||
|
<option value="DIM_START_STOP">Start/Stop Dimming</option>
|
||||||
|
<option value="DIM_INCREASE">Increase Brightness Until Stopped</option>
|
||||||
|
<option value="DIM_DECREASE">Decrease Brightness Until Stopped</option>
|
||||||
|
<option value="UP_LIGHT_DIM_START_STOP">Start/Stop Dimming</option>
|
||||||
|
<option value="UP_LIGHT_DIM_INCREASE">Increase Brightness Until Stopped</option>
|
||||||
|
<option value="UP_LIGHT_DIM_DECREASE">Decrease Brightness Until Stopped</option>
|
||||||
|
<option value="DOWN_LIGHT_DIM_START_STOP">Start/Stop Dimming</option>
|
||||||
|
<option value="DOWN_LIGHT_DIM_INCREASE">Increase Brightness Until Stopped</option>
|
||||||
|
<option value="DOWN_LIGHT_DIM_DECREASE">Decrease Brightness Until Stopped</option>
|
||||||
|
</options>
|
||||||
|
</command>
|
||||||
|
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="fanSpeedChannelType">
|
||||||
|
<item-type>Dimmer</item-type>
|
||||||
|
<label>Fan Speed</label>
|
||||||
|
<description>Sets fan speed</description>
|
||||||
|
<category>Heating</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="breezeStateChannelType">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Breeze Mode</label>
|
||||||
|
<description>Enables or disables breeze mode</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="breezeMeanChannelType">
|
||||||
|
<item-type>Dimmer</item-type>
|
||||||
|
<label>Mean Breeze Speed</label>
|
||||||
|
<description>Sets the average speed in breeze mode. 0 = minimum average speed (calm), 100 = maximum average speed
|
||||||
|
(storm)</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="breezeVariabilityChannelType">
|
||||||
|
<item-type>Dimmer</item-type>
|
||||||
|
<label>Breeze Variability</label>
|
||||||
|
<description>Sets the variability of the speed in breeze mode. 0 = minimum variation (steady), 100 = maximum
|
||||||
|
variation
|
||||||
|
(gusty)</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="directionChannelType">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Fan Direction</label>
|
||||||
|
<description>Sets the fan direction; forward or reverse. The forward and reverse modes are sometimes called Summer
|
||||||
|
and
|
||||||
|
Winter</description>
|
||||||
|
<state readOnly="false">
|
||||||
|
<options>
|
||||||
|
<option value="summer">Summer</option>
|
||||||
|
<option value="winter">Winter</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="timerChannelType">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Timer</label>
|
||||||
|
<description>Starts a timer for s seconds. If power if off, device is implicitly turned on</description>
|
||||||
|
<category>Time</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="lightChannelType">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Light</label>
|
||||||
|
<description>Turns the light on the ceiling fan on or off</description>
|
||||||
|
<category>Light</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="enableChannelType">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Enable Up or Down Light</label>
|
||||||
|
<description>Enables or disables the up or down light of the ceiling fan. The light must also be on to turn on the up
|
||||||
|
light.</description>
|
||||||
|
<category>Light</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="flameChannelType">
|
||||||
|
<item-type>Dimmer</item-type>
|
||||||
|
<label>Flame Level</label>
|
||||||
|
<description>Turns on or adjust the flame level</description>
|
||||||
|
<category>Heating</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="fpFanSpeedChannelType">
|
||||||
|
<item-type>Dimmer</item-type>
|
||||||
|
<label>Fireplace Fan Speed</label>
|
||||||
|
<description>Adjusts the speed of the fireplace fan</description>
|
||||||
|
<category>Heating</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="rollershutterChannelType">
|
||||||
|
<item-type>Rollershutter</item-type>
|
||||||
|
<label>Shade</label>
|
||||||
|
<description>Opens, closes, or stops motorized shades</description>
|
||||||
|
<category>Rollershutter</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- A Fireplace Thing -->
|
||||||
|
<thing-type id="bondFireplace">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bondBridge"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Bond Home Fireplace</label>
|
||||||
|
<description>An RF or IR remote controlled fireplace with or without a fan</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="common" typeId="commonChannelGroup"/>
|
||||||
|
<channel-group id="fireplace" typeId="fireplaceChannelGroup"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>deviceId</representation-property>
|
||||||
|
|
||||||
|
<config-description-ref uri="thing-type:bondhome:bonddevice"/>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- A Generic Thing -->
|
||||||
|
<thing-type id="bondGenericThing">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bondBridge"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Bond Home Generic Remote</label>
|
||||||
|
<description>A generic RF or IR remote controlled device</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="common" typeId="commonChannelGroup"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>deviceId</representation-property>
|
||||||
|
|
||||||
|
<config-description-ref uri="thing-type:bondhome:bonddevice"/>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bondhome"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- A Motorized Shade Thing -->
|
||||||
|
<thing-type id="bondShades">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bondBridge"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Bond Home Motorized Shades</label>
|
||||||
|
<description>An RF or IR remote controlled motorized shade</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="common" typeId="commonChannelGroup"/>
|
||||||
|
<channel-group id="shade" typeId="shadeChannelGroup"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>deviceId</representation-property>
|
||||||
|
|
||||||
|
<config-description-ref uri="thing-type:bondhome:bonddevice"/>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -76,6 +76,7 @@
|
|||||||
<module>org.openhab.binding.bluetooth.govee</module>
|
<module>org.openhab.binding.bluetooth.govee</module>
|
||||||
<module>org.openhab.binding.bluetooth.roaming</module>
|
<module>org.openhab.binding.bluetooth.roaming</module>
|
||||||
<module>org.openhab.binding.bluetooth.ruuvitag</module>
|
<module>org.openhab.binding.bluetooth.ruuvitag</module>
|
||||||
|
<module>org.openhab.binding.bondhome</module>
|
||||||
<module>org.openhab.binding.boschindego</module>
|
<module>org.openhab.binding.boschindego</module>
|
||||||
<module>org.openhab.binding.boschshc</module>
|
<module>org.openhab.binding.boschshc</module>
|
||||||
<module>org.openhab.binding.bosesoundtouch</module>
|
<module>org.openhab.binding.bosesoundtouch</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user