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="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="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="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.evohome</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,119 @@
# evohome Binding
This binding integrates the Honeywell evohome system.
It uses your Honeywell Total Connect Comfort account to access your locations and heating zones.
## Supported Things
The binding supports the following things:
* evohome Account
* evotouch control display
* Heating zones
### evohome Account
This thing functions as the bridge between all the other things.
It contains your credentials and connects to the Honeywell web API.
### evotouch
This thing represents the central display controller.
It is used to view and change the current system mode.
### Heating zone
The heating zone thing represents the evohome heating zone.
It displays the current temperature, the temperature set point and the status of the set point.
It also allows you to permanently override the current temperature set point as well as canceling any active overrides.
## Discovery
After setting up the evohome account, the evotouch and heating zones available to your account will be discovered after a manual scan.
## Thing Configuration
Thing configuration is optional, it is easier to use discovery which will automatically add all your zones and displays to the inbox, once the account Thing is online.
### Account
| Name | Required | Description |
|-----------------|----------|--------------------------------------------------------|
| username | yes | The username of your TCC account |
| password | yes | The password of your TCC account |
| refreshInterval | no | The amount of time in seconds between updates (0-3000) |
### Display &amp; Zone
| Name | Required | Description |
|------|----------|----------------------------------------------------------------------------------------|
| id | yes | The id which can be found by auto-discovery or the response data (using TRACE logging) |
| name | no | A friendly name for use in the UI |
## Channels
### Account
None
### Display
| Channel Type ID | Item Type | Description |
|-----------------|-----------|--------------------------------------------------------------------------------------------------------------------|
| Mode | String | Allows to view or set the system mode. Supported values are: Auto, AutoWithEco, Away, DayOff, HeatingOff, Custom |
### Zone
| Channel Type ID | Item Type | Description |
|-------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Temperature | Number | Allows for viewing the current actual temperature of the zone. |
| SetPointStatus | String | Allows for viewing the current set point mode of the zone. |
| SetPoint | Number | Allows for viewing and permanently overriding the temperature set point of the zone. Sending 0 cancels any active set point overrides. |
## Full Example
### demo.things
```
Bridge evohome:account:your_account_alias [ username="your_user_name", password="your_password" ]
{
display your_display_alias [ id="1234" ]
heatingzone your_zone_alias [ id="5678" ]
}
```
### demo.items
```
// evohome Display
String DemoMode { channel="evohome:display:your_account_alias:your_display_alias:SystemMode" }
// evohome Heatingzone
Number DemoZoneTemperature { channel="evohome:heatingzone:your_account_alias:your_zone_alias:Temperature" }
String DemoZoneSetPointStatus { channel="evohome:heatingzone:your_account_alias:your_zone_alias:SetPointStatus" }
Number DemoZoneSetPoint { channel="evohome:heatingzone:your_account_alias:your_zone_alias:SetPoint" }
```
### demo.sitemap
```
sitemap evohome label="evohome Menu"
{
Frame label="evohome display" {
Selection label="[%s]" item=DemoMode mappings=[
"Auto"="Normal",
"AutoWithEco"="Eco",
"Away"="Away",
"DayOff"="Day Off",
"HeatingOff"="Off",
"Custom"="Custom"
]
}
Frame label="evohome heating zone" {
Text label="Temperature" item=DemoZoneTemperature
Text label="Status" item=DemoZoneSetPointStatus
Setpoint label="Zone set point" item=DemoZoneSetPoint minValue=5 maxValue=35 step=0.5
}
}
```

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

View File

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

View File

@@ -0,0 +1,52 @@
/**
* 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.evohome.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link EvohomeBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jasper van Zuijlen - Initial contribution
* @author Neil Renaud - Heating Zones
*/
public class EvohomeBindingConstants {
private static final String BINDING_ID = "evohome";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_EVOHOME_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
public static final ThingTypeUID THING_TYPE_EVOHOME_DISPLAY = new ThingTypeUID(BINDING_ID, "display");
public static final ThingTypeUID THING_TYPE_EVOHOME_HEATING_ZONE = new ThingTypeUID(BINDING_ID, "heatingzone");
// List of all Channel IDs
public static final String DISPLAY_SYSTEM_MODE_CHANNEL = "SystemMode";
public static final String ZONE_TEMPERATURE_CHANNEL = "Temperature";
public static final String ZONE_SET_POINT_STATUS_CHANNEL = "SetPointStatus";
public static final String ZONE_SET_POINT_CHANNEL = "SetPoint";
// List of Discovery properties
public static final String PROPERTY_ID = "id";
public static final String PROPERTY_NAME = "name";
// List of all addressable things in OH = SUPPORTED_DEVICE_THING_TYPES_UIDS + the virtual bridge
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_EVOHOME_ACCOUNT, THING_TYPE_EVOHOME_DISPLAY, THING_TYPE_EVOHOME_HEATING_ZONE)
.collect(Collectors.toSet()));
}

View File

@@ -0,0 +1,107 @@
/**
* 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.evohome.internal;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.evohome.internal.discovery.EvohomeDiscoveryService;
import org.openhab.binding.evohome.internal.handler.EvohomeAccountBridgeHandler;
import org.openhab.binding.evohome.internal.handler.EvohomeHeatingZoneHandler;
import org.openhab.binding.evohome.internal.handler.EvohomeTemperatureControlSystemHandler;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Provides the thing factory for this binding
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.evohome")
@NonNullByDefault
public class EvohomeHandlerFactory extends BaseThingHandlerFactory {
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final HttpClient httpClient;
@Activate
public EvohomeHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return EvohomeBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(EvohomeBindingConstants.THING_TYPE_EVOHOME_ACCOUNT)) {
EvohomeAccountBridgeHandler bridge = new EvohomeAccountBridgeHandler((Bridge) thing, httpClient);
registerEvohomeDiscoveryService(bridge);
return bridge;
} else if (thingTypeUID.equals(EvohomeBindingConstants.THING_TYPE_EVOHOME_DISPLAY)) {
return new EvohomeTemperatureControlSystemHandler(thing);
} else if (thingTypeUID.equals(EvohomeBindingConstants.THING_TYPE_EVOHOME_HEATING_ZONE)) {
return new EvohomeHeatingZoneHandler(thing);
}
return null;
}
private void registerEvohomeDiscoveryService(EvohomeAccountBridgeHandler evohomeBridgeHandler) {
EvohomeDiscoveryService discoveryService = new EvohomeDiscoveryService(evohomeBridgeHandler);
this.discoveryServiceRegs.put(evohomeBridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
@Override
public ThingHandler registerHandler(Thing thing) {
return super.registerHandler(thing);
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof EvohomeAccountBridgeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.get(thingHandler.getThing().getUID());
if (serviceReg != null) {
EvohomeDiscoveryService service = (EvohomeDiscoveryService) bundleContext
.getService(serviceReg.getReference());
if (service != null) {
service.deactivate();
}
serviceReg.unregister();
discoveryServiceRegs.remove(thingHandler.getThing().getUID());
}
}
}
}

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.evohome.internal;
import java.util.concurrent.TimeoutException;
/**
* Provides an interface for a delegate that can throw a timeout
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public interface RunnableWithTimeout {
public abstract void run() throws TimeoutException;
}

View File

@@ -0,0 +1,202 @@
/**
* 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.evohome.internal.api;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Provides access to (an optionally OAUTH based) API. Makes sure that all the necessary headers are set.
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class ApiAccess {
private static final int REQUEST_TIMEOUT_SECONDS = 5;
private final Logger logger = LoggerFactory.getLogger(ApiAccess.class);
private final HttpClient httpClient;
private final Gson gson;
private Authentication authenticationData;
private String applicationId;
public ApiAccess(HttpClient httpClient) {
this.gson = new GsonBuilder().create();
this.httpClient = httpClient;
}
/**
* Sets the authentication details on the type
*
* @param authentication The authentication details to apply
*/
public void setAuthentication(Authentication authentication) {
authenticationData = authentication;
}
/**
* Gets the current authentication details of the type
*
* @return The current authentication details
*/
public Authentication getAuthentication() {
return authenticationData;
}
/**
* Sets the application id on the type
*
* @param applicationId The application id to apply
*/
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
/**
* Issues an HTTP request on the API's URL. Makes sure that the request is correctly formatted.
*
* @param method The HTTP method to use (POST, GET, ...)
* @param url The URL to query
* @param headers The optional additional headers to apply, can be null
* @param requestData The optional request data to use, can be null
* @param contentType The content type to use with the request data. Required when using requestData
* @return The result of the request or null
* @throws TimeoutException Thrown when a request times out
*/
public <TOut> TOut doRequest(HttpMethod method, String url, Map<String, String> headers, String requestData,
String contentType, Class<TOut> outClass) throws TimeoutException {
TOut retVal = null;
logger.debug("Requesting: [{}]", url);
try {
Request request = httpClient.newRequest(url).method(method);
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
request.header(header.getKey(), header.getValue());
}
}
if (requestData != null) {
request.content(new StringContentProvider(requestData), contentType);
}
ContentResponse response = request.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
logger.debug("Response: {}", response);
logger.debug("\n{}\n{}", response.getHeaders(), response.getContentAsString());
if ((response.getStatus() == HttpStatus.OK_200) || (response.getStatus() == HttpStatus.ACCEPTED_202)) {
String reply = response.getContentAsString();
if (outClass != null) {
retVal = new Gson().fromJson(reply, outClass);
}
}
} catch (InterruptedException | ExecutionException e) {
logger.debug("Error in handling request: ", e);
}
return retVal;
}
/**
* Issues an HTTP GET request on the API's URL, using an object that is serialized to JSON as input.
* Makes sure that the request is correctly formatted.*
*
* @param url The URL to query
* @param outClass The type of the requested result
* @return The result of the request or null
* @throws TimeoutException Thrown when a request times out
*/
public <TOut> TOut doAuthenticatedGet(String url, Class<TOut> outClass) throws TimeoutException {
return doAuthenticatedRequest(HttpMethod.GET, url, null, outClass);
}
/**
* Issues an HTTP request on the API's URL, using an object that is serialized to JSON as input.
* Makes sure that the request is correctly formatted.*
*
* @param url The URL to query
* @param requestContainer The object to use as JSON data for the request
* @throws TimeoutException Thrown when a request times out
*/
public void doAuthenticatedPut(String url, Object requestContainer) throws TimeoutException {
doAuthenticatedRequest(HttpMethod.PUT, url, requestContainer, null);
}
/**
* Issues an HTTP request on the API's URL, using an object that is serialized to JSON as input.
* Makes sure that the request is correctly formatted.*
*
* @param method The HTTP method to use (POST, GET, ...)
* @param url The URL to query
* @param headers The optional additional headers to apply, can be null
* @param requestContainer The object to use as JSON data for the request
* @param outClass The type of the requested result
* @return The result of the request or null
* @throws TimeoutException Thrown when a request times out
*/
private <TOut> TOut doRequest(HttpMethod method, String url, Map<String, String> headers, Object requestContainer,
Class<TOut> outClass) throws TimeoutException {
String json = null;
if (requestContainer != null) {
json = this.gson.toJson(requestContainer);
}
return doRequest(method, url, headers, json, "application/json", outClass);
}
/**
* Issues an HTTP request on the API's URL, using an object that is serialized to JSON as input and
* using the authentication applied to the type.
* Makes sure that the request is correctly formatted.*
*
* @param method The HTTP method to use (POST, GET, ...)
* @param url The URL to query
* @param requestContainer The object to use as JSON data for the request
* @param outClass The type of the requested result
* @return The result of the request or null
* @throws TimeoutException Thrown when a request times out
*/
private <TOut> TOut doAuthenticatedRequest(HttpMethod method, String url, Object requestContainer,
Class<TOut> outClass) throws TimeoutException {
Map<String, String> headers = null;
if (authenticationData != null) {
headers = new HashMap<>();
headers.put("Authorization", "Bearer " + authenticationData.getAccessToken());
headers.put("applicationId", applicationId);
headers.put("Accept",
"application/json, application/xml, text/json, text/x-json, text/javascript, text/xml");
}
return doRequest(method, url, headers, requestContainer, outClass);
}
}

View File

@@ -0,0 +1,260 @@
/**
* 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.evohome.internal.api;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.evohome.internal.api.models.v2.request.HeatSetPoint;
import org.openhab.binding.evohome.internal.api.models.v2.request.HeatSetPointBuilder;
import org.openhab.binding.evohome.internal.api.models.v2.request.Mode;
import org.openhab.binding.evohome.internal.api.models.v2.request.ModeBuilder;
import org.openhab.binding.evohome.internal.api.models.v2.response.Authentication;
import org.openhab.binding.evohome.internal.api.models.v2.response.Location;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Locations;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationsStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.UserAccount;
import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the evohome client V2 api
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeApiClient {
private static final String APPLICATION_ID = "b013aa26-9724-4dbd-8897-048b9aada249";
private static final String CLIENT_ID = "4a231089-d2b6-41bd-a5eb-16a0a422b999";
private static final String CLIENT_SECRET = "1a15cdb8-42de-407b-add0-059f92c530cb";
private final Logger logger = LoggerFactory.getLogger(EvohomeApiClient.class);
private final HttpClient httpClient;
private final EvohomeAccountConfiguration configuration;
private final ApiAccess apiAccess;
private Locations locations = new Locations();
private UserAccount useraccount;
private LocationsStatus locationsStatus;
/**
* Creates a new API client based on the V2 API interface
*
* @param configuration The configuration of the account to use
* @throws Exception
*/
public EvohomeApiClient(EvohomeAccountConfiguration configuration, HttpClient httpClient) throws Exception {
this.configuration = configuration;
this.httpClient = httpClient;
try {
httpClient.start();
} catch (Exception e) {
logger.error("Could not start http client", e);
throw new EvohomeApiClientException("Could not start http client", e);
}
apiAccess = new ApiAccess(httpClient);
apiAccess.setApplicationId(APPLICATION_ID);
}
/**
* Closes the current connection to the API
*/
public void close() {
apiAccess.setAuthentication(null);
useraccount = null;
locations = null;
locationsStatus = null;
if (httpClient.isStarted()) {
try {
httpClient.stop();
} catch (Exception e) {
logger.debug("Could not stop http client.", e);
}
}
}
public boolean login() {
boolean success = authenticateWithUsername();
// If the authentication succeeded, gather the basic intel as well
if (success) {
try {
useraccount = requestUserAccount();
locations = requestLocations();
} catch (TimeoutException e) {
logger.warn("Timeout while retrieving user and location information. Failing loging.");
success = false;
}
} else {
apiAccess.setAuthentication(null);
logger.debug("Authorization failed");
}
return success;
}
public void logout() {
close();
}
public void update() {
updateAuthentication();
try {
locationsStatus = requestLocationsStatus();
} catch (TimeoutException e) {
logger.info("Timeout on update");
}
}
public Locations getInstallationInfo() {
return locations;
}
public LocationsStatus getInstallationStatus() {
return locationsStatus;
}
public void setTcsMode(String tcsId, String mode) throws TimeoutException {
String url = String.format(EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_MODE, tcsId);
Mode modeCommand = new ModeBuilder().setMode(mode).build();
apiAccess.doAuthenticatedPut(url, modeCommand);
}
public void setHeatingZoneOverride(String zoneId, double setPoint) throws TimeoutException {
HeatSetPoint setPointCommand = new HeatSetPointBuilder().setSetPoint(setPoint).build();
setHeatingZoneOverride(zoneId, setPointCommand);
}
public void cancelHeatingZoneOverride(String zoneId) throws TimeoutException {
HeatSetPoint setPointCommand = new HeatSetPointBuilder().setCancelSetPoint().build();
setHeatingZoneOverride(zoneId, setPointCommand);
}
private void setHeatingZoneOverride(String zoneId, HeatSetPoint heatSetPoint) throws TimeoutException {
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_HEAT_SETPOINT;
url = String.format(url, zoneId);
apiAccess.doAuthenticatedPut(url, heatSetPoint);
}
private UserAccount requestUserAccount() throws TimeoutException {
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_ACCOUNT;
return apiAccess.doAuthenticatedGet(url, UserAccount.class);
}
private Locations requestLocations() throws TimeoutException {
Locations locations = new Locations();
if (useraccount != null) {
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_INSTALLATION_INFO;
url = String.format(url, useraccount.getUserId());
locations = apiAccess.doAuthenticatedGet(url, Locations.class);
}
return locations;
}
private LocationsStatus requestLocationsStatus() throws TimeoutException {
LocationsStatus locationsStatus = new LocationsStatus();
if (locations != null) {
for (Location location : locations) {
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_LOCATION_STATUS;
url = String.format(url, location.getLocationInfo().getLocationId());
LocationStatus status = apiAccess.doAuthenticatedGet(url, LocationStatus.class);
locationsStatus.add(status);
}
}
return locationsStatus;
}
private boolean authenticate(String credentials, String grantType) {
String data = credentials + "&" + "Host=rs.alarmnet.com%2F&" + "Pragma=no-cache&"
+ "Cache-Control=no-store+no-cache&"
+ "scope=EMEA-V1-Basic+EMEA-V1-Anonymous+EMEA-V1-Get-Current-User-Account&" + "grant_type=" + grantType
+ "&" + "Content-Type=application%2Fx-www-form-urlencoded%3B+charset%3Dutf-8&"
+ "Connection=Keep-Alive";
Map<String, String> headers = new HashMap<>();
String basicAuth = Base64.getEncoder().encodeToString((CLIENT_ID + ":" + CLIENT_SECRET).getBytes());
headers.put("Authorization", "Basic " + basicAuth);
headers.put("Accept", "application/json, application/xml, text/json, text/x-json, text/javascript, text/xml");
Authentication authentication;
try {
authentication = apiAccess.doRequest(HttpMethod.POST, EvohomeApiConstants.URL_V2_AUTH, headers, data,
"application/x-www-form-urlencoded", Authentication.class);
} catch (TimeoutException e) {
// A timeout is not a successful login as well
authentication = null;
}
apiAccess.setAuthentication(authentication);
if (authentication != null) {
authentication.setSystemTime(System.currentTimeMillis() / 1000);
}
return (authentication != null);
}
private boolean authenticateWithUsername() {
boolean result = false;
try {
String credentials = "Username=" + URLEncoder.encode(configuration.username, "UTF-8") + "&" + "Password="
+ URLEncoder.encode(configuration.password, "UTF-8");
result = authenticate(credentials, "password");
} catch (UnsupportedEncodingException e) {
logger.error("Credential conversion failed", e);
}
return result;
}
private boolean authenticateWithToken(String accessToken) {
String credentials = "refresh_token=" + accessToken;
return authenticate(credentials, "refresh_token");
}
private void updateAuthentication() {
Authentication authentication = apiAccess.getAuthentication();
if (authentication == null) {
authenticateWithUsername();
} else {
// Compare current time to the expiration time minus four intervals for slack
long currentTime = System.currentTimeMillis() / 1000;
long expiration = authentication.getSystemTime() + authentication.getExpiresIn();
expiration -= 4 * configuration.refreshInterval;
// Update the access token just before it expires, but fall back to username and password
// when it fails (i.e. refresh token had been invalidated)
if (currentTime > expiration) {
authenticateWithToken(authentication.getRefreshToken());
if (apiAccess.getAuthentication() == null) {
authenticateWithUsername();
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.evohome.internal.api;
/**
* Exception for errors from the API Client.
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeApiClientException extends Exception {
public EvohomeApiClientException() {
}
public EvohomeApiClientException(String message) {
super(message);
}
public EvohomeApiClientException(String message, Throwable cause) {
super(message, cause);
}
}

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.evohome.internal.api;
/**
* List of evohome API constants
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeApiConstants {
public static final String URL_V2_AUTH = "https://tccna.honeywell.com/Auth/OAuth/Token";
public static final String URL_V2_BASE = "https://tccna.honeywell.com/WebAPI/emea/api/v1/";
public static final String URL_V2_ACCOUNT = "userAccount";
public static final String URL_V2_INSTALLATION_INFO = "location/installationInfo?userId=%s&includeTemperatureControlSystems=True";// {userId}
public static final String URL_V2_LOCATION = "location/%s/installationInfo?includeTemperatureControlSystems=True"; // {locationId}
public static final String URL_V2_GATEWAY = "gateway";
public static final String URL_V2_HOT_WATER = "domesticHotWater/%s/state"; // {hardwareId}
public static final String URL_V2_SCHEDULE = "%s/%s/schedule"; // {zone_type}, {zoneId}
public static final String URL_V2_HEAT_SETPOINT = "temperatureZone/%s/heatSetpoint"; // {zoneId}
public static final String URL_V2_LOCATION_STATUS = "location/%s/status?includeTemperatureControlSystems=True"; // {locationId}
public static final String URL_V2_MODE = "temperatureControlSystem/%s/mode"; // {systemId}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.request;
import com.google.gson.annotations.SerializedName;
/**
* Request model for the mode
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class HeatSetPoint {
/**
* Constructs an override reset
*/
HeatSetPoint() {
heatSetpointValue = 0.0;
setpointMode = "FollowSchedule";
timeUntil = null;
}
/**
* Constructs a permanent override with the given temperature
*
* @param setPoint The target temperature to set the set point to
*/
HeatSetPoint(double setPoint) {
// Make sure that the value is rounded toward the nearest 0.5
heatSetpointValue = Math.round(setPoint * 2) / 2.0;
setpointMode = "PermanentOverride";
timeUntil = null;
}
@SerializedName("heatSetpointValue")
private double heatSetpointValue;
@SerializedName("setpointMode")
private String setpointMode;
@SerializedName("timeUntil")
private String timeUntil;
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.request;
/**
* Builder for heat set point API requests
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class HeatSetPointBuilder implements RequestBuilder<HeatSetPoint> {
private double setPoint;
private boolean hasSetPoint;
private boolean cancelSetPoint;
/**
* Creates a new heat set point command
*
* @return A heat set point command or null when the configuration is invalid
*
*/
@Override
public HeatSetPoint build() {
if (cancelSetPoint) {
return new HeatSetPoint();
}
if (hasSetPoint) {
return new HeatSetPoint(setPoint);
}
return null;
}
public HeatSetPointBuilder setSetPoint(double setPoint) {
this.hasSetPoint = true;
this.setPoint = setPoint;
return this;
}
public HeatSetPointBuilder setCancelSetPoint() {
cancelSetPoint = true;
return this;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.evohome.internal.api.models.v2.request;
import com.google.gson.annotations.SerializedName;
/**
* Request model for the mode
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Mode {
Mode(String mode) {
systemMode = mode;
timeUntil = null;
permanent = true;
}
Mode(String mode, int day, int month, int year) {
systemMode = mode;
timeUntil = String.format("%s-%s-%sT00:00:00Z", year, month, day);
permanent = false;
}
@SerializedName("systemMode")
private String systemMode;
@SerializedName("timeUntil")
private String timeUntil;
@SerializedName("permanent")
private boolean permanent;
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.request;
/**
* Builder for mode API requests
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class ModeBuilder extends TimedRequestBuilder<Mode> {
private String mode;
private boolean hasSetMode;
/**
* Creates a new mode command
*
* @return A mode command or null when the configuration is invalid
*
*/
@Override
public Mode build() {
if (hasSetMode) {
if (useEndTime()) {
return new Mode(mode, getYear(), getMonth(), getDay());
} else {
return new Mode(mode);
}
}
return null;
}
public ModeBuilder setMode(String mode) {
this.hasSetMode = true;
this.mode = mode;
return this;
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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.evohome.internal.api.models.v2.request;
/**
* Builder for API requests
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public interface RequestBuilder<T> {
public T build();
}

View File

@@ -0,0 +1,50 @@
/**
* 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.evohome.internal.api.models.v2.request;
/**
* Builder for timed API requests
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public abstract class TimedRequestBuilder<T> implements RequestBuilder<T> {
private boolean useEndTime;
private int year;
private int month;
private int day;
public RequestBuilder<T> withEndTime(int year, int month, int day) {
this.useEndTime = true;
this.year = year;
this.month = month;
this.day = day;
return this;
}
protected boolean useEndTime() {
return useEndTime;
}
protected int getYear() {
return year;
}
protected int getMonth() {
return month;
}
protected int getDay() {
return day;
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the active fault
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class ActiveFault {
@SerializedName("faultType")
private String faultType;
@SerializedName("since")
private String since;
public String getFaultType() {
return faultType;
}
}

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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the authentication
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Authentication {
@SerializedName("access_token")
private String accessToken;
@SerializedName("token_type")
private String tokenType;
@SerializedName("expires_in")
private int expiresIn;
@SerializedName("refresh_token")
private String refreshToken;
@SerializedName("scope")
private String scope;
/** Convenience variable for current system time in seconds */
private long systemTime;
public String getAccessToken() {
return accessToken;
}
public int getExpiresIn() {
return expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setSystemTime(long systemTime) {
this.systemTime = systemTime;
}
public long getSystemTime() {
return systemTime;
}
}

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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the gateway
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Gateway {
@SerializedName("gatewayInfo")
private GatewayInfo gatewayInfo;
@SerializedName("temperatureControlSystems")
private List<TemperatureControlSystem> temperatureControlSystems;
public GatewayInfo getGatewayInfo() {
return gatewayInfo;
}
public List<TemperatureControlSystem> getTemperatureControlSystems() {
return temperatureControlSystems;
}
}

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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the gateway info
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class GatewayInfo {
@SerializedName("gatewayId")
private String gatewayId;
@SerializedName("mac")
private String macAddress;
@SerializedName("crc")
private String crc;
@SerializedName("isWiFi")
private boolean isWifi;
public String getGatewayId() {
return gatewayId;
}
}

View File

@@ -0,0 +1,47 @@
/**
* 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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the gateway status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class GatewayStatus {
@SerializedName("gatewayId")
private String gatewayId;
@SerializedName("temperatureControlSystems")
private List<TemperatureControlSystemStatus> temperatureControlSystems;
@SerializedName("activeFaults")
private List<ActiveFault> activeFaults;
public List<TemperatureControlSystemStatus> getTemperatureControlSystems() {
return temperatureControlSystems;
}
public boolean hasActiveFaults() {
return !activeFaults.isEmpty();
}
public ActiveFault getActiveFault(int index) {
return activeFaults.get(index);
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the heat set point capabilities
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class HeatSetpointCapabilities {
@SerializedName("maxHeatSetpoint")
private double maxHeatSetpoint;
@SerializedName("minHeatSetpoint")
private double minHeatSetpoint;
@SerializedName("valueResolution")
private double valueResolution;
@SerializedName("allowedSetpointModes")
private List<String> allowedSetpointModes;
@SerializedName("maxDuration")
private String maxDuration;
@SerializedName("timingResolution")
private String timingResolution;
}

View File

@@ -0,0 +1,38 @@
/**
* 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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the heat setpoint status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class HeatSetpointStatus {
@SerializedName("targetHeatTemperature")
private double targetTemperature;
@SerializedName("setpointMode")
private String setpointMode;
public double getTargetTemperature() {
return targetTemperature;
}
public String getSetpointMode() {
return setpointMode;
}
}

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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the location
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Location {
@SerializedName("locationInfo")
private LocationInfo locationInfo;
@SerializedName("gateways")
private List<Gateway> gateways;
public LocationInfo getLocationInfo() {
return locationInfo;
}
public List<Gateway> getGateways() {
return gateways;
}
}

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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the location info
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class LocationInfo {
@SerializedName("locationId")
private String locationId;
@SerializedName("name")
private String name;
@SerializedName("streetAddress")
private String streetAddress;
@SerializedName("city")
private String city;
@SerializedName("country")
private String country;
@SerializedName("postcode")
private String postcode;
@SerializedName("locationType")
private String locationType;
@SerializedName("useDaylightSaveSwitching")
private boolean useDaylightSaveSwitching;
@SerializedName("timeZone")
private TimeZone timeZone;
@SerializedName("locationOwner")
private LocationOwner locationOwner;
public String getLocationId() {
return locationId;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the location owner
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class LocationOwner {
@SerializedName("userId")
private int userId;
@SerializedName("username")
private String username;
@SerializedName("firstname")
private String firstName;
@SerializedName("lastname")
private String lastName;
}

View File

@@ -0,0 +1,42 @@
/**
* 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.evohome.internal.api.models.v2.response;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the location status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class LocationStatus {
@SerializedName("locationId")
private String locationId;
@SerializedName("gateways")
private List<GatewayStatus> gateways;
public LocationStatus() {
locationId = "";
gateways = new ArrayList<>();
}
public List<GatewayStatus> getGateways() {
return gateways;
}
}

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.evohome.internal.api.models.v2.response;
import java.util.ArrayList;
/**
* Alias for a list of locations
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Locations extends ArrayList<Location> {
}

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.evohome.internal.api.models.v2.response;
import java.util.ArrayList;
/**
* Alias for a list of location statuses
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class LocationsStatus extends ArrayList<LocationStatus> {
}

View File

@@ -0,0 +1,42 @@
/**
* 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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the mode
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Mode {
@SerializedName("systemMode")
private String systemMode;
@SerializedName("canBePermanent")
private boolean canBePermanent;
@SerializedName("canBeTemporary")
private boolean canBeTemporary;
@SerializedName("timingMode")
private String timingMode;
@SerializedName("maxDuration")
private String maxDuration;
@SerializedName("timingResolution")
private String timingResolution;
}

View File

@@ -0,0 +1,36 @@
/**
* 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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the schedule capabilities
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class ScheduleCapabilities {
@SerializedName("maxSwitchpointsPerDay")
private int maxSwitchpointsPerDay;
@SerializedName("minSwitchpointsPerDay")
private int minSwitchpointsPerDay;
@SerializedName("setpointValueResolution")
private double setpointValueResolution;
@SerializedName("timingResolution")
private String timingResolution;
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the system mode status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class SystemModeStatus {
@SerializedName("mode")
private String mode;
@SerializedName("isPermanent")
private boolean isPermanent;
public String getMode() {
return mode;
}
}

View File

@@ -0,0 +1,46 @@
/**
* 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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the temperature control system
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class TemperatureControlSystem {
@SerializedName("systemId")
private String systemId;
@SerializedName("modelType")
private String modelType;
@SerializedName("zones")
private List<Zone> zones;
@SerializedName("allowedSystemModes")
private List<Mode> allowedSystemModes;
public String getSystemId() {
return systemId;
}
public List<Zone> getZones() {
return zones;
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the temperature control system status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class TemperatureControlSystemStatus {
@SerializedName("systemId")
private String systemId;
@SerializedName("systemModeStatus")
private SystemModeStatus mode;
@SerializedName("zones")
private List<ZoneStatus> zones;
@SerializedName("activeFaults")
private List<ActiveFault> activeFaults;
public String getSystemId() {
return systemId;
}
public SystemModeStatus getMode() {
return mode;
}
public List<ZoneStatus> getZones() {
return zones;
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the temperature status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class TemperatureStatus {
@SerializedName("temperature")
private double temperature;
@SerializedName("isAvailable")
private boolean isAvailable;
public double getTemperature() {
return temperature;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the time zone
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class TimeZone {
@SerializedName("timeZoneId")
private String timeZoneId;
@SerializedName("displayName")
private String displayName;
@SerializedName("offsetMinutes")
private int offsetMinutes;
@SerializedName("currentOffsetMinutes")
private int currentOffsetMinutes;
@SerializedName("supportsDaylightSaving")
private boolean supportsDaylightSaving;
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the user account
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class UserAccount {
@SerializedName("userId")
private String userId;
@SerializedName("username")
private String userName;
@SerializedName("firstname")
private String firstName;
@SerializedName("lastname")
private String lastName;
@SerializedName("streetAddress")
private String streetAddress;
@SerializedName("city")
private String city;
@SerializedName("postcode")
private String postCode;
@SerializedName("country")
private String country;
@SerializedName("language")
private String language;
public String getUserId() {
return userId;
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.evohome.internal.api.models.v2.response;
import com.google.gson.annotations.SerializedName;
/**
* Response model for the zone
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class Zone {
@SerializedName("zoneId")
private String zoneId;
@SerializedName("modelType")
private String modelType;
@SerializedName("name")
private String name;
@SerializedName("zoneType")
private String zoneType;
@SerializedName("heatSetpointCapabilities")
private HeatSetpointCapabilities heatSetpointCapabilities;
@SerializedName("scheduleCapabilities")
private ScheduleCapabilities scheduleCapabilities;
public String getZoneId() {
return zoneId;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.evohome.internal.api.models.v2.response;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* Response model for zone status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class ZoneStatus {
@SerializedName("zoneId")
private String zoneId;
@SerializedName("name")
private String name;
@SerializedName("temperatureStatus")
private TemperatureStatus temperature;
@SerializedName("setpointStatus")
private HeatSetpointStatus heatSetpoint;
@SerializedName("activeFaults")
private List<ActiveFault> activeFaults;
public String getZoneId() {
return zoneId;
}
public TemperatureStatus getTemperature() {
return temperature;
}
public HeatSetpointStatus getHeatSetpoint() {
return heatSetpoint;
}
public boolean hasActiveFaults() {
return !activeFaults.isEmpty();
}
public ActiveFault getActiveFault(int index) {
return activeFaults.get(index);
}
}

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.evohome.internal.configuration;
/**
* Contains the configuration of the binding.
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeAccountConfiguration {
public String username;
public String password;
public String applicationId;
public int refreshInterval;
}

View File

@@ -0,0 +1,24 @@
/**
* 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.evohome.internal.configuration;
/**
* Contains the configuration of the binding.
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeTemperatureControlSystemConfiguration {
public String id;
public String name;
}

View File

@@ -0,0 +1,24 @@
/**
* 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.evohome.internal.configuration;
/**
* Contains the common configuration definition of an evohome Thing
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeThingConfiguration {
public String id;
public String name;
}

View File

@@ -0,0 +1,133 @@
/**
* 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.evohome.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.evohome.internal.EvohomeBindingConstants;
import org.openhab.binding.evohome.internal.api.models.v2.response.Gateway;
import org.openhab.binding.evohome.internal.api.models.v2.response.Location;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystem;
import org.openhab.binding.evohome.internal.api.models.v2.response.Zone;
import org.openhab.binding.evohome.internal.handler.AccountStatusListener;
import org.openhab.binding.evohome.internal.handler.EvohomeAccountBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EvohomeDiscoveryService} class is capable of discovering the available data from Evohome
*
* @author Neil Renaud - Initial contribution
* @author Jasper van Zuijlen - Background discovery
*
*/
public class EvohomeDiscoveryService extends AbstractDiscoveryService implements AccountStatusListener {
private final Logger logger = LoggerFactory.getLogger(EvohomeDiscoveryService.class);
private static final int TIMEOUT = 5;
private EvohomeAccountBridgeHandler bridge;
private ThingUID bridgeUID;
public EvohomeDiscoveryService(EvohomeAccountBridgeHandler bridge) {
super(EvohomeBindingConstants.SUPPORTED_THING_TYPES_UIDS, TIMEOUT);
this.bridge = bridge;
this.bridgeUID = this.bridge.getThing().getUID();
this.bridge.addAccountStatusListener(this);
}
@Override
protected void startScan() {
discoverDevices();
}
@Override
protected void startBackgroundDiscovery() {
discoverDevices();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
@Override
public void accountStatusChanged(ThingStatus status) {
if (status == ThingStatus.ONLINE) {
discoverDevices();
}
}
@Override
public void deactivate() {
super.deactivate();
bridge.removeAccountStatusListener(this);
}
private void discoverDevices() {
if (bridge.getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("Evohome Gateway not online, scanning postponed");
return;
}
for (Location location : bridge.getEvohomeConfig()) {
for (Gateway gateway : location.getGateways()) {
for (TemperatureControlSystem tcs : gateway.getTemperatureControlSystems()) {
addDisplayDiscoveryResult(location, tcs);
for (Zone zone : tcs.getZones()) {
addZoneDiscoveryResult(location, zone);
}
}
}
}
stopScan();
}
private void addDisplayDiscoveryResult(Location location, TemperatureControlSystem tcs) {
String id = tcs.getSystemId();
String name = location.getLocationInfo().getName();
ThingUID thingUID = new ThingUID(EvohomeBindingConstants.THING_TYPE_EVOHOME_DISPLAY, bridgeUID, id);
Map<String, Object> properties = new HashMap<>(2);
properties.put(EvohomeBindingConstants.PROPERTY_ID, id);
properties.put(EvohomeBindingConstants.PROPERTY_NAME, name);
addDiscoveredThing(thingUID, properties, name);
}
private void addZoneDiscoveryResult(Location location, Zone zone) {
String id = zone.getZoneId();
String name = zone.getName() + " (" + location.getLocationInfo().getName() + ")";
ThingUID thingUID = new ThingUID(EvohomeBindingConstants.THING_TYPE_EVOHOME_HEATING_ZONE, bridgeUID, id);
Map<String, Object> properties = new HashMap<>(2);
properties.put(EvohomeBindingConstants.PROPERTY_ID, id);
properties.put(EvohomeBindingConstants.PROPERTY_NAME, name);
addDiscoveredThing(thingUID, properties, name);
}
private void addDiscoveredThing(ThingUID thingUID, Map<String, Object> properties, String displayLabel) {
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUID).withLabel(displayLabel).build();
thingDiscovered(discoveryResult);
}
}

View File

@@ -0,0 +1,31 @@
/**
* 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.evohome.internal.handler;
import org.openhab.core.thing.ThingStatus;
/**
* Interface for a listener of the evohome account status
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public interface AccountStatusListener {
/**
* Notifies the client that the status has changed.
*
* @param status The new status of the account thing
*/
public void accountStatusChanged(ThingStatus status);
}

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.evohome.internal.handler;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.evohome.internal.api.models.v2.response.Locations;
import org.openhab.binding.evohome.internal.configuration.EvohomeThingConfiguration;
import org.openhab.core.thing.Bridge;
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;
/**
* Base class for an evohome handler
*
* @author Jasper van Zuijlen - Initial contribution
*/
public abstract class BaseEvohomeHandler extends BaseThingHandler {
private EvohomeThingConfiguration configuration;
public BaseEvohomeHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
configuration = getConfigAs(EvohomeThingConfiguration.class);
checkConfig();
}
@Override
public void dispose() {
configuration = null;
}
public String getId() {
if (configuration != null) {
return configuration.id;
}
return null;
}
/**
* Returns the configuration of the Thing
*
* @return The parsed configuration or null
*/
protected EvohomeThingConfiguration getEvohomeThingConfig() {
return configuration;
}
/**
* Retrieves the bridge
*
* @return The evohome brdige
*/
protected EvohomeAccountBridgeHandler getEvohomeBridge() {
Bridge bridge = getBridge();
if (bridge != null) {
return (EvohomeAccountBridgeHandler) bridge.getHandler();
}
return null;
}
/**
* Retrieves the evohome configuration from the bridge
*
* @return The current evohome configuration
*/
protected Locations getEvohomeConfig() {
EvohomeAccountBridgeHandler bridge = getEvohomeBridge();
if (bridge != null) {
return bridge.getEvohomeConfig();
}
return null;
}
/**
* Retrieves the evohome configuration from the bridge
*
* @return The current evohome configuration
*/
protected void requestUpdate() {
Bridge bridge = getBridge();
if (bridge != null) {
((EvohomeAccountBridgeHandler) bridge).getEvohomeConfig();
}
}
/**
* Updates the status of the evohome thing when it changes
*
* @param newStatus The new status to update to
*/
protected void updateEvohomeThingStatus(ThingStatus newStatus) {
updateEvohomeThingStatus(newStatus, ThingStatusDetail.NONE, null);
}
/**
* Updates the status of the evohome thing when it changes
*
* @param newStatus The new status to update to
* @param detail The status detail value
* @param message The message to show with the status
*/
protected void updateEvohomeThingStatus(ThingStatus newStatus, ThingStatusDetail detail, String message) {
// Prevent spamming the log file
if (!newStatus.equals(getThing().getStatus())) {
updateStatus(newStatus, detail, message);
}
}
/**
* Checks the configuration for validity, result is reflected in the status of the Thing
*
* @param configuration The configuration to check
*/
private void checkConfig() {
if (configuration == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Configuration is missing or corrupted");
} else if (StringUtils.isEmpty(configuration.id)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Id not configured");
}
}
}

View File

@@ -0,0 +1,278 @@
/**
* 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.evohome.internal.handler;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.evohome.internal.RunnableWithTimeout;
import org.openhab.binding.evohome.internal.api.EvohomeApiClient;
import org.openhab.binding.evohome.internal.api.models.v2.response.Gateway;
import org.openhab.binding.evohome.internal.api.models.v2.response.GatewayStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Location;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Locations;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationsStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystem;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystemStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Zone;
import org.openhab.binding.evohome.internal.api.models.v2.response.ZoneStatus;
import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides the bridge for this binding. Controls the authentication sequence.
* Manages the scheduler for getting updates from the API and updates the Things it contains.
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(EvohomeAccountBridgeHandler.class);
private final HttpClient httpClient;
private EvohomeAccountConfiguration configuration;
private EvohomeApiClient apiClient;
private List<AccountStatusListener> listeners = new CopyOnWriteArrayList<>();
protected ScheduledFuture<?> refreshTask;
public EvohomeAccountBridgeHandler(Bridge thing, HttpClient httpClient) {
super(thing);
this.httpClient = httpClient;
}
@Override
public void initialize() {
configuration = getConfigAs(EvohomeAccountConfiguration.class);
if (checkConfig()) {
try {
apiClient = new EvohomeApiClient(configuration, this.httpClient);
} catch (Exception e) {
logger.error("Could not start API client", e);
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not create evohome API client");
}
if (apiClient != null) {
// Initialization can take a while, so kick it off on a separate thread
scheduler.schedule(() -> {
if (apiClient.login()) {
if (checkInstallationInfoHasDuplicateIds(apiClient.getInstallationInfo())) {
startRefreshTask();
} else {
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"System Information Sanity Check failed");
}
} else {
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Authentication failed");
}
}, 0, TimeUnit.SECONDS);
}
}
}
@Override
public void dispose() {
disposeRefreshTask();
disposeApiClient();
listeners.clear();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
public Locations getEvohomeConfig() {
return apiClient.getInstallationInfo();
}
public LocationsStatus getEvohomeStatus() {
return apiClient.getInstallationStatus();
}
public void setTcsMode(String tcsId, String mode) {
tryToCall(() -> apiClient.setTcsMode(tcsId, mode));
}
public void setPermanentSetPoint(String zoneId, double doubleValue) {
tryToCall(() -> apiClient.setHeatingZoneOverride(zoneId, doubleValue));
}
public void cancelSetPointOverride(String zoneId) {
tryToCall(() -> apiClient.cancelHeatingZoneOverride(zoneId));
}
public void addAccountStatusListener(AccountStatusListener listener) {
listeners.add(listener);
listener.accountStatusChanged(getThing().getStatus());
}
public void removeAccountStatusListener(AccountStatusListener listener) {
listeners.remove(listener);
}
private void tryToCall(RunnableWithTimeout action) {
try {
action.run();
} catch (TimeoutException e) {
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Timeout on executing request");
}
}
private boolean checkInstallationInfoHasDuplicateIds(Locations locations) {
boolean result = true;
// Make sure that there are no duplicate IDs
Set<String> ids = new HashSet<>();
for (Location location : locations) {
result &= ids.add(location.getLocationInfo().getLocationId());
for (Gateway gateway : location.getGateways()) {
result &= ids.add(gateway.getGatewayInfo().getGatewayId());
for (TemperatureControlSystem tcs : gateway.getTemperatureControlSystems()) {
result &= ids.add(tcs.getSystemId());
for (Zone zone : tcs.getZones()) {
result &= ids.add(zone.getZoneId());
}
}
}
}
return result;
}
private void disposeApiClient() {
if (apiClient != null) {
apiClient.logout();
}
apiClient = null;
}
private void disposeRefreshTask() {
if (refreshTask != null) {
refreshTask.cancel(true);
}
}
private boolean checkConfig() {
String errorMessage = "";
if (configuration == null) {
errorMessage = "Configuration is missing or corrupted";
} else if (StringUtils.isEmpty(configuration.username)) {
errorMessage = "Username not configured";
} else if (StringUtils.isEmpty(configuration.password)) {
errorMessage = "Password not configured";
} else {
return true;
}
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
return false;
}
private void startRefreshTask() {
disposeRefreshTask();
refreshTask = scheduler.scheduleWithFixedDelay(this::update, 0, configuration.refreshInterval,
TimeUnit.SECONDS);
}
private void update() {
try {
apiClient.update();
updateAccountStatus(ThingStatus.ONLINE);
updateThings();
} catch (Exception e) {
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
logger.debug("Failed to update installation status", e);
}
}
private void updateAccountStatus(ThingStatus newStatus) {
updateAccountStatus(newStatus, ThingStatusDetail.NONE, null);
}
private void updateAccountStatus(ThingStatus newStatus, ThingStatusDetail detail, String message) {
// Prevent spamming the log file
if (!newStatus.equals(getThing().getStatus())) {
updateStatus(newStatus, detail, message);
updateListeners(newStatus);
}
}
private void updateListeners(ThingStatus status) {
for (AccountStatusListener listener : listeners) {
listener.accountStatusChanged(status);
}
}
private void updateThings() {
Map<String, TemperatureControlSystemStatus> idToTcsMap = new HashMap<>();
Map<String, ZoneStatus> idToZoneMap = new HashMap<>();
Map<String, GatewayStatus> tcsIdToGatewayMap = new HashMap<>();
Map<String, String> zoneIdToTcsIdMap = new HashMap<>();
Map<String, ThingStatus> idToTcsThingsStatusMap = new HashMap<>();
// First, create a lookup table
for (LocationStatus location : apiClient.getInstallationStatus()) {
for (GatewayStatus gateway : location.getGateways()) {
for (TemperatureControlSystemStatus tcs : gateway.getTemperatureControlSystems()) {
idToTcsMap.put(tcs.getSystemId(), tcs);
tcsIdToGatewayMap.put(tcs.getSystemId(), gateway);
for (ZoneStatus zone : tcs.getZones()) {
idToZoneMap.put(zone.getZoneId(), zone);
zoneIdToTcsIdMap.put(zone.getZoneId(), tcs.getSystemId());
}
}
}
}
// Then update the things by type, with pre-filtered info
for (Thing handler : getThing().getThings()) {
ThingHandler thingHandler = handler.getHandler();
if (thingHandler instanceof EvohomeTemperatureControlSystemHandler) {
EvohomeTemperatureControlSystemHandler tcsHandler = (EvohomeTemperatureControlSystemHandler) thingHandler;
tcsHandler.update(tcsIdToGatewayMap.get(tcsHandler.getId()), idToTcsMap.get(tcsHandler.getId()));
idToTcsThingsStatusMap.put(tcsHandler.getId(), tcsHandler.getThing().getStatus());
}
if (thingHandler instanceof EvohomeHeatingZoneHandler) {
EvohomeHeatingZoneHandler zoneHandler = (EvohomeHeatingZoneHandler) thingHandler;
zoneHandler.update(idToTcsThingsStatusMap.get(zoneIdToTcsIdMap.get(zoneHandler.getId())),
idToZoneMap.get(zoneHandler.getId()));
}
}
}
}

View File

@@ -0,0 +1,105 @@
/**
* 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.evohome.internal.handler;
import org.openhab.binding.evohome.internal.EvohomeBindingConstants;
import org.openhab.binding.evohome.internal.api.models.v2.response.ZoneStatus;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
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.types.Command;
import org.openhab.core.types.RefreshType;
/**
* The {@link EvohomeHeatingZoneHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Jasper van Zuijlen - Initial contribution
* @author Neil Renaud - Working implementation
* @author Jasper van Zuijlen - Refactor + Permanent Zone temperature setting
*/
public class EvohomeHeatingZoneHandler extends BaseEvohomeHandler {
private static final int CANCEL_SET_POINT_OVERRIDE = 0;
private ThingStatus tcsStatus;
private ZoneStatus zoneStatus;
public EvohomeHeatingZoneHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
}
public void update(ThingStatus tcsStatus, ZoneStatus zoneStatus) {
this.tcsStatus = tcsStatus;
this.zoneStatus = zoneStatus;
// Make the zone offline when the related display is offline
// If the related display is not a thing, ignore this
if (tcsStatus != null && tcsStatus.equals(ThingStatus.OFFLINE)) {
updateEvohomeThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Display Controller offline");
} else if (zoneStatus == null) {
updateEvohomeThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Status not found, check the zone id");
} else if (!handleActiveFaults(zoneStatus)) {
updateEvohomeThingStatus(ThingStatus.ONLINE);
updateState(EvohomeBindingConstants.ZONE_TEMPERATURE_CHANNEL,
new DecimalType(zoneStatus.getTemperature().getTemperature()));
updateState(EvohomeBindingConstants.ZONE_SET_POINT_STATUS_CHANNEL,
new StringType(zoneStatus.getHeatSetpoint().getSetpointMode()));
updateState(EvohomeBindingConstants.ZONE_SET_POINT_CHANNEL,
new DecimalType(zoneStatus.getHeatSetpoint().getTargetTemperature()));
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH) {
update(tcsStatus, zoneStatus);
} else {
EvohomeAccountBridgeHandler bridge = getEvohomeBridge();
if (bridge != null) {
String channelId = channelUID.getId();
if (EvohomeBindingConstants.ZONE_SET_POINT_CHANNEL.equals(channelId)
&& command instanceof DecimalType) {
double newTemp = ((DecimalType) command).doubleValue();
if (newTemp == CANCEL_SET_POINT_OVERRIDE) {
bridge.cancelSetPointOverride(getEvohomeThingConfig().id);
} else if (newTemp < 5) {
newTemp = 5;
}
if (newTemp >= 5 && newTemp <= 35) {
bridge.setPermanentSetPoint(getEvohomeThingConfig().id, newTemp);
}
}
}
}
}
private boolean handleActiveFaults(ZoneStatus zoneStatus) {
if (zoneStatus.hasActiveFaults()) {
updateEvohomeThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
zoneStatus.getActiveFault(0).getFaultType());
return true;
}
return false;
}
}

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.evohome.internal.handler;
import org.openhab.binding.evohome.internal.EvohomeBindingConstants;
import org.openhab.binding.evohome.internal.api.models.v2.response.GatewayStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystemStatus;
import org.openhab.core.library.types.StringType;
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.types.Command;
import org.openhab.core.types.RefreshType;
/**
* Handler for a temperature control system. Gets and sets global system mode.
*
* @author Jasper van Zuijlen - Initial contribution
*
*/
public class EvohomeTemperatureControlSystemHandler extends BaseEvohomeHandler {
private GatewayStatus gatewayStatus;
private TemperatureControlSystemStatus tcsStatus;
public EvohomeTemperatureControlSystemHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
}
public void update(GatewayStatus gatewayStatus, TemperatureControlSystemStatus tcsStatus) {
this.gatewayStatus = gatewayStatus;
this.tcsStatus = tcsStatus;
if (tcsStatus == null || gatewayStatus == null) {
updateEvohomeThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Status not found, check the display id");
} else if (!handleActiveFaults(gatewayStatus)) {
updateEvohomeThingStatus(ThingStatus.ONLINE);
updateState(EvohomeBindingConstants.DISPLAY_SYSTEM_MODE_CHANNEL,
new StringType(tcsStatus.getMode().getMode()));
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH) {
update(gatewayStatus, tcsStatus);
} else if (channelUID.getId().equals(EvohomeBindingConstants.DISPLAY_SYSTEM_MODE_CHANNEL)) {
EvohomeAccountBridgeHandler bridge = getEvohomeBridge();
if (bridge != null) {
bridge.setTcsMode(getEvohomeThingConfig().id, command.toString());
}
}
}
private boolean handleActiveFaults(GatewayStatus gatewayStatus) {
if (gatewayStatus.hasActiveFaults()) {
updateEvohomeThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
gatewayStatus.getActiveFault(0).getFaultType());
return true;
}
return false;
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="evohome" 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>evohome Binding</name>
<description>The evohome binding controls the Honeywell evohome system.</description>
<author>Jasper van Zuijlen</author>
</binding:binding>

View File

@@ -0,0 +1,11 @@
# binding
binding.evohome.name = Evohome binding
binding.evohome.description = De evohome binding maakt het mogelijk om het Honeywell evohome systeem te besturen.
# thing types
thing-type.evohome.display.label = Scherm
thing-type.evohome.display.description = Dit Thing representeert het evohome bedieningsscherm
# channel types
channel-type.evohome.systemMode.label = Systeemmodus
channel-type.evohome.systemMode.description = Huidige systeemmodus

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="evohome"
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">
<!-- evohome Gateway Bridge -->
<bridge-type id="account">
<label>evohome Account</label>
<description>The evohome account is used to connect to your Total Connect Comfort (TCC) using your TCC username and
password.</description>
<config-description>
<parameter-group name="auth">
<label>Authentication</label>
<description>Contains the settings needed to authenticate against the TCC service.</description>
</parameter-group>
<parameter name="username" type="text" required="true" groupName="auth">
<label>Username</label>
<description>Your TCC Username</description>
</parameter>
<parameter name="password" type="text" required="true" groupName="auth">
<label>Password</label>
<description>Your TCC Password</description>
<context>password</context>
</parameter>
<parameter name="refreshInterval" type="integer" required="false" min="15" max="3000">
<label>Refresh Interval</label>
<description>The refresh interval to poll TCC (in seconds).</description>
<default>15</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="evohome"
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">
<channel-type id="systemMode">
<item-type>String</item-type>
<label>System Mode</label>
<description>Current system mode</description>
<category>heating</category>
<state pattern="%s">
<options>
<option value="Auto">Normal</option>
<option value="AutoWithEco">Eco</option>
<option value="Away">Away</option>
<option value="DayOff">Day off</option>
<option value="HeatingOff">Off</option>
<option value="Custom">Custom</option>
</options>
</state>
</channel-type>
<channel-type id="temperature">
<item-type>Number</item-type>
<label>Temperature</label>
<description>Current zone temperature</description>
<category>temperature</category>
<state readOnly="true" pattern="%.1f °C">
</state>
</channel-type>
<channel-type id="setpoint">
<item-type>Number</item-type>
<label>Set Point</label>
<description>Gets or sets the set point of this zone (0 cancels the override).</description>
<category>heating</category>
<state min="0.0" max="35.0" step="0.5" pattern="%.1f °C"/>
</channel-type>
<channel-type id="setpointstatus">
<item-type>String</item-type>
<label>Set Point Status</label>
<description>Current set point status</description>
<category>heating</category>
<state pattern="%s" readOnly="true">
<options>
<option value="PermanentOverride">Permanent override</option>
<option value="FollowSchedule">Follow schedule</option>
<option value="TemporaryOverride">Temporary override</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="evohome"
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">
<thing-type id="display" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>evohome Display</label>
<description>This represents the evohome control display.</description>
<channels>
<channel id="SystemMode" typeId="systemMode"/>
</channels>
<config-description>
<parameter name="id" type="text" required="true" readOnly="true">
<label>ID</label>
<description>ID of the display</description>
</parameter>
<parameter name="name" type="text" required="false" readOnly="true">
<label>Name</label>
<description>Name of the display</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="heatingzone" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>evohome Heating Zone</label>
<description>This represents the evohome Heating Zone.</description>
<channels>
<channel id="Temperature" typeId="temperature"/>
<channel id="SetPointStatus" typeId="setpointstatus"/>
<channel id="SetPoint" typeId="setpoint"/>
</channels>
<config-description>
<parameter name="id" type="text" required="true" readOnly="true">
<label>ID</label>
<description>ID of the zone</description>
</parameter>
<parameter name="name" type="text" required="false" readOnly="true">
<label>Name</label>
<description>Name of the zone</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>