added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.dlinksmarthome/.classpath
Normal file
32
bundles/org.openhab.binding.dlinksmarthome/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.dlinksmarthome/.project
Normal file
23
bundles/org.openhab.binding.dlinksmarthome/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.dlinksmarthome</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>
|
||||
13
bundles/org.openhab.binding.dlinksmarthome/NOTICE
Normal file
13
bundles/org.openhab.binding.dlinksmarthome/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
54
bundles/org.openhab.binding.dlinksmarthome/README.md
Normal file
54
bundles/org.openhab.binding.dlinksmarthome/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# D-Link Smart Home Binding
|
||||
|
||||
A binding for D-Link Smart Home devices.
|
||||
|
||||
## Supported Things
|
||||
|
||||
### DCH-S150 (WiFi motion sensor)
|
||||
|
||||
The binding has been tested with hardware revisions A1 and A2 running firmware version 1.22.
|
||||
|
||||
## Discovery
|
||||
|
||||
The binding can automatically discover devices that have already been added to the Wifi network. Please refer to your mydlink Home app for instructions on how to add your device to your Wifi network.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding does not require any special configuration.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
It is recommended to let the binding discover and add devices.
|
||||
Once added the configuration must be updated to specify the PIN code located on the back of the device.
|
||||
|
||||
### DCH-S150
|
||||
|
||||
- **ipAddress** - Hostname or IP of the device
|
||||
- **pin** - PIN code from the back of the device
|
||||
|
||||
To manually configure a DCH-S150 Thing you must specify its IP address and PIN code.
|
||||
|
||||
In the Thing file, this looks like e.g.
|
||||
|
||||
```java
|
||||
Thing dlinksmarthome:DCH-S150:mysensor [ ipAddress="192.168.2.132" pin="1234" ]
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
### DCH-S150
|
||||
|
||||
- **motion** - Triggered when the sensor detects motion.
|
||||
|
||||
## Example usage
|
||||
|
||||
### DCH-S150
|
||||
|
||||
```perl
|
||||
rule "Landing motion"
|
||||
when
|
||||
Channel "dlinksmarthome:DCH-S150:90-8D-78-XX-XX-XX:motion" triggered
|
||||
then
|
||||
println("Motion has been detected")
|
||||
end
|
||||
```
|
||||
17
bundles/org.openhab.binding.dlinksmarthome/pom.xml
Normal file
17
bundles/org.openhab.binding.dlinksmarthome/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-v4_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.dlinksmarthome</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: D-Link Smart Home Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.dlinksmarthome-${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-dlinksmarthome" description="D-Link Smart Home Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<feature dependency="true">openhab.tp-jaxws</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.dlinksmarthome/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,466 @@
|
||||
/**
|
||||
* 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.dlinksmarthome.internal;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.soap.MessageFactory;
|
||||
import javax.xml.soap.MimeHeader;
|
||||
import javax.xml.soap.MimeHeaders;
|
||||
import javax.xml.soap.SOAPBody;
|
||||
import javax.xml.soap.SOAPElement;
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* The {@link DLinkHNAPCommunication} is responsible for communicating with D-Link
|
||||
* Smart Home devices using the HNAP interface.
|
||||
*
|
||||
* This abstract class handles login and authentication which is common between devices.
|
||||
*
|
||||
* Reverse engineered from Login.html and soapclient.js retrieved from the device.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
public abstract class DLinkHNAPCommunication {
|
||||
|
||||
// SOAP actions
|
||||
private static final String LOGIN_ACTION = "\"http://purenetworks.com/HNAP1/LOGIN\"";
|
||||
|
||||
// Strings used more than once
|
||||
private static final String LOGIN = "LOGIN";
|
||||
private static final String ACTION = "Action";
|
||||
private static final String USERNAME = "Username";
|
||||
private static final String LOGINPASSWORD = "LoginPassword";
|
||||
private static final String CAPTCHA = "Captcha";
|
||||
private static final String ADMIN = "Admin";
|
||||
private static final String LOGINRESULT = "LOGINResult";
|
||||
private static final String COOKIE = "Cookie";
|
||||
|
||||
/**
|
||||
* HNAP XMLNS
|
||||
*/
|
||||
protected static final String HNAP_XMLNS = "http://purenetworks.com/HNAP1";
|
||||
/**
|
||||
* The SOAP action HTML header
|
||||
*/
|
||||
protected static final String SOAPACTION = "SOAPAction";
|
||||
/**
|
||||
* OK represents a successful action
|
||||
*/
|
||||
protected static final String OK = "OK";
|
||||
|
||||
/**
|
||||
* Use to log connection issues
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(DLinkHNAPCommunication.class);
|
||||
|
||||
private URI uri;
|
||||
private final HttpClient httpClient;
|
||||
private final String pin;
|
||||
private String privateKey;
|
||||
|
||||
private DocumentBuilder parser;
|
||||
private SOAPMessage requestAction;
|
||||
private SOAPMessage loginAction;
|
||||
|
||||
private HNAPStatus status = HNAPStatus.INITIALISED;
|
||||
|
||||
/**
|
||||
* Indicates the status of the HNAP interface
|
||||
*
|
||||
*/
|
||||
protected enum HNAPStatus {
|
||||
/**
|
||||
* Ready to start communication with device
|
||||
*/
|
||||
INITIALISED,
|
||||
/**
|
||||
* Successfully logged in to device
|
||||
*/
|
||||
LOGGED_IN,
|
||||
/**
|
||||
* Problem communicating with device
|
||||
*/
|
||||
COMMUNICATION_ERROR,
|
||||
/**
|
||||
* Internal error
|
||||
*/
|
||||
INTERNAL_ERROR,
|
||||
/**
|
||||
* Error due to unsupported firmware
|
||||
*/
|
||||
UNSUPPORTED_FIRMWARE,
|
||||
/**
|
||||
* Error due to invalid pin code
|
||||
*/
|
||||
INVALID_PIN
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link #getHNAPStatus()} to determine the status of the HNAP connection
|
||||
* after construction.
|
||||
*
|
||||
* @param ipAddress
|
||||
* @param pin
|
||||
*/
|
||||
public DLinkHNAPCommunication(final String ipAddress, final String pin) {
|
||||
this.pin = pin;
|
||||
|
||||
httpClient = new HttpClient();
|
||||
|
||||
try {
|
||||
uri = new URI("http://" + ipAddress + "/HNAP1");
|
||||
httpClient.start();
|
||||
|
||||
parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
|
||||
final MessageFactory messageFactory = MessageFactory.newInstance();
|
||||
requestAction = messageFactory.createMessage();
|
||||
loginAction = messageFactory.createMessage();
|
||||
|
||||
buildRequestAction();
|
||||
buildLoginAction();
|
||||
} catch (final SOAPException e) {
|
||||
logger.debug("DLinkHNAPCommunication - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final URISyntaxException e) {
|
||||
logger.debug("DLinkHNAPCommunication - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final ParserConfigurationException e) {
|
||||
logger.debug("DLinkHNAPCommunication - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final Exception e) {
|
||||
// Thrown by httpClient.start()
|
||||
logger.debug("DLinkHNAPCommunication - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop communicating with the device
|
||||
*/
|
||||
public void dispose() {
|
||||
try {
|
||||
httpClient.stop();
|
||||
} catch (final Exception e) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the first SOAP message used in the login process and is used to retrieve
|
||||
* the cookie, challenge and public key used for authentication.
|
||||
*
|
||||
* @throws SOAPException
|
||||
*/
|
||||
private void buildRequestAction() throws SOAPException {
|
||||
requestAction.getSOAPHeader().detachNode();
|
||||
final SOAPBody soapBody = requestAction.getSOAPBody();
|
||||
final SOAPElement soapBodyElem = soapBody.addChildElement(LOGIN, "", HNAP_XMLNS);
|
||||
soapBodyElem.addChildElement(ACTION).addTextNode("request");
|
||||
soapBodyElem.addChildElement(USERNAME).addTextNode(ADMIN);
|
||||
soapBodyElem.addChildElement(LOGINPASSWORD);
|
||||
soapBodyElem.addChildElement(CAPTCHA);
|
||||
|
||||
final MimeHeaders headers = requestAction.getMimeHeaders();
|
||||
headers.addHeader(SOAPACTION, LOGIN_ACTION);
|
||||
|
||||
requestAction.saveChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the second SOAP message used in the login process and uses a password derived
|
||||
* from the challenge, public key and the device's pin code.
|
||||
*
|
||||
* @throws SOAPException
|
||||
*/
|
||||
private void buildLoginAction() throws SOAPException {
|
||||
loginAction.getSOAPHeader().detachNode();
|
||||
final SOAPBody soapBody = loginAction.getSOAPBody();
|
||||
final SOAPElement soapBodyElem = soapBody.addChildElement(LOGIN, "", HNAP_XMLNS);
|
||||
soapBodyElem.addChildElement(ACTION).addTextNode("login");
|
||||
soapBodyElem.addChildElement(USERNAME).addTextNode(ADMIN);
|
||||
soapBodyElem.addChildElement(LOGINPASSWORD);
|
||||
soapBodyElem.addChildElement(CAPTCHA);
|
||||
|
||||
final MimeHeaders headers = loginAction.getMimeHeaders();
|
||||
headers.addHeader(SOAPACTION, LOGIN_ACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password for the second login message based on the data received from the
|
||||
* first login message. Also sets the private key used to generate the authentication header.
|
||||
*
|
||||
* @param challenge
|
||||
* @param cookie
|
||||
* @param publicKey
|
||||
* @throws SOAPException
|
||||
* @throws InvalidKeyException
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private void setAuthenticationData(final String challenge, final String cookie, final String publicKey)
|
||||
throws SOAPException, InvalidKeyException, NoSuchAlgorithmException {
|
||||
final MimeHeaders loginHeaders = loginAction.getMimeHeaders();
|
||||
loginHeaders.setHeader(COOKIE, "uid=" + cookie);
|
||||
|
||||
privateKey = hash(challenge, publicKey + pin);
|
||||
|
||||
final String password = hash(challenge, privateKey);
|
||||
|
||||
loginAction.getSOAPBody().getElementsByTagName(LOGINPASSWORD).item(0).setTextContent(password);
|
||||
loginAction.saveChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to hash the authentication data such as the login password and the authentication header
|
||||
* for the detection message.
|
||||
*
|
||||
* @param data
|
||||
* @param key
|
||||
* @return The hashed data
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws InvalidKeyException
|
||||
*/
|
||||
private String hash(final String data, final String key) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
final Mac mac = Mac.getInstance("HMACMD5");
|
||||
final SecretKeySpec sKey = new SecretKeySpec(key.getBytes(), "ASCII");
|
||||
|
||||
mac.init(sKey);
|
||||
final byte[] bytes = mac.doFinal(data.getBytes());
|
||||
|
||||
final StringBuilder hashBuf = new StringBuilder();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
final String hex = Integer.toHexString(0xFF & bytes[i]).toUpperCase();
|
||||
if (hex.length() == 1) {
|
||||
hashBuf.append('0');
|
||||
}
|
||||
hashBuf.append(hex);
|
||||
}
|
||||
|
||||
return hashBuf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output unexpected responses to the debug log and sets the FIRMWARE error.
|
||||
*
|
||||
* @param message
|
||||
* @param soapResponse
|
||||
*/
|
||||
private void unexpectedResult(final String message, final Document soapResponse) {
|
||||
logUnexpectedResult(message, soapResponse);
|
||||
|
||||
// Best guess when receiving unexpected responses
|
||||
status = HNAPStatus.UNSUPPORTED_FIRMWARE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the HNAP interface
|
||||
*
|
||||
* @return the HNAP status
|
||||
*/
|
||||
protected HNAPStatus getHNAPStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the two login messages and stores the private key used to generate the
|
||||
* authentication header required for actions.
|
||||
*
|
||||
* Use {@link #getHNAPStatus()} to determine the status of the HNAP connection
|
||||
* after calling this method.
|
||||
*
|
||||
* @param timeout - Connection timeout in milliseconds
|
||||
*/
|
||||
protected void login(final int timeout) {
|
||||
if (status != HNAPStatus.INTERNAL_ERROR) {
|
||||
try {
|
||||
Document soapResponse = sendReceive(requestAction, timeout);
|
||||
|
||||
Node result = soapResponse.getElementsByTagName(LOGINRESULT).item(0);
|
||||
|
||||
if (result != null && OK.equals(result.getTextContent())) {
|
||||
final Node challengeNode = soapResponse.getElementsByTagName("Challenge").item(0);
|
||||
final Node cookieNode = soapResponse.getElementsByTagName(COOKIE).item(0);
|
||||
final Node publicKeyNode = soapResponse.getElementsByTagName("PublicKey").item(0);
|
||||
|
||||
if (challengeNode != null && cookieNode != null && publicKeyNode != null) {
|
||||
setAuthenticationData(challengeNode.getTextContent(), cookieNode.getTextContent(),
|
||||
publicKeyNode.getTextContent());
|
||||
|
||||
soapResponse = sendReceive(loginAction, timeout);
|
||||
result = soapResponse.getElementsByTagName(LOGINRESULT).item(0);
|
||||
|
||||
if (result != null) {
|
||||
if ("success".equals(result.getTextContent())) {
|
||||
status = HNAPStatus.LOGGED_IN;
|
||||
} else {
|
||||
logger.debug("login - Check pin is correct");
|
||||
// Assume pin code problem rather than a firmware change
|
||||
status = HNAPStatus.INVALID_PIN;
|
||||
}
|
||||
} else {
|
||||
unexpectedResult("login - Unexpected login response", soapResponse);
|
||||
}
|
||||
} else {
|
||||
unexpectedResult("login - Unexpected request response", soapResponse);
|
||||
}
|
||||
} else {
|
||||
unexpectedResult("login - Unexpected request response", soapResponse);
|
||||
}
|
||||
} catch (final InvalidKeyException e) {
|
||||
logger.debug("login - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
logger.debug("login - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final Exception e) {
|
||||
// Assume there has been some problem trying to send one of the messages
|
||||
if (status != HNAPStatus.COMMUNICATION_ERROR) {
|
||||
logger.debug("login - Communication error", e);
|
||||
status = HNAPStatus.COMMUNICATION_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authentication headers for the action message. This should only be called
|
||||
* after a successful login.
|
||||
*
|
||||
* Use {@link #getHNAPStatus()} to determine the status of the HNAP connection
|
||||
* after calling this method.
|
||||
*
|
||||
* @param action - SOAP Action to add headers
|
||||
*/
|
||||
protected void setAuthenticationHeaders(final SOAPMessage action) {
|
||||
if (status == HNAPStatus.LOGGED_IN) {
|
||||
try {
|
||||
final MimeHeaders loginHeaders = loginAction.getMimeHeaders();
|
||||
final MimeHeaders actionHeaders = action.getMimeHeaders();
|
||||
|
||||
actionHeaders.setHeader(COOKIE, loginHeaders.getHeader(COOKIE)[0]);
|
||||
|
||||
final String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
final String auth = hash(timeStamp + actionHeaders.getHeader(SOAPACTION)[0], privateKey) + " "
|
||||
+ timeStamp;
|
||||
actionHeaders.setHeader("HNAP_AUTH", auth);
|
||||
|
||||
action.saveChanges();
|
||||
} catch (final InvalidKeyException e) {
|
||||
logger.debug("setAuthenticationHeaders - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
logger.debug("setAuthenticationHeaders - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
} catch (final SOAPException e) {
|
||||
// No communication happening so assume system error
|
||||
logger.debug("setAuthenticationHeaders - Internal error", e);
|
||||
status = HNAPStatus.INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the SOAP message using Jetty HTTP client. Jetty is used in preference to
|
||||
* HttpURLConnection which can result in the HNAP interface becoming unresponsive.
|
||||
*
|
||||
* @param action - SOAP Action to send
|
||||
* @param timeout - Connection timeout in milliseconds
|
||||
* @return The result
|
||||
* @throws IOException
|
||||
* @throws SOAPException
|
||||
* @throws SAXException
|
||||
* @throws ExecutionException
|
||||
* @throws TimeoutException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
protected Document sendReceive(final SOAPMessage action, final int timeout) throws IOException, SOAPException,
|
||||
SAXException, InterruptedException, TimeoutException, ExecutionException {
|
||||
Document result;
|
||||
|
||||
final Request request = httpClient.POST(uri);
|
||||
request.timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
final Iterator<?> it = action.getMimeHeaders().getAllHeaders();
|
||||
while (it.hasNext()) {
|
||||
final MimeHeader header = (MimeHeader) it.next();
|
||||
request.header(header.getName(), header.getValue());
|
||||
}
|
||||
|
||||
try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
action.writeTo(os);
|
||||
request.content(new BytesContentProvider(os.toByteArray()));
|
||||
final ContentResponse response = request.send();
|
||||
try (final ByteArrayInputStream is = new ByteArrayInputStream(response.getContent())) {
|
||||
result = parser.parse(is);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output unexpected responses to the debug log.
|
||||
*
|
||||
* @param message
|
||||
* @param soapResponse
|
||||
*/
|
||||
protected void logUnexpectedResult(final String message, final Document soapResponse) {
|
||||
// No point formatting for output if debug logging is not enabled
|
||||
if (logger.isDebugEnabled()) {
|
||||
try {
|
||||
final TransformerFactory transFactory = TransformerFactory.newInstance();
|
||||
final Transformer transformer = transFactory.newTransformer();
|
||||
final StringWriter buffer = new StringWriter();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(new DOMSource(soapResponse), new StreamResult(buffer));
|
||||
logger.debug("{} : {}", message, buffer);
|
||||
} catch (final TransformerException e) {
|
||||
logger.debug("{}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.dlinksmarthome.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link DLinkSmartHomeBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DLinkSmartHomeBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "dlinksmarthome";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_DCHS150 = new ThingTypeUID(BINDING_ID, "DCH-S150");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_DCHS150);
|
||||
|
||||
// Motion trigger channel
|
||||
public static final String MOTION = "motion";
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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.dlinksmarthome.internal;
|
||||
|
||||
import static org.openhab.binding.dlinksmarthome.internal.DLinkSmartHomeBindingConstants.*;
|
||||
import static org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorConfig.IP_ADDRESS;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link DLinkSmartHomeDiscoveryParticipant} is responsible for discovering devices through UPnP.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
public class DLinkSmartHomeDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private static final String SERVICE_TYPE = "_dhnap._tcp.local.";
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscoveryResult createResult(final ServiceInfo serviceInfo) {
|
||||
final ThingUID thingUID = getThingUID(serviceInfo);
|
||||
|
||||
if (thingUID == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ThingTypeUID thingTypeUID = getThingType(serviceInfo);
|
||||
|
||||
if (THING_TYPE_DCHS150.equals(thingTypeUID)) {
|
||||
return createMotionSensor(thingUID, thingTypeUID, serviceInfo);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getThingUID(final ServiceInfo serviceInfo) {
|
||||
final ThingTypeUID thingTypeUID = getThingType(serviceInfo);
|
||||
|
||||
if (thingTypeUID != null) {
|
||||
final String mac = serviceInfo.getPropertyString("mac").replace(":", "").toLowerCase();
|
||||
return new ThingUID(thingTypeUID, mac);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ThingTypeUID getThingType(final ServiceInfo serviceInfo) {
|
||||
final String model = serviceInfo.getPropertyString("model_number");
|
||||
|
||||
if (model == null) {
|
||||
return null;
|
||||
} else if (model.equals("DCH-S150")) {
|
||||
return THING_TYPE_DCHS150;
|
||||
} else {
|
||||
logger.debug("D-Link HNAP Type: {}", model);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private DiscoveryResult createMotionSensor(final ThingUID thingUID, final ThingTypeUID thingType,
|
||||
final ServiceInfo serviceInfo) {
|
||||
final String host = serviceInfo.getHostAddresses()[0];
|
||||
final String mac = serviceInfo.getPropertyString("mac");
|
||||
|
||||
final Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(IP_ADDRESS, host);
|
||||
|
||||
logger.debug("DCH-S150 found: {}", host);
|
||||
|
||||
return DiscoveryResultBuilder.create(thingUID).withThingType(thingType).withProperties(properties)
|
||||
.withLabel("Motion Sensor (" + mac + ")").build();
|
||||
}
|
||||
}
|
||||
@@ -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.dlinksmarthome.internal;
|
||||
|
||||
import static org.openhab.binding.dlinksmarthome.internal.DLinkSmartHomeBindingConstants.*;
|
||||
|
||||
import org.openhab.binding.dlinksmarthome.internal.handler.DLinkMotionSensorHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link DLinkSmartHomeHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.dlinksmarthome")
|
||||
public class DLinkSmartHomeHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(final Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_DCHS150)) {
|
||||
return new DLinkMotionSensorHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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.dlinksmarthome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.dlinksmarthome.internal.DLinkSmartHomeBindingConstants.MOTION;
|
||||
|
||||
import org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorCommunication;
|
||||
import org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorCommunication.DeviceStatus;
|
||||
import org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorConfig;
|
||||
import org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorListener;
|
||||
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.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link DLinkMotionSensorHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
public class DLinkMotionSensorHandler extends BaseThingHandler implements DLinkMotionSensorListener {
|
||||
|
||||
private DLinkMotionSensorCommunication motionSensor;
|
||||
|
||||
private final ChannelUID motionChannel;
|
||||
|
||||
public DLinkMotionSensorHandler(final Thing thing) {
|
||||
super(thing);
|
||||
motionChannel = new ChannelUID(getThing().getUID(), MOTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
// Does not support commands
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
final DLinkMotionSensorConfig config = getConfigAs(DLinkMotionSensorConfig.class);
|
||||
motionSensor = new DLinkMotionSensorCommunication(config, this, scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void motionDetected() {
|
||||
triggerChannel(motionChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sensorStatus(final DeviceStatus status) {
|
||||
switch (status) {
|
||||
case ONLINE:
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
break;
|
||||
case COMMUNICATION_ERROR:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
break;
|
||||
case INVALID_PIN:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid pin code");
|
||||
break;
|
||||
case INTERNAL_ERROR:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "System error");
|
||||
break;
|
||||
case UNSUPPORTED_FIRMWARE:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Unsupported firmware");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (motionSensor != null) {
|
||||
motionSensor.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* 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.dlinksmarthome.internal.motionsensor;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.xml.soap.MessageFactory;
|
||||
import javax.xml.soap.MimeHeaders;
|
||||
import javax.xml.soap.SOAPBody;
|
||||
import javax.xml.soap.SOAPElement;
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
|
||||
import org.openhab.binding.dlinksmarthome.internal.DLinkHNAPCommunication;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* The {@link DLinkMotionSensorCommunication} is responsible for communicating with a DCH-S150
|
||||
* motion sensor.
|
||||
*
|
||||
* Motion is detected by polling the last detection time via the HNAP interface.
|
||||
*
|
||||
* Reverse engineered from Login.html and soapclient.js retrieved from the device.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
|
||||
|
||||
// SOAP actions
|
||||
private static final String DETECTION_ACTION = "\"http://purenetworks.com/HNAP1/GetLatestDetection\"";
|
||||
|
||||
private static final int DETECT_TIMEOUT_MS = 5000;
|
||||
private static final int DETECT_POLL_S = 1;
|
||||
|
||||
/**
|
||||
* Indicates the device status
|
||||
*
|
||||
*/
|
||||
public enum DeviceStatus {
|
||||
/**
|
||||
* Starting communication with device
|
||||
*/
|
||||
INITIALISING,
|
||||
/**
|
||||
* Successfully communicated with device
|
||||
*/
|
||||
ONLINE,
|
||||
/**
|
||||
* Problem communicating with device
|
||||
*/
|
||||
COMMUNICATION_ERROR,
|
||||
/**
|
||||
* Internal error
|
||||
*/
|
||||
INTERNAL_ERROR,
|
||||
/**
|
||||
* Error due to unsupported firmware
|
||||
*/
|
||||
UNSUPPORTED_FIRMWARE,
|
||||
/**
|
||||
* Error due to invalid pin code
|
||||
*/
|
||||
INVALID_PIN
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to log connection issues
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(DLinkMotionSensorCommunication.class);
|
||||
|
||||
private final DLinkMotionSensorListener listener;
|
||||
|
||||
private SOAPMessage detectionAction;
|
||||
|
||||
private boolean loginSuccess;
|
||||
private boolean detectSuccess;
|
||||
|
||||
private long prevDetection;
|
||||
private long lastDetection;
|
||||
|
||||
private final ScheduledFuture<?> detectFuture;
|
||||
|
||||
private boolean online = true;
|
||||
private DeviceStatus status = DeviceStatus.INITIALISING;
|
||||
|
||||
/**
|
||||
* Inform the listener if motion is detected
|
||||
*/
|
||||
private final Runnable detect = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean updateStatus = false;
|
||||
|
||||
switch (status) {
|
||||
case INITIALISING:
|
||||
online = false;
|
||||
updateStatus = true;
|
||||
// FALL-THROUGH
|
||||
case COMMUNICATION_ERROR:
|
||||
case ONLINE:
|
||||
if (!loginSuccess) {
|
||||
login(detectionAction, DETECT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
if (!getLastDetection(false)) {
|
||||
// Try login again in case the session has timed out
|
||||
login(detectionAction, DETECT_TIMEOUT_MS);
|
||||
getLastDetection(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (loginSuccess && detectSuccess) {
|
||||
status = DeviceStatus.ONLINE;
|
||||
if (!online) {
|
||||
online = true;
|
||||
listener.sensorStatus(status);
|
||||
|
||||
// Ignore old detections
|
||||
prevDetection = lastDetection;
|
||||
}
|
||||
|
||||
if (lastDetection != prevDetection) {
|
||||
listener.motionDetected();
|
||||
}
|
||||
} else {
|
||||
if (online || updateStatus) {
|
||||
online = false;
|
||||
listener.sensorStatus(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public DLinkMotionSensorCommunication(final DLinkMotionSensorConfig config,
|
||||
final DLinkMotionSensorListener listener, final ScheduledExecutorService scheduler) {
|
||||
super(config.ipAddress, config.pin);
|
||||
this.listener = listener;
|
||||
|
||||
if (getHNAPStatus() == HNAPStatus.INTERNAL_ERROR) {
|
||||
status = DeviceStatus.INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
try {
|
||||
final MessageFactory messageFactory = MessageFactory.newInstance();
|
||||
detectionAction = messageFactory.createMessage();
|
||||
|
||||
buildDetectionAction();
|
||||
|
||||
} catch (final SOAPException e) {
|
||||
logger.debug("DLinkMotionSensorCommunication - Internal error", e);
|
||||
status = DeviceStatus.INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
detectFuture = scheduler.scheduleWithFixedDelay(detect, 0, DETECT_POLL_S, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop communicating with the device
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
detectFuture.cancel(true);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the SOAP message used to retrieve the last detection time. This message will
|
||||
* only receive a successful response after the login process has been completed and the
|
||||
* authentication data has been set.
|
||||
*
|
||||
* @throws SOAPException
|
||||
*/
|
||||
private void buildDetectionAction() throws SOAPException {
|
||||
detectionAction.getSOAPHeader().detachNode();
|
||||
final SOAPBody soapBody = detectionAction.getSOAPBody();
|
||||
final SOAPElement soapBodyElem = soapBody.addChildElement("GetLatestDetection", "", HNAP_XMLNS);
|
||||
soapBodyElem.addChildElement("ModuleID").addTextNode("1");
|
||||
|
||||
final MimeHeaders headers = detectionAction.getMimeHeaders();
|
||||
headers.addHeader(SOAPACTION, DETECTION_ACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output unexpected responses to the debug log and sets the FIRMWARE error.
|
||||
*
|
||||
* @param message
|
||||
* @param soapResponse
|
||||
*/
|
||||
private void unexpectedResult(final String message, final Document soapResponse) {
|
||||
logUnexpectedResult(message, soapResponse);
|
||||
|
||||
// Best guess when receiving unexpected responses
|
||||
status = DeviceStatus.UNSUPPORTED_FIRMWARE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the two login messages and sets the authentication header for the action
|
||||
* message.
|
||||
*
|
||||
* @param action
|
||||
* @param timeout
|
||||
*/
|
||||
private void login(final SOAPMessage action, final int timeout) {
|
||||
loginSuccess = false;
|
||||
|
||||
login(timeout);
|
||||
setAuthenticationHeaders(action);
|
||||
|
||||
switch (getHNAPStatus()) {
|
||||
case LOGGED_IN:
|
||||
loginSuccess = true;
|
||||
break;
|
||||
case COMMUNICATION_ERROR:
|
||||
status = DeviceStatus.COMMUNICATION_ERROR;
|
||||
break;
|
||||
case INVALID_PIN:
|
||||
status = DeviceStatus.INVALID_PIN;
|
||||
break;
|
||||
case INTERNAL_ERROR:
|
||||
status = DeviceStatus.INTERNAL_ERROR;
|
||||
break;
|
||||
case UNSUPPORTED_FIRMWARE:
|
||||
status = DeviceStatus.UNSUPPORTED_FIRMWARE;
|
||||
break;
|
||||
case INITIALISED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the detection message
|
||||
*
|
||||
* @param isRetry - Has this been called as a result of a login retry
|
||||
* @return true, if the last detection time was successfully retrieved, otherwise false
|
||||
*/
|
||||
private boolean getLastDetection(final boolean isRetry) {
|
||||
detectSuccess = false;
|
||||
|
||||
if (loginSuccess) {
|
||||
try {
|
||||
final Document soapResponse = sendReceive(detectionAction, DETECT_TIMEOUT_MS);
|
||||
|
||||
final Node result = soapResponse.getElementsByTagName("GetLatestDetectionResult").item(0);
|
||||
|
||||
if (result != null) {
|
||||
if (OK.equals(result.getTextContent())) {
|
||||
final Node timeNode = soapResponse.getElementsByTagName("LatestDetectTime").item(0);
|
||||
|
||||
if (timeNode != null) {
|
||||
prevDetection = lastDetection;
|
||||
lastDetection = Long.valueOf(timeNode.getTextContent());
|
||||
detectSuccess = true;
|
||||
} else {
|
||||
unexpectedResult("getLastDetection - Unexpected response", soapResponse);
|
||||
}
|
||||
} else if (isRetry) {
|
||||
unexpectedResult("getLastDetection - Unexpected response", soapResponse);
|
||||
}
|
||||
} else {
|
||||
unexpectedResult("getLastDetection - Unexpected response", soapResponse);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Assume there has been some problem trying to send one of the messages
|
||||
if (status != DeviceStatus.COMMUNICATION_ERROR) {
|
||||
logger.debug("getLastDetection - Communication error", e);
|
||||
status = DeviceStatus.COMMUNICATION_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return detectSuccess;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.dlinksmarthome.internal.motionsensor;
|
||||
|
||||
/**
|
||||
* The {@link DLinkMotionSensorConfig} provides configuration data
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
public class DLinkMotionSensorConfig {
|
||||
|
||||
/**
|
||||
* Constants representing the configuration strings
|
||||
*/
|
||||
public static final String IP_ADDRESS = "ipAddress";
|
||||
public static final String PIN = "pin";
|
||||
|
||||
/**
|
||||
* The IP address of the device
|
||||
*/
|
||||
public String ipAddress;
|
||||
|
||||
/**
|
||||
* The pin code of the device
|
||||
*/
|
||||
public String pin;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dlinksmarthome.internal.motionsensor;
|
||||
|
||||
import org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorCommunication.DeviceStatus;
|
||||
|
||||
/**
|
||||
* The {@link DLinkMotionSensorListener} provides callbacks for motion detection
|
||||
* and device status changes.
|
||||
*
|
||||
* @author Mike Major - Initial contribution
|
||||
*/
|
||||
public interface DLinkMotionSensorListener {
|
||||
|
||||
/**
|
||||
* Callback to indicate motion has been detected
|
||||
*/
|
||||
void motionDetected();
|
||||
|
||||
/**
|
||||
* Callback to indicate a change in the device status
|
||||
*/
|
||||
void sensorStatus(final DeviceStatus status);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="dlinksmarthome" 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>D-Link Smart Home Binding</name>
|
||||
<description>This is the binding for D-Link Smart Home devices</description>
|
||||
<author>Mike Major</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dlinksmarthome"
|
||||
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">
|
||||
|
||||
<!-- Motion Sensor DCH-S150 Thing Type -->
|
||||
<thing-type id="DCH-S150">
|
||||
<label>Motion Sensor</label>
|
||||
<description>D-Link DCH-S150 WiFi motion sensor</description>
|
||||
|
||||
<channels>
|
||||
<channel id="motion" typeId="system.trigger">
|
||||
<label>Motion Detected</label>
|
||||
<description>Triggered when the sensor detects motion</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Hostname or IP</label>
|
||||
<description>Hostname or IP of the device.</description>
|
||||
</parameter>
|
||||
<parameter name="pin" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>PIN Code</label>
|
||||
<description>PIN code from the back of the device.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user