mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[velux] hub discovery; representation properties; socket lock up issues (#8777)
* [velux] set explicit timeouts & keepalives on socket * [velux] implement mdns service * [velux] fix representation property names * [velux] fix representation properties * [velux] finalize mdns * [velux] spotless * [velux] use both mDNS and regular DNS to resolve ip addresses * [velux] complete class rewrite using asynchronous polling thread * [velux] refactor bridgeDirectCommunicate to simplify looping * [velux] asynchronous polling means Thread.sleep no longer needed * [velux] faster synch of actuator changes * [velux] use single thread executor instead of thread pool * [velux] faster synch of actuator changes * [velux] shut down task executor Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
d3b9bd592b
commit
072113f51b
@ -34,8 +34,9 @@ The binding supports the following types of Thing.
|
|||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
To simplify the initial provisioning, the binding provides one thing which can be found by autodiscovery.
|
To simplify the initial provisioning, the binding provides one thing which can be found by autodiscovery.
|
||||||
Unfortunately there is no way to discover Velux bridges themselves within the local network.
|
The binding will automatically discover Velux Bridges within the local network, and place them in the Inbox.
|
||||||
But after configuring a Velux Bridge, it is possible to discover all scenes and actuators like windows and rollershutters in that hub.
|
Once a Velux Bridge has been discovered, you will need to enter the `password` Configuration Parameter (see below) before the binding can communicate with it.
|
||||||
|
And once the Velux Bridge is fully configured, the binding will automatically discover all its respective scenes and actuators (like windows and rollershutters), and place them in the Inbox.
|
||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ In addition there are some optional Configuration Parameters.
|
|||||||
|-------------------------|------------------|:--------:|--------------------------------------------------------------|
|
|-------------------------|------------------|:--------:|--------------------------------------------------------------|
|
||||||
| ipAddress | | Yes | Hostname or address for accessing the Velux Bridge. |
|
| ipAddress | | Yes | Hostname or address for accessing the Velux Bridge. |
|
||||||
| password | velux123 | Yes | Password for authentication against the Velux Bridge.(\*\*) |
|
| password | velux123 | Yes | Password for authentication against the Velux Bridge.(\*\*) |
|
||||||
| timeoutMsecs | 500 | No | Communication timeout in milliseconds. |
|
| timeoutMsecs | 2000 | No | Communication timeout in milliseconds. |
|
||||||
| protocol | slip | No | Underlying communication protocol (http/https/slip). |
|
| protocol | slip | No | Underlying communication protocol (http/https/slip). |
|
||||||
| tcpPort | 51200 | No | TCP port (80 or 51200) for accessing the Velux Bridge. |
|
| tcpPort | 51200 | No | TCP port (80 or 51200) for accessing the Velux Bridge. |
|
||||||
| retries | 5 | No | Number of retries during I/O. |
|
| retries | 5 | No | Number of retries during I/O. |
|
||||||
@ -89,7 +90,7 @@ In addition there are some optional Configuration Parameters.
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
1. To enable a complete invertion of all parameter values (i.e. for Velux windows), use the property `inverted` or add a trailing star to the eight-byte serial number. For an example, see below at item `Velux DG Window Bathroom`.
|
1. To enable a complete inversion of all parameter values (i.e. for Velux windows), use the property `inverted` or add a trailing star to the eight-byte serial number. For an example, see below at item `Velux DG Window Bathroom`.
|
||||||
|
|
||||||
2. Somfy devices do not provide a valid serial number to the Velux KLF200 gateway. The bridge reports a registration of the serial number 00:00:00:00:00:00:00:00. Therefore the binding implements a fallback to allow an item specification with a actuator `name` instead of actuator serial number whenever such an invalid serial number occurs. For an example, see below at item `Velux OG Somfy Shutter`.
|
2. Somfy devices do not provide a valid serial number to the Velux KLF200 gateway. The bridge reports a registration of the serial number 00:00:00:00:00:00:00:00. Therefore the binding implements a fallback to allow an item specification with a actuator `name` instead of actuator serial number whenever such an invalid serial number occurs. For an example, see below at item `Velux OG Somfy Shutter`.
|
||||||
|
|
||||||
@ -99,9 +100,10 @@ The Velux Bridge in API version one (firmware version 0.1.1.*) allows activating
|
|||||||
So besides the bridge, only one real Thing type exists, namely "scene".
|
So besides the bridge, only one real Thing type exists, namely "scene".
|
||||||
This type of Thing is configured by means of its scene name in the hub.
|
This type of Thing is configured by means of its scene name in the hub.
|
||||||
|
|
||||||
| Configuration Parameter | Default | Required | Description |
|
| Configuration Parameter | Default | Required | Description |
|
||||||
|-------------------------|------------------------|:--------:|-----------------------------------------------------------|
|
|-------------------------|------------------------|:--------:|-----------------------------------------------------------------------|
|
||||||
| sceneName | | Yes | Name of the scene in the hub. |
|
| sceneName | | Yes | Name of the scene in the hub. |
|
||||||
|
| velocity | | No | The speed at which the scene will be executed (deafult, silent, fast) |
|
||||||
|
|
||||||
### Thing Configuration for "vshutter"
|
### Thing Configuration for "vshutter"
|
||||||
|
|
||||||
@ -128,7 +130,7 @@ The supported Channels and their associated channel types are shown below.
|
|||||||
| downtime | Number | Time interval (sec) between last successful and most recent device interaction. |
|
| downtime | Number | Time interval (sec) between last successful and most recent device interaction. |
|
||||||
| doDetection | Switch | Command to activate bridge detection mode. |
|
| doDetection | Switch | Command to activate bridge detection mode. |
|
||||||
|
|
||||||
### Channels for "window", "rollershutter" Things
|
### Channels for "window" / "rollershutter" Things
|
||||||
|
|
||||||
The supported Channels and their associated channel types are shown below.
|
The supported Channels and their associated channel types are shown below.
|
||||||
|
|
||||||
@ -138,6 +140,15 @@ The supported Channels and their associated channel types are shown below.
|
|||||||
| limitMinimum | Rollershutter | Minimum limit position of the window or device. |
|
| limitMinimum | Rollershutter | Minimum limit position of the window or device. |
|
||||||
| limitMaximum | Rollershutter | Maximum limit position of the window or device. |
|
| limitMaximum | Rollershutter | Maximum limit position of the window or device. |
|
||||||
|
|
||||||
|
The `position` Channel indicates the open/close state of the window (resp. roller shutter) in percent (0% .. 100%) as follows..
|
||||||
|
|
||||||
|
- As a general rule the display is the actual physical position.
|
||||||
|
- If it is moving towards a new target position, the display is the target position.
|
||||||
|
- After the movement has completed, the display is the final physical position.
|
||||||
|
- If a window is opened manually, the display is `UNDEF`.
|
||||||
|
- In case of errors (e.g. window jammed) the display is `UNDEF`.
|
||||||
|
- If a Somfy actuator is commanded to its 'favorite' position via a Somfy remote control, under some circumstances the display is `UNDEF`. See also Rules below.
|
||||||
|
|
||||||
### Channels for "actuator" Things
|
### Channels for "actuator" Things
|
||||||
|
|
||||||
The supported Channels and their associated channel types are shown below.
|
The supported Channels and their associated channel types are shown below.
|
||||||
@ -149,6 +160,8 @@ The supported Channels and their associated channel types are shown below.
|
|||||||
| limitMinimum | Rollershutter | Minimum limit position of the window or device. |
|
| limitMinimum | Rollershutter | Minimum limit position of the window or device. |
|
||||||
| limitMaximum | Rollershutter | Maximum limit position of the window or device. |
|
| limitMaximum | Rollershutter | Maximum limit position of the window or device. |
|
||||||
|
|
||||||
|
See the section above for "window" / "rollershutter" Things for further information concerning the `position` Channel.
|
||||||
|
|
||||||
### Channels for "scene" Things
|
### Channels for "scene" Things
|
||||||
|
|
||||||
The supported Channels and their associated channel types are shown below.
|
The supported Channels and their associated channel types are shown below.
|
||||||
@ -166,6 +179,8 @@ The supported Channel and its associated channel type is shown below.
|
|||||||
|--------------|---------------|-----------------------------------------|
|
|--------------|---------------|-----------------------------------------|
|
||||||
| position | Rollershutter | Position of the virtual roller shutter. |
|
| position | Rollershutter | Position of the virtual roller shutter. |
|
||||||
|
|
||||||
|
See the section above for "window" / "rollershutter" Things for further information concerning the `position` Channel.
|
||||||
|
|
||||||
### Channels for "information" Thing
|
### Channels for "information" Thing
|
||||||
|
|
||||||
The supported Channel and its associated channel type is shown below.
|
The supported Channel and its associated channel type is shown below.
|
||||||
@ -187,13 +202,13 @@ The bridge Thing provides the following properties.
|
|||||||
|
|
||||||
| Property | Description |
|
| Property | Description |
|
||||||
|-------------------|-----------------------------------------------------------------|
|
|-------------------|-----------------------------------------------------------------|
|
||||||
|
| address | IP address of the Bridge |
|
||||||
| check | Result of the check of current item configuration |
|
| check | Result of the check of current item configuration |
|
||||||
| connectionAttempt | Date-Time of last connection attampt |
|
| connectionAttempt | Date-Time of last connection attampt |
|
||||||
| connectionSuccess | Date-Time of last successful connection attampt |
|
| connectionSuccess | Date-Time of last successful connection attampt |
|
||||||
| defaultGW | IP address of the Default Gateway of the Bridge |
|
| defaultGW | IP address of the Default Gateway of the Bridge |
|
||||||
| DHCP | Flag whether automatic IP configuration is enabled |
|
| DHCP | Flag whether automatic IP configuration is enabled |
|
||||||
| firmware | Software version of the Bridge |
|
| firmware | Software version of the Bridge |
|
||||||
| ipAddress | IP address of the Bridge |
|
|
||||||
| products | List of all recognized products |
|
| products | List of all recognized products |
|
||||||
| scenes | List of all defined scenes |
|
| scenes | List of all defined scenes |
|
||||||
| subnetMask | IP subnetmask of the Bridge |
|
| subnetMask | IP subnetmask of the Bridge |
|
||||||
@ -231,12 +246,14 @@ Frame label="Velux Windows" {
|
|||||||
|
|
||||||
[=> download sample sitemaps file for textual configuration](./doc/conf/sitemaps/velux.sitemap)
|
[=> download sample sitemaps file for textual configuration](./doc/conf/sitemaps/velux.sitemap)
|
||||||
|
|
||||||
### Rules
|
### Rule for closing windows after a period of time
|
||||||
|
|
||||||
**Rule for closing windows after a period of time**:
|
Especially in the colder months, it is advisable to close the window after adequate ventilation.
|
||||||
Especially in the colder months, it is advisable to close the window after adequate ventilation. Therefore, automatic closing after one minute is good to save on heating costs.
|
Therefore, automatic closing after one minute is good to save on heating costs.
|
||||||
However, to allow the case of intentional prolonged opening, an automatic closure is made only with the window fully open.
|
However, to allow the case of intentional prolonged opening, an automatic closure is made only with the window fully open.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
rule "V_WINDOW_changed"
|
rule "V_WINDOW_changed"
|
||||||
when
|
when
|
||||||
@ -245,14 +262,14 @@ then
|
|||||||
logInfo("rules.V_WINDOW", "V_WINDOW_changes() called.")
|
logInfo("rules.V_WINDOW", "V_WINDOW_changes() called.")
|
||||||
// Get the sensor value
|
// Get the sensor value
|
||||||
val Number windowState = V_WINDOW.state as DecimalType
|
val Number windowState = V_WINDOW.state as DecimalType
|
||||||
logWarn("rules.V_WINDOW", "Window state is "+windowState+".")
|
logWarn("rules.V_WINDOW", "Window state is " + windowState + ".")
|
||||||
if (windowState < 80) {
|
if (windowState < 80) {
|
||||||
if (windowState == 0) {
|
if (windowState == 0) {
|
||||||
logWarn("rules.V_WINDOW", "V-WINDOW changed to fully open.")
|
logWarn("rules.V_WINDOW", "V-WINDOW changed to fully open.")
|
||||||
var int interval = 1
|
var int interval = 1
|
||||||
createTimer(now.plusMinutes(interval)) [ |
|
createTimer(now.plusMinutes(interval)) [ |
|
||||||
logWarn("rules.V_WINDOW:event", "event-V_WINDOW(): setting V-WINDOW to 100.")
|
logWarn("rules.V_WINDOW:event", "event-V_WINDOW(): setting V-WINDOW to 100.")
|
||||||
sendCommand(V_WINDOW,100)
|
sendCommand(V_WINDOW, 100)
|
||||||
V_WINDOW.postUpdate(100)
|
V_WINDOW.postUpdate(100)
|
||||||
logWarn("rules.V_WINDOW:event", "event-V_WINDOW done.")
|
logWarn("rules.V_WINDOW:event", "event-V_WINDOW done.")
|
||||||
]
|
]
|
||||||
@ -267,6 +284,69 @@ end
|
|||||||
|
|
||||||
[=> download sample rules file for textual configuration](./doc/conf/rules/velux.rules)
|
[=> download sample rules file for textual configuration](./doc/conf/rules/velux.rules)
|
||||||
|
|
||||||
|
### Rule for rebooting the Bridge
|
||||||
|
|
||||||
|
This binding includes a rule action to reboot the Velux Bridge by remote command:
|
||||||
|
|
||||||
|
- `boolean isRebooting = rebootBridge()`
|
||||||
|
|
||||||
|
_Warning: use this command carefully..._
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
rule "Reboot KLF 200"
|
||||||
|
when
|
||||||
|
...
|
||||||
|
then
|
||||||
|
val veluxActions = getActions("velux", "velux:klf200:myhubname")
|
||||||
|
if (veluxActions !== null) {
|
||||||
|
val isRebooting = veluxActions.rebootBridge()
|
||||||
|
logWarn("Rules", "Velux KLF 200 rebooting: " + isRebooting)
|
||||||
|
} else {
|
||||||
|
logWarn("Rules", "Velux KLF 200 actions not found, check thing ID")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule for checking if a Window has been manually opened
|
||||||
|
|
||||||
|
In the case that a window has been manually opened, and you then try to move it via the binding, its `position` will become `UNDEF`.
|
||||||
|
You can exploit this behaviour in a rule to check regularly if a window has been manually opened.
|
||||||
|
|
||||||
|
```java
|
||||||
|
rule "Every 10 minutes, check if window is in manual mode"
|
||||||
|
when
|
||||||
|
Time cron "0 0/10 * * * ?" // every 10 minutes
|
||||||
|
then
|
||||||
|
if (Velux_Window.state != UNDEF) {
|
||||||
|
// command the window to its actual position; this will either
|
||||||
|
// - succeed: the actual position will not change, or
|
||||||
|
// - fail: the position becomes UNDEF (logged next time this rule executes)
|
||||||
|
Velux_Window.sendCommand(Velux_Window.state)
|
||||||
|
} else {
|
||||||
|
logWarn("Rules", "Velux in Manual mode, trying to close again")
|
||||||
|
// try to close it
|
||||||
|
Velux_Window.sendCommand(0)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule for Somfy actuators
|
||||||
|
|
||||||
|
If a Somfy actuator is commanded to its 'favorite' position via a Somfy remote control, under some circumstances the display is `UNDEF`.
|
||||||
|
You can resolve this behaviour in a rule that detects the `UNDEF` position and (re-)commands it to its favorite position.
|
||||||
|
|
||||||
|
```java
|
||||||
|
rule "Somfy Actuator: resolve undefined position"
|
||||||
|
when
|
||||||
|
Item Somfy_Actuator changed to UNDEF
|
||||||
|
then
|
||||||
|
val favoritePosition = 91
|
||||||
|
Somfy_Actuator.sendCommand(favoritePosition)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
For those who are interested in more detailed insight of the processing of this binding, a deeper look can be achieved by increased loglevel.
|
For those who are interested in more detailed insight of the processing of this binding, a deeper look can be achieved by increased loglevel.
|
||||||
|
@ -79,7 +79,7 @@ public class VeluxBinding extends VeluxBridgeConfiguration {
|
|||||||
this.password = uncheckedConfiguration.password;
|
this.password = uncheckedConfiguration.password;
|
||||||
}
|
}
|
||||||
logger.trace("VeluxBinding(): checking {}.", VeluxBridgeConfiguration.BRIDGE_TIMEOUT_MSECS);
|
logger.trace("VeluxBinding(): checking {}.", VeluxBridgeConfiguration.BRIDGE_TIMEOUT_MSECS);
|
||||||
if ((uncheckedConfiguration.timeoutMsecs > 0) && (uncheckedConfiguration.timeoutMsecs <= 10000)) {
|
if ((uncheckedConfiguration.timeoutMsecs >= 500) && (uncheckedConfiguration.timeoutMsecs <= 5000)) {
|
||||||
this.timeoutMsecs = uncheckedConfiguration.timeoutMsecs;
|
this.timeoutMsecs = uncheckedConfiguration.timeoutMsecs;
|
||||||
}
|
}
|
||||||
logger.trace("VeluxBinding(): checking {}.", VeluxBridgeConfiguration.BRIDGE_RETRIES);
|
logger.trace("VeluxBinding(): checking {}.", VeluxBridgeConfiguration.BRIDGE_RETRIES);
|
||||||
@ -87,7 +87,7 @@ public class VeluxBinding extends VeluxBridgeConfiguration {
|
|||||||
this.retries = uncheckedConfiguration.retries;
|
this.retries = uncheckedConfiguration.retries;
|
||||||
}
|
}
|
||||||
logger.trace("VeluxBinding(): checking {}.", VeluxBridgeConfiguration.BRIDGE_REFRESH_MSECS);
|
logger.trace("VeluxBinding(): checking {}.", VeluxBridgeConfiguration.BRIDGE_REFRESH_MSECS);
|
||||||
if ((uncheckedConfiguration.refreshMSecs > 0) && (uncheckedConfiguration.refreshMSecs <= 10000)) {
|
if ((uncheckedConfiguration.refreshMSecs >= 1000) && (uncheckedConfiguration.refreshMSecs <= 60000)) {
|
||||||
this.refreshMSecs = uncheckedConfiguration.refreshMSecs;
|
this.refreshMSecs = uncheckedConfiguration.refreshMSecs;
|
||||||
}
|
}
|
||||||
this.isBulkRetrievalEnabled = uncheckedConfiguration.isBulkRetrievalEnabled;
|
this.isBulkRetrievalEnabled = uncheckedConfiguration.isBulkRetrievalEnabled;
|
||||||
@ -106,15 +106,20 @@ public class VeluxBinding extends VeluxBridgeConfiguration {
|
|||||||
*/
|
*/
|
||||||
public VeluxBridgeConfiguration checked() {
|
public VeluxBridgeConfiguration checked() {
|
||||||
logger.trace("checked() called.");
|
logger.trace("checked() called.");
|
||||||
|
// @formatter:off
|
||||||
logger.debug("{}Config[{}={},{}={},{}={},{}={},{}={},{}={},{}={},{}={},{}={},{}={}]",
|
logger.debug("{}Config[{}={},{}={},{}={},{}={},{}={},{}={},{}={},{}={},{}={},{}={}]",
|
||||||
VeluxBindingConstants.BINDING_ID, VeluxBridgeConfiguration.BRIDGE_PROTOCOL, protocol,
|
VeluxBindingConstants.BINDING_ID,
|
||||||
VeluxBridgeConfiguration.BRIDGE_IPADDRESS, this.ipAddress, VeluxBridgeConfiguration.BRIDGE_TCPPORT,
|
VeluxBridgeConfiguration.BRIDGE_PROTOCOL, protocol,
|
||||||
tcpPort, VeluxBridgeConfiguration.BRIDGE_PASSWORD, password.replaceAll(".", "*"),
|
VeluxBridgeConfiguration.BRIDGE_IPADDRESS, this.ipAddress,
|
||||||
VeluxBridgeConfiguration.BRIDGE_TIMEOUT_MSECS, timeoutMsecs, VeluxBridgeConfiguration.BRIDGE_RETRIES,
|
VeluxBridgeConfiguration.BRIDGE_TCPPORT, tcpPort,
|
||||||
retries, VeluxBridgeConfiguration.BRIDGE_REFRESH_MSECS, refreshMSecs,
|
VeluxBridgeConfiguration.BRIDGE_PASSWORD, password.replaceAll(".", "*"),
|
||||||
|
VeluxBridgeConfiguration.BRIDGE_TIMEOUT_MSECS, timeoutMsecs,
|
||||||
|
VeluxBridgeConfiguration.BRIDGE_RETRIES, retries,
|
||||||
|
VeluxBridgeConfiguration.BRIDGE_REFRESH_MSECS, refreshMSecs,
|
||||||
VeluxBridgeConfiguration.BRIDGE_IS_BULK_RETRIEVAL_ENABLED, isBulkRetrievalEnabled,
|
VeluxBridgeConfiguration.BRIDGE_IS_BULK_RETRIEVAL_ENABLED, isBulkRetrievalEnabled,
|
||||||
VeluxBridgeConfiguration.BRIDGE_IS_SEQUENTIAL_ENFORCED, isSequentialEnforced,
|
VeluxBridgeConfiguration.BRIDGE_IS_SEQUENTIAL_ENFORCED, isSequentialEnforced,
|
||||||
VeluxBridgeConfiguration.BRIDGE_PROTOCOL_TRACE_ENABLED, isProtocolTraceEnabled);
|
VeluxBridgeConfiguration.BRIDGE_PROTOCOL_TRACE_ENABLED, isProtocolTraceEnabled);
|
||||||
|
// @formatter:off
|
||||||
logger.trace("checked() done.");
|
logger.trace("checked() done.");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -91,10 +91,15 @@ public class VeluxBindingConstants {
|
|||||||
// Definitions of different set of Things
|
// Definitions of different set of Things
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THINGS_BINDING = new HashSet<>(Arrays.asList(THING_TYPE_BINDING));
|
public static final Set<ThingTypeUID> SUPPORTED_THINGS_BINDING = new HashSet<>(Arrays.asList(THING_TYPE_BINDING));
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THINGS_BRIDGE = new HashSet<>(Arrays.asList(THING_TYPE_BRIDGE));
|
public static final Set<ThingTypeUID> SUPPORTED_THINGS_BRIDGE = new HashSet<>(Arrays.asList(THING_TYPE_BRIDGE));
|
||||||
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THINGS_ITEMS = new HashSet<>(
|
public static final Set<ThingTypeUID> SUPPORTED_THINGS_ITEMS = new HashSet<>(
|
||||||
Arrays.asList(THING_TYPE_VELUX_SCENE, THING_TYPE_VELUX_ACTUATOR, THING_TYPE_VELUX_ROLLERSHUTTER,
|
Arrays.asList(THING_TYPE_VELUX_SCENE, THING_TYPE_VELUX_ACTUATOR, THING_TYPE_VELUX_ROLLERSHUTTER,
|
||||||
THING_TYPE_VELUX_WINDOW, THING_TYPE_VELUX_VSHUTTER));
|
THING_TYPE_VELUX_WINDOW, THING_TYPE_VELUX_VSHUTTER));
|
||||||
|
|
||||||
|
public static final Set<ThingTypeUID> DISCOVERABLE_THINGS = Set.of(THING_TYPE_VELUX_SCENE,
|
||||||
|
THING_TYPE_VELUX_ACTUATOR, THING_TYPE_VELUX_ROLLERSHUTTER, THING_TYPE_VELUX_WINDOW,
|
||||||
|
THING_TYPE_VELUX_VSHUTTER, THING_TYPE_BINDING, THING_TYPE_BRIDGE);
|
||||||
|
|
||||||
// *** List of all Channel ids ***
|
// *** List of all Channel ids ***
|
||||||
|
|
||||||
// List of all binding channel ids
|
// List of all binding channel ids
|
||||||
@ -113,7 +118,7 @@ public class VeluxBindingConstants {
|
|||||||
public static final String PROPERTY_BRIDGE_TIMESTAMP_SUCCESS = "connectionSuccess";
|
public static final String PROPERTY_BRIDGE_TIMESTAMP_SUCCESS = "connectionSuccess";
|
||||||
public static final String PROPERTY_BRIDGE_TIMESTAMP_ATTEMPT = "connectionAttempt";
|
public static final String PROPERTY_BRIDGE_TIMESTAMP_ATTEMPT = "connectionAttempt";
|
||||||
public static final String PROPERTY_BRIDGE_FIRMWARE = "firmware";
|
public static final String PROPERTY_BRIDGE_FIRMWARE = "firmware";
|
||||||
public static final String PROPERTY_BRIDGE_IPADDRESS = "ipAddress";
|
public static final String PROPERTY_BRIDGE_ADDRESS = "address";
|
||||||
public static final String PROPERTY_BRIDGE_SUBNETMASK = "subnetMask";
|
public static final String PROPERTY_BRIDGE_SUBNETMASK = "subnetMask";
|
||||||
public static final String PROPERTY_BRIDGE_DEFAULTGW = "defaultGW";
|
public static final String PROPERTY_BRIDGE_DEFAULTGW = "defaultGW";
|
||||||
public static final String PROPERTY_BRIDGE_DHCP = "DHCP";
|
public static final String PROPERTY_BRIDGE_DHCP = "DHCP";
|
||||||
|
@ -84,7 +84,7 @@ public enum VeluxItemType {
|
|||||||
BRIDGE_DO_DETECTION(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.CHANNEL_BRIDGE_DO_DETECTION, TypeFlavor.INITIATOR),
|
BRIDGE_DO_DETECTION(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.CHANNEL_BRIDGE_DO_DETECTION, TypeFlavor.INITIATOR),
|
||||||
|
|
||||||
BRIDGE_FIRMWARE(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_FIRMWARE, TypeFlavor.PROPERTY),
|
BRIDGE_FIRMWARE(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_FIRMWARE, TypeFlavor.PROPERTY),
|
||||||
BRIDGE_IPADDRESS(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_IPADDRESS, TypeFlavor.PROPERTY),
|
BRIDGE_ADDRESS(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_ADDRESS, TypeFlavor.PROPERTY),
|
||||||
BRIDGE_SUBNETMASK(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_SUBNETMASK, TypeFlavor.PROPERTY),
|
BRIDGE_SUBNETMASK(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_SUBNETMASK, TypeFlavor.PROPERTY),
|
||||||
BRIDGE_DEFAULTGW(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_DEFAULTGW, TypeFlavor.PROPERTY),
|
BRIDGE_DEFAULTGW(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_DEFAULTGW, TypeFlavor.PROPERTY),
|
||||||
BRIDGE_DHCP(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_DHCP, TypeFlavor.PROPERTY),
|
BRIDGE_DHCP(VeluxBindingConstants.THING_TYPE_BRIDGE, VeluxBindingConstants.PROPERTY_BRIDGE_DHCP, TypeFlavor.PROPERTY),
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.velux.internal.action;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link IVeluxActions} defines rule action interface for rebooting the bridge
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface IVeluxActions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to send a reboot command to a Velux Bridge
|
||||||
|
*
|
||||||
|
* @return true if the command was sent
|
||||||
|
* @throws IllegalStateException if something is wrong
|
||||||
|
*/
|
||||||
|
Boolean rebootBridge() throws IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to send a relative move command to a Velux actuator
|
||||||
|
*
|
||||||
|
* @param nodeId the node Id in the bridge
|
||||||
|
* @param relativePercent the target position relative to its current position (-100% <= relativePercent <= +100%)
|
||||||
|
* @return true if the command was sent
|
||||||
|
* @throws NumberFormatException if either of the arguments is not an integer, or out of range
|
||||||
|
* @throws IllegalStateException if anything else is wrong
|
||||||
|
*/
|
||||||
|
Boolean moveRelative(String nodeId, String relativePercent) throws NumberFormatException, IllegalStateException;
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.velux.internal.action;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
|
import org.openhab.core.automation.annotation.ActionInput;
|
||||||
|
import org.openhab.core.automation.annotation.ActionOutput;
|
||||||
|
import org.openhab.core.automation.annotation.RuleAction;
|
||||||
|
import org.openhab.core.thing.binding.ThingActions;
|
||||||
|
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link VeluxActions} implementation of the rule action for rebooting the bridge
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@ThingActionsScope(name = "velux")
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VeluxActions implements ThingActions, IVeluxActions {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(VeluxActions.class);
|
||||||
|
|
||||||
|
private @Nullable VeluxBridgeHandler bridgeHandler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||||
|
if (handler instanceof VeluxBridgeHandler) {
|
||||||
|
this.bridgeHandler = (VeluxBridgeHandler) handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingHandler getThingHandler() {
|
||||||
|
return this.bridgeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@RuleAction(label = "reboot Bridge", description = "issues a reboot command to the KLF200 bridge")
|
||||||
|
public @ActionOutput(name = "executing", type = "java.lang.Boolean", label = "executing", description = "indicates the command was issued") Boolean rebootBridge()
|
||||||
|
throws IllegalStateException {
|
||||||
|
logger.trace("rebootBridge(): action called");
|
||||||
|
VeluxBridgeHandler bridge = bridgeHandler;
|
||||||
|
if (bridge == null) {
|
||||||
|
throw new IllegalStateException("Bridge instance is null");
|
||||||
|
}
|
||||||
|
return bridge.runReboot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@RuleAction(label = "move relative", description = "issues a relative move command to an actuator")
|
||||||
|
public @ActionOutput(name = "executing", type = "java.lang.Boolean", label = "executing", description = "indicates the command was issued") Boolean moveRelative(
|
||||||
|
@ActionInput(name = "nodeId", required = true, label = "nodeId", description = "actuator id in the bridge", type = "java.lang.String") String nodeId,
|
||||||
|
@ActionInput(name = "relativePercent", required = true, label = "relativePercent", description = "position delta from current", type = "java.lang.String") String relativePercent)
|
||||||
|
throws NumberFormatException, IllegalStateException {
|
||||||
|
logger.trace("moveRelative(): action called");
|
||||||
|
VeluxBridgeHandler bridge = bridgeHandler;
|
||||||
|
if (bridge == null) {
|
||||||
|
throw new IllegalStateException("Bridge instance is null");
|
||||||
|
}
|
||||||
|
int node = Integer.parseInt(nodeId);
|
||||||
|
if (node < 0 || node > 200) {
|
||||||
|
throw new NumberFormatException("Node Id out of range");
|
||||||
|
}
|
||||||
|
int relPct = Integer.parseInt(relativePercent);
|
||||||
|
if (Math.abs(relPct) > 100) {
|
||||||
|
throw new NumberFormatException("Relative Percent out of range");
|
||||||
|
}
|
||||||
|
return bridge.moveRelative(node, relPct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method to send a reboot command to a Velux Bridge
|
||||||
|
*
|
||||||
|
* @param actions ThingActions from the caller
|
||||||
|
* @return true if the command was sent
|
||||||
|
* @throws IllegalArgumentException if actions is invalid
|
||||||
|
* @throws IllegalStateException if anything else is wrong
|
||||||
|
*/
|
||||||
|
public static Boolean rebootBridge(@Nullable ThingActions actions)
|
||||||
|
throws IllegalArgumentException, IllegalStateException {
|
||||||
|
if (!(actions instanceof IVeluxActions)) {
|
||||||
|
throw new IllegalArgumentException("Unsupported action");
|
||||||
|
}
|
||||||
|
return ((IVeluxActions) actions).rebootBridge();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method to send a relative move command to a Velux actuator
|
||||||
|
*
|
||||||
|
* @param actions ThingActions from the caller
|
||||||
|
* @param nodeId the node Id in the bridge
|
||||||
|
* @param relativePercent the target position relative to its current position (-100% <= relativePercent <= +100%)
|
||||||
|
* @return true if the command was sent
|
||||||
|
* @throws IllegalArgumentException if actions is invalid
|
||||||
|
* @throws NumberFormatException if either of nodeId or relativePercent is not an integer, or out of range
|
||||||
|
* @throws IllegalStateException if anything else is wrong
|
||||||
|
*/
|
||||||
|
public static Boolean moveRelative(@Nullable ThingActions actions, String nodeId, String relativePercent)
|
||||||
|
throws IllegalArgumentException, NumberFormatException, IllegalStateException {
|
||||||
|
if (!(actions instanceof IVeluxActions)) {
|
||||||
|
throw new IllegalArgumentException("Unsupported action");
|
||||||
|
}
|
||||||
|
return ((IVeluxActions) actions).moveRelative(nodeId, relativePercent);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* NOTE: All relevant classes of this binding are below the internal node.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.velux.internal.action;
|
@ -20,6 +20,7 @@ import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
|||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.Login;
|
import org.openhab.binding.velux.internal.bridge.common.Login;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.Logout;
|
import org.openhab.binding.velux.internal.bridge.common.Logout;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ public abstract class VeluxBridge {
|
|||||||
* Handler to access global bridge instance methods
|
* Handler to access global bridge instance methods
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected VeluxBridgeInstance bridgeInstance;
|
protected VeluxBridgeHandler bridgeInstance;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ************************
|
* ************************
|
||||||
@ -90,7 +91,7 @@ public abstract class VeluxBridge {
|
|||||||
* @param bridgeInstance refers to the binding-wide instance for dealing for common informations
|
* @param bridgeInstance refers to the binding-wide instance for dealing for common informations
|
||||||
* like existing actuators and predefined scenes.
|
* like existing actuators and predefined scenes.
|
||||||
*/
|
*/
|
||||||
public VeluxBridge(VeluxBridgeInstance bridgeInstance) {
|
public VeluxBridge(VeluxBridgeHandler bridgeInstance) {
|
||||||
logger.trace("VeluxBridge(constructor,bridgeInstance={}) called.", bridgeInstance);
|
logger.trace("VeluxBridge(constructor,bridgeInstance={}) called.", bridgeInstance);
|
||||||
this.bridgeInstance = bridgeInstance;
|
this.bridgeInstance = bridgeInstance;
|
||||||
logger.trace("VeluxBridge(constructor) done.");
|
logger.trace("VeluxBridge(constructor) done.");
|
||||||
|
@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution
|
* @author Guenther Schreiner - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class VeluxBridgeSetSceneVelocity {
|
public class VeluxBridgeSetSceneVelocity {
|
||||||
private final Logger logger = LoggerFactory.getLogger(VeluxBridgeSetSceneVelocity.class);
|
private final Logger logger = LoggerFactory.getLogger(VeluxBridgeSetSceneVelocity.class);
|
||||||
|
@ -104,4 +104,7 @@ public interface BridgeAPI {
|
|||||||
SetSceneVelocity setSceneVelocity();
|
SetSceneVelocity setSceneVelocity();
|
||||||
|
|
||||||
RunScene runScene();
|
RunScene runScene();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
RunReboot runReboot();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.velux.internal.bridge.common;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <B>Common bridge communication message scheme supported by the </B><I>Velux</I><B> bridge.</B>
|
||||||
|
* <P>
|
||||||
|
* Message semantic will be defined by the implementations according to the different comm paths.
|
||||||
|
* <P>
|
||||||
|
* In addition to the common methods defined by {@link BridgeCommunicationProtocol}
|
||||||
|
* each protocol-specific implementation has to provide the following methods:
|
||||||
|
*
|
||||||
|
* @see BridgeCommunicationProtocol
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class RunReboot implements BridgeCommunicationProtocol {
|
||||||
|
}
|
@ -29,6 +29,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class SetSceneVelocity implements BridgeCommunicationProtocol {
|
public abstract class SetSceneVelocity implements BridgeCommunicationProtocol {
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import org.openhab.binding.velux.internal.bridge.common.SetSceneVelocity;
|
|||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
class JCsetSceneVelocity extends SetSceneVelocity implements JsonBridgeCommunicationProtocol {
|
class JCsetSceneVelocity extends SetSceneVelocity implements JsonBridgeCommunicationProtocol {
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
|
|||||||
import org.openhab.binding.velux.internal.bridge.common.RunProductDiscovery;
|
import org.openhab.binding.velux.internal.bridge.common.RunProductDiscovery;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.RunProductIdentification;
|
import org.openhab.binding.velux.internal.bridge.common.RunProductIdentification;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.RunProductSearch;
|
import org.openhab.binding.velux.internal.bridge.common.RunProductSearch;
|
||||||
|
import org.openhab.binding.velux.internal.bridge.common.RunReboot;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.RunScene;
|
import org.openhab.binding.velux.internal.bridge.common.RunScene;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.SetHouseStatusMonitor;
|
import org.openhab.binding.velux.internal.bridge.common.SetHouseStatusMonitor;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.SetProductLimitation;
|
import org.openhab.binding.velux.internal.bridge.common.SetProductLimitation;
|
||||||
@ -205,4 +206,9 @@ class JsonBridgeAPI implements BridgeAPI {
|
|||||||
public SetSceneVelocity setSceneVelocity() {
|
public SetSceneVelocity setSceneVelocity() {
|
||||||
return jsonSetSceneVelocity;
|
return jsonSetSceneVelocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable RunReboot runReboot() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,9 @@ import java.util.TreeSet;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridge;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridge;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
|
|
||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -84,7 +84,7 @@ public class JsonVeluxBridge extends VeluxBridge {
|
|||||||
*
|
*
|
||||||
* @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
|
* @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
|
||||||
*/
|
*/
|
||||||
public JsonVeluxBridge(VeluxBridgeInstance bridgeInstance) {
|
public JsonVeluxBridge(VeluxBridgeHandler bridgeInstance) {
|
||||||
super(bridgeInstance);
|
super(bridgeInstance);
|
||||||
logger.trace("JsonVeluxBridge(constructor) called.");
|
logger.trace("JsonVeluxBridge(constructor) called.");
|
||||||
bridgeAPI = new JsonBridgeAPI(bridgeInstance);
|
bridgeAPI = new JsonBridgeAPI(bridgeInstance);
|
||||||
|
@ -14,6 +14,7 @@ package org.openhab.binding.velux.internal.bridge.slip;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.GetWLANConfig;
|
import org.openhab.binding.velux.internal.bridge.common.GetWLANConfig;
|
||||||
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
||||||
import org.openhab.binding.velux.internal.things.VeluxGwWLAN;
|
import org.openhab.binding.velux.internal.things.VeluxGwWLAN;
|
||||||
@ -51,8 +52,6 @@ class SCgetWLANConfig extends GetWLANConfig implements SlipBridgeCommunicationPr
|
|||||||
private static final String DESCRIPTION = "Retrieve WLAN configuration";
|
private static final String DESCRIPTION = "Retrieve WLAN configuration";
|
||||||
private static final Command COMMAND = Command.GW_GET_NETWORK_SETUP_REQ;
|
private static final Command COMMAND = Command.GW_GET_NETWORK_SETUP_REQ;
|
||||||
|
|
||||||
private static final String UNSUPPORTED = "*** unsupported-by-current-gateway-firmware ***";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Message Objects
|
* Message Objects
|
||||||
*/
|
*/
|
||||||
@ -118,6 +117,6 @@ class SCgetWLANConfig extends GetWLANConfig implements SlipBridgeCommunicationPr
|
|||||||
public VeluxGwWLAN getWLANConfig() {
|
public VeluxGwWLAN getWLANConfig() {
|
||||||
logger.trace("getWLANConfig() called.");
|
logger.trace("getWLANConfig() called.");
|
||||||
// Enhancement idea: Velux should provide an enhanced API.
|
// Enhancement idea: Velux should provide an enhanced API.
|
||||||
return new VeluxGwWLAN(UNSUPPORTED, UNSUPPORTED);
|
return new VeluxGwWLAN(VeluxBindingConstants.UNKNOWN, VeluxBindingConstants.UNKNOWN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.velux.internal.bridge.slip;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.velux.internal.bridge.common.RunReboot;
|
||||||
|
import org.openhab.binding.velux.internal.bridge.slip.utils.KLF200Response;
|
||||||
|
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||||
|
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol specific bridge communication supported by the Velux bridge:
|
||||||
|
* <B>Reboot Bridge</B>
|
||||||
|
* <P>
|
||||||
|
* Common Message semantic: Communication with the bridge and (optionally) storing returned information within the class
|
||||||
|
* itself.
|
||||||
|
* <P>
|
||||||
|
* As 3rd level class it defines informations how to send query and receive answer through the
|
||||||
|
* {@link org.openhab.binding.velux.internal.bridge.VeluxBridgeProvider VeluxBridgeProvider}
|
||||||
|
* as described by the {@link org.openhab.binding.velux.internal.bridge.slip.SlipBridgeCommunicationProtocol
|
||||||
|
* SlipBridgeCommunicationProtocol}.
|
||||||
|
* <P>
|
||||||
|
* Methods in addition to the mentioned interface:
|
||||||
|
* <UL>
|
||||||
|
* <LI>{@link #runReboot} for rebooting the Velux hub.</LI>
|
||||||
|
* </UL>
|
||||||
|
*
|
||||||
|
* @see RunReboot
|
||||||
|
* @see SlipBridgeCommunicationProtocol
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution.
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
class SCrunReboot extends RunReboot implements SlipBridgeCommunicationProtocol {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(SCrunReboot.class);
|
||||||
|
|
||||||
|
private static final String DESCRIPTION = "Issue the reboot command";
|
||||||
|
private static final Command COMMAND = Command.GW_REBOOT_REQ;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================
|
||||||
|
* Message Objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
private byte[] requestData = new byte[0];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================
|
||||||
|
* Result Objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
private boolean success = false;
|
||||||
|
private boolean finished = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===========================================================
|
||||||
|
* Methods required for interface {@link SlipBridgeCommunicationProtocol}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return DESCRIPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandNumber getRequestCommand() {
|
||||||
|
success = false;
|
||||||
|
finished = false;
|
||||||
|
logger.debug("getRequestCommand() returns {} ({}).", COMMAND.name(), COMMAND.getCommand());
|
||||||
|
return COMMAND.getCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRequestDataAsArrayOfBytes() {
|
||||||
|
return requestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
|
||||||
|
KLF200Response.introLogging(logger, responseCommand, thisResponseData);
|
||||||
|
success = false;
|
||||||
|
finished = false;
|
||||||
|
switch (Command.get(responseCommand)) {
|
||||||
|
case GW_REBOOT_CFM:
|
||||||
|
if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 0)) {
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
success = true;
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
KLF200Response.errorLogging(logger, responseCommand);
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
KLF200Response.outroLogging(logger, success, finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCommunicationFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCommunicationSuccessful() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
// ToDo: THIS MESSAGE EXCHANGE IS AN UNDOCUMENTED FEATURE. Check the updated Velux doc against this implementation.
|
// TODO: THIS MESSAGE EXCHANGE IS AN UNDOCUMENTED FEATURE. Check the updated Velux doc against this implementation.
|
||||||
|
@Deprecated
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
class SCsetSceneVelocity extends SetSceneVelocity implements SlipBridgeCommunicationProtocol {
|
class SCsetSceneVelocity extends SetSceneVelocity implements SlipBridgeCommunicationProtocol {
|
||||||
private final Logger logger = LoggerFactory.getLogger(SCsetSceneVelocity.class);
|
private final Logger logger = LoggerFactory.getLogger(SCsetSceneVelocity.class);
|
||||||
|
@ -31,6 +31,7 @@ import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
|
|||||||
import org.openhab.binding.velux.internal.bridge.common.RunProductDiscovery;
|
import org.openhab.binding.velux.internal.bridge.common.RunProductDiscovery;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.RunProductIdentification;
|
import org.openhab.binding.velux.internal.bridge.common.RunProductIdentification;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.RunProductSearch;
|
import org.openhab.binding.velux.internal.bridge.common.RunProductSearch;
|
||||||
|
import org.openhab.binding.velux.internal.bridge.common.RunReboot;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.RunScene;
|
import org.openhab.binding.velux.internal.bridge.common.RunScene;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.SetHouseStatusMonitor;
|
import org.openhab.binding.velux.internal.bridge.common.SetHouseStatusMonitor;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.SetProductLimitation;
|
import org.openhab.binding.velux.internal.bridge.common.SetProductLimitation;
|
||||||
@ -102,6 +103,7 @@ class SlipBridgeAPI implements BridgeAPI {
|
|||||||
private final SetHouseStatusMonitor slipSetHouseMonitor = new SCsetHouseStatusMonitor();
|
private final SetHouseStatusMonitor slipSetHouseMonitor = new SCsetHouseStatusMonitor();
|
||||||
private final SetProductLimitation slipSetProductLimitation = new SCsetLimitation();
|
private final SetProductLimitation slipSetProductLimitation = new SCsetLimitation();
|
||||||
private final SetSceneVelocity slipSetSceneVelocity = new SCsetSceneVelocity();
|
private final SetSceneVelocity slipSetSceneVelocity = new SCsetSceneVelocity();
|
||||||
|
private final RunReboot slipRunReboot = new SCrunReboot();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@ -210,4 +212,9 @@ class SlipBridgeAPI implements BridgeAPI {
|
|||||||
public SetSceneVelocity setSceneVelocity() {
|
public SetSceneVelocity setSceneVelocity() {
|
||||||
return slipSetSceneVelocity;
|
return slipSetSceneVelocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable RunReboot runReboot() {
|
||||||
|
return slipRunReboot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.velux.internal.bridge.slip;
|
package org.openhab.binding.velux.internal.bridge.slip;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridge;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridge;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
|
|
||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
||||||
import org.openhab.binding.velux.internal.bridge.slip.io.Connection;
|
import org.openhab.binding.velux.internal.bridge.slip.io.Connection;
|
||||||
@ -26,8 +26,8 @@ import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
|||||||
import org.openhab.binding.velux.internal.bridge.slip.utils.SlipEncoding;
|
import org.openhab.binding.velux.internal.bridge.slip.utils.SlipEncoding;
|
||||||
import org.openhab.binding.velux.internal.bridge.slip.utils.SlipRFC1055;
|
import org.openhab.binding.velux.internal.bridge.slip.utils.SlipRFC1055;
|
||||||
import org.openhab.binding.velux.internal.development.Threads;
|
import org.openhab.binding.velux.internal.development.Threads;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
|
|
||||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -35,8 +35,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
/**
|
/**
|
||||||
* SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
|
* SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
|
||||||
* <P>
|
* <P>
|
||||||
* It provides methods for pre- and postcommunication
|
* It provides methods for pre- and post- communication as well as a common method for the real communication.
|
||||||
* as well as a common method for the real communication.
|
|
||||||
* <P>
|
* <P>
|
||||||
* In addition to the generic {@link VeluxBridge} methods, i.e.
|
* In addition to the generic {@link VeluxBridge} methods, i.e.
|
||||||
* <UL>
|
* <UL>
|
||||||
@ -53,9 +52,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
* </UL>
|
* </UL>
|
||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
|
* @author Andrew Fiddian-Green - Refactored (simplified) the message processing loop
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SlipVeluxBridge extends VeluxBridge {
|
public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SlipVeluxBridge.class);
|
private final Logger logger = LoggerFactory.getLogger(SlipVeluxBridge.class);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -100,7 +101,7 @@ public class SlipVeluxBridge extends VeluxBridge {
|
|||||||
*
|
*
|
||||||
* @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
|
* @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
|
||||||
*/
|
*/
|
||||||
public SlipVeluxBridge(VeluxBridgeInstance bridgeInstance) {
|
public SlipVeluxBridge(VeluxBridgeHandler bridgeInstance) {
|
||||||
super(bridgeInstance);
|
super(bridgeInstance);
|
||||||
logger.trace("SlipVeluxBridge(constructor) called.");
|
logger.trace("SlipVeluxBridge(constructor) called.");
|
||||||
bridgeAPI = new SlipBridgeAPI(bridgeInstance);
|
bridgeAPI = new SlipBridgeAPI(bridgeInstance);
|
||||||
@ -153,7 +154,7 @@ public class SlipVeluxBridge extends VeluxBridge {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean bridgeDirectCommunicate(BridgeCommunicationProtocol communication, boolean useAuthentication) {
|
protected boolean bridgeDirectCommunicate(BridgeCommunicationProtocol communication, boolean useAuthentication) {
|
||||||
logger.trace("bridgeDirectCommunicate(BCP: {},{}authenticated) called.", communication.name(),
|
logger.trace("bridgeDirectCommunicate(BCP: {}, {}authenticated) called.", communication.name(),
|
||||||
useAuthentication ? "" : "un");
|
useAuthentication ? "" : "un");
|
||||||
return bridgeDirectCommunicate((SlipBridgeCommunicationProtocol) communication, useAuthentication);
|
return bridgeDirectCommunicate((SlipBridgeCommunicationProtocol) communication, useAuthentication);
|
||||||
}
|
}
|
||||||
@ -181,214 +182,242 @@ public class SlipVeluxBridge extends VeluxBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a client/server communication towards <b>Velux</b> veluxBridge
|
* Initializes a client/server communication towards the Velux Bridge based on the Basic I/O interface
|
||||||
* based on the Basic I/O interface {@link Connection#io} and parameters
|
* {@link Connection#io} and parameters passed as arguments (see below).
|
||||||
* passed as arguments (see below).
|
|
||||||
*
|
*
|
||||||
* @param communication Structure of interface type {@link SlipBridgeCommunicationProtocol} describing the
|
* @param communication a structure of interface type {@link SlipBridgeCommunicationProtocol} describing the
|
||||||
* intended communication, that is request and response interactions as well as appropriate URL
|
* intended communication, that is request and response interactions as well as appropriate URL
|
||||||
* definition.
|
* definition.
|
||||||
* @param useAuthentication boolean flag to decide whether to use authenticated communication.
|
* @param useAuthentication a boolean flag to select whether to use authenticated communication.
|
||||||
* @return <b>success</b> of type boolean which signals the success of the communication.
|
* @return a boolean which in general signals the success of the communication, but in the
|
||||||
|
* special case of receive-only calls, signals if any products were updated during the call
|
||||||
*/
|
*/
|
||||||
private synchronized boolean bridgeDirectCommunicate(SlipBridgeCommunicationProtocol communication,
|
private synchronized boolean bridgeDirectCommunicate(SlipBridgeCommunicationProtocol communication,
|
||||||
boolean useAuthentication) {
|
boolean useAuthentication) {
|
||||||
String host = this.bridgeInstance.veluxBridgeConfiguration().ipAddress;
|
logger.trace("bridgeDirectCommunicate() '{}', {}authenticated", communication.name(),
|
||||||
logger.trace("bridgeDirectCommunicate({},{}authenticated) on {} called.", host, communication.name(),
|
|
||||||
useAuthentication ? "" : "un");
|
useAuthentication ? "" : "un");
|
||||||
|
|
||||||
assert this.bridgeInstance.veluxBridgeConfiguration().protocol.contentEquals("slip");
|
// store common parameters as constants for frequent use
|
||||||
|
final short txCmd = communication.getRequestCommand().toShort();
|
||||||
|
final byte[] txData = communication.getRequestDataAsArrayOfBytes();
|
||||||
|
final Command txEnum = Command.get(txCmd);
|
||||||
|
final String txName = txEnum.toString();
|
||||||
|
final boolean isSequentialEnforced = this.bridgeInstance.veluxBridgeConfiguration().isSequentialEnforced;
|
||||||
|
final boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
|
||||||
|
final long expiryTime = System.currentTimeMillis() + COMMUNICATION_TIMEOUT_MSECS;
|
||||||
|
|
||||||
long communicationStartInMSecs = System.currentTimeMillis();
|
// logger format string
|
||||||
|
final String loggerFmt = String.format("bridgeDirectCommunicate() [%s] %s => {} {} {}",
|
||||||
boolean isSequentialEnforced = this.bridgeInstance.veluxBridgeConfiguration().isSequentialEnforced;
|
this.bridgeInstance.veluxBridgeConfiguration().ipAddress, txName);
|
||||||
boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
|
|
||||||
|
|
||||||
// From parameters
|
|
||||||
short command = communication.getRequestCommand().toShort();
|
|
||||||
byte[] data = communication.getRequestDataAsArrayOfBytes();
|
|
||||||
// For further use at different logging statements
|
|
||||||
String commandString = Command.get(command).toString();
|
|
||||||
|
|
||||||
if (isProtocolTraceEnabled) {
|
if (isProtocolTraceEnabled) {
|
||||||
Threads.findDeadlocked();
|
Threads.findDeadlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("bridgeDirectCommunicate({},{}authenticated) on {} initiated by {}.", host, commandString,
|
logger.debug(loggerFmt, "started =>", Thread.currentThread(), "");
|
||||||
useAuthentication ? "" : "un", Thread.currentThread());
|
|
||||||
boolean success = false;
|
|
||||||
|
|
||||||
communication: do {
|
boolean looping = false;
|
||||||
if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
|
boolean success = false;
|
||||||
logger.warn(
|
boolean sending = false;
|
||||||
"{} bridgeDirectCommunicate({}) on {}: communication handshake failed (unexpected sequence of requests/responses).",
|
boolean rcvonly = false;
|
||||||
VeluxBindingConstants.BINDING_VALUES_SEPARATOR, communication.name(), host);
|
byte[] txPacket = emptyPacket;
|
||||||
|
|
||||||
|
// handling of the requests
|
||||||
|
switch (txEnum) {
|
||||||
|
case GW_OPENHAB_CLOSE:
|
||||||
|
logger.trace(loggerFmt, "shut down command", "=> executing", "");
|
||||||
|
connection.resetConnection();
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GW_OPENHAB_RECEIVEONLY:
|
||||||
|
logger.trace(loggerFmt, "receive-only mode", "=> checking messages", "");
|
||||||
|
if (!connection.isAlive()) {
|
||||||
|
logger.trace(loggerFmt, "no connection", "=> opening", "");
|
||||||
|
looping = true;
|
||||||
|
} else if (connection.isMessageAvailable()) {
|
||||||
|
logger.trace(loggerFmt, "message(s) waiting", "=> start reading", "");
|
||||||
|
looping = true;
|
||||||
|
} else {
|
||||||
|
logger.trace(loggerFmt, "no waiting messages", "=> done", "");
|
||||||
|
}
|
||||||
|
rcvonly = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.trace(loggerFmt, "send mode", "=> preparing command", "");
|
||||||
|
SlipEncoding slipEnc = new SlipEncoding(txCmd, txData);
|
||||||
|
if (!slipEnc.isValid()) {
|
||||||
|
logger.debug(loggerFmt, "slip encoding error", "=> aborting", "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
txPacket = new SlipRFC1055().encode(slipEnc.toMessage());
|
||||||
|
logger.trace(loggerFmt, "command ready", "=> start sending", "");
|
||||||
|
looping = sending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (looping) {
|
||||||
|
// timeout
|
||||||
|
if (System.currentTimeMillis() > expiryTime) {
|
||||||
|
logger.warn(loggerFmt, "process loop time out", "=> aborting", "=> PLEASE REPORT !!");
|
||||||
|
// abort the processing loop
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling
|
// send command (optionally), and receive response
|
||||||
if (Command.get(command) == Command.GW_OPENHAB_CLOSE) {
|
byte[] rxPacket;
|
||||||
logger.trace("bridgeDirectCommunicate(): special command: shutting down connection.");
|
try {
|
||||||
connection.resetConnection();
|
if (sending) {
|
||||||
success = true;
|
if (isProtocolTraceEnabled) {
|
||||||
continue;
|
logger.info("sending command {}", txName);
|
||||||
}
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
// Normal processing
|
logger.trace(loggerFmt, txName, "=> sending data =>", new Packet(txData));
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: working on request {} with {} bytes of data.", host,
|
} else {
|
||||||
commandString, data.length);
|
logger.debug(loggerFmt, txName, "=> sending data length =>", txData.length);
|
||||||
byte[] sendBytes = emptyPacket;
|
}
|
||||||
if (Command.get(command) == Command.GW_OPENHAB_RECEIVEONLY) {
|
|
||||||
logger.trace(
|
|
||||||
"bridgeDirectCommunicate() on {}: special command: determine whether there is any message waiting.",
|
|
||||||
host);
|
|
||||||
logger.trace("bridgeDirectCommunicate(): check for a waiting message.");
|
|
||||||
if (!connection.isMessageAvailable()) {
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: no message waiting, aborting.", host);
|
|
||||||
break communication;
|
|
||||||
}
|
}
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: there is a message waiting.", host);
|
rxPacket = connection.io(this.bridgeInstance, sending ? txPacket : emptyPacket);
|
||||||
} else {
|
// message sent, don't send it again
|
||||||
SlipEncoding t = new SlipEncoding(command, data);
|
sending = false;
|
||||||
if (!t.isValid()) {
|
if (rxPacket.length == 0) {
|
||||||
logger.warn("bridgeDirectCommunicate() on {}: SlipEncoding() failed, aborting.", host);
|
// only log in send mode (in receive-only mode, no response is ok)
|
||||||
|
if (!rcvonly) {
|
||||||
|
logger.debug(loggerFmt, "no response", "=> aborting", "");
|
||||||
|
}
|
||||||
|
// abort the processing loop
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: transportEncoding={}.", host, t.toString());
|
} catch (IOException e) {
|
||||||
sendBytes = new SlipRFC1055().encode(t.toMessage());
|
logger.debug(loggerFmt, "i/o error =>", e.getMessage(), "=> aborting");
|
||||||
|
// abort the processing loop
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
do {
|
|
||||||
if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
|
// RFC1055 decode response
|
||||||
logger.warn("bridgeDirectCommunicate() on {}: receive takes too long. Please report to maintainer.",
|
byte[] rfc1055;
|
||||||
host);
|
try {
|
||||||
break communication;
|
rfc1055 = new SlipRFC1055().decode(rxPacket);
|
||||||
}
|
} catch (ParseException e) {
|
||||||
byte[] receivedPacket;
|
logger.debug(loggerFmt, "parsing error =>", e.getMessage(), "=> aborting");
|
||||||
try {
|
// abort the processing loop
|
||||||
if (sendBytes.length > 0) {
|
break;
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: sending {} bytes.", host, sendBytes.length);
|
}
|
||||||
if (isProtocolTraceEnabled) {
|
|
||||||
logger.info("Sending command {}.", commandString);
|
// SLIP decode response
|
||||||
}
|
SlipEncoding slipEnc = new SlipEncoding(rfc1055);
|
||||||
} else {
|
if (!slipEnc.isValid()) {
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: initiating receive-only.", host);
|
logger.debug(loggerFmt, "slip decode error", "=> aborting", "");
|
||||||
|
// abort the processing loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// attributes of the received (rx) response
|
||||||
|
final short rxCmd = slipEnc.getCommand();
|
||||||
|
final byte[] rxData = slipEnc.getData();
|
||||||
|
final Command rxEnum = Command.get(rxCmd);
|
||||||
|
final String rxName = rxEnum.toString();
|
||||||
|
|
||||||
|
// logging
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(loggerFmt, rxName, "=> received data =>", new Packet(rxData));
|
||||||
|
} else {
|
||||||
|
logger.debug(loggerFmt, rxName, "=> received data length =>", rxData.length);
|
||||||
|
}
|
||||||
|
if (isProtocolTraceEnabled) {
|
||||||
|
logger.info("received message {} => {}", rxName, new Packet(rxData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// handling of the responses
|
||||||
|
switch (rxEnum) {
|
||||||
|
case GW_ERROR_NTF:
|
||||||
|
byte code = rxData[0];
|
||||||
|
switch (code) {
|
||||||
|
case 7: // busy
|
||||||
|
logger.trace(loggerFmt, rxName, getErrorText(code), "=> retrying");
|
||||||
|
sending = true;
|
||||||
|
break;
|
||||||
|
case 12: // authentication failed
|
||||||
|
logger.debug(loggerFmt, rxName, getErrorText(code), "=> aborting");
|
||||||
|
resetAuthentication();
|
||||||
|
looping = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.warn(loggerFmt, rxName, getErrorText(code), "=> aborting");
|
||||||
|
looping = false;
|
||||||
}
|
}
|
||||||
// (Optionally) Send and receive packet.
|
break;
|
||||||
receivedPacket = connection.io(this.bridgeInstance, sendBytes);
|
|
||||||
// Once being sent, it should never be sent again
|
|
||||||
sendBytes = emptyPacket;
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("bridgeDirectCommunicate() on {}: connection.io returns {}", host, e.getMessage());
|
|
||||||
break communication;
|
|
||||||
}
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: received packet {}.", host,
|
|
||||||
new Packet(receivedPacket).toString());
|
|
||||||
byte[] response;
|
|
||||||
try {
|
|
||||||
response = new SlipRFC1055().decode(receivedPacket);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
logger.warn("bridgeDirectCommunicate() on {}: method SlipRFC1055() raised a decoding error: {}.",
|
|
||||||
host, e.getMessage());
|
|
||||||
break communication;
|
|
||||||
}
|
|
||||||
SlipEncoding tr = new SlipEncoding(response);
|
|
||||||
if (!tr.isValid()) {
|
|
||||||
logger.warn("bridgeDirectCommunicate() on {}: method SlipEncoding() raised a decoding error.",
|
|
||||||
host);
|
|
||||||
break communication;
|
|
||||||
}
|
|
||||||
short responseCommand = tr.getCommand();
|
|
||||||
byte[] responseData = tr.getData();
|
|
||||||
logger.debug("bridgeDirectCommunicate() on {}: working on response {} with {} bytes of data.", host,
|
|
||||||
Command.get(responseCommand).toString(), responseData.length);
|
|
||||||
if (isProtocolTraceEnabled) {
|
|
||||||
logger.info("Received answer {}.", Command.get(responseCommand).toString());
|
|
||||||
}
|
|
||||||
// Handle some common (unexpected) answers
|
|
||||||
switch (Command.get(responseCommand)) {
|
|
||||||
case GW_NODE_INFORMATION_CHANGED_NTF:
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: received GW_NODE_INFORMATION_CHANGED_NTF.",
|
|
||||||
host);
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
|
|
||||||
continue;
|
|
||||||
case GW_NODE_STATE_POSITION_CHANGED_NTF:
|
|
||||||
logger.trace(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_NODE_STATE_POSITION_CHANGED_NTF, special processing of this packet.",
|
|
||||||
host);
|
|
||||||
SCgetHouseStatus receiver = new SCgetHouseStatus();
|
|
||||||
receiver.setResponse(responseCommand, responseData, isSequentialEnforced);
|
|
||||||
if (receiver.isCommunicationSuccessful()) {
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: existingProducts().update() called.", host);
|
|
||||||
bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
|
|
||||||
receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
|
|
||||||
}
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
|
|
||||||
continue;
|
|
||||||
case GW_ERROR_NTF:
|
|
||||||
switch (responseData[0]) {
|
|
||||||
case 0:
|
|
||||||
logger.warn(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF on {} (Not further defined error), aborting.",
|
|
||||||
host, commandString);
|
|
||||||
break communication;
|
|
||||||
case 1:
|
|
||||||
logger.warn(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Unknown Command or command is not accepted at this state) on {}, aborting.",
|
|
||||||
host, commandString);
|
|
||||||
break communication;
|
|
||||||
case 2:
|
|
||||||
logger.warn(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (ERROR on Frame Structure) on {}, aborting.",
|
|
||||||
host, commandString);
|
|
||||||
break communication;
|
|
||||||
case 7:
|
|
||||||
logger.trace(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Busy. Try again later) on {}, retrying.",
|
|
||||||
host, commandString);
|
|
||||||
sendBytes = emptyPacket;
|
|
||||||
continue;
|
|
||||||
case 8:
|
|
||||||
logger.warn(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Bad system table index) on {}, aborting.",
|
|
||||||
host, commandString);
|
|
||||||
break communication;
|
|
||||||
case 12:
|
|
||||||
logger.warn(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Not authenticated) on {}, aborting.",
|
|
||||||
host, commandString);
|
|
||||||
resetAuthentication();
|
|
||||||
break communication;
|
|
||||||
default:
|
|
||||||
logger.warn(
|
|
||||||
"bridgeDirectCommunicate() on {}: received GW_ERROR_NTF ({}) on {}, aborting.",
|
|
||||||
host, responseData[0], commandString);
|
|
||||||
break communication;
|
|
||||||
}
|
|
||||||
case GW_ACTIVATION_LOG_UPDATED_NTF:
|
|
||||||
logger.info("bridgeDirectCommunicate() on {}: received GW_ACTIVATION_LOG_UPDATED_NTF.", host);
|
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case GW_COMMAND_RUN_STATUS_NTF:
|
case GW_NODE_INFORMATION_CHANGED_NTF:
|
||||||
case GW_COMMAND_REMAINING_TIME_NTF:
|
case GW_ACTIVATION_LOG_UPDATED_NTF:
|
||||||
case GW_SESSION_FINISHED_NTF:
|
logger.trace(loggerFmt, rxName, "=> ignorable command", "=> continuing");
|
||||||
if (!isSequentialEnforced) {
|
break;
|
||||||
logger.trace(
|
|
||||||
"bridgeDirectCommunicate() on {}: response ignored due to activated parallelism, continue with receiving.",
|
|
||||||
host);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
case GW_NODE_STATE_POSITION_CHANGED_NTF:
|
||||||
}
|
logger.trace(loggerFmt, rxName, "=> special command", "=> starting");
|
||||||
logger.trace("bridgeDirectCommunicate() on {}: passes back command {} and data {}.", host,
|
SCgetHouseStatus receiver = new SCgetHouseStatus();
|
||||||
new CommandNumber(responseCommand).toString(), new Packet(responseData).toString());
|
receiver.setResponse(rxCmd, rxData, isSequentialEnforced);
|
||||||
communication.setResponse(responseCommand, responseData, isSequentialEnforced);
|
if (receiver.isCommunicationSuccessful()) {
|
||||||
} while (!communication.isCommunicationFinished());
|
bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
|
||||||
success = communication.isCommunicationSuccessful();
|
receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
|
||||||
} while (false); // communication
|
logger.trace(loggerFmt, rxName, "=> special command", "=> product updated");
|
||||||
logger.debug("bridgeDirectCommunicate({}) on {}: returns {}.", commandString, host,
|
if (rcvonly) {
|
||||||
success ? "success" : "failure");
|
// receive-only: return success to confirm that product(s) were updated
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.trace(loggerFmt, rxName, "=> special command", "=> continuing");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GW_COMMAND_RUN_STATUS_NTF:
|
||||||
|
case GW_COMMAND_REMAINING_TIME_NTF:
|
||||||
|
case GW_SESSION_FINISHED_NTF:
|
||||||
|
if (!isSequentialEnforced) {
|
||||||
|
logger.trace(loggerFmt, rxName, "=> parallelism allowed", "=> continuing");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logger.trace(loggerFmt, rxName, "=> serialism enforced", "=> default processing");
|
||||||
|
// fall through => execute default processing
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.trace(loggerFmt, rxName, "=> applying data length =>", rxData.length);
|
||||||
|
communication.setResponse(rxCmd, rxData, isSequentialEnforced);
|
||||||
|
looping = !communication.isCommunicationFinished();
|
||||||
|
success = communication.isCommunicationSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// in receive-only mode 'failure` just means that no products were updated, so don't log it as a failure..
|
||||||
|
logger.debug(loggerFmt, "finished", "=>", ((success || rcvonly) ? "success" : "failure"));
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return text description of potential GW_ERROR_NTF error codes, for logging purposes
|
||||||
|
*
|
||||||
|
* @param errCode is the GW_ERROR_NTF error code
|
||||||
|
* @return the description message
|
||||||
|
*/
|
||||||
|
private static String getErrorText(byte errCode) {
|
||||||
|
switch (errCode) {
|
||||||
|
case 0:
|
||||||
|
return "=> (0) not further defined error";
|
||||||
|
case 1:
|
||||||
|
return "=> (1) unknown command or command is not accepted at this state";
|
||||||
|
case 2:
|
||||||
|
return "=> (2) error on frame structure";
|
||||||
|
case 7:
|
||||||
|
return "=> (7) busy, try again later";
|
||||||
|
case 8:
|
||||||
|
return "=> (8) bad system table index";
|
||||||
|
case 12:
|
||||||
|
return "=> (12) not authenticated";
|
||||||
|
}
|
||||||
|
return String.format("=> (%d) unknown error", errCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.velux.internal.bridge.slip.io;
|
package org.openhab.binding.velux.internal.bridge.slip.io;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
|
|
||||||
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
||||||
|
import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class Connection {
|
public class Connection implements Closeable {
|
||||||
private final Logger logger = LoggerFactory.getLogger(Connection.class);
|
private final Logger logger = LoggerFactory.getLogger(Connection.class);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -76,8 +79,10 @@ public class Connection {
|
|||||||
* @throws java.net.ConnectException in case of unrecoverable communication failures.
|
* @throws java.net.ConnectException in case of unrecoverable communication failures.
|
||||||
* @throws java.io.IOException in case of continuous communication I/O failures.
|
* @throws java.io.IOException in case of continuous communication I/O failures.
|
||||||
*/
|
*/
|
||||||
public synchronized byte[] io(VeluxBridgeInstance bridgeInstance, byte[] request)
|
public synchronized byte[] io(VeluxBridgeHandler bridgeInstance, byte[] request)
|
||||||
throws ConnectException, IOException {
|
throws ConnectException, IOException {
|
||||||
|
VeluxBridgeConfiguration cfg = bridgeInstance.veluxBridgeConfiguration();
|
||||||
|
host = cfg.ipAddress;
|
||||||
logger.trace("io() on {}: called.", host);
|
logger.trace("io() on {}: called.", host);
|
||||||
|
|
||||||
lastCommunicationInMSecs = System.currentTimeMillis();
|
lastCommunicationInMSecs = System.currentTimeMillis();
|
||||||
@ -89,15 +94,11 @@ public class Connection {
|
|||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
if (!connectivity.isReady()) {
|
if (!connectivity.isReady()) {
|
||||||
|
// dispose old connectivity class instances (if any)
|
||||||
|
resetConnection();
|
||||||
try {
|
try {
|
||||||
// From configuration
|
logger.trace("io() on {}: connecting to port {}", cfg.ipAddress, cfg.tcpPort);
|
||||||
host = bridgeInstance.veluxBridgeConfiguration().ipAddress;
|
connectivity = new SSLconnection(bridgeInstance);
|
||||||
int port = bridgeInstance.veluxBridgeConfiguration().tcpPort;
|
|
||||||
int timeoutMsecs = bridgeInstance.veluxBridgeConfiguration().timeoutMsecs;
|
|
||||||
|
|
||||||
logger.trace("io() on {}: connecting to port {}", host, port);
|
|
||||||
connectivity = new SSLconnection(host, port);
|
|
||||||
connectivity.setTimeout(timeoutMsecs);
|
|
||||||
} catch (ConnectException ce) {
|
} catch (ConnectException ce) {
|
||||||
throw new ConnectException(String
|
throw new ConnectException(String
|
||||||
.format("raised a non-recoverable error during connection setup: %s", ce.getMessage()));
|
.format("raised a non-recoverable error during connection setup: %s", ce.getMessage()));
|
||||||
@ -107,7 +108,8 @@ public class Connection {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (request.length > 0) {
|
boolean sending = request.length > 0;
|
||||||
|
if (sending) {
|
||||||
try {
|
try {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("io() on {}: sending packet with {} bytes: {}", host, request.length,
|
logger.trace("io() on {}: sending packet with {} bytes: {}", host, request.length,
|
||||||
@ -122,22 +124,15 @@ public class Connection {
|
|||||||
logger.info("io() on {}: raised an error during sending: {}.", host, e.getMessage());
|
logger.info("io() on {}: raised an error during sending: {}.", host, e.getMessage());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give the bridge some time to breathe
|
|
||||||
if (bridgeInstance.veluxBridgeConfiguration().timeoutMsecs > 0) {
|
|
||||||
logger.trace("io() on {}: wait time {} msecs.", host,
|
|
||||||
bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
|
|
||||||
try {
|
|
||||||
Thread.sleep(bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
|
|
||||||
} catch (InterruptedException ie) {
|
|
||||||
logger.trace("io() on {}: wait interrupted.", host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
byte[] packet = new byte[0];
|
byte[] packet = new byte[0];
|
||||||
logger.trace("io() on {}: receiving bytes.", host);
|
logger.trace("io() on {}: receiving bytes.", host);
|
||||||
if (connectivity.isReady()) {
|
if (connectivity.isReady()) {
|
||||||
packet = connectivity.receive();
|
packet = connectivity.receive();
|
||||||
|
// in receive-only mode, a zero length response packet is NOT a timeout
|
||||||
|
if (sending && (packet.length == 0)) {
|
||||||
|
throw new SocketTimeoutException("read time out after send");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("io() on {}: received packet with {} bytes: {}", host, packet.length,
|
logger.trace("io() on {}: received packet with {} bytes: {}", host, packet.length,
|
||||||
@ -168,9 +163,7 @@ public class Connection {
|
|||||||
bridgeInstance.veluxBridgeConfiguration().retries);
|
bridgeInstance.veluxBridgeConfiguration().retries);
|
||||||
}
|
}
|
||||||
logger.trace("io() on {}: shutting down connection.", host);
|
logger.trace("io() on {}: shutting down connection.", host);
|
||||||
if (connectivity.isReady()) {
|
resetConnection();
|
||||||
connectivity.close();
|
|
||||||
}
|
|
||||||
logger.trace("io() on {}: finishes with failure by throwing exception.", host);
|
logger.trace("io() on {}: finishes with failure by throwing exception.", host);
|
||||||
throw lastIOE;
|
throw lastIOE;
|
||||||
}
|
}
|
||||||
@ -192,17 +185,13 @@ public class Connection {
|
|||||||
*/
|
*/
|
||||||
public synchronized boolean isMessageAvailable() {
|
public synchronized boolean isMessageAvailable() {
|
||||||
logger.trace("isMessageAvailable() on {}: called.", host);
|
logger.trace("isMessageAvailable() on {}: called.", host);
|
||||||
try {
|
if (!connectivity.isReady()) {
|
||||||
if ((connectivity.isReady()) && (connectivity.available())) {
|
logger.trace("isMessageAvailable() on {}: lost connection, there may be messages", host);
|
||||||
logger.trace("isMessageAvailable() on {}: there is a message waiting.", host);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.trace("isMessageAvailable() on {}: lost connection due to {}.", host, e.getMessage());
|
|
||||||
resetConnection();
|
|
||||||
}
|
}
|
||||||
logger.trace("isMessageAvailable() on {}: no message waiting.", host);
|
boolean result = connectivity.available();
|
||||||
return false;
|
logger.trace("isMessageAvailable() on {}: there are {}messages waiting.", host, result ? "" : "no ");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -237,4 +226,9 @@ public class Connection {
|
|||||||
}
|
}
|
||||||
logger.trace("resetConnection() on {}: done.", host);
|
logger.trace("resetConnection() on {}: done.", host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
resetConnection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,124 +12,222 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.velux.internal.bridge.slip.io;
|
package org.openhab.binding.velux.internal.bridge.slip.io;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an extension of {@link java.io.DataInputStream}, which adds timeouts to receive operation.
|
* This is an wrapper around {@link java.io.InputStream} to support socket receive operations.
|
||||||
* <P>
|
*
|
||||||
* A data input stream lets an application read primitive Java data
|
* It implements a secondary polling thread to asynchronously read bytes from the socket input stream into a buffer. And
|
||||||
* types from an underlying input stream in a machine-independent
|
* it parses the bytes into SLIP messages, which are placed on a message queue. Callers can access the SLIP messages in
|
||||||
* way. An application uses a data output stream to write data that
|
* this queue independently from the polling thread.
|
||||||
* can later be read by a data input stream.
|
|
||||||
* <p>
|
|
||||||
* For an in-depth discussion, see:
|
|
||||||
* https://stackoverflow.com/questions/804951/is-it-possible-to-read-from-a-inputstream-with-a-timeout
|
|
||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
|
* @author Andrew Fiddian-Green - Complete rewrite using asynchronous polling thread.
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
class DataInputStreamWithTimeout extends DataInputStream {
|
class DataInputStreamWithTimeout implements Closeable {
|
||||||
|
|
||||||
/*
|
private static final int QUEUE_SIZE = 512;
|
||||||
* ***************************
|
private static final int BUFFER_SIZE = 512;
|
||||||
* ***** Private Objects *****
|
private static final int SLEEP_INTERVAL_MSECS = 50;
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
// special character that marks the first and last byte of a slip message
|
||||||
* Executor for asynchronous read command
|
private static final byte SLIP_MARK = (byte) 0xc0;
|
||||||
*/
|
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
|
||||||
|
|
||||||
/**
|
private final Logger logger = LoggerFactory.getLogger(DataInputStreamWithTimeout.class);
|
||||||
* Creates a DataInputStreamWithTimeout that uses the specified
|
|
||||||
* underlying DataInputStream.
|
|
||||||
*
|
|
||||||
* @param in the specified input stream
|
|
||||||
*/
|
|
||||||
public DataInputStreamWithTimeout(InputStream in) {
|
|
||||||
super(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private final Queue<byte[]> slipMessageQueue = new ConcurrentLinkedQueue<>();
|
||||||
* Reads up to <code>len</code> bytes of data from the contained
|
|
||||||
* input stream into an array of bytes. An attempt is made to read
|
private InputStream inputStream;
|
||||||
* as many as <code>len</code> bytes, but a smaller number may be read,
|
|
||||||
* possibly zero. The number of bytes actually read is returned as an
|
private @Nullable String pollException = null;
|
||||||
* integer.
|
private @Nullable Poller pollRunner = null;
|
||||||
*
|
private ExecutorService executor;
|
||||||
* <p>
|
|
||||||
* This method blocks until input data is available, end of file is
|
private class Poller implements Callable<Boolean> {
|
||||||
* detected, or an exception is thrown <B>until</B> the given timeout.
|
|
||||||
*
|
private boolean interrupted = false;
|
||||||
* <p>
|
|
||||||
* If <code>len</code> is zero, then no bytes are read and
|
public void interrupt() {
|
||||||
* <code>0</code> is returned; otherwise, there is an attempt to read at
|
interrupted = true;
|
||||||
* least one byte. If no byte is available because the stream is at end of
|
}
|
||||||
* file, the value <code>-1</code> is returned; otherwise, at least one
|
|
||||||
* byte is read and stored into <code>b</code>.
|
/**
|
||||||
*
|
* Task that loops to read bytes from {@link InputStream} and build SLIP packets from them. The SLIP packets are
|
||||||
* <p>
|
* placed in a {@link ConcurrentLinkedQueue}. It loops continuously until 'interrupt()' or 'Thread.interrupt()'
|
||||||
* The first byte read is stored into element <code>b[off]</code>, the
|
* are called when terminates early after the next socket read timeout.
|
||||||
* next one into <code>b[off+1]</code>, and so on. The number of bytes read
|
*/
|
||||||
* is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
|
@Override
|
||||||
* bytes actually read; these bytes will be stored in elements
|
public Boolean call() throws Exception {
|
||||||
* <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
|
byte[] buf = new byte[BUFFER_SIZE];
|
||||||
* leaving elements <code>b[off+</code><i>k</i><code>]</code> through
|
byte byt;
|
||||||
* <code>b[off+len-1]</code> unaffected.
|
int i = 0;
|
||||||
*
|
|
||||||
* <p>
|
// clean start, no exception, empty queue
|
||||||
* In every case, elements <code>b[0]</code> through
|
pollException = null;
|
||||||
* <code>b[off]</code> and elements <code>b[off+len]</code> through
|
slipMessageQueue.clear();
|
||||||
* <code>b[b.length-1]</code> are unaffected.
|
|
||||||
*
|
// loop forever or until internally or externally interrupted
|
||||||
* @param b the buffer into which the data is read.
|
while ((!interrupted) && (!Thread.interrupted())) {
|
||||||
* @param off the start offset in the destination array <code>b</code>
|
try {
|
||||||
* @param len the maximum number of bytes read.
|
buf[i] = byt = (byte) inputStream.read();
|
||||||
* @param timeoutMSecs the maximum duration of this read before throwing a TimeoutException.
|
if (byt == SLIP_MARK) {
|
||||||
* @return the total number of bytes read into the buffer, or
|
if (i > 0) {
|
||||||
* <code>-1</code> if there is no more data because the end
|
// the minimal slip message is 7 bytes [MM PP LL CC CC KK MM]
|
||||||
* of the stream has been reached.
|
if ((i > 5) && (buf[0] == SLIP_MARK)) {
|
||||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
slipMessageQueue.offer(Arrays.copyOfRange(buf, 0, i + 1));
|
||||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
if (slipMessageQueue.size() > QUEUE_SIZE) {
|
||||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
logger.warn("pollRunner() => slip message queue overflow => PLEASE REPORT !!");
|
||||||
* <code>b.length - off</code>
|
slipMessageQueue.poll();
|
||||||
* @exception IOException if the first byte cannot be read for any reason
|
}
|
||||||
* other than end of file, the stream has been closed and the underlying
|
}
|
||||||
* input stream does not support reading after close, or another I/O
|
i = 0;
|
||||||
* error occurs. Additionally it will occur when the timeout happens.
|
buf[0] = SLIP_MARK;
|
||||||
* @see java.io.DataInputStream#read
|
continue;
|
||||||
*/
|
}
|
||||||
public synchronized int read(byte b[], int off, int len, int timeoutMSecs) throws IOException {
|
}
|
||||||
// Definition of Method which encapsulates the Read of data
|
if (++i >= BUFFER_SIZE) {
|
||||||
Callable<Integer> readTask = new Callable<Integer>() {
|
i = 0;
|
||||||
@Override
|
}
|
||||||
public Integer call() throws IOException {
|
} catch (SocketTimeoutException e) {
|
||||||
return in.read(b, off, len);
|
// socket read time outs are OK => keep on polling
|
||||||
|
continue;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// any other exception => stop polling
|
||||||
|
String msg = e.getMessage();
|
||||||
|
pollException = msg != null ? msg : "Generic IOException";
|
||||||
|
logger.debug("pollRunner() stopping '{}'", pollException);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
try {
|
// we only get here if shutdown or an error occurs so free ourself so we can be recreated again
|
||||||
Future<Integer> future = executor.submit(readTask);
|
pollRunner = null;
|
||||||
return future.get(timeoutMSecs, TimeUnit.MILLISECONDS);
|
return true;
|
||||||
} catch (RejectedExecutionException e) {
|
|
||||||
throw new IOException("executor failed", e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new IOException("execution failed", e);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new IOException("read interrupted", e);
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
throw new IOException("read timeout", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there was an exception on the polling loop task and if so, throw it back on the caller thread.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void throwIfPollException() throws IOException {
|
||||||
|
if (pollException != null) {
|
||||||
|
logger.debug("passPollException() polling loop exception {}", pollException);
|
||||||
|
throw new IOException(pollException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link DataInputStreamWithTimeout} as a wrapper around the specified underlying {@link InputStream}
|
||||||
|
*
|
||||||
|
* @param stream the specified input stream
|
||||||
|
* @param bridge the actual Bridge Thing instance
|
||||||
|
*/
|
||||||
|
public DataInputStreamWithTimeout(InputStream stream, VeluxBridgeHandler bridge) {
|
||||||
|
inputStream = stream;
|
||||||
|
executor = Executors.newSingleThreadExecutor(bridge.getThreadFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden method of {@link Closeable} interface. Stops the polling thread.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
stopPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and removes the next available SLIP message from the queue. If the queue is empty, continue polling
|
||||||
|
* until either a message is found, or the timeout expires.
|
||||||
|
*
|
||||||
|
* @param timeoutMSecs the timeout period in milliseconds.
|
||||||
|
* @return the next SLIP message if there is one on the queue, or any empty byte[] array if not.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public synchronized byte[] readSlipMessage(int timeoutMSecs) throws IOException {
|
||||||
|
startPolling();
|
||||||
|
int i = (timeoutMSecs / SLEEP_INTERVAL_MSECS) + 1;
|
||||||
|
while (i-- >= 0) {
|
||||||
|
try {
|
||||||
|
byte[] slip = slipMessageQueue.remove();
|
||||||
|
logger.trace("readSlipMessage() => return slip message");
|
||||||
|
return slip;
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
// queue empty, wait and continue
|
||||||
|
}
|
||||||
|
throwIfPollException();
|
||||||
|
try {
|
||||||
|
Thread.sleep(SLEEP_INTERVAL_MSECS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("readSlipMessage() => thread interrupt");
|
||||||
|
throw new IOException("Thread Interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("readSlipMessage() => no slip message after {}mS => time out", timeoutMSecs);
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of incoming messages in the queue
|
||||||
|
*
|
||||||
|
* @return the number of incoming messages in the queue
|
||||||
|
*/
|
||||||
|
public int available() {
|
||||||
|
int size = slipMessageQueue.size();
|
||||||
|
logger.trace("available() => slip message count {}", size);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the queue
|
||||||
|
*/
|
||||||
|
public void flush() {
|
||||||
|
logger.trace("flush() called");
|
||||||
|
slipMessageQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the polling task
|
||||||
|
*/
|
||||||
|
private void startPolling() {
|
||||||
|
Poller pollRunner = this.pollRunner;
|
||||||
|
if (pollRunner == null) {
|
||||||
|
logger.trace("startPolling()");
|
||||||
|
pollRunner = this.pollRunner = new Poller();
|
||||||
|
executor.submit(pollRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the polling task
|
||||||
|
*/
|
||||||
|
private void stopPolling() {
|
||||||
|
Poller pollRunner = this.pollRunner;
|
||||||
|
if (pollRunner != null) {
|
||||||
|
logger.trace("stopPolling()");
|
||||||
|
pollRunner.interrupt();
|
||||||
|
this.pollRunner = null;
|
||||||
|
}
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.velux.internal.bridge.slip.io;
|
package org.openhab.binding.velux.internal.bridge.slip.io;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@ -29,6 +31,8 @@ import javax.net.ssl.X509TrustManager;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
||||||
|
import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
|
||||||
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
class SSLconnection {
|
class SSLconnection implements Closeable {
|
||||||
private final Logger logger = LoggerFactory.getLogger(SSLconnection.class);
|
private final Logger logger = LoggerFactory.getLogger(SSLconnection.class);
|
||||||
|
|
||||||
// Public definition
|
// Public definition
|
||||||
@ -62,13 +66,12 @@ class SSLconnection {
|
|||||||
* ***** Private Objects *****
|
* ***** Private Objects *****
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private static final int CONNECTION_BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
private boolean ready = false;
|
|
||||||
private @Nullable SSLSocket socket;
|
private @Nullable SSLSocket socket;
|
||||||
private @Nullable DataOutputStream dOut;
|
private @Nullable DataOutputStream dOut;
|
||||||
private @Nullable DataInputStreamWithTimeout dIn;
|
private @Nullable DataInputStreamWithTimeout dIn;
|
||||||
private int ioTimeoutMSecs = 60000;
|
|
||||||
|
private int readTimeoutMSecs = 2000;
|
||||||
|
private int connTimeoutMSecs = 6000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fake trust manager to suppress any certificate errors,
|
* Fake trust manager to suppress any certificate errors,
|
||||||
@ -102,21 +105,18 @@ class SSLconnection {
|
|||||||
*/
|
*/
|
||||||
SSLconnection() {
|
SSLconnection() {
|
||||||
logger.debug("SSLconnection() called.");
|
logger.debug("SSLconnection() called.");
|
||||||
ready = false;
|
|
||||||
logger.trace("SSLconnection() finished.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to setup and establish a connection.
|
* Constructor to setup and establish a connection.
|
||||||
*
|
*
|
||||||
* @param host as String describing the Service Access Point location i.e. hostname.
|
* @param bridgeInstance the actual Bridge Thing instance
|
||||||
* @param port as String describing the Service Access Point location i.e. TCP port.
|
|
||||||
* @throws java.net.ConnectException in case of unrecoverable communication failures.
|
* @throws java.net.ConnectException in case of unrecoverable communication failures.
|
||||||
* @throws java.io.IOException in case of continuous communication I/O failures.
|
* @throws java.io.IOException in case of continuous communication I/O failures.
|
||||||
* @throws java.net.UnknownHostException in case of continuous communication I/O failures.
|
* @throws java.net.UnknownHostException in case of continuous communication I/O failures.
|
||||||
*/
|
*/
|
||||||
SSLconnection(String host, int port) throws ConnectException, IOException, UnknownHostException {
|
SSLconnection(VeluxBridgeHandler bridgeInstance) throws ConnectException, IOException, UnknownHostException {
|
||||||
logger.debug("SSLconnection({},{}) called.", host, port);
|
logger.debug("SSLconnection() called");
|
||||||
logger.info("Starting {} bridge connection.", VeluxBindingConstants.BINDING_ID);
|
logger.info("Starting {} bridge connection.", VeluxBindingConstants.BINDING_ID);
|
||||||
SSLContext ctx = null;
|
SSLContext ctx = null;
|
||||||
try {
|
try {
|
||||||
@ -126,15 +126,27 @@ class SSLconnection {
|
|||||||
throw new IOException(String.format("create of an empty trust store failed: %s.", e.getMessage()));
|
throw new IOException(String.format("create of an empty trust store failed: %s.", e.getMessage()));
|
||||||
}
|
}
|
||||||
logger.trace("SSLconnection(): creating socket...");
|
logger.trace("SSLconnection(): creating socket...");
|
||||||
// Just for avoidance of Potential null pointer access
|
SSLSocket socket = this.socket = (SSLSocket) ctx.getSocketFactory().createSocket();
|
||||||
SSLSocket socketX = (SSLSocket) ctx.getSocketFactory().createSocket(host, port);
|
if (socket != null) {
|
||||||
logger.trace("SSLconnection(): starting SSL handshake...");
|
VeluxBridgeConfiguration cfg = bridgeInstance.veluxBridgeConfiguration();
|
||||||
if (socketX != null) {
|
readTimeoutMSecs = cfg.timeoutMsecs;
|
||||||
socketX.startHandshake();
|
connTimeoutMSecs = Math.max(connTimeoutMSecs, readTimeoutMSecs);
|
||||||
dOut = new DataOutputStream(socketX.getOutputStream());
|
// use longer timeout when establishing the connection
|
||||||
dIn = new DataInputStreamWithTimeout(socketX.getInputStream());
|
socket.setSoTimeout(connTimeoutMSecs);
|
||||||
ready = true;
|
socket.setKeepAlive(true);
|
||||||
socket = socketX;
|
socket.connect(new InetSocketAddress(cfg.ipAddress, cfg.tcpPort), connTimeoutMSecs);
|
||||||
|
logger.trace("SSLconnection(): starting SSL handshake...");
|
||||||
|
socket.startHandshake();
|
||||||
|
// use shorter timeout for normal communications
|
||||||
|
socket.setSoTimeout(readTimeoutMSecs);
|
||||||
|
dOut = new DataOutputStream(socket.getOutputStream());
|
||||||
|
dIn = new DataInputStreamWithTimeout(socket.getInputStream(), bridgeInstance);
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(
|
||||||
|
"SSLconnection(): connected... (ip={}, port={}, sslTimeout={}, soTimeout={}, soKeepAlive={})",
|
||||||
|
cfg.ipAddress, cfg.tcpPort, connTimeoutMSecs, socket.getSoTimeout(),
|
||||||
|
socket.getKeepAlive() ? "true" : "false");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.trace("SSLconnection() finished.");
|
logger.trace("SSLconnection() finished.");
|
||||||
}
|
}
|
||||||
@ -150,38 +162,27 @@ class SSLconnection {
|
|||||||
* @return <b>ready</b> as boolean for an established connection.
|
* @return <b>ready</b> as boolean for an established connection.
|
||||||
*/
|
*/
|
||||||
synchronized boolean isReady() {
|
synchronized boolean isReady() {
|
||||||
return ready;
|
return socket != null && dIn != null && dOut != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to pass a message towards the bridge.
|
* Method to pass a message towards the bridge. This method gets called when we are initiating a new SLIP
|
||||||
* This method gets called when we are initiating a new SLIP transaction.
|
* transaction.
|
||||||
* <p>
|
|
||||||
* Note that DataOutputStream and DataInputStream are buffered I/O's. The SLIP protocol requires that prior requests
|
|
||||||
* should have been fully sent over the socket, and their responses should have been fully read from the buffer
|
|
||||||
* before the next request is initiated. i.e. Both read and write buffers should already be empty. Nevertheless,
|
|
||||||
* just in case, we do the following..
|
|
||||||
* <p>
|
|
||||||
* 1) Flush from the read buffer any orphan response data that may have been left over from prior transactions, and
|
|
||||||
* 2) Flush the write buffer directly to the socket to ensure that any exceptions are raised immediately, and the
|
|
||||||
* KLF starts work immediately
|
|
||||||
*
|
*
|
||||||
* @param packet as Array of bytes to be transmitted towards the bridge via the established connection.
|
* @param <b>packet</b> as Array of bytes to be transmitted towards the bridge via the established connection.
|
||||||
* @throws java.io.IOException in case of a communication I/O failure, and sets 'ready' = false
|
* @throws java.io.IOException in case of a communication I/O failure
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
synchronized void send(byte[] packet) throws IOException {
|
synchronized void send(byte[] packet) throws IOException {
|
||||||
logger.trace("send() called, writing {} bytes.", packet.length);
|
logger.trace("send() called, writing {} bytes.", packet.length);
|
||||||
|
DataOutputStream dOutX = dOut;
|
||||||
|
if (dOutX == null) {
|
||||||
|
throw new IOException("DataOutputStream not initialised");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (!ready || (dOut == null) || (dIn == null)) {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
// flush the read buffer if (exceptionally) there is orphan response data in it
|
|
||||||
flushReadBufffer();
|
|
||||||
// copy packet data to the write buffer
|
// copy packet data to the write buffer
|
||||||
dOut.write(packet, 0, packet.length);
|
dOutX.write(packet, 0, packet.length);
|
||||||
// force the write buffer data to be written to the socket
|
// force the write buffer data to be written to the socket
|
||||||
dOut.flush();
|
dOutX.flush();
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (byte b : packet) {
|
for (byte b : packet) {
|
||||||
@ -190,7 +191,7 @@ class SSLconnection {
|
|||||||
logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
|
logger.trace("send() finished after having send {} bytes: {}", packet.length, sb.toString());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ready = false;
|
close();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,47 +199,43 @@ class SSLconnection {
|
|||||||
/**
|
/**
|
||||||
* Method to verify that there is message from the bridge.
|
* Method to verify that there is message from the bridge.
|
||||||
*
|
*
|
||||||
* @return <b>true</b> if there are any bytes ready to be queried using {@link SSLconnection#receive}.
|
* @return <b>true</b> if there are any messages ready to be queried using {@link SSLconnection#receive}.
|
||||||
* @throws java.io.IOException in case of a communication I/O failure.
|
|
||||||
*/
|
*/
|
||||||
synchronized boolean available() throws IOException {
|
synchronized boolean available() {
|
||||||
logger.trace("available() called.");
|
logger.trace("available() called.");
|
||||||
if (!ready || (dIn == null)) {
|
DataInputStreamWithTimeout dInX = dIn;
|
||||||
throw new IOException();
|
if (dInX != null) {
|
||||||
|
int availableMessages = dInX.available();
|
||||||
|
logger.trace("available(): found {} messages ready to be read (> 0 means true).", availableMessages);
|
||||||
|
return availableMessages > 0;
|
||||||
}
|
}
|
||||||
@SuppressWarnings("null")
|
return false;
|
||||||
int availableBytes = dIn.available();
|
|
||||||
logger.trace("available(): found {} bytes ready to be read (> 0 means true).", availableBytes);
|
|
||||||
return availableBytes > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get a message from the bridge.
|
* Method to get a message from the bridge.
|
||||||
*
|
*
|
||||||
* @return <b>packet</b> as Array of bytes as received from the bridge via the established connection.
|
* @return <b>packet</b> as Array of bytes as received from the bridge via the established connection.
|
||||||
* @throws java.io.IOException in case of a communication I/O failure, and sets 'ready' = false
|
* @throws java.io.IOException in case of a communication I/O failure.
|
||||||
*/
|
*/
|
||||||
synchronized byte[] receive() throws IOException {
|
synchronized byte[] receive() throws IOException {
|
||||||
logger.trace("receive() called.");
|
logger.trace("receive() called.");
|
||||||
|
DataInputStreamWithTimeout dInX = dIn;
|
||||||
|
if (dInX == null) {
|
||||||
|
throw new IOException("DataInputStreamWithTimeout not initialised");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (!ready || (dIn == null)) {
|
byte[] packet = dInX.readSlipMessage(readTimeoutMSecs);
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
byte[] message = new byte[CONNECTION_BUFFER_SIZE];
|
|
||||||
@SuppressWarnings("null")
|
|
||||||
int messageLength = dIn.read(message, 0, message.length, ioTimeoutMSecs);
|
|
||||||
byte[] packet = new byte[messageLength];
|
|
||||||
System.arraycopy(message, 0, packet, 0, messageLength);
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (byte b : packet) {
|
for (byte b : packet) {
|
||||||
sb.append(String.format("%02X ", b));
|
sb.append(String.format("%02X ", b));
|
||||||
}
|
}
|
||||||
logger.trace("receive() finished after having read {} bytes: {}", messageLength, sb.toString());
|
logger.trace("receive() finished after having read {} bytes: {}", packet.length, sb.toString());
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ready = false;
|
close();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,67 +244,39 @@ class SSLconnection {
|
|||||||
* Destructor to tear down a connection.
|
* Destructor to tear down a connection.
|
||||||
*
|
*
|
||||||
* @throws java.io.IOException in case of a communication I/O failure.
|
* @throws java.io.IOException in case of a communication I/O failure.
|
||||||
|
* But actually eats all exceptions to ensure sure that all shutdown code is executed
|
||||||
*/
|
*/
|
||||||
synchronized void close() throws IOException {
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
logger.debug("close() called.");
|
logger.debug("close() called.");
|
||||||
ready = false;
|
|
||||||
logger.info("Shutting down Velux bridge connection.");
|
|
||||||
// Just for avoidance of Potential null pointer access
|
|
||||||
DataInputStreamWithTimeout dInX = dIn;
|
DataInputStreamWithTimeout dInX = dIn;
|
||||||
if (dInX != null) {
|
if (dInX != null) {
|
||||||
dInX.close();
|
try {
|
||||||
dIn = null;
|
dInX.close();
|
||||||
}
|
} catch (IOException e) {
|
||||||
// Just for avoidance of Potential null pointer access
|
// eat the exception so the following will always be executed
|
||||||
DataOutputStream dOutX = dOut;
|
|
||||||
if (dOutX != null) {
|
|
||||||
dOutX.close();
|
|
||||||
dOut = null;
|
|
||||||
}
|
|
||||||
// Just for avoidance of Potential null pointer access
|
|
||||||
SSLSocket socketX = socket;
|
|
||||||
if (socketX != null) {
|
|
||||||
socketX.close();
|
|
||||||
socket = null;
|
|
||||||
}
|
|
||||||
logger.trace("close() finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameter modification.
|
|
||||||
*
|
|
||||||
* @param timeoutMSecs the maximum duration in milliseconds for read operations.
|
|
||||||
*/
|
|
||||||
void setTimeout(int timeoutMSecs) {
|
|
||||||
logger.debug("setTimeout() set timeout to {} milliseconds.", timeoutMSecs);
|
|
||||||
ioTimeoutMSecs = timeoutMSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to flush the input buffer.
|
|
||||||
*
|
|
||||||
* @throws java.io.IOException in case of a communication I/O failure.
|
|
||||||
*/
|
|
||||||
private void flushReadBufffer() throws IOException {
|
|
||||||
logger.trace("flushReadBuffer() called.");
|
|
||||||
DataInputStreamWithTimeout dInX = dIn;
|
|
||||||
if (!ready || (dInX == null)) {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
int byteCount = dInX.available();
|
|
||||||
if (byteCount > 0) {
|
|
||||||
byte[] byteArray = new byte[byteCount];
|
|
||||||
dInX.readFully(byteArray);
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (byte currByte : byteArray) {
|
|
||||||
stringBuilder.append(String.format("%02X ", currByte));
|
|
||||||
}
|
|
||||||
logger.trace("flushReadBuffer(): discarded {} unexpected bytes in the input buffer: {}", byteCount,
|
|
||||||
stringBuilder.toString());
|
|
||||||
} else {
|
|
||||||
logger.warn("flushReadBuffer(): discarded {} unexpected bytes in the input buffer", byteCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DataOutputStream dOutX = dOut;
|
||||||
|
if (dOutX != null) {
|
||||||
|
try {
|
||||||
|
dOutX.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// eat the exception so the following will always be executed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSLSocket socketX = socket;
|
||||||
|
if (socketX != null) {
|
||||||
|
logger.debug("Shutting down Velux bridge connection.");
|
||||||
|
try {
|
||||||
|
socketX.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// eat the exception so the following will always be executed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dIn = null;
|
||||||
|
dOut = null;
|
||||||
|
socket = null;
|
||||||
|
logger.trace("close() finished.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ public class KLF200Response {
|
|||||||
public static void errorLogging(Logger logger, short responseCommand) {
|
public static void errorLogging(Logger logger, short responseCommand) {
|
||||||
logger.trace("setResponse(): cannot handle response {} ({}).", Command.get(responseCommand).toString(),
|
logger.trace("setResponse(): cannot handle response {} ({}).", Command.get(responseCommand).toString(),
|
||||||
new CommandNumber(responseCommand).toString());
|
new CommandNumber(responseCommand).toString());
|
||||||
logger.warn("Gateway response {} ({}) cannot be handled at this point of interaction.",
|
logger.debug("Gateway response {} ({}) cannot be handled at this point of interaction.",
|
||||||
Command.get(responseCommand).toString(), new CommandNumber(responseCommand).toString());
|
Command.get(responseCommand).toString(), new CommandNumber(responseCommand).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ public class KLF200Response {
|
|||||||
logger.trace("check4matchingAnyID() called for request {} {} and response {} {}.", idName, requestID, idName,
|
logger.trace("check4matchingAnyID() called for request {} {} and response {} {}.", idName, requestID, idName,
|
||||||
responseID);
|
responseID);
|
||||||
if (requestID != responseID) {
|
if (requestID != responseID) {
|
||||||
logger.warn("Gateway query for {} {} received unexpected response of {} {}.", idName, requestID, idName,
|
logger.debug("Gateway query for {} {} received unexpected response of {} {}.", idName, requestID, idName,
|
||||||
responseID);
|
responseID);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,330 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.velux.internal.discovery;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.MulticastSocket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that uses Multicast DNS (mDNS) to discover Velux Bridges and return their ipv4 addresses
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VeluxBridgeFinder implements Closeable {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(VeluxBridgeFinder.class);
|
||||||
|
|
||||||
|
// timing constants
|
||||||
|
private static final int BUFFER_SIZE = 256;
|
||||||
|
private static final int SLEEP_MSECS = 100;
|
||||||
|
private static final int SOCKET_TIMEOUT_MSECS = 500;
|
||||||
|
private static final int SEARCH_DURATION_MSECS = 5000;
|
||||||
|
private static final int REPEAT_COUNT = 3;
|
||||||
|
|
||||||
|
// dns communication constants
|
||||||
|
private static final int MDNS_PORT = 5353;
|
||||||
|
private static final String MDNS_ADDR = "224.0.0.251";
|
||||||
|
|
||||||
|
// dns flag constants
|
||||||
|
private static final short FLAGS_QR = (short) 0x8000;
|
||||||
|
private static final short FLAGS_AA = 0x0400;
|
||||||
|
|
||||||
|
// dns message class constants
|
||||||
|
private static final short CLASS_IN = 0x0001;
|
||||||
|
private static final short CLASS_MASK = 0x7FFF;
|
||||||
|
|
||||||
|
// dns message type constants
|
||||||
|
private static final short TYPE_PTR = 0x000c;
|
||||||
|
|
||||||
|
private static final byte NULL = 0x00;
|
||||||
|
|
||||||
|
// Velux bridge identifiers
|
||||||
|
private static final String KLF_SERVICE_ID = "_http._tcp.local";
|
||||||
|
private static final String KLF_HOST_PREFIX = "VELUX_KLF_";
|
||||||
|
|
||||||
|
private short randomQueryId;
|
||||||
|
private ScheduledExecutorService executor;
|
||||||
|
private @Nullable Listener listener = null;
|
||||||
|
|
||||||
|
private class Listener implements Callable<Set<String>> {
|
||||||
|
|
||||||
|
private boolean interrupted = false;
|
||||||
|
private boolean started = false;
|
||||||
|
|
||||||
|
public void interrupt() {
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasStarted() {
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for Velux Bridges and returns their IP addresses. It loops for SEARCH_DURATION_MSECS or until
|
||||||
|
* 'interrupt()' or 'Thread.interrupted()' are called when it terminates early after the next socket read
|
||||||
|
* timeout i.e. after SOCKET_TIMEOUT_MSECS
|
||||||
|
*
|
||||||
|
* @return a set of strings containing dotted IP addresses e.g. '123.123.123.123'
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Set<String> call() throws Exception {
|
||||||
|
final Set<String> ipAddresses = new HashSet<>();
|
||||||
|
|
||||||
|
// create a multicast listener socket
|
||||||
|
try (MulticastSocket rcvSocket = new MulticastSocket(MDNS_PORT)) {
|
||||||
|
|
||||||
|
final byte[] rcvBytes = new byte[BUFFER_SIZE];
|
||||||
|
final long finishTime = System.currentTimeMillis() + SEARCH_DURATION_MSECS;
|
||||||
|
|
||||||
|
rcvSocket.setReuseAddress(true);
|
||||||
|
rcvSocket.joinGroup(InetAddress.getByName(MDNS_ADDR));
|
||||||
|
rcvSocket.setSoTimeout(SOCKET_TIMEOUT_MSECS);
|
||||||
|
|
||||||
|
// tell the caller that we are ready to roll
|
||||||
|
started = true;
|
||||||
|
|
||||||
|
// loop until time out or internally or externally interrupted
|
||||||
|
while ((System.currentTimeMillis() < finishTime) && (!interrupted) && (!Thread.interrupted())) {
|
||||||
|
// read next packet
|
||||||
|
DatagramPacket rcvPacket = new DatagramPacket(rcvBytes, rcvBytes.length);
|
||||||
|
try {
|
||||||
|
rcvSocket.receive(rcvPacket);
|
||||||
|
if (isKlfLanResponse(rcvPacket.getData())) {
|
||||||
|
ipAddresses.add(rcvPacket.getAddress().getHostAddress());
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// time out is ok, continue listening
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("listenerRunnable(): udp socket exception '{}'", e.getMessage());
|
||||||
|
}
|
||||||
|
// prevent caller waiting forever in case start up failed
|
||||||
|
started = true;
|
||||||
|
return ipAddresses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an mDNS query package to query SERVICE_ID looking for host names
|
||||||
|
*
|
||||||
|
* @return a byte array containing the query datagram payload, or an empty array if failed
|
||||||
|
*/
|
||||||
|
private byte[] buildQuery() {
|
||||||
|
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||||
|
DataOutputStream dataStream = new DataOutputStream(byteStream);
|
||||||
|
try {
|
||||||
|
dataStream.writeShort(randomQueryId); // id
|
||||||
|
dataStream.writeShort(0); // flags
|
||||||
|
dataStream.writeShort(1); // qdCount
|
||||||
|
dataStream.writeShort(0); // anCount
|
||||||
|
dataStream.writeShort(0); // nsCount
|
||||||
|
dataStream.writeShort(0); // arCount
|
||||||
|
for (String segString : KLF_SERVICE_ID.split("\\.")) {
|
||||||
|
byte[] segBytes = segString.getBytes(StandardCharsets.UTF_8);
|
||||||
|
dataStream.writeByte(segBytes.length); // length
|
||||||
|
dataStream.write(segBytes); // byte string
|
||||||
|
}
|
||||||
|
dataStream.writeByte(NULL); // end of name
|
||||||
|
dataStream.writeShort(TYPE_PTR); // type
|
||||||
|
dataStream.writeShort(CLASS_IN); // class
|
||||||
|
return byteStream.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an mDNS response package and check if it is from a KLF bridge
|
||||||
|
*
|
||||||
|
* @param responsePayload a byte array containing the response datagram payload
|
||||||
|
* @return true if the response is from a KLF bridge
|
||||||
|
*/
|
||||||
|
private boolean isKlfLanResponse(byte[] responsePayload) {
|
||||||
|
DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(responsePayload));
|
||||||
|
try {
|
||||||
|
// check if the package id matches the query
|
||||||
|
short id = dataStream.readShort();
|
||||||
|
if (id == randomQueryId) {
|
||||||
|
short flags = dataStream.readShort();
|
||||||
|
boolean isResponse = (flags & FLAGS_QR) == FLAGS_QR;
|
||||||
|
boolean isAuthoritative = (flags & FLAGS_AA) == FLAGS_AA;
|
||||||
|
|
||||||
|
// check if it is an authoritative response
|
||||||
|
if (isResponse && isAuthoritative) {
|
||||||
|
short qdCount = dataStream.readShort();
|
||||||
|
short anCount = dataStream.readShort();
|
||||||
|
|
||||||
|
dataStream.readShort(); // nsCount
|
||||||
|
dataStream.readShort(); // arCount
|
||||||
|
|
||||||
|
// check it is an answer (and not a query)
|
||||||
|
if ((anCount == 0) || (qdCount != 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the answers
|
||||||
|
for (short an = 0; an < anCount; an++) {
|
||||||
|
// parse the name
|
||||||
|
byte[] str = new byte[BUFFER_SIZE];
|
||||||
|
int i = 0;
|
||||||
|
int segLength;
|
||||||
|
while ((segLength = dataStream.readByte()) > 0) {
|
||||||
|
i += dataStream.read(str, i, segLength);
|
||||||
|
str[i] = '.';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
String name = new String(str, 0, i, StandardCharsets.UTF_8);
|
||||||
|
short typ = dataStream.readShort();
|
||||||
|
short clazz = (short) (CLASS_MASK & dataStream.readShort());
|
||||||
|
if (!(name.startsWith(KLF_SERVICE_ID)) || (typ != TYPE_PTR) || (clazz != CLASS_IN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, the name and response type are valid
|
||||||
|
dataStream.readInt(); // TTL
|
||||||
|
dataStream.readShort(); // dataLen
|
||||||
|
|
||||||
|
// parse the host name
|
||||||
|
i = 0;
|
||||||
|
while ((segLength = dataStream.readByte()) > 0) {
|
||||||
|
i += dataStream.read(str, i, segLength);
|
||||||
|
str[i] = '.';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the host name matches
|
||||||
|
String host = new String(str, 0, i, StandardCharsets.UTF_8);
|
||||||
|
if (host.startsWith(KLF_HOST_PREFIX)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private synchronized method that searches for Velux Bridges and returns their IP addresses. Takes
|
||||||
|
* SEARCH_DURATION_MSECS to complete.
|
||||||
|
*
|
||||||
|
* @return a set of strings containing dotted IP addresses e.g. '123.123.123.123'
|
||||||
|
*/
|
||||||
|
private synchronized Set<String> discoverBridgeIpAddresses() {
|
||||||
|
@Nullable
|
||||||
|
Set<String> result = null;
|
||||||
|
|
||||||
|
// create a random query id
|
||||||
|
Random random = new Random();
|
||||||
|
randomQueryId = (short) random.nextInt(Short.MAX_VALUE);
|
||||||
|
|
||||||
|
// create the listener task and start it
|
||||||
|
Listener listener = this.listener = new Listener();
|
||||||
|
|
||||||
|
// create a datagram socket
|
||||||
|
try (DatagramSocket socket = new DatagramSocket()) {
|
||||||
|
// prepare query packet
|
||||||
|
byte[] dnsBytes = buildQuery();
|
||||||
|
DatagramPacket dnsPacket = new DatagramPacket(dnsBytes, 0, dnsBytes.length,
|
||||||
|
InetAddress.getByName(MDNS_ADDR), MDNS_PORT);
|
||||||
|
|
||||||
|
// create listener and wait until it has started
|
||||||
|
Future<Set<String>> future = executor.submit(listener);
|
||||||
|
while (!listener.hasStarted()) {
|
||||||
|
Thread.sleep(SLEEP_MSECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the query several times
|
||||||
|
for (int i = 0; i < REPEAT_COUNT; i++) {
|
||||||
|
// send the query several times
|
||||||
|
socket.send(dnsPacket);
|
||||||
|
Thread.sleep(SLEEP_MSECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the listener future to get the result
|
||||||
|
result = future.get();
|
||||||
|
} catch (InterruptedException | IOException | ExecutionException e) {
|
||||||
|
logger.debug("discoverBridgeIpAddresses(): unexpected exception '{}'", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up listener task (just in case) and return
|
||||||
|
listener.interrupt();
|
||||||
|
this.listener = null;
|
||||||
|
return result != null ? result : new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param executor the caller's task executor
|
||||||
|
*/
|
||||||
|
public VeluxBridgeFinder(ScheduledExecutorService executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interrupt the {@link Listener}
|
||||||
|
*
|
||||||
|
* @throws IOException (not)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
Listener listener = this.listener;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.interrupt();
|
||||||
|
this.listener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method to search for Velux Bridges and return their IP addresses. NOTE: it takes SEARCH_DURATION_MSECS to
|
||||||
|
* complete, so don't call it on the main thread!
|
||||||
|
*
|
||||||
|
* @return set of dotted IP address e.g. '123.123.123.123'
|
||||||
|
*/
|
||||||
|
public static Set<String> discoverIpAddresses(ScheduledExecutorService scheduler) {
|
||||||
|
try (VeluxBridgeFinder finder = new VeluxBridgeFinder(scheduler)) {
|
||||||
|
return finder.discoverBridgeIpAddresses();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import java.util.Set;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingProperties;
|
import org.openhab.binding.velux.internal.VeluxBindingProperties;
|
||||||
|
import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
|
||||||
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||||
import org.openhab.binding.velux.internal.things.VeluxProductSerialNo;
|
import org.openhab.binding.velux.internal.things.VeluxProductSerialNo;
|
||||||
@ -45,11 +46,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
//
|
|
||||||
// To-be-discussed: check whether an immediate activation is preferable.
|
|
||||||
// Might be activated by:
|
|
||||||
// @Component(service = DiscoveryService.class, configurationPid = "discovery.velux")
|
|
||||||
//
|
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.velux")
|
@Component(service = DiscoveryService.class, configurationPid = "discovery.velux")
|
||||||
public class VeluxDiscoveryService extends AbstractDiscoveryService implements Runnable {
|
public class VeluxDiscoveryService extends AbstractDiscoveryService implements Runnable {
|
||||||
@ -57,7 +53,7 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
|||||||
|
|
||||||
// Class internal
|
// Class internal
|
||||||
|
|
||||||
private static final int DISCOVER_TIMEOUT_SECONDS = 300;
|
private static final int DISCOVER_TIMEOUT_SECONDS = 60;
|
||||||
|
|
||||||
private @NonNullByDefault({}) LocaleProvider localeProvider;
|
private @NonNullByDefault({}) LocaleProvider localeProvider;
|
||||||
private @NonNullByDefault({}) TranslationProvider i18nProvider;
|
private @NonNullByDefault({}) TranslationProvider i18nProvider;
|
||||||
@ -80,7 +76,7 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
|||||||
* Initializes the {@link VeluxDiscoveryService} without any further information.
|
* Initializes the {@link VeluxDiscoveryService} without any further information.
|
||||||
*/
|
*/
|
||||||
public VeluxDiscoveryService() {
|
public VeluxDiscoveryService() {
|
||||||
super(VeluxBindingConstants.SUPPORTED_THINGS_ITEMS, DISCOVER_TIMEOUT_SECONDS);
|
super(VeluxBindingConstants.DISCOVERABLE_THINGS, DISCOVER_TIMEOUT_SECONDS);
|
||||||
logger.trace("VeluxDiscoveryService(without Bridge) just initialized.");
|
logger.trace("VeluxDiscoveryService(without Bridge) just initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +103,7 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
|||||||
* @param localizationHandler Initialized localization handler.
|
* @param localizationHandler Initialized localization handler.
|
||||||
*/
|
*/
|
||||||
public VeluxDiscoveryService(Localization localizationHandler) {
|
public VeluxDiscoveryService(Localization localizationHandler) {
|
||||||
super(VeluxBindingConstants.SUPPORTED_THINGS_ITEMS, DISCOVER_TIMEOUT_SECONDS);
|
super(VeluxBindingConstants.DISCOVERABLE_THINGS, DISCOVER_TIMEOUT_SECONDS);
|
||||||
logger.trace("VeluxDiscoveryService(locale={},i18n={}) just initialized.", localeProvider, i18nProvider);
|
logger.trace("VeluxDiscoveryService(locale={},i18n={}) just initialized.", localeProvider, i18nProvider);
|
||||||
localization = localizationHandler;
|
localization = localizationHandler;
|
||||||
}
|
}
|
||||||
@ -143,10 +139,15 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
|||||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||||
.withProperty(VeluxBindingProperties.PROPERTY_BINDING_BUNDLEVERSION,
|
.withProperty(VeluxBindingProperties.PROPERTY_BINDING_BUNDLEVERSION,
|
||||||
ManifestInformation.getBundleVersion())
|
ManifestInformation.getBundleVersion())
|
||||||
|
.withRepresentationProperty(VeluxBindingProperties.PROPERTY_BINDING_BUNDLEVERSION)
|
||||||
.withLabel(localization.getText("discovery.velux.binding...label")).build();
|
.withLabel(localization.getText("discovery.velux.binding...label")).build();
|
||||||
logger.debug("startScan(): registering new thing {}.", discoveryResult);
|
logger.debug("startScan(): registering new thing {}.", discoveryResult);
|
||||||
thingDiscovered(discoveryResult);
|
thingDiscovered(discoveryResult);
|
||||||
|
|
||||||
|
scheduler.execute(() -> {
|
||||||
|
discoverBridges();
|
||||||
|
});
|
||||||
|
|
||||||
if (bridgeHandlers.isEmpty()) {
|
if (bridgeHandlers.isEmpty()) {
|
||||||
logger.debug("startScan(): VeluxDiscoveryService cannot proceed due to missing Velux bridge(s).");
|
logger.debug("startScan(): VeluxDiscoveryService cannot proceed due to missing Velux bridge(s).");
|
||||||
} else {
|
} else {
|
||||||
@ -161,7 +162,6 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
|||||||
public synchronized void stopScan() {
|
public synchronized void stopScan() {
|
||||||
logger.trace("stopScan() called.");
|
logger.trace("stopScan() called.");
|
||||||
super.stopScan();
|
super.stopScan();
|
||||||
removeOlderResults(getTimestampOfLastScan());
|
|
||||||
logger.trace("stopScan() done.");
|
logger.trace("stopScan() done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,4 +286,21 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
|||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return bridgeHandlers.isEmpty();
|
return bridgeHandlers.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover any bridges on the network that are not yet instantiated.
|
||||||
|
*/
|
||||||
|
private void discoverBridges() {
|
||||||
|
// discover the list of IP addresses of bridges on the network
|
||||||
|
Set<String> foundBridgeIpAddresses = VeluxBridgeFinder.discoverIpAddresses(scheduler);
|
||||||
|
// publish discovery results
|
||||||
|
for (String ipAddr : foundBridgeIpAddresses) {
|
||||||
|
ThingUID thingUID = new ThingUID(THING_TYPE_BRIDGE, ipAddr.replace(".", "_"));
|
||||||
|
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_BRIDGE)
|
||||||
|
.withProperty(VeluxBridgeConfiguration.BRIDGE_IPADDRESS, ipAddr)
|
||||||
|
.withRepresentationProperty(VeluxBridgeConfiguration.BRIDGE_IPADDRESS)
|
||||||
|
.withLabel(String.format("Velux Bridge (%s)", ipAddr)).build();
|
||||||
|
thingDiscovered(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeRunProductCommand;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridgeRunProductCommand;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.GetProduct;
|
import org.openhab.binding.velux.internal.bridge.common.GetProduct;
|
||||||
import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
|
import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
|
||||||
|
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.PercentType;
|
import org.openhab.core.library.types.PercentType;
|
||||||
@ -27,6 +28,7 @@ import org.openhab.core.library.types.UpDownType;
|
|||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -88,14 +90,16 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
|
|||||||
bcp.setProductId(veluxActuator.getProductBridgeIndex().toInt());
|
bcp.setProductId(veluxActuator.getProductBridgeIndex().toInt());
|
||||||
if (thisBridgeHandler.thisBridge.bridgeCommunicate(bcp) && bcp.isCommunicationSuccessful()) {
|
if (thisBridgeHandler.thisBridge.bridgeCommunicate(bcp) && bcp.isCommunicationSuccessful()) {
|
||||||
try {
|
try {
|
||||||
VeluxProductPosition position = new VeluxProductPosition(bcp.getProduct().getCurrentPosition());
|
VeluxProduct product = bcp.getProduct();
|
||||||
|
VeluxProductPosition position = new VeluxProductPosition(product.getDisplayPosition());
|
||||||
if (position.isValid()) {
|
if (position.isValid()) {
|
||||||
PercentType positionAsPercent = position.getPositionAsPercentType(veluxActuator.isInverted());
|
PercentType posPercent = position.getPositionAsPercentType(veluxActuator.isInverted());
|
||||||
LOGGER.trace("handleRefresh(): found actuator at level {}.", positionAsPercent);
|
LOGGER.trace("handleRefresh(): position of actuator is {}%.", posPercent);
|
||||||
newState = positionAsPercent;
|
newState = posPercent;
|
||||||
} else {
|
break;
|
||||||
LOGGER.trace("handleRefresh(): level of actuator is unknown.");
|
|
||||||
}
|
}
|
||||||
|
LOGGER.trace("handleRefresh(): position of actuator is 'UNDEFINED'.");
|
||||||
|
newState = UnDefType.UNDEF;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.warn("handleRefresh(): getProducts() exception: {}.", e.getMessage());
|
LOGGER.warn("handleRefresh(): getProducts() exception: {}.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,9 @@ package org.openhab.binding.velux.internal.handler;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
|
||||||
import org.openhab.binding.velux.internal.VeluxItemType;
|
import org.openhab.binding.velux.internal.VeluxItemType;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeLANConfig;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridgeLANConfig;
|
||||||
import org.openhab.binding.velux.internal.handler.utils.StateUtils;
|
import org.openhab.binding.velux.internal.handler.utils.StateUtils;
|
||||||
import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
|
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -71,25 +69,17 @@ final class ChannelBridgeLANconfig extends ChannelHandlerTemplate {
|
|||||||
VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thisBridgeHandler.thingTypeUIDOf(channelUID),
|
VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thisBridgeHandler.thingTypeUIDOf(channelUID),
|
||||||
channelUID.getId());
|
channelUID.getId());
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case BRIDGE_IPADDRESS:
|
case BRIDGE_ADDRESS:
|
||||||
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABipAddress);
|
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABipAddress);
|
||||||
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_IPADDRESS,
|
|
||||||
thisBridgeHandler.bridgeParameters.lanConfig.openHABipAddress.toString());
|
|
||||||
break;
|
break;
|
||||||
case BRIDGE_SUBNETMASK:
|
case BRIDGE_SUBNETMASK:
|
||||||
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABsubnetMask);
|
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABsubnetMask);
|
||||||
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_SUBNETMASK,
|
|
||||||
thisBridgeHandler.bridgeParameters.lanConfig.openHABsubnetMask.toString());
|
|
||||||
break;
|
break;
|
||||||
case BRIDGE_DEFAULTGW:
|
case BRIDGE_DEFAULTGW:
|
||||||
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABdefaultGW);
|
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABdefaultGW);
|
||||||
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_DEFAULTGW,
|
|
||||||
thisBridgeHandler.bridgeParameters.lanConfig.openHABdefaultGW.toString());
|
|
||||||
break;
|
break;
|
||||||
case BRIDGE_DHCP:
|
case BRIDGE_DHCP:
|
||||||
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABenabledDHCP);
|
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.lanConfig.openHABenabledDHCP);
|
||||||
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_DHCP,
|
|
||||||
thisBridgeHandler.bridgeParameters.lanConfig.openHABenabledDHCP.toString());
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,9 @@ package org.openhab.binding.velux.internal.handler;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
|
||||||
import org.openhab.binding.velux.internal.VeluxItemType;
|
import org.openhab.binding.velux.internal.VeluxItemType;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
|
||||||
import org.openhab.binding.velux.internal.handler.utils.StateUtils;
|
import org.openhab.binding.velux.internal.handler.utils.StateUtils;
|
||||||
import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
|
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -70,15 +67,13 @@ final class ChannelBridgeWLANconfig extends ChannelHandlerTemplate {
|
|||||||
if (thisBridgeHandler.bridgeParameters.wlanConfig.isRetrieved) {
|
if (thisBridgeHandler.bridgeParameters.wlanConfig.isRetrieved) {
|
||||||
VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thisBridgeHandler.thingTypeUIDOf(channelUID),
|
VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thisBridgeHandler.thingTypeUIDOf(channelUID),
|
||||||
channelUID.getId());
|
channelUID.getId());
|
||||||
String msg = thisBridgeHandler.localization.getText("config.velux.bridge.unAvailable");
|
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case BRIDGE_WLANSSID:
|
case BRIDGE_WLANSSID:
|
||||||
newState = StateUtils.createState(new StringType(msg));
|
newState = StateUtils.createState(thisBridgeHandler.bridgeParameters.wlanConfig.openHABwlanSSID);
|
||||||
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_WLANSSID, msg);
|
|
||||||
break;
|
break;
|
||||||
case BRIDGE_WLANPASSWORD:
|
case BRIDGE_WLANPASSWORD:
|
||||||
newState = StateUtils.createState(new StringType(msg));
|
newState = StateUtils
|
||||||
ThingProperty.setValue(thisBridgeHandler, VeluxBindingConstants.PROPERTY_BRIDGE_WLANPASSWORD, msg);
|
.createState(thisBridgeHandler.bridgeParameters.wlanConfig.openHABwlanPassword);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Guenther Schreiner - Initial contribution.
|
* @author Guenther Schreiner - Initial contribution.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
final class ChannelSceneSilentmode extends ChannelHandlerTemplate {
|
final class ChannelSceneSilentmode extends ChannelHandlerTemplate {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelSceneSilentmode.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelSceneSilentmode.class);
|
||||||
|
@ -12,9 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.velux.internal.handler;
|
package org.openhab.binding.velux.internal.handler;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -23,6 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
import org.openhab.binding.velux.internal.VeluxBinding;
|
import org.openhab.binding.velux.internal.VeluxBinding;
|
||||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
||||||
import org.openhab.binding.velux.internal.VeluxItemType;
|
import org.openhab.binding.velux.internal.VeluxItemType;
|
||||||
|
import org.openhab.binding.velux.internal.action.VeluxActions;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridge;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridge;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeActuators;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridgeActuators;
|
||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeDeviceStatus;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridgeDeviceStatus;
|
||||||
@ -36,6 +40,8 @@ import org.openhab.binding.velux.internal.bridge.VeluxBridgeSetHouseStatusMonito
|
|||||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
|
import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
|
||||||
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
|
||||||
|
import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
|
||||||
|
import org.openhab.binding.velux.internal.bridge.common.RunReboot;
|
||||||
import org.openhab.binding.velux.internal.bridge.json.JsonVeluxBridge;
|
import org.openhab.binding.velux.internal.bridge.json.JsonVeluxBridge;
|
||||||
import org.openhab.binding.velux.internal.bridge.slip.SlipVeluxBridge;
|
import org.openhab.binding.velux.internal.bridge.slip.SlipVeluxBridge;
|
||||||
import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
|
import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
|
||||||
@ -50,7 +56,7 @@ import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex
|
|||||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||||
import org.openhab.binding.velux.internal.utils.Localization;
|
import org.openhab.binding.velux.internal.utils.Localization;
|
||||||
import org.openhab.core.common.AbstractUID;
|
import org.openhab.core.common.AbstractUID;
|
||||||
import org.openhab.core.common.ThreadPoolManager;
|
import org.openhab.core.common.NamedThreadFactory;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.PercentType;
|
import org.openhab.core.library.types.PercentType;
|
||||||
@ -59,9 +65,11 @@ import org.openhab.core.thing.ChannelUID;
|
|||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -87,6 +95,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements VeluxBridgeInstance, VeluxBridgeProvider {
|
public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements VeluxBridgeInstance, VeluxBridgeProvider {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(VeluxBridgeHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(VeluxBridgeHandler.class);
|
||||||
|
|
||||||
// Class internal
|
// Class internal
|
||||||
@ -102,10 +111,14 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
private int refreshCounter = 0;
|
private int refreshCounter = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dedicated thread pool for the long-running bridge communication threads.
|
* Dedicated task executor for the long-running bridge communication tasks.
|
||||||
|
*
|
||||||
|
* Note: there is no point in using multi threaded thread-pool here, since all the submitted (Runnable) tasks are
|
||||||
|
* anyway forced to go through the same serial pipeline, because they all call the same class level "synchronized"
|
||||||
|
* method to actually communicate with the KLF bridge via its one single TCP socket connection
|
||||||
*/
|
*/
|
||||||
private ScheduledExecutorService handleScheduler = ThreadPoolManager
|
private @Nullable ExecutorService taskExecutor = null;
|
||||||
.getScheduledPool(VeluxBindingConstants.BINDING_ID);
|
private @Nullable NamedThreadFactory threadFactory = null;
|
||||||
|
|
||||||
private VeluxBridge myJsonBridge = new JsonVeluxBridge(this);
|
private VeluxBridge myJsonBridge = new JsonVeluxBridge(this);
|
||||||
private VeluxBridge mySlipBridge = new SlipVeluxBridge(this);
|
private VeluxBridge mySlipBridge = new SlipVeluxBridge(this);
|
||||||
@ -250,10 +263,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
logger.warn("initialize(): scheduler is shutdown, aborting the initialization of this bridge.");
|
logger.warn("initialize(): scheduler is shutdown, aborting the initialization of this bridge.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleScheduler.isShutdown()) {
|
getTaskExecutor();
|
||||||
logger.trace("initialize(): handleScheduler is shutdown, aborting the initialization of this bridge.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.trace("initialize(): preparing background initialization task.");
|
logger.trace("initialize(): preparing background initialization task.");
|
||||||
// Background initialization...
|
// Background initialization...
|
||||||
scheduler.execute(() -> {
|
scheduler.execute(() -> {
|
||||||
@ -291,6 +301,11 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
logger.trace("dispose(): stopping the refresh.");
|
logger.trace("dispose(): stopping the refresh.");
|
||||||
currentRefreshJob.cancel(true);
|
currentRefreshJob.cancel(true);
|
||||||
}
|
}
|
||||||
|
// shut down the task executor
|
||||||
|
ExecutorService taskExecutor = this.taskExecutor;
|
||||||
|
if (taskExecutor != null) {
|
||||||
|
taskExecutor.shutdownNow();
|
||||||
|
}
|
||||||
// Background execution of dispose
|
// Background execution of dispose
|
||||||
scheduler.execute(() -> {
|
scheduler.execute(() -> {
|
||||||
logger.trace("dispose.scheduled(): (synchronous) logout initiated.");
|
logger.trace("dispose.scheduled(): (synchronous) logout initiated.");
|
||||||
@ -396,32 +411,30 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
|
|
||||||
private synchronized void refreshOpenHAB() {
|
private synchronized void refreshOpenHAB() {
|
||||||
logger.debug("refreshOpenHAB() initiated by {} starting cycle {}.", Thread.currentThread(), refreshCounter);
|
logger.debug("refreshOpenHAB() initiated by {} starting cycle {}.", Thread.currentThread(), refreshCounter);
|
||||||
|
|
||||||
if (handleScheduler.isShutdown()) {
|
|
||||||
logger.trace("refreshOpenHAB(): handleScheduler is shutdown, recreating a scheduler pool.");
|
|
||||||
handleScheduler = ThreadPoolManager.getScheduledPool(VeluxBindingConstants.BINDING_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace("refreshOpenHAB(): processing of possible HSM messages.");
|
logger.trace("refreshOpenHAB(): processing of possible HSM messages.");
|
||||||
|
|
||||||
// Background execution of bridge related I/O
|
// Background execution of bridge related I/O
|
||||||
handleScheduler.execute(() -> {
|
getTaskExecutor().execute(() -> {
|
||||||
logger.trace("refreshOpenHAB.scheduled() initiated by {} will process HouseStatus.",
|
logger.trace("refreshOpenHAB.scheduled() initiated by {} will process HouseStatus.",
|
||||||
Thread.currentThread());
|
Thread.currentThread());
|
||||||
if (new VeluxBridgeGetHouseStatus().evaluateState(thisBridge)) {
|
if (new VeluxBridgeGetHouseStatus().evaluateState(thisBridge)) {
|
||||||
logger.trace("refreshOpenHAB.scheduled(): successfully processed of GetHouseStatus()");
|
logger.trace("refreshOpenHAB.scheduled(): => GetHouseStatus() => updates received => synchronizing");
|
||||||
|
syncChannelsWithProducts();
|
||||||
|
} else {
|
||||||
|
logger.trace("refreshOpenHAB.scheduled(): => GetHouseStatus() => no updates");
|
||||||
}
|
}
|
||||||
logger.trace("refreshOpenHAB.scheduled() initiated by {} has finished.", Thread.currentThread());
|
logger.trace("refreshOpenHAB.scheduled() initiated by {} has finished.", Thread.currentThread());
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.trace(
|
logger.trace("refreshOpenHAB(): loop through all (child things and bridge) linked channels needing a refresh");
|
||||||
"refreshOpenHAB(): looping through all (both child things and bridge) linked channels for a need of refresh.");
|
|
||||||
for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
|
for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
|
||||||
if (VeluxItemType.isToBeRefreshedNow(refreshCounter, thingTypeUIDOf(channelUID), channelUID.getId())) {
|
if (VeluxItemType.isToBeRefreshedNow(refreshCounter, thingTypeUIDOf(channelUID), channelUID.getId())) {
|
||||||
logger.trace("refreshOpenHAB(): refreshing channel {}.", channelUID);
|
logger.trace("refreshOpenHAB(): refreshing channel {}.", channelUID);
|
||||||
handleCommand(channelUID, RefreshType.REFRESH);
|
handleCommand(channelUID, RefreshType.REFRESH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.trace("refreshOpenHAB(): looping through properties for a need of refresh.");
|
|
||||||
|
logger.trace("refreshOpenHAB(): loop through properties needing a refresh");
|
||||||
for (VeluxItemType veluxItem : VeluxItemType.getPropertyEntriesByThing(getThing().getThingTypeUID())) {
|
for (VeluxItemType veluxItem : VeluxItemType.getPropertyEntriesByThing(getThing().getThingTypeUID())) {
|
||||||
if (VeluxItemType.isToBeRefreshedNow(refreshCounter, getThing().getThingTypeUID(),
|
if (VeluxItemType.isToBeRefreshedNow(refreshCounter, getThing().getThingTypeUID(),
|
||||||
veluxItem.getIdentifier())) {
|
veluxItem.getIdentifier())) {
|
||||||
@ -439,11 +452,11 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
*/
|
*/
|
||||||
private void syncChannelsWithProducts() {
|
private void syncChannelsWithProducts() {
|
||||||
if (!bridgeParameters.actuators.getChannel().existingProducts.isDirty()) {
|
if (!bridgeParameters.actuators.getChannel().existingProducts.isDirty()) {
|
||||||
|
logger.trace("syncChannelsWithProducts(): no existing products with changed parameters.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.trace("syncChannelsWithProducts(): there are some existing products with changed parameters.");
|
logger.trace("syncChannelsWithProducts(): there are some existing products with changed parameters.");
|
||||||
outer: for (VeluxProduct product : bridgeParameters.actuators.getChannel().existingProducts
|
for (VeluxProduct product : bridgeParameters.actuators.getChannel().existingProducts.valuesOfModified()) {
|
||||||
.valuesOfModified()) {
|
|
||||||
logger.trace("syncChannelsWithProducts(): actuator {} has changed values.", product.getProductName());
|
logger.trace("syncChannelsWithProducts(): actuator {} has changed values.", product.getProductName());
|
||||||
ProductBridgeIndex productPbi = product.getBridgeProductIndex();
|
ProductBridgeIndex productPbi = product.getBridgeProductIndex();
|
||||||
logger.trace("syncChannelsWithProducts(): bridge index is {}.", productPbi);
|
logger.trace("syncChannelsWithProducts(): bridge index is {}.", productPbi);
|
||||||
@ -452,28 +465,29 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
logger.trace("syncChannelsWithProducts(): channel {} not found.", channelUID);
|
logger.trace("syncChannelsWithProducts(): channel {} not found.", channelUID);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!channel2VeluxActuator.get(channelUID).isKnown()) {
|
Thing2VeluxActuator actuator = channel2VeluxActuator.get(channelUID);
|
||||||
|
if (!actuator.isKnown()) {
|
||||||
logger.trace("syncChannelsWithProducts(): channel {} not registered on bridge.", channelUID);
|
logger.trace("syncChannelsWithProducts(): channel {} not registered on bridge.", channelUID);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ProductBridgeIndex channelPbi = channel2VeluxActuator.get(channelUID).getProductBridgeIndex();
|
ProductBridgeIndex channelPbi = actuator.getProductBridgeIndex();
|
||||||
if (!channelPbi.equals(productPbi)) {
|
if (!channelPbi.equals(productPbi)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Handle value inversion
|
// Handle value inversion
|
||||||
boolean isInverted = channel2VeluxActuator.get(channelUID).isInverted();
|
boolean isInverted = actuator.isInverted();
|
||||||
logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
|
logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
|
||||||
VeluxProductPosition position = new VeluxProductPosition(product.getCurrentPosition());
|
VeluxProductPosition position = new VeluxProductPosition(product.getDisplayPosition());
|
||||||
if (position.isValid()) {
|
if (position.isValid()) {
|
||||||
PercentType positionAsPercent = position.getPositionAsPercentType(isInverted);
|
PercentType positionAsPercent = position.getPositionAsPercentType(isInverted);
|
||||||
logger.debug("syncChannelsWithProducts(): updating channel {} to position {}%.", channelUID,
|
logger.debug("syncChannelsWithProducts(): updating channel {} to position {}%.", channelUID,
|
||||||
positionAsPercent);
|
positionAsPercent);
|
||||||
updateState(channelUID, positionAsPercent);
|
updateState(channelUID, positionAsPercent);
|
||||||
} else {
|
break;
|
||||||
logger.trace("syncChannelsWithProducts(): update of channel {} to position {} skipped.", channelUID,
|
|
||||||
position);
|
|
||||||
}
|
}
|
||||||
break outer;
|
logger.trace("syncChannelsWithProducts(): update channel {} to 'UNDEFINED'.", channelUID);
|
||||||
|
updateState(channelUID, UnDefType.UNDEF);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.trace("syncChannelsWithProducts(): resetting dirty flag.");
|
logger.trace("syncChannelsWithProducts(): resetting dirty flag.");
|
||||||
@ -490,7 +504,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
logger.debug("handleCommand({},{}) called.", channelUID.getAsString(), command);
|
logger.debug("handleCommand({},{}) called.", channelUID.getAsString(), command);
|
||||||
|
|
||||||
// Background execution of bridge related I/O
|
// Background execution of bridge related I/O
|
||||||
handleScheduler.execute(() -> {
|
getTaskExecutor().execute(() -> {
|
||||||
logger.trace("handleCommand.scheduled({}) Start work with calling handleCommandScheduled().",
|
logger.trace("handleCommand.scheduled({}) Start work with calling handleCommandScheduled().",
|
||||||
Thread.currentThread());
|
Thread.currentThread());
|
||||||
handleCommandScheduled(channelUID, command);
|
handleCommandScheduled(channelUID, command);
|
||||||
@ -570,7 +584,9 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
case BRIDGE_FIRMWARE:
|
case BRIDGE_FIRMWARE:
|
||||||
newState = ChannelBridgeFirmware.handleRefresh(channelUID, channelId, this);
|
newState = ChannelBridgeFirmware.handleRefresh(channelUID, channelId, this);
|
||||||
break;
|
break;
|
||||||
case BRIDGE_IPADDRESS:
|
case BRIDGE_ADDRESS:
|
||||||
|
// delete legacy property name entry (if any) and fall through
|
||||||
|
ThingProperty.setValue(this, VeluxBridgeConfiguration.BRIDGE_IPADDRESS, null);
|
||||||
case BRIDGE_SUBNETMASK:
|
case BRIDGE_SUBNETMASK:
|
||||||
case BRIDGE_DEFAULTGW:
|
case BRIDGE_DEFAULTGW:
|
||||||
case BRIDGE_DHCP:
|
case BRIDGE_DHCP:
|
||||||
@ -599,6 +615,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
case ACTUATOR_LIMIT_MINIMUM:
|
case ACTUATOR_LIMIT_MINIMUM:
|
||||||
case ROLLERSHUTTER_LIMIT_MINIMUM:
|
case ROLLERSHUTTER_LIMIT_MINIMUM:
|
||||||
case WINDOW_LIMIT_MINIMUM:
|
case WINDOW_LIMIT_MINIMUM:
|
||||||
|
// note: the empty string ("") below is intentional
|
||||||
newState = ChannelActuatorLimitation.handleRefresh(channelUID, "", this);
|
newState = ChannelActuatorLimitation.handleRefresh(channelUID, "", this);
|
||||||
break;
|
break;
|
||||||
case ACTUATOR_LIMIT_MAXIMUM:
|
case ACTUATOR_LIMIT_MAXIMUM:
|
||||||
@ -624,11 +641,14 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
if (itemType.isChannel()) {
|
if (itemType.isChannel()) {
|
||||||
logger.debug("handleCommandScheduled(): updating channel {} to {}.", channelUID, newState);
|
logger.debug("handleCommandScheduled(): updating channel {} to {}.", channelUID, newState);
|
||||||
updateState(channelUID, newState);
|
updateState(channelUID, newState);
|
||||||
}
|
} else if (itemType.isProperty()) {
|
||||||
if (itemType.isProperty()) {
|
// if property value is 'unknown', null it completely
|
||||||
logger.debug("handleCommandScheduled(): updating property {} to {}.", channelUID, newState);
|
String val = newState.toString();
|
||||||
ThingProperty.setValue(this, itemType.getIdentifier(), newState.toString());
|
if (VeluxBindingConstants.UNKNOWN.equals(val)) {
|
||||||
|
val = null;
|
||||||
|
}
|
||||||
|
logger.debug("handleCommandScheduled(): updating property {} to {}.", channelUID, val);
|
||||||
|
ThingProperty.setValue(this, itemType.getIdentifier(), val);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info("handleCommandScheduled({},{}): updating of item {} (type {}) failed.",
|
logger.info("handleCommandScheduled({},{}): updating of item {} (type {}) failed.",
|
||||||
@ -662,6 +682,20 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
case SCENE_ACTION:
|
case SCENE_ACTION:
|
||||||
ChannelSceneAction.handleCommand(channelUID, channelId, command, this);
|
ChannelSceneAction.handleCommand(channelUID, channelId, command, this);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTA BENE: Setting of a scene silent mode is no longer supported via the KLF API (i.e. the
|
||||||
|
* GW_SET_NODE_VELOCITY_REQ/CFM command set is no longer supported in the API), so the binding can
|
||||||
|
* no longer explicitly support a Channel with such a function. Therefore the silent mode Channel
|
||||||
|
* type was removed from the binding implementation.
|
||||||
|
*
|
||||||
|
* By contrast scene actions can still be called with a silent mode argument, so a silent mode
|
||||||
|
* Configuration Parameter has been introduced as a means for the user to set this argument.
|
||||||
|
*
|
||||||
|
* Strictly speaking the following case statement will now never be called, so in theory it,
|
||||||
|
* AND ALL THE CLASSES BEHIND, could be deleted from the binding CODE BASE. But out of prudence
|
||||||
|
* it is retained anyway 'just in case'.
|
||||||
|
*/
|
||||||
case SCENE_SILENTMODE:
|
case SCENE_SILENTMODE:
|
||||||
ChannelSceneSilentmode.handleCommand(channelUID, channelId, command, this);
|
ChannelSceneSilentmode.handleCommand(channelUID, channelId, command, this);
|
||||||
break;
|
break;
|
||||||
@ -671,7 +705,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
case ACTUATOR_STATE:
|
case ACTUATOR_STATE:
|
||||||
case ROLLERSHUTTER_POSITION:
|
case ROLLERSHUTTER_POSITION:
|
||||||
case WINDOW_POSITION:
|
case WINDOW_POSITION:
|
||||||
ChannelActuatorPosition.handleCommand(channelUID, channelId, command, this);
|
newValue = ChannelActuatorPosition.handleCommand(channelUID, channelId, command, this);
|
||||||
break;
|
break;
|
||||||
case ACTUATOR_LIMIT_MINIMUM:
|
case ACTUATOR_LIMIT_MINIMUM:
|
||||||
case ROLLERSHUTTER_LIMIT_MINIMUM:
|
case ROLLERSHUTTER_LIMIT_MINIMUM:
|
||||||
@ -706,4 +740,84 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
|||||||
new java.util.Date(thisBridge.lastSuccessfulCommunication()).toString());
|
new java.util.Date(thisBridge.lastSuccessfulCommunication()).toString());
|
||||||
logger.trace("handleCommandScheduled({}) done.", Thread.currentThread());
|
logger.trace("handleCommandScheduled({}) done.", Thread.currentThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the exported actions
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||||
|
return Collections.singletonList(VeluxActions.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exported method (called by an OpenHAB Rules Action) to issue a reboot command to the hub.
|
||||||
|
*
|
||||||
|
* @return true if the command could be issued
|
||||||
|
*/
|
||||||
|
public boolean runReboot() {
|
||||||
|
logger.trace("runReboot() called on {}", getThing().getUID());
|
||||||
|
RunReboot bcp = thisBridge.bridgeAPI().runReboot();
|
||||||
|
if (bcp != null) {
|
||||||
|
// background execution of reboot process
|
||||||
|
getTaskExecutor().execute(() -> {
|
||||||
|
if (thisBridge.bridgeCommunicate(bcp)) {
|
||||||
|
logger.info("Reboot command {}sucessfully sent to {}", bcp.isCommunicationSuccessful() ? "" : "un",
|
||||||
|
getThing().getUID());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exported method (called by an OpenHAB Rules Action) to move an actuator relative to its current position
|
||||||
|
*
|
||||||
|
* @param nodeId the node to be moved
|
||||||
|
* @param relativePercent relative position change to the current position (-100% <= relativePercent <= +100%)
|
||||||
|
* @return true if the command could be issued
|
||||||
|
*/
|
||||||
|
public boolean moveRelative(int nodeId, int relativePercent) {
|
||||||
|
logger.trace("moveRelative() called on {}", getThing().getUID());
|
||||||
|
RunProductCommand bcp = thisBridge.bridgeAPI().runProductCommand();
|
||||||
|
if (bcp != null) {
|
||||||
|
bcp.setNodeAndMainParameter(nodeId, new VeluxProductPosition(new PercentType(Math.abs(relativePercent)))
|
||||||
|
.getAsRelativePosition((relativePercent >= 0)));
|
||||||
|
// background execution of moveRelative
|
||||||
|
getTaskExecutor().execute(() -> {
|
||||||
|
if (thisBridge.bridgeCommunicate(bcp)) {
|
||||||
|
logger.trace("moveRelative() command {}sucessfully sent to {}",
|
||||||
|
bcp.isCommunicationSuccessful() ? "" : "un", getThing().getUID());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If necessary initialise the task executor and return it
|
||||||
|
*
|
||||||
|
* @return the task executor
|
||||||
|
*/
|
||||||
|
private ExecutorService getTaskExecutor() {
|
||||||
|
ExecutorService taskExecutor = this.taskExecutor;
|
||||||
|
if (taskExecutor == null || taskExecutor.isShutdown()) {
|
||||||
|
taskExecutor = this.taskExecutor = Executors.newSingleThreadExecutor(getThreadFactory());
|
||||||
|
}
|
||||||
|
return taskExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If necessary initialise the thread factory and return it
|
||||||
|
*
|
||||||
|
* @return the thread factory
|
||||||
|
*/
|
||||||
|
public NamedThreadFactory getThreadFactory() {
|
||||||
|
NamedThreadFactory threadFactory = this.threadFactory;
|
||||||
|
if (threadFactory == null) {
|
||||||
|
threadFactory = new NamedThreadFactory(getThing().getUID().getAsString());
|
||||||
|
}
|
||||||
|
return threadFactory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,7 @@ public class VeluxHandler extends ExtendedBaseThingHandler {
|
|||||||
for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
|
for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
|
||||||
logger.trace("handleConfigurationUpdate(): found modified config entry {}.",
|
logger.trace("handleConfigurationUpdate(): found modified config entry {}.",
|
||||||
configurationParameter.getKey());
|
configurationParameter.getKey());
|
||||||
|
configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
|
||||||
}
|
}
|
||||||
// persist new configuration and reinitialize handler
|
// persist new configuration and reinitialize handler
|
||||||
dispose();
|
dispose();
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.velux.internal.handler.utils;
|
package org.openhab.binding.velux.internal.handler.utils;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingUID;
|
import org.openhab.core.thing.ThingUID;
|
||||||
@ -61,7 +62,7 @@ public class ThingProperty {
|
|||||||
* @param propertyName defines the property which is to be modified,
|
* @param propertyName defines the property which is to be modified,
|
||||||
* @param propertyValue defines the new property value.
|
* @param propertyValue defines the new property value.
|
||||||
*/
|
*/
|
||||||
public static void setValue(Thing thing, String propertyName, String propertyValue) {
|
public static void setValue(Thing thing, String propertyName, @Nullable String propertyValue) {
|
||||||
thing.setProperty(propertyName, propertyValue);
|
thing.setProperty(propertyName, propertyValue);
|
||||||
LOGGER.trace("setValue() {} set to {}.", propertyName, propertyValue);
|
LOGGER.trace("setValue() {} set to {}.", propertyName, propertyValue);
|
||||||
return;
|
return;
|
||||||
@ -75,7 +76,8 @@ public class ThingProperty {
|
|||||||
* @param propertyName defines the property which is to be modified.
|
* @param propertyName defines the property which is to be modified.
|
||||||
* @param propertyValue defines the new property value.
|
* @param propertyValue defines the new property value.
|
||||||
*/
|
*/
|
||||||
public static void setValue(ExtendedBaseBridgeHandler bridgeHandler, String propertyName, String propertyValue) {
|
public static void setValue(ExtendedBaseBridgeHandler bridgeHandler, String propertyName,
|
||||||
|
@Nullable String propertyValue) {
|
||||||
setValue(bridgeHandler.getThing(), propertyName, propertyValue);
|
setValue(bridgeHandler.getThing(), propertyName, propertyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ public class ThingProperty {
|
|||||||
* @param propertyValue defines the new property value.
|
* @param propertyValue defines the new property value.
|
||||||
*/
|
*/
|
||||||
public static void setValue(ExtendedBaseBridgeHandler bridgeHandler, ChannelUID channelUID, String propertyName,
|
public static void setValue(ExtendedBaseBridgeHandler bridgeHandler, ChannelUID channelUID, String propertyName,
|
||||||
String propertyValue) {
|
@Nullable String propertyValue) {
|
||||||
ThingUID channelTUID = channelUID.getThingUID();
|
ThingUID channelTUID = channelUID.getThingUID();
|
||||||
Thing thingOfChannel = bridgeHandler.getThing().getThing(channelTUID);
|
Thing thingOfChannel = bridgeHandler.getThing().getThing(channelTUID);
|
||||||
if (thingOfChannel == null) {
|
if (thingOfChannel == null) {
|
||||||
|
@ -121,10 +121,10 @@ public class VeluxExistingProducts {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
VeluxProduct thisProduct = this.get(bridgeProductIndex);
|
VeluxProduct thisProduct = this.get(bridgeProductIndex);
|
||||||
if (thisProduct.setState(productState) || thisProduct.setCurrentPosition(productPosition)
|
dirty |= thisProduct.setState(productState);
|
||||||
|| thisProduct.setTarget(productTarget)) {
|
dirty |= thisProduct.setCurrentPosition(productPosition);
|
||||||
dirty = true;
|
dirty |= thisProduct.setTarget(productTarget);
|
||||||
|
if (dirty) {
|
||||||
String uniqueIndex = thisProduct.isV2() ? thisProduct.getSerialNumber()
|
String uniqueIndex = thisProduct.isV2() ? thisProduct.getSerialNumber()
|
||||||
: thisProduct.getProductUniqueIndex();
|
: thisProduct.getProductUniqueIndex();
|
||||||
logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
|
logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
|
||||||
|
@ -57,6 +57,24 @@ public class VeluxProduct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// State (of movement) of an actuator
|
||||||
|
public static enum State {
|
||||||
|
NON_EXECUTING(0),
|
||||||
|
ERROR(1),
|
||||||
|
NOT_USED(2),
|
||||||
|
WAITING_FOR_POWER(3),
|
||||||
|
EXECUTING(4),
|
||||||
|
DONE(5),
|
||||||
|
MANUAL_OVERRIDE(0x80),
|
||||||
|
UNKNOWN(0xFF);
|
||||||
|
|
||||||
|
public final int value;
|
||||||
|
|
||||||
|
private State(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Class internal
|
// Class internal
|
||||||
|
|
||||||
private VeluxProductName name;
|
private VeluxProductName name;
|
||||||
@ -70,9 +88,9 @@ public class VeluxProduct {
|
|||||||
private int variation = 0;
|
private int variation = 0;
|
||||||
private int powerMode = 0;
|
private int powerMode = 0;
|
||||||
private String serialNumber = VeluxProductSerialNo.UNKNOWN;
|
private String serialNumber = VeluxProductSerialNo.UNKNOWN;
|
||||||
private int state = 0;
|
private int state = State.UNKNOWN.value;
|
||||||
private int currentPosition = 0;
|
private int currentPosition = 0;
|
||||||
private int target = 0;
|
private int targetPosition = 0;
|
||||||
private int remainingTime = 0;
|
private int remainingTime = 0;
|
||||||
private int timeStamp = 0;
|
private int timeStamp = 0;
|
||||||
|
|
||||||
@ -143,7 +161,7 @@ public class VeluxProduct {
|
|||||||
this.serialNumber = serialNumber;
|
this.serialNumber = serialNumber;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.currentPosition = currentPosition;
|
this.currentPosition = currentPosition;
|
||||||
this.target = target;
|
this.targetPosition = target;
|
||||||
this.remainingTime = remainingTime;
|
this.remainingTime = remainingTime;
|
||||||
this.timeStamp = timeStamp;
|
this.timeStamp = timeStamp;
|
||||||
}
|
}
|
||||||
@ -155,7 +173,7 @@ public class VeluxProduct {
|
|||||||
if (this.v2) {
|
if (this.v2) {
|
||||||
return new VeluxProduct(this.name, this.typeId, this.bridgeProductIndex, this.order, this.placement,
|
return new VeluxProduct(this.name, this.typeId, this.bridgeProductIndex, this.order, this.placement,
|
||||||
this.velocity, this.variation, this.powerMode, this.serialNumber, this.state, this.currentPosition,
|
this.velocity, this.variation, this.powerMode, this.serialNumber, this.state, this.currentPosition,
|
||||||
this.target, this.remainingTime, this.timeStamp);
|
this.targetPosition, this.remainingTime, this.timeStamp);
|
||||||
} else {
|
} else {
|
||||||
return new VeluxProduct(this.name, this.typeId, this.bridgeProductIndex);
|
return new VeluxProduct(this.name, this.typeId, this.bridgeProductIndex);
|
||||||
}
|
}
|
||||||
@ -302,7 +320,7 @@ public class VeluxProduct {
|
|||||||
* @return <b>target</b> as type int shows the target position of the current operation.
|
* @return <b>target</b> as type int shows the target position of the current operation.
|
||||||
*/
|
*/
|
||||||
public int getTarget() {
|
public int getTarget() {
|
||||||
return target;
|
return targetPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,12 +328,12 @@ public class VeluxProduct {
|
|||||||
* @return <b>modified</b> as boolean to signal a real modification.
|
* @return <b>modified</b> as boolean to signal a real modification.
|
||||||
*/
|
*/
|
||||||
public boolean setTarget(int newTarget) {
|
public boolean setTarget(int newTarget) {
|
||||||
if (this.target == newTarget) {
|
if (this.targetPosition == newTarget) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
logger.trace("setCurrentPosition(name={},index={}) target {} replaced by {}.", name.toString(),
|
logger.trace("setCurrentPosition(name={},index={}) target {} replaced by {}.", name.toString(),
|
||||||
bridgeProductIndex.toInt(), this.target, newTarget);
|
bridgeProductIndex.toInt(), this.targetPosition, newTarget);
|
||||||
this.target = newTarget;
|
this.targetPosition = newTarget;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,4 +351,35 @@ public class VeluxProduct {
|
|||||||
public int getTimeStamp() {
|
public int getTimeStamp() {
|
||||||
return timeStamp;
|
return timeStamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the display position of the actuator.
|
||||||
|
* <li>As a general rule it returns <b>currentPosition</b>, except as follows..
|
||||||
|
* <li>If the actuator is in a motion state it returns <b>targetPosition</b>
|
||||||
|
* <li>If the motion state is 'done' but the currentPosition is invalid it returns <b>targetPosition</b>
|
||||||
|
* <li>If the manual override flag is set it returns the <b>unknown</b> position value
|
||||||
|
*
|
||||||
|
* @return The display position of the actuator
|
||||||
|
*/
|
||||||
|
public int getDisplayPosition() {
|
||||||
|
// manual override flag set: position is 'unknown'
|
||||||
|
if ((state & State.MANUAL_OVERRIDE.value) != 0) {
|
||||||
|
return VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||||
|
}
|
||||||
|
// only check other conditions if targetPosition is valid and differs from currentPosition
|
||||||
|
if ((targetPosition != currentPosition) && (targetPosition <= VeluxProductPosition.VPP_VELUX_MAX)
|
||||||
|
&& (targetPosition >= VeluxProductPosition.VPP_VELUX_MIN)) {
|
||||||
|
int state = this.state & 0xf;
|
||||||
|
// actuator is in motion: for quicker UI update, return targetPosition
|
||||||
|
if ((state > State.ERROR.value) && (state < State.DONE.value)) {
|
||||||
|
return targetPosition;
|
||||||
|
}
|
||||||
|
// motion complete but currentPosition is not valid: return targetPosition
|
||||||
|
if ((state == State.DONE.value) && ((currentPosition > VeluxProductPosition.VPP_VELUX_MAX)
|
||||||
|
|| (currentPosition < VeluxProductPosition.VPP_VELUX_MIN))) {
|
||||||
|
return targetPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentPosition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,14 @@ public class VeluxProductPosition {
|
|||||||
|
|
||||||
private static final int VPP_OPENHAB_MIN = 0;
|
private static final int VPP_OPENHAB_MIN = 0;
|
||||||
private static final int VPP_OPENHAB_MAX = 100;
|
private static final int VPP_OPENHAB_MAX = 100;
|
||||||
private static final int VPP_VELUX_MIN = 0x0000;
|
|
||||||
private static final int VPP_VELUX_MAX = 0xc800;
|
|
||||||
private static final int VPP_VELUX_UNKNOWN = 0xF7FF;
|
|
||||||
|
|
||||||
private static final int VPP_VELUX_PERCENTAGE_MIN = 0xc900;
|
public static final int VPP_VELUX_MIN = 0x0000;
|
||||||
private static final int VPP_VELUX_PERCENTAGE_MAX = 0xd0d0;
|
public static final int VPP_VELUX_MAX = 0xc800;
|
||||||
|
public static final int VPP_VELUX_UNKNOWN = 0xF7FF;
|
||||||
|
|
||||||
|
// relative mode commands
|
||||||
|
private static final int VPP_VELUX_RELATIVE_ORIGIN = 0xCCE8;
|
||||||
|
private static final int VPP_VELUX_RELATIVE_RANGE = 1000; // same for positive and negative offsets
|
||||||
|
|
||||||
// Class internal
|
// Class internal
|
||||||
|
|
||||||
@ -159,15 +161,8 @@ public class VeluxProductPosition {
|
|||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
|
|
||||||
public static int getRelativePositionAsVeluxType(boolean upwards, PercentType position) {
|
public int getAsRelativePosition(boolean positive) {
|
||||||
float result = (VPP_VELUX_PERCENTAGE_MAX + VPP_VELUX_PERCENTAGE_MIN) / 2;
|
int offset = position.intValue() * VPP_VELUX_RELATIVE_RANGE / (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN);
|
||||||
if (upwards) {
|
return positive ? VPP_VELUX_RELATIVE_ORIGIN + offset : VPP_VELUX_RELATIVE_ORIGIN - offset;
|
||||||
result = result + (ONE * position.intValue() - VPP_OPENHAB_MIN) / (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN)
|
|
||||||
* ((VPP_VELUX_PERCENTAGE_MAX - VPP_VELUX_PERCENTAGE_MIN) / 2);
|
|
||||||
} else {
|
|
||||||
result = result - (ONE * position.intValue() - VPP_OPENHAB_MIN) / (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN)
|
|
||||||
* ((VPP_VELUX_PERCENTAGE_MAX - VPP_VELUX_PERCENTAGE_MIN) / 2);
|
|
||||||
}
|
|
||||||
return (int) result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public enum VeluxProductVelocity {
|
public enum VeluxProductVelocity {
|
||||||
DEFAULT((short) 0, "default"),
|
DEFAULT((short) 0, "default"),
|
||||||
SILENT((short) 1, "short"),
|
SILENT((short) 1, "silent"),
|
||||||
FAST((short) 2, "fast"),
|
FAST((short) 2, "fast"),
|
||||||
VELOCITY_NOT_AVAILABLE((short) 255, ""),
|
VELOCITY_NOT_AVAILABLE((short) 255, ""),
|
||||||
UNDEFTYPE((short) 0xffff, VeluxBindingConstants.UNKNOWN);
|
UNDEFTYPE((short) 0xffff, VeluxBindingConstants.UNKNOWN);
|
||||||
@ -69,7 +69,7 @@ public enum VeluxProductVelocity {
|
|||||||
return velocity;
|
return velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VeluxProductVelocity get(int velocity) {
|
public static VeluxProductVelocity get(short velocity) {
|
||||||
return LOOKUPTYPEID2ENUM.getOrDefault(velocity, VeluxProductVelocity.UNDEFTYPE);
|
return LOOKUPTYPEID2ENUM.getOrDefault(velocity, VeluxProductVelocity.UNDEFTYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,11 +43,11 @@
|
|||||||
<!-- Velux Bridge factory default -->
|
<!-- Velux Bridge factory default -->
|
||||||
<default>velux123</default>
|
<default>velux123</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="timeoutMsecs" type="integer" min="1" step="1" max="60000">
|
<parameter name="timeoutMsecs" type="integer" min="500" step="1" max="5000">
|
||||||
<label>@text/config.velux.bridge.timeoutMsecs.label</label>
|
<label>@text/config.velux.bridge.timeoutMsecs.label</label>
|
||||||
<description>@text/config.velux.bridge.timeoutMsecs.description</description>
|
<description>@text/config.velux.bridge.timeoutMsecs.description</description>
|
||||||
<required>false</required>
|
<required>false</required>
|
||||||
<default>500</default>
|
<default>2000</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="retries" type="integer" min="0" step="1" max="10">
|
<parameter name="retries" type="integer" min="0" step="1" max="10">
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<default>5</default>
|
<default>5</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="refreshMsecs" type="integer" min="1" step="1" max="60000">
|
<parameter name="refreshMsecs" type="integer" min="5000" step="1" max="60000">
|
||||||
<label>@text/config.velux.bridge.refreshMsecs.label</label>
|
<label>@text/config.velux.bridge.refreshMsecs.label</label>
|
||||||
<description>@text/config.velux.bridge.refreshMsecs.description</description>
|
<description>@text/config.velux.bridge.refreshMsecs.description</description>
|
||||||
<required>false</required>
|
<required>false</required>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<channel id="limitMinimum" typeId="limitMinimum"/>
|
<channel id="limitMinimum" typeId="limitMinimum"/>
|
||||||
<channel id="limitMaximum" typeId="limitMaximum"/>
|
<channel id="limitMaximum" typeId="limitMaximum"/>
|
||||||
</channels>
|
</channels>
|
||||||
<representation-property>serialNumber</representation-property>
|
<representation-property>serial</representation-property>
|
||||||
<config-description-ref uri="thing-type:velux:actuator"/>
|
<config-description-ref uri="thing-type:velux:actuator"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -17,5 +17,6 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<property name="bundleVersion">N/A</property>
|
<property name="bundleVersion">N/A</property>
|
||||||
</properties>
|
</properties>
|
||||||
|
<representation-property>bundleVersion</representation-property>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
<property name="check" />
|
<property name="check" />
|
||||||
-->
|
-->
|
||||||
</properties>
|
</properties>
|
||||||
|
<representation-property>ipAddress</representation-property>
|
||||||
|
|
||||||
<config-description-ref uri="bridge-type:velux:bridge"/>
|
<config-description-ref uri="bridge-type:velux:bridge"/>
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<channel id="limitMinimum" typeId="limitMinimum"/>
|
<channel id="limitMinimum" typeId="limitMinimum"/>
|
||||||
<channel id="limitMaximum" typeId="limitMaximum"/>
|
<channel id="limitMaximum" typeId="limitMaximum"/>
|
||||||
</channels>
|
</channels>
|
||||||
<representation-property>unique</representation-property>
|
<representation-property>serial</representation-property>
|
||||||
<config-description-ref uri="thing-type:velux:rollershutter"/>
|
<config-description-ref uri="thing-type:velux:rollershutter"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -17,9 +17,8 @@
|
|||||||
<category>Blinds</category>
|
<category>Blinds</category>
|
||||||
<channels>
|
<channels>
|
||||||
<channel id="action" typeId="action"/>
|
<channel id="action" typeId="action"/>
|
||||||
<channel id="silentMode" typeId="silentMode"/>
|
|
||||||
</channels>
|
</channels>
|
||||||
<representation-property>unique</representation-property>
|
<representation-property>sceneName</representation-property>
|
||||||
<config-description-ref uri="thing-type:velux:scene"/>
|
<config-description-ref uri="thing-type:velux:scene"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<channel id="limitMinimum" typeId="limitMinimum"/>
|
<channel id="limitMinimum" typeId="limitMinimum"/>
|
||||||
<channel id="limitMaximum" typeId="limitMaximum"/>
|
<channel id="limitMaximum" typeId="limitMaximum"/>
|
||||||
</channels>
|
</channels>
|
||||||
<representation-property>serialNumber</representation-property>
|
<representation-property>serial</representation-property>
|
||||||
<config-description-ref uri="thing-type:velux:window"/>
|
<config-description-ref uri="thing-type:velux:window"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
Loading…
Reference in New Issue
Block a user