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.venstarthermostat</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,89 @@
# Venstar Thermostat Binding
The Venstar Thermostat binding supports an interface to WiFi enabled ColorTouch and Explorer thermostats manufactured by [Venstar](http://www.venstar.com).
Venstar WiFi enabled thermostats provide a local API that this binding uses
to communicate with the thermostat. This binding does not require "cloud"
access and may be used independently of Venstar's Skyport cloud services.
The Local API is not enabled by default, so you will need to set up your
thermostat by configuring its WiFi connection and enabling the Local API. In
order for the binding to connect, you will need to enable HTTPS support and
set a username and password. While it is possible to enable the Local API
without HTTPS and authentication, the binding doesn't support it, in an effort
to provide as secure an installation as possible.
When you've set the username and password, make a note of these, as you'll need
to enter them in the thermostat configuration in openHAB.
## Supported Things
| Thing Type | Description |
|----------------------|-----------------------------------------------------------------------------------|
| colorTouchThermostat | A Venstar [ColorTouch](http://www.venstar.com/thermostats/colortouch/) thermostat |
## Discovery
Once the binding is installed it will attempt to auto discovery Venstar thermostats located on the local network.
These will appear as Things in the system Inbox.
After adding the Inbox item, enter the user name and password from the physical thermostat in the Thing's configuration.
## Thing Configuration
### ColorTouch Thermostat
| Parameter | Description | Required |
|-----------|------------------------------------------------------------------------------|----------|
| username | The username set on the thermostats configuration screen (typically 'admin') | yes |
| password | The password set set on the thermostats configuration screen | yes |
| url | URL of the thermostat in the format 'proto://host' | yes |
| refresh | The frequency in which the binding will pool for update information | no |
### Channels
| Channel | Type | Description | Notes |
|--------------------|--------------------|------------------------------|--------------------------------------------------------|
| systemMode | String | System Mode | |
| systemModeRaw | Number | System Mode Raw (Read Only) | 0 (Off) 1 (Heat) 2 (Cool) 3 (Auto) |
| systemState | String | System State (Read Only) | |
| systemStateRaw | Number | System State Raw (Read Only) | 0 (Idle) 1 (Heating) 2 (Cooling) 3 (Lockout) 4 (Error) |
| heatingSetpoint | Number:Temperature | Heating Set Point | |
| coolingSetpoint | Number:Temperature | Cooling Set Point | |
| temperature | Number:Temperature | Current Temperature | |
| outdoorTemperature | Number:Temperature | Outdoor Temperature | |
| humidity | Number | Humidity | |
## Example
### thermostat.things
```
Thing venstarthermostat:colorTouchThermostat:001122334455 "Venstar Thermostat (Guest)" [ username="admin", password="secret", url="https://192.168.1.100", refresh=30 ]
```
### thermostat.items
```
Number:Temperature Guest_HVAC_Temperature "Temperature [%d °F]" {channel="venstarthermostat:colorTouchThermostat:001122334455:temperature"}
Number:Temperature Guest_HVAC_HeatSetpoint "Heat Setpoint [%d °F]" {channel="venstarthermostat:colorTouchThermostat:001122334455:heatingSetpoint"}
Number:Temperature Guest_HVAC_CoolSetpoint "Cool Setpoint [%d °F]" {channel="venstarthermostat:colorTouchThermostat:001122334455:coolingSetpoint"}
Number Guest_HVAC_Mode "Mode [%s]" {channel="venstarthermostat:colorTouchThermostat:001122334455:systemMode"}
Number Guest_HVAC_Humidity "Humidity [%d %%]" {channel="venstarthermostat:colorTouchThermostat:001122334455:humidity"}
Number Guest_HVAC_State "State [%s]" {channel="venstarthermostat:colorTouchThermostat:001122334455:systemState"}
```
### thermostat.sitemap
```
sitemap demo label="Venstar Color Thermostat Demo"
{
Frame {
Setpoint item=Guest_HVAC_HeatSetpoint minValue=50 maxValue=99
Setpoint item=Guest_HVAC_CoolSetpoint minValue=50 maxValue=99
Switch item=Guest_HVAC_Mode mappings=[off=Off,heat=Heat,cool=Cool,auto=Auto]
Text item=Guest_HVAC_State
}
}
```

View File

@@ -0,0 +1,15 @@
<?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.venstarthermostat</artifactId>
<name>openHAB Add-ons :: Bundles :: Venstar Thermostat Binding</name>
</project>

View File

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

View File

@@ -0,0 +1,57 @@
/**
* 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.venstarthermostat.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link VenstarThermostatBinding} class defines common constants, which are
* used across the whole binding.
*
* @author William Welliver - Initial contribution
*/
@NonNullByDefault
public class VenstarThermostatBindingConstants {
public static final String BINDING_ID = "venstarthermostat";
// List of all Thing Type UIDs
public final static ThingTypeUID THING_TYPE_COLOR_TOUCH = new ThingTypeUID(BINDING_ID, "colorTouchThermostat");
public final static Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COLOR_TOUCH);
// List of all Channel ids
public final static String CHANNEL_TEMPERATURE = "temperature";
public final static String CHANNEL_HUMIDITY = "humidity";
public final static String CHANNEL_EXTERNAL_TEMPERATURE = "outdoorTemperature";
public final static String CHANNEL_HEATING_SETPOINT = "heatingSetpoint";
public final static String CHANNEL_COOLING_SETPOINT = "coolingSetpoint";
public final static String CHANNEL_SYSTEM_STATE = "systemState";
public final static String CHANNEL_SYSTEM_MODE = "systemMode";
public final static String CHANNEL_SYSTEM_STATE_RAW = "systemStateRaw";
public final static String CHANNEL_SYSTEM_MODE_RAW = "systemModeRaw";
public final static String CONFIG_USERNAME = "username";
public final static String CONFIG_PASSWORD = "password";
public final static String CONFIG_REFRESH = "refresh";
public final static String PROPERTY_URL = "url";
public final static String PROPERTY_UUID = "uuid";
public final static String REFRESH_INVALID = "refresh-invalid";
public final static String EMPTY_INVALID = "empty-invalid";
}

View File

@@ -0,0 +1,26 @@
/**
* 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.venstarthermostat.internal;
/**
* The {@link VenstarThermostatConfiguration} is responsible for holding configuration information.
*
* @author William Welliver - Initial contribution
*/
public class VenstarThermostatConfiguration {
public String username;
public String password;
public String url;
public Integer refresh;
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.venstarthermostat.internal;
import static org.openhab.binding.venstarthermostat.internal.VenstarThermostatBindingConstants.THING_TYPE_COLOR_TOUCH;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.venstarthermostat.internal.handler.VenstarThermostatHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link VenstarThermostatHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author William Welliver - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.venstarthermostat")
public class VenstarThermostatHandlerFactory extends BaseThingHandlerFactory {
private final static Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_COLOR_TOUCH);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_COLOR_TOUCH)) {
return new VenstarThermostatHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,249 @@
/**
* 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.venstarthermostat.internal.discovery;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.venstarthermostat.internal.VenstarThermostatBindingConstants;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VenstarThermostatDiscoveryService} is responsible for discovery of
* Venstar thermostats on the local network
*
* @author William Welliver - Initial contribution
* @author Dan Cunningham - Refactoring and Improvements
*/
@Component(service = DiscoveryService.class, configurationPid = "discovery.venstarthermostat")
public class VenstarThermostatDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(VenstarThermostatDiscoveryService.class);
private static final String COLOR_TOUCH_DISCOVERY_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
+ "Host: 239.255.255.250:1900\r\n" + "Man: ssdp:discover\r\n" + "ST: colortouch:ecp\r\n" + "\r\n";
private static final Pattern USN_PATTERN = Pattern
.compile("^(colortouch:)?ecp((?::[0-9a-fA-F]{2}){6}):name:(.+)(?::type:(\\w+))");
private static final String SSDP_MATCH = "colortouch:ecp";
private static final int BACKGROUND_SCAN_INTERVAL_SECONDS = 300;
private ScheduledFuture<?> scheduledFuture = null;
public VenstarThermostatDiscoveryService() {
super(VenstarThermostatBindingConstants.SUPPORTED_THING_TYPES, 30, true);
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Starting Background Scan");
stopBackgroundDiscovery();
scheduledFuture = scheduler.scheduleAtFixedRate(this::doRunRun, 0, BACKGROUND_SCAN_INTERVAL_SECONDS,
TimeUnit.SECONDS);
}
@Override
protected void stopBackgroundDiscovery() {
if (scheduledFuture != null && !scheduledFuture.isCancelled()) {
scheduledFuture.cancel(true);
}
}
@Override
protected void startScan() {
logger.debug("Starting Interactive Scan");
doRunRun();
}
protected synchronized void doRunRun() {
logger.trace("Sending SSDP discover.");
for (int i = 0; i < 5; i++) {
try {
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
while (nets.hasMoreElements()) {
NetworkInterface ni = nets.nextElement();
MulticastSocket socket = sendDiscoveryBroacast(ni);
if (socket != null) {
scanResposesForKeywords(socket);
}
}
} catch (IOException e) {
logger.debug("Error discoverying devices", e);
}
}
}
/**
* Broadcasts a SSDP discovery message into the network to find provided
* services.
*
* @return The Socket the answers will arrive at.
* @throws UnknownHostException
* @throws IOException
* @throws SocketException
* @throws UnsupportedEncodingException
*/
private MulticastSocket sendDiscoveryBroacast(NetworkInterface ni)
throws UnknownHostException, SocketException, UnsupportedEncodingException {
InetAddress m = InetAddress.getByName("239.255.255.250");
final int port = 1900;
logger.trace("Considering {}", ni.getName());
try {
if (!ni.isUp() || !ni.supportsMulticast()) {
logger.trace("skipping interface {}", ni.getName());
return null;
}
Enumeration<InetAddress> addrs = ni.getInetAddresses();
InetAddress a = null;
while (addrs.hasMoreElements()) {
a = addrs.nextElement();
if (a instanceof Inet4Address) {
break;
} else {
a = null;
}
}
if (a == null) {
logger.trace("no ipv4 address on {}", ni.getName());
return null;
}
// for whatever reason, the venstar thermostat responses will not be seen
// if we bind this socket to a particular address.
// this seems to be okay on linux systems, but osx apparently prefers ipv6, so this
// prevents responses from being received unless the ipv4 stack is given preference.
MulticastSocket socket = new MulticastSocket(new InetSocketAddress(port));
socket.setSoTimeout(2000);
socket.setReuseAddress(true);
socket.setNetworkInterface(ni);
socket.joinGroup(m);
logger.trace("Joined UPnP Multicast group on Interface: {}", ni.getName());
byte[] requestMessage = COLOR_TOUCH_DISCOVERY_MESSAGE.getBytes("UTF-8");
DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, m, port);
socket.send(datagramPacket);
return socket;
} catch (IOException e) {
logger.trace("got ioexception: {}", e.getMessage());
}
return null;
}
/**
* Scans all messages that arrive on the socket and scans them for the
* search keywords. The search is not case sensitive.
*
* @param socket
* The socket where the answers arrive.
* @param keywords
* The keywords to be searched for.
* @return
* @throws IOException
*/
private void scanResposesForKeywords(MulticastSocket socket, String... keywords) throws IOException {
// In the worst case a SocketTimeoutException raises
do {
byte[] rxbuf = new byte[8192];
DatagramPacket packet = new DatagramPacket(rxbuf, rxbuf.length);
try {
socket.receive(packet);
} catch (IOException e) {
logger.trace("Got exception while trying to receive UPnP packets: {}", e.getMessage());
return;
}
String response = new String(packet.getData());
if (response.contains(SSDP_MATCH)) {
logger.trace("Match: {} ", response);
parseResponse(response);
}
} while (true);
}
protected void parseResponse(String response) {
DiscoveryResult result;
String name = null;
String url = null;
String uuid = null;
Scanner scanner = new Scanner(response);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] pair = line.split(":", 2);
if (pair.length != 2) {
continue;
}
String key = pair[0].toLowerCase();
String value = pair[1].trim();
logger.trace("key: {} value: {}.", key, value);
switch (key) {
case "location":
url = value;
break;
case "usn":
Matcher m = USN_PATTERN.matcher(value);
if (m.find()) {
uuid = m.group(2);
name = m.group(3);
}
break;
default:
break;
}
}
scanner.close();
logger.trace("Found thermostat, name: {} uuid: {} url: {}", name, uuid, url);
if (name == null || uuid == null || url == null) {
logger.trace("Bad Format from thermostat");
return;
}
uuid = uuid.replace(":", "").toLowerCase();
ThingUID thingUid = new ThingUID(VenstarThermostatBindingConstants.THING_TYPE_COLOR_TOUCH, uuid);
logger.trace("Got discovered device.");
String label = String.format("Venstar Thermostat (%s)", name);
result = DiscoveryResultBuilder.create(thingUid).withLabel(label)
.withRepresentationProperty(VenstarThermostatBindingConstants.PROPERTY_UUID)
.withProperty(VenstarThermostatBindingConstants.PROPERTY_UUID, uuid)
.withProperty(VenstarThermostatBindingConstants.PROPERTY_URL, url).build();
logger.trace("New venstar thermostat discovered with ID=<{}>", uuid);
this.thingDiscovered(result);
}
}

View File

@@ -0,0 +1,495 @@
/**
* 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.venstarthermostat.internal.handler;
import static org.openhab.binding.venstarthermostat.internal.VenstarThermostatBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Temperature;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.DigestAuthentication;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.openhab.binding.venstarthermostat.internal.VenstarThermostatConfiguration;
import org.openhab.binding.venstarthermostat.internal.model.VenstarInfoData;
import org.openhab.binding.venstarthermostat.internal.model.VenstarResponse;
import org.openhab.binding.venstarthermostat.internal.model.VenstarSensor;
import org.openhab.binding.venstarthermostat.internal.model.VenstarSensorData;
import org.openhab.binding.venstarthermostat.internal.model.VenstarSystemMode;
import org.openhab.binding.venstarthermostat.internal.model.VenstarSystemModeSerializer;
import org.openhab.binding.venstarthermostat.internal.model.VenstarSystemState;
import org.openhab.binding.venstarthermostat.internal.model.VenstarSystemStateSerializer;
import org.openhab.core.config.core.status.ConfigStatusMessage;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
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.ConfigStatusThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* The {@link VenstarThermostatHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author William Welliver - Initial contribution
* @author Dan Cunningham - Migration to Jetty, annotations and various improvements
*/
@NonNullByDefault
public class VenstarThermostatHandler extends ConfigStatusThingHandler {
private static final int TIMEOUT_SECONDS = 30;
private static final int UPDATE_AFTER_COMMAND_SECONDS = 2;
private Logger log = LoggerFactory.getLogger(VenstarThermostatHandler.class);
private List<VenstarSensor> sensorData = new ArrayList<>();
private VenstarInfoData infoData = new VenstarInfoData();
private Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
private @Nullable Future<?> updatesTask;
private @Nullable URL baseURL;
private int refresh;
private final HttpClient httpClient;
private final Gson gson;
// Venstar Thermostats are most commonly installed in the US, so start with a reasonable default.
private Unit<Temperature> unitSystem = ImperialUnits.FAHRENHEIT;
public VenstarThermostatHandler(Thing thing) {
super(thing);
httpClient = new HttpClient(new SslContextFactory(true));
gson = new GsonBuilder().registerTypeAdapter(VenstarSystemState.class, new VenstarSystemStateSerializer())
.registerTypeAdapter(VenstarSystemMode.class, new VenstarSystemModeSerializer()).create();
log.trace("VenstarThermostatHandler for thing {}", getThing().getUID());
}
@SuppressWarnings("null") // compiler does not see conf.refresh == null check
@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
Collection<ConfigStatusMessage> status = new ArrayList<>();
VenstarThermostatConfiguration config = getConfigAs(VenstarThermostatConfiguration.class);
if (StringUtils.isBlank(config.username)) {
log.warn("username is empty");
status.add(ConfigStatusMessage.Builder.error(CONFIG_USERNAME).withMessageKeySuffix(EMPTY_INVALID)
.withArguments(CONFIG_USERNAME).build());
}
if (StringUtils.isBlank(config.password)) {
log.warn("password is empty");
status.add(ConfigStatusMessage.Builder.error(CONFIG_PASSWORD).withMessageKeySuffix(EMPTY_INVALID)
.withArguments(CONFIG_PASSWORD).build());
}
if (config.refresh == null || config.refresh < 10) {
log.warn("refresh is too small: {}", config.refresh);
status.add(ConfigStatusMessage.Builder.error(CONFIG_REFRESH).withMessageKeySuffix(REFRESH_INVALID)
.withArguments(CONFIG_REFRESH).build());
}
return status;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (getThing().getStatus() != ThingStatus.ONLINE) {
log.debug("Controller is NOT ONLINE and is not responding to commands");
return;
}
stopUpdateTasks();
if (command instanceof RefreshType) {
log.debug("Refresh command requested for {}", channelUID);
stateMap.clear();
startUpdatesTask(0);
} else {
stateMap.remove(channelUID.getAsString());
if (channelUID.getId().equals(CHANNEL_HEATING_SETPOINT)) {
QuantityType<Temperature> quantity = commandToQuantityType(command, unitSystem);
int value = quantityToRoundedTemperature(quantity, unitSystem).intValue();
log.debug("Setting heating setpoint to {}", value);
setHeatingSetpoint(value);
} else if (channelUID.getId().equals(CHANNEL_COOLING_SETPOINT)) {
QuantityType<Temperature> quantity = commandToQuantityType(command, unitSystem);
int value = quantityToRoundedTemperature(quantity, unitSystem).intValue();
log.debug("Setting cooling setpoint to {}", value);
setCoolingSetpoint(value);
} else if (channelUID.getId().equals(CHANNEL_SYSTEM_MODE)) {
VenstarSystemMode value;
if (command instanceof StringType) {
value = VenstarSystemMode.valueOf(((StringType) command).toString().toUpperCase());
} else {
value = VenstarSystemMode.fromInt(((DecimalType) command).intValue());
}
log.debug("Setting system mode to {}", value);
setSystemMode(value);
updateIfChanged(CHANNEL_SYSTEM_MODE_RAW, new StringType("" + value));
}
startUpdatesTask(UPDATE_AFTER_COMMAND_SECONDS);
}
}
@Override
public void dispose() {
stopUpdateTasks();
if (httpClient.isStarted()) {
try {
httpClient.stop();
} catch (Exception e) {
log.debug("Could not stop HttpClient", e);
}
}
}
@Override
public void initialize() {
connect();
}
protected void goOnline() {
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}
protected void goOffline(ThingStatusDetail detail, String reason) {
if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, detail, reason);
}
}
@SuppressWarnings("null") // compiler does not see new URL(url) as never being null
private void connect() {
stopUpdateTasks();
VenstarThermostatConfiguration config = getConfigAs(VenstarThermostatConfiguration.class);
try {
baseURL = new URL(config.url);
if (!httpClient.isStarted()) {
httpClient.start();
}
httpClient.getAuthenticationStore().clearAuthentications();
httpClient.getAuthenticationStore().clearAuthenticationResults();
httpClient.getAuthenticationStore().addAuthentication(
new DigestAuthentication(baseURL.toURI(), "thermostat", config.username, config.password));
refresh = config.refresh;
startUpdatesTask(0);
} catch (Exception e) {
log.debug("Could not conntect to URL {}", config.url, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
}
/**
* Start the poller after an initial delay
*
* @param initialDelay
*/
private synchronized void startUpdatesTask(int initialDelay) {
stopUpdateTasks();
updatesTask = scheduler.scheduleWithFixedDelay(this::updateData, initialDelay, refresh, TimeUnit.SECONDS);
}
/**
* Stop the poller
*/
@SuppressWarnings("null")
private void stopUpdateTasks() {
Future<?> localUpdatesTask = updatesTask;
if (isFutureValid(localUpdatesTask)) {
localUpdatesTask.cancel(false);
}
}
private boolean isFutureValid(@Nullable Future<?> future) {
return future != null && !future.isCancelled();
}
private State getTemperature() {
Optional<VenstarSensor> optSensor = sensorData.stream()
.filter(sensor -> sensor.getName().equalsIgnoreCase("Thermostat")).findAny();
if (optSensor.isPresent()) {
return new QuantityType<Temperature>(optSensor.get().getTemp(), unitSystem);
}
return UnDefType.UNDEF;
}
private State getHumidity() {
Optional<VenstarSensor> optSensor = sensorData.stream()
.filter(sensor -> sensor.getName().equalsIgnoreCase("Thermostat")).findAny();
if (optSensor.isPresent()) {
return new QuantityType<Dimensionless>(optSensor.get().getHum(), SmartHomeUnits.PERCENT);
}
return UnDefType.UNDEF;
}
private State getOutdoorTemperature() {
Optional<VenstarSensor> optSensor = sensorData.stream()
.filter(sensor -> sensor.getName().equalsIgnoreCase("Outdoor")).findAny();
if (optSensor.isPresent()) {
return new QuantityType<Temperature>(optSensor.get().getTemp(), unitSystem);
}
return UnDefType.UNDEF;
}
private void setCoolingSetpoint(int cool) {
int heat = getHeatingSetpoint().intValue();
VenstarSystemMode mode = getSystemMode();
updateThermostat(heat, cool, mode);
}
private void setSystemMode(VenstarSystemMode mode) {
int cool = getCoolingSetpoint().intValue();
int heat = getHeatingSetpoint().intValue();
updateThermostat(heat, cool, mode);
}
private void setHeatingSetpoint(int heat) {
int cool = getCoolingSetpoint().intValue();
VenstarSystemMode mode = getSystemMode();
updateThermostat(heat, cool, mode);
}
private QuantityType<Temperature> getCoolingSetpoint() {
return new QuantityType<Temperature>(infoData.getCooltemp(), unitSystem);
}
private QuantityType<Temperature> getHeatingSetpoint() {
return new QuantityType<Temperature>(infoData.getHeattemp(), unitSystem);
}
private VenstarSystemState getSystemState() {
return infoData.getState();
}
private VenstarSystemMode getSystemMode() {
return infoData.getMode();
}
private void updateThermostat(int heat, int cool, VenstarSystemMode mode) {
Map<String, String> params = new HashMap<>();
log.debug("Updating thermostat {} heat:{} cool {} mode: {}", getThing().getLabel(), heat, cool, mode);
if (heat > 0) {
params.put("heattemp", String.valueOf(heat));
}
if (cool > 0) {
params.put("cooltemp", String.valueOf(cool));
}
params.put("mode", "" + mode.mode());
try {
String result = postData("/control", params);
VenstarResponse res = gson.fromJson(result, VenstarResponse.class);
if (res.isSuccess()) {
log.debug("Updated thermostat");
// update our local copy until the next refresh occurs
infoData = new VenstarInfoData(cool, heat, infoData.getState(), mode);
} else {
log.debug("Failed to update thermostat: {}", res.getReason());
goOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Thermostat update failed: " + res.getReason());
}
} catch (VenstarCommunicationException | JsonSyntaxException e) {
log.debug("Unable to fetch info data", e);
goOffline(ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (VenstarAuthenticationException e) {
goOffline(ThingStatusDetail.CONFIGURATION_ERROR, "Authorization Failed");
}
}
private void updateData() {
try {
Future<?> localUpdatesTask = updatesTask;
String response = getData("/query/sensors");
if (!isFutureValid(localUpdatesTask)) {
return;
}
VenstarSensorData res = gson.fromJson(response, VenstarSensorData.class);
sensorData = res.getSensors();
updateIfChanged(CHANNEL_TEMPERATURE, getTemperature());
updateIfChanged(CHANNEL_EXTERNAL_TEMPERATURE, getOutdoorTemperature());
updateIfChanged(CHANNEL_HUMIDITY, getHumidity());
response = getData("/query/info");
if (!isFutureValid(localUpdatesTask)) {
return;
}
infoData = gson.fromJson(response, VenstarInfoData.class);
updateUnits(infoData);
updateIfChanged(CHANNEL_HEATING_SETPOINT, getHeatingSetpoint());
updateIfChanged(CHANNEL_COOLING_SETPOINT, getCoolingSetpoint());
updateIfChanged(CHANNEL_SYSTEM_STATE, new StringType(getSystemState().stateName()));
updateIfChanged(CHANNEL_SYSTEM_MODE, new StringType(getSystemMode().modeName()));
updateIfChanged(CHANNEL_SYSTEM_STATE_RAW, new DecimalType(getSystemState().state()));
updateIfChanged(CHANNEL_SYSTEM_MODE_RAW, new DecimalType(getSystemMode().mode()));
goOnline();
} catch (VenstarCommunicationException | JsonSyntaxException e) {
log.debug("Unable to fetch info data", e);
goOffline(ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (VenstarAuthenticationException e) {
goOffline(ThingStatusDetail.CONFIGURATION_ERROR, "Authorization Failed");
}
}
private void updateIfChanged(String channelID, State state) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelID);
State oldState = stateMap.put(channelUID.toString(), state);
if (!state.equals(oldState)) {
log.trace("updating channel {} with state {} (old state {})", channelUID, state, oldState);
updateState(channelUID, state);
}
}
private void updateUnits(VenstarInfoData infoData) {
int tempunits = infoData.getTempunits();
if (tempunits == 0) {
unitSystem = ImperialUnits.FAHRENHEIT;
} else if (tempunits == 1) {
unitSystem = SIUnits.CELSIUS;
} else {
log.warn("Thermostat returned unknown unit system type: {}", tempunits);
}
}
private String getData(String path) throws VenstarAuthenticationException, VenstarCommunicationException {
try {
URL getURL = new URL(baseURL, path);
Request request = httpClient.newRequest(getURL.toURI()).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return sendRequest(request);
} catch (MalformedURLException | URISyntaxException e) {
throw new VenstarCommunicationException(e);
}
}
private String postData(String path, Map<String, String> params)
throws VenstarAuthenticationException, VenstarCommunicationException {
try {
URL postURL = new URL(baseURL, path);
Request request = httpClient.newRequest(postURL.toURI()).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.method(HttpMethod.POST);
params.forEach(request::param);
return sendRequest(request);
} catch (MalformedURLException | URISyntaxException e) {
throw new VenstarCommunicationException(e);
}
}
private String sendRequest(Request request) throws VenstarAuthenticationException, VenstarCommunicationException {
log.trace("sendRequest: requesting {}", request.getURI());
try {
ContentResponse response = request.send();
log.trace("Response code {}", response.getStatus());
if (response.getStatus() == 401) {
throw new VenstarAuthenticationException();
}
if (response.getStatus() != 200) {
throw new VenstarCommunicationException(
"Error communitcating with thermostat. Error Code: " + response.getStatus());
}
String content = response.getContentAsString();
log.trace("sendRequest: response {}", content);
return content;
} catch (InterruptedException | TimeoutException | ExecutionException e) {
throw new VenstarCommunicationException(e);
}
}
@SuppressWarnings("unchecked")
protected <U extends Quantity<U>> QuantityType<U> commandToQuantityType(Command command, Unit<U> defaultUnit) {
if (command instanceof QuantityType) {
return (QuantityType<U>) command;
}
return new QuantityType<U>(new BigDecimal(command.toString()), defaultUnit);
}
protected DecimalType commandToDecimalType(Command command) {
if (command instanceof DecimalType) {
return (DecimalType) command;
}
return new DecimalType(new BigDecimal(command.toString()));
}
private BigDecimal quantityToRoundedTemperature(QuantityType<Temperature> quantity, Unit<Temperature> unit)
throws IllegalArgumentException {
QuantityType<Temperature> temparatureQuantity = quantity.toUnit(unit);
if (temparatureQuantity == null) {
return quantity.toBigDecimal();
}
BigDecimal value = temparatureQuantity.toBigDecimal();
BigDecimal increment = CELSIUS == unit ? new BigDecimal("0.5") : new BigDecimal("1");
BigDecimal divisor = value.divide(increment, 0, RoundingMode.HALF_UP);
return divisor.multiply(increment);
}
@SuppressWarnings("serial")
private class VenstarAuthenticationException extends Exception {
public VenstarAuthenticationException() {
super("Invalid Credentials");
}
}
@SuppressWarnings("serial")
private class VenstarCommunicationException extends Exception {
public VenstarCommunicationException(Exception e) {
super(e);
}
public VenstarCommunicationException(String message) {
super(message);
}
}
}

View File

@@ -0,0 +1,79 @@
/**
* 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.venstarthermostat.internal.model;
/**
* The {@link VenstarInfoData} represents a thermostat state from the REST API.
*
* @author William Welliver - Initial contribution
*/
public class VenstarInfoData {
double cooltemp;
double heattemp;
VenstarSystemState state;
VenstarSystemMode mode;
int tempunits;
public VenstarInfoData() {
super();
}
public VenstarInfoData(double cooltemp, double heattemp, VenstarSystemState state, VenstarSystemMode mode) {
super();
this.cooltemp = cooltemp;
this.heattemp = heattemp;
this.state = state;
this.mode = mode;
}
public double getCooltemp() {
return cooltemp;
}
public void setCooltemp(double cooltemp) {
this.cooltemp = cooltemp;
}
public double getHeattemp() {
return heattemp;
}
public void setHeattemp(double heattemp) {
this.heattemp = heattemp;
}
public VenstarSystemState getState() {
return state;
}
public void setState(VenstarSystemState state) {
this.state = state;
}
public VenstarSystemMode getMode() {
return mode;
}
public void setMode(VenstarSystemMode mode) {
this.mode = mode;
}
public int getTempunits() {
return tempunits;
}
public void setTempunits(int tempunits) {
this.tempunits = tempunits;
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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.venstarthermostat.internal.model;
/**
* The {@link VenstarResponse} represents a response message from the REST API.
*
* @author William Welliver - Initial contribution
*/
public class VenstarResponse {
private boolean success;
private String reason;
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.venstarthermostat.internal.model;
/**
* The {@link VenstarSensor} represents a sensor returned from the REST API.
*
* @author William Welliver - Initial contribution
*/
public class VenstarSensor {
String name;
float temp;
float hum;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getTemp() {
return temp;
}
public void setTemp(float temp) {
this.temp = temp;
}
public float getHum() {
return hum;
}
public void setHum(float hum) {
this.hum = hum;
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.venstarthermostat.internal.model;
import java.util.List;
/**
* The {@link VenstarSensorData} represents sensor data returned from the REST API.
*
* @author William Welliver - Initial contribution
*/
public class VenstarSensorData {
List<VenstarSensor> sensors;
public List<VenstarSensor> getSensors() {
return sensors;
}
public void setSensors(List<VenstarSensor> sensors) {
this.sensors = sensors;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.venstarthermostat.internal.model;
/**
* The {@link VenstarSystemMode} represents the value of the system mode returned
* from the REST API.
*
* @author William Welliver - Initial contribution
*/
public enum VenstarSystemMode {
OFF(0, "off", "Off"),
HEAT(1, "heat", "Heat"),
COOL(2, "cool", "Cool"),
AUTO(3, "auto", "Auto");
private int mode;
private String name;
private String friendlyName;
VenstarSystemMode(int mode, String name, String friendlyName) {
this.mode = mode;
this.name = name;
this.friendlyName = friendlyName;
}
public int mode() {
return mode;
}
public String modeName() {
return name;
}
public String friendlyName() {
return friendlyName;
}
public static VenstarSystemMode fromInt(int mode) {
for (VenstarSystemMode sm : values()) {
if (sm.mode == mode) {
return sm;
}
}
throw (new IllegalArgumentException("Invalid system mode " + mode));
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.venstarthermostat.internal.model;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
/**
* The {@link VenstarSystemModeSerializer} parses system mode values
* from the REST API JSON.
*
* @author William Welliver - Initial contribution
*/
public class VenstarSystemModeSerializer implements JsonDeserializer<VenstarSystemMode> {
@Override
public VenstarSystemMode deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
int key = element.getAsInt();
return VenstarSystemMode.fromInt(key);
}
}

View File

@@ -0,0 +1,59 @@
/**
* 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.venstarthermostat.internal.model;
/**
* The {@link VenstarSystemState} represents the value of the system state
* returneda from the REST API.
*
* @author William Welliver - Initial contribution
*/
public enum VenstarSystemState {
IDLE(0, "idle", "Idle"),
HEATING(1, "heating", "Heating"),
COOLING(2, "cooling", "Cooling"),
LOCKOUT(3, "lockout", "Lockout"),
ERROR(4, "error", "Error");
private int state;
private String name;
private String friendlyName;
VenstarSystemState(int state, String name, String friendlyName) {
this.state = state;
this.name = name;
this.friendlyName = friendlyName;
}
public int state() {
return state;
}
public String stateName() {
return name;
}
public String friendlyName() {
return friendlyName;
}
public static VenstarSystemState fromInt(int state) {
for (VenstarSystemState ss : values()) {
if (ss.state == state) {
return ss;
}
}
throw (new IllegalArgumentException("Invalid system state " + state));
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.venstarthermostat.internal.model;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
/**
* The {@link VenstarSystemStateSerializer} parses system state values
* from the REST API JSON.
*
* @author William Welliver - Initial contribution
*/
public class VenstarSystemStateSerializer implements JsonDeserializer<VenstarSystemState> {
@Override
public VenstarSystemState deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
int key = element.getAsInt();
return VenstarSystemState.fromInt(key);
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="venstarthermostat" 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>Venstar Thermostats</name>
<description>This is a binding for Venstar Thermostats.</description>
<author>William Welliver</author>
</binding:binding>

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="venstarthermostat"
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">
<!-- Sample Thing Type -->
<thing-type id="colorTouchThermostat">
<label>ColorTouch Thermostat</label>
<description>Venstar ColorTouch Thermostat</description>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
<channel id="outdoorTemperature" typeId="outdoorTemperature"/>
<channel id="systemMode" typeId="systemMode"/>
<channel id="systemModeRaw" typeId="systemModeRaw"/>
<channel id="heatingSetpoint" typeId="heatingSetpoint"/>
<channel id="coolingSetpoint" typeId="coolingSetpoint"/>
<channel id="systemState" typeId="systemState"/>
<channel id="systemStateRaw" typeId="systemStateRaw"/>
</channels>
<properties>
<property name="uuid"></property>
</properties>
<config-description>
<parameter name="username" type="text" required="true">
<label>Username</label>
<description></description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<context>password</context>
<description></description>
</parameter>
<parameter name="url" type="text" required="true">
<label>URL</label>
<description> URL of the thermostat in the format 'proto://host' (example: https://192.168.1.100)</description>
</parameter>
<parameter name="refresh" type="integer" min="1">
<label>Refresh interval</label>
<description>Specifies the refresh interval in seconds.</description>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="systemMode">
<item-type>String</item-type>
<label>System Mode</label>
<description>Current System Operating Mode</description>
<state readOnly="false">
<options>
<option value="off">Off</option>
<option value="heat">Heat</option>
<option value="cool">Cool</option>
<option value="auto">Auto</option>
</options>
</state>
</channel-type>
<channel-type id="systemModeRaw" advanced="true">
<item-type>Number</item-type>
<label>System Mode (Raw)</label>
<description>Current System Operating Mode, as an integer number</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="systemState">
<item-type>String</item-type>
<label>System State</label>
<description>Current System Operating State</description>
<state readOnly="true">
<options>
<option value="idle">Idle</option>
<option value="heating">Heating</option>
<option value="cooling">Cooling</option>
<option value="lockout">Lockout</option>
<option value="error">Error</option>
</options>
</state>
</channel-type>
<channel-type id="systemStateRaw" advanced="true">
<item-type>Number</item-type>
<label>System State (Raw)</label>
<description>Current System Operating State, as an integer</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="heatingSetpoint">
<item-type>Number:Temperature</item-type>
<label>Heating Setpoint</label>
<description>Heating Setpoint</description>
<category>Temperature</category>
<state readOnly="false" pattern="%.1f %unit%" min="40" max="80" step="1.0"/>
</channel-type>
<channel-type id="coolingSetpoint">
<item-type>Number:Temperature</item-type>
<label>Cooling Setpoint</label>
<description>Cooling Setpoint</description>
<category>Temperature</category>
<state readOnly="false" pattern="%.1f %unit%" min="60" max="95" step="1.0"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="outdoorTemperature">
<item-type>Number:Temperature</item-type>
<label>Outdoor Temperature</label>
<description>Outdoor Temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>Indoor Humidity</description>
<category>Humidity</category>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
</thing:thing-descriptions>