[evohome] Add null annotation and minor refactoring (#13885)

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
This commit is contained in:
lsiepel 2023-02-24 16:08:53 +01:00 committed by GitHub
parent cb31f420ff
commit dd21d92a80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 312 additions and 201 deletions

View File

@ -64,11 +64,12 @@ None
### Zone ### Zone
| Channel Type ID | Item Type | Description | | Channel Type ID | Item Type | Description |
|-------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------|
| Temperature | Number | Allows for viewing the current actual temperature of the zone. | | Temperature | Number:Temperature | Allows for viewing the current actual temperature of the zone. |
| SetPointStatus | String | Allows for viewing the current set point mode 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. | | SetPoint | Number:Temperature | Allows for viewing and permanently overriding the temperature set point of the zone. Sending 0 cancels any active set point overrides. |
|
## Full Example ## Full Example
@ -89,9 +90,9 @@ Bridge evohome:account:your_account_alias [ username="your_user_name", password=
String DemoMode { channel="evohome:display:your_account_alias:your_display_alias:SystemMode" } String DemoMode { channel="evohome:display:your_account_alias:your_display_alias:SystemMode" }
// evohome Heatingzone // evohome Heatingzone
Number DemoZoneTemperature { channel="evohome:heatingzone:your_account_alias:your_zone_alias:Temperature" } Number:Temperature DemoZoneTemperature { channel="evohome:heatingzone:your_account_alias:your_zone_alias:Temperature" }
String DemoZoneSetPointStatus { channel="evohome:heatingzone:your_account_alias:your_zone_alias:SetPointStatus" } String DemoZoneSetPointStatus { channel="evohome:heatingzone:your_account_alias:your_zone_alias:SetPointStatus" }
Number DemoZoneSetPoint { channel="evohome:heatingzone:your_account_alias:your_zone_alias:SetPoint" } Number:Temperature DemoZoneSetPoint { channel="evohome:heatingzone:your_account_alias:your_zone_alias:SetPoint" }
``` ```
### demo.sitemap ### demo.sitemap

View File

@ -17,6 +17,7 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
/** /**
@ -26,6 +27,7 @@ import org.openhab.core.thing.ThingTypeUID;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* @author Neil Renaud - Heating Zones * @author Neil Renaud - Heating Zones
*/ */
@NonNullByDefault
public class EvohomeBindingConstants { public class EvohomeBindingConstants {
private static final String BINDING_ID = "evohome"; private static final String BINDING_ID = "evohome";

View File

@ -14,12 +14,15 @@ package org.openhab.binding.evohome.internal;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Provides an interface for a delegate that can throw a timeout * Provides an interface for a delegate that can throw a timeout
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public interface RunnableWithTimeout { public interface RunnableWithTimeout {
public abstract void run() throws TimeoutException; public abstract void run() throws TimeoutException;

View File

@ -18,13 +18,15 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Authentication; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Authentication;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,17 +39,17 @@ import com.google.gson.GsonBuilder;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class ApiAccess { public class ApiAccess {
private static final int REQUEST_TIMEOUT_SECONDS = 5; private static final int REQUEST_TIMEOUT_SECONDS = 5;
private final Logger logger = LoggerFactory.getLogger(ApiAccess.class); private final Logger logger = LoggerFactory.getLogger(ApiAccess.class);
private final HttpClient httpClient; private final HttpClient httpClient;
private final Gson gson; private final Gson gson = new GsonBuilder().create();
private Authentication authenticationData; private @Nullable Authentication authenticationData;
private String applicationId; private @Nullable String applicationId;
public ApiAccess(HttpClient httpClient) { public ApiAccess(HttpClient httpClient) {
this.gson = new GsonBuilder().create();
this.httpClient = httpClient; this.httpClient = httpClient;
} }
@ -56,7 +58,7 @@ public class ApiAccess {
* *
* @param authentication The authentication details to apply * @param authentication The authentication details to apply
*/ */
public void setAuthentication(Authentication authentication) { public void setAuthentication(@Nullable Authentication authentication) {
authenticationData = authentication; authenticationData = authentication;
} }
@ -65,7 +67,7 @@ public class ApiAccess {
* *
* @return The current authentication details * @return The current authentication details
*/ */
public Authentication getAuthentication() { public @Nullable Authentication getAuthentication() {
return authenticationData; return authenticationData;
} }
@ -74,7 +76,7 @@ public class ApiAccess {
* *
* @param applicationId The application id to apply * @param applicationId The application id to apply
*/ */
public void setApplicationId(String applicationId) { public void setApplicationId(@Nullable String applicationId) {
this.applicationId = applicationId; this.applicationId = applicationId;
} }
@ -89,18 +91,16 @@ public class ApiAccess {
* @return The result of the request or null * @return The result of the request or null
* @throws TimeoutException Thrown when a request times out * @throws TimeoutException Thrown when a request times out
*/ */
public <TOut> TOut doRequest(HttpMethod method, String url, Map<String, String> headers, String requestData, public @Nullable <TOut> TOut doRequest(HttpMethod method, String url, Map<String, String> headers,
String contentType, Class<TOut> outClass) throws TimeoutException { @Nullable String requestData, String contentType, @Nullable Class<TOut> outClass) throws TimeoutException {
TOut retVal = null;
logger.debug("Requesting: [{}]", url); logger.debug("Requesting: [{}]", url);
@Nullable
TOut retVal = null;
try { try {
Request request = httpClient.newRequest(url).method(method); Request request = httpClient.newRequest(url).method(method);
if (headers != null) { for (Map.Entry<String, String> header : headers.entrySet()) {
for (Map.Entry<String, String> header : headers.entrySet()) { request.header(header.getKey(), header.getValue());
request.header(header.getKey(), header.getValue());
}
} }
if (requestData != null) { if (requestData != null) {
@ -109,8 +109,8 @@ public class ApiAccess {
ContentResponse response = request.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS).send(); ContentResponse response = request.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
logger.debug("Response: {}", response); logger.trace("Response: {}", response);
logger.debug("\n{}\n{}", response.getHeaders(), response.getContentAsString()); logger.trace("\n{}\n{}", response.getHeaders(), response.getContentAsString());
if ((response.getStatus() == HttpStatus.OK_200) || (response.getStatus() == HttpStatus.ACCEPTED_202)) { if ((response.getStatus() == HttpStatus.OK_200) || (response.getStatus() == HttpStatus.ACCEPTED_202)) {
String reply = response.getContentAsString(); String reply = response.getContentAsString();
@ -118,6 +118,10 @@ public class ApiAccess {
if (outClass != null) { if (outClass != null) {
retVal = new Gson().fromJson(reply, outClass); retVal = new Gson().fromJson(reply, outClass);
} }
} else if ((response.getStatus() == HttpStatus.CREATED_201)) {
// success nothing to return ignore
} else {
logger.debug("Request failed with unexpected response code {}", response.getStatus());
} }
} catch (ExecutionException e) { } catch (ExecutionException e) {
logger.debug("Error in handling request: ", e); logger.debug("Error in handling request: ", e);
@ -125,7 +129,6 @@ public class ApiAccess {
logger.debug("Handling request interrupted: ", e); logger.debug("Handling request interrupted: ", e);
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
return retVal; return retVal;
} }
@ -138,7 +141,7 @@ public class ApiAccess {
* @return The result of the request or null * @return The result of the request or null
* @throws TimeoutException Thrown when a request times out * @throws TimeoutException Thrown when a request times out
*/ */
public <TOut> TOut doAuthenticatedGet(String url, Class<TOut> outClass) throws TimeoutException { public @Nullable <TOut> TOut doAuthenticatedGet(String url, Class<TOut> outClass) throws TimeoutException {
return doAuthenticatedRequest(HttpMethod.GET, url, null, outClass); return doAuthenticatedRequest(HttpMethod.GET, url, null, outClass);
} }
@ -161,16 +164,16 @@ public class ApiAccess {
* @param method The HTTP method to use (POST, GET, ...) * @param method The HTTP method to use (POST, GET, ...)
* @param url The URL to query * @param url The URL to query
* @param headers The optional additional headers to apply, can be null * @param headers The optional additional headers to apply, can be null
* @param requestContainer The object to use as JSON data for the request * @param requestContainer The object to use as JSON data for the request, can be null
* @param outClass The type of the requested result * @param outClass The type of the requested result, can be null
* @return The result of the request or null * @return The result of the request or null
* @throws TimeoutException Thrown when a request times out * @throws TimeoutException Thrown when a request times out
*/ */
private <TOut> TOut doRequest(HttpMethod method, String url, Map<String, String> headers, Object requestContainer, private @Nullable <TOut> TOut doRequest(HttpMethod method, String url, Map<String, String> headers,
Class<TOut> outClass) throws TimeoutException { @Nullable Object requestContainer, @Nullable Class<TOut> outClass) throws TimeoutException {
String json = null; String json = null;
if (requestContainer != null) { if (requestContainer != null) {
json = this.gson.toJson(requestContainer); json = gson.toJson(requestContainer);
} }
return doRequest(method, url, headers, json, "application/json", outClass); return doRequest(method, url, headers, json, "application/json", outClass);
@ -188,17 +191,19 @@ public class ApiAccess {
* @return The result of the request or null * @return The result of the request or null
* @throws TimeoutException Thrown when a request times out * @throws TimeoutException Thrown when a request times out
*/ */
private <TOut> TOut doAuthenticatedRequest(HttpMethod method, String url, Object requestContainer, private @Nullable <TOut> TOut doAuthenticatedRequest(HttpMethod method, String url,
Class<TOut> outClass) throws TimeoutException { @Nullable Object requestContainer, @Nullable Class<TOut> outClass) throws TimeoutException {
Map<String, String> headers = null; Map<String, String> headers = new HashMap<>();
if (authenticationData != null) { Authentication localAuthenticationData = authenticationData;
headers = new HashMap<>(); String localApplicationId = applicationId;
headers.put("Authorization", "Bearer " + authenticationData.getAccessToken()); if (localAuthenticationData != null) {
headers.put("applicationId", applicationId); headers.put("Authorization", "Bearer " + localAuthenticationData.getAccessToken());
headers.put("Accept",
"application/json, application/xml, text/json, text/x-json, text/javascript, text/xml");
} }
if (localApplicationId != null) {
headers.put("applicationId", localApplicationId);
}
headers.put("Accept", "application/json, application/xml, text/json, text/x-json, text/javascript, text/xml");
return doRequest(method, url, headers, requestContainer, outClass); return doRequest(method, url, headers, requestContainer, outClass);
} }

View File

@ -19,18 +19,20 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethod; 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.dto.request.HeatSetPoint;
import org.openhab.binding.evohome.internal.api.models.v2.request.HeatSetPointBuilder; import org.openhab.binding.evohome.internal.api.models.v2.dto.request.HeatSetPointBuilder;
import org.openhab.binding.evohome.internal.api.models.v2.request.Mode; import org.openhab.binding.evohome.internal.api.models.v2.dto.request.Mode;
import org.openhab.binding.evohome.internal.api.models.v2.request.ModeBuilder; import org.openhab.binding.evohome.internal.api.models.v2.dto.request.ModeBuilder;
import org.openhab.binding.evohome.internal.api.models.v2.response.Authentication; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Authentication;
import org.openhab.binding.evohome.internal.api.models.v2.response.Location; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Location;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.LocationStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Locations; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Locations;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationsStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.LocationsStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.UserAccount; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.UserAccount;
import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration; import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -41,6 +43,7 @@ import org.slf4j.LoggerFactory;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeApiClient { public class EvohomeApiClient {
private static final String APPLICATION_ID = "b013aa26-9724-4dbd-8897-048b9aada249"; private static final String APPLICATION_ID = "b013aa26-9724-4dbd-8897-048b9aada249";
@ -52,8 +55,8 @@ public class EvohomeApiClient {
private final ApiAccess apiAccess; private final ApiAccess apiAccess;
private Locations locations = new Locations(); private Locations locations = new Locations();
private UserAccount useraccount; private @Nullable UserAccount useraccount;
private LocationsStatus locationsStatus; private @Nullable LocationsStatus locationsStatus;
/** /**
* Creates a new API client based on the V2 API interface * Creates a new API client based on the V2 API interface
@ -72,7 +75,7 @@ public class EvohomeApiClient {
public void close() { public void close() {
apiAccess.setAuthentication(null); apiAccess.setAuthentication(null);
useraccount = null; useraccount = null;
locations = null; locations = new Locations();
locationsStatus = null; locationsStatus = null;
} }
@ -113,7 +116,7 @@ public class EvohomeApiClient {
return locations; return locations;
} }
public LocationsStatus getInstallationStatus() { public @Nullable LocationsStatus getInstallationStatus() {
return locationsStatus; return locationsStatus;
} }
@ -139,33 +142,33 @@ public class EvohomeApiClient {
apiAccess.doAuthenticatedPut(url, heatSetPoint); apiAccess.doAuthenticatedPut(url, heatSetPoint);
} }
private UserAccount requestUserAccount() throws TimeoutException { private @Nullable UserAccount requestUserAccount() throws TimeoutException {
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_ACCOUNT; String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_ACCOUNT;
return apiAccess.doAuthenticatedGet(url, UserAccount.class); return apiAccess.doAuthenticatedGet(url, UserAccount.class);
} }
private Locations requestLocations() throws TimeoutException { private Locations requestLocations() throws TimeoutException {
Locations locations = new Locations(); Locations locations = null;
if (useraccount != null) { UserAccount localAccount = useraccount;
if (localAccount != null) {
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_INSTALLATION_INFO; String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_INSTALLATION_INFO;
url = String.format(url, useraccount.getUserId()); url = String.format(url, localAccount.getUserId());
locations = apiAccess.doAuthenticatedGet(url, Locations.class); locations = apiAccess.doAuthenticatedGet(url, Locations.class);
} }
return locations; return locations != null ? locations : new Locations();
} }
private LocationsStatus requestLocationsStatus() throws TimeoutException { private LocationsStatus requestLocationsStatus() throws TimeoutException {
LocationsStatus locationsStatus = new LocationsStatus(); LocationsStatus locationsStatus = new LocationsStatus();
if (locations != null) { for (Location location : locations) {
for (Location location : locations) { String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_LOCATION_STATUS;
String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_LOCATION_STATUS; url = String.format(url, location.getLocationInfo().getLocationId());
url = String.format(url, location.getLocationInfo().getLocationId()); LocationStatus status = apiAccess.doAuthenticatedGet(url, LocationStatus.class);
LocationStatus status = apiAccess.doAuthenticatedGet(url, LocationStatus.class); locationsStatus.add(status);
locationsStatus.add(status);
}
} }
return locationsStatus; return locationsStatus;
} }

View File

@ -12,13 +12,17 @@
*/ */
package org.openhab.binding.evohome.internal.api; package org.openhab.binding.evohome.internal.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Exception for errors from the API Client. * Exception for errors from the API Client.
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeApiClientException extends Exception { public class EvohomeApiClientException extends Exception {
private static final long serialVersionUID = 1L;
public EvohomeApiClientException() { public EvohomeApiClientException() {
} }

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.evohome.internal.api; package org.openhab.binding.evohome.internal.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* List of evohome API constants * List of evohome API constants
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeApiConstants { public class EvohomeApiConstants {
public static final String URL_V2_AUTH = "https://tccna.honeywell.com/Auth/OAuth/Token"; public static final String URL_V2_AUTH = "https://tccna.honeywell.com/Auth/OAuth/Token";

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.request; package org.openhab.binding.evohome.internal.api.models.v2.dto.request;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -43,12 +43,15 @@ public class HeatSetPoint {
timeUntil = null; timeUntil = null;
} }
@SuppressWarnings("unused")
@SerializedName("heatSetpointValue") @SerializedName("heatSetpointValue")
private double heatSetpointValue; private double heatSetpointValue;
@SuppressWarnings("unused")
@SerializedName("setpointMode") @SerializedName("setpointMode")
private String setpointMode; private String setpointMode;
@SuppressWarnings("unused")
@SerializedName("timeUntil") @SerializedName("timeUntil")
private String timeUntil; private String timeUntil;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.request; package org.openhab.binding.evohome.internal.api.models.v2.dto.request;
/** /**
* Builder for heat set point API requests * Builder for heat set point API requests

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.request; package org.openhab.binding.evohome.internal.api.models.v2.dto.request;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -34,12 +34,15 @@ public class Mode {
permanent = false; permanent = false;
} }
@SuppressWarnings("unused")
@SerializedName("systemMode") @SerializedName("systemMode")
private String systemMode; private String systemMode;
@SuppressWarnings("unused")
@SerializedName("timeUntil") @SerializedName("timeUntil")
private String timeUntil; private String timeUntil;
@SuppressWarnings("unused")
@SerializedName("permanent") @SerializedName("permanent")
private boolean permanent; private boolean permanent;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.request; package org.openhab.binding.evohome.internal.api.models.v2.dto.request;
/** /**
* Builder for mode API requests * Builder for mode API requests

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.request; package org.openhab.binding.evohome.internal.api.models.v2.dto.request;
/** /**
* Builder for API requests * Builder for API requests

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.request; package org.openhab.binding.evohome.internal.api.models.v2.dto.request;
/** /**
* Builder for timed API requests * Builder for timed API requests

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,5 +21,5 @@ import java.util.ArrayList;
* *
*/ */
public class Locations extends ArrayList<Location> { public class Locations extends ArrayList<Location> {
private static final long serialVersionUID = 1L;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,5 +21,5 @@ import java.util.ArrayList;
* *
*/ */
public class LocationsStatus extends ArrayList<LocationStatus> { public class LocationsStatus extends ArrayList<LocationStatus> {
private static final long serialVersionUID = 1L;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.evohome.internal.api.models.v2.response; package org.openhab.binding.evohome.internal.api.models.v2.dto.response;
import java.util.List; import java.util.List;

View File

@ -12,15 +12,18 @@
*/ */
package org.openhab.binding.evohome.internal.configuration; package org.openhab.binding.evohome.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Contains the configuration of the binding. * Contains the configuration of the binding.
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeAccountConfiguration { public class EvohomeAccountConfiguration {
public String username; public String username = "";
public String password; public String password = "";
public String applicationId; public String applicationId = "";
public int refreshInterval; public int refreshInterval;
} }

View File

@ -12,13 +12,16 @@
*/ */
package org.openhab.binding.evohome.internal.configuration; package org.openhab.binding.evohome.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Contains the configuration of the binding. * Contains the configuration of the binding.
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeTemperatureControlSystemConfiguration { public class EvohomeTemperatureControlSystemConfiguration {
public String id; public String id = "";
public String name; public String name = "";
} }

View File

@ -12,13 +12,16 @@
*/ */
package org.openhab.binding.evohome.internal.configuration; package org.openhab.binding.evohome.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Contains the common configuration definition of an evohome Thing * Contains the common configuration definition of an evohome Thing
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeThingConfiguration { public class EvohomeThingConfiguration {
public String id; public String id = "";
public String name; public String name = "";
} }

View File

@ -15,11 +15,13 @@ package org.openhab.binding.evohome.internal.discovery;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.evohome.internal.EvohomeBindingConstants; 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.dto.response.Gateway;
import org.openhab.binding.evohome.internal.api.models.v2.response.Location; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Location;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystem; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Locations;
import org.openhab.binding.evohome.internal.api.models.v2.response.Zone; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.TemperatureControlSystem;
import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Zone;
import org.openhab.binding.evohome.internal.handler.AccountStatusListener; import org.openhab.binding.evohome.internal.handler.AccountStatusListener;
import org.openhab.binding.evohome.internal.handler.EvohomeAccountBridgeHandler; import org.openhab.binding.evohome.internal.handler.EvohomeAccountBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
@ -37,6 +39,7 @@ import org.slf4j.LoggerFactory;
* @author Jasper van Zuijlen - Background discovery * @author Jasper van Zuijlen - Background discovery
* *
*/ */
@NonNullByDefault
public class EvohomeDiscoveryService extends AbstractDiscoveryService implements AccountStatusListener { public class EvohomeDiscoveryService extends AbstractDiscoveryService implements AccountStatusListener {
private final Logger logger = LoggerFactory.getLogger(EvohomeDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(EvohomeDiscoveryService.class);
private static final int TIMEOUT = 5; private static final int TIMEOUT = 5;
@ -86,18 +89,29 @@ public class EvohomeDiscoveryService extends AbstractDiscoveryService implements
logger.debug("Evohome Gateway not online, scanning postponed"); logger.debug("Evohome Gateway not online, scanning postponed");
return; return;
} }
Locations localEvohomeConfig = bridge.getEvohomeConfig();
for (Location location : bridge.getEvohomeConfig()) { if (localEvohomeConfig == null) {
return;
}
for (Location location : localEvohomeConfig) {
if (location == null) {
continue;
}
for (Gateway gateway : location.getGateways()) { for (Gateway gateway : location.getGateways()) {
for (TemperatureControlSystem tcs : gateway.getTemperatureControlSystems()) { for (TemperatureControlSystem tcs : gateway.getTemperatureControlSystems()) {
if (tcs == null) {
continue;
}
addDisplayDiscoveryResult(location, tcs); addDisplayDiscoveryResult(location, tcs);
for (Zone zone : tcs.getZones()) { for (Zone zone : tcs.getZones()) {
addZoneDiscoveryResult(location, zone); if (zone != null) {
addZoneDiscoveryResult(location, zone);
}
} }
} }
} }
} }
stopScan(); stopScan();
} }

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.evohome.internal.handler; package org.openhab.binding.evohome.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
/** /**
@ -20,6 +21,7 @@ import org.openhab.core.thing.ThingStatus;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public interface AccountStatusListener { public interface AccountStatusListener {
/** /**

View File

@ -12,7 +12,9 @@
*/ */
package org.openhab.binding.evohome.internal.handler; package org.openhab.binding.evohome.internal.handler;
import org.openhab.binding.evohome.internal.api.models.v2.response.Locations; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Locations;
import org.openhab.binding.evohome.internal.configuration.EvohomeThingConfiguration; import org.openhab.binding.evohome.internal.configuration.EvohomeThingConfiguration;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -25,8 +27,9 @@ import org.openhab.core.thing.binding.BaseThingHandler;
* *
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
*/ */
@NonNullByDefault
public abstract class BaseEvohomeHandler extends BaseThingHandler { public abstract class BaseEvohomeHandler extends BaseThingHandler {
private EvohomeThingConfiguration configuration; private EvohomeThingConfiguration configuration = new EvohomeThingConfiguration();
public BaseEvohomeHandler(Thing thing) { public BaseEvohomeHandler(Thing thing) {
super(thing); super(thing);
@ -40,14 +43,10 @@ public abstract class BaseEvohomeHandler extends BaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
configuration = null;
} }
public String getId() { public String getId() {
if (configuration != null) { return configuration.id;
return configuration.id;
}
return null;
} }
/** /**
@ -64,7 +63,7 @@ public abstract class BaseEvohomeHandler extends BaseThingHandler {
* *
* @return The evohome brdige * @return The evohome brdige
*/ */
protected EvohomeAccountBridgeHandler getEvohomeBridge() { protected @Nullable EvohomeAccountBridgeHandler getEvohomeBridge() {
Bridge bridge = getBridge(); Bridge bridge = getBridge();
if (bridge != null) { if (bridge != null) {
return (EvohomeAccountBridgeHandler) bridge.getHandler(); return (EvohomeAccountBridgeHandler) bridge.getHandler();
@ -78,10 +77,10 @@ public abstract class BaseEvohomeHandler extends BaseThingHandler {
* *
* @return The current evohome configuration * @return The current evohome configuration
*/ */
protected Locations getEvohomeConfig() { protected @Nullable Locations getEvohomeConfig() {
EvohomeAccountBridgeHandler bridge = getEvohomeBridge(); EvohomeAccountBridgeHandler bridgeAccountHandler = getEvohomeBridge();
if (bridge != null) { if (bridgeAccountHandler != null) {
return bridge.getEvohomeConfig(); return bridgeAccountHandler.getEvohomeConfig();
} }
return null; return null;
@ -115,7 +114,7 @@ public abstract class BaseEvohomeHandler extends BaseThingHandler {
* @param detail The status detail value * @param detail The status detail value
* @param message The message to show with the status * @param message The message to show with the status
*/ */
protected void updateEvohomeThingStatus(ThingStatus newStatus, ThingStatusDetail detail, String message) { protected void updateEvohomeThingStatus(ThingStatus newStatus, ThingStatusDetail detail, @Nullable String message) {
// Prevent spamming the log file // Prevent spamming the log file
if (!newStatus.equals(getThing().getStatus())) { if (!newStatus.equals(getThing().getStatus())) {
updateStatus(newStatus, detail, message); updateStatus(newStatus, detail, message);
@ -128,10 +127,7 @@ public abstract class BaseEvohomeHandler extends BaseThingHandler {
* @param configuration The configuration to check * @param configuration The configuration to check
*/ */
private void checkConfig() { private void checkConfig() {
if (configuration == null) { if (configuration.id.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Configuration is missing or corrupted");
} else if (configuration.id == null || configuration.id.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Id not configured"); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Id not configured");
} }
} }

View File

@ -22,19 +22,21 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.evohome.internal.RunnableWithTimeout; import org.openhab.binding.evohome.internal.RunnableWithTimeout;
import org.openhab.binding.evohome.internal.api.EvohomeApiClient; 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.dto.response.Gateway;
import org.openhab.binding.evohome.internal.api.models.v2.response.GatewayStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.GatewayStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Location; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Location;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.LocationStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Locations; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Locations;
import org.openhab.binding.evohome.internal.api.models.v2.response.LocationsStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.LocationsStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystem; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.TemperatureControlSystem;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystemStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.TemperatureControlSystemStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.Zone; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.Zone;
import org.openhab.binding.evohome.internal.api.models.v2.response.ZoneStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.ZoneStatus;
import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration; import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
@ -54,15 +56,16 @@ import org.slf4j.LoggerFactory;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeAccountBridgeHandler extends BaseBridgeHandler { public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(EvohomeAccountBridgeHandler.class); private final Logger logger = LoggerFactory.getLogger(EvohomeAccountBridgeHandler.class);
private final HttpClient httpClient; private final HttpClient httpClient;
private EvohomeAccountConfiguration configuration; private EvohomeAccountConfiguration configuration = new EvohomeAccountConfiguration();
private EvohomeApiClient apiClient; private @Nullable EvohomeApiClient apiClient;
private List<AccountStatusListener> listeners = new CopyOnWriteArrayList<>(); private List<AccountStatusListener> listeners = new CopyOnWriteArrayList<>();
protected ScheduledFuture<?> refreshTask; protected @Nullable ScheduledFuture<?> refreshTask;
public EvohomeAccountBridgeHandler(Bridge thing, HttpClient httpClient) { public EvohomeAccountBridgeHandler(Bridge thing, HttpClient httpClient) {
super(thing); super(thing);
@ -73,13 +76,14 @@ public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
public void initialize() { public void initialize() {
configuration = getConfigAs(EvohomeAccountConfiguration.class); configuration = getConfigAs(EvohomeAccountConfiguration.class);
if (checkConfig()) { if (checkConfig(configuration)) {
apiClient = new EvohomeApiClient(configuration, this.httpClient); apiClient = new EvohomeApiClient(configuration, this.httpClient);
// Initialization can take a while, so kick it off on a separate thread // Initialization can take a while, so kick it off on a separate thread
scheduler.schedule(() -> { scheduler.schedule(() -> {
if (apiClient.login()) { EvohomeApiClient localApiCLient = apiClient;
if (checkInstallationInfoHasDuplicateIds(apiClient.getInstallationInfo())) { if (localApiCLient != null && localApiCLient.login()) {
if (checkInstallationInfoHasDuplicateIds(localApiCLient.getInstallationInfo())) {
startRefreshTask(); startRefreshTask();
} else { } else {
updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateAccountStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
@ -104,24 +108,41 @@ public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
} }
public Locations getEvohomeConfig() { public @Nullable Locations getEvohomeConfig() {
return apiClient.getInstallationInfo(); EvohomeApiClient localApiCLient = apiClient;
if (localApiCLient != null) {
return localApiCLient.getInstallationInfo();
}
return null;
} }
public LocationsStatus getEvohomeStatus() { public @Nullable LocationsStatus getEvohomeStatus() {
return apiClient.getInstallationStatus(); EvohomeApiClient localApiCLient = apiClient;
if (localApiCLient != null) {
return localApiCLient.getInstallationStatus();
}
return null;
} }
public void setTcsMode(String tcsId, String mode) { public void setTcsMode(String tcsId, String mode) {
tryToCall(() -> apiClient.setTcsMode(tcsId, mode)); EvohomeApiClient localApiCLient = apiClient;
if (localApiCLient != null) {
tryToCall(() -> localApiCLient.setTcsMode(tcsId, mode));
}
} }
public void setPermanentSetPoint(String zoneId, double doubleValue) { public void setPermanentSetPoint(String zoneId, double doubleValue) {
tryToCall(() -> apiClient.setHeatingZoneOverride(zoneId, doubleValue)); EvohomeApiClient localApiCLient = apiClient;
if (localApiCLient != null) {
tryToCall(() -> localApiCLient.setHeatingZoneOverride(zoneId, doubleValue));
}
} }
public void cancelSetPointOverride(String zoneId) { public void cancelSetPointOverride(String zoneId) {
tryToCall(() -> apiClient.cancelHeatingZoneOverride(zoneId)); EvohomeApiClient localApiCLient = apiClient;
if (localApiCLient != null) {
tryToCall(() -> localApiCLient.cancelHeatingZoneOverride(zoneId));
}
} }
public void addAccountStatusListener(AccountStatusListener listener) { public void addAccountStatusListener(AccountStatusListener listener) {
@ -164,26 +185,27 @@ public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
} }
private void disposeApiClient() { private void disposeApiClient() {
if (apiClient != null) { EvohomeApiClient localApiClient = apiClient;
apiClient.logout(); if (localApiClient != null) {
localApiClient.logout();
this.apiClient = null;
} }
apiClient = null;
} }
private void disposeRefreshTask() { private void disposeRefreshTask() {
if (refreshTask != null) { ScheduledFuture<?> localRefreshTask = refreshTask;
refreshTask.cancel(true); if (localRefreshTask != null) {
localRefreshTask.cancel(true);
this.refreshTask = null;
} }
} }
private boolean checkConfig() { private boolean checkConfig(EvohomeAccountConfiguration configuration) {
String errorMessage = ""; String errorMessage = "";
if (configuration == null) { if (configuration.username.isBlank()) {
errorMessage = "Configuration is missing or corrupted";
} else if (configuration.username == null || configuration.username.isEmpty()) {
errorMessage = "Username not configured"; errorMessage = "Username not configured";
} else if (configuration.password == null || configuration.password.isEmpty()) { } else if (configuration.password.isBlank()) {
errorMessage = "Password not configured"; errorMessage = "Password not configured";
} else { } else {
return true; return true;
@ -202,7 +224,10 @@ public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
private void update() { private void update() {
try { try {
apiClient.update(); EvohomeApiClient localApiCLient = apiClient;
if (localApiCLient != null) {
localApiCLient.update();
}
updateAccountStatus(ThingStatus.ONLINE); updateAccountStatus(ThingStatus.ONLINE);
updateThings(); updateThings();
} catch (Exception e) { } catch (Exception e) {
@ -215,7 +240,7 @@ public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
updateAccountStatus(newStatus, ThingStatusDetail.NONE, null); updateAccountStatus(newStatus, ThingStatusDetail.NONE, null);
} }
private void updateAccountStatus(ThingStatus newStatus, ThingStatusDetail detail, String message) { private void updateAccountStatus(ThingStatus newStatus, ThingStatusDetail detail, @Nullable String message) {
// Prevent spamming the log file // Prevent spamming the log file
if (!newStatus.equals(getThing().getStatus())) { if (!newStatus.equals(getThing().getStatus())) {
updateStatus(newStatus, detail, message); updateStatus(newStatus, detail, message);
@ -236,15 +261,32 @@ public class EvohomeAccountBridgeHandler extends BaseBridgeHandler {
Map<String, String> zoneIdToTcsIdMap = new HashMap<>(); Map<String, String> zoneIdToTcsIdMap = new HashMap<>();
Map<String, ThingStatus> idToTcsThingsStatusMap = new HashMap<>(); Map<String, ThingStatus> idToTcsThingsStatusMap = new HashMap<>();
// First, create a lookup table EvohomeApiClient localApiClient = apiClient;
for (LocationStatus location : apiClient.getInstallationStatus()) { if (localApiClient != null) {
for (GatewayStatus gateway : location.getGateways()) { // First, create a lookup table
for (TemperatureControlSystemStatus tcs : gateway.getTemperatureControlSystems()) { LocationsStatus localLocationsStatus = localApiClient.getInstallationStatus();
idToTcsMap.put(tcs.getSystemId(), tcs); if (localLocationsStatus != null) {
tcsIdToGatewayMap.put(tcs.getSystemId(), gateway); for (LocationStatus location : localLocationsStatus) {
for (ZoneStatus zone : tcs.getZones()) { for (GatewayStatus gateway : location.getGateways()) {
idToZoneMap.put(zone.getZoneId(), zone); if (gateway == null) {
zoneIdToTcsIdMap.put(zone.getZoneId(), tcs.getSystemId()); continue;
}
for (TemperatureControlSystemStatus tcs : gateway.getTemperatureControlSystems()) {
String systemId = tcs.getSystemId();
if (systemId != null) {
idToTcsMap.put(systemId, tcs);
tcsIdToGatewayMap.put(systemId, gateway);
}
for (ZoneStatus zone : tcs.getZones()) {
String zoneId = zone.getZoneId();
if (zoneId != null) {
idToZoneMap.put(zoneId, zone);
if (systemId != null) {
zoneIdToTcsIdMap.put(zoneId, systemId);
}
}
}
}
} }
} }
} }

View File

@ -12,10 +12,15 @@
*/ */
package org.openhab.binding.evohome.internal.handler; package org.openhab.binding.evohome.internal.handler;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.evohome.internal.EvohomeBindingConstants; import org.openhab.binding.evohome.internal.EvohomeBindingConstants;
import org.openhab.binding.evohome.internal.api.models.v2.response.ZoneStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.ZoneStatus;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -30,12 +35,14 @@ import org.openhab.core.types.RefreshType;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* @author Neil Renaud - Working implementation * @author Neil Renaud - Working implementation
* @author Jasper van Zuijlen - Refactor + Permanent Zone temperature setting * @author Jasper van Zuijlen - Refactor + Permanent Zone temperature setting
* @author Leo Siepel - Add UoM
*/ */
@NonNullByDefault
public class EvohomeHeatingZoneHandler extends BaseEvohomeHandler { public class EvohomeHeatingZoneHandler extends BaseEvohomeHandler {
private static final int CANCEL_SET_POINT_OVERRIDE = 0; private static final int CANCEL_SET_POINT_OVERRIDE = 0;
private ThingStatus tcsStatus; private @Nullable ThingStatus tcsStatus;
private ZoneStatus zoneStatus; private @Nullable ZoneStatus zoneStatus;
public EvohomeHeatingZoneHandler(Thing thing) { public EvohomeHeatingZoneHandler(Thing thing) {
super(thing); super(thing);
@ -46,7 +53,7 @@ public class EvohomeHeatingZoneHandler extends BaseEvohomeHandler {
super.initialize(); super.initialize();
} }
public void update(ThingStatus tcsStatus, ZoneStatus zoneStatus) { public void update(@Nullable ThingStatus tcsStatus, @Nullable ZoneStatus zoneStatus) {
this.tcsStatus = tcsStatus; this.tcsStatus = tcsStatus;
this.zoneStatus = zoneStatus; this.zoneStatus = zoneStatus;
@ -62,11 +69,11 @@ public class EvohomeHeatingZoneHandler extends BaseEvohomeHandler {
updateEvohomeThingStatus(ThingStatus.ONLINE); updateEvohomeThingStatus(ThingStatus.ONLINE);
updateState(EvohomeBindingConstants.ZONE_TEMPERATURE_CHANNEL, updateState(EvohomeBindingConstants.ZONE_TEMPERATURE_CHANNEL,
new DecimalType(zoneStatus.getTemperature().getTemperature())); new QuantityType<Temperature>(zoneStatus.getTemperature().getTemperature(), SIUnits.CELSIUS));
updateState(EvohomeBindingConstants.ZONE_SET_POINT_STATUS_CHANNEL, updateState(EvohomeBindingConstants.ZONE_SET_POINT_STATUS_CHANNEL,
new StringType(zoneStatus.getHeatSetpoint().getSetpointMode())); new StringType(zoneStatus.getHeatSetpoint().getSetpointMode()));
updateState(EvohomeBindingConstants.ZONE_SET_POINT_CHANNEL, updateState(EvohomeBindingConstants.ZONE_SET_POINT_CHANNEL, new QuantityType<Temperature>(
new DecimalType(zoneStatus.getHeatSetpoint().getTargetTemperature())); zoneStatus.getHeatSetpoint().getTargetTemperature(), SIUnits.CELSIUS));
} }
} }
@ -78,16 +85,19 @@ public class EvohomeHeatingZoneHandler extends BaseEvohomeHandler {
EvohomeAccountBridgeHandler bridge = getEvohomeBridge(); EvohomeAccountBridgeHandler bridge = getEvohomeBridge();
if (bridge != null) { if (bridge != null) {
String channelId = channelUID.getId(); String channelId = channelUID.getId();
if (EvohomeBindingConstants.ZONE_SET_POINT_CHANNEL.equals(channelId) if (EvohomeBindingConstants.ZONE_SET_POINT_CHANNEL.equals(channelId)) {
&& command instanceof DecimalType) { if (command instanceof QuantityType) {
double newTemp = ((DecimalType) command).doubleValue(); QuantityType<?> state = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
if (newTemp == CANCEL_SET_POINT_OVERRIDE) { double newTempInCelsius = state.doubleValue();
bridge.cancelSetPointOverride(getEvohomeThingConfig().id);
} else if (newTemp < 5) { if (newTempInCelsius == CANCEL_SET_POINT_OVERRIDE) {
newTemp = 5; bridge.cancelSetPointOverride(getEvohomeThingConfig().id);
} } else if (newTempInCelsius < 5) {
if (newTemp >= 5 && newTemp <= 35) { newTempInCelsius = 5;
bridge.setPermanentSetPoint(getEvohomeThingConfig().id, newTemp); }
if (newTempInCelsius >= 5 && newTempInCelsius <= 35) {
bridge.setPermanentSetPoint(getEvohomeThingConfig().id, newTempInCelsius);
}
} }
} }
} }

View File

@ -12,9 +12,11 @@
*/ */
package org.openhab.binding.evohome.internal.handler; package org.openhab.binding.evohome.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.evohome.internal.EvohomeBindingConstants; 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.dto.response.GatewayStatus;
import org.openhab.binding.evohome.internal.api.models.v2.response.TemperatureControlSystemStatus; import org.openhab.binding.evohome.internal.api.models.v2.dto.response.TemperatureControlSystemStatus;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -29,9 +31,10 @@ import org.openhab.core.types.RefreshType;
* @author Jasper van Zuijlen - Initial contribution * @author Jasper van Zuijlen - Initial contribution
* *
*/ */
@NonNullByDefault
public class EvohomeTemperatureControlSystemHandler extends BaseEvohomeHandler { public class EvohomeTemperatureControlSystemHandler extends BaseEvohomeHandler {
private GatewayStatus gatewayStatus; private @Nullable GatewayStatus gatewayStatus;
private TemperatureControlSystemStatus tcsStatus; private @Nullable TemperatureControlSystemStatus tcsStatus;
public EvohomeTemperatureControlSystemHandler(Thing thing) { public EvohomeTemperatureControlSystemHandler(Thing thing) {
super(thing); super(thing);
@ -42,7 +45,7 @@ public class EvohomeTemperatureControlSystemHandler extends BaseEvohomeHandler {
super.initialize(); super.initialize();
} }
public void update(GatewayStatus gatewayStatus, TemperatureControlSystemStatus tcsStatus) { public void update(@Nullable GatewayStatus gatewayStatus, @Nullable TemperatureControlSystemStatus tcsStatus) {
this.gatewayStatus = gatewayStatus; this.gatewayStatus = gatewayStatus;
this.tcsStatus = tcsStatus; this.tcsStatus = tcsStatus;

View File

@ -20,19 +20,27 @@
</state> </state>
</channel-type> </channel-type>
<channel-type id="temperature"> <channel-type id="temperature">
<item-type>Number</item-type> <item-type>Number:Temperature</item-type>
<label>Temperature</label> <label>Temperature</label>
<description>Current zone temperature</description> <description>Current zone temperature</description>
<category>temperature</category> <category>temperature</category>
<state readOnly="true" pattern="%.1f °C"> <tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%">
</state> </state>
</channel-type> </channel-type>
<channel-type id="setpoint"> <channel-type id="setpoint">
<item-type>Number</item-type> <item-type>Number:Temperature</item-type>
<label>Set Point</label> <label>Set Point</label>
<description>Gets or sets the set point of this zone (0 cancels the override).</description> <description>Gets or sets the set point of this zone (0 cancels the override).</description>
<category>heating</category> <category>heating</category>
<state min="0.0" max="35.0" step="0.5" pattern="%.1f °C"/> <tags>
<tag>Setpoint</tag>
<tag>Temperature</tag>
</tags>
<state min="0.0" max="35.0" step="0.5" pattern="%.1f %unit%"/>
</channel-type> </channel-type>
<channel-type id="setpointstatus"> <channel-type id="setpointstatus">
<item-type>String</item-type> <item-type>String</item-type>