mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-02-04 03:14:07 +01:00
[hue] Fix and improve error logging and status descriptions for API v2 (#15512)
* Provide detailed error information on failed commands * Log as info when command succeeds * Revert collect(Collectors.toList()) refactoring * Provide exception message in status description Fixes #15511 --------- Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
14c3c0cd63
commit
9bbcb85f59
@ -1081,10 +1081,11 @@ public class Clip2Bridge implements Closeable {
|
|||||||
* accessing the session while it is being recreated.
|
* accessing the session while it is being recreated.
|
||||||
*
|
*
|
||||||
* @param resource the resource to put.
|
* @param resource the resource to put.
|
||||||
|
* @return the resource, which may contain errors.
|
||||||
* @throws ApiException if something fails.
|
* @throws ApiException if something fails.
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public void putResource(Resource resource) throws ApiException, InterruptedException {
|
public Resources putResource(Resource resource) throws ApiException, InterruptedException {
|
||||||
Stream stream = null;
|
Stream stream = null;
|
||||||
try (Throttler throttler = new Throttler(MAX_CONCURRENT_STREAMS);
|
try (Throttler throttler = new Throttler(MAX_CONCURRENT_STREAMS);
|
||||||
SessionSynchronizer sessionSynchronizer = new SessionSynchronizer(false)) {
|
SessionSynchronizer sessionSynchronizer = new SessionSynchronizer(false)) {
|
||||||
@ -1106,17 +1107,17 @@ public class Clip2Bridge implements Closeable {
|
|||||||
String contentType = contentStreamListener.getContentType();
|
String contentType = contentStreamListener.getContentType();
|
||||||
int status = contentStreamListener.getStatus();
|
int status = contentStreamListener.getStatus();
|
||||||
LOGGER.trace("HTTP/2 {} (Content-Type: {}) << {}", status, contentType, contentJson);
|
LOGGER.trace("HTTP/2 {} (Content-Type: {}) << {}", status, contentType, contentJson);
|
||||||
if (status != HttpStatus.OK_200) {
|
if (!HttpStatus.isSuccess(status)) {
|
||||||
throw new ApiException(String.format("Unexpected HTTP status '%d'", status));
|
throw new ApiException(String.format("Unexpected HTTP status '%d'", status));
|
||||||
}
|
}
|
||||||
if (!MediaType.APPLICATION_JSON.equals(contentType)) {
|
if (!MediaType.APPLICATION_JSON.equals(contentType)) {
|
||||||
throw new ApiException("Unexpected Content-Type: " + contentType);
|
throw new ApiException("Unexpected Content-Type: " + contentType);
|
||||||
}
|
}
|
||||||
|
if (contentJson.isEmpty()) {
|
||||||
|
throw new ApiException("Response payload is empty");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Resources resources = Objects.requireNonNull(jsonParser.fromJson(contentJson, Resources.class));
|
return Objects.requireNonNull(jsonParser.fromJson(contentJson, Resources.class));
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
resources.getErrors().forEach(error -> LOGGER.debug("putResource() resources error:{}", error));
|
|
||||||
}
|
|
||||||
} catch (JsonParseException e) {
|
} catch (JsonParseException e) {
|
||||||
LOGGER.debug("putResource() parsing error json:{}", contentJson, e);
|
LOGGER.debug("putResource() parsing error json:{}", contentJson, e);
|
||||||
throw new ApiException("Parsing error", e);
|
throw new ApiException("Parsing error", e);
|
||||||
|
@ -32,6 +32,10 @@ public class Resources {
|
|||||||
return errors.stream().map(Error::getDescription).collect(Collectors.toList());
|
return errors.stream().map(Error::getDescription).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasErrors() {
|
||||||
|
return !errors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Resource> getResources() {
|
public List<Resource> getResources() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -161,73 +161,50 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
private synchronized void checkConnection() {
|
private synchronized void checkConnection() {
|
||||||
logger.debug("checkConnection()");
|
logger.debug("checkConnection()");
|
||||||
|
|
||||||
// check connection to the hub
|
boolean retryApplicationKey = false;
|
||||||
ThingStatusDetail thingStatus;
|
boolean retryConnection = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
checkAssetsLoaded();
|
checkAssetsLoaded();
|
||||||
getClip2Bridge().testConnectionState();
|
getClip2Bridge().testConnectionState();
|
||||||
thingStatus = ThingStatusDetail.NONE;
|
updateSelf(); // go online
|
||||||
} catch (HttpUnauthorizedException e) {
|
} catch (HttpUnauthorizedException unauthorizedException) {
|
||||||
logger.debug("checkConnection() {}", e.getMessage(), e);
|
logger.debug("checkConnection() {}", unauthorizedException.getMessage(), unauthorizedException);
|
||||||
thingStatus = ThingStatusDetail.CONFIGURATION_ERROR;
|
if (applKeyRetriesRemaining > 0) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.press-pairing-button");
|
||||||
|
try {
|
||||||
|
registerApplicationKey();
|
||||||
|
retryApplicationKey = true;
|
||||||
|
} catch (HttpUnauthorizedException e) {
|
||||||
|
retryApplicationKey = true;
|
||||||
|
} catch (ApiException e) {
|
||||||
|
setStatusOfflineWithCommunicationError(e);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.read-only");
|
||||||
|
} catch (AssetNotLoadedException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.assets-not-loaded");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.not-authorized");
|
||||||
|
}
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
logger.debug("checkConnection() {}", e.getMessage(), e);
|
logger.debug("checkConnection() {}", e.getMessage(), e);
|
||||||
thingStatus = ThingStatusDetail.COMMUNICATION_ERROR;
|
setStatusOfflineWithCommunicationError(e);
|
||||||
|
retryConnection = connectRetriesRemaining > 0;
|
||||||
} catch (AssetNotLoadedException e) {
|
} catch (AssetNotLoadedException e) {
|
||||||
logger.debug("checkConnection() {}", e.getMessage(), e);
|
logger.debug("checkConnection() {}", e.getMessage(), e);
|
||||||
thingStatus = ThingStatusDetail.HANDLER_INITIALIZING_ERROR;
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.assets-not-loaded");
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the thing status
|
|
||||||
boolean retryApplicationKey = false;
|
|
||||||
boolean retryConnection = false;
|
|
||||||
switch (thingStatus) {
|
|
||||||
case CONFIGURATION_ERROR:
|
|
||||||
if (applKeyRetriesRemaining > 0) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"@text/offline.api2.conf-error.press-pairing-button");
|
|
||||||
try {
|
|
||||||
registerApplicationKey();
|
|
||||||
retryApplicationKey = true;
|
|
||||||
} catch (HttpUnauthorizedException e) {
|
|
||||||
retryApplicationKey = true;
|
|
||||||
} catch (ApiException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"@text/offline.communication-error");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"@text/offline.api2.conf-error.read-only");
|
|
||||||
} catch (AssetNotLoadedException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"@text/offline.api2.conf-error.assets-not-loaded");
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"@text/offline.api2.conf-error.not-authorized");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMUNICATION_ERROR:
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"@text/offline.communication-error");
|
|
||||||
retryConnection = connectRetriesRemaining > 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HANDLER_INITIALIZING_ERROR:
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"@text/offline.api2.conf-error.assets-not-loaded");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NONE:
|
|
||||||
default:
|
|
||||||
updateSelf(); // go online
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int milliSeconds;
|
int milliSeconds;
|
||||||
if (retryApplicationKey) {
|
if (retryApplicationKey) {
|
||||||
// short delay used during attempts to create or validate an application key
|
// short delay used during attempts to create or validate an application key
|
||||||
@ -250,6 +227,18 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
checkConnectionTask = scheduler.schedule(() -> checkConnection(), milliSeconds, TimeUnit.MILLISECONDS);
|
checkConnectionTask = scheduler.schedule(() -> checkConnection(), milliSeconds, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setStatusOfflineWithCommunicationError(Exception e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
String causeMessage = cause == null ? null : cause.getMessage();
|
||||||
|
if (causeMessage == null || causeMessage.isEmpty()) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + "\"]");
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + " -> " + causeMessage + "\"]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a child thing has been added, and the bridge is online, update the child's data.
|
* If a child thing has been added, and the bridge is online, update the child's data.
|
||||||
*/
|
*/
|
||||||
@ -467,8 +456,7 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.trace("initializeAssets() communication error on '{}'", ipAddress, e);
|
logger.trace("initializeAssets() communication error on '{}'", ipAddress, e);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
setStatusOfflineWithCommunicationError(e);
|
||||||
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + "\"]");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,8 +479,7 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
clip2Bridge = new Clip2Bridge(httpClientFactory, this, ipAddress, applicationKey);
|
clip2Bridge = new Clip2Bridge(httpClientFactory, this, ipAddress, applicationKey);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
logger.trace("initializeAssets() communication error on '{}'", ipAddress, e);
|
logger.trace("initializeAssets() communication error on '{}'", ipAddress, e);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
setStatusOfflineWithCommunicationError(e);
|
||||||
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + "\"]");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,14 +542,15 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
* Execute an HTTP PUT to send a Resource object to the server.
|
* Execute an HTTP PUT to send a Resource object to the server.
|
||||||
*
|
*
|
||||||
* @param resource the resource to put.
|
* @param resource the resource to put.
|
||||||
|
* @return the resource, which may contain errors.
|
||||||
* @throws ApiException if a communication error occurred.
|
* @throws ApiException if a communication error occurred.
|
||||||
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public void putResource(Resource resource) throws ApiException, AssetNotLoadedException, InterruptedException {
|
public Resources putResource(Resource resource) throws ApiException, AssetNotLoadedException, InterruptedException {
|
||||||
logger.debug("putResource() {}", resource);
|
logger.debug("putResource() {}", resource);
|
||||||
checkAssetsLoaded();
|
checkAssetsLoaded();
|
||||||
getClip2Bridge().putResource(resource);
|
return getClip2Bridge().putResource(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -684,8 +672,7 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
|||||||
getClip2Bridge().open();
|
getClip2Bridge().open();
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
logger.trace("updateSelf() {}", e.getMessage(), e);
|
logger.trace("updateSelf() {}", e.getMessage(), e);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
setStatusOfflineWithCommunicationError(e);
|
||||||
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + "\"]");
|
|
||||||
onConnectionOffline();
|
onConnectionOffline();
|
||||||
} catch (AssetNotLoadedException e) {
|
} catch (AssetNotLoadedException e) {
|
||||||
logger.trace("updateSelf() {}", e.getMessage(), e);
|
logger.trace("updateSelf() {}", e.getMessage(), e);
|
||||||
|
@ -44,6 +44,7 @@ import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
|
|||||||
import org.openhab.binding.hue.internal.dto.clip2.ProductData;
|
import org.openhab.binding.hue.internal.dto.clip2.ProductData;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resources;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
||||||
@ -507,7 +508,11 @@ public class Clip2ThingHandler extends BaseThingHandler {
|
|||||||
logger.debug("{} -> handleCommand() put resource {}", resourceId, putResource);
|
logger.debug("{} -> handleCommand() put resource {}", resourceId, putResource);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
getBridgeHandler().putResource(putResource);
|
Resources resources = getBridgeHandler().putResource(putResource);
|
||||||
|
if (resources.hasErrors()) {
|
||||||
|
logger.info("Command '{}' for thing '{}', channel '{}' succeeded with errors: {}", command,
|
||||||
|
thing.getUID(), channelUID, String.join("; ", resources.getErrors()));
|
||||||
|
}
|
||||||
} catch (ApiException | AssetNotLoadedException e) {
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("{} -> handleCommand() error {}", resourceId, e.getMessage(), e);
|
logger.debug("{} -> handleCommand() error {}", resourceId, e.getMessage(), e);
|
||||||
|
@ -232,7 +232,7 @@ offline.group-removed = Hue Bridge reports group as removed.
|
|||||||
# api v2 offline configuration error descriptions
|
# api v2 offline configuration error descriptions
|
||||||
|
|
||||||
offline.api2.comm-error.zigbee-connectivity-issue = Zigbee connectivity issue.
|
offline.api2.comm-error.zigbee-connectivity-issue = Zigbee connectivity issue.
|
||||||
offline.api2.comm-error.exception = An unexpected exception ''{0}'' occurred.
|
offline.api2.comm-error.exception = An unexpected exception occurred: {0}
|
||||||
offline.api2.conf-error.certificate-load = Certificate loading failed. Please check your configuration settings (network address, type of certificate).
|
offline.api2.conf-error.certificate-load = Certificate loading failed. Please check your configuration settings (network address, type of certificate).
|
||||||
offline.api2.conf-error.assets-not-loaded = Bridge/Thing handler assets not loaded.
|
offline.api2.conf-error.assets-not-loaded = Bridge/Thing handler assets not loaded.
|
||||||
offline.api2.conf-error.press-pairing-button = Not authenticated. Press pairing button on the Hue Bridge or set a valid application key in configuration.
|
offline.api2.conf-error.press-pairing-button = Not authenticated. Press pairing button on the Hue Bridge or set a valid application key in configuration.
|
||||||
|
Loading…
Reference in New Issue
Block a user