added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View 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>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.konnected</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>

View 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

View File

@@ -0,0 +1,96 @@
# Konnected Binding
This binding is for interacting with the [Konnected Alarm Panel](https://konnected.io/).
A module which interfaces with existing home security sensors.
Konnected is an open-source firmware and software that runs on a NodeMCU ESP8266 device.
The Konnected hardware is specifically designed for an alarm panel installation, but the general purpose firmware/software can be run on any ESP8266 device.
## Supported Things
This binding supports one type of thing module, which represents a Konnected Alarm Panel.
## Discovery
The binding will auto discover The Konnected Alarm Panels which are attached to the same network as the server running openHAB via UPnP.
The binding will then create things for each module discovered which can be added.
## Thing Configuration
The binding attempts to discover The Konnected Alarm Panels via the UPnP service.
The auto-discovery service of the binding will detect the ip address and port of the Konnected Alarm Panel.
The binding will attempt to obtain the ip address of your openHAB server as configured in the OSGi framework.
However, if it is unable to determine the ip address it will also attempt to use the network address service to obtain the ip address and port.
In addition you can also turn off discovery which when this setting is synced to the module will cause the device to no longer respond to UPnP requests as documented.
https://help.konnected.io/support/solutions/articles/32000023968-disabling-device-discovery
Please use this setting with caution and do not disable until a static ip address has been provided for your Konnected Alarm Panel via DHCP, router or otherwise.
The blink setting will disable the transmission LED on the Konnected Alarm Panel.
## Channels
The auto discovered thing adds two default channels.
| Channel | Channel Id | Channel Type | Description |
|---------|------------|--------------|----------------------------------------------------------|
| 1 | Zone_6 | Switch | A Switch channel for zone 6 |
| 2 | Out | Actuator | The Channel for the Out Pin on the Konnected Alarm Panel |
One channel for Zone 6 which is a sensor type channel, and one channel for the out pin that is an actuator type channel.
These channels represent the two pins on the Konnected Alarm Panel whose type cannot be changed.
For zones 1-5, you will need to add channels for the remaining zones that you have connected and configure them with the appropriate configuration parameters for each channel.
| Channel Type | Item Type | Config Parameters | Description |
|--------------|----------------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Switch | Switch | Zone Number | This is the channel type for sensors or other read only devices |
| Actuator | Switch | Zone Number, Momentary, Pause, Times | This is the channel type for devices whose state can be turned on an off by the Konnected Alarm Panel |
| Temperature | Number:Temperature | Zone Number, DHT22, Poll Interval, DS18b20 Address | This is the channel for sensors which measure temperature (DHT22 and DS18B20). The DHT22 setting should be set to true when the channel is monitoring a zone connected to a DHT22 sensor and false if the zone is connected to a DS1820B sensor |
| Humidity | Number:Dimensionless | Zone Number | This is the channel type for the humidity sensor on a connected DHT22 sensor |
You will need to configure each channel with the appropriate zone number corresponding to the zone on The Konnected Alarm Panel.
Then you need to link the corresponding item to the channel.
For the actuator type channels you can also add configuration parameters times, pause and momentary which will be added to the payload that is sent to the Konnected Alarm Panel.
These parameters will tell the module to pulse the actuator for certain time period.
A momentary switch actuates a switch for a specified time (in milliseconds) and then reverts it back to the off state.
This is commonly used with a relay module to actuate a garage door opener, or with a door bell to send a momentary trigger to sound the door bell.
A beep/blink switch is like a momentary switch that repeats either a specified number of times or indefinitely.
This is commonly used with a piezo buzzer to make a "beep beep" sound when a door is opened, or to make a repeating beep pattern for an alarm or audible warning.
It can also be used to blink lights.
DSB1820 temperature probes.
These are one wire devices which can all be Konnected to the same "Zone" on the Konnected Alarm Panel.
As part of its transmission the module will include an unique "address" property of each sensor probe that will be logged to the debug log when received.
This needs to be added to the channel if there are multiple probes connected.
The default behavior in absence of this configuration will be to simply log the address of the received event.
A channel should be added for each probe, as indicated above and configured with the appropriate address.
## Full Example
*.items
```
Contact Front_Door_Sensor "Front Door" {channel="konnected:module:generic:switch"}
Switch Siren "Siren" {channel="konnected:module:generic:actuator"}
```
*.sitemap
```
Switch item=Front_Door_Sensor label="Front Door" icon="door" mappings=[OPEN="Open", CLOSED="Closed"]
Switch item=Siren label="Alarm Siren" icon="Siren" mappings=[ON="Open", OFF="Closed"]
```
*.things
```
Thing konnected:module:generic "Konnected Module" [ipAddress="http://192.168.30.153:9586", macAddress="1586517"]{
Type switch : switch "Front Door" [channel_zone=1]
Type actuator : actuator "Siren" [channel_zone=1, momentary = 50, times = 2, pause = 50]
Type humidity : humidity "DHT - Humidity" [channel_zone=1]
Type temperature : temperature "DHT Temperature" [channel_zone=1, tempsensorType = true, pollinterval = 1]
Type temperature : temperature "DS18B20 Temperature" [channel_zone=1, tempsensorType = false, pollinterval = 1, ds18b20_address = "XX:XX:XX:XX:XX:XX:XX"]
}
```

View 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-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.konnected</artifactId>
<name>openHAB Add-ons :: Bundles :: Konnected Binding</name>
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.konnected-${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-konnected" description="Konnected Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-http</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.konnected/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,62 @@
/**
* 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.konnected.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link KonnectedBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Zachary Christiansen - Initial contribution
*/
@NonNullByDefault
public class KonnectedBindingConstants {
public static final String BINDING_ID = "konnected";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_MODULE = new ThingTypeUID(BINDING_ID, "module");
// Thing config properties
public static final String HOST = "ipAddress";
public static final String MAC_ADDR = "macAddress";
public static final String CALLBACK_PATH = "callBackPath";
public static final String REQUEST_TIMEOUT = "request_timeout";
public static final String RETRY_COUNT = "retry_count";
// PIN_TO_ZONE array, this array maps an index location as a zone to the corresponding
// pin location
public static final Integer[] PIN_TO_ZONE = { 0, 1, 2, 5, 6, 7, 9, 8 };
public static final String WEBHOOK_APP = "app_security";
public static final String CHANNEL_ZONE = "zone";
// channeltypeids
public static final String CHANNEL_SWITCH = "konnected:switch";
public static final String CHANNEL_ACTUATOR = "konnected:actuator";
public static final String CHANNEL_TEMPERATURE = "konnected:temperature";
public static final String CHANNEL_HUMIDITY = "konnected:humidity";
public static final String CHANNEL_TEMPERATURE_TYPE = "tempsensorType";
public static final String CHANNEL_TEMPERATURE_DS18B20_ADDRESS = "ds18b20_address";
public static final String CHANNEL_TEMPERATRUE_POLL = "pollinterval";
public static final String CHANNEL_ACTUATOR_TIMES = "times";
public static final String CHANNEL_ACTUATOR_MOMENTARY = "momentary";
public static final String CHANNEL_ACTUATOR_PAUSE = "pause";
public static final String CHANNEL_ONVALUE = "onvalue";
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.konnected.internal;
import org.openhab.core.config.core.Configuration;
/**
* The {@link KonnectedConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Zachary Christiansen - Initial contribution
*/
public class KonnectedConfiguration extends Configuration {
/**
* @param blink identifies whether the Konnected Alarm Panel LED will blink on transmission of Wifi Commands
* @param discovery identifies whether the Konnected Alarm Panel will be discoverable via UPnP
*/
public boolean blink;
public boolean discovery;
}

View File

@@ -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.konnected.internal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HTTP Get and Put reqeust class.
*
* @author Zachary Christiansen - Initial contribution
*/
public class KonnectedHTTPUtils {
private final Logger logger = LoggerFactory.getLogger(KonnectedHTTPUtils.class);
private int requestTimeout;
private String logTest = "";
public KonnectedHTTPUtils(int requestTimeout) {
this.requestTimeout = (int) TimeUnit.SECONDS.toMillis(requestTimeout);
}
public void setRequestTimeout(int requestTimeout) {
this.requestTimeout = (int) TimeUnit.SECONDS.toMillis(requestTimeout);
}
/**
* Sends a {@link doPut} request with a timeout of 30 seconds
*
* @param urlAddress the address to send the request
* @param payload the json payload to include with the request
*/
private String doPut(String urlAddress, String payload) throws IOException {
logger.debug("The String url we want to put is : {}", urlAddress);
logger.debug("The payload we want to put is: {}", payload);
ByteArrayInputStream input = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
String retVal = HttpUtil.executeUrl("PUT", urlAddress, getHttpHeaders(), input, "application/json",
requestTimeout);
logger.trace("return value: {}", retVal);
return retVal;
}
protected Properties getHttpHeaders() {
Properties httpHeaders = new Properties();
httpHeaders.put("Content-Type", "application/json");
return httpHeaders;
}
/**
* Sends a {@link doGet} request with a timeout of 30 seconds
*
* @param urlAddress the address to send the request
*/
private synchronized String doGet(String urlAddress) throws IOException {
logger.debug("The String url we want to get is : {}", urlAddress);
String retVal = HttpUtil.executeUrl("GET", urlAddress, requestTimeout);
logger.trace("return value: {}", retVal);
return retVal;
}
/**
* Sends a {@link doGet} request with a timeout of 30 seconds
*
* @param urlAddress the address to send the request
* @param payload the json payload you want to send as part of the request
*/
private synchronized String doGet(String urlAddress, String payload) throws IOException {
logger.debug("The String url we want to get is : {}", urlAddress);
ByteArrayInputStream input = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
String retVal = HttpUtil.executeUrl("GET", urlAddress, getHttpHeaders(), input, "application/json",
requestTimeout);
logger.trace("return value: {}", retVal);
return retVal;
}
/**
* Sends a {@link doGet} request with a timeout of 30 seconds
*
* @param urlAddress the address to send the request
* @param payload the json payload you want to send as part of the request, may be null.
* @param retry the number of retries before throwing the IOexpcetion back to the handler
*/
public synchronized String doGet(String urlAddress, String payload, int retryCount)
throws KonnectedHttpRetryExceeded {
String response = null;
int x = 0;
Boolean loop = true;
while (loop) {
try {
if (payload == null) {
response = doGet(urlAddress, payload);
} else {
response = doGet(urlAddress);
}
loop = false;
} catch (IOException e) {
x++;
if (x > retryCount) {
throw new KonnectedHttpRetryExceeded("The number of retry attempts was exceeded", e.getCause());
}
}
}
return response;
}
/**
* Sends a {@link doPut} request with a timeout of 30 seconds
*
* @param urlAddress the address to send the request
* @param payload the json payload you want to send as part of the request
* @param retry the number of retries before throwing the IOexpcetion back to the handler
*/
public synchronized String doPut(String urlAddress, String payload, int retryCount)
throws KonnectedHttpRetryExceeded {
String response = null;
int x = 0;
Boolean loop = true;
while (loop) {
try {
response = doPut(urlAddress, payload);
loop = false;
} catch (IOException e) {
x++;
if (x > retryCount) {
throw new KonnectedHttpRetryExceeded("The number of retry attempts was exceeded", e.getCause());
}
}
}
return response;
}
}

View File

@@ -0,0 +1,156 @@
/**
* 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.konnected.internal;
import static org.openhab.binding.konnected.internal.KonnectedBindingConstants.*;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Set;
import javax.servlet.ServletException;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.konnected.internal.handler.KonnectedHandler;
import org.openhab.binding.konnected.internal.servlet.KonnectedHTTPServlet;
import org.openhab.binding.konnected.internal.servlet.KonnectedWebHookFail;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
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.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KonnectedHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Zachary Christiansen - Initial contribution
*/
@Component(configurationPid = "binding.konnected", service = ThingHandlerFactory.class)
public class KonnectedHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(KonnectedHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_MODULE);
private static final String alias = "/" + BINDING_ID;
private HttpService httpService;
private String callbackUrl = null;
private NetworkAddressService networkAddressService;
private KonnectedHTTPServlet servlet;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
Dictionary<String, Object> properties = componentContext.getProperties();
callbackUrl = (String) properties.get("callbackUrl");
try {
this.servlet = registerWebHookServlet();
} catch (KonnectedWebHookFail e) {
logger.error("Failed registering Konnected servlet - binding is not functional!", e);
}
}
@Override
protected void deactivate(ComponentContext componentContext) {
super.deactivate(componentContext);
httpService.unregister(alias);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
KonnectedHandler thingHandler = new KonnectedHandler(thing, '/' + BINDING_ID, createCallbackUrl(),
createCallbackPort());
if (servlet != null) {
logger.debug("Adding thinghandler for thing {} to webhook.", thing.getUID().getId());
servlet.add(thingHandler);
}
return thingHandler;
}
/**
* @param thingHandler thing handler to be removed
*/
@Override
protected void removeHandler(ThingHandler thingHandler) {
servlet.remove((KonnectedHandler) thingHandler);
thingHandler.dispose();
super.removeHandler(thingHandler);
}
private KonnectedHTTPServlet registerWebHookServlet() throws KonnectedWebHookFail {
KonnectedHTTPServlet servlet = new KonnectedHTTPServlet();
try {
httpService.registerServlet(alias, servlet, null, httpService.createDefaultHttpContext());
return servlet;
} catch (ServletException | NamespaceException e) {
throw new KonnectedWebHookFail("Could not start Konnected Webhook servlet: " + e.getMessage(), e);
}
}
@Reference
public void setHttpService(HttpService httpService) {
this.httpService = httpService;
}
public void unsetHttpService(HttpService httpService) {
this.httpService = null;
}
private String createCallbackUrl() {
if (callbackUrl != null) {
logger.debug("The callback ip address from the OSGI is:{}", callbackUrl);
return callbackUrl;
} else {
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
if (ipAddress == null) {
logger.warn("No network interface could be found.");
return null;
}
logger.debug("The callback ip address obtained from the Network Address Service was:{}", ipAddress);
return ipAddress;
}
}
private String createCallbackPort() {
// we do not use SSL as it can cause certificate validation issues.
final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
if (port == -1) {
logger.warn("Cannot find port of the http service.");
return null;
}
logger.debug("the port for the callback is: {}", port);
return Integer.toString(port);
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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.konnected.internal;
/**
* Custom exception class to be thrown when number of retries is exceeded.
*
* @author Zachary Christiansen - Initial contribution
*/
@SuppressWarnings("serial")
public class KonnectedHttpRetryExceeded extends Exception {
public KonnectedHttpRetryExceeded(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,87 @@
/**
* 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.konnected.internal.discovery;
import static org.openhab.binding.konnected.internal.KonnectedBindingConstants.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.DeviceDetails;
import org.jupnp.model.meta.ModelDetails;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
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 KonnectedUPnPServer} is responsible for discovering new
* Konnectedmodules modules. It uses the central {@link UpnpDiscoveryService}.
*
* @author Zachary Christainsen - Initial contribution
*
*/
@NonNullByDefault
@Component(service = UpnpDiscoveryParticipant.class, immediate = true)
public class KonnectedUPnPServer implements UpnpDiscoveryParticipant {
private Logger logger = LoggerFactory.getLogger(KonnectedUPnPServer.class);
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(THING_TYPE_MODULE);
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
ThingUID uid = getThingUID(device);
if (uid != null) {
Map<String, Object> properties = new HashMap<>();
properties.put(HOST, device.getDetails().getBaseURL());
properties.put(MAC_ADDR, device.getDetails().getSerialNumber());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
.withLabel(device.getDetails().getFriendlyName()).withRepresentationProperty(MAC_ADDR).build();
return result;
} else {
return null;
}
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
DeviceDetails details = device.getDetails();
if (details != null) {
ModelDetails modelDetails = details.getModelDetails();
if (modelDetails != null) {
String modelName = modelDetails.getModelName();
logger.debug("Model Details: {} Url: {} UDN: {} Model Number: {}", modelName, details.getBaseURL(),
details.getSerialNumber(), modelDetails.getModelNumber());
if (modelName != null) {
if (modelName.startsWith("Konnected")) {
return new ThingUID(THING_TYPE_MODULE, details.getSerialNumber());
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,114 @@
/**
* 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.konnected.internal.gson;
import com.google.gson.annotations.SerializedName;
/**
* The {@link KonnectedModuleGson} is responsible to hold
* data that models pin information which can be sent to a Konnected Module
*
* @author Zachary Christiansen - Initial contribution
*
*/
public class KonnectedModuleGson {
private Integer pin;
private String temp;
private String humi;
private String state;
@SerializedName("Auth_Token")
private String authToken;
private String momentary;
private String pause;
private String times;
@SerializedName("poll_interval")
private Integer pollInterval;
private String addr;
public Integer getPin() {
return pin;
}
public void setPin(Integer setPin) {
this.pin = setPin;
}
public Integer getPollInterval() {
return pollInterval;
}
public void setPollInterval(Integer setPollInterval) {
this.pollInterval = setPollInterval;
}
public String getTemp() {
return temp;
}
public void setTemp(String setTemp) {
this.temp = setTemp;
}
public String getHumi() {
return humi;
}
public void setHumi(String setHumi) {
this.humi = setHumi;
}
public String getState() {
return state;
}
public void setState(String setState) {
this.state = setState;
}
public String getAuthToken() {
return authToken;
}
public String getMomentary() {
return momentary;
}
public void setMomentary(String setMomentary) {
this.momentary = setMomentary;
}
public String getPause() {
return pause;
}
public void setPause(String setPause) {
this.pause = setPause;
}
public String getTimes() {
return times;
}
public void setTimes(String setTimes) {
this.times = setTimes;
}
public String getAddr() {
return addr;
}
public void setAddr(String setAddr) {
this.addr = setAddr;
}
}

View File

@@ -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.konnected.internal.gson;
import java.util.HashSet;
import java.util.Set;
import com.google.gson.annotations.SerializedName;
/**
* The {@link KonnectedModulePayload} is responsible to hold
* data to send as settings for the konnected module
*
* @author Zachary Christiansen - Initial contribution
*
*/
public class KonnectedModulePayload {
private Set<KonnectedModuleGson> sensors;
private Set<KonnectedModuleGson> actuators;
@SerializedName("dht_sensors")
private Set<KonnectedModuleGson> dht22;
@SerializedName("ds18b20_sensors")
private Set<KonnectedModuleGson> ds18b20;
@SerializedName("token")
private String authToken;
private String apiUrl;
private Boolean blink;
private Boolean discovery;
public KonnectedModulePayload(String authTokenPassed, String apiURLPassed) {
this.authToken = authTokenPassed;
this.apiUrl = apiURLPassed;
this.sensors = new HashSet<>();
this.actuators = new HashSet<>();
this.dht22 = new HashSet<>();
this.ds18b20 = new HashSet<>();
}
public Set<KonnectedModuleGson> getSensors() {
return sensors;
}
public void addSensor(KonnectedModuleGson sensor) {
this.sensors.add(sensor);
}
public void removeSensor(KonnectedModuleGson sensor) {
this.sensors.remove(sensor);
}
public Set<KonnectedModuleGson> getActuators() {
return actuators;
}
public void addActuators(KonnectedModuleGson actuator) {
this.actuators.add(actuator);
}
public void removeActuator(KonnectedModuleGson actuator) {
this.actuators.remove(actuator);
}
public String getToken() {
return authToken;
}
public String getapiUrl() {
return apiUrl;
}
public Set<KonnectedModuleGson> getDht22() {
return dht22;
}
public void addDht22(KonnectedModuleGson Dht22) {
this.dht22.add(Dht22);
}
public void removeDht22(KonnectedModuleGson Dht22) {
this.dht22.remove(Dht22);
}
public Set<KonnectedModuleGson> getDs18b20() {
return ds18b20;
}
public void addDs18b20(KonnectedModuleGson Ds18b20) {
this.ds18b20.add(Ds18b20);
}
public void removeDs18b20(KonnectedModuleGson Ds18b20) {
this.ds18b20.remove(Ds18b20);
}
public void setDiscovery(Boolean setDiscovery) {
this.discovery = setDiscovery;
}
public void setBlink(Boolean setBlink) {
this.blink = setBlink;
}
}

View File

@@ -0,0 +1,523 @@
/**
* 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.konnected.internal.handler;
import static org.openhab.binding.konnected.internal.KonnectedBindingConstants.*;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.konnected.internal.KonnectedConfiguration;
import org.openhab.binding.konnected.internal.KonnectedHTTPUtils;
import org.openhab.binding.konnected.internal.KonnectedHttpRetryExceeded;
import org.openhab.binding.konnected.internal.gson.KonnectedModuleGson;
import org.openhab.binding.konnected.internal.gson.KonnectedModulePayload;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.validation.ConfigValidationException;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* The {@link KonnectedHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Zachary Christiansen - Initial contribution
*/
public class KonnectedHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(KonnectedHandler.class);
private KonnectedConfiguration config;
private final String konnectedServletPath;
private final KonnectedHTTPUtils http = new KonnectedHTTPUtils(30);
private String callbackIpAddress = null;
private String moduleIpAddress;
private Gson gson = new GsonBuilder().create();
private int retryCount;
/**
* This is the constructor of the Konnected Handler.
*
* @param thing the instance of the Konnected thing
* @param webHookServlet the instance of the callback servlet that is running for communication with the Konnected
* Module
* @param hostAddress the webaddress of the openHAB server instance obtained by the runtime
* @param port the port on which the openHAB instance is running that was obtained by the runtime.
*/
public KonnectedHandler(Thing thing, String path, String hostAddress, String port) {
super(thing);
this.konnectedServletPath = path;
callbackIpAddress = hostAddress + ":" + port;
logger.debug("The callback ip address is: {}", callbackIpAddress);
retryCount = 2;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// get the zone number in integer form
Channel channel = this.getThing().getChannel(channelUID.getId());
String channelType = channel.getChannelTypeUID().getAsString();
String zoneNumber = (String) channel.getConfiguration().get(CHANNEL_ZONE);
Integer zone = Integer.parseInt(zoneNumber);
logger.debug("The channelUID is: {} and the zone is : {}", channelUID.getAsString(), zone);
// convert the zone to the pin based on value at index of zone
Integer pin = Arrays.asList(PIN_TO_ZONE).get(zone);
// if the command is OnOfftype
if (command instanceof OnOffType) {
if (channelType.equalsIgnoreCase(CHANNEL_SWITCH)) {
logger.debug("A command was sent to a sensor type so we are ignoring the command");
} else {
int sendCommand = (OnOffType.OFF.compareTo((OnOffType) command));
logger.debug("The command being sent to pin {} for channel:{} is {}", pin, channelUID.getAsString(),
sendCommand);
sendActuatorCommand(Integer.toString(sendCommand), pin, channelUID);
}
} else if (command instanceof RefreshType) {
// check to see if handler has been initialized before attempting to get state of pin, else wait one minute
if (this.isInitialized()) {
getSwitchState(pin, channelUID);
} else {
scheduler.schedule(() -> {
handleCommand(channelUID, command);
}, 1, TimeUnit.MINUTES);
}
}
}
/**
* Process a {@link WebHookEvent} that has been received by the Servlet from a Konnected module with respect to a
* sensor event or status update request
*
* @param event the {@link KonnectedModuleGson} event that contains the state and pin information to be processed
*/
public void handleWebHookEvent(KonnectedModuleGson event) {
// if we receive a command upteate the thing status to being online
updateStatus(ThingStatus.ONLINE);
// get the zone number based off of the index location of the pin value
String sentZone = Integer.toString(Arrays.asList(PIN_TO_ZONE).indexOf(event.getPin()));
// check that the zone number is in one of the channelUID definitions
logger.debug("Looping Through all channels on thing: {} to find a match for {}", thing.getUID().getAsString(),
event.getAuthToken());
getThing().getChannels().forEach(channel -> {
ChannelUID channelId = channel.getUID();
String zoneNumber = (String) channel.getConfiguration().get(CHANNEL_ZONE);
// if the string zone that was sent equals the last digit of the channelId found process it as the
// channelId else do nothing
if (sentZone.equalsIgnoreCase(zoneNumber)) {
logger.debug(
"The configrued zone of channelID: {} was a match for the zone sent by the alarm panel: {} on thing: {}",
channelId, sentZone, this.getThing().getUID().getId());
String channelType = channel.getChannelTypeUID().getAsString();
logger.debug("The channeltypeID is: {}", channelType);
// check if the itemType has been defined for the zone received
// check the itemType of the Zone, if Contact, send the State if Temp send Temp, etc.
if (channelType.equalsIgnoreCase(CHANNEL_SWITCH) || channelType.equalsIgnoreCase(CHANNEL_ACTUATOR)) {
OnOffType onOffType = event.getState().equalsIgnoreCase(getOnState(channel)) ? OnOffType.ON
: OnOffType.OFF;
updateState(channelId, onOffType);
} else if (channelType.equalsIgnoreCase(CHANNEL_HUMIDITY)) {
// if the state is of type number then this means it is the humidity channel of the dht22
updateState(channelId,
new QuantityType<>(Double.parseDouble(event.getHumi()), SmartHomeUnits.PERCENT));
} else if (channelType.equalsIgnoreCase(CHANNEL_TEMPERATURE)) {
Configuration configuration = channel.getConfiguration();
if (((Boolean) configuration.get(CHANNEL_TEMPERATURE_TYPE))) {
updateState(channelId,
new QuantityType<>(Double.parseDouble(event.getTemp()), SIUnits.CELSIUS));
} else {
// need to check to make sure right dsb1820 address
logger.debug("The address of the DSB1820 sensor received from modeule {} is: {}",
this.thing.getUID(), event.getAddr());
if (event.getAddr().toString()
.equalsIgnoreCase((String) (configuration.get(CHANNEL_TEMPERATURE_DS18B20_ADDRESS)))) {
updateState(channelId,
new QuantityType<>(Double.parseDouble(event.getTemp()), SIUnits.CELSIUS));
} else {
logger.debug("The address of {} does not match {} not updating this channel",
event.getAddr().toString(),
(configuration.get(CHANNEL_TEMPERATURE_DS18B20_ADDRESS)));
}
}
}
} else {
logger.trace(
"The zone number sent by the alarm panel: {} was not a match the configured zone for channelId: {} for thing {}",
sentZone, channelId, getThing().getThingTypeUID().toString());
}
});
}
private void checkConfiguration() throws ConfigValidationException {
logger.debug("Checking configuration on thing {}", this.getThing().getUID().getAsString());
Configuration testConfig = this.getConfig();
String testRetryCount = testConfig.get(RETRY_COUNT).toString();
String testRequestTimeout = testConfig.get(REQUEST_TIMEOUT).toString();
logger.debug("The RequestTimeout Parameter is Configured as: {}", testRequestTimeout);
logger.debug("The Retry Count Parameter is Configured as: {}", testRetryCount);
try {
this.retryCount = Integer.parseInt(testRetryCount);
} catch (NumberFormatException e) {
logger.debug(
"Please check your configuration of the Retry Count as it is not an Integer. It is configured as: {}, will contintue to configure the binding with the default of 2",
testRetryCount);
this.retryCount = 2;
}
try {
this.http.setRequestTimeout(Integer.parseInt(testRequestTimeout));
} catch (NumberFormatException e) {
logger.debug(
"Please check your configuration of the Request Timeout as it is not an Integer. It is configured as: {}, will contintue to configure the binding with the default of 30",
testRequestTimeout);
}
if ((callbackIpAddress == null)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Unable to obtain hostaddress from OSGI service, please configure hostaddress");
}
else {
this.config = getConfigAs(KonnectedConfiguration.class);
}
}
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters)
throws ConfigValidationException {
this.validateConfigurationParameters(configurationParameters);
for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
Object value = configurationParameter.getValue();
logger.debug("Controller Configuration update {} to {}", configurationParameter.getKey(), value);
if (value == null) {
continue;
}
// this is a nonstandard implementation to to address the configuration of the konnected alarm panel (as
// opposed to the handler) until
// https://github.com/eclipse/smarthome/issues/3484 has been implemented in the framework
String[] cfg = configurationParameter.getKey().split("_");
if ("controller".equals(cfg[0])) {
if (cfg[1].equals("softreset") && value instanceof Boolean && (Boolean) value) {
scheduler.execute(() -> {
try {
http.doGet(moduleIpAddress + "/settings?restart=true", null, retryCount);
} catch (KonnectedHttpRetryExceeded e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
});
value = false;
} else if (cfg[1].equals("removewifi") && value instanceof Boolean && (Boolean) value) {
scheduler.execute(() -> {
try {
http.doGet(moduleIpAddress + "/settings?restore=true", null, retryCount);
} catch (KonnectedHttpRetryExceeded e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
});
value = false;
} else if (cfg[1].equals("sendConfig") && value instanceof Boolean && (Boolean) value) {
scheduler.execute(() -> {
try {
String response = updateKonnectedModule();
logger.trace("The response from the konnected module with thingID {} was {}",
getThing().getUID().toString(), response);
if (response == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unable to communicate with Konnected Module.");
} else {
updateStatus(ThingStatus.ONLINE);
}
} catch (KonnectedHttpRetryExceeded e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
});
value = false;
}
}
}
super.handleConfigurationUpdate(configurationParameters);
try
{
String response = updateKonnectedModule();
logger.trace("The response from the konnected module with thingID {} was {}",
getThing().getUID().toString(), response);
if (response == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unable to communicate with Konnected Module confirm settings.");
} else {
updateStatus(ThingStatus.ONLINE);
}
} catch (KonnectedHttpRetryExceeded e) {
logger.trace("The number of retries was exceeeded during the HandleConfigurationUpdate(): {}",
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
try {
checkConfiguration();
} catch (ConfigValidationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
this.moduleIpAddress = this.getThing().getProperties().get(HOST).toString();
scheduler.execute(() -> {
try {
String response = updateKonnectedModule();
if (response == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unable to communicate with Konnected Module confirm settings or readd thing.");
} else {
updateStatus(ThingStatus.ONLINE);
}
} catch (KonnectedHttpRetryExceeded e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
});
}
@Override
public void dispose() {
logger.debug("Running dispose()");
super.dispose();
}
/**
* This method constructs the payload that will be sent
* to the Konnected module via the put request
* it adds the appropriate sensors and actuators to the {@link KonnectedModulePayload}
* as well as the location of the callback {@link KonnectedJTTPServlet}
* and auth_token which can be used for validation
*
* @return a json settings payload which can be sent to the Konnected Module based on the Thing
*/
private String constructSettingsPayload() {
String hostPath = "";
hostPath = callbackIpAddress + this.konnectedServletPath;
String authToken = getThing().getUID().getAsString();
logger.debug("The Auth_Token is: {}", authToken);
KonnectedModulePayload payload = new KonnectedModulePayload(authToken, "http://" + hostPath);
payload.setBlink(config.blink);
payload.setDiscovery(config.discovery);
this.getThing().getChannels().forEach(channel -> {
// ChannelUID channelId = channel.getUID();
if (isLinked(channel.getUID())) {
// adds linked channels to list based on last value of Channel ID
// which is set to a number
// get the zone number in integer form
String zoneNumber = (String) channel.getConfiguration().get(CHANNEL_ZONE);
Integer zone = Integer.parseInt(zoneNumber);
// convert the zone to the pin based on value at index of zone
Integer pin = Arrays.asList(PIN_TO_ZONE).get(zone);
// if the pin is an actuator add to actuator string
// else add to sensor string
// This is determined based off of the accepted item type, contact types are sensors
// switch types are actuators
String channelType = channel.getChannelTypeUID().getAsString();
logger.debug("The channeltypeID is: {}", channelType);
KonnectedModuleGson module = new KonnectedModuleGson();
module.setPin(pin);
if (channelType.equalsIgnoreCase(CHANNEL_SWITCH)) {
payload.addSensor(module);
logger.trace("Channel {} will be configured on the konnected alarm panel as a switch",
channel.toString());
} else if (channelType.equalsIgnoreCase(CHANNEL_ACTUATOR)) {
payload.addActuators(module);
logger.trace("Channel {} will be configured on the konnected alarm panel as an actuator",
channel.toString());
} else if (channelType.equalsIgnoreCase(CHANNEL_HUMIDITY)) {
// the humidity channels do not need to be added because the supported sensor (dht22) is added under
// the temp sensor
logger.trace("Channel {} is a humidity channel.", channel.toString());
} else if (channelType.equalsIgnoreCase(CHANNEL_TEMPERATURE)) {
logger.trace("Channel {} will be configured on the konnected alarm panel as a temperature sensor",
channel.toString());
Configuration configuration = channel.getConfiguration();
if (configuration.get(CHANNEL_TEMPERATRUE_POLL) == null) {
module.setPollInterval(3);
} else {
module.setPollInterval(((BigDecimal) configuration.get(CHANNEL_TEMPERATRUE_POLL)).intValue());
}
logger.trace("The Temperature Sensor Type is: {} ",
configuration.get(CHANNEL_TEMPERATURE_TYPE).toString());
if ((boolean) configuration.get(CHANNEL_TEMPERATURE_TYPE)) {
// add it as a dht22 module
payload.addDht22(module);
logger.trace(
"Channel {} will be configured on the konnected alarm panel as a DHT22 temperature sensor",
channel.toString());
} else {
// add to payload as a DS18B20 module if the parameter is false
payload.addDs18b20(module);
logger.trace(
"Channel {} will be configured on the konnected alarm panel as a DS18B20 temperature sensor",
channel.toString());
}
} else {
logger.debug("Channel {} is of type {} which is not supported by the konnected binding",
channel.toString(), channelType);
}
} else {
logger.debug("The Channel {} is not linked to an item", channel.getUID());
}
});
// Create Json to Send to Konnected Module
String payloadString = gson.toJson(payload);
logger.debug("The payload is: {}", payloadString);
return payloadString;
}
/*
* Prepares and sends the {@link KonnectedModulePayload} via the {@link KonnectedHttpUtils}
*
* @return response obtained from sending the settings payload to Konnected module defined by the thing
*
* @throws KonnectedHttpRetryExceeded if unable to communicate with the Konnected module defined by the Thing
*/
private String updateKonnectedModule() throws KonnectedHttpRetryExceeded {
String payload = constructSettingsPayload();
String response = http.doPut(moduleIpAddress + "/settings", payload, retryCount);
logger.debug("The response of the put request was: {}", response);
return response;
}
/**
* Sends a command to the module via {@link KonnectedHTTPUtils}
*
* @param scommand the string command, either 0 or 1 to send to the actutor pin on the Konnected module
* @param pin the pin to send the command to on the Konnected Module
*/
private void sendActuatorCommand(String scommand, Integer pin, ChannelUID channelId) {
try {
Channel channel = getThing().getChannel(channelId.getId());
if (!(channel == null)) {
logger.debug("getasstring: {} getID: {} getGroupId: {} toString:{}", channelId.getAsString(),
channelId.getId(), channelId.getGroupId(), channelId.toString());
Configuration configuration = channel.getConfiguration();
KonnectedModuleGson payload = new KonnectedModuleGson();
payload.setState(scommand);
payload.setPin(pin);
// check to see if this is an On Command type, if so add the momentary, pause, times to the payload if
// they exist on the configuration.
if (scommand.equals(getOnState(channel))) {
if (configuration.get(CHANNEL_ACTUATOR_TIMES) == null) {
logger.debug(
"The times configuration was not set for channelID: {}, not adding it to the payload.",
channelId.toString());
} else {
payload.setTimes(configuration.get(CHANNEL_ACTUATOR_TIMES).toString());
logger.debug("The times configuration was set to: {} for channelID: {}.",
configuration.get(CHANNEL_ACTUATOR_TIMES).toString(), channelId.toString());
}
if (configuration.get(CHANNEL_ACTUATOR_MOMENTARY) == null) {
logger.debug(
"The momentary configuration was not set for channelID: {}, not adding it to the payload.",
channelId.toString());
} else {
payload.setMomentary(configuration.get(CHANNEL_ACTUATOR_MOMENTARY).toString());
logger.debug("The momentary configuration set to: {} channelID: {}.",
configuration.get(CHANNEL_ACTUATOR_MOMENTARY).toString(), channelId.toString());
}
if (configuration.get(CHANNEL_ACTUATOR_PAUSE) == null) {
logger.debug(
"The pause configuration was not set for channelID: {}, not adding it to the payload.",
channelId.toString());
} else {
payload.setPause(configuration.get(CHANNEL_ACTUATOR_PAUSE).toString());
logger.debug("The pause configuration was set to: {} for channelID: {}.",
configuration.get(CHANNEL_ACTUATOR_PAUSE).toString(), channelId.toString());
}
}
String payloadString = gson.toJson(payload);
logger.debug("The command payload is: {}", payloadString);
http.doPut(moduleIpAddress + "/device", payloadString, retryCount);
} else {
logger.debug("The channel {} returned null for channelId.getID(): {}", channelId.toString(),
channelId.getId());
}
} catch (KonnectedHttpRetryExceeded e) {
logger.debug("Attempting to set the state of the actuator on thing {} failed: {}",
this.thing.getUID().getId(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unable to communicate with Konnected Alarm Panel confirm settings, and that module is online.");
}
}
private void getSwitchState(Integer pin, ChannelUID channelId) {
Channel channel = getThing().getChannel(channelId.getId());
if (!(channel == null)) {
logger.debug("getasstring: {} getID: {} getGroupId: {} toString:{}", channelId.getAsString(),
channelId.getId(), channelId.getGroupId(), channelId.toString());
KonnectedModuleGson payload = new KonnectedModuleGson();
payload.setPin(pin);
String payloadString = gson.toJson(payload);
logger.debug("The command payload is: {}", payloadString);
try {
sendSetSwitchState(payloadString);
} catch (KonnectedHttpRetryExceeded e) {
// try to get the state of the device one more time 30 seconds later. This way it can be confirmed if
// the device was simply in a reboot loop when device state was attempted the first time
scheduler.schedule(() -> {
try {
sendSetSwitchState(payloadString);
} catch (KonnectedHttpRetryExceeded ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unable to communicate with Konnected Alarm Panel confirm settings, and that module is online.");
logger.debug("Attempting to get the state of the zone on thing {} failed for channel: {} : {}",
this.thing.getUID().getId(), channelId.getAsString(), ex.getMessage());
}
}, 2, TimeUnit.MINUTES);
}
} else {
logger.debug("The channel {} returned null for channelId.getID(): {}", channelId.toString(),
channelId.getId());
}
}
private void sendSetSwitchState(String payloadString) throws KonnectedHttpRetryExceeded {
String response = http.doGet(moduleIpAddress + "/device", payloadString, retryCount);
KonnectedModuleGson event = gson.fromJson(response, KonnectedModuleGson.class);
this.handleWebHookEvent(event);
}
private String getOnState(Channel channel) {
String config = (String) channel.getConfiguration().get(CHANNEL_ONVALUE);
if (config == null) {
return "1";
} else {
return config;
}
}
}

View File

@@ -0,0 +1,102 @@
/**
* 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.konnected.internal.servlet;
import java.io.IOException;
import java.util.HashMap;
import java.util.Scanner;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.openhab.binding.konnected.internal.gson.KonnectedModuleGson;
import org.openhab.binding.konnected.internal.handler.KonnectedHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* Main OSGi service and HTTP servlet for Konnected Webhook.
*
* @author Zachary Christiansen - Initial contribution
*/
public class KonnectedHTTPServlet extends HttpServlet {
private final Logger logger = LoggerFactory.getLogger(KonnectedHTTPServlet.class);
private static final long serialVersionUID = 1288539782077957954L;
private static final String APPLICATION_JSON = "application/json";
private static final String CHARSET = "utf-8";
private final Gson gson = new Gson();
private HashMap<String, KonnectedHandler> konnectedThingHandlers = new HashMap<>();
public KonnectedHTTPServlet() {
}
public void add(KonnectedHandler thingHandler) {
logger.trace("Adding KonnectedHandler[{}] to KonnectedHTTPServlet.", thingHandler.getThing().getUID());
konnectedThingHandlers.put(thingHandler.getThing().getUID().getAsString(), thingHandler);
}
public void remove(KonnectedHandler thingHandler) {
logger.trace("Removing KonnectedHandler [{}] from KonnectedHTTPServlet. ", thingHandler.getThing().getUID());
konnectedThingHandlers.remove(thingHandler.getThing().getUID().getAsString());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) {
try {
String data = inputStreamToString(req);
logger.debug("The raw json data is: {}", data);
if (data != null && !konnectedThingHandlers.isEmpty()) {
KonnectedModuleGson event = gson.fromJson(data, KonnectedModuleGson.class);
String authorizationHeader = req.getHeader("Authorization");
String thingHandlerKey = authorizationHeader.substring("Bearer".length()).trim();
logger.debug("The path of the response was: {}", req.getContextPath());
logger.debug("The json received was: {}", event.toString());
logger.debug("The thing handler to send the command to is the handler for thing: {}", thingHandlerKey);
try {
KonnectedHandler thingHandler = konnectedThingHandlers.get(thingHandlerKey);
thingHandler.handleWebHookEvent(event);
} catch (NullPointerException e) {
logger.debug("There was not a handler registered on the servlet to handler commands for thing: {}",
thingHandlerKey);
}
}
setHeaders(resp);
resp.getWriter().write("");
} catch (IOException | JsonSyntaxException e) {
logger.debug("The response received from the module was not valid. {}", e.getMessage());
}
}
private String inputStreamToString(HttpServletRequest req) throws IOException {
Scanner scanner = new Scanner(req.getInputStream()).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
}
private void setHeaders(HttpServletResponse response) {
response.setCharacterEncoding(CHARSET);
response.setContentType(APPLICATION_JSON);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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.konnected.internal.servlet;
/**
* Custom exception class to be thrown by servlet when unable to start.
*
* @author Zachary Christiansen - Initial contribution
*/
@SuppressWarnings("serial")
public class KonnectedWebHookFail extends Exception {
public KonnectedWebHookFail(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="konnected" 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>Konnected Binding</name>
<description>This is the binding for Konnected.</description>
<author>Zachary Christiansen</author>
</binding:binding>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:konnected:module">
<parameter-group name="actions">
<label>Actions</label>
<description/>
</parameter-group>
<parameter name="blink" type="boolean">
<label>Blink</label>
<description> When set to false the Led on the device won't blink during transmission.</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="discovery" type="boolean">
<label>Discovery</label>
<description>If set to false the device will not respond to discovery requests via UPnP. Make sure you have
statically assigned an IP address to the module before turning this setting off. See
https://help.konnected.io/support/solutions/articles/32000023968-disabling-device-discovery</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="retry_count" type="integer">
<label>Retry Count</label>
<description>The number of times the binding attempts to send http requests to the Konnected Alarm Panel. Increase
this setting if you are experiencing situations where the module is reporting as offline but you can access the
website of the Alarm Panel to confirm that the Alarm Panel is Konnected to the Network. This will allow the binding
to attempt more retries before it considers the connection a failure and marks the thing as offline.</description>
<default>2</default>
<advanced>true</advanced>
</parameter>
<parameter name="request_timeout" type="integer">
<label>Request Timeout</label>
<description>The timeout period in seconds for HTTP requests to the Konnected Alarm Panel. The default is 30.
Adjusting this setting can help in networks situations with high latency where the binding is erroneously reporting
the thing as offline.</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="controller_softreset" type="boolean" groupName="actions">
<label>Soft Reset Module</label>
<description>Send A Restart Command to the Module.</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
<parameter name="controller_removewifi" type="boolean" groupName="actions">
<label>Factory Reset</label>
<description>Resets the module to Factory Conditions.</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
<parameter name="controller_sendConfig" type="boolean" groupName="actions">
<label>Update Settings</label>
<description>Manually sends the settings to the module. The binding will send settings on every restart and if there
are any configuration changes but this can be used to manually update the settings as needed.</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="konnected"
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">
<!-- This is the Konnected Thing-Type -->
<thing-type id="module" extensible="switch,actuator,temperature,humidity">
<label>The Konnected Alarm Panel</label>
<description>The Konnected Module</description>
<channels>
<channel id="Zone_6" typeId="switch">
<label>Zone 6</label>
<description>Zone 6 Sensor</description>
</channel>
<channel id="Out" typeId="actuator">
<label>The out Pin</label>
</channel>
</channels>
<config-description-ref uri="thing-type:konnected:module"/>
</thing-type>
<!-- Zone Channel Type -->
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Switch</label>
<description>This zone is a read only switch type zone</description>
<state readOnly="true"/>
<config-description>
<parameter name="zone" type="text" required="true">
<label>Zone Number</label>
<description>The Zone Number of the channel.</description>
<default>6</default>
<options>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">Out</option>
</options>
</parameter>
<parameter name="onvalue" type="text">
<label>On Value</label>
<description>The value that will be treated by the binding as the on value. For sensors that activate with a high
value leave at the default of 1 and sensors that activate with a low value set to 0.</description>
<default>1</default>
<options>
<option value="0">0</option>
<option value="1">1</option>
</options>
</parameter>
</config-description>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>This zone measures temperature</description>
<state readOnly="true"/>
<config-description>
<parameter name="zone" type="text">
<label>Zone Number</label>
<description>The Zone Number of the channel.</description>
<options>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">Out</option>
</options>
</parameter>
<parameter name="tempsensorType" type="boolean">
<label>DHT22</label>
<description>Is the sensor a dht22 or a ds18b20? Set to true for dht22 sensor</description>
</parameter>
<parameter name="pollinterval" type="integer">
<label>Poll Interval</label>
<description>The interval in minutes to poll the sensor.</description>
</parameter>
<parameter name="ds18b20_address" type="text">
<label>DS18b20 Address</label>
<description>This is the unique address of the sensor on the one wire bus.</description>
</parameter>
</config-description>
</channel-type>
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>This zone measures humidity</description>
<state readOnly="true"/>
<config-description>
<parameter name="zone" type="text" required="true">
<label>Zone Number</label>
<description>The Zone Number of the channel.</description>
<options>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">Out</option>
</options>
</parameter>
</config-description>
</channel-type>
<channel-type id="actuator">
<item-type>Switch</item-type>
<label>Actuator</label>
<description>This zone is an actuator</description>
<config-description>
<parameter name="zone" type="text" required="true">
<label>Zone Number</label>
<description>The Zone Number of the channel.</description>
<default>7</default>
<options>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">Out</option>
</options>
</parameter>
<parameter name="onvalue" type="text">
<label>On Value</label>
<description>The value that will be treated by the binding as an on command. For actuators that activate with a high
command set to 1 and actuators that activate with a low value set to 0.</description>
<default>1</default>
<options>
<option value="0">0</option>
<option value="1">1</option>
</options>
</parameter>
<parameter name="momentary" type="integer">
<label>Momentary</label>
<description>The duration of the pulse in millisecods</description>
</parameter>
<parameter name="pause" type="integer">
<label>Pause</label>
<description>The time between pulses in millisecods</description>
</parameter>
<parameter name="times" type="integer">
<label>Times</label>
<description>is the number of times to repeat or `-1` for an infinitely repeating pulse</description>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>