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