From c1ef6c896f0970516899e50b0a1e2b4c2a04421e Mon Sep 17 00:00:00 2001 From: digitaldan Date: Fri, 20 Mar 2015 21:43:35 -0700 Subject: [PATCH] Initial Commit of the Autelis Pool Control binding. Signed-off-by: Dan Cunningham (github: @digitaldan) --- .../org.openhab.binding.autelis/.classpath | 7 + .../org.openhab.binding.autelis/.project | 33 + .../ESH-INF/binding/binding.xml | 11 + .../ESH-INF/thing/thing-types.xml | 570 ++++++++++++++++++ .../META-INF/MANIFEST.MF | 25 + .../OSGI-INF/AutelisDiscovery.xml | 14 + .../OSGI-INF/AutelisHandlerFactory.xml | 19 + .../build.properties | 6 + .../org.openhab.binding.autelis/pom.xml | 19 + .../autelis/AutelisBindingConstants.java | 33 + .../autelis/config/AutelisConfiguration.java | 42 ++ .../discovery/AutelisDiscoveryService.java | 110 ++++ .../autelis/handler/AutelisHandler.java | 433 +++++++++++++ .../internal/AutelisHandlerFactory.java | 50 ++ addons/binding/pom.xml | 3 +- 15 files changed, 1374 insertions(+), 1 deletion(-) create mode 100644 addons/binding/org.openhab.binding.autelis/.classpath create mode 100644 addons/binding/org.openhab.binding.autelis/.project create mode 100644 addons/binding/org.openhab.binding.autelis/ESH-INF/binding/binding.xml create mode 100644 addons/binding/org.openhab.binding.autelis/ESH-INF/thing/thing-types.xml create mode 100644 addons/binding/org.openhab.binding.autelis/META-INF/MANIFEST.MF create mode 100644 addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisDiscovery.xml create mode 100644 addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisHandlerFactory.xml create mode 100644 addons/binding/org.openhab.binding.autelis/build.properties create mode 100644 addons/binding/org.openhab.binding.autelis/pom.xml create mode 100644 addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/AutelisBindingConstants.java create mode 100644 addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/config/AutelisConfiguration.java create mode 100644 addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/discovery/AutelisDiscoveryService.java create mode 100644 addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/handler/AutelisHandler.java create mode 100644 addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/internal/AutelisHandlerFactory.java diff --git a/addons/binding/org.openhab.binding.autelis/.classpath b/addons/binding/org.openhab.binding.autelis/.classpath new file mode 100644 index 0000000000000..a95e0906ca013 --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/binding/org.openhab.binding.autelis/.project b/addons/binding/org.openhab.binding.autelis/.project new file mode 100644 index 0000000000000..8554832662527 --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/.project @@ -0,0 +1,33 @@ + + + org.openhab.binding.autelis + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/addons/binding/org.openhab.binding.autelis/ESH-INF/binding/binding.xml b/addons/binding/org.openhab.binding.autelis/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..607354071bc0f --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/ESH-INF/binding/binding.xml @@ -0,0 +1,11 @@ + + + + Autelis Pool Control Binding + This is the binding for a Autelis pool controller. + Dan Cunningham + + diff --git a/addons/binding/org.openhab.binding.autelis/ESH-INF/thing/thing-types.xml b/addons/binding/org.openhab.binding.autelis/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..f96599dfc984b --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/ESH-INF/thing/thing-types.xml @@ -0,0 +1,570 @@ + + + + + A pool control thing represents a Autelis pool controller + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + network-address + + The host name or IP address of the Autelis Controller. + + + network-address + + The port the Autelis Controller is listening on. + 80 + + + + The user name to use when connecting to a Autelis Controller. + + + + The password to use when connecting to a Autelis Controller. + + + + Specifies the refresh interval in seconds + 5 + + + + + Switch + + The controller's runstate. 1 = Starting Up, 2-49 = Getting Data, 50 = Ready + + + + + Number + + The model of the Intellitouch® controller + + + + + Number + + The controller's harware address + + + + + Number + + The current state of the Intellitouch® controller + + + + + + + + + Number + + The state of freeze protect + + + + + + + + + Number + + The state of sensor 1 (water sensor) + + + + + + + + + Number + + The state of sensor 2 (solar sensor) + + + + + + + + + Number + + The state of sensor 3 (air sensor) + + + + + + + + + Number + + The state of sensor 4 (water sensor 2) + + + + + + + + + Number + + The state of sensor 5 (solar sensor 2) + + + + + + + + + String + + The firmware version of the Pool Control device + + + + + Number + + The time as kept by the Pool Control device + + + + + Switch + + The current state of circuit 1 (Spa or Lo-Temp) + + + + + Switch + + The current state of circuit 2 + + + + + Switch + + The current state of circuit 3 + + + + + Switch + + The current state of circuit 4 + + + + + Switch + + The current state of circuit 5 + + + + + Switch + + The current state of circuit 6 (Pool or Hi-Temp) + + + + + Switch + + The current state of circuit 7 + + + + + Switch + + The current state of circuit 8 + + + + + Switch + + The current state of circuit 9 + + + + + Number + + The current state of circuit 10 + + + + + Number + + The current state of feature/macro 1 + + + + + Number + + The current state of feature/macro 2 + + + + + Number + + The current state of feature/macro 3 + + + + + Number + + The current state of feature/macro 4 + + + + + Number + + The current state of feature/macro 5 + + + + + Number + + The current state of feature/macro 6 + + + + + Number + + The current state of feature/macro 7 + + + + + Number + + The current state of feature/macro 8 + + + + + Number + + The current state of feature/macro 9 + + + + + Number + + The current state of feature/macro 10 + + + + + Number + + The current pool/hi-temp heater settings + Temperature + + + + + + + + + + + Switch + + Spa Heat + Temperature + + + + + + + + + + + Number + + Heat Status + + + + + Number + + The current pool/hi-temp setpoint + Temperature + + + Number + + The current spa/lo-temp setpoint + Temperature + + + Number + + The current pool temperature. Note: Only accurate when pool is running + Temperature + + + + + Number + + The current spa temperature. Note: Only accurate when spa is running + Temperature + + + + + Number + + The current air temperature + Temperature + + + + + String + + The selected units for temperature + + + + + + + + + Number + + Heat Pump + + + + + String + + Pump 1 + + + + + String + + Pump 2 + + + + + String + + Pump 3 + + + + + String + + Pump 4 + + + + + String + + Pump 5 + + + + + String + + Pump 6 + + + + + String + + Pump 7 + + + + + String + + Pump 8 + + + + + Number + + Chlorine + + + + + Number + + Chlorine Pool Setpoint + + + + + Number + + Chlorine Sap Setpoint + + + + + Number + + Chlorine Salt Level + + + + + Number + + Chlorine Super Level + + + + + Number + + Chlorine Error value + + + + + String + + Chlorine Model Name + + + + + String + + Send A lighting command + ColorLight + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.autelis/META-INF/MANIFEST.MF b/addons/binding/org.openhab.binding.autelis/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000..983623095a9c1 --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/META-INF/MANIFEST.MF @@ -0,0 +1,25 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Autelis Binding +Bundle-SymbolicName: org.openhab.binding.autelis;singleton:=true +Bundle-Vendor: openHAB +Bundle-Version: 2.0.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ClassPath: . +Import-Package: com.google.common.collect, + org.apache.commons.httpclient, + org.apache.commons.httpclient.auth, + org.apache.commons.httpclient.methods, + org.apache.commons.httpclient.params, + org.apache.commons.io, + org.apache.commons.lang, + org.eclipse.smarthome.config.core, + org.eclipse.smarthome.config.discovery, + org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.thing, + org.eclipse.smarthome.core.thing.binding, + org.eclipse.smarthome.core.types, + org.slf4j +Service-Component: OSGI-INF/*.xml +Export-Package: org.openhab.binding.autelis, + org.openhab.binding.autelis.handler diff --git a/addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisDiscovery.xml b/addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisDiscovery.xml new file mode 100644 index 0000000000000..784cf740b8d7b --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisDiscovery.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisHandlerFactory.xml b/addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisHandlerFactory.xml new file mode 100644 index 0000000000000..cdd82ead2595b --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/OSGI-INF/AutelisHandlerFactory.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.autelis/build.properties b/addons/binding/org.openhab.binding.autelis/build.properties new file mode 100644 index 0000000000000..66e21b90751a7 --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/build.properties @@ -0,0 +1,6 @@ +source.. = src/main/java/ +output.. = target/classes +bin.includes = META-INF/,\ + .,\ + OSGI-INF/,\ + ESH-INF/ \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.autelis/pom.xml b/addons/binding/org.openhab.binding.autelis/pom.xml new file mode 100644 index 0000000000000..72e18107b47cf --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/pom.xml @@ -0,0 +1,19 @@ + + + + 4.0.0 + + + org.openhab.binding + pom + 2.0.0-SNAPSHOT + + + org.openhab.binding + org.openhab.binding.autelis + 2.0.0-SNAPSHOT + + Autelis Binding + eclipse-plugin + + diff --git a/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/AutelisBindingConstants.java b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/AutelisBindingConstants.java new file mode 100644 index 0000000000000..31ed84b4d4fde --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/AutelisBindingConstants.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2014-2014 openHAB UG (haftungsbeschraenkt) and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.autelis; + +import java.util.Set; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; + +import com.google.common.collect.ImmutableSet; + +/** + * The {@link AutelisBinding} class defines common constants, which are used + * across the whole binding. + * + * @author Dan Cunningham - Initial contribution + */ +public class AutelisBindingConstants { + + public static final String BINDING_ID = "autelis"; + + // List of all Thing Type UIDs + public final static ThingTypeUID POOLCONTROL_THING_TYPE_UID = new ThingTypeUID( + BINDING_ID, "poolcontrol"); + + public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet + .of(POOLCONTROL_THING_TYPE_UID); +} diff --git a/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/config/AutelisConfiguration.java b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/config/AutelisConfiguration.java new file mode 100644 index 0000000000000..051a64a1fa12b --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/config/AutelisConfiguration.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2014-2014 openHAB UG (haftungsbeschraenkt) and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.autelis.config; + +/** + * Configuration properties for connecting to a Autelis Controller + * + * @author Dan Cunningham + * + */ +public class AutelisConfiguration { + + /** + * Host of the Autelis controller + */ + public String host; + /** + * port of the Autelis controller + */ + public Integer port; + + /** + * user to us when connecting to the Autelis controller + */ + public String user; + + /** + * password to us when connecting to the Autelis controller + */ + public String password; + + /** + * Rate we poll for new data + */ + public Integer refresh; +} diff --git a/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/discovery/AutelisDiscoveryService.java b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/discovery/AutelisDiscoveryService.java new file mode 100644 index 0000000000000..fee55facdccbc --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/discovery/AutelisDiscoveryService.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2014-2014 openHAB UG (haftungsbeschraenkt) and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.autelis.discovery; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.commons.io.IOUtils; +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryResult; +import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.autelis.AutelisBindingConstants; + +/** + * + * Discovery Service for Autelis Pool Controllers. + * + * @author Dan Cunningham + * + */ +public class AutelisDiscoveryService extends AbstractDiscoveryService { + + private final Logger logger = LoggerFactory + .getLogger(AutelisDiscoveryService.class); + + private static String DEFAULT_NAME = "poolcontrol"; + + public AutelisDiscoveryService() throws IllegalArgumentException { + super(AutelisBindingConstants.SUPPORTED_THING_TYPES_UIDS, 10); + } + + @Override + public Set getSupportedThingTypes() { + return AutelisBindingConstants.SUPPORTED_THING_TYPES_UIDS; + } + + @Override + protected void startBackgroundDiscovery() { + scheduler.schedule(new Runnable() { + @Override + public void run() { + discoverAutelis(); + } + }, 0, TimeUnit.MILLISECONDS); + } + + @Override + protected void startScan() { + discoverAutelis(); + } + + /** + * Looks for devices that respond back with the proper title tags + */ + private void discoverAutelis() { + String response = get("http://" + DEFAULT_NAME + "/app.html"); + if (response != null + && response.contains("Autelis Pool Control")) { + ThingUID uid = new ThingUID( + AutelisBindingConstants.POOLCONTROL_THING_TYPE_UID, "pool"); + Map properties = new HashMap<>(1); + properties.put("host", DEFAULT_NAME); + properties.put("user", "admin"); + properties.put("password", "admin"); + properties.put("port", new Integer(80)); + DiscoveryResult result = DiscoveryResultBuilder.create(uid) + .withProperties(properties) + .withLabel("Autelis Pool Controller").build(); + thingDiscovered(result); + } + } + + /** + * Performs a get request + * + * @param url + * to get + * @return the string response or null + */ + private String get(String url) { + String response = null; + try { + URL _url = new URL(url); + URLConnection connection = _url.openConnection(); + response = IOUtils.toString(connection.getInputStream()); + } catch (MalformedURLException e) { + logger.debug("Constructed url '{}' is not valid: {}", url, + e.getMessage()); + } catch (IOException e) { + logger.error("Error accessing url '{}' : {} ", url, e.getMessage()); + } + return response; + } +} diff --git a/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/handler/AutelisHandler.java b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/handler/AutelisHandler.java new file mode 100644 index 0000000000000..53ee2ae5fc952 --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/handler/AutelisHandler.java @@ -0,0 +1,433 @@ +/** + * Copyright (c) 2014-2014 openHAB UG (haftungsbeschraenkt) and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.autelis.handler; + +import java.io.StringReader; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.autelis.config.AutelisConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; + +/** + * + * Autelis Pool Control Binding + * + * Autelis controllers allow remote access to many common pool systems. This + * binding allows openHAB to both monitor and control a pool system through + * these controllers. + * + * @see http://autelis.com + * + * The {@link AutelisHandler} is responsible for handling commands, which + * are sent to one of the channels. + * + * @author Dan Cunningham - Initial contribution + */ +public class AutelisHandler extends BaseThingHandler { + + private Logger logger = LoggerFactory.getLogger(AutelisHandler.class); + + /** + * Default timeout for http connections to a Autelis controller + */ + static final int TIMEOUT = 5000; + + /** + * Autelis controllers will not update their XML immediately after we change + * a value. To compensate we cache previous values for a {@link Channel} + * using the item name as a key. After a polling run has been executed we + * only update an channel if the value is different then what's in the + * cache. This cache is cleared after a fixed time period when commands are + * sent. + */ + private Map stateMap = Collections + .synchronizedMap(new HashMap()); + + /** + * Clear our state every hour + */ + private static int NORMAL_CLEARTIME = 60 * 60; // one hour + + /** + * Clear state after an command is sent + */ + private static int UPDATE_CLEARTIME = 60 * 2; // two minutes + + /** + * Holds the next clear time in millis + */ + private long clearTime; + + /** + * Constructed URL consisting of host and port + */ + private String baseURL; + + /** + * Our poll rate + */ + int refresh; + + /** + * The http client used for polling requests + */ + HttpClient client; + + /** + * Regex expression to match XML responses from the Autelis, this is used to + * combine similar XML docs into a single document, {@link XPath} is still + * used for XML querying + */ + private Pattern responsePattern = Pattern.compile( + "(.+?)", Pattern.DOTALL); + + /** + * is out config correct + */ + private boolean properlyConfigured; + + /** + * Future to poll for updated + */ + private ScheduledFuture pollFuture; + + public AutelisHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + super.initialize(); + configure(); + } + + @Override + public void dispose() { + logger.debug("Handler disposed."); + stopPolling(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + + if (channelUID.getId().equals("lightscmd")) { + /* + * lighting command possible values, but we will let anything + * through. alloff, allon, csync, cset, cswim, party, romance, + * caribbean, american, sunset, royalty, blue, green, red, white, + * magenta, hold, recall + */ + getUrl(baseURL + "lights.cgi?val=" + command.toString(), TIMEOUT); + } else { + String[] args = channelUID.getId().split("-"); + if (args.length < 2) { + logger.warn("Unown channel {} for command {}", channelUID, + command); + return; + } + String type = args[0]; + String name = args[1]; + + if (type.equals("equipment")) { + String cmd = "value"; + int value; + if (command == OnOffType.OFF) { + value = 0; + } else if (command == OnOffType.ON) { + value = 1; + } else if (command instanceof DecimalType) { + value = ((DecimalType) command).intValue(); + if (value >= 3) { + // this is a dim type. not sure what 2 does + cmd = "dim"; + } + } else { + logger.error("command type {} is not supported", command); + return; + } + String response = getUrl(baseURL + "/set.cgi?name=" + name + + "&" + cmd + "=" + value, TIMEOUT); + logger.trace("equipment set {} {} {} : result {}", name, cmd, + value, response); + } else if (type.equals("temp")) { + String value; + if (command == IncreaseDecreaseType.INCREASE) { + value = "up"; + } else if (command == IncreaseDecreaseType.DECREASE) { + value = "down"; + } else { + value = command.toString(); + } + + String cmd; + // name ending in sp are setpoints, ht are heat types? + if (name.endsWith("sp")) { + cmd = "temp"; + } else if (name.endsWith("ht")) { + cmd = "hval"; + } else { + logger.error("Unknown temp type {}", name); + return; + } + + String response = getUrl(baseURL + "/set.cgi?wait=1&name=" + + name + "&" + cmd + "=" + value, TIMEOUT); + logger.trace("temp set {} {} : result {}", cmd, value, response); + } else { + logger.error("Unsupported type {}", type); + } + } + scheduleClearTime(UPDATE_CLEARTIME); + } + + /** + * Configures this thing + */ + private void configure() { + + properlyConfigured = false; + getThing().setStatus(ThingStatus.OFFLINE); + AutelisConfiguration configuration = getConfig().as( + AutelisConfiguration.class); + try { + Integer _refresh = configuration.refresh; + Integer _port = configuration.port; + String host = configuration.host; + String username = configuration.user; + String password = configuration.password; + + if (StringUtils.isBlank(username)) + throw new RuntimeException("username must not be empty"); + + if (StringUtils.isBlank(password)) + throw new RuntimeException("password must not be empty"); + + if (StringUtils.isBlank(host)) + throw new RuntimeException("hostname must not be empty"); + + refresh = 5; + if (_refresh != null) { + refresh = _refresh.intValue(); + } + + int port = 80; + if (_port != null) { + port = _port.intValue(); + } + + client = new HttpClient(); + + Credentials creds = new UsernamePasswordCredentials(username, + password); + client.getState().setCredentials(new AuthScope(host, port), creds); + client.getParams().setAuthenticationPreemptive(true); + + baseURL = "http://" + host + ":" + port; + + properlyConfigured = true; + + logger.debug( + "Autelius binding configured with base url {} and refresh period of {}", + baseURL, refresh); + + initPolling(); + + } catch (Exception e) { + logger.error("Could not configure autelis instance", e); + } + } + + /** + * starts this things polling future + */ + private void initPolling() { + stopPolling(); + pollFuture = scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + if (properlyConfigured) { + execute(); + } + } catch (Exception e) { + logger.debug("Exception during poll : {}", e); + } + } + }, 0, refresh, TimeUnit.SECONDS); + + } + + /** + * Stops this thing's polling future + */ + private void stopPolling() { + if (pollFuture != null && !pollFuture.isCancelled()) { + pollFuture.cancel(true); + pollFuture = null; + } + } + + /** + * The polling future executes this every iteration + */ + protected void execute() { + logger.trace("Connecting to {}" + baseURL); + + clearState(); + + // we will reconstruct the document with all the responses combined for + // XPATH + StringBuilder sb = new StringBuilder(""); + + // pull down the three xml documents + String[] statuses = { "status", "chem", "pumps" }; + for (String status : statuses) { + String response = getUrl(baseURL + "/" + status + ".xml", TIMEOUT); + logger.trace(baseURL + "/" + status + ".xml \n {}", response); + if (response == null) { + logger.debug("No response from Autelis controller!"); + updateStatus(ThingStatus.OFFLINE); + return; + } + // get the xml data between the response tags and append to our main + // doc + Matcher m = responsePattern.matcher(response); + if (m.find()) { + sb.append(m.group(1)); + } + } + // finish our "new" XML Document + sb.append(""); + + updateStatus(ThingStatus.ONLINE); + + /* + * This xmlDoc will now contain the three XML documents we retrieved + * wrapped in response tags for easier querying in XPath. + */ + String xmlDoc = sb.toString(); + for (Channel channel : getThing().getChannels()) { + String key = channel.getUID().getId().replace('-', '/'); + XPathFactory xpathFactory = XPathFactory.newInstance(); + XPath xpath = xpathFactory.newXPath(); + try { + InputSource is = new InputSource(new StringReader(xmlDoc)); + String value = xpath.evaluate("response/" + key, is); + + if (StringUtils.isEmpty((value))) + continue; + + State state = toState(channel.getAcceptedItemType(), value); + State oldState = stateMap.put(channel.getUID().getAsString(), + state); + if (!state.equals(oldState)) { + logger.trace("updating channel {} with state {}", channel, + state); + updateState(channel.getUID(), state); + } + } catch (XPathExpressionException e) { + logger.error("could not parse xml", e); + } + } + } + + /** + * Simple logic to perform a authenticated GET request + * + * @param url + * @param timeout + * @return + */ + private String getUrl(String url, int timeout) { + GetMethod method = new GetMethod(url); + method.getParams().setSoTimeout(timeout); + try { + int statusCode = client.executeMethod(method); + if (statusCode != HttpStatus.SC_OK) { + logger.debug("Method failed: {}", method.getStatusLine()); + return null; + } + return IOUtils.toString(method.getResponseBodyAsStream()); + } catch (Exception e) { + logger.debug("Could not make http connection", e); + } finally { + method.releaseConnection(); + } + return null; + } + + /** + * Converts a {@link String} value to a {@link State} for a given + * {@link String} accepted type + * + * @param itemType + * @param value + * @return {@link State} + */ + private State toState(String type, String value) + throws NumberFormatException { + if (type.equals("Number")) { + return new DecimalType(value); + } else if (type.equals("Switch")) { + return Integer.parseInt(value) > 0 ? OnOffType.ON : OnOffType.OFF; + } else { + return StringType.valueOf(value); + } + } + + /** + * Clears our state if it is time + */ + private void clearState() { + if (System.currentTimeMillis() >= clearTime) { + stateMap.clear(); + scheduleClearTime(NORMAL_CLEARTIME); + } + } + + /** + * Schedule when our next clear cycle will be + * + * @param secs + */ + private void scheduleClearTime(int secs) { + clearTime = System.currentTimeMillis() + (secs * 1000); + } +} diff --git a/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/internal/AutelisHandlerFactory.java b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/internal/AutelisHandlerFactory.java new file mode 100644 index 0000000000000..d9c73e2dcc602 --- /dev/null +++ b/addons/binding/org.openhab.binding.autelis/src/main/java/org/openhab/binding/autelis/internal/AutelisHandlerFactory.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2014-2014 openHAB UG (haftungsbeschraenkt) and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.autelis.internal; + +import static org.openhab.binding.autelis.AutelisBindingConstants.*; + +import java.util.Collections; +import java.util.Set; + +import org.openhab.binding.autelis.handler.AutelisHandler; + +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; + +/** + * The {@link AutelisHandlerFactory} is responsible for creating things and + * thing handlers. + * + * @author Dan Cunningham - Initial contribution + */ +public class AutelisHandlerFactory extends BaseThingHandlerFactory { + + private final static Set SUPPORTED_THING_TYPES_UIDS = Collections + .singleton(POOLCONTROL_THING_TYPE_UID); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(POOLCONTROL_THING_TYPE_UID)) { + return new AutelisHandler(thing); + } + + return null; + } +} diff --git a/addons/binding/pom.xml b/addons/binding/pom.xml index f41819eadeef4..c47d0e6b95ae5 100644 --- a/addons/binding/pom.xml +++ b/addons/binding/pom.xml @@ -22,7 +22,8 @@ org.openhab.binding.max.test org.openhab.binding.network org.openhab.binding.pulseaudio - org.openhab.binding.sonos + org.openhab.binding.sonos + org.openhab.binding.autelis