[huesync] Fix lost api-token when device goes offline (#18100)
* fix(18062): [huesync] Configuration (API Token) lost if device goes offline Signed-off-by: Patrik Gfeller <patrik.gfeller@proton.me>pull/17817/merge
parent
51df8f302f
commit
a41c6d4b4f
|
@ -23,6 +23,14 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class HueSyncConstants {
|
public class HueSyncConstants {
|
||||||
|
public static class EXCEPTION_TYPES {
|
||||||
|
public static class CONNECTION {
|
||||||
|
public static final String UNAUTHORIZED_401 = "invalidLogin";
|
||||||
|
public static final String NOT_FOUND_404 = "notFound";
|
||||||
|
public static final String INTERNAL_SERVER_ERROR_500 = "deviceError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class ENDPOINTS {
|
public static class ENDPOINTS {
|
||||||
public static final String DEVICE = "device";
|
public static final String DEVICE = "device";
|
||||||
public static final String REGISTRATIONS = "registrations";
|
public static final String REGISTRATIONS = "registrations";
|
||||||
|
@ -81,9 +89,11 @@ public class HueSyncConstants {
|
||||||
public static final String PARAMETER_HOST = "host";
|
public static final String PARAMETER_HOST = "host";
|
||||||
public static final String PARAMETER_PORT = "port";
|
public static final String PARAMETER_PORT = "port";
|
||||||
|
|
||||||
public static final Integer REGISTRATION_INITIAL_DELAY = 3;
|
public static final Integer REGISTRATION_INITIAL_DELAY = 5;
|
||||||
public static final Integer REGISTRATION_INTERVAL = 1;
|
public static final Integer REGISTRATION_INTERVAL = 1;
|
||||||
|
|
||||||
|
public static final Integer POLL_INITIAL_DELAY = 10;
|
||||||
|
|
||||||
public static final String REGISTRATION_ID = "registrationId";
|
public static final String REGISTRATION_ID = "registrationId";
|
||||||
public static final String API_TOKEN = "apiAccessToken";
|
public static final String API_TOKEN = "apiAccessToken";
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.net.URISyntaxException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
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.NonNullByDefault;
|
||||||
|
@ -26,8 +27,6 @@ import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpResponseException;
|
import org.eclipse.jetty.client.HttpResponseException;
|
||||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||||
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.Response;
|
|
||||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
@ -55,8 +54,10 @@ public class HueSyncConnection {
|
||||||
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
|
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
/**
|
/**
|
||||||
* Request format: The Sync Box API can be accessed locally via HTTPS on root level (port 443,
|
* Request format: The Sync Box API can be accessed locally via HTTPS on root
|
||||||
* /api/v1), resource level /api/v1/<resource> and in some cases sub-resource level
|
* level (port 443,
|
||||||
|
* /api/v1), resource level /api/v1/<resource> and in some cases sub-resource
|
||||||
|
* level
|
||||||
* /api/v1/<resource>/<sub-resource>.
|
* /api/v1/<resource>/<sub-resource>.
|
||||||
*/
|
*/
|
||||||
private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s";
|
private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s";
|
||||||
|
@ -72,6 +73,41 @@ public class HueSyncConnection {
|
||||||
|
|
||||||
private Optional<HueSyncAuthenticationResult> authentication = Optional.empty();
|
private Optional<HueSyncAuthenticationResult> authentication = Optional.empty();
|
||||||
|
|
||||||
|
private class Request {
|
||||||
|
|
||||||
|
private final String endpoint;
|
||||||
|
|
||||||
|
private HttpMethod method = HttpMethod.GET;
|
||||||
|
private String payload = "";
|
||||||
|
|
||||||
|
private Request(HttpMethod httpMethod, String endpoint, String payload) {
|
||||||
|
this.method = httpMethod;
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Request(String endpoint) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Request(HttpMethod httpMethod, String endpoint) {
|
||||||
|
this.method = httpMethod;
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContentResponse execute() throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
String uri = String.format(REQUEST_FORMAT, host, port, API, endpoint);
|
||||||
|
|
||||||
|
var request = httpClient.newRequest(uri).method(method).timeout(1, TimeUnit.SECONDS);
|
||||||
|
if (!payload.isBlank()) {
|
||||||
|
request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.toString())
|
||||||
|
.content(new StringContentProvider(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected String registrationId = "";
|
protected String registrationId = "";
|
||||||
|
|
||||||
public HueSyncConnection(HttpClient httpClient, String host, Integer port)
|
public HueSyncConnection(HttpClient httpClient, String host, Integer port)
|
||||||
|
@ -102,47 +138,31 @@ public class HueSyncConnection {
|
||||||
|
|
||||||
// #region protected
|
// #region protected
|
||||||
protected @Nullable <T> T executeRequest(HttpMethod method, String endpoint, String payload,
|
protected @Nullable <T> T executeRequest(HttpMethod method, String endpoint, String payload,
|
||||||
@Nullable Class<T> type) {
|
@Nullable Class<T> type) throws HueSyncConnectionException {
|
||||||
try {
|
|
||||||
return this.processedResponse(this.executeRequest(method, endpoint, payload), type);
|
return this.executeRequest(new Request(method, endpoint, payload), type);
|
||||||
} catch (ExecutionException e) {
|
|
||||||
this.handleExecutionException(e);
|
|
||||||
} catch (InterruptedException | TimeoutException e) {
|
|
||||||
this.logger.warn("{}", e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
protected @Nullable <T> T executeRequest(HttpMethod httpMethod, String endpoint, @Nullable Class<T> type)
|
||||||
|
throws HueSyncConnectionException {
|
||||||
|
return this.executeRequest(new Request(httpMethod, endpoint), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected @Nullable <T> T executeGetRequest(String endpoint, Class<T> type) {
|
protected @Nullable <T> T executeGetRequest(String endpoint, Class<T> type) throws HueSyncConnectionException {
|
||||||
try {
|
return this.executeRequest(new Request(endpoint), type);
|
||||||
return this.processedResponse(this.executeGetRequest(endpoint), type);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
this.handleExecutionException(e);
|
|
||||||
} catch (InterruptedException | TimeoutException e) {
|
|
||||||
this.logger.warn("{}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isRegistered() {
|
protected boolean isRegistered() {
|
||||||
return this.authentication.isPresent();
|
return this.authentication.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void unregisterDevice() {
|
protected void unregisterDevice() throws HueSyncConnectionException {
|
||||||
if (this.isRegistered()) {
|
if (this.isRegistered()) {
|
||||||
try {
|
|
||||||
String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId;
|
String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId;
|
||||||
ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint);
|
|
||||||
|
|
||||||
if (response.getStatus() == HttpStatus.OK_200) {
|
this.executeRequest(HttpMethod.DELETE, endpoint, null);
|
||||||
this.removeAuthentication();
|
this.removeAuthentication();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
|
||||||
this.logger.warn("{}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void dispose() {
|
protected void dispose() {
|
||||||
|
@ -151,93 +171,55 @@ public class HueSyncConnection {
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region private
|
// #region private
|
||||||
private @Nullable <T> T processedResponse(Response response, @Nullable Class<T> type) {
|
|
||||||
int status = response.getStatus();
|
private @Nullable <T> T executeRequest(Request request, @Nullable Class<T> type) throws HueSyncConnectionException {
|
||||||
|
String message = "@text/connection.generic-error";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
ContentResponse response = request.execute();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 400 Invalid State: Registration in progress
|
* 400 Invalid State: Registration in progress
|
||||||
*
|
*
|
||||||
* 401 Authentication failed: If credentials are missing or invalid, errors out. If
|
* 401 Authentication failed: If credentials are missing or invalid, errors out.
|
||||||
* credentials are missing, continues on to GET only the Configuration state when
|
* If
|
||||||
|
* credentials are missing, continues on to GET only the Configuration state
|
||||||
|
* when
|
||||||
* unauthenticated, to allow for device identification.
|
* unauthenticated, to allow for device identification.
|
||||||
*
|
*
|
||||||
* 404 Invalid URI Path: Accessing URI path which is not supported
|
* 404 Invalid URI Path: Accessing URI path which is not supported
|
||||||
*
|
*
|
||||||
* 500 Internal: Internal errors like out of memory
|
* 500 Internal: Internal errors like out of memory
|
||||||
*/
|
*/
|
||||||
switch (status) {
|
switch (response.getStatus()) {
|
||||||
case HttpStatus.OK_200 -> {
|
case HttpStatus.OK_200 -> {
|
||||||
return (type != null && (response instanceof ContentResponse))
|
return this.deserialize(response.getContentAsString(), type);
|
||||||
? this.deserialize(((ContentResponse) response).getContentAsString(), type)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
case HttpStatus.BAD_REQUEST_400 -> this.logger.debug("registration in progress: no token received yet");
|
|
||||||
case HttpStatus.UNAUTHORIZED_401 -> {
|
|
||||||
this.authentication = Optional.empty();
|
|
||||||
throw new HueSyncConnectionException("@text/connection.invalid-login");
|
|
||||||
}
|
|
||||||
case HttpStatus.NOT_FOUND_404 -> this.logger.warn("invalid device URI or API endpoint");
|
|
||||||
case HttpStatus.INTERNAL_SERVER_ERROR_500 -> this.logger.warn("hue sync box server problem");
|
|
||||||
default -> this.logger.warn("unexpected HTTP status: {}", status);
|
|
||||||
}
|
|
||||||
} catch (HueSyncConnectionException e) {
|
|
||||||
this.logger.warn("{}", e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
case HttpStatus.BAD_REQUEST_400 -> {
|
||||||
|
logger.debug("registration in progress: no token received yet");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
case HttpStatus.UNAUTHORIZED_401 -> message = "@text/connection.invalid-login";
|
||||||
|
case HttpStatus.NOT_FOUND_404 -> message = "@text/connection.generic-error";
|
||||||
|
}
|
||||||
|
throw new HueSyncConnectionException(message, new HttpResponseException(message, response));
|
||||||
|
} catch (JsonProcessingException | InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
|
|
||||||
private @Nullable <T> T deserialize(String json, Class<T> type) {
|
var logMessage = message + " {}";
|
||||||
try {
|
this.logger.warn(logMessage, e.toString());
|
||||||
return OBJECT_MAPPER.readValue(json, type);
|
|
||||||
} catch (JsonProcessingException | NoClassDefFoundError e) {
|
|
||||||
this.logger.error("{}", e.getMessage());
|
|
||||||
|
|
||||||
return null;
|
throw new HueSyncConnectionException(message, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContentResponse executeRequest(HttpMethod method, String endpoint)
|
private @Nullable <T> T deserialize(String json, @Nullable Class<T> type) throws JsonProcessingException {
|
||||||
throws InterruptedException, TimeoutException, ExecutionException {
|
return type == null ? null : OBJECT_MAPPER.readValue(json, type);
|
||||||
return this.executeRequest(method, endpoint, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContentResponse executeGetRequest(String endpoint)
|
|
||||||
throws InterruptedException, ExecutionException, TimeoutException {
|
|
||||||
String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint);
|
|
||||||
|
|
||||||
return httpClient.GET(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload)
|
|
||||||
throws InterruptedException, TimeoutException, ExecutionException {
|
|
||||||
String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint);
|
|
||||||
|
|
||||||
Request request = this.httpClient.newRequest(uri).method(method);
|
|
||||||
|
|
||||||
this.logger.trace("uri: {}", uri);
|
|
||||||
this.logger.trace("method: {}", method);
|
|
||||||
this.logger.trace("payload: {}", payload);
|
|
||||||
|
|
||||||
if (!payload.isBlank()) {
|
|
||||||
request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.toString())
|
|
||||||
.content(new StringContentProvider(payload));
|
|
||||||
}
|
|
||||||
|
|
||||||
return request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleExecutionException(ExecutionException e) {
|
|
||||||
this.logger.warn("{}", e.getMessage());
|
|
||||||
|
|
||||||
Throwable cause = e.getCause();
|
|
||||||
if (cause != null && cause instanceof HttpResponseException) {
|
|
||||||
processedResponse(((HttpResponseException) cause).getResponse(), null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeAuthentication() {
|
private void removeAuthentication() {
|
||||||
AuthenticationStore store = this.httpClient.getAuthenticationStore();
|
AuthenticationStore store = this.httpClient.getAuthenticationStore();
|
||||||
store.clearAuthenticationResults();
|
store.clearAuthenticationResults();
|
||||||
|
|
||||||
this.httpClient.setAuthenticationStore(store);
|
this.httpClient.setAuthenticationStore(store);
|
||||||
|
|
||||||
this.registrationId = "";
|
this.registrationId = "";
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistra
|
||||||
import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest;
|
import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest;
|
||||||
import org.openhab.binding.huesync.internal.config.HueSyncConfiguration;
|
import org.openhab.binding.huesync.internal.config.HueSyncConfiguration;
|
||||||
import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
|
import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
|
||||||
|
import org.openhab.binding.huesync.internal.types.HueSyncExceptionHandler;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
|
@ -55,11 +56,14 @@ public class HueSyncDeviceConnection {
|
||||||
private final Logger logger = LoggerFactory.getLogger(HueSyncDeviceConnection.class);
|
private final Logger logger = LoggerFactory.getLogger(HueSyncDeviceConnection.class);
|
||||||
|
|
||||||
private final HueSyncConnection connection;
|
private final HueSyncConnection connection;
|
||||||
|
private final HueSyncExceptionHandler exceptionHandler;
|
||||||
|
|
||||||
private final Map<String, Consumer<Command>> deviceCommandExecutors = new HashMap<>();
|
private final Map<String, Consumer<Command>> deviceCommandExecutors = new HashMap<>();
|
||||||
|
|
||||||
public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration)
|
public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration,
|
||||||
throws CertificateException, IOException, URISyntaxException {
|
HueSyncExceptionHandler exceptionHandler) throws CertificateException, IOException, URISyntaxException {
|
||||||
|
|
||||||
|
this.exceptionHandler = exceptionHandler;
|
||||||
this.connection = new HueSyncConnection(httpClient, configuration.host, configuration.port);
|
this.connection = new HueSyncConnection(httpClient, configuration.host, configuration.port);
|
||||||
|
|
||||||
registerCommandHandlers();
|
registerCommandHandlers();
|
||||||
|
@ -109,7 +113,11 @@ public class HueSyncDeviceConnection {
|
||||||
|
|
||||||
String json = String.format("{ \"%s\": %s }", key, value);
|
String json = String.format("{ \"%s\": %s }", key, value);
|
||||||
|
|
||||||
|
try {
|
||||||
this.connection.executeRequest(HttpMethod.PUT, ENDPOINTS.EXECUTION, json, null);
|
this.connection.executeRequest(HttpMethod.PUT, ENDPOINTS.EXECUTION, json, null);
|
||||||
|
} catch (HueSyncConnectionException exception) {
|
||||||
|
exceptionHandler.handle(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
@ -131,29 +139,29 @@ public class HueSyncDeviceConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable HueSyncDevice getDeviceInfo() {
|
public @Nullable HueSyncDevice getDeviceInfo() throws Exception {
|
||||||
return this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDevice.class);
|
return this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDevice.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable HueSyncDeviceDetailed getDetailedDeviceInfo() {
|
public @Nullable HueSyncDeviceDetailed getDetailedDeviceInfo() throws Exception {
|
||||||
return this.connection.isRegistered()
|
return this.connection.isRegistered()
|
||||||
? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDeviceDetailed.class)
|
? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDeviceDetailed.class)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable HueSyncHdmi getHdmiInfo() {
|
public @Nullable HueSyncHdmi getHdmiInfo() throws Exception {
|
||||||
return this.connection.isRegistered()
|
return this.connection.isRegistered()
|
||||||
? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmi.class)
|
? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmi.class)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable HueSyncExecution getExecutionInfo() {
|
public @Nullable HueSyncExecution getExecutionInfo() throws Exception {
|
||||||
return this.connection.isRegistered()
|
return this.connection.isRegistered()
|
||||||
? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.EXECUTION, "", HueSyncExecution.class)
|
? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.EXECUTION, "", HueSyncExecution.class)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable HueSyncRegistration registerDevice(String id) throws HueSyncConnectionException {
|
public @Nullable HueSyncRegistration registerDevice(String id) throws Exception {
|
||||||
if (!id.isBlank()) {
|
if (!id.isBlank()) {
|
||||||
try {
|
try {
|
||||||
HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest();
|
HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest();
|
||||||
|
@ -181,7 +189,11 @@ public class HueSyncDeviceConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterDevice() {
|
public void unregisterDevice() {
|
||||||
|
try {
|
||||||
this.connection.unregisterDevice();
|
this.connection.unregisterDevice();
|
||||||
|
} catch (HueSyncConnectionException e) {
|
||||||
|
this.logger.warn("{}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
package org.openhab.binding.huesync.internal.exceptions;
|
package org.openhab.binding.huesync.internal.exceptions;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -21,8 +22,18 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class HueSyncConnectionException extends HueSyncException {
|
public class HueSyncConnectionException extends HueSyncException {
|
||||||
private static final long serialVersionUID = 0L;
|
private static final long serialVersionUID = 0L;
|
||||||
|
private @Nullable Exception innerException = null;
|
||||||
|
|
||||||
|
public HueSyncConnectionException(String message, Exception exception) {
|
||||||
|
super(message);
|
||||||
|
this.innerException = exception;
|
||||||
|
}
|
||||||
|
|
||||||
public HueSyncConnectionException(String message) {
|
public HueSyncConnectionException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Exception getInnerException() {
|
||||||
|
return this.innerException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
package org.openhab.binding.huesync.internal.exceptions;
|
package org.openhab.binding.huesync.internal.exceptions;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer;
|
import org.openhab.binding.huesync.internal.i18n.ResourceHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all HueSyncExceptions
|
* Base class for all HueSyncExceptions
|
||||||
|
@ -25,6 +25,6 @@ public abstract class HueSyncException extends Exception {
|
||||||
private static final long serialVersionUID = 0L;
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
public HueSyncException(String message) {
|
public HueSyncException(String message) {
|
||||||
super(message.startsWith("@text") ? HueSyncLocalizer.getResourceString(message) : message);
|
super(message.startsWith("@text") ? ResourceHelper.getResourceString(message) : message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,6 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.huesync.internal.factory;
|
package org.openhab.binding.huesync.internal.factory;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -31,8 +28,6 @@ import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.Reference;
|
import org.osgi.service.component.annotations.Reference;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HueSyncHandlerFactory} is responsible for creating things and
|
* The {@link HueSyncHandlerFactory} is responsible for creating things and
|
||||||
|
@ -44,9 +39,7 @@ import org.slf4j.LoggerFactory;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class)
|
@Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class)
|
||||||
public class HueSyncHandlerFactory extends BaseThingHandlerFactory {
|
public class HueSyncHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
private final HttpClientFactory httpClientFactory;
|
private final HttpClientFactory httpClientFactory;
|
||||||
private final Logger logger = LoggerFactory.getLogger(HueSyncHandlerFactory.class);
|
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception {
|
public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception {
|
||||||
|
@ -66,12 +59,7 @@ public class HueSyncHandlerFactory extends BaseThingHandlerFactory {
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
if (HueSyncConstants.THING_TYPE_UID.equals(thingTypeUID)) {
|
if (HueSyncConstants.THING_TYPE_UID.equals(thingTypeUID)) {
|
||||||
try {
|
|
||||||
return new HueSyncHandler(thing, this.httpClientFactory);
|
return new HueSyncHandler(thing, this.httpClientFactory);
|
||||||
} catch (IOException | URISyntaxException | CertificateException e) {
|
|
||||||
this.logger.warn("It was not possible to create a handler for {}: {}", thingTypeUID.getId(),
|
|
||||||
e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -12,9 +12,6 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.huesync.internal.handler;
|
package org.openhab.binding.huesync.internal.handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
@ -25,6 +22,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpResponseException;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.openhab.binding.huesync.internal.HdmiChannels;
|
import org.openhab.binding.huesync.internal.HdmiChannels;
|
||||||
import org.openhab.binding.huesync.internal.HueSyncConstants;
|
import org.openhab.binding.huesync.internal.HueSyncConstants;
|
||||||
import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
|
import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
|
||||||
|
@ -36,9 +35,11 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistra
|
||||||
import org.openhab.binding.huesync.internal.config.HueSyncConfiguration;
|
import org.openhab.binding.huesync.internal.config.HueSyncConfiguration;
|
||||||
import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
|
import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
|
||||||
import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException;
|
import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException;
|
||||||
|
import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
|
||||||
import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask;
|
import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask;
|
||||||
import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask;
|
import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask;
|
||||||
import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTaskResult;
|
import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTaskResult;
|
||||||
|
import org.openhab.binding.huesync.internal.types.HueSyncExceptionHandler;
|
||||||
import org.openhab.core.config.core.Configuration;
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
@ -49,6 +50,7 @@ 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;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.thing.ThingStatusInfo;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
|
@ -63,34 +65,104 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class HueSyncHandler extends BaseThingHandler {
|
public class HueSyncHandler extends BaseThingHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception handler implementation
|
||||||
|
*
|
||||||
|
* @author Patrik Gfeller - Initial contribution
|
||||||
|
* @author Patrik Gfeller - Issue #18062, improve connection exception handling.
|
||||||
|
*/
|
||||||
|
private class ExceptionHandler implements HueSyncExceptionHandler {
|
||||||
|
private final HueSyncHandler handler;
|
||||||
|
|
||||||
|
private ExceptionHandler(HueSyncHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Exception exception) {
|
||||||
|
ThingStatusDetail detail = ThingStatusDetail.COMMUNICATION_ERROR;
|
||||||
|
String description;
|
||||||
|
|
||||||
|
if (exception instanceof HueSyncConnectionException connectionException) {
|
||||||
|
if (connectionException.getInnerException() instanceof HttpResponseException innerException) {
|
||||||
|
switch (innerException.getResponse().getStatus()) {
|
||||||
|
case HttpStatus.BAD_REQUEST_400 -> {
|
||||||
|
detail = ThingStatusDetail.CONFIGURATION_PENDING;
|
||||||
|
}
|
||||||
|
case HttpStatus.UNAUTHORIZED_401 -> {
|
||||||
|
detail = ThingStatusDetail.CONFIGURATION_ERROR;
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
detail = ThingStatusDetail.COMMUNICATION_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description = connectionException.getLocalizedMessage();
|
||||||
|
} else {
|
||||||
|
detail = ThingStatusDetail.COMMUNICATION_ERROR;
|
||||||
|
description = exception.getLocalizedMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThingStatusInfo statusInfo = new ThingStatusInfo(ThingStatus.OFFLINE, detail, description);
|
||||||
|
this.handler.thing.setStatusInfo(statusInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String REGISTER = "Registration";
|
private static final String REGISTER = "Registration";
|
||||||
private static final String POLL = "Update";
|
private static final String POLL = "Update";
|
||||||
|
|
||||||
private static final String PROPERTY_API_VERSION = "apiVersion";
|
private static final String PROPERTY_API_VERSION = "apiVersion";
|
||||||
|
|
||||||
|
private final ExceptionHandler exceptionHandler;
|
||||||
private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class);
|
||||||
|
|
||||||
Map<String, @Nullable ScheduledFuture<?>> tasks = new HashMap<>();
|
Map<String, @Nullable ScheduledFuture<?>> tasks = new HashMap<>();
|
||||||
|
|
||||||
private Optional<HueSyncDevice> deviceInfo = Optional.empty();
|
private Optional<HueSyncDevice> deviceInfo = Optional.empty();
|
||||||
|
private Optional<HueSyncDeviceConnection> connection = Optional.empty();
|
||||||
|
|
||||||
private final HueSyncDeviceConnection connection;
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory)
|
public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) {
|
||||||
throws CertificateException, IOException, URISyntaxException {
|
|
||||||
super(thing);
|
super(thing);
|
||||||
|
|
||||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
this.updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
|
||||||
this.connection = new HueSyncDeviceConnection(this.httpClient, this.getConfigAs(HueSyncConfiguration.class));
|
this.exceptionHandler = new ExceptionHandler(this);
|
||||||
|
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region override
|
||||||
|
@Override
|
||||||
|
protected Configuration editConfiguration() {
|
||||||
|
this.logger.debug("Configuration change detected.");
|
||||||
|
|
||||||
|
return new Configuration(this.thing.getConfiguration().getProperties());
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
// #region private
|
// #region private
|
||||||
private Runnable initializeConnection() {
|
private Runnable initializeConnection() {
|
||||||
return () -> {
|
return () -> {
|
||||||
this.deviceInfo = Optional.ofNullable(this.connection.getDeviceInfo());
|
try {
|
||||||
|
var connectionInstance = new HueSyncDeviceConnection(this.httpClient,
|
||||||
|
this.getConfigAs(HueSyncConfiguration.class), this.exceptionHandler);
|
||||||
|
|
||||||
|
this.connection = Optional.of(connectionInstance);
|
||||||
|
this.deviceInfo = Optional.ofNullable(connectionInstance.getDeviceInfo());
|
||||||
|
|
||||||
this.deviceInfo.ifPresent(info -> {
|
this.deviceInfo.ifPresent(info -> {
|
||||||
|
connect(connectionInstance, info);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.exceptionHandler.handle(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect(HueSyncDeviceConnection connectionInstance, HueSyncDevice info) {
|
||||||
setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId != null ? info.uniqueId : "");
|
setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId != null ? info.uniqueId : "");
|
||||||
setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType);
|
setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType);
|
||||||
setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion);
|
setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion);
|
||||||
|
@ -100,33 +172,23 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
try {
|
try {
|
||||||
this.checkCompatibility();
|
this.checkCompatibility();
|
||||||
} catch (HueSyncApiException e) {
|
} catch (HueSyncApiException e) {
|
||||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
this.exceptionHandler.handle(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.startTasks();
|
this.startTasks(connectionInstance);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopTask(@Nullable ScheduledFuture<?> task) {
|
|
||||||
if (task == null || task.isCancelled() || task.isDone()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
task.cancel(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> executeTask(Runnable task, long initialDelay, long interval) {
|
private @Nullable ScheduledFuture<?> executeTask(Runnable task, long initialDelay, long interval) {
|
||||||
return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS);
|
return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startTasks() {
|
private synchronized void startTasks(HueSyncDeviceConnection connection) {
|
||||||
this.stopTasks();
|
this.stopTasks();
|
||||||
|
|
||||||
this.connection.updateConfiguration(this.getConfigAs(HueSyncConfiguration.class));
|
connection.updateConfiguration(this.getConfigAs(HueSyncConfiguration.class));
|
||||||
|
|
||||||
Runnable task = null;
|
Runnable task = null;
|
||||||
String id = this.connection.isRegistered() ? POLL : REGISTER;
|
String id = connection.isRegistered() ? POLL : REGISTER;
|
||||||
|
|
||||||
this.logger.debug("startTasks - [{}]", id);
|
this.logger.debug("startTasks - [{}]", id);
|
||||||
|
|
||||||
|
@ -135,13 +197,13 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case POLL -> {
|
case POLL -> {
|
||||||
initialDelay = 0;
|
|
||||||
interval = this.getConfigAs(HueSyncConfiguration.class).statusUpdateInterval;
|
|
||||||
|
|
||||||
this.updateStatus(ThingStatus.ONLINE);
|
this.updateStatus(ThingStatus.ONLINE);
|
||||||
|
|
||||||
task = new HueSyncUpdateTask(this.connection, this.deviceInfo.get(),
|
initialDelay = HueSyncConstants.POLL_INITIAL_DELAY;
|
||||||
deviceStatus -> this.handleUpdate(deviceStatus));
|
|
||||||
|
interval = this.getConfigAs(HueSyncConfiguration.class).statusUpdateInterval;
|
||||||
|
task = new HueSyncUpdateTask(connection, this.deviceInfo.get(),
|
||||||
|
deviceStatus -> this.handleUpdate(deviceStatus), this.exceptionHandler);
|
||||||
}
|
}
|
||||||
case REGISTER -> {
|
case REGISTER -> {
|
||||||
initialDelay = HueSyncConstants.REGISTRATION_INITIAL_DELAY;
|
initialDelay = HueSyncConstants.REGISTRATION_INITIAL_DELAY;
|
||||||
|
@ -150,8 +212,8 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||||
"@text/thing.config.huesync.box.registration");
|
"@text/thing.config.huesync.box.registration");
|
||||||
|
|
||||||
task = new HueSyncRegistrationTask(this.connection, this.deviceInfo.get(),
|
task = new HueSyncRegistrationTask(connection, this.deviceInfo.get(),
|
||||||
registration -> this.handleRegistration(registration));
|
registration -> this.handleRegistration(registration, connection), this.exceptionHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +223,7 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopTasks() {
|
private synchronized void stopTasks() {
|
||||||
logger.debug("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet()));
|
logger.debug("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet()));
|
||||||
|
|
||||||
this.tasks.values().forEach(task -> this.stopTask(task));
|
this.tasks.values().forEach(task -> this.stopTask(task));
|
||||||
|
@ -171,32 +233,42 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
"@text/thing.config.huesync.box.registration");
|
"@text/thing.config.huesync.box.registration");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void stopTask(@Nullable ScheduledFuture<?> task) {
|
||||||
|
if (task == null || task.isCancelled() || task.isDone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleUpdate(@Nullable HueSyncUpdateTaskResult dto) {
|
private void handleUpdate(@Nullable HueSyncUpdateTaskResult dto) {
|
||||||
try {
|
synchronized (this) {
|
||||||
HueSyncUpdateTaskResult update = Optional.ofNullable(dto).get();
|
ThingStatus status = this.thing.getStatus();
|
||||||
|
|
||||||
try {
|
switch (status) {
|
||||||
this.updateFirmwareInformation(Optional.ofNullable(update.deviceStatus).get());
|
case ONLINE:
|
||||||
} catch (NoSuchElementException e) {
|
Optional.ofNullable(dto).ifPresent(taskResult -> {
|
||||||
this.logMissingUpdateInformation("device");
|
Optional.ofNullable(taskResult.deviceStatus)
|
||||||
}
|
.ifPresent(payload -> this.updateFirmwareInformation(payload));
|
||||||
|
Optional.ofNullable(taskResult.hdmiStatus)
|
||||||
|
.ifPresent(payload -> this.updateHdmiInformation(payload));
|
||||||
|
Optional.ofNullable(taskResult.execution)
|
||||||
|
.ifPresent(payload -> this.updateExecutionInformation(payload));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case OFFLINE:
|
||||||
|
this.stopTasks();
|
||||||
|
|
||||||
this.updateHdmiInformation(Optional.ofNullable(update.hdmiStatus).get());
|
this.connection.ifPresent(connectionInstance -> {
|
||||||
this.updateExecutionInformation(Optional.ofNullable(update.execution).get());
|
this.deviceInfo.ifPresent(deviceInfoInstance -> {
|
||||||
} catch (NoSuchElementException e) {
|
this.connect(connectionInstance, deviceInfoInstance);
|
||||||
Configuration configuration = this.editConfiguration();
|
});
|
||||||
|
});
|
||||||
configuration.put(HueSyncConstants.REGISTRATION_ID, "");
|
break;
|
||||||
configuration.put(HueSyncConstants.API_TOKEN, "");
|
default:
|
||||||
|
this.logger.debug("Unable to execute update - Status: [{}]", status);
|
||||||
this.updateConfiguration(configuration);
|
|
||||||
|
|
||||||
this.startTasks();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logMissingUpdateInformation(String api) {
|
|
||||||
this.logger.warn("Device information - {} status missing", api);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateHdmiInformation(HueSyncHdmi hdmiStatus) {
|
private void updateHdmiInformation(HueSyncHdmi hdmiStatus) {
|
||||||
|
@ -240,7 +312,7 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
this.updateState(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, new DecimalType(executionStatus.brightness));
|
this.updateState(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, new DecimalType(executionStatus.brightness));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRegistration(HueSyncRegistration registration) {
|
private void handleRegistration(HueSyncRegistration registration, HueSyncDeviceConnection connection) {
|
||||||
this.stopTasks();
|
this.stopTasks();
|
||||||
|
|
||||||
setProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId);
|
setProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId);
|
||||||
|
@ -252,7 +324,7 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
|
|
||||||
this.updateConfiguration(configuration);
|
this.updateConfiguration(configuration);
|
||||||
|
|
||||||
this.startTasks();
|
this.startTasks(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCompatibility() throws HueSyncApiException {
|
private void checkCompatibility() throws HueSyncApiException {
|
||||||
|
@ -291,25 +363,25 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Override
|
// #region Override
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
try {
|
try {
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
|
||||||
|
|
||||||
this.stopTasks();
|
this.stopTasks();
|
||||||
|
this.updateStatus(ThingStatus.OFFLINE);
|
||||||
|
|
||||||
scheduler.execute(initializeConnection());
|
scheduler.execute(initializeConnection());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
this.stopTasks();
|
||||||
this.logger.warn("{}", e.getMessage());
|
this.logger.warn("{}", e.getMessage());
|
||||||
|
this.exceptionHandler.handle(e);
|
||||||
this.updateStatus(ThingStatus.OFFLINE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
if (thing.getStatus() != ThingStatus.ONLINE || this.connection.isEmpty()) {
|
||||||
this.logger.warn("Device status: {} - Command {} for chanel {} will be ignored",
|
this.logger.warn("Device status: {} - Command {} for channel {} will be ignored",
|
||||||
thing.getStatus().toString(), command.toFullString(), channelUID.toString());
|
thing.getStatus().toString(), command.toFullString(), channelUID.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -321,28 +393,32 @@ public class HueSyncHandler extends BaseThingHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.executeCommand(channel, command);
|
this.connection.get().executeCommand(channel, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
synchronized (this) {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.stopTasks();
|
this.stopTasks();
|
||||||
this.connection.dispose();
|
this.connection.orElseThrow().dispose();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
this.logger.warn("{}", e.getMessage());
|
this.logger.warn("{}", e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
this.logger.debug("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID());
|
this.logger.debug("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRemoval() {
|
public void handleRemoval() {
|
||||||
super.handleRemoval();
|
super.handleRemoval();
|
||||||
|
|
||||||
this.connection.unregisterDevice();
|
if (this.connection.isPresent()) {
|
||||||
|
this.connection.get().unregisterDevice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
|
@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
|
import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
|
||||||
import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration;
|
import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration;
|
||||||
import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
|
import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
|
||||||
import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
|
import org.openhab.binding.huesync.internal.types.HueSyncExceptionHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -33,10 +33,13 @@ public class HueSyncRegistrationTask implements Runnable {
|
||||||
|
|
||||||
private final HueSyncDeviceConnection connection;
|
private final HueSyncDeviceConnection connection;
|
||||||
private final HueSyncDevice deviceInfo;
|
private final HueSyncDevice deviceInfo;
|
||||||
|
private final HueSyncExceptionHandler exceptionHandler;
|
||||||
private final Consumer<HueSyncRegistration> action;
|
private final Consumer<HueSyncRegistration> action;
|
||||||
|
|
||||||
public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
|
public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
|
||||||
Consumer<HueSyncRegistration> action) {
|
Consumer<HueSyncRegistration> action, HueSyncExceptionHandler exceptionHandler) {
|
||||||
|
|
||||||
|
this.exceptionHandler = exceptionHandler;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.deviceInfo = deviceInfo;
|
this.deviceInfo = deviceInfo;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
|
@ -61,8 +64,8 @@ public class HueSyncRegistrationTask implements Runnable {
|
||||||
|
|
||||||
this.action.accept(registration);
|
this.action.accept(registration);
|
||||||
}
|
}
|
||||||
} catch (HueSyncConnectionException e) {
|
} catch (Exception e) {
|
||||||
this.logger.warn("{}", e.getMessage());
|
this.exceptionHandler.handle(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
|
import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
|
||||||
import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
|
import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
|
||||||
|
import org.openhab.binding.huesync.internal.types.HueSyncExceptionHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -34,10 +35,13 @@ public class HueSyncUpdateTask implements Runnable {
|
||||||
private final HueSyncDeviceConnection connection;
|
private final HueSyncDeviceConnection connection;
|
||||||
private final HueSyncDevice deviceInfo;
|
private final HueSyncDevice deviceInfo;
|
||||||
|
|
||||||
|
private final HueSyncExceptionHandler exceptionHandler;
|
||||||
private final Consumer<@Nullable HueSyncUpdateTaskResult> action;
|
private final Consumer<@Nullable HueSyncUpdateTaskResult> action;
|
||||||
|
|
||||||
public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
|
public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
|
||||||
Consumer<@Nullable HueSyncUpdateTaskResult> action) {
|
Consumer<@Nullable HueSyncUpdateTaskResult> action, HueSyncExceptionHandler exceptionHandler) {
|
||||||
|
|
||||||
|
this.exceptionHandler = exceptionHandler;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.deviceInfo = deviceInfo;
|
this.deviceInfo = deviceInfo;
|
||||||
|
|
||||||
|
@ -46,24 +50,21 @@ public class HueSyncUpdateTask implements Runnable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
|
||||||
this.logger.debug("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType,
|
|
||||||
this.deviceInfo.uniqueId);
|
|
||||||
|
|
||||||
if (!this.connection.isRegistered()) {
|
|
||||||
this.action.accept(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
HueSyncUpdateTaskResult updateInfo = new HueSyncUpdateTaskResult();
|
HueSyncUpdateTaskResult updateInfo = new HueSyncUpdateTaskResult();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType,
|
||||||
|
this.deviceInfo.uniqueId);
|
||||||
|
|
||||||
updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo();
|
updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo();
|
||||||
updateInfo.hdmiStatus = this.connection.getHdmiInfo();
|
updateInfo.hdmiStatus = this.connection.getHdmiInfo();
|
||||||
updateInfo.execution = this.connection.getExecutionInfo();
|
updateInfo.execution = this.connection.getExecutionInfo();
|
||||||
|
|
||||||
this.action.accept(updateInfo);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
this.logger.debug("{}", e.getMessage());
|
this.logger.warn("{}", e.getMessage());
|
||||||
this.action.accept(null);
|
|
||||||
|
this.exceptionHandler.handle(e);
|
||||||
|
} finally {
|
||||||
|
this.action.accept(updateInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,9 @@ import org.osgi.framework.ServiceReference;
|
||||||
* @author Patrik Gfeller - Initial Contribution
|
* @author Patrik Gfeller - Initial Contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class HueSyncLocalizer {
|
public class ResourceHelper {
|
||||||
private static final Locale LOCALE = Locale.ENGLISH;
|
private static final Locale LOCALE = Locale.ENGLISH;
|
||||||
private static final BundleContext BUNDLE_CONTEXT = FrameworkUtil.getBundle(HueSyncLocalizer.class)
|
private static final BundleContext BUNDLE_CONTEXT = FrameworkUtil.getBundle(ResourceHelper.class)
|
||||||
.getBundleContext();
|
.getBundleContext();
|
||||||
private static final ServiceReference<TranslationProvider> SERVICE_REFERENCE = BUNDLE_CONTEXT
|
private static final ServiceReference<TranslationProvider> SERVICE_REFERENCE = BUNDLE_CONTEXT
|
||||||
.getServiceReference(TranslationProvider.class);
|
.getServiceReference(TranslationProvider.class);
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 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.huesync.internal.types;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for exception handler implementations
|
||||||
|
*
|
||||||
|
* @author Patrik Gfeller - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface HueSyncExceptionHandler {
|
||||||
|
|
||||||
|
void handle(Exception exception);
|
||||||
|
}
|
|
@ -91,15 +91,13 @@ channel-type.huesync.execution-mode.command.option.music = Music
|
||||||
channel-type.huesync.execution-sync-active.label = Synchronization Active
|
channel-type.huesync.execution-sync-active.label = Synchronization Active
|
||||||
channel-type.huesync.execution-sync-active.description = <p> <b>OFF</b> in case of <i>powersave</i> or <i>passthrough</i> mode, and <b>ON</b> in case of <i>video</i>, <i>game</i> or <i>music</i> mode. </p> <p> When changed from <b>OFF</b> to <b>ON</b>, it will start syncing in last used mode for current source. When changed from <b>ON</b> to <b>OFF</b>, will set <i>passthrough</i> mode. </p>
|
channel-type.huesync.execution-sync-active.description = <p> <b>OFF</b> in case of <i>powersave</i> or <i>passthrough</i> mode, and <b>ON</b> in case of <i>video</i>, <i>game</i> or <i>music</i> mode. </p> <p> When changed from <b>OFF</b> to <b>ON</b>, it will start syncing in last used mode for current source. When changed from <b>ON</b> to <b>OFF</b>, will set <i>passthrough</i> mode. </p>
|
||||||
|
|
||||||
# *** exceptions ***
|
|
||||||
|
|
||||||
exception.generic.connection = "Unable to connect to device."
|
|
||||||
|
|
||||||
# api & connection exceptions
|
# api & connection exceptions
|
||||||
|
|
||||||
api.minimal-version = Only devices with API level >= 7 are supported
|
api.minimal-version = Only devices with API level >= 7 are supported
|
||||||
api.communication-problem = Communication problem with the device
|
api.communication-problem = Unable to communicate with the device (API)
|
||||||
connection.invalid-login = Invalid or missing credentials
|
connection.invalid-login = Invalid or missing credentials
|
||||||
|
connection.generic-error = Unable to communicate with the device (Generic)
|
||||||
|
connection.server-error = Device was not able to process the request.
|
||||||
|
|
||||||
# registration
|
# registration
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue