added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
33
bundles/org.openhab.binding.neeo/.classpath
Normal file
33
bundles/org.openhab.binding.neeo/.classpath
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src/3rdparty/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.neeo/.project
Normal file
23
bundles/org.openhab.binding.neeo/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.neeo</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
19
bundles/org.openhab.binding.neeo/NOTICE
Normal file
19
bundles/org.openhab.binding.neeo/NOTICE
Normal file
@@ -0,0 +1,19 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
This binding includes a class from Jersey in the src/3rdparty/java folder.
|
||||
* License: CDDL License
|
||||
* Project: https://eclipse-ee4j.github.io/jersey/
|
||||
290
bundles/org.openhab.binding.neeo/README.md
Normal file
290
bundles/org.openhab.binding.neeo/README.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# NEEO Binding
|
||||
|
||||
This binding will discovery and control a NEEO Brain/Remote combination.
|
||||
NEEO is a smart home solution that includes an IP based remote.
|
||||
More information can be found at [NEEO](neeo.com) or in the forums at [NEEO Planet](https://planet.neeo.com).
|
||||
**This binding was not developed by NEEO** - so please don't ask questions on the NEEO forums.
|
||||
|
||||
Discovery occurs in three steps:
|
||||
|
||||
1. Discover your NEEO Brain.
|
||||
2. Once you have added a NEEO Brain, each Room will be discovered (which will include all Recipes and Scenarios).
|
||||
3. Once you have added a NEEO Room, each Device in the Room will be discovered (which will include all Macros for the Device).
|
||||
|
||||
The Recipes/Scenarios can then be started or stopped from openHAB or from the remote.
|
||||
If a Recipe/Scenario is started on the Brain, the status of the Recipe/Scenario will change in openHAB as well.
|
||||
Likewise, starting a Recipe/Scenario in openHAB will change the status on the Brain/remote.
|
||||
|
||||
This binding has been designed to compliment the NEEO Transport (which will expose openHAB Devices to the Brain[s] and expose each Brains Device to the other Brains).
|
||||
|
||||
The Room/Scenario/Recipe/Device information is read at startup time.
|
||||
If you make changes to any Room/Scenario/Recipe/Device, you will need to delete the item in question and re-discover the item (see discovery section below).
|
||||
|
||||
Since this binding allows you to trigger actions on NEEO Devices, this allows you to use the NEEO Brain as an IR solution to openHAB.
|
||||
In other words, if the NEEO Brain supports a Device over IR - then openHAB can use the NEEO Brain to control that Device regardless if there is an openHAB binding for it or not.
|
||||
|
||||
## openHAB Primary Address
|
||||
|
||||
This binding will use the primary address defined in openHAB to register itself with the NEEO Brain (allowing the NEEO Brain to forward events back to the binding).
|
||||
If you change the primary address option, this binding will de-register the old address and re-register the new address with the NEEO Brain.
|
||||
|
||||
## Definitions
|
||||
|
||||
A NEEO Scenario is a package of Recipes making up the Scenario.
|
||||
A Scenario is generally related to the buttons on the NEEO remote screen and are named "Watch a TV" or "Watch a Movie", etc.
|
||||
The Scenario will have one or more Recipes - most commonly two Recipes (a launch and poweroff Recipe).
|
||||
|
||||
A NEEO Recipes is a sequence of steps that accomplish a single task (launching a Scenario or turning off a Scenario).
|
||||
You can view/modify Recipes using the NEEO app.
|
||||
|
||||
You can run a Scenario by sending ON to the Scenario status channel and end the Scenario by sending OFF to the Scenario status channel.
|
||||
Likewise, you can send ON to any "launch" type Recipe status channel to start the Scenario and send ON to the "poweroff" type Recipe status channel to end the Scenario.
|
||||
Sending OFF to any Recipe status channel does nothing.
|
||||
|
||||
A NEEO Device is simply a collection of Macros that the Device supports.
|
||||
|
||||
A NEEO Macro is an action that can be performed on the Device.
|
||||
Actions can be triggered by sending ON to the channel
|
||||
|
||||
## Supported Things
|
||||
|
||||
* Bridge: NEEO Brain.
|
||||
This bridge represents a physical NEEO Brain and will contain one to many Rooms within it.
|
||||
|
||||
* Bridge: NEEO Room.
|
||||
Represents a Room on the NEEO Brain. Only rooms that have atleast one device or one recipe (custom if no devices) will be shown unless the brain configuration option "discoverEmptyRooms" is set to true.
|
||||
|
||||
* Thing: NEEO Device.
|
||||
|
||||
Represents a Device within the NEEO Room.
|
||||
|
||||
## Discover
|
||||
|
||||
NEEO Brains will be automatically discovered if mDNS/bonjour/zeroconf is installed on the local machine:
|
||||
|
||||
1. On Windows - installing iTunes will install bonjour.
|
||||
2. On Linux - please install zeroconf (see vendor documentation on how to do that).
|
||||
3. On Mac - should already be installed.
|
||||
|
||||
When you add the NEEO Brain, the Rooms on the Brain will then be auto discovered and placed in the inbox.
|
||||
When you add a Room, all Devices should be auto discovered and placed in the inbox.
|
||||
If you remove any discovered thing either from the inbox or from the added things, simply re-trigger a NEEO binding scan to rediscover it.
|
||||
|
||||
If you have the Brain both wired and wireless, the Brain will NOT be discovered twice (only once) and which interface is discovered depends on the timing of the beacon discover message (first one wins).
|
||||
If you discovered the wired first but want to use the wireless (or in the reverse), add the Brain and then modify its configuration to the IP address you want to use.
|
||||
|
||||
If the Brain is not discovered, here is list of the most common issues:
|
||||
|
||||
1. You can generally trigger discovery by starting up the NEEO APP on your mobile device, press MENU->NEEO Brain->Change Brain.
|
||||
This will generally send out the necessary mDNS broadcast messages to discovery the Brain.
|
||||
2. You did not wait long enough.
|
||||
I have noticed that it will take up to 5 minutes for the discovery to find the Brain.
|
||||
3. Local firewall is blocking the mDNS broadcast messages.
|
||||
Modify the firewall to allow mDNS packets - typically port 5353 and/or IP address 224.0.0.251
|
||||
4. The Brain is on a different subnet.
|
||||
Unless you have special routing rules, having the Brain on a different subnet than the openHAB instance will prevent discovery.
|
||||
Either add routing rules or move one of them to the same subnet.
|
||||
5. Bug in the mDNS library.
|
||||
Occasionally a broadcast will be missed and a simple openHAB restart will fix the issue.
|
||||
6. Brain isn't reachable.
|
||||
|
||||
Ping the Brain's address from the openHAB machine and see if it responds.
|
||||
|
||||
If none of the above work, there are a few more things you can try:
|
||||
|
||||
1. Use your local dns-sd command to see if you find the instance ("dns-sd -B _neeo._tcp").
|
||||
2. Manually configure the Brain and specify its IP address.
|
||||
3. Look in the issues forum on the NEEO SDK GitHub - specifically the [Brain Discovery not working](https://github.com/NEEOInc/neeo-sdk/issues/36).
|
||||
|
||||
## Forward Actions
|
||||
|
||||
The NEEO Brain has the option to forward all actions performed on it to a specific address.
|
||||
The forward actions will be a JSON string representation:
|
||||
|
||||
```
|
||||
{ "action": "xxx", "actionparameter": "xxx", "recipe": "xxx", "device": "xxx", "room": "xxx" }
|
||||
```
|
||||
|
||||
All parameters are optional (based on what action has been taken) with atleast one of them filled in.
|
||||
If the Recipe "Watch TV" is launched, the forward action would be:
|
||||
|
||||
```
|
||||
{ "action": "launch", "recipe": "Watch TV" }
|
||||
```
|
||||
|
||||
The NEEO Brain bridge will register itself as the destination for actions and has a trigger channel defined to accept the results of any forward action.
|
||||
An example rule might look like (for a Brain with an ID of d487672e):
|
||||
|
||||
```
|
||||
rule "NEEO"
|
||||
when
|
||||
Channel 'neeo:Brain:d487672e:forwardActions' triggered
|
||||
then
|
||||
logInfo("neeo", "action received")
|
||||
|
||||
var data = receivedEvent.getEvent()
|
||||
|
||||
logInfo("neeo", "data: {}", data)
|
||||
|
||||
var String recipe = transform("JSONPATH", "$.recipe", data);
|
||||
var String action = transform("JSONPATH", "$.action", data);
|
||||
var String device = transform("JSONPATH", "$.device", data);
|
||||
var String room = transform("JSONPATH", "$.room", data);
|
||||
var String actionparameter = transform("JSONPATH", "$.actionparameter", data);
|
||||
|
||||
logInfo("neeo", "action: {}, recipe: {}, device: {}, room: {}, actionparameter: {}", action, recipe, device, room, actionparameter)
|
||||
end
|
||||
```
|
||||
|
||||
Since the NEEO Brain ONLY allows a single forward actions URL, the NEEO Brain Bridge can be configured to:
|
||||
|
||||
1. Whether to register for forward actions or not.
|
||||
2. If forward actions has been registered, forward the action on to other URLs for processing.
|
||||
|
||||
This will allow you to use other devices that want to consume the forward actions (in addition to openHAB).
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The following are the configurations available to each of the bridges/things:
|
||||
|
||||
### NEEO Brain
|
||||
|
||||
| Name | Type | Required | Default | Description |
|
||||
|----------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------|
|
||||
| ipAddress | string | Yes | (None) | IP Address or host name of the NEEO Brain |
|
||||
| enableForwardActions | boolean | No | true | Whether to enable registration of forward actions or not |
|
||||
| forwardChain | string | No | blank | Comma delimited list of other IP addresses to forward actions to |
|
||||
| discoverEmptyRooms | boolean | No | false | Whether to discover Rooms with no Devices in them |
|
||||
| checkStatusInterval | number | No | 10 | The interval (in seconds) to check the status of the Brain. Specify <=0 to disable |
|
||||
|
||||
|
||||
|
||||
### NEEO Room
|
||||
|
||||
| Name | Type | Required | Default | Description |
|
||||
|----------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------|
|
||||
| roomKey | string | Yes | (None) | The unique key identifying the Room on the NEEO Brain |
|
||||
| refreshPolling | number | No | 120 | The interval (in seconds) to refresh active Scenarios. Specify <=0 to disable |
|
||||
| excludeThings | boolean | No | true | Exclude devices that are openHAB things (exposed by the NEEO Transport) |
|
||||
|
||||
### NEEO Device
|
||||
|
||||
| Name | Type | Required | Default | Description |
|
||||
|----------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------|
|
||||
| deviceKey | string | Yes | (None) | The unique key identifying the Device on the NEEO Brain |
|
||||
|
||||
|
||||
## Channels
|
||||
|
||||
### NEEO Brain
|
||||
|
||||
The NEEO Brain has the following channels:
|
||||
|
||||
| Channel Type ID | Read/Write | Item Type | Description |
|
||||
|--------------------|------------|--------------|--------------------------------------------------------------------------------------------|
|
||||
| forwardActions | R | Trigger | The forward actions channel |
|
||||
|
||||
|
||||
The following properties are available at the time of this writing:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| AirKey | Unknown (hints at a future airplay feature) |
|
||||
| Version | The software (not firmware) version of the NEEO Brain |
|
||||
| Is Configured | Whether the Brain has gone through its initial setup (true) or not (false) |
|
||||
| Label | Internal label assigned to the Brain |
|
||||
| Last Change | The time (in milliseconds) that the Brain was last updated (Recipe/Devices/etc change - again not firmware) |
|
||||
| Key | The unique identifier of the Brain |
|
||||
| Name | Internal name of the Brain |
|
||||
|
||||
|
||||
### NEEO Room
|
||||
|
||||
The NEEO Room is dynamically generated from the Brain.
|
||||
Each Room will dynamically generate the following channel groups:
|
||||
|
||||
1) Each Room will have exactly one "room-state" representing the current state of the Room.
|
||||
2) Each Room will have zero or more "room-recipe-xxx" (where xxx is the Recipe key) groups representing each Recipe in the Room.
|
||||
3) Each Room will have zero or more "room-scenario-xxx" (where xxx is the Scenario key) groups representing each Scenario in the Room.
|
||||
|
||||
#### Room State Group
|
||||
|
||||
The following channels will be in the Room state group:
|
||||
|
||||
| Channel Type ID | Read/Write | Item Type | Description |
|
||||
|--------------------|------------|--------------|--------------------------------------------------------------------------------------------|
|
||||
| currentStep* | R | trigger | Displays the current step being executed |
|
||||
|
||||
Current Step will ONLY be triggered if openHAB started the corresponding recipe (or scenario).
|
||||
If the NEEO Remote or NEEO App starts the recipe or scenario, the currentStep will never be triggered.
|
||||
|
||||
The current step is ONLY communicated from the Brain to the device that started the Recipe/Scenario.
|
||||
If the remote started the Recipe/Scenario, it will show the current step but openHAB will not be notified.
|
||||
Likewise if openHAB starts the Recipe/scenario, the remote will not be notified of the current step (although it will know the Recipe/Scenario became active).
|
||||
|
||||
#### Room Recipe Group
|
||||
|
||||
Each Room Recipe group will have the following channels:
|
||||
|
||||
| Channel Type ID | Read/Write | Item Type | Description |
|
||||
|--------------------|------------|--------------|---------------------------------------------------------------------------------------|
|
||||
| name | R | String | The name of the Recipe |
|
||||
| type* | R | String | The type of Recipe |
|
||||
| enabled | R | Switch | Whether the Recipe is enabled or not |
|
||||
| status | RW | Switch | Whether the Recipe is currently running (you can start/stop Recipes with this switch) |
|
||||
|
||||
The list of types is unknown at this time and the only ones I know of are "launch" and "poweroff".
|
||||
|
||||
Simply view the Recipe channel prior to using the type in a rule.
|
||||
|
||||
#### Room Scenario Group
|
||||
|
||||
Each Scenario group will have the following channels:
|
||||
|
||||
| Channel Type ID | Read/Write | Item Type | Description |
|
||||
|--------------------|------------|--------------|----------------------------------------------------------------------------------------- |
|
||||
| name | R | String | The name of the Scenario |
|
||||
| configured | R | Switch | Whether the Scenario is configured (or waiting additional input) |
|
||||
| status | RW | Switch | Whether the Scenario is currently running (you can start/stop Scenarios with this switch |
|
||||
|
||||
### NEEO Device
|
||||
|
||||
The NEEO Device is dynamically generated from the Brain.
|
||||
Each Device will have a single group (Macros) and that group will contain one or more channels defined by the Macro key (as defined by the NEEO Brain):
|
||||
|
||||
| Channel Type ID | Read/Write | Item Type | Description |
|
||||
|--------------------|------------|--------------|----------------------------------------------------------------------------------------- |
|
||||
| (macro key) | RW | Switch | Send ON to trigger Macro, resets to false afterwards |
|
||||
|
||||
## Full Example
|
||||
|
||||
.things
|
||||
|
||||
```
|
||||
neeo:brain:home [ ipAddress="192.168.1.24" ]
|
||||
neeo:room:attic (neeo:brain:home) [ roomKey="6277847230179180544" ]
|
||||
neeo:device:tv (neeo:room:attic) [ deviceKey="6343464057630097408" ]
|
||||
```
|
||||
|
||||
.items
|
||||
|
||||
```
|
||||
String Attic_RecipeName "Recipe Name [%s]" { channel="neeo:room-6277847230179180544:attic:room:recipe#name-6277847545657950208" }
|
||||
Switch Attic_RecipeEnabled "Recipe Enabled" { channel="neeo:room-6277847230179180544:attic:room:recipe#enabled-6277847545657950208" }
|
||||
Switch Attic_RecipeStatus "Running" { channel="neeo:room-6277847230179180544:attic:room:recipe#status-6277847545657950208" }
|
||||
String Attic_ScenarioName "Scenario Name [%s]" { channel="neeo:room-6277847230179180544:attic:room:scenario#name-6277847545657950208" }
|
||||
Switch Attic_ScenarioStatus "Running" { channel="neeo:room-6277847230179180544:attic:room:scenario#status-6277847545657950208" }
|
||||
Switch Attic_TvInput1 "Input1" { channel="neeo:device:tv:macros#status-6343464057651068928" }
|
||||
```
|
||||
|
||||
.sitemap
|
||||
|
||||
```
|
||||
sitemap demo label="NEEO" {
|
||||
Frame label="Attic" {
|
||||
Text item=Attic_RecipeName
|
||||
Switch item=Attic_RecipeStatus
|
||||
Switch item=Attic_TvInput1
|
||||
}
|
||||
}
|
||||
```
|
||||
38
bundles/org.openhab.binding.neeo/pom.xml
Normal file
38
bundles/org.openhab.binding.neeo/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.neeo</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Neeo Binding</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/3rdparty/java</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
355
bundles/org.openhab.binding.neeo/src/3rdparty/java/org/glassfish/jersey/filter/LoggingFilter.java
vendored
Normal file
355
bundles/org.openhab.binding.neeo/src/3rdparty/java/org/glassfish/jersey/filter/LoggingFilter.java
vendored
Normal file
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
||||
*
|
||||
* Copyright (c) 2011-2015 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* The contents of this file are subject to the terms of either the GNU
|
||||
* General Public License Version 2 only ("GPL") or the Common Development
|
||||
* and Distribution License("CDDL") (collectively, the "License"). You
|
||||
* may not use this file except in compliance with the License. You can
|
||||
* obtain a copy of the License at
|
||||
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
|
||||
* or packager/legal/LICENSE.txt. See the License for the specific
|
||||
* language governing permissions and limitations under the License.
|
||||
*
|
||||
* When distributing the software, include this License Header Notice in each
|
||||
* file and include the License file at packager/legal/LICENSE.txt.
|
||||
*
|
||||
* GPL Classpath Exception:
|
||||
* Oracle designates this particular file as subject to the "Classpath"
|
||||
* exception as provided by Oracle in the GPL Version 2 section of the License
|
||||
* file that accompanied this code.
|
||||
*
|
||||
* Modifications:
|
||||
* If applicable, add the following below the License Header, with the fields
|
||||
* enclosed by brackets [] replaced by your own identifying information:
|
||||
* "Portions Copyright [year] [name of copyright owner]"
|
||||
*
|
||||
* Contributor(s):
|
||||
* If you wish your version of this file to be governed by only the CDDL or
|
||||
* only the GPL Version 2, indicate your decision by adding "[Contributor]
|
||||
* elects to include this software in this distribution under the [CDDL or GPL
|
||||
* Version 2] license." If you don't indicate a single choice of license, a
|
||||
* recipient has the option to distribute your version of this file under
|
||||
* either the CDDL, the GPL Version 2 or to extend the choice of license to
|
||||
* its licensees as provided above. However, if you add GPL Version 2 code
|
||||
* and therefore, elected the GPL Version 2 license, then the option applies
|
||||
* only if the new code is made subject to such option by the copyright
|
||||
* holder.
|
||||
*/
|
||||
package org.glassfish.jersey.filter;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.client.ClientRequestFilter;
|
||||
import javax.ws.rs.client.ClientResponseContext;
|
||||
import javax.ws.rs.client.ClientResponseFilter;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
import javax.ws.rs.container.ContainerResponseContext;
|
||||
import javax.ws.rs.container.ContainerResponseFilter;
|
||||
import javax.ws.rs.container.PreMatching;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.WriterInterceptor;
|
||||
import javax.ws.rs.ext.WriterInterceptorContext;
|
||||
|
||||
/**
|
||||
* Universal logging filter.
|
||||
* <p/>
|
||||
* Can be used on client or server side. Has the highest priority.
|
||||
*
|
||||
* @author Pavel Bucek (pavel.bucek at oracle.com)
|
||||
* @author Martin Matula
|
||||
*/
|
||||
@PreMatching
|
||||
@Priority(Integer.MIN_VALUE)
|
||||
public final class LoggingFilter implements ContainerRequestFilter, ClientRequestFilter, ContainerResponseFilter,
|
||||
ClientResponseFilter, WriterInterceptor {
|
||||
|
||||
public static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(LoggingFilter.class.getName());
|
||||
private static final String NOTIFICATION_PREFIX = "* ";
|
||||
private static final String REQUEST_PREFIX = "> ";
|
||||
private static final String RESPONSE_PREFIX = "< ";
|
||||
private static final String ENTITY_LOGGER_PROPERTY = LoggingFilter.class.getName() + ".entityLogger";
|
||||
private static final String LOGGING_ID_PROPERTY = LoggingFilter.class.getName() + ".id";
|
||||
|
||||
private static final Comparator<Map.Entry<String, List<String>>> COMPARATOR = new Comparator<Map.Entry<String, List<String>>>() {
|
||||
|
||||
@Override
|
||||
public int compare(final Map.Entry<String, List<String>> o1, final Map.Entry<String, List<String>> o2) {
|
||||
return o1.getKey().compareToIgnoreCase(o2.getKey());
|
||||
}
|
||||
};
|
||||
|
||||
private static final int DEFAULT_MAX_ENTITY_SIZE = 8 * 1024;
|
||||
|
||||
//
|
||||
private final Logger logger;
|
||||
private final AtomicLong _id = new AtomicLong(0);
|
||||
private final boolean printEntity;
|
||||
private final int maxEntitySize;
|
||||
|
||||
/**
|
||||
* Create a logging filter logging the request and response to a default JDK
|
||||
* logger, named as the fully qualified class name of this class. Entity
|
||||
* logging is turned off by default.
|
||||
*/
|
||||
public LoggingFilter() {
|
||||
this(LOGGER, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a logging filter with custom logger and custom settings of entity
|
||||
* logging.
|
||||
*
|
||||
* @param logger the logger to log requests and responses.
|
||||
* @param printEntity if true, entity will be logged as well up to the default maxEntitySize, which is 8KB
|
||||
*/
|
||||
public LoggingFilter(final Logger logger, final boolean printEntity) {
|
||||
this.logger = logger;
|
||||
this.printEntity = printEntity;
|
||||
this.maxEntitySize = DEFAULT_MAX_ENTITY_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a logging filter with custom logger and entity logging turned on, but potentially limiting the size
|
||||
* of entity to be buffered and logged.
|
||||
*
|
||||
* @param logger the logger to log requests and responses.
|
||||
* @param maxEntitySize maximum number of entity bytes to be logged (and buffered) - if the entity is larger,
|
||||
* logging filter will print (and buffer in memory) only the specified number of bytes
|
||||
* and print "...more..." string at the end. Negative values are interpreted as zero.
|
||||
*/
|
||||
public LoggingFilter(final Logger logger, final int maxEntitySize) {
|
||||
this.logger = logger;
|
||||
this.printEntity = true;
|
||||
this.maxEntitySize = Math.max(0, maxEntitySize);
|
||||
}
|
||||
|
||||
private void log(final StringBuilder b) {
|
||||
if (logger != null) {
|
||||
logger.info(b.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private StringBuilder prefixId(final StringBuilder b, final long id) {
|
||||
b.append(Long.toString(id)).append(" ");
|
||||
return b;
|
||||
}
|
||||
|
||||
private void printRequestLine(final StringBuilder b, final String note, final long id, final String method,
|
||||
final URI uri) {
|
||||
prefixId(b, id).append(NOTIFICATION_PREFIX).append(note).append(" on thread ")
|
||||
.append(Thread.currentThread().getName()).append("\n");
|
||||
prefixId(b, id).append(REQUEST_PREFIX).append(method).append(" ").append(uri.toASCIIString()).append("\n");
|
||||
}
|
||||
|
||||
private void printResponseLine(final StringBuilder b, final String note, final long id, final int status) {
|
||||
prefixId(b, id).append(NOTIFICATION_PREFIX).append(note).append(" on thread ")
|
||||
.append(Thread.currentThread().getName()).append("\n");
|
||||
prefixId(b, id).append(RESPONSE_PREFIX).append(Integer.toString(status)).append("\n");
|
||||
}
|
||||
|
||||
private void printPrefixedHeaders(final StringBuilder b, final long id, final String prefix,
|
||||
final MultivaluedMap<String, String> headers) {
|
||||
for (final Map.Entry<String, List<String>> headerEntry : getSortedHeaders(headers.entrySet())) {
|
||||
final List<?> val = headerEntry.getValue();
|
||||
final String header = headerEntry.getKey();
|
||||
|
||||
if (val.size() == 1) {
|
||||
prefixId(b, id).append(prefix).append(header).append(": ").append(val.get(0)).append("\n");
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean add = false;
|
||||
for (final Object s : val) {
|
||||
if (add) {
|
||||
sb.append(',');
|
||||
}
|
||||
add = true;
|
||||
sb.append(s);
|
||||
}
|
||||
prefixId(b, id).append(prefix).append(header).append(": ").append(sb.toString()).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Map.Entry<String, List<String>>> getSortedHeaders(final Set<Map.Entry<String, List<String>>> headers) {
|
||||
final TreeSet<Map.Entry<String, List<String>>> sortedHeaders = new TreeSet<Map.Entry<String, List<String>>>(
|
||||
COMPARATOR);
|
||||
sortedHeaders.addAll(headers);
|
||||
return sortedHeaders;
|
||||
}
|
||||
|
||||
private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset)
|
||||
throws IOException {
|
||||
if (!stream.markSupported()) {
|
||||
stream = new BufferedInputStream(stream);
|
||||
}
|
||||
stream.mark(maxEntitySize + 1);
|
||||
final byte[] entity = new byte[maxEntitySize + 1];
|
||||
final int entitySize = stream.read(entity);
|
||||
b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));
|
||||
if (entitySize > maxEntitySize) {
|
||||
b.append("...more...");
|
||||
}
|
||||
b.append('\n');
|
||||
stream.reset();
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(final ClientRequestContext context) throws IOException {
|
||||
final long id = _id.incrementAndGet();
|
||||
context.setProperty(LOGGING_ID_PROPERTY, id);
|
||||
|
||||
final StringBuilder b = new StringBuilder();
|
||||
|
||||
printRequestLine(b, "Sending client request", id, context.getMethod(), context.getUri());
|
||||
printPrefixedHeaders(b, id, REQUEST_PREFIX, context.getStringHeaders());
|
||||
|
||||
if (printEntity && context.hasEntity()) {
|
||||
final OutputStream stream = new LoggingStream(b, context.getEntityStream());
|
||||
context.setEntityStream(stream);
|
||||
context.setProperty(ENTITY_LOGGER_PROPERTY, stream);
|
||||
// not calling log(b) here - it will be called by the interceptor
|
||||
} else {
|
||||
log(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext)
|
||||
throws IOException {
|
||||
final Object requestId = requestContext.getProperty(LOGGING_ID_PROPERTY);
|
||||
final long id = requestId != null ? (Long) requestId : _id.incrementAndGet();
|
||||
|
||||
final StringBuilder b = new StringBuilder();
|
||||
|
||||
printResponseLine(b, "Client response received", id, responseContext.getStatus());
|
||||
printPrefixedHeaders(b, id, RESPONSE_PREFIX, responseContext.getHeaders());
|
||||
|
||||
if (printEntity && responseContext.hasEntity()) {
|
||||
responseContext.setEntityStream(
|
||||
logInboundEntity(b, responseContext.getEntityStream(), getCharset(responseContext.getMediaType())));
|
||||
}
|
||||
|
||||
log(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(final ContainerRequestContext context) throws IOException {
|
||||
final long id = _id.incrementAndGet();
|
||||
context.setProperty(LOGGING_ID_PROPERTY, id);
|
||||
|
||||
final StringBuilder b = new StringBuilder();
|
||||
|
||||
printRequestLine(b, "Server has received a request", id, context.getMethod(),
|
||||
context.getUriInfo().getRequestUri());
|
||||
printPrefixedHeaders(b, id, REQUEST_PREFIX, context.getHeaders());
|
||||
|
||||
if (printEntity && context.hasEntity()) {
|
||||
context.setEntityStream(logInboundEntity(b, context.getEntityStream(), getCharset(context.getMediaType())));
|
||||
}
|
||||
|
||||
log(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
|
||||
throws IOException {
|
||||
final Object requestId = requestContext.getProperty(LOGGING_ID_PROPERTY);
|
||||
final long id = requestId != null ? (Long) requestId : _id.incrementAndGet();
|
||||
|
||||
final StringBuilder b = new StringBuilder();
|
||||
|
||||
printResponseLine(b, "Server responded with a response", id, responseContext.getStatus());
|
||||
printPrefixedHeaders(b, id, RESPONSE_PREFIX, responseContext.getStringHeaders());
|
||||
|
||||
if (printEntity && responseContext.hasEntity()) {
|
||||
final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());
|
||||
responseContext.setEntityStream(stream);
|
||||
requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);
|
||||
// not calling log(b) here - it will be called by the interceptor
|
||||
} else {
|
||||
log(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext)
|
||||
throws IOException, WebApplicationException {
|
||||
final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);
|
||||
writerInterceptorContext.proceed();
|
||||
if (stream != null) {
|
||||
log(stream.getStringBuilder(getCharset(writerInterceptorContext.getMediaType())));
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingStream extends FilterOutputStream {
|
||||
|
||||
private final StringBuilder b;
|
||||
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
LoggingStream(final StringBuilder b, final OutputStream inner) {
|
||||
super(inner);
|
||||
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
StringBuilder getStringBuilder(final Charset charset) {
|
||||
// write entity to the builder
|
||||
final byte[] entity = baos.toByteArray();
|
||||
|
||||
b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));
|
||||
if (entity.length > maxEntitySize) {
|
||||
b.append("...more...");
|
||||
}
|
||||
b.append('\n');
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final int i) throws IOException {
|
||||
if (baos.size() <= maxEntitySize) {
|
||||
baos.write(i);
|
||||
}
|
||||
out.write(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character set from a media type.
|
||||
* <p>
|
||||
* The character set is obtained from the media type parameter "charset".
|
||||
* If the parameter is not present the {@link #UTF8} charset is utilized.
|
||||
*
|
||||
* @param m the media type.
|
||||
* @return the character set.
|
||||
*/
|
||||
public static Charset getCharset(MediaType m) {
|
||||
String name = (m == null) ? null : m.getParameters().get(MediaType.CHARSET_PARAMETER);
|
||||
return (name == null) ? UTF8 : Charset.forName(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.neeo-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-neeo" description="NEEO Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.neeo/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.neeo.internal.models.ExecuteResult;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoBrain;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoForwardActions;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.binding.neeo.internal.net.HttpRequest;
|
||||
import org.openhab.binding.neeo.internal.net.HttpResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The class provides the API for communicating with a NEEO brain
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoBrainApi implements AutoCloseable {
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoBrainApi.class);
|
||||
|
||||
/** The gson used in communications */
|
||||
private final Gson gson = NeeoUtil.getGson();
|
||||
|
||||
/** The {@link HttpRequest} used for making requests */
|
||||
private final AtomicReference<HttpRequest> request = new AtomicReference<>(new HttpRequest());
|
||||
|
||||
/** The IP address of the neeo brain */
|
||||
private final NeeoUrlBuilder urlBuilder;
|
||||
|
||||
/**
|
||||
* Instantiates the API using the specified IP Address
|
||||
*
|
||||
* @param ipAddress the non-empty ip address
|
||||
*/
|
||||
public NeeoBrainApi(String ipAddress) {
|
||||
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
|
||||
|
||||
this.urlBuilder = new NeeoUrlBuilder(
|
||||
NeeoConstants.PROTOCOL + ipAddress + ":" + NeeoConstants.DEFAULT_BRAIN_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link NeeoBrain}
|
||||
*
|
||||
* @return the non-null {@link NeeoBrain}
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public NeeoBrain getBrain() throws IOException {
|
||||
final String url = urlBuilder.append(NeeoConstants.PROJECTS_HOME).toString();
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendGetCommand(url);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
|
||||
return gson.fromJson(resp.getContent(), NeeoBrain.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link NeeoRoom} from the brain for the specified room key
|
||||
*
|
||||
* @param roomKey the non-empty room key
|
||||
* @return the {@link NeeoRoom}
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public NeeoRoom getRoom(String roomKey) throws IOException {
|
||||
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
|
||||
|
||||
final String url = urlBuilder.append(NeeoConstants.GET_ROOM).sub("roomkey", roomKey).toString();
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendGetCommand(url);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
|
||||
return gson.fromJson(resp.getContent(), NeeoRoom.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the specified recipe in the specified room
|
||||
*
|
||||
* @param roomKey the non-empty room key
|
||||
* @param recipeKey the non-empty recipe key
|
||||
* @return the execute result
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
ExecuteResult executeRecipe(String roomKey, String recipeKey) throws IOException {
|
||||
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
final String url = urlBuilder.append(NeeoConstants.EXECUTE_RECIPE).sub("roomkey", roomKey)
|
||||
.sub("recipekey", recipeKey).toString();
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendGetCommand(url);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
|
||||
return gson.fromJson(resp.getContent(), ExecuteResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the specified scenario in the specified room
|
||||
*
|
||||
* @param roomKey the non-empty room key
|
||||
* @param scenarioKey the non-empty scenario key
|
||||
* @return the execute result
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
ExecuteResult stopScenario(String roomKey, String scenarioKey) throws IOException {
|
||||
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
|
||||
|
||||
final String url = urlBuilder.append(NeeoConstants.STOP_SCENARIO).sub("roomkey", roomKey)
|
||||
.sub("scenariokey", scenarioKey).toString();
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendGetCommand(url);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
|
||||
return gson.fromJson(resp.getContent(), ExecuteResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active scenarios.
|
||||
*
|
||||
* @return the non-null, possibly empty list of active scenarios keys
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public String[] getActiveScenarios() throws IOException {
|
||||
final String url = urlBuilder.append(NeeoConstants.GET_ACTIVESCENARIOS).toString();
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendGetCommand(url);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
|
||||
return gson.fromJson(resp.getContent(), String[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger macro on a specified device in the specified room.
|
||||
*
|
||||
* @param roomKey the non-null room key
|
||||
* @param deviceKey the non-null device key
|
||||
* @param macroKey the non-null macro key
|
||||
* @return the execute result
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
ExecuteResult triggerMacro(String roomKey, String deviceKey, String macroKey) throws IOException {
|
||||
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(deviceKey, "deviceKey cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(macroKey, "macroKey cannot be empty");
|
||||
|
||||
final String url = urlBuilder.append(NeeoConstants.TRIGGER_MACRO).sub("roomkey", roomKey)
|
||||
.sub("devicekey", deviceKey).sub("macrokey", macroKey).toString();
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendGetCommand(url);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
|
||||
return gson.fromJson(resp.getContent(), ExecuteResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our API with the brain's forward actions.
|
||||
*
|
||||
* @param url the non-null URL to register to
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public void registerForwardActions(URL url) throws IOException {
|
||||
Objects.requireNonNull(url, "url cannot be null");
|
||||
|
||||
final String brainUrl = urlBuilder.append(NeeoConstants.FORWARD_ACTIONS).toString();
|
||||
|
||||
logger.debug("Registering forward actions {} using callback {}", brainUrl, url.toExternalForm());
|
||||
|
||||
final String forwardActions = gson.toJson(new NeeoForwardActions(url.getHost(), url.getPort(), url.getPath()));
|
||||
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl, forwardActions);
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregister our API with the brain's forward actions.
|
||||
*
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public void deregisterForwardActions() throws IOException {
|
||||
final String brainUrl = urlBuilder.append(NeeoConstants.FORWARD_ACTIONS).toString();
|
||||
|
||||
logger.debug("Deregistering forward actions callback {}", brainUrl);
|
||||
final HttpRequest rqst = request.get();
|
||||
final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl, "");
|
||||
if (resp.getHttpCode() != HttpStatus.OK_200) {
|
||||
throw resp.createException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
NeeoUtil.close(request.getAndSet(new HttpRequest()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to help build NEEO URLs
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
private class NeeoUrlBuilder {
|
||||
/** The current url */
|
||||
private final String url;
|
||||
|
||||
/**
|
||||
* Create a new class from the given URL
|
||||
*
|
||||
* @param url a non-null, non-empty URL
|
||||
*/
|
||||
NeeoUrlBuilder(final String url) {
|
||||
NeeoUtil.requireNotEmpty(url, "url cannot be empty");
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitutes '{key}' into value from the URL and returns a new {@link NeeoUrlBuilder} with the new URL
|
||||
*
|
||||
* @param key a non-null, non-empty key
|
||||
* @param value a non-null, non-empty key
|
||||
* @return a non-null {@link NeeoUrlBuilder} with the new URL
|
||||
*/
|
||||
NeeoUrlBuilder sub(String key, String value) {
|
||||
NeeoUtil.requireNotEmpty(key, "key cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(value, "value cannot be empty");
|
||||
|
||||
final String newUrl = url.replace("{" + key + "}", value);
|
||||
return new NeeoUrlBuilder(newUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the specified value to the URL and returns the new {@link NeeoUrlBuilder} with the new URL
|
||||
*
|
||||
* @param value a non-null, non-empty value
|
||||
* @return a non-null {@link NeeoUrlBuilder} with the new URL
|
||||
*/
|
||||
NeeoUrlBuilder append(String value) {
|
||||
NeeoUtil.requireNotEmpty(value, "value cannot be empty");
|
||||
|
||||
return new NeeoUrlBuilder(url + (value.startsWith("/") ? value : ("/" + value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.handler.NeeoBrainHandler;
|
||||
|
||||
/**
|
||||
* The configuration class a neeo brain and used by {@link NeeoBrainHandler}
|
||||
*
|
||||
* @author Tim Roberts - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoBrainConfig {
|
||||
|
||||
/** The ip address */
|
||||
@Nullable
|
||||
private String ipAddress;
|
||||
|
||||
/** Whether to enable forward actions */
|
||||
private boolean enableForwardActions;
|
||||
|
||||
/** The forward actions chain (comma delimited) */
|
||||
@Nullable
|
||||
private String forwardChain;
|
||||
|
||||
/** Whether to discover empty rooms or not */
|
||||
private boolean discoverEmptyRooms;
|
||||
|
||||
/** The check status interval (in seconds) */
|
||||
private int checkStatusInterval;
|
||||
|
||||
/**
|
||||
* Gets the ip address.
|
||||
*
|
||||
* @return the ip address
|
||||
*/
|
||||
@Nullable
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ip address.
|
||||
*
|
||||
* @param ipAddress the new ip address
|
||||
*/
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if forward actions is enabled
|
||||
*
|
||||
* @return true for enabled, false otherwise
|
||||
*/
|
||||
public boolean isEnableForwardActions() {
|
||||
return enableForwardActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to enable forward actions
|
||||
*
|
||||
* @param enableForwardActions true to enable, false otherwise
|
||||
*/
|
||||
public void setEnableForwardActions(boolean enableForwardActions) {
|
||||
this.enableForwardActions = enableForwardActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the forward chain
|
||||
*
|
||||
* @return the forward chain
|
||||
*/
|
||||
@Nullable
|
||||
public String getForwardChain() {
|
||||
return forwardChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the forward change
|
||||
*
|
||||
* @param forwardChain the forward chain
|
||||
*/
|
||||
public void setForwardChain(String forwardChain) {
|
||||
this.forwardChain = forwardChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether empty rooms should be discovered or not
|
||||
*
|
||||
* @return true to discover empty rooms, false otherwise
|
||||
*/
|
||||
public boolean isDiscoverEmptyRooms() {
|
||||
return discoverEmptyRooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's whether to discover empty rooms
|
||||
*
|
||||
* @param discoverEmptyRooms true to discover, false otherwise
|
||||
*/
|
||||
public void setDiscoverEmptyRooms(boolean discoverEmptyRooms) {
|
||||
this.discoverEmptyRooms = discoverEmptyRooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interval (in seconds) to check the brain status
|
||||
*
|
||||
* @return the check status interval (negative to disable)
|
||||
*/
|
||||
public int getCheckStatusInterval() {
|
||||
return checkStatusInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interval (in seconds) to check the brain status
|
||||
*
|
||||
* @param checkStatusInterval return the check status interval (negative to disable)
|
||||
*/
|
||||
public void setCheckStatusInterval(int checkStatusInterval) {
|
||||
this.checkStatusInterval = checkStatusInterval;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link NeeoConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoConstants {
|
||||
|
||||
/** The main binding */
|
||||
public static final String BINDING_ID = "neeo";
|
||||
|
||||
/** The various bridge/thing UIDs */
|
||||
public static final ThingTypeUID BRIDGE_TYPE_BRAIN = new ThingTypeUID(BINDING_ID, "brain");
|
||||
public static final ThingTypeUID BRIDGE_TYPE_ROOM = new ThingTypeUID(BINDING_ID, "room");
|
||||
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
|
||||
|
||||
/** The MDNS type for the NEEO brain */
|
||||
public static final String NEEO_MDNS_TYPE = "_neeo._tcp.local.";
|
||||
|
||||
/** Various config related */
|
||||
public static final String CONFIG_IPADDRESS = "ipAddress";
|
||||
public static final String CONFIG_ENABLEFORWARDACTIONS = "enableForwardActions";
|
||||
public static final String CONFIG_REFRESH_POLLING = "refreshPolling";
|
||||
public static final String CONFIG_DEVICEKEY = "deviceKey";
|
||||
public static final String CONFIG_ROOMKEY = "roomKey";
|
||||
public static final String CONFIG_EXCLUDE_THINGS = "excludeThings";
|
||||
|
||||
/** Brain channels */
|
||||
public static final String CHANNEL_BRAIN_FOWARDACTIONS = "forwardActions";
|
||||
|
||||
/** The various room channel constants */
|
||||
public static final String ROOM_CHANNEL_NAME = "name";
|
||||
public static final String ROOM_CHANNEL_TYPE = "type";
|
||||
public static final String ROOM_CHANNEL_ENABLED = "enabled";
|
||||
public static final String ROOM_CHANNEL_CONFIGURED = "configured";
|
||||
public static final String ROOM_CHANNEL_STATUS = "status";
|
||||
public static final String ROOM_CHANNEL_CURRENTSTEP = "currentStep";
|
||||
public static final String ROOM_GROUP_STATE_ID = "state";
|
||||
public static final String ROOM_GROUP_SCENARIO_ID = "scenario";
|
||||
public static final String ROOM_GROUP_RECIPE_ID = "recipe";
|
||||
public static final ChannelTypeUID ROOM_STATE_CURRENTSTEP_UID = new ChannelTypeUID(BINDING_ID,
|
||||
"room-state-currentstep");
|
||||
public static final ChannelTypeUID ROOM_SCENARIO_NAME_UID = new ChannelTypeUID(BINDING_ID, "room-scenario-name");
|
||||
public static final ChannelTypeUID ROOM_SCENARIO_CONFIGURED_UID = new ChannelTypeUID(BINDING_ID,
|
||||
"room-scenario-configured");
|
||||
public static final ChannelTypeUID ROOM_SCENARIO_STATUS_UID = new ChannelTypeUID(BINDING_ID,
|
||||
"room-scenario-status");
|
||||
public static final ChannelTypeUID ROOM_RECIPE_NAME_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-name");
|
||||
public static final ChannelTypeUID ROOM_RECIPE_TYPE_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-type");
|
||||
public static final ChannelTypeUID ROOM_RECIPE_ENABLED_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-enabled");
|
||||
public static final ChannelTypeUID ROOM_RECIPE_STATUS_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-status");
|
||||
|
||||
/** The various device channel constants */
|
||||
public static final String DEVICE_CHANNEL_STATUS = "status";
|
||||
public static final String DEVICE_GROUP_MACROS_ID = "macros";
|
||||
public static final ChannelTypeUID DEVICE_MACRO_STATUS_UID = new ChannelTypeUID(BINDING_ID, "device-macros-status");
|
||||
|
||||
public static final String WEBAPP_FORWARDACTIONS = "/neeo/binding/{brainid}/forwardactions";
|
||||
|
||||
/** The default port the brain listens on. */
|
||||
public static final int DEFAULT_BRAIN_PORT = 3000;
|
||||
public static final int DEFAULT_BRAIN_HTTP_PORT = 8080;
|
||||
|
||||
/** The default protocol for the brain. */
|
||||
public static final String PROTOCOL = "http://";
|
||||
|
||||
/** The brain API constants */
|
||||
private static final String NEEO_VERSION = "/v1";
|
||||
public static final String FORWARD_ACTIONS = NEEO_VERSION + "/forwardactions";
|
||||
public static final String PROJECTS_HOME = NEEO_VERSION + "/projects/home";
|
||||
public static final String GET_ACTIVESCENARIOS = PROJECTS_HOME + "/activescenariokeys";
|
||||
public static final String GET_ROOM = PROJECTS_HOME + "/rooms/{roomkey}";
|
||||
public static final String EXECUTE_RECIPE = PROJECTS_HOME + "/rooms/{roomkey}/recipes/{recipekey}/execute";
|
||||
public static final String STOP_SCENARIO = PROJECTS_HOME + "/rooms/{roomkey}/scenarios/{scenariokey}/poweroff";
|
||||
public static final String TRIGGER_MACRO = PROJECTS_HOME
|
||||
+ "/rooms/{roomkey}/devices/{devicekey}/macros/{macrokey}/trigger";
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.handler.NeeoDeviceHandler;
|
||||
|
||||
/**
|
||||
* THe configuration class for the device used by {@link NeeoDeviceHandler}
|
||||
*
|
||||
* @author Tim Roberts - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDeviceConfig {
|
||||
|
||||
/** The NEEO device key */
|
||||
@Nullable
|
||||
private String deviceKey;
|
||||
|
||||
/**
|
||||
* Gets the device key
|
||||
*
|
||||
* @return the device key
|
||||
*/
|
||||
@Nullable
|
||||
public String getDeviceKey() {
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the device key.
|
||||
*
|
||||
* @param deviceKey the new device key
|
||||
*/
|
||||
public void setDeviceKey(String deviceKey) {
|
||||
this.deviceKey = deviceKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevice;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevices;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoMacro;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This protocol class for a Neeo Device
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDeviceProtocol {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoDeviceProtocol.class);
|
||||
|
||||
/** The {@link NeeoHandlerCallback} */
|
||||
private final NeeoHandlerCallback callback;
|
||||
|
||||
/** The room key */
|
||||
private final String roomKey;
|
||||
|
||||
/** The device key */
|
||||
private final String deviceKey;
|
||||
|
||||
/** The {@link NeeoDevice} in the room */
|
||||
private final NeeoDevice neeoDevice;
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo device protocol.
|
||||
*
|
||||
* @param callback the non-null callback
|
||||
* @param roomKey the non-empty room key
|
||||
* @param deviceKey the non-empty device key
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public NeeoDeviceProtocol(NeeoHandlerCallback callback, String roomKey, String deviceKey) throws IOException {
|
||||
Objects.requireNonNull(callback, "callback cannot be null");
|
||||
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(deviceKey, "deviceKey cannot be empty");
|
||||
|
||||
this.roomKey = roomKey;
|
||||
this.callback = callback;
|
||||
this.deviceKey = deviceKey;
|
||||
|
||||
final NeeoBrainApi api = callback.getApi();
|
||||
if (api == null) {
|
||||
throw new IllegalArgumentException("NeeoBrainApi cannot be null");
|
||||
}
|
||||
|
||||
final NeeoRoom neeoRoom = api.getRoom(roomKey);
|
||||
|
||||
final NeeoDevices devices = neeoRoom.getDevices();
|
||||
final NeeoDevice device = devices.getDevice(deviceKey);
|
||||
if (device == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Device (" + deviceKey + ") was not found in the NEEO Brain for room (" + roomKey + ")");
|
||||
}
|
||||
|
||||
neeoDevice = device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the callback being used
|
||||
*
|
||||
* @return the non-null callback
|
||||
*/
|
||||
public NeeoHandlerCallback getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the macro status.
|
||||
*
|
||||
* @param macroKey the non-null macro key
|
||||
*/
|
||||
public void refreshMacroStatus(String macroKey) {
|
||||
NeeoUtil.requireNotEmpty(macroKey, "macroKey cannot be empty");
|
||||
|
||||
final NeeoMacro macro = neeoDevice.getMacros().getMacro(macroKey);
|
||||
if (macro != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.DEVICE_GROUP_MACROS_ID,
|
||||
NeeoConstants.DEVICE_CHANNEL_STATUS, macroKey), OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the macro status. If the status is true, the macro will be triggered. If false, nothing occurs
|
||||
*
|
||||
* @param macroKey the non-null macro key
|
||||
* @param start whether to start (true) or stop (false) the macro
|
||||
*/
|
||||
public void setMacroStatus(String macroKey, boolean start) {
|
||||
NeeoUtil.requireNotEmpty(macroKey, "macroKey cannot be empty");
|
||||
|
||||
final NeeoBrainApi api = callback.getApi();
|
||||
if (api == null) {
|
||||
logger.debug("API is null [likely bridge is offline]");
|
||||
} else {
|
||||
try {
|
||||
if (start) {
|
||||
api.triggerMacro(roomKey, deviceKey, macroKey);
|
||||
|
||||
// NEEO macros are not what we generally think of for macros
|
||||
// Trigger a NEEO macro is simply asking the brain to send an IR pulse
|
||||
// for whatever the macro is linked up to (POWER ON would send the IR
|
||||
// pulse for the specified device). Because of this, the execution of the
|
||||
// macro will never take more than 100ms to complete. Since we get no
|
||||
// feedback from the brain whether the macro has executed or completed
|
||||
// AND it's impossible to tell if any macro is executing or not (no equivalent
|
||||
// API to poll for), we simply refresh the status back to OFF after 500ms
|
||||
callback.scheduleTask(() -> {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.DEVICE_GROUP_MACROS_ID,
|
||||
NeeoConstants.DEVICE_CHANNEL_STATUS, macroKey), OnOffType.OFF);
|
||||
}, 500);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Some macros have issues executing on the NEEO Brain (depends on the firmware)
|
||||
// and IO exception will be thrown if the macro encounters an issue
|
||||
// (mostly it depends on the state of the brain - if it's starting up or in the process
|
||||
// of executing a long scenario - the macro will likely timeout or simply throw an exception)
|
||||
// Because of this, we simply log the error versus taking the binding offline
|
||||
logger.warn(
|
||||
"Exception occurred during execution of a macro (may need to update the brain firmware): {}",
|
||||
e.getMessage(), e);
|
||||
// callback.statusChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
*
|
||||
* This interface is used to provide a callback mechanism between a @link {@link ThingHandler} and the assoicated
|
||||
* protocol.
|
||||
* This is necessary since the status and state of a bridge/thing is private and the protocol handler cannot access it
|
||||
* directly.
|
||||
*
|
||||
* @author Tim Roberts - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface NeeoHandlerCallback {
|
||||
/**
|
||||
* Callback to the bridge/thing to update the status of the bridge/thing.
|
||||
*
|
||||
* @param status a non-null {@link ThingStatus}
|
||||
* @param detail a non-null {@link ThingStatusDetail}
|
||||
* @param msg a possibly null, possibly empty message
|
||||
*/
|
||||
void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg);
|
||||
|
||||
/**
|
||||
* Callback to the bridge/thing to update the state of a channel in the bridge/thing.
|
||||
*
|
||||
* @param channelId the non-null, non-empty channel id
|
||||
* @param state the new non-null {@State}
|
||||
*/
|
||||
void stateChanged(String channelId, State state);
|
||||
|
||||
/**
|
||||
* Callback to set a property for the thing.
|
||||
*
|
||||
* @param propertyName a non-null, non-empty property name
|
||||
* @param propertyValue a non-null, possibly empty property value
|
||||
*/
|
||||
void setProperty(String propertyName, String propertyValue);
|
||||
|
||||
/**
|
||||
* Schedule a task to be executed in the future
|
||||
*
|
||||
* @param task the non-null task
|
||||
* @param milliSeconds the milliseconds (>0)
|
||||
*/
|
||||
void scheduleTask(Runnable task, long milliSeconds);
|
||||
|
||||
/**
|
||||
* Callback to trigger an event
|
||||
*
|
||||
* @param channelID a non-null, non-empty channel id
|
||||
* @param event a possibly null, possibly empty event
|
||||
*/
|
||||
void triggerEvent(String channelID, String event);
|
||||
|
||||
/**
|
||||
* Callback to retrieve the current {@link NeeoBrainApi}
|
||||
*
|
||||
* @return a possibly null {@link NeeoBrainApi}
|
||||
*/
|
||||
@Nullable
|
||||
NeeoBrainApi getApi();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.handler.NeeoRoomHandler;
|
||||
|
||||
/**
|
||||
* THe configuration class for the room used by {@link NeeoRoomHandler}
|
||||
*
|
||||
* @author Tim Roberts - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRoomConfig {
|
||||
|
||||
/** The NEEO room key */
|
||||
@Nullable
|
||||
private String roomKey;
|
||||
|
||||
/** The refresh polling (in seconds) */
|
||||
private int refreshPolling;
|
||||
|
||||
/** Whether to exclude things */
|
||||
private boolean excludeThings;
|
||||
|
||||
/**
|
||||
* Gets the room key
|
||||
*
|
||||
* @return the room key
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomKey() {
|
||||
return roomKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the room key.
|
||||
*
|
||||
* @param roomKey the new room key
|
||||
*/
|
||||
public void setRoomKey(String roomKey) {
|
||||
this.roomKey = roomKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the refresh polling (in seconds)
|
||||
*
|
||||
* @return the refresh polling
|
||||
*/
|
||||
public int getRefreshPolling() {
|
||||
return refreshPolling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's the refresh polling
|
||||
*
|
||||
* @param refreshPolling the refresh polling
|
||||
*/
|
||||
public void setRefreshPolling(int refreshPolling) {
|
||||
this.refreshPolling = refreshPolling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to exclude things or not
|
||||
*
|
||||
* @return true to exclude, false otherwise
|
||||
*/
|
||||
public boolean isExcludeThings() {
|
||||
return excludeThings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to exclude things
|
||||
*
|
||||
* @param excludeThings true to exclude, false otherwise
|
||||
*/
|
||||
public void setExcludeThings(boolean excludeThings) {
|
||||
this.excludeThings = excludeThings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.models.ExecuteResult;
|
||||
import org.openhab.binding.neeo.internal.models.ExecuteStep;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoAction;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRecipe;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRecipes;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoScenario;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This protocol class for a Neeo Room
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRoomProtocol {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoRoomProtocol.class);
|
||||
|
||||
/** The {@link NeeoHandlerCallback} */
|
||||
private final NeeoHandlerCallback callback;
|
||||
|
||||
/** The room key */
|
||||
private final String roomKey;
|
||||
|
||||
/** The {@link NeeoRoom} */
|
||||
private final NeeoRoom neeoRoom;
|
||||
|
||||
/** The currently active scenarios */
|
||||
private final AtomicReference<String[]> activeScenarios = new AtomicReference<>(new String[0]);
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo room protocol.
|
||||
*
|
||||
* @param callback the non-null callback
|
||||
* @param roomKey the non-empty room key
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
public NeeoRoomProtocol(NeeoHandlerCallback callback, String roomKey) throws IOException {
|
||||
Objects.requireNonNull(callback, "callback cannot be null");
|
||||
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
|
||||
|
||||
this.callback = callback;
|
||||
this.roomKey = roomKey;
|
||||
|
||||
final NeeoBrainApi api = callback.getApi();
|
||||
if (api == null) {
|
||||
throw new IllegalArgumentException("NeeoBrainApi cannot be null");
|
||||
}
|
||||
|
||||
neeoRoom = api.getRoom(roomKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the callback being used
|
||||
*
|
||||
* @return the non-null callback
|
||||
*/
|
||||
public NeeoHandlerCallback getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the action if it applies to this room
|
||||
*
|
||||
* @param action a non-null action to process
|
||||
*/
|
||||
public void processAction(NeeoAction action) {
|
||||
Objects.requireNonNull(action, "action cannot be null");
|
||||
|
||||
final NeeoRecipes recipes = neeoRoom.getRecipes();
|
||||
final boolean launch = StringUtils.equalsIgnoreCase(NeeoRecipe.LAUNCH, action.getAction());
|
||||
final boolean poweroff = StringUtils.equalsIgnoreCase(NeeoRecipe.POWEROFF, action.getAction());
|
||||
|
||||
// Can't be both true but if both false - it's neither one
|
||||
if (launch == poweroff) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String recipeName = action.getRecipe();
|
||||
final NeeoRecipe recipe = recipeName == null ? null : recipes.getRecipeByName(recipeName);
|
||||
final String scenarioKey = recipe == null ? null : recipe.getScenarioKey();
|
||||
|
||||
if (scenarioKey != null && StringUtils.isNotEmpty(scenarioKey)) {
|
||||
processScenarioChange(scenarioKey, launch);
|
||||
} else {
|
||||
logger.debug("Could not find a recipe named '{}' for the action {}", recipeName, action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a change to the scenario (whether it's been launched or not)
|
||||
*
|
||||
* @param scenarioKey a non-null, non-empty scenario key
|
||||
* @param launch true if the scenario was launched, false otherwise
|
||||
*/
|
||||
private void processScenarioChange(String scenarioKey, boolean launch) {
|
||||
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
|
||||
|
||||
final String[] activeScenarios = this.activeScenarios.get();
|
||||
final int idx = ArrayUtils.indexOf(activeScenarios, scenarioKey);
|
||||
|
||||
// already set that way
|
||||
if ((idx < 0 && !launch) || (idx >= 0 && launch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String[] newScenarios = idx >= 0 ? (String[]) ArrayUtils.remove(activeScenarios, idx)
|
||||
: (String[]) ArrayUtils.add(activeScenarios, scenarioKey);
|
||||
|
||||
this.activeScenarios.set(newScenarios);
|
||||
|
||||
refreshScenarioStatus(scenarioKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh state of the room - currently only refreshes the active scenarios via {@link #refreshActiveScenarios()}
|
||||
*/
|
||||
public void refreshState() {
|
||||
refreshActiveScenarios();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the recipe name
|
||||
*
|
||||
* @param recipeKey the non-empty recipe key
|
||||
*/
|
||||
public void refreshRecipeName(String recipeKey) {
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
|
||||
if (recipe != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_NAME, recipeKey), new StringType(recipe.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the recipe type
|
||||
*
|
||||
* @param recipeKey the non-empty recipe key
|
||||
*/
|
||||
public void refreshRecipeType(String recipeKey) {
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
|
||||
if (recipe != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_TYPE, recipeKey), new StringType(recipe.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh whether the recipe is enabled
|
||||
*
|
||||
* @param recipeKey the non-null recipe key
|
||||
*/
|
||||
public void refreshRecipeEnabled(String recipeKey) {
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
|
||||
if (recipe != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_ENABLED, recipeKey), recipe.isEnabled() ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the recipe status.
|
||||
*
|
||||
* @param recipeKey the non-null recipe key
|
||||
*/
|
||||
public void refreshRecipeStatus(String recipeKey) {
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
|
||||
if (recipe != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_STATUS, recipeKey), OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the scenario name.
|
||||
*
|
||||
* @param scenarioKey the non-null scenario key
|
||||
*/
|
||||
public void refreshScenarioName(String scenarioKey) {
|
||||
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
|
||||
|
||||
final NeeoScenario scenario = neeoRoom.getScenarios().getScenario(scenarioKey);
|
||||
if (scenario != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_NAME, scenarioKey), new StringType(scenario.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh whether the scenario is configured.
|
||||
*
|
||||
* @param scenarioKey the non-null scenario key
|
||||
*/
|
||||
public void refreshScenarioConfigured(String scenarioKey) {
|
||||
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
|
||||
|
||||
final NeeoScenario scenario = neeoRoom.getScenarios().getScenario(scenarioKey);
|
||||
if (scenario != null) {
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_SCENARIO_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_ENABLED, scenarioKey),
|
||||
scenario.isConfigured() ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the scenario status.
|
||||
*
|
||||
* @param scenarioKey the non-null scenario key
|
||||
*/
|
||||
public void refreshScenarioStatus(String scenarioKey) {
|
||||
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
|
||||
|
||||
final NeeoScenario scenario = neeoRoom.getScenarios().getScenario(scenarioKey);
|
||||
if (scenario != null) {
|
||||
final String[] active = activeScenarios.get();
|
||||
final boolean isActive = ArrayUtils.contains(active, scenarioKey);
|
||||
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_SCENARIO_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_STATUS, scenarioKey), isActive ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh active scenarios
|
||||
*/
|
||||
private void refreshActiveScenarios() {
|
||||
final NeeoBrainApi api = callback.getApi();
|
||||
if (api == null) {
|
||||
logger.debug("API is null [likely bridge is offline]");
|
||||
} else {
|
||||
try {
|
||||
final String[] activeScenarios = api.getActiveScenarios();
|
||||
final String[] oldScenarios = this.activeScenarios.getAndSet(activeScenarios);
|
||||
|
||||
if (!ArrayUtils.isEquals(activeScenarios, oldScenarios)) {
|
||||
for (String scenario : activeScenarios) {
|
||||
refreshScenarioStatus(scenario);
|
||||
}
|
||||
|
||||
for (String oldScenario : oldScenarios) {
|
||||
if (!ArrayUtils.contains(activeScenarios, oldScenario)) {
|
||||
refreshScenarioStatus(oldScenario);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Exception requesting active scenarios: {}", e.getMessage(), e);
|
||||
// callback.statusChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the trigger for the current step
|
||||
*
|
||||
* @param step a possibly null, possibly empty step to send
|
||||
*/
|
||||
private void sendCurrentStepTrigger(@Nullable String step) {
|
||||
callback.triggerEvent(
|
||||
UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_STATE_ID, NeeoConstants.ROOM_CHANNEL_CURRENTSTEP),
|
||||
step == null || StringUtils.isEmpty(step) ? "" : step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the given recipe key
|
||||
*
|
||||
* @param recipeKey the non-null recipe key
|
||||
*/
|
||||
public void startRecipe(String recipeKey) {
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
final NeeoBrainApi api = callback.getApi();
|
||||
if (api == null) {
|
||||
logger.debug("API is null [likely bridge is offline] - cannot start recipe: {}", recipeKey);
|
||||
} else {
|
||||
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
|
||||
final String scenarioKey = recipe == null ? null : recipe.getScenarioKey();
|
||||
|
||||
if (recipe != null) {
|
||||
if (recipe.isEnabled()) {
|
||||
final boolean isLaunch = StringUtils.equalsIgnoreCase(NeeoRecipe.LAUNCH, recipe.getType());
|
||||
|
||||
try {
|
||||
if (isLaunch || scenarioKey == null || StringUtils.isEmpty(scenarioKey)) {
|
||||
handleExecuteResult(scenarioKey, recipeKey, true, api.executeRecipe(roomKey, recipeKey));
|
||||
} else {
|
||||
handleExecuteResult(scenarioKey, recipeKey, false, api.stopScenario(roomKey, scenarioKey));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("recipe for key {} was not enabled, cannot start or stop", recipeKey);
|
||||
}
|
||||
} else {
|
||||
logger.debug("recipe key {} was not found", recipeKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scenario status.
|
||||
*
|
||||
* @param scenarioKey the non-null scenario key
|
||||
* @param start whether to start (true) or stop (false) the scenario
|
||||
*/
|
||||
public void setScenarioStatus(String scenarioKey, boolean start) {
|
||||
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
|
||||
|
||||
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipeByScenarioKey(scenarioKey,
|
||||
start ? NeeoRecipe.LAUNCH : NeeoRecipe.POWEROFF);
|
||||
final String recipeKey = recipe == null ? null : recipe.getKey();
|
||||
|
||||
if (recipe != null && recipeKey != null && StringUtils.isNotEmpty(recipeKey)) {
|
||||
if (recipe.isEnabled()) {
|
||||
startRecipe(recipeKey);
|
||||
} else {
|
||||
logger.debug("Recipe ({}) found for scenario {} but was not enabled", recipe.getKey(), scenarioKey);
|
||||
}
|
||||
} else {
|
||||
logger.debug("No recipe found for scenario {} to start ({})", scenarioKey, start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the {@link ExecuteResult} from a call
|
||||
*
|
||||
* @param scenarioKey the possibly null scenario key being changed
|
||||
* @param recipeKey the non-null recipe key being used
|
||||
* @param launch whether the recipe launches the scenario (true) or not (false)
|
||||
* @param result the non-null result (null will do nothing)
|
||||
*/
|
||||
private void handleExecuteResult(@Nullable String scenarioKey, String recipeKey, boolean launch,
|
||||
ExecuteResult result) {
|
||||
Objects.requireNonNull(result, "result cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
|
||||
|
||||
int nextStep = 0;
|
||||
if (scenarioKey != null && StringUtils.isNotEmpty(scenarioKey)) {
|
||||
callback.scheduleTask(() -> {
|
||||
processScenarioChange(scenarioKey, launch);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
for (final ExecuteStep step : result.getSteps()) {
|
||||
callback.scheduleTask(() -> {
|
||||
sendCurrentStepTrigger(step.getText());
|
||||
}, nextStep);
|
||||
nextStep += step.getDuration();
|
||||
}
|
||||
|
||||
callback.scheduleTask(() -> {
|
||||
sendCurrentStepTrigger(null);
|
||||
refreshRecipeStatus(recipeKey);
|
||||
}, nextStep);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevices;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevicesDeserializer;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoMacros;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoMacrosDeserializer;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRecipes;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRecipesDeserializer;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRooms;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoomsDeserializer;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoScenarios;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoScenariosDeserializer;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Various utility functions used by the NEEO binding
|
||||
*
|
||||
* @author Tim Roberts - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoUtil {
|
||||
|
||||
/**
|
||||
* Builds and returns a {@link Gson}. The gson has adapters registered for {@link NeeoRooms}, {@link NeeoRecipes}
|
||||
* and {@link NeeoScenarios}
|
||||
*
|
||||
* @return a non-null {@link Gson} to use
|
||||
*/
|
||||
public static Gson getGson() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(NeeoRooms.class, new NeeoRoomsDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(NeeoRecipes.class, new NeeoRecipesDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(NeeoScenarios.class, new NeeoScenariosDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(NeeoDevices.class, new NeeoDevicesDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(NeeoMacros.class, new NeeoMacrosDeserializer());
|
||||
return gsonBuilder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to close a {@link AutoCloseable} and log any exception thrown.
|
||||
*
|
||||
* @param closeable a possibly null {@link AutoCloseable}. If null, no action is done.
|
||||
*/
|
||||
public static void close(@Nullable AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
LoggerFactory.getLogger(NeeoUtil.class).debug("Exception closing: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current thread has been interrupted and throws {@link InterruptedException} if it's been
|
||||
* interrupted
|
||||
*
|
||||
* @throws InterruptedException the interrupted exception
|
||||
*/
|
||||
public static void checkInterrupt() throws InterruptedException {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException("thread interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the specified {@link Future}
|
||||
*
|
||||
* @param future a possibly null future. If null, no action is done
|
||||
*/
|
||||
public static void cancel(@Nullable Future<?> future) {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Require the specified value to be a non-null, non-empty string
|
||||
*
|
||||
* @param value the value to check
|
||||
* @param msg the msg to use when throwing an exception
|
||||
* @throws NullPointerException if value is null
|
||||
* @throws IllegalArgumentException if value is an empty string
|
||||
*/
|
||||
public static void requireNotEmpty(@Nullable String value, String msg) {
|
||||
Objects.requireNonNull(value, msg);
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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.neeo.internal;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevice;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDeviceDetails;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* Utility class for generating some UIDs.
|
||||
*
|
||||
* @author Tim Roberts - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UidUtils {
|
||||
|
||||
/** The delimiter to separate 'parts' of an UID */
|
||||
private static final char DELIMITER = '-';
|
||||
|
||||
/**
|
||||
* Determines if the specified device is an openhab thing or not. The determination is made if the adapter name for
|
||||
* the device is a valid {@link ThingUID}
|
||||
*
|
||||
* @param device a possibly null device
|
||||
* @return true if a thing, false otherwise
|
||||
*/
|
||||
public static boolean isThing(NeeoDevice device) {
|
||||
final NeeoDeviceDetails details = device.getDetails();
|
||||
if (details == null) {
|
||||
return false;
|
||||
}
|
||||
final String adapterName = details.getAdapterName();
|
||||
if (adapterName == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
new ThingUID(adapterName);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the channel id/key in the {@link ChannelUID} and will return a non-null, non-empty string array
|
||||
* representing the parts. The first element will always be the channelID itself. If there is a second element, the
|
||||
* second element will the the channel key
|
||||
*
|
||||
* @param uid the non-null channel uid
|
||||
* @return the non-null, non empty (only 1 or 2 element) list of parts
|
||||
*/
|
||||
public static String[] parseChannelId(ChannelUID uid) {
|
||||
Objects.requireNonNull(uid, "uid cannot be null");
|
||||
|
||||
final String channelId = uid.getIdWithoutGroup();
|
||||
final int idx = channelId.indexOf(DELIMITER);
|
||||
if (idx < 0 || idx == channelId.length() - 1) {
|
||||
return new String[] { channelId };
|
||||
}
|
||||
return new String[] { channelId.substring(0, idx), channelId.substring(idx + 1) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the channel id from the group/channel
|
||||
*
|
||||
* @param groupId the not empty group id
|
||||
* @param channelId the not empty channel id
|
||||
* @return the full channel id
|
||||
*/
|
||||
public static String createChannelId(String groupId, String channelId) {
|
||||
NeeoUtil.requireNotEmpty(groupId, "groupId cannot be empty");
|
||||
NeeoUtil.requireNotEmpty(channelId, "channelId cannot be empty");
|
||||
|
||||
return createChannelId(groupId, channelId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the channel id from the group, channel id, and key
|
||||
*
|
||||
* @param groupId the possibly empty/null group id
|
||||
* @param channelId the not empty channel id
|
||||
* @param channelKey the possibly empty/null channel key
|
||||
* @return the full channel id
|
||||
*/
|
||||
public static String createChannelId(@Nullable String groupId, String channelId, @Nullable String channelKey) {
|
||||
NeeoUtil.requireNotEmpty(channelId, "channelId cannot be empty");
|
||||
|
||||
return (StringUtils.isEmpty(groupId) ? "" : (groupId + "#"))
|
||||
+ (StringUtils.isEmpty(channelKey) ? channelId : (channelId + DELIMITER + channelKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link ChannelUID} for a give thingUID, group ID and channel ID
|
||||
*
|
||||
* @param thingUid a non-null thing UID
|
||||
* @param groupId a non-null, non-empty group ID
|
||||
* @param channelId a non-null, non-empty channel ID
|
||||
* @return a non-null {@link ChannelUID}
|
||||
*/
|
||||
public static ChannelUID createChannelUID(ThingUID thingUid, String groupId, String channelId) {
|
||||
return createChannelUID(thingUid, groupId, channelId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link ChannelUID} for a give thingUID, group ID, channel ID and channel key
|
||||
*
|
||||
* @param thingUid a non-null thing UID
|
||||
* @param groupId a non-null, non-empty group ID
|
||||
* @param channelId a non-null, non-empty channel ID
|
||||
* @param channelKey a potentially null, potentially empty channel KEY
|
||||
* @return a non-null {@link ChannelUID}
|
||||
*/
|
||||
public static ChannelUID createChannelUID(ThingUID thingUid, String groupId, String channelId,
|
||||
@Nullable String channelKey) {
|
||||
return new ChannelUID(thingUid, groupId,
|
||||
channelId + (StringUtils.isEmpty(channelKey) ? "" : (DELIMITER + channelKey)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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.neeo.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.neeo.internal.NeeoConstants.BRIDGE_TYPE_BRAIN;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MDNSDiscoveryParticipant} that will discover NEEO brain(s).
|
||||
*
|
||||
* @author Tim Roberts - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
public class NeeoBrainDiscovery implements MDNSDiscoveryParticipant {
|
||||
|
||||
/** The logger */
|
||||
private Logger logger = LoggerFactory.getLogger(NeeoBrainDiscovery.class);
|
||||
|
||||
@Override
|
||||
public Set<@Nullable ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(BRIDGE_TYPE_BRAIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return NeeoConstants.NEEO_MDNS_TYPE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DiscoveryResult createResult(@Nullable ServiceInfo service) {
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ThingUID uid = getThingUID(service);
|
||||
if (uid == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.debug("createResult is evaluating: {}", service);
|
||||
|
||||
final Map<String, Object> properties = new HashMap<>(2);
|
||||
|
||||
final InetAddress ip = getIpAddress(service);
|
||||
if (ip == null) {
|
||||
logger.debug("Application not 'neeo' in MDNS serviceinfo: {}", service);
|
||||
return null;
|
||||
}
|
||||
final String inetAddress = ip.getHostAddress();
|
||||
|
||||
final String id = uid.getId();
|
||||
final String label = service.getName() + " (" + id + ")";
|
||||
|
||||
properties.put(NeeoConstants.CONFIG_IPADDRESS, inetAddress);
|
||||
properties.put(NeeoConstants.CONFIG_ENABLEFORWARDACTIONS, true);
|
||||
|
||||
logger.debug("Adding NEEO Brain to inbox: {} at {}", id, inetAddress);
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ThingUID getThingUID(@Nullable ServiceInfo service) {
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.debug("getThingUID is evaluating: {}", service);
|
||||
if (!StringUtils.equals("neeo", service.getApplication())) {
|
||||
logger.debug("Application not 'neeo' in MDNS serviceinfo: {}", service);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (getIpAddress(service) == null) {
|
||||
logger.debug("No IP address found in MDNS serviceinfo: {}", service);
|
||||
return null;
|
||||
}
|
||||
|
||||
String model = service.getPropertyString("hon"); // model
|
||||
if (model == null) {
|
||||
final String server = service.getServer(); // NEEO-xxxxx.local.
|
||||
if (server != null) {
|
||||
final int idx = server.indexOf(".");
|
||||
if (idx >= 0) {
|
||||
model = server.substring(0, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (model == null || model.length() <= 5 || !model.toLowerCase().startsWith("neeo")) {
|
||||
logger.debug("No 'hon' found in MDNS serviceinfo: {}", service);
|
||||
return null;
|
||||
}
|
||||
|
||||
final String id = model.substring(5);
|
||||
logger.debug("NEEO Brain Found: {}", id);
|
||||
|
||||
return new ThingUID(BRIDGE_TYPE_BRAIN, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ip address found in the {@link ServiceInfo}
|
||||
*
|
||||
* @param service a non-null service
|
||||
* @return the ip address of the service or null if none found.
|
||||
*/
|
||||
@Nullable
|
||||
private InetAddress getIpAddress(ServiceInfo service) {
|
||||
Objects.requireNonNull(service, "service cannot be null");
|
||||
|
||||
for (String addr : service.getHostAddresses()) {
|
||||
try {
|
||||
return InetAddress.getByName(addr);
|
||||
} catch (UnknownHostException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
for (InetAddress addr : service.getInet4Addresses()) {
|
||||
return addr;
|
||||
}
|
||||
// Fallback for Inet6addresses
|
||||
for (InetAddress addr : service.getInet6Addresses()) {
|
||||
return addr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.neeo.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainApi;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.NeeoRoomConfig;
|
||||
import org.openhab.binding.neeo.internal.UidUtils;
|
||||
import org.openhab.binding.neeo.internal.handler.NeeoRoomHandler;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevice;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AbstractDiscoveryService} that will discover the devices in a NEEO room;
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoDeviceDiscoveryService.class);
|
||||
|
||||
/** The device thing type we support */
|
||||
private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
|
||||
.singleton(NeeoConstants.THING_TYPE_DEVICE);
|
||||
|
||||
/** The timeout (in seconds) for searching the room */
|
||||
private static final int SEARCH_TIME = 10;
|
||||
|
||||
/** The room handler to search */
|
||||
private final NeeoRoomHandler roomHandler;
|
||||
|
||||
/**
|
||||
* Constructs the discovery service from the room handler
|
||||
*
|
||||
* @param roomHandler a non-null room handler
|
||||
*/
|
||||
public NeeoDeviceDiscoveryService(NeeoRoomHandler roomHandler) {
|
||||
super(DISCOVERABLE_THING_TYPES_UIDS, SEARCH_TIME);
|
||||
Objects.requireNonNull(roomHandler, "roomHandler cannot be null");
|
||||
this.roomHandler = roomHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
final Bridge roomBridge = roomHandler.getThing();
|
||||
final ThingUID roomUid = roomBridge.getUID();
|
||||
|
||||
final String brainId = roomHandler.getNeeoBrainId();
|
||||
if (brainId == null || StringUtils.isEmpty(brainId)) {
|
||||
logger.debug("Unknown brain ID for roomHandler: {}", roomHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
final NeeoBrainApi api = roomHandler.getNeeoBrainApi();
|
||||
if (api == null) {
|
||||
logger.debug("Brain API was not available for {} - skipping", brainId);
|
||||
return;
|
||||
}
|
||||
|
||||
final NeeoRoomConfig config = roomBridge.getConfiguration().as(NeeoRoomConfig.class);
|
||||
final String roomKey = config.getRoomKey();
|
||||
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
|
||||
logger.debug("RoomKey wasn't configured for {} - skipping", brainId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final NeeoRoom room = api.getRoom(roomKey);
|
||||
final NeeoDevice[] devices = room.getDevices().getDevices();
|
||||
|
||||
if (devices.length == 0) {
|
||||
logger.debug("Room {} found - but there were no devices - skipping", room.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Room {} found, scanning {} devices in it", room.getName(), devices.length);
|
||||
for (NeeoDevice device : devices) {
|
||||
final String deviceKey = device.getKey();
|
||||
if (deviceKey == null || StringUtils.isEmpty(deviceKey)) {
|
||||
logger.debug("Device key wasn't found for device: {}", device);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.isExcludeThings() && UidUtils.isThing(device)) {
|
||||
logger.debug("Found openHAB thing but ignoring per configuration: {}", device);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("Device #{} found - {}", deviceKey, device.getName());
|
||||
|
||||
final ThingUID thingUID = new ThingUID(NeeoConstants.THING_TYPE_DEVICE, roomUid, deviceKey);
|
||||
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(NeeoConstants.CONFIG_DEVICEKEY, deviceKey).withBridge(roomUid)
|
||||
.withLabel(device.getName() + " (NEEO " + brainId + ")").build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException occurred getting brain info ({}): {}", brainId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainApi;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainConfig;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.handler.NeeoBrainHandler;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoBrain;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AbstractDiscoveryService} that will discover the rooms in a NEEO brain;
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRoomDiscoveryService extends AbstractDiscoveryService {
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoRoomDiscoveryService.class);
|
||||
|
||||
/** The room bridge type we support */
|
||||
private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
|
||||
.singleton(NeeoConstants.BRIDGE_TYPE_ROOM);
|
||||
|
||||
/** The timeout (in seconds) for searching the brain */
|
||||
private static final int SEARCH_TIME = 10;
|
||||
|
||||
/** The brain handler that we will use */
|
||||
private final NeeoBrainHandler brainHandler;
|
||||
|
||||
/**
|
||||
* Constructs the discover service from the brain handler
|
||||
*
|
||||
* @param brainHandler a non-null brain handler
|
||||
*/
|
||||
public NeeoRoomDiscoveryService(NeeoBrainHandler brainHandler) {
|
||||
super(DISCOVERABLE_THING_TYPES_UIDS, SEARCH_TIME);
|
||||
Objects.requireNonNull(brainHandler, "brainHandler cannot be null");
|
||||
this.brainHandler = brainHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
final String brainId = brainHandler.getNeeoBrainId();
|
||||
|
||||
final Bridge brainBridge = brainHandler.getThing();
|
||||
final ThingUID brainUid = brainBridge.getUID();
|
||||
|
||||
final NeeoBrainApi api = brainHandler.getNeeoBrainApi();
|
||||
if (api == null) {
|
||||
logger.debug("Brain API was not available for {} - skipping", brainId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final NeeoBrain brain = api.getBrain();
|
||||
final NeeoBrainConfig config = brainBridge.getConfiguration().as(NeeoBrainConfig.class);
|
||||
final NeeoRoom[] rooms = brain.getRooms().getRooms();
|
||||
|
||||
if (rooms.length == 0) {
|
||||
logger.debug("Brain {} ({}) found - but there were no rooms - skipping", brain.getName(), brainId);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Brain {} ({}) found, scanning {} rooms in it", brain.getName(), brainId, rooms.length);
|
||||
for (NeeoRoom room : rooms) {
|
||||
final String roomKey = room.getKey();
|
||||
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
|
||||
logger.debug("Room didn't have a room key: {}", room);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (room.getDevices().getDevices().length == 0 && room.getRecipes().getRecipes().length == 0
|
||||
&& !config.isDiscoverEmptyRooms()) {
|
||||
logger.debug("Room {} ({}) found but has no devices or recipes, ignoring - {}", roomKey, brainId,
|
||||
room.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
final ThingUID thingUID = new ThingUID(NeeoConstants.BRIDGE_TYPE_ROOM, brainUid, roomKey);
|
||||
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(NeeoConstants.CONFIG_ROOMKEY, roomKey)
|
||||
.withProperty(NeeoConstants.CONFIG_EXCLUDE_THINGS, true).withBridge(brainUid)
|
||||
.withLabel(room.getName() + " (NEEO " + brainId + ")").build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException occurred getting brain info ({}): {}", brainId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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.neeo.internal.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.UidUtils;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevice;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoMacro;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRecipe;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRecipes;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoScenario;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoScenarios;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
|
||||
/**
|
||||
* Utility class for generating channels
|
||||
*
|
||||
* @author Tim Roberts - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ChannelUtils {
|
||||
/**
|
||||
* Generates a list of {@link Channel} s for a specific device. This implementation will generate a channel for each
|
||||
* macro found on the device.
|
||||
*
|
||||
* @param thingUid a non-null thingUID
|
||||
* @param device a non-null device
|
||||
* @return a non-null but possibly empty list of {@link Channel} s
|
||||
*/
|
||||
static List<Channel> generateChannels(ThingUID thingUid, NeeoDevice device) {
|
||||
Objects.requireNonNull(thingUid, "thingUid cannot be null");
|
||||
Objects.requireNonNull(device, "device cannot be null");
|
||||
|
||||
final List<Channel> channels = new ArrayList<>();
|
||||
for (NeeoMacro macro : device.getMacros().getMacros()) {
|
||||
final String key = macro.getKey();
|
||||
if (key != null && StringUtils.isNotEmpty(key)) {
|
||||
final String label = StringUtils.isEmpty(macro.getName()) ? macro.getLabel() : macro.getName();
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.DEVICE_GROUP_MACROS_ID,
|
||||
NeeoConstants.DEVICE_CHANNEL_STATUS, key), "Switch")
|
||||
.withLabel(label == null || StringUtils.isEmpty(label) ? key : label)
|
||||
.withType(NeeoConstants.DEVICE_MACRO_STATUS_UID).build());
|
||||
}
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of {@link Channel} s for a specific room. This implementation will generate multiple channels
|
||||
* for each scenario and recipe in the room (in addition to the current step channel)
|
||||
*
|
||||
* @param thingUid a non-null thingUID
|
||||
* @param room a non-null room
|
||||
* @return a non-null but possibly empty list of {@link Channel} s
|
||||
*/
|
||||
static List<Channel> generateChannels(ThingUID thingUid, NeeoRoom room) {
|
||||
Objects.requireNonNull(thingUid, "thingUid cannot be null");
|
||||
Objects.requireNonNull(room, "room cannot be null");
|
||||
|
||||
final List<Channel> channels = new ArrayList<>();
|
||||
channels.addAll(generateStateChannels(thingUid));
|
||||
channels.addAll(generateScenarioChannels(thingUid, room.getScenarios()));
|
||||
channels.addAll(generateRecipeChannels(thingUid, room.getRecipes()));
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate state channels (the current step)
|
||||
*
|
||||
* @param thingUid a non-null thingUID
|
||||
* @return a non-null but possibly empty list of {@link Channel} s
|
||||
*/
|
||||
private static List<Channel> generateStateChannels(ThingUID thingUid) {
|
||||
Objects.requireNonNull(thingUid, "thingUid cannot be null");
|
||||
|
||||
final List<Channel> channels = new ArrayList<>();
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_STATE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_CURRENTSTEP), "String")
|
||||
.withType(NeeoConstants.ROOM_STATE_CURRENTSTEP_UID).build());
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate scenario channels
|
||||
*
|
||||
* @param thingUid a non-null thingUID
|
||||
* @param scenarios the non-null scenarios
|
||||
* @return a non-null but possibly empty list of {@link Channel} s
|
||||
*/
|
||||
private static List<Channel> generateScenarioChannels(ThingUID thingUid, NeeoScenarios scenarios) {
|
||||
Objects.requireNonNull(thingUid, "thingUid cannot be null");
|
||||
Objects.requireNonNull(scenarios, "scenarios cannot be null");
|
||||
|
||||
final List<Channel> channels = new ArrayList<>();
|
||||
for (NeeoScenario scenario : scenarios.getScenarios()) {
|
||||
final String key = scenario.getKey();
|
||||
if (key != null && StringUtils.isNotEmpty(key)) {
|
||||
final String scenarioLabel = StringUtils.isEmpty(scenario.getName()) ? null : scenario.getName();
|
||||
final String nameLabel = (scenarioLabel == null || StringUtils.isEmpty(scenarioLabel) ? key
|
||||
: scenarioLabel) + " Name";
|
||||
final String configuredLabel = (scenarioLabel == null || StringUtils.isEmpty(scenarioLabel) ? key
|
||||
: scenarioLabel) + " Configured";
|
||||
final String statusLabel = (scenarioLabel == null || StringUtils.isEmpty(scenarioLabel) ? key
|
||||
: scenarioLabel) + " Status";
|
||||
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_SCENARIO_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_NAME, key), "String")
|
||||
.withLabel(nameLabel).withType(NeeoConstants.ROOM_SCENARIO_NAME_UID).build());
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_SCENARIO_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_CONFIGURED, key), "Switch")
|
||||
.withLabel(configuredLabel).withType(NeeoConstants.ROOM_SCENARIO_CONFIGURED_UID).build());
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_SCENARIO_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_STATUS, key), "Switch")
|
||||
.withLabel(statusLabel).withType(NeeoConstants.ROOM_SCENARIO_STATUS_UID).build());
|
||||
}
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate recipe channels
|
||||
*
|
||||
* @param thingUid a non-null thingUID
|
||||
* @param recipes the non-null recipes
|
||||
* @return a non-null but possibly empty list of {@link Channel} s
|
||||
*/
|
||||
private static List<Channel> generateRecipeChannels(ThingUID thingUid, NeeoRecipes recipes) {
|
||||
Objects.requireNonNull(thingUid, "thingUid cannot be null");
|
||||
Objects.requireNonNull(recipes, "recipes cannot be null");
|
||||
|
||||
final List<Channel> channels = new ArrayList<>();
|
||||
for (NeeoRecipe recipe : recipes.getRecipes()) {
|
||||
final String key = recipe.getKey();
|
||||
if (key != null && StringUtils.isNotEmpty(key)) {
|
||||
final String recipeLabel = StringUtils.isEmpty(recipe.getName()) ? null : recipe.getName();
|
||||
final String nameLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key : recipeLabel)
|
||||
+ " Name (" + recipe.getType() + ")";
|
||||
final String typeLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key : recipeLabel)
|
||||
+ " Type (" + recipe.getType() + ")";
|
||||
final String enabledLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key
|
||||
: recipeLabel) + " Enabled (" + recipe.getType() + ")";
|
||||
final String statusLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key : recipeLabel)
|
||||
+ " Status (" + recipe.getType() + ")";
|
||||
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_NAME, key), "String")
|
||||
.withLabel(nameLabel).withType(NeeoConstants.ROOM_RECIPE_NAME_UID).build());
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_TYPE, key), "String")
|
||||
.withLabel(enabledLabel).withType(NeeoConstants.ROOM_RECIPE_TYPE_UID).build());
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_ENABLED, key), "Switch")
|
||||
.withLabel(typeLabel).withType(NeeoConstants.ROOM_RECIPE_ENABLED_UID).build());
|
||||
channels.add(ChannelBuilder
|
||||
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
|
||||
NeeoConstants.ROOM_CHANNEL_STATUS, key), "Switch")
|
||||
.withLabel(statusLabel).withType(NeeoConstants.ROOM_RECIPE_STATUS_UID).build());
|
||||
}
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 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.neeo.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainApi;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainConfig;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.NeeoUtil;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoAction;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoBrain;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* A subclass of {@link BaseBridgeHandler} is responsible for handling commands and discovery for a
|
||||
* {@link NeeoBrain}
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoBrainHandler extends BaseBridgeHandler {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoBrainHandler.class);
|
||||
|
||||
/** The {@link HttpService} to register callbacks */
|
||||
private final HttpService httpService;
|
||||
|
||||
/** The {@link NetworkAddressService} to use */
|
||||
private final NetworkAddressService networkAddressService;
|
||||
|
||||
/** GSON implementation - only used to deserialize {@link NeeoAction} */
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/** The port the HTTP service is listening on */
|
||||
private final int servicePort;
|
||||
|
||||
/**
|
||||
* The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
|
||||
|
||||
/** The check status task (not-null when connecting, null otherwise) */
|
||||
private final AtomicReference<@Nullable Future<?>> checkStatus = new AtomicReference<>();
|
||||
|
||||
/** The lock that protected multi-threaded access to the state variables */
|
||||
private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
|
||||
|
||||
/** The {@link NeeoBrainApi} (null until set by {@link #initializationTask}) */
|
||||
@Nullable
|
||||
private NeeoBrainApi neeoBrainApi;
|
||||
|
||||
/** The path to the forward action servlet - will be null if not enabled */
|
||||
@Nullable
|
||||
private String servletPath;
|
||||
|
||||
/** The servlet for forward actions - will be null if not enabled */
|
||||
@Nullable
|
||||
private NeeoForwardActionsServlet forwardActionServlet;
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo brain handler from the {@link Bridge}, service port, {@link HttpService} and
|
||||
* {@link NetworkAddressService}.
|
||||
*
|
||||
* @param bridge the non-null {@link Bridge}
|
||||
* @param servicePort the service port the http service is listening on
|
||||
* @param httpService the non-null {@link HttpService}
|
||||
* @param networkAddressService the non-null {@link NetworkAddressService}
|
||||
*/
|
||||
NeeoBrainHandler(Bridge bridge, int servicePort, HttpService httpService,
|
||||
NetworkAddressService networkAddressService) {
|
||||
super(bridge);
|
||||
|
||||
Objects.requireNonNull(bridge, "bridge cannot be null");
|
||||
Objects.requireNonNull(httpService, "httpService cannot be null");
|
||||
Objects.requireNonNull(networkAddressService, "networkAddressService cannot be null");
|
||||
|
||||
this.servicePort = servicePort;
|
||||
this.httpService = httpService;
|
||||
this.networkAddressService = networkAddressService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles any {@Commands} sent - this bridge has no commands and does nothing
|
||||
*
|
||||
* @see
|
||||
* org.openhab.core.thing.binding.ThingHandler#handleCommand(org.openhab.core.thing.ChannelUID,
|
||||
* org.openhab.core.types.Command)
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply cancels any existing initialization tasks and schedules a new task
|
||||
*
|
||||
* @see org.openhab.core.thing.binding.BaseThingHandler#initialize()
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
|
||||
initializeTask();
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the bridge by connecting to the configuration ip address and parsing the results. Properties will be
|
||||
* set and the thing will go online.
|
||||
*/
|
||||
private void initializeTask() {
|
||||
final Lock writerLock = stateLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
NeeoUtil.checkInterrupt();
|
||||
|
||||
final NeeoBrainConfig config = getBrainConfig();
|
||||
final String ipAddress = config.getIpAddress();
|
||||
if (ipAddress == null || StringUtils.isEmpty(ipAddress)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Brain IP Address must be specified");
|
||||
return;
|
||||
}
|
||||
final NeeoBrainApi api = new NeeoBrainApi(ipAddress);
|
||||
final NeeoBrain brain = api.getBrain();
|
||||
final String brainId = getNeeoBrainId();
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
neeoBrainApi = api;
|
||||
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
addProperty(properties, "Name", brain.getName());
|
||||
addProperty(properties, "Version", brain.getVersion());
|
||||
addProperty(properties, "Label", brain.getLabel());
|
||||
addProperty(properties, "Is Configured", String.valueOf(brain.isConfigured()));
|
||||
addProperty(properties, "Key", brain.getKey());
|
||||
addProperty(properties, "AirKey", brain.getAirkey());
|
||||
addProperty(properties, "Last Change", String.valueOf(brain.getLastChange()));
|
||||
updateProperties(properties);
|
||||
|
||||
if (config.isEnableForwardActions()) {
|
||||
NeeoUtil.checkInterrupt();
|
||||
|
||||
forwardActionServlet = new NeeoForwardActionsServlet(scheduler,
|
||||
new NeeoForwardActionsServlet.Callback() {
|
||||
@Override
|
||||
public void post(String json) {
|
||||
triggerChannel(NeeoConstants.CHANNEL_BRAIN_FOWARDACTIONS, json);
|
||||
|
||||
final NeeoAction action = gson.fromJson(json, NeeoAction.class);
|
||||
|
||||
for (final Thing child : getThing().getThings()) {
|
||||
final ThingHandler th = child.getHandler();
|
||||
if (th instanceof NeeoRoomHandler) {
|
||||
((NeeoRoomHandler) th).processAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, config.getForwardChain());
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
try {
|
||||
servletPath = NeeoConstants.WEBAPP_FORWARDACTIONS.replace("{brainid}", brainId);
|
||||
|
||||
httpService.registerServlet(servletPath, forwardActionServlet, new Hashtable<>(),
|
||||
httpService.createDefaultHttpContext());
|
||||
|
||||
final URL callbackURL = createCallbackUrl(brainId, config);
|
||||
if (callbackURL == null) {
|
||||
logger.debug(
|
||||
"Unable to create a callback URL because there is no primary address specified (please set the primary address in the configuration)");
|
||||
} else {
|
||||
final URL url = new URL(callbackURL, servletPath);
|
||||
api.registerForwardActions(url);
|
||||
}
|
||||
} catch (NamespaceException | ServletException e) {
|
||||
logger.debug("Error registering forward actions to {}: {}", servletPath, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
NeeoUtil.checkInterrupt();
|
||||
if (config.getCheckStatusInterval() > 0) {
|
||||
NeeoUtil.cancel(checkStatus.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
NeeoUtil.checkInterrupt();
|
||||
checkStatus(ipAddress);
|
||||
} catch (InterruptedException e) {
|
||||
// do nothing - we were interrupted and should stop
|
||||
}
|
||||
}, config.getCheckStatusInterval(), config.getCheckStatusInterval(), TimeUnit.SECONDS)));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Exception occurred connecting to brain: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Exception occurred connecting to brain: " + e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Initializtion was interrupted", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"Initialization was interrupted");
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to add a property to the properties map if the value is not null
|
||||
*
|
||||
* @param properties a non-null properties map
|
||||
* @param key a non-null, non-empty key
|
||||
* @param value a possibly null, possibly empty key
|
||||
*/
|
||||
private void addProperty(Map<String, String> properties, String key, @Nullable String value) {
|
||||
Objects.requireNonNull(properties, "properties cannot be null");
|
||||
NeeoUtil.requireNotEmpty(key, "key cannot be empty");
|
||||
if (value != null && StringUtils.isNotEmpty(value)) {
|
||||
properties.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link NeeoBrainApi} used by this bridge
|
||||
*
|
||||
* @return a possibly null {@link NeeoBrainApi}
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoBrainApi getNeeoBrainApi() {
|
||||
final Lock readerLock = stateLock.readLock();
|
||||
readerLock.lock();
|
||||
try {
|
||||
return neeoBrainApi;
|
||||
} finally {
|
||||
readerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the brain id used by this bridge
|
||||
*
|
||||
* @return a non-null, non-empty brain id
|
||||
*/
|
||||
public String getNeeoBrainId() {
|
||||
return getThing().getUID().getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the {@link NeeoBrainConfig}
|
||||
*
|
||||
* @return the {@link NeeoBrainConfig}
|
||||
*/
|
||||
private NeeoBrainConfig getBrainConfig() {
|
||||
return getConfigAs(NeeoBrainConfig.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the status of the brain via a quick socket connection. If the status is unavailable and we are
|
||||
* {@link ThingStatus#ONLINE}, then we go {@link ThingStatus#OFFLINE}. If the status is available and we are
|
||||
* {@link ThingStatus#OFFLINE}, we go {@link ThingStatus#ONLINE}.
|
||||
*
|
||||
* @param ipAddress a non-null, non-empty IP address
|
||||
*/
|
||||
private void checkStatus(String ipAddress) {
|
||||
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
|
||||
|
||||
try {
|
||||
try (Socket soc = new Socket()) {
|
||||
soc.connect(new InetSocketAddress(ipAddress, NeeoConstants.DEFAULT_BRAIN_PORT), 5000);
|
||||
}
|
||||
logger.debug("Checking connectivity to {}:{} - successful", ipAddress, NeeoConstants.DEFAULT_BRAIN_PORT);
|
||||
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
logger.debug("Checking connectivity to {}:{} - unsuccessful - going offline: {}", ipAddress,
|
||||
NeeoConstants.DEFAULT_BRAIN_PORT, e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Exception occurred connecting to brain: " + e.getMessage());
|
||||
} else {
|
||||
logger.debug("Checking connectivity to {}:{} - unsuccessful - still offline", ipAddress,
|
||||
NeeoConstants.DEFAULT_BRAIN_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the bridge by closing/removing the {@link #neeoBrainApi} and canceling/removing any pending
|
||||
* {@link #initializeTask()}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
final Lock writerLock = stateLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
final NeeoBrainApi api = neeoBrainApi;
|
||||
neeoBrainApi = null;
|
||||
|
||||
NeeoUtil.cancel(initializationTask.getAndSet(null));
|
||||
NeeoUtil.cancel(checkStatus.getAndSet(null));
|
||||
|
||||
if (forwardActionServlet != null) {
|
||||
forwardActionServlet = null;
|
||||
|
||||
if (api != null) {
|
||||
try {
|
||||
api.deregisterForwardActions();
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException occurred deregistering the forward actions: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (servletPath != null) {
|
||||
httpService.unregister(servletPath);
|
||||
servletPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
NeeoUtil.close(api);
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the URL the brain should callback. Note: if there is multiple interfaces, we try to prefer the one on the
|
||||
* same subnet as the brain
|
||||
*
|
||||
* @param brainId the non-null, non-empty brain identifier
|
||||
* @param config the non-null brain configuration
|
||||
* @return the callback URL
|
||||
* @throws MalformedURLException if the URL is malformed
|
||||
*/
|
||||
@Nullable
|
||||
private URL createCallbackUrl(String brainId, NeeoBrainConfig config) throws MalformedURLException {
|
||||
NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty");
|
||||
Objects.requireNonNull(config, "config cannot be null");
|
||||
|
||||
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
|
||||
if (ipAddress == null) {
|
||||
logger.debug("No network interface could be found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new URL("http://" + ipAddress + ":" + servicePort);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* 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.neeo.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainApi;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.NeeoDeviceConfig;
|
||||
import org.openhab.binding.neeo.internal.NeeoDeviceProtocol;
|
||||
import org.openhab.binding.neeo.internal.NeeoHandlerCallback;
|
||||
import org.openhab.binding.neeo.internal.NeeoRoomConfig;
|
||||
import org.openhab.binding.neeo.internal.NeeoUtil;
|
||||
import org.openhab.binding.neeo.internal.UidUtils;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDevice;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDeviceDetails;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoDeviceDetailsTiming;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* An extension of {@link BaseThingHandler} that is responsible for handling commands for a device
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDeviceHandler extends BaseThingHandler {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoDeviceHandler.class);
|
||||
|
||||
/**
|
||||
* The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>(null);
|
||||
|
||||
/**
|
||||
* The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
|
||||
*/
|
||||
private final AtomicReference<@Nullable ScheduledFuture<?>> refreshTask = new AtomicReference<>(null);
|
||||
|
||||
/** The {@link NeeoDeviceProtocol} (null until set by {@link #initializationTask}) */
|
||||
private final AtomicReference<@Nullable NeeoDeviceProtocol> deviceProtocol = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo device handler.
|
||||
*
|
||||
* @param thing the non-null thing
|
||||
*/
|
||||
NeeoDeviceHandler(Thing thing) {
|
||||
super(thing);
|
||||
Objects.requireNonNull(thing, "thing cannot be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
Objects.requireNonNull(channelUID, "channelUID cannot be null");
|
||||
Objects.requireNonNull(command, "command cannot be null");
|
||||
|
||||
final NeeoDeviceProtocol protocol = deviceProtocol.get();
|
||||
if (protocol == null) {
|
||||
logger.debug("Protocol is null - ignoring update: {}", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
final String[] channelIds = UidUtils.parseChannelId(channelUID);
|
||||
if (channelIds.length == 0) {
|
||||
logger.debug("Bad group declaration: {}", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
final String localGroupId = channelUID.getGroupId();
|
||||
final String groupId = localGroupId == null || StringUtils.isEmpty(localGroupId) ? "" : localGroupId;
|
||||
final String channelId = channelIds[0];
|
||||
final String channelKey = channelIds.length > 1 ? channelIds[1] : "";
|
||||
|
||||
if (StringUtils.isEmpty(groupId)) {
|
||||
logger.debug("GroupID for channel is null - ignoring command: {}", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
refreshChannel(protocol, groupId, channelId, channelKey);
|
||||
} else {
|
||||
switch (groupId) {
|
||||
case NeeoConstants.DEVICE_GROUP_MACROS_ID:
|
||||
switch (channelId) {
|
||||
case NeeoConstants.DEVICE_CHANNEL_STATUS:
|
||||
if (command instanceof OnOffType) {
|
||||
protocol.setMacroStatus(channelKey, command == OnOffType.ON);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown channel to set: {}", channelUID);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown group to set: {}", channelUID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the specified channel section, key and id using the specified protocol
|
||||
*
|
||||
* @param protocol a non-null protocol to use
|
||||
* @param groupId the non-empty groupId
|
||||
* @param channelId the non-empty channel id
|
||||
* @param channelKey the non-empty channel key
|
||||
*/
|
||||
private void refreshChannel(NeeoDeviceProtocol protocol, String groupId, String channelId, String channelKey) {
|
||||
Objects.requireNonNull(protocol, "protocol cannot be null");
|
||||
NeeoUtil.requireNotEmpty(groupId, "groupId must not be empty");
|
||||
NeeoUtil.requireNotEmpty(channelId, "channelId must not be empty");
|
||||
NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
|
||||
|
||||
switch (groupId) {
|
||||
case NeeoConstants.DEVICE_GROUP_MACROS_ID:
|
||||
switch (channelId) {
|
||||
case NeeoConstants.DEVICE_CHANNEL_STATUS:
|
||||
protocol.refreshMacroStatus(channelKey);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
|
||||
initializeTask();
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the task be creating the {@link NeeoDeviceProtocol}, going online and then scheduling the refresh
|
||||
* task.
|
||||
*/
|
||||
private void initializeTask() {
|
||||
final NeeoDeviceConfig config = getConfigAs(NeeoDeviceConfig.class);
|
||||
|
||||
final String roomKey = getRoomKey();
|
||||
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Room key (from the parent room bridge) was not found");
|
||||
return;
|
||||
}
|
||||
|
||||
final String deviceKey = config.getDeviceKey();
|
||||
if (deviceKey == null || StringUtils.isEmpty(deviceKey)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Device key was not found or empty");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
NeeoUtil.checkInterrupt();
|
||||
final NeeoBrainApi brainApi = getNeeoBrainApi();
|
||||
if (brainApi == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Cannot find the NEEO Brain API");
|
||||
return;
|
||||
}
|
||||
|
||||
final NeeoRoom room = brainApi.getRoom(roomKey);
|
||||
|
||||
final NeeoDevice device = room.getDevices().getDevice(deviceKey);
|
||||
if (device == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Device (" + config.getDeviceKey() + ") was not found in room (" + roomKey + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
final ThingUID thingUid = getThing().getUID();
|
||||
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
final NeeoDeviceDetails details = device.getDetails();
|
||||
if (details != null) {
|
||||
/** The following properties have matches in org.openhab.io.neeo.OpenHabToDeviceConverter.java */
|
||||
addProperty(properties, "Source Name", details.getSourceName());
|
||||
addProperty(properties, "Adapter Name", details.getAdapterName());
|
||||
addProperty(properties, "Type", details.getType());
|
||||
addProperty(properties, "Manufacturer", details.getManufacturer());
|
||||
addProperty(properties, "Name", details.getName());
|
||||
|
||||
final NeeoDeviceDetailsTiming timing = details.getTiming();
|
||||
if (timing != null) {
|
||||
properties.put("Standby Command Delay", toString(timing.getStandbyCommandDelay()));
|
||||
properties.put("Source Switch Delay", toString(timing.getSourceSwitchDelay()));
|
||||
properties.put("Shutdown Delay", toString(timing.getShutdownDelay()));
|
||||
}
|
||||
|
||||
properties.put("Device Capabilities", StringUtils.join(details.getDeviceCapabilities(), ','));
|
||||
}
|
||||
|
||||
final ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withLabel(device.getName() + " (NEEO " + brainApi.getBrain().getKey() + ")")
|
||||
.withProperties(properties).withChannels(ChannelUtils.generateChannels(thingUid, device));
|
||||
updateThing(thingBuilder.build());
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
final NeeoDeviceProtocol protocol = new NeeoDeviceProtocol(new NeeoHandlerCallback() {
|
||||
|
||||
@Override
|
||||
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
|
||||
updateStatus(status, detail, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(String channelId, State state) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String propertyName, String propertyValue) {
|
||||
getThing().setProperty(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleTask(Runnable task, long milliSeconds) {
|
||||
scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerEvent(String channelID, String event) {
|
||||
triggerChannel(channelID, event);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoBrainApi getApi() {
|
||||
return getNeeoBrainApi();
|
||||
}
|
||||
}, roomKey, deviceKey);
|
||||
deviceProtocol.getAndSet(protocol);
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException during initialization", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Room " + roomKey + " couldn't be found");
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Initialization was interrupted", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"Initialization was interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to add a property to the properties map if the value is not null
|
||||
*
|
||||
* @param properties a non-null properties map
|
||||
* @param key a non-null, non-empty key
|
||||
* @param value a possibly null, possibly empty key
|
||||
*/
|
||||
private void addProperty(Map<String, String> properties, String key, @Nullable String value) {
|
||||
Objects.requireNonNull(properties, "properties cannot be null");
|
||||
NeeoUtil.requireNotEmpty(key, "key cannot be empty");
|
||||
if (value != null && StringUtils.isNotEmpty(value)) {
|
||||
properties.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the room key from the parent bridge (which should be a room)
|
||||
*
|
||||
* @return a non-null, non-empty room key if found, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
private String getRoomKey() {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
final BridgeHandler handler = bridge.getHandler();
|
||||
if (handler instanceof NeeoRoomHandler) {
|
||||
return handler.getThing().getConfiguration().as(NeeoRoomConfig.class).getRoomKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to simply create a string from an integer
|
||||
*
|
||||
* @param i the integer
|
||||
* @return the resulting string representation
|
||||
*/
|
||||
private static String toString(@Nullable Integer i) {
|
||||
if (i == null) {
|
||||
return "";
|
||||
}
|
||||
return i.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return the {@link NeeoRoomHandler} associated with this handler
|
||||
*
|
||||
* @return a possibly null {@link NeeoRoomHandler}
|
||||
*/
|
||||
@Nullable
|
||||
private NeeoRoomHandler getRoomHandler() {
|
||||
final Bridge parent = getBridge();
|
||||
if (parent != null) {
|
||||
final BridgeHandler handler = parent.getHandler();
|
||||
if (handler instanceof NeeoRoomHandler) {
|
||||
return ((NeeoRoomHandler) handler);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link NeeoBrainApi} associated with this handler.
|
||||
*
|
||||
* @return the {@link NeeoBrainApi} or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
private NeeoBrainApi getNeeoBrainApi() {
|
||||
final NeeoRoomHandler handler = getRoomHandler();
|
||||
return handler == null ? null : handler.getNeeoBrainApi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the brain ID associated with this handler.
|
||||
*
|
||||
* @return the brain ID or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public String getNeeoBrainId() {
|
||||
final NeeoRoomHandler handler = getRoomHandler();
|
||||
return handler == null ? null : handler.getNeeoBrainId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
NeeoUtil.cancel(initializationTask.getAndSet(null));
|
||||
NeeoUtil.cancel(refreshTask.getAndSet(null));
|
||||
deviceProtocol.getAndSet(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.neeo.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.neeo.internal.net.HttpRequest;
|
||||
import org.openhab.binding.neeo.internal.net.HttpResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This servlet handles the forward actions events from the NEEO Brain. The forward actions will be posted to the
|
||||
* callback and then will be forwarded on to any URLs lised in {@link #forwardChain}
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings("serial")
|
||||
public class NeeoForwardActionsServlet extends HttpServlet {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoForwardActionsServlet.class);
|
||||
|
||||
/** The event publisher */
|
||||
private final Callback callback;
|
||||
|
||||
/** The forwarding chain */
|
||||
@Nullable
|
||||
private final String forwardChain;
|
||||
|
||||
/** The scheduler to use to schedule recipe execution */
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* Creates the servlet the will process foward action events from the NEEO brain.
|
||||
*
|
||||
* @param scheduler a non-null {@link ScheduledExecutorService} to schedule forward actions
|
||||
* @param callback a non-null {@link Callback}
|
||||
* @param forwardChain a possibly null, possibly empty forwarding chain
|
||||
*/
|
||||
NeeoForwardActionsServlet(ScheduledExecutorService scheduler, Callback callback, @Nullable String forwardChain) {
|
||||
super();
|
||||
|
||||
Objects.requireNonNull(scheduler, "scheduler cannot be null");
|
||||
Objects.requireNonNull(callback, "callback cannot be null");
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.callback = callback;
|
||||
this.forwardChain = forwardChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the post action from the NEEO brain. Simply get's the specified json and then forwards it on (if
|
||||
* needed)
|
||||
*
|
||||
* @param req the non-null request
|
||||
* @param resp the non-null response
|
||||
*/
|
||||
@Override
|
||||
protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
Objects.requireNonNull(req, "req cannot be null");
|
||||
Objects.requireNonNull(resp, "resp cannot be null");
|
||||
|
||||
final String json = IOUtils.toString(req.getReader());
|
||||
logger.debug("handleForwardActions {}", json);
|
||||
|
||||
callback.post(json);
|
||||
|
||||
final String fc = forwardChain;
|
||||
if (fc != null && StringUtils.isNotEmpty(fc)) {
|
||||
scheduler.execute(() -> {
|
||||
try (final HttpRequest request = new HttpRequest()) {
|
||||
for (final String forwardUrl : fc.split(",")) {
|
||||
if (StringUtils.isNotEmpty(forwardUrl)) {
|
||||
final HttpResponse httpResponse = request.sendPostJsonCommand(forwardUrl, json);
|
||||
if (httpResponse.getHttpCode() != HttpStatus.OK_200) {
|
||||
logger.debug("Cannot forward event {} to {}: {}", json, forwardUrl,
|
||||
httpResponse.getHttpCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
void post(String json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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.neeo.internal.handler;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.discovery.NeeoDeviceDiscoveryService;
|
||||
import org.openhab.binding.neeo.internal.discovery.NeeoRoomDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.net.HttpServiceUtil;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
|
||||
/**
|
||||
* The {@link NeeoHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.neeo")
|
||||
public class NeeoHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
/** The {@link HttpService} used to register callbacks */
|
||||
@NonNullByDefault({})
|
||||
private HttpService httpService;
|
||||
|
||||
/** The {@link NetworkAddressService} used for ip lookup */
|
||||
@NonNullByDefault({})
|
||||
private NetworkAddressService networkAddressService;
|
||||
|
||||
/** The discovery services created by this class (one per room and one for each device) */
|
||||
private final ConcurrentMap<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpService}.
|
||||
*
|
||||
* @param httpService the non-null {@link HttpService} to use
|
||||
*/
|
||||
@Reference
|
||||
protected void setHttpService(HttpService httpService) {
|
||||
Objects.requireNonNull(httpService, "httpService cannot be null");
|
||||
this.httpService = httpService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the {@link HttpService}
|
||||
*
|
||||
* @param httpService the {@link HttpService} (not used in this implementation)
|
||||
*/
|
||||
protected void unsetHttpService(HttpService httpService) {
|
||||
this.httpService = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link NetworkAddressService}.
|
||||
*
|
||||
* @param networkAddressService the non-null {@link NetworkAddressService} to use
|
||||
*/
|
||||
@Reference
|
||||
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
|
||||
Objects.requireNonNull(networkAddressService, "networkAddressService cannot be null");
|
||||
this.networkAddressService = networkAddressService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the {@link NetworkAddressService}
|
||||
*
|
||||
* @param networkAddressService the {@link NetworkAddressService} (not used in this implementation)
|
||||
*/
|
||||
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
|
||||
this.networkAddressService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
Objects.requireNonNull(thingTypeUID, "thingTypeUID cannot be null");
|
||||
|
||||
return NeeoConstants.BINDING_ID.equals(thingTypeUID.getBindingId());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
Objects.requireNonNull(thing, "thing cannot be null");
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_BRAIN)) {
|
||||
final HttpService localHttpService = httpService;
|
||||
final NetworkAddressService localNetworkAddressService = networkAddressService;
|
||||
|
||||
Objects.requireNonNull(localHttpService, "HttpService cannot be null");
|
||||
Objects.requireNonNull(localNetworkAddressService, "networkAddressService cannot be null");
|
||||
|
||||
final int port = HttpServiceUtil.getHttpServicePort(this.bundleContext);
|
||||
|
||||
final NeeoBrainHandler handler = new NeeoBrainHandler((Bridge) thing,
|
||||
port < 0 ? NeeoConstants.DEFAULT_BRAIN_HTTP_PORT : port, localHttpService,
|
||||
localNetworkAddressService);
|
||||
registerRoomDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_ROOM)) {
|
||||
final NeeoRoomHandler handler = new NeeoRoomHandler((Bridge) thing);
|
||||
registerDeviceDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (thingTypeUID.equals(NeeoConstants.THING_TYPE_DEVICE)) {
|
||||
return new NeeoDeviceHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to register the room discovery service
|
||||
*
|
||||
* @param handler a non-null brain handler
|
||||
*/
|
||||
private void registerRoomDiscoveryService(NeeoBrainHandler handler) {
|
||||
Objects.requireNonNull(handler, "handler cannot be null");
|
||||
final NeeoRoomDiscoveryService discoveryService = new NeeoRoomDiscoveryService(handler);
|
||||
this.discoveryServiceRegs.put(handler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to register the device discovery service
|
||||
*
|
||||
* @param handler a non-null room handler
|
||||
*/
|
||||
private void registerDeviceDiscoveryService(NeeoRoomHandler handler) {
|
||||
Objects.requireNonNull(handler, "handler cannot be null");
|
||||
final NeeoDeviceDiscoveryService discoveryService = new NeeoDeviceDiscoveryService(handler);
|
||||
this.discoveryServiceRegs.put(handler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
final ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
serviceReg.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/**
|
||||
* 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.neeo.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainApi;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.NeeoHandlerCallback;
|
||||
import org.openhab.binding.neeo.internal.NeeoRoomConfig;
|
||||
import org.openhab.binding.neeo.internal.NeeoRoomProtocol;
|
||||
import org.openhab.binding.neeo.internal.NeeoUtil;
|
||||
import org.openhab.binding.neeo.internal.UidUtils;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoAction;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoRoom;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A subclass of {@link BaseBridgeHandler} that is responsible for handling commands for a room
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRoomHandler extends BaseBridgeHandler {
|
||||
|
||||
/** The logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(NeeoRoomHandler.class);
|
||||
|
||||
/**
|
||||
* The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> refreshTask = new AtomicReference<>();
|
||||
|
||||
/** The {@link NeeoRoomProtocol} (null until set by {@link #initializationTask}) */
|
||||
private final AtomicReference<@Nullable NeeoRoomProtocol> roomProtocol = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo room handler.
|
||||
*
|
||||
* @param bridge the non-null bridge
|
||||
*/
|
||||
NeeoRoomHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
Objects.requireNonNull(bridge, "bridge cannot be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
Objects.requireNonNull(channelUID, "channelUID cannot be null");
|
||||
Objects.requireNonNull(command, "command cannot be null");
|
||||
|
||||
final NeeoRoomProtocol protocol = roomProtocol.get();
|
||||
if (protocol == null) {
|
||||
logger.debug("Protocol is null - ignoring update: {}", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
final String[] channelIds = UidUtils.parseChannelId(channelUID);
|
||||
if (channelIds.length == 0) {
|
||||
logger.debug("Bad group declaration: {}", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
final String localGroupId = channelUID.getGroupId();
|
||||
final String groupId = localGroupId == null || StringUtils.isEmpty(localGroupId) ? "" : localGroupId;
|
||||
final String channelId = channelIds[0];
|
||||
final String channelKey = channelIds.length > 1 ? channelIds[1] : "";
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
refreshChannel(protocol, groupId, channelKey, channelId);
|
||||
} else {
|
||||
switch (groupId) {
|
||||
case NeeoConstants.ROOM_GROUP_RECIPE_ID:
|
||||
switch (channelId) {
|
||||
case NeeoConstants.ROOM_CHANNEL_STATUS:
|
||||
// Ignore OFF status updates
|
||||
if (command == OnOffType.ON) {
|
||||
protocol.startRecipe(channelKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
|
||||
switch (channelId) {
|
||||
case NeeoConstants.ROOM_CHANNEL_STATUS:
|
||||
if (command instanceof OnOffType) {
|
||||
protocol.setScenarioStatus(channelKey, command == OnOffType.ON);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown channel to set: {}", channelUID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the specified channel section, key and id using the specified protocol
|
||||
*
|
||||
* @param protocol a non-null protocol to use
|
||||
* @param groupId the non-empty channel section
|
||||
* @param channelKey the non-empty channel key
|
||||
* @param channelId the non-empty channel id
|
||||
*/
|
||||
private void refreshChannel(NeeoRoomProtocol protocol, String groupId, String channelKey, String channelId) {
|
||||
Objects.requireNonNull(protocol, "protocol cannot be null");
|
||||
NeeoUtil.requireNotEmpty(groupId, "groupId must not be empty");
|
||||
NeeoUtil.requireNotEmpty(channelId, "channelId must not be empty");
|
||||
|
||||
switch (groupId) {
|
||||
case NeeoConstants.ROOM_GROUP_RECIPE_ID:
|
||||
NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
|
||||
switch (channelId) {
|
||||
case NeeoConstants.ROOM_CHANNEL_NAME:
|
||||
protocol.refreshRecipeName(channelKey);
|
||||
break;
|
||||
case NeeoConstants.ROOM_CHANNEL_TYPE:
|
||||
protocol.refreshRecipeType(channelKey);
|
||||
break;
|
||||
case NeeoConstants.ROOM_CHANNEL_ENABLED:
|
||||
protocol.refreshRecipeEnabled(channelKey);
|
||||
break;
|
||||
case NeeoConstants.ROOM_CHANNEL_STATUS:
|
||||
protocol.refreshRecipeStatus(channelKey);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
|
||||
NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
|
||||
switch (channelId) {
|
||||
case NeeoConstants.ROOM_CHANNEL_NAME:
|
||||
protocol.refreshScenarioName(channelKey);
|
||||
break;
|
||||
case NeeoConstants.ROOM_CHANNEL_CONFIGURED:
|
||||
protocol.refreshScenarioConfigured(channelKey);
|
||||
break;
|
||||
case NeeoConstants.ROOM_CHANNEL_STATUS:
|
||||
protocol.refreshScenarioStatus(channelKey);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
|
||||
initializeTask();
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the task be creating the {@link NeeoRoomProtocol}, going online and then scheduling the refresh task.
|
||||
*/
|
||||
private void initializeTask() {
|
||||
final NeeoRoomConfig config = getConfigAs(NeeoRoomConfig.class);
|
||||
|
||||
final String roomKey = config.getRoomKey();
|
||||
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Room key (from the parent room bridge) was not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
NeeoUtil.checkInterrupt();
|
||||
final NeeoBrainApi brainApi = getNeeoBrainApi();
|
||||
if (brainApi == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Cannot find the NEEO Brain API");
|
||||
return;
|
||||
}
|
||||
|
||||
final NeeoRoom room = brainApi.getRoom(roomKey);
|
||||
|
||||
final ThingUID thingUid = getThing().getUID();
|
||||
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
properties.put("Key", roomKey);
|
||||
|
||||
final ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withLabel(room.getName() + " (NEEO " + brainApi.getBrain().getKey() + ")")
|
||||
.withProperties(properties).withChannels(ChannelUtils.generateChannels(thingUid, room));
|
||||
updateThing(thingBuilder.build());
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
final NeeoRoomProtocol protocol = new NeeoRoomProtocol(new NeeoHandlerCallback() {
|
||||
|
||||
@Override
|
||||
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
|
||||
updateStatus(status, detail, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(String channelId, State state) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String propertyName, String propertyValue) {
|
||||
getThing().setProperty(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleTask(Runnable task, long milliSeconds) {
|
||||
scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerEvent(String channelID, String event) {
|
||||
triggerChannel(channelID, event);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoBrainApi getApi() {
|
||||
return getNeeoBrainApi();
|
||||
}
|
||||
}, roomKey);
|
||||
roomProtocol.getAndSet(protocol);
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
if (config.getRefreshPolling() > 0) {
|
||||
NeeoUtil.checkInterrupt();
|
||||
NeeoUtil.cancel(refreshTask.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
refreshState();
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Refresh State was interrupted", e);
|
||||
}
|
||||
}, 0, config.getRefreshPolling(), TimeUnit.SECONDS)));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException during initialization", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Room " + config.getRoomKey() + " couldn't be found");
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Initialization was interrupted", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"Initialization was interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the action if it applies to this room
|
||||
*
|
||||
* @param action a non-null action to process
|
||||
*/
|
||||
void processAction(NeeoAction action) {
|
||||
Objects.requireNonNull(action, "action cannot be null");
|
||||
final NeeoRoomProtocol protocol = roomProtocol.get();
|
||||
if (protocol != null) {
|
||||
protocol.processAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the state of the room by calling {@link NeeoRoomProtocol#refreshState()}
|
||||
*
|
||||
* @throws InterruptedException if the call is interrupted
|
||||
*/
|
||||
private void refreshState() throws InterruptedException {
|
||||
NeeoUtil.checkInterrupt();
|
||||
final NeeoRoomProtocol protocol = roomProtocol.get();
|
||||
if (protocol != null) {
|
||||
NeeoUtil.checkInterrupt();
|
||||
protocol.refreshState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return the {@link NeeoBrainHandler} associated with this handler
|
||||
*
|
||||
* @return a possibly null {@link NeeoBrainHandler}
|
||||
*/
|
||||
@Nullable
|
||||
private NeeoBrainHandler getBrainHandler() {
|
||||
final Bridge parent = getBridge();
|
||||
if (parent != null) {
|
||||
final BridgeHandler handler = parent.getHandler();
|
||||
if (handler instanceof NeeoBrainHandler) {
|
||||
return ((NeeoBrainHandler) handler);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link NeeoBrainApi} associated with this handler.
|
||||
*
|
||||
* @return the {@link NeeoBrainApi} or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoBrainApi getNeeoBrainApi() {
|
||||
final NeeoBrainHandler handler = getBrainHandler();
|
||||
return handler == null ? null : handler.getNeeoBrainApi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the brain ID associated with this handler.
|
||||
*
|
||||
* @return the brain ID or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public String getNeeoBrainId() {
|
||||
final NeeoBrainHandler handler = getBrainHandler();
|
||||
return handler == null ? null : handler.getNeeoBrainId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
NeeoUtil.cancel(initializationTask.getAndSet(null));
|
||||
NeeoUtil.cancel(refreshTask.getAndSet(null));
|
||||
roomProtocol.getAndSet(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an error response (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ErrorResponse {
|
||||
|
||||
/** The error */
|
||||
@Nullable
|
||||
private String error;
|
||||
|
||||
/** The message */
|
||||
@Nullable
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Gets the error.
|
||||
*
|
||||
* @return the error
|
||||
*/
|
||||
@Nullable
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message.
|
||||
*
|
||||
* @return the message
|
||||
*/
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ErrorResponse [error=" + error + ", message=" + message + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an execute result (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ExecuteResult {
|
||||
|
||||
/** The estimated duration */
|
||||
private int estimatedDuration;
|
||||
|
||||
/** The name */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The start time */
|
||||
private long startTime;
|
||||
|
||||
/** The steps */
|
||||
private ExecuteStep @Nullable [] steps;
|
||||
|
||||
/** The type */
|
||||
@Nullable
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Gets the estimated duration.
|
||||
*
|
||||
* @return the estimated duration
|
||||
*/
|
||||
public int getEstimatedDuration() {
|
||||
return estimatedDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start time.
|
||||
*
|
||||
* @return the start time
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the steps.
|
||||
*
|
||||
* @return the steps
|
||||
*/
|
||||
public ExecuteStep[] getSteps() {
|
||||
final ExecuteStep @Nullable [] localSteps = steps;
|
||||
return localSteps == null ? new ExecuteStep[0] : localSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
@Nullable
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExecuteResult [estimatedDuration=" + estimatedDuration + ", name=" + name + ", startTime=" + startTime
|
||||
+ ", steps=" + Arrays.toString(steps) + ", type=" + type + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an execute setp (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ExecuteStep {
|
||||
|
||||
/** The duration of the step */
|
||||
private int duration;
|
||||
|
||||
/** The text describing the step */
|
||||
@Nullable
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Gets the duration of the step
|
||||
*
|
||||
* @return the duration
|
||||
*/
|
||||
public int getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text describing the step
|
||||
*
|
||||
* @return the text
|
||||
*/
|
||||
@Nullable
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExecuteStep [duration=" + duration + ", text=" + text + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The model representing an forward actions result (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoAction {
|
||||
/** The action - can be null */
|
||||
@Nullable
|
||||
private String action;
|
||||
|
||||
/** The action parameter - generally null */
|
||||
@Nullable
|
||||
@SerializedName("actionparameter")
|
||||
private String actionParameter;
|
||||
|
||||
/** The recipe name - only valid on launch of recipe */
|
||||
@Nullable
|
||||
private String recipe;
|
||||
|
||||
/** The device name - usually filled */
|
||||
@Nullable
|
||||
private String device;
|
||||
|
||||
/** The room name - usually filled */
|
||||
@Nullable
|
||||
private String room;
|
||||
|
||||
/**
|
||||
* Returns the action
|
||||
*
|
||||
* @return a possibly null, possibly empty action
|
||||
*/
|
||||
@Nullable
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action parameter
|
||||
*
|
||||
* @return a possibly null, possibly empty action parameter
|
||||
*/
|
||||
@Nullable
|
||||
public String getActionParameter() {
|
||||
return actionParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recipe name
|
||||
*
|
||||
* @return a possibly null, possibly empty recipe name
|
||||
*/
|
||||
@Nullable
|
||||
public String getRecipe() {
|
||||
return recipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the device name
|
||||
*
|
||||
* @return a possibly null, possibly empty device name
|
||||
*/
|
||||
@Nullable
|
||||
public String getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the room name
|
||||
*
|
||||
* @return a possibly null, possibly room name
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoom() {
|
||||
return room;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoAction [action=" + action + ", actionParameter=" + actionParameter + ", recipe=" + recipe
|
||||
+ ", device=" + device + ", room=" + room + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Brain(serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoBrain {
|
||||
|
||||
/** The brain name */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The version of the brain */
|
||||
@Nullable
|
||||
private String version;
|
||||
|
||||
/** The brain's label */
|
||||
@Nullable
|
||||
private String label;
|
||||
|
||||
/** Whether the brain has been configured */
|
||||
private boolean configured;
|
||||
|
||||
/** The brain key */
|
||||
@Nullable
|
||||
private String key;
|
||||
|
||||
/** ?? The brain airkey ?? */
|
||||
@Nullable
|
||||
private String airkey;
|
||||
|
||||
/** Last time the brain was changed */
|
||||
private long lastchange;
|
||||
|
||||
/** The rooms in the brain */
|
||||
@Nullable
|
||||
private NeeoRooms rooms;
|
||||
|
||||
/**
|
||||
* Gets the brain name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of the brain
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
@Nullable
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the brain's label
|
||||
*
|
||||
* @return the label
|
||||
*/
|
||||
@Nullable
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the brain is configured
|
||||
*
|
||||
* @return true, if is configured
|
||||
*/
|
||||
public boolean isConfigured() {
|
||||
return configured;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the brain key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
@Nullable
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the brain's airkey
|
||||
*
|
||||
* @return the airkey
|
||||
*/
|
||||
@Nullable
|
||||
public String getAirkey() {
|
||||
return airkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last time the brain was changed
|
||||
*
|
||||
* @return the lastchange
|
||||
*/
|
||||
public long getLastChange() {
|
||||
return lastchange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rooms in the brain
|
||||
*
|
||||
* @return the rooms
|
||||
*/
|
||||
public NeeoRooms getRooms() {
|
||||
final NeeoRooms localRooms = rooms;
|
||||
return localRooms == null ? new NeeoRooms(new NeeoRoom[0]) : localRooms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoBrain [name=" + name + ", version=" + version + ", label=" + label + ", configured=" + configured
|
||||
+ ", key=" + key + ", airkey=" + airkey + ", lastchange=" + lastchange + ", rooms=" + rooms + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an NEEO Device (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDevice {
|
||||
|
||||
/** The device name */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The associated room name */
|
||||
@Nullable
|
||||
private String roomName;
|
||||
|
||||
/** The associated room key */
|
||||
@Nullable
|
||||
private String roomKey;
|
||||
|
||||
/** The adapter device id */
|
||||
@Nullable
|
||||
private String adapterDeviceId;
|
||||
|
||||
/** The device key */
|
||||
@Nullable
|
||||
private String key;
|
||||
|
||||
/** The macros for the device */
|
||||
@Nullable
|
||||
private NeeoMacros macros;
|
||||
|
||||
/** The device details */
|
||||
@Nullable
|
||||
private NeeoDeviceDetails details;
|
||||
|
||||
/**
|
||||
* Gets the device name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room name
|
||||
*
|
||||
* @return the room name
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room key
|
||||
*
|
||||
* @return the room key
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomKey() {
|
||||
return roomKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the adapter device id
|
||||
*
|
||||
* @return the adapter device id
|
||||
*/
|
||||
@Nullable
|
||||
public String getAdapterDeviceId() {
|
||||
return adapterDeviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the macro key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
@Nullable
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the macros for the device
|
||||
*
|
||||
* @return the macros
|
||||
*/
|
||||
public NeeoMacros getMacros() {
|
||||
final NeeoMacros localMacros = macros;
|
||||
return localMacros == null ? new NeeoMacros(new NeeoMacro[0]) : localMacros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the details for the device
|
||||
*
|
||||
* @return the details
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoDeviceDetails getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoDevice [name=" + name + ", roomName=" + roomName + ", roomKey=" + roomKey + ", adapterDeviceId="
|
||||
+ adapterDeviceId + ", key=" + key + ", macros=" + macros + ", details=" + details + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Device Details (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDeviceDetails {
|
||||
|
||||
/** The source name (neeo-deviceadapter or sdk name) */
|
||||
@Nullable
|
||||
private String sourceName;
|
||||
|
||||
/** The adapter name (name given by source) */
|
||||
@Nullable
|
||||
private String adapterName;
|
||||
|
||||
/** The NEEO type */
|
||||
@Nullable
|
||||
private String type;
|
||||
|
||||
/** The manufacture */
|
||||
@Nullable
|
||||
private String manufacturer;
|
||||
|
||||
/** The name of the device given by source */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The timings of the device */
|
||||
@Nullable
|
||||
private NeeoDeviceDetailsTiming timing;
|
||||
|
||||
/** The device capabilities */
|
||||
private String @Nullable [] deviceCapabilities;
|
||||
|
||||
/**
|
||||
* The device source name
|
||||
*
|
||||
* @return the device source name
|
||||
*/
|
||||
@Nullable
|
||||
public String getSourceName() {
|
||||
return sourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The device adapter name (given by the source)
|
||||
*
|
||||
* @return the adapter name
|
||||
*/
|
||||
@Nullable
|
||||
public String getAdapterName() {
|
||||
return adapterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The NEEO device type
|
||||
*
|
||||
* @return the NEEO device type
|
||||
*/
|
||||
@Nullable
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The manufacturer of the device
|
||||
*
|
||||
* @return the manufacturer
|
||||
*/
|
||||
@Nullable
|
||||
public String getManufacturer() {
|
||||
return manufacturer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the device (given by the source)
|
||||
*
|
||||
* @return the device name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The device timing
|
||||
*
|
||||
* @return the timings
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoDeviceDetailsTiming getTiming() {
|
||||
return timing;
|
||||
}
|
||||
|
||||
/**
|
||||
* The device capabilities
|
||||
*
|
||||
* @return the capabilities
|
||||
*/
|
||||
public String[] getDeviceCapabilities() {
|
||||
final String[] localCapabilities = deviceCapabilities;
|
||||
return localCapabilities == null ? new String[0] : localCapabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoDeviceDetails [sourceName=" + sourceName + ", adapterName=" + adapterName + ", type=" + type
|
||||
+ ", manufacturer=" + manufacturer + ", name=" + name + ", timing=" + timing + ", deviceCapabilities="
|
||||
+ StringUtils.join(deviceCapabilities, ',') + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Device Details Timings (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDeviceDetailsTiming {
|
||||
|
||||
/** Standby delay in ms (time to turn on device) */
|
||||
@Nullable
|
||||
private Integer standbyCommandDelay;
|
||||
|
||||
/** Source switch in ms (time to switch inputs) */
|
||||
@Nullable
|
||||
private Integer sourceSwitchDelay;
|
||||
|
||||
/** Shutdown delay in ms (time to shutdown device) */
|
||||
@Nullable
|
||||
private Integer shutdownDelay;
|
||||
|
||||
/**
|
||||
* The time (in ms) to turn on device. May be null if not supported
|
||||
*
|
||||
* @return a possibly null time to turn on device
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getStandbyCommandDelay() {
|
||||
return standbyCommandDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* The time (in ms) to switch inputs. May be null if not supported
|
||||
*
|
||||
* @return a possibly null time to switch inputs
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getSourceSwitchDelay() {
|
||||
return sourceSwitchDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* The time (in ms) to shutdown device. May be null if not supported
|
||||
*
|
||||
* @return a possibly null time to shut down device
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getShutdownDelay() {
|
||||
return shutdownDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoDeviceDetailsTiming [standbyCommandDelay=" + standbyCommandDelay + ", sourceSwitchDelay="
|
||||
+ sourceSwitchDelay + ", shutdownDelay=" + shutdownDelay + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing Neeo Devices (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDevices {
|
||||
|
||||
/** The devices. */
|
||||
private final NeeoDevice @Nullable [] devices;
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo devices.
|
||||
*
|
||||
* @param devices the devices
|
||||
*/
|
||||
NeeoDevices(NeeoDevice[] devices) {
|
||||
Objects.requireNonNull(devices, "devices cannot be null");
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the devices.
|
||||
*
|
||||
* @return the devices
|
||||
*/
|
||||
public NeeoDevice[] getDevices() {
|
||||
final NeeoDevice[] localDevices = devices;
|
||||
return localDevices == null ? new NeeoDevice[0] : localDevices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the device.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the device
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoDevice getDevice(String key) {
|
||||
for (NeeoDevice device : getDevices()) {
|
||||
if (StringUtils.equalsIgnoreCase(key, device.getKey())) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoDevice [devices=" + Arrays.toString(devices) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.models;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoDevices} class
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoDevicesDeserializer implements JsonDeserializer<@Nullable NeeoDevices> {
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoDevices deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
|
||||
Objects.requireNonNull(context, "context cannot be null");
|
||||
|
||||
if (jsonElement instanceof JsonObject) {
|
||||
final List<NeeoDevice> scenarios = new ArrayList<>();
|
||||
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
|
||||
final NeeoDevice device = context.deserialize(entry.getValue(), NeeoDevice.class);
|
||||
scenarios.add(device);
|
||||
}
|
||||
|
||||
return new NeeoDevices(scenarios.toArray(new NeeoDevice[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an forward actions request (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoForwardActions {
|
||||
/** The host to forward actions to */
|
||||
@Nullable
|
||||
private final String host;
|
||||
|
||||
/** The port to use */
|
||||
private final int port;
|
||||
|
||||
/** The path the actions should go to */
|
||||
@Nullable
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Creates the forward actions from the given parms
|
||||
*
|
||||
* @param host the host name
|
||||
* @param port the port
|
||||
* @param path the path
|
||||
*/
|
||||
public NeeoForwardActions(String host, int port, String path) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name to forward actions to
|
||||
*
|
||||
* @return the hostname
|
||||
*/
|
||||
@Nullable
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port number
|
||||
*
|
||||
* @return the port number
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to use
|
||||
*
|
||||
* @return the path
|
||||
*/
|
||||
@Nullable
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoForwardActions [host=" + host + ", port=" + port + ", path=" + path + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Macro (serialize/deserialize json use only)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoMacro {
|
||||
|
||||
/** The macro key */
|
||||
@Nullable
|
||||
private String key;
|
||||
|
||||
/** The component type */
|
||||
@Nullable
|
||||
private String componentType;
|
||||
|
||||
/** The macro name */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The macro label */
|
||||
@Nullable
|
||||
private String label;
|
||||
|
||||
/** The associated device name */
|
||||
@Nullable
|
||||
private String deviceName;
|
||||
|
||||
/** The associated room name */
|
||||
@Nullable
|
||||
private String roomName;
|
||||
|
||||
/**
|
||||
* Gets the macro key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
@Nullable
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component type
|
||||
*
|
||||
* @return the component type
|
||||
*/
|
||||
@Nullable
|
||||
public String getComponentType() {
|
||||
return componentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the macro name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the macro label
|
||||
*
|
||||
* @return the label
|
||||
*/
|
||||
@Nullable
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated device name
|
||||
*
|
||||
* @return the device name
|
||||
*/
|
||||
@Nullable
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room name
|
||||
*
|
||||
* @return the room name
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoMacro [key=" + key + ", componentType=" + componentType + ", name=" + name + ", label=" + label
|
||||
+ ", deviceName=" + deviceName + ", roomName=" + roomName + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing Neeo Macros (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoMacros {
|
||||
|
||||
/** The macros. */
|
||||
private final NeeoMacro @Nullable [] macros;
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo macros.
|
||||
*
|
||||
* @param macros the macros
|
||||
*/
|
||||
NeeoMacros(NeeoMacro[] macros) {
|
||||
Objects.requireNonNull(macros, "macros cannot be null");
|
||||
this.macros = macros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the macros.
|
||||
*
|
||||
* @return the macros
|
||||
*/
|
||||
public NeeoMacro[] getMacros() {
|
||||
final NeeoMacro @Nullable [] localMacros = macros;
|
||||
return localMacros == null ? new NeeoMacro[0] : localMacros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the macro.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the macro
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoMacro getMacro(String key) {
|
||||
for (NeeoMacro macro : getMacros()) {
|
||||
if (StringUtils.equalsIgnoreCase(key, macro.getKey())) {
|
||||
return macro;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoMacro [macros=" + Arrays.toString(macros) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.models;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoMacros} class
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoMacrosDeserializer implements JsonDeserializer<@Nullable NeeoMacros> {
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoMacros deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
|
||||
Objects.requireNonNull(context, "context cannot be null");
|
||||
|
||||
if (jsonElement instanceof JsonObject) {
|
||||
final List<NeeoMacro> scenarios = new ArrayList<>();
|
||||
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
|
||||
final NeeoMacro macro = context.deserialize(entry.getValue(), NeeoMacro.class);
|
||||
scenarios.add(macro);
|
||||
}
|
||||
|
||||
return new NeeoMacros(scenarios.toArray(new NeeoMacro[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Recipe (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRecipe {
|
||||
|
||||
/** Recipe type for launching the recipe */
|
||||
public static final String LAUNCH = "launch";
|
||||
|
||||
/** Recipe type for powering off the recipe */
|
||||
public static final String POWEROFF = "poweroff";
|
||||
|
||||
/** The recipe key */
|
||||
@Nullable
|
||||
private String key;
|
||||
|
||||
/** The type of recipe (generally launch/poweroff) */
|
||||
@Nullable
|
||||
private String type;
|
||||
|
||||
/** The name of the recipe */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** Whether the recipe is enabled */
|
||||
private boolean enabled;
|
||||
|
||||
/** ?? whether the recipe is dirty ?? */
|
||||
private boolean dirty;
|
||||
|
||||
// May be used in the future...
|
||||
// private NeeoStep[] steps;
|
||||
// private NeeoCondition[] conditions;
|
||||
// private NeeoTrigger trigger;
|
||||
|
||||
/** The associated room key */
|
||||
@Nullable
|
||||
private String roomKey;
|
||||
|
||||
/** The associated room name. */
|
||||
@Nullable
|
||||
private String roomName;
|
||||
|
||||
/** The scenario key recipe is linked to */
|
||||
@Nullable
|
||||
private String scenarioKey;
|
||||
|
||||
/** Whether the recipe is hidden or not */
|
||||
private boolean isHiddenRecipe;
|
||||
|
||||
/** ?? whether this is a custom recipe ?? */
|
||||
private boolean isCustom;
|
||||
|
||||
/**
|
||||
* Gets the recipe key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
@Nullable
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipe type
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
@Nullable
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipe name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the recipe is enabled
|
||||
*
|
||||
* @return true, if is enabled
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the recipe is dirty
|
||||
*
|
||||
* @return true, if is dirty
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room key.
|
||||
*
|
||||
* @return the room key
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomKey() {
|
||||
return roomKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room name.
|
||||
*
|
||||
* @return the room name
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated scenario key.
|
||||
*
|
||||
* @return the scenario key
|
||||
*/
|
||||
@Nullable
|
||||
public String getScenarioKey() {
|
||||
return scenarioKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the recipe is hidden
|
||||
*
|
||||
* @return true, if is hidden recipe
|
||||
*/
|
||||
public boolean isHiddenRecipe() {
|
||||
return isHiddenRecipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if its a custom recipe
|
||||
*
|
||||
* @return true, if is custom
|
||||
*/
|
||||
public boolean isCustom() {
|
||||
return isCustom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoRecipe [key=" + key + ", type=" + type + ", name=" + name + ", enabled=" + enabled + ", dirty="
|
||||
+ dirty + ", roomKey=" + roomKey + ", roomName=" + roomName + ", scenarioKey=" + scenarioKey
|
||||
+ ", isHiddenRecipe=" + isHiddenRecipe + ", isCustom=" + isCustom + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing Neeo Recipes (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRecipes {
|
||||
|
||||
/** The recipes. */
|
||||
private NeeoRecipe @Nullable [] recipes;
|
||||
|
||||
/**
|
||||
* Creates the recipes from the given recipes
|
||||
*
|
||||
* @param recipes the recipes
|
||||
*/
|
||||
NeeoRecipes(NeeoRecipe[] recipes) {
|
||||
Objects.requireNonNull(recipes, "recipes cannot be null");
|
||||
this.recipes = recipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipes.
|
||||
*
|
||||
* @return the recipes
|
||||
*/
|
||||
public NeeoRecipe[] getRecipes() {
|
||||
final NeeoRecipe[] localRecipes = recipes;
|
||||
return localRecipes == null ? new NeeoRecipe[0] : localRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipe by key
|
||||
*
|
||||
* @param key the key
|
||||
* @return the recipe or null if none found
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoRecipe getRecipe(String key) {
|
||||
if (recipes == null || StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (NeeoRecipe recipe : getRecipes()) {
|
||||
if (StringUtils.equalsIgnoreCase(key, recipe.getKey())) {
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipe by a scenario key and recipe type
|
||||
*
|
||||
* @param key the key
|
||||
* @param type the recipe type
|
||||
* @return the recipe or null if none found
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoRecipe getRecipeByScenarioKey(String key, String type) {
|
||||
if (recipes == null || StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (NeeoRecipe recipe : getRecipes()) {
|
||||
if (StringUtils.equalsIgnoreCase(key, recipe.getScenarioKey())
|
||||
&& StringUtils.equalsIgnoreCase(type, recipe.getType())) {
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipe by name
|
||||
*
|
||||
* @param name the recipe name
|
||||
* @return the recipe or null if none found
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoRecipe getRecipeByName(String name) {
|
||||
if (recipes == null || StringUtils.isEmpty(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (NeeoRecipe recipe : getRecipes()) {
|
||||
if (StringUtils.equalsIgnoreCase(name, recipe.getName())) {
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoRecipes [recipes=" + Arrays.toString(recipes) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.models;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoRecipes} class
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRecipesDeserializer implements JsonDeserializer<@Nullable NeeoRecipes> {
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoRecipes deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
|
||||
Objects.requireNonNull(context, "context cannot be null");
|
||||
|
||||
if (jsonElement instanceof JsonObject) {
|
||||
final List<NeeoRecipe> recipes = new ArrayList<>();
|
||||
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
|
||||
final NeeoRecipe recipe = context.deserialize(entry.getValue(), NeeoRecipe.class);
|
||||
recipes.add(recipe);
|
||||
}
|
||||
|
||||
return new NeeoRecipes(recipes.toArray(new NeeoRecipe[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.neeo.internal.models;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Room (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRoom {
|
||||
|
||||
/** The room name */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The room key */
|
||||
@Nullable
|
||||
private String key;
|
||||
|
||||
/** The devices in the room */
|
||||
@Nullable
|
||||
private NeeoDevices devices;
|
||||
|
||||
/** The scenarios in the room */
|
||||
@Nullable
|
||||
private NeeoScenarios scenarios;
|
||||
|
||||
/** The recipes in the room */
|
||||
@Nullable
|
||||
private NeeoRecipes recipes;
|
||||
|
||||
/**
|
||||
* Gets the room name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the room key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
@Nullable
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipes in the room
|
||||
*
|
||||
* @return the recipes
|
||||
*/
|
||||
public NeeoRecipes getRecipes() {
|
||||
final NeeoRecipes localRecipes = recipes;
|
||||
return localRecipes == null ? new NeeoRecipes(new NeeoRecipe[0]) : localRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the devices in the room
|
||||
*
|
||||
* @return the devices
|
||||
*/
|
||||
public NeeoDevices getDevices() {
|
||||
final NeeoDevices localDevices = devices;
|
||||
return localDevices == null ? new NeeoDevices(new NeeoDevice[0]) : localDevices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scenarios in the room
|
||||
*
|
||||
* @return the scenarios
|
||||
*/
|
||||
public NeeoScenarios getScenarios() {
|
||||
final NeeoScenarios localScenarios = scenarios;
|
||||
return localScenarios == null ? new NeeoScenarios(new NeeoScenario[0]) : localScenarios;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoRoom [name=" + name + ", key=" + key + ", scenarios=" + scenarios + ", devices=" + devices
|
||||
+ ", recipes=" + recipes + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing Neeo Rooms (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRooms {
|
||||
|
||||
/** The rooms. */
|
||||
private final NeeoRoom @Nullable [] rooms;
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo rooms.
|
||||
*
|
||||
* @param rooms the rooms
|
||||
*/
|
||||
NeeoRooms(NeeoRoom[] rooms) {
|
||||
Objects.requireNonNull(rooms, "rooms cannot be null");
|
||||
this.rooms = rooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rooms.
|
||||
*
|
||||
* @return the rooms
|
||||
*/
|
||||
public NeeoRoom[] getRooms() {
|
||||
final NeeoRoom @Nullable [] localRooms = rooms;
|
||||
return localRooms == null ? new NeeoRoom[0] : localRooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the room.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the room
|
||||
*/
|
||||
NeeoRoom getRoom(String key) {
|
||||
for (NeeoRoom room : getRooms()) {
|
||||
if (StringUtils.equalsIgnoreCase(key, room.getKey())) {
|
||||
return room;
|
||||
}
|
||||
}
|
||||
return new NeeoRoom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoRooms [rooms=" + Arrays.toString(rooms) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoRooms} class
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoRoomsDeserializer implements JsonDeserializer<@Nullable NeeoRooms> {
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoRooms deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
|
||||
Objects.requireNonNull(context, "context cannot be null");
|
||||
if (jsonElement instanceof JsonObject) {
|
||||
final List<NeeoRoom> recipes = new ArrayList<>();
|
||||
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
|
||||
final NeeoRoom room = context.deserialize(entry.getValue(), NeeoRoom.class);
|
||||
recipes.add(room);
|
||||
}
|
||||
|
||||
return new NeeoRooms(recipes.toArray(new NeeoRoom[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing an Neeo Scenario (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoScenario {
|
||||
|
||||
/** The scenario name */
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** The main device key */
|
||||
@Nullable
|
||||
private String mainDeviceKey;
|
||||
|
||||
/** The volume device key */
|
||||
@Nullable
|
||||
private String volumeDeviceKey;
|
||||
|
||||
/** The scenario key */
|
||||
@Nullable
|
||||
private String key;
|
||||
|
||||
/** Whether the scenario is pending configuration */
|
||||
private boolean configured;
|
||||
|
||||
/** The associated room key */
|
||||
@Nullable
|
||||
private String roomKey;
|
||||
|
||||
/** The associated room name */
|
||||
@Nullable
|
||||
private String roomName;
|
||||
|
||||
/** The devices that make up the scenario */
|
||||
private String @Nullable [] devices;
|
||||
|
||||
// may be used in the future
|
||||
// private final NeeoShortcut[] shortcuts;
|
||||
// @Nullable private String[] deviceInputMacroNames;
|
||||
// private final NeeoCapability[] capabilities;
|
||||
|
||||
/**
|
||||
* Gets the scenario name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main device key
|
||||
*
|
||||
* @return the main device key
|
||||
*/
|
||||
@Nullable
|
||||
public String getMainDeviceKey() {
|
||||
return mainDeviceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the volume device key
|
||||
*
|
||||
* @return the volume device key
|
||||
*/
|
||||
@Nullable
|
||||
public String getVolumeDeviceKey() {
|
||||
return volumeDeviceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scenario key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
@Nullable
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the scenario is pending configuration
|
||||
*
|
||||
* @return true, if is configured
|
||||
*/
|
||||
public boolean isConfigured() {
|
||||
return configured;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room key
|
||||
*
|
||||
* @return the room key
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomKey() {
|
||||
return roomKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated room name
|
||||
*
|
||||
* @return the room name
|
||||
*/
|
||||
@Nullable
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the devices
|
||||
*
|
||||
* @return the devices
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getDevices() {
|
||||
final String @Nullable [] localDevices = devices;
|
||||
return localDevices == null ? new String[0] : localDevices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoScenario [name=" + name + ", mainDeviceKey=" + mainDeviceKey + ", volumeDeviceKey="
|
||||
+ volumeDeviceKey + ", key=" + key + ", configured=" + configured + ", roomKey=" + roomKey
|
||||
+ ", roomName=" + roomName + ", devices=" + Arrays.toString(devices) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The model representing Neeo Scenarios (serialize/deserialize json use only).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoScenarios {
|
||||
|
||||
/** The scenarios. */
|
||||
private final NeeoScenario @Nullable [] scenarios;
|
||||
|
||||
/**
|
||||
* Instantiates a new neeo scenarios.
|
||||
*
|
||||
* @param scenarios the scenarios
|
||||
*/
|
||||
NeeoScenarios(NeeoScenario[] scenarios) {
|
||||
Objects.requireNonNull(scenarios, "scenarios cannot be null");
|
||||
this.scenarios = scenarios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scenarios.
|
||||
*
|
||||
* @return the scenarios
|
||||
*/
|
||||
public NeeoScenario[] getScenarios() {
|
||||
final NeeoScenario @Nullable [] localScenarios = scenarios;
|
||||
return localScenarios == null ? new NeeoScenario[0] : localScenarios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scenario.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the scenario
|
||||
*/
|
||||
@Nullable
|
||||
public NeeoScenario getScenario(String key) {
|
||||
for (NeeoScenario scenario : getScenarios()) {
|
||||
if (StringUtils.equalsIgnoreCase(key, scenario.getKey())) {
|
||||
return scenario;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeeoScenarios [scenarios=" + Arrays.toString(scenarios) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.neeo.internal.models;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoScenarios} class
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NeeoScenariosDeserializer implements JsonDeserializer<@Nullable NeeoScenarios> {
|
||||
@Nullable
|
||||
@Override
|
||||
public NeeoScenarios deserialize(@Nullable JsonElement jsonElement, @Nullable Type jtype,
|
||||
@Nullable JsonDeserializationContext context) throws JsonParseException {
|
||||
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
|
||||
Objects.requireNonNull(context, "context cannot be null");
|
||||
if (jsonElement instanceof JsonObject) {
|
||||
final List<NeeoScenario> scenarios = new ArrayList<>();
|
||||
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
|
||||
final NeeoScenario scenario = context.deserialize(entry.getValue(), NeeoScenario.class);
|
||||
scenarios.add(scenario);
|
||||
}
|
||||
|
||||
return new NeeoScenarios(scenarios.toArray(new NeeoScenario[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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.neeo.internal.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.Invocation.Builder;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.glassfish.jersey.filter.LoggingFilter;
|
||||
import org.openhab.binding.neeo.internal.NeeoUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class represents an HTTP session with a client
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpRequest implements AutoCloseable {
|
||||
|
||||
/** the logger */
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
|
||||
|
||||
/** The client to use */
|
||||
private final Client client;
|
||||
|
||||
/**
|
||||
* Instantiates a new request
|
||||
*/
|
||||
public HttpRequest() {
|
||||
client = ClientBuilder.newClient();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a get command to the specified uri
|
||||
*
|
||||
* @param uri the non-null uri
|
||||
* @return the {@link HttpResponse}
|
||||
*/
|
||||
public HttpResponse sendGetCommand(String uri) {
|
||||
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
|
||||
try {
|
||||
final Builder request = client.target(uri).request();
|
||||
|
||||
final Response content = request.get();
|
||||
|
||||
try {
|
||||
return new HttpResponse(content);
|
||||
} finally {
|
||||
content.close();
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send post JSON command using the body
|
||||
*
|
||||
* @param uri the non empty uri
|
||||
* @param body the non-null, possibly empty body
|
||||
* @return the {@link HttpResponse}
|
||||
*/
|
||||
public HttpResponse sendPostJsonCommand(String uri, String body) {
|
||||
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
|
||||
Objects.requireNonNull(body, "body cannot be null");
|
||||
|
||||
try {
|
||||
final Builder request = client.target(uri).request(MediaType.APPLICATION_JSON);
|
||||
|
||||
final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
return new HttpResponse(content);
|
||||
} finally {
|
||||
content.close();
|
||||
}
|
||||
// IllegalArgumentException/ProcessingException catches issues with the URI being invalid
|
||||
// as well
|
||||
} catch (IOException | IllegalStateException | IllegalArgumentException | ProcessingException e) {
|
||||
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
@@ -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.neeo.internal.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This class represents an {@link HttpRequest} response
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpResponse {
|
||||
|
||||
/** The http status */
|
||||
private final int httpStatus;
|
||||
|
||||
/** The http reason */
|
||||
private final String httpReason;
|
||||
|
||||
/** The http headers */
|
||||
private final Map<String, String> headers = new HashMap<>();
|
||||
|
||||
/** The contents as a raw byte array */
|
||||
private final byte @Nullable [] contents;
|
||||
|
||||
/**
|
||||
* Instantiates a new http response from the {@link Response}.
|
||||
*
|
||||
* @param response the non-null response
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
HttpResponse(Response response) throws IOException {
|
||||
Objects.requireNonNull(response, "response cannot be null");
|
||||
|
||||
httpStatus = response.getStatus();
|
||||
httpReason = response.getStatusInfo().getReasonPhrase();
|
||||
|
||||
if (response.hasEntity()) {
|
||||
InputStream is = response.readEntity(InputStream.class);
|
||||
contents = IOUtils.toByteArray(is);
|
||||
} else {
|
||||
contents = null;
|
||||
}
|
||||
|
||||
for (String key : response.getHeaders().keySet()) {
|
||||
headers.put(key, response.getHeaderString(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new http response.
|
||||
*
|
||||
* @param httpCode the http code
|
||||
* @param msg the msg
|
||||
*/
|
||||
HttpResponse(int httpCode, String msg) {
|
||||
httpStatus = httpCode;
|
||||
httpReason = msg;
|
||||
contents = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP status code.
|
||||
*
|
||||
* @return the HTTP status code
|
||||
*/
|
||||
public int getHttpCode() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content.
|
||||
*
|
||||
* @return the content
|
||||
*/
|
||||
public String getContent() {
|
||||
final byte[] localContents = contents;
|
||||
if (localContents == null || localContents.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return new String(localContents, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link IOException} from the {@link #httpReason}
|
||||
*
|
||||
* @return the IO exception
|
||||
*/
|
||||
public IOException createException() {
|
||||
return new IOException(httpReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getHttpCode() + " (" + (contents == null ? ("http reason: " + httpReason) : getContent()) + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.neeo.internal.net;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Logging adapter to use for Slf4j
|
||||
*
|
||||
* @author Tim Roberts - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class Slf4LoggingAdapter extends java.util.logging.Logger {
|
||||
|
||||
/** The logger. */
|
||||
private final Logger logger;
|
||||
|
||||
/**
|
||||
* Creates the logging adapter from the given logger
|
||||
*
|
||||
* @param logger a non-null logger to use
|
||||
*/
|
||||
protected Slf4LoggingAdapter(Logger logger) {
|
||||
super("jersey", null);
|
||||
Objects.requireNonNull(logger, "logger cannot be null");
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(@Nullable LogRecord record) {
|
||||
logger.debug("{}", record == null ? "" : record.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="neeo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Neeo Binding</name>
|
||||
<description>This binding allows you to discover NEEO Brain(s), discover the Rooms within and the Devices within those
|
||||
Rooms. The binding then will expose that information to openHAB and allow you to execute Recipes/Scenarios and Macros
|
||||
on the Devices</description>
|
||||
<author>Tim Roberts</author>
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,184 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="neeo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- The brain bridge -->
|
||||
<bridge-type id="brain">
|
||||
<label>Neeo Brain</label>
|
||||
<description>The NEEO Brain</description>
|
||||
<channels>
|
||||
<channel id="forwardActions" typeId="brain-forwardactions"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>IP or Host Name</label>
|
||||
<description>The IP or host name of the NEEO Brain</description>
|
||||
</parameter>
|
||||
<parameter name="discoverEmptyRooms" type="boolean">
|
||||
<label>Discover Empty Rooms</label>
|
||||
<description>Whether to discover rooms with no devices or not</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="enableForwardActions" type="boolean">
|
||||
<label>Enable Forward Actions</label>
|
||||
<description>Whether to have the NEEO Brain forward actions to openHAB</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="forwardChain" type="text">
|
||||
<label>Forward Chaining</label>
|
||||
<description>Comma delimited list of URLs to forward NEEO brain actions to</description>
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="checkStatusInterval" type="integer">
|
||||
<label>Check Status Interval</label>
|
||||
<description>The interval (in seconds) to check the status of the brain (specify <=0 to disable)</description>
|
||||
<default>10</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
<bridge-type id="room">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="brain"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Neeo Room</label>
|
||||
<description>Neeo Room</description>
|
||||
<channel-groups>
|
||||
<channel-group id="state" typeId="room-state"/>
|
||||
<channel-group id="scenario" typeId="room-scenario"/>
|
||||
<channel-group id="recipe" typeId="room-recipe"/>
|
||||
</channel-groups>
|
||||
<config-description>
|
||||
<parameter name="roomKey" type="text" required="true">
|
||||
<label>Room Key</label>
|
||||
<description>Unique key of the room</description>
|
||||
</parameter>
|
||||
<parameter name="excludeThings" type="boolean">
|
||||
<label>Exclude Things</label>
|
||||
<description>Exclude openHAB things (from NEEO transport)</description>
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="refreshPolling" type="integer">
|
||||
<label>Refresh Polling</label>
|
||||
<description>The time (in seconds) to refresh state (<= 0 to disable)</description>
|
||||
<default>120</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
<thing-type id="device">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="room"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Neeo Device</label>
|
||||
<description>Neeo Device</description>
|
||||
<channel-groups>
|
||||
<channel-group id="macros" typeId="device-macros"/>
|
||||
</channel-groups>
|
||||
<config-description>
|
||||
<parameter name="deviceKey" type="text" required="true">
|
||||
<label>Device Key</label>
|
||||
<description>Unique key of the device</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<!-- Channel types for the NEEO Brain -->
|
||||
<channel-type id="brain-forwardactions">
|
||||
<kind>trigger</kind>
|
||||
<label>Forward Actions</label>
|
||||
<description>The forward Actions</description>
|
||||
<event>
|
||||
</event>
|
||||
</channel-type>
|
||||
<!-- Channel Types for the NEEO Room -->
|
||||
<channel-group-type id="room-state">
|
||||
<label>Room State</label>
|
||||
<description>The room's state</description>
|
||||
<channels>
|
||||
<channel id="currentStep" typeId="room-state-currentstep"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-type id="room-state-currentstep">
|
||||
<kind>trigger</kind>
|
||||
<label>Current Step</label>
|
||||
<description>The current step executing</description>
|
||||
<event>
|
||||
</event>
|
||||
</channel-type>
|
||||
<channel-group-type id="room-recipe">
|
||||
<label>Recipes</label>
|
||||
<description>The room recipes</description>
|
||||
<channels>
|
||||
<channel id="name" typeId="room-recipe-name"/>
|
||||
<channel id="type" typeId="room-recipe-type"/>
|
||||
<channel id="enabled" typeId="room-recipe-enabled"/>
|
||||
<channel id="status" typeId="room-recipe-status"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-type id="room-recipe-name" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>The recipe name</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="room-recipe-type" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>The type of recipe</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="room-recipe-enabled" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>Whether the recipe is enabled or not.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="room-recipe-status">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>Send ON to execute the recipe</description>
|
||||
</channel-type>
|
||||
<channel-group-type id="room-scenario">
|
||||
<label>Scenarios</label>
|
||||
<description>The room scenarios</description>
|
||||
<channels>
|
||||
<channel id="name" typeId="room-scenario-name"/>
|
||||
<channel id="configured" typeId="room-scenario-configured"/>
|
||||
<channel id="status" typeId="room-scenario-status"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-type id="room-scenario-name" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>The scenario name</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="room-scenario-configured" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>Whether the scenario is configured or not</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="room-scenario-status">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>Whether the scenario is running or not (send ON to turn on the scenario, OFF to turn off the scenario)</description>
|
||||
</channel-type>
|
||||
<channel-group-type id="device-macros">
|
||||
<label>Macros</label>
|
||||
<description>Executable macros</description>
|
||||
<channels>
|
||||
<channel id="status" typeId="device-macros-status"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-type id="device-macros-status">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Label Dynamically Generated</label>
|
||||
<description>Send ON to trigger the macro</description>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user