--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.boschindego.internal;
+
+import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
+import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
+import org.openhab.core.auth.client.oauth2.OAuthClientService;
+import org.openhab.core.auth.client.oauth2.OAuthException;
+import org.openhab.core.auth.client.oauth2.OAuthResponseException;
+
+/**
+ * The {@link AuthorizationController} acts as a bridge between
+ * {@link OAuthClientService} and {@link IndegoController}.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class AuthorizationController implements AuthorizationProvider {
+
+ private static final String BEARER = "Bearer ";
+
+ private final AuthorizationListener listener;
+
+ private OAuthClientService oAuthClientService;
+
+ public AuthorizationController(OAuthClientService oAuthClientService, AuthorizationListener listener) {
+ this.oAuthClientService = oAuthClientService;
+ this.listener = listener;
+ }
+
+ public void setOAuthClientService(OAuthClientService oAuthClientService) {
+ this.oAuthClientService = oAuthClientService;
+ }
+
+ public String getAuthorizationHeader() throws IndegoAuthenticationException {
+ final AccessTokenResponse accessTokenResponse;
+ try {
+ accessTokenResponse = getAccessToken();
+ } catch (OAuthException | OAuthResponseException e) {
+ var throwable = new IndegoAuthenticationException(
+ "Error fetching access token. Invalid authcode? Please generate a new one -> "
+ + getAuthorizationUrl(),
+ e);
+ listener.onFailedAuthorization(throwable);
+ throw throwable;
+ } catch (IOException e) {
+ var throwable = new IndegoAuthenticationException("An unexpected IOException occurred: " + e.getMessage(),
+ e);
+ listener.onFailedAuthorization(throwable);
+ throw throwable;
+ }
+
+ String accessToken = accessTokenResponse.getAccessToken();
+ if (accessToken == null || accessToken.isEmpty()) {
+ var throwable = new IndegoAuthenticationException(
+ "No access token. Is this thing authorized? -> " + getAuthorizationUrl());
+ listener.onFailedAuthorization(throwable);
+ throw throwable;
+ }
+ if (accessTokenResponse.getRefreshToken() == null || accessTokenResponse.getRefreshToken().isEmpty()) {
+ var throwable = new IndegoAuthenticationException(
+ "No refresh token. Please reauthorize -> " + getAuthorizationUrl());
+ listener.onFailedAuthorization(throwable);
+ throw throwable;
+ }
+
+ listener.onSuccessfulAuthorization();
+
+ return BEARER + accessToken;
+ }
+
+ public AccessTokenResponse getAccessToken() throws OAuthException, OAuthResponseException, IOException {
+ AccessTokenResponse accessTokenResponse = oAuthClientService.getAccessTokenResponse();
+ if (accessTokenResponse == null) {
+ throw new OAuthException("No access token response");
+ }
+
+ return accessTokenResponse;
+ }
+
+ private String getAuthorizationUrl() {
+ try {
+ return oAuthClientService.getAuthorizationUrl(BSK_REDIRECT_URI, BSK_SCOPE, null);
+ } catch (OAuthException e) {
+ return "";
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.boschindego.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * {@link} AuthorizationListener} is used for notifying {@link BoschAccountHandler}
+ * when authorization state has changed and for notifying {@link BoschIndegoHandler}
+ * when authorization flow is completed.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public interface AuthorizationListener {
+ /**
+ * Called upon successful OAuth authorization.
+ */
+ void onSuccessfulAuthorization();
+
+ /**
+ * Called upon failed OAuth authorization.
+ */
+ void onFailedAuthorization(Throwable throwable);
+
+ /**
+ * Called upon successful completion of OAuth authorization flow.
+ */
+ void onAuthorizationFlowCompleted();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.boschindego.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
+
+/**
+ * The {@link AuthorizationProvider} is responsible for providing
+ * authorization headers needed for communicating with the Bosch Indego
+ * cloud services.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public interface AuthorizationProvider {
+ /**
+ * Get HTTP authorization header for authenticating with Bosch Indego services.
+ *
+ * @return the header contents
+ * @throws IndegoException if not authorized
+ */
+ String getAuthorizationHeader() throws IndegoException;
+}
*/
package org.openhab.binding.boschindego.internal;
-import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
-
-import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidResponseException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoTimeoutException;
-import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
-import org.openhab.core.auth.client.oauth2.OAuthClientService;
-import org.openhab.core.auth.client.oauth2.OAuthException;
-import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.library.types.RawType;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
private static final String BASE_URL = "https://api.indego-cloud.iot.bosch-si.com/api/v1/";
private static final String CONTENT_TYPE_HEADER = "application/json";
- private static final String BEARER = "Bearer ";
private final Logger logger = LoggerFactory.getLogger(IndegoController.class);
private final Gson gson = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer()).create();
private final HttpClient httpClient;
- private final OAuthClientService oAuthClientService;
+ private final AuthorizationProvider authorizationProvider;
private final String userAgent;
/**
* Initialize the controller instance.
*
* @param httpClient the HttpClient for communicating with the service
- * @param oAuthClientService the OAuthClientService for authorization
+ * @param authorizationProvider the AuthorizationProvider for authenticating with the service
*/
- public IndegoController(HttpClient httpClient, OAuthClientService oAuthClientService) {
+ public IndegoController(HttpClient httpClient, AuthorizationProvider authorizationProvider) {
this.httpClient = httpClient;
- this.oAuthClientService = oAuthClientService;
+ this.authorizationProvider = authorizationProvider;
userAgent = "openHAB/" + FrameworkUtil.getBundle(this.getClass()).getVersion().toString();
}
return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/", DevicePropertiesResponse.class);
}
- private String getAuthorizationUrl() {
- try {
- return oAuthClientService.getAuthorizationUrl(BSK_REDIRECT_URI, BSK_SCOPE, null);
- } catch (OAuthException e) {
- return "";
- }
- }
-
- private String getAuthorizationHeader() throws IndegoException {
- final AccessTokenResponse accessTokenResponse;
- try {
- accessTokenResponse = oAuthClientService.getAccessTokenResponse();
- } catch (OAuthException | OAuthResponseException e) {
- logger.debug("Error fetching access token: {}", e.getMessage(), e);
- throw new IndegoAuthenticationException(
- "Error fetching access token. Invalid authcode? Please generate a new one -> "
- + getAuthorizationUrl(),
- e);
- } catch (IOException e) {
- throw new IndegoException("An unexpected IOException occurred: " + e.getMessage(), e);
- }
- if (accessTokenResponse == null || accessTokenResponse.getAccessToken() == null
- || accessTokenResponse.getAccessToken().isEmpty()) {
- throw new IndegoAuthenticationException(
- "No access token. Is this thing authorized? -> " + getAuthorizationUrl());
- }
- if (accessTokenResponse.getRefreshToken() == null || accessTokenResponse.getRefreshToken().isEmpty()) {
- throw new IndegoAuthenticationException("No refresh token. Please reauthorize -> " + getAuthorizationUrl());
- }
-
- return BEARER + accessTokenResponse.getAccessToken();
- }
-
/**
* Sends a GET request to the server and returns the deserialized JSON response.
*
int status = 0;
try {
Request request = httpClient.newRequest(BASE_URL + path).method(HttpMethod.GET)
- .header(HttpHeader.AUTHORIZATION, getAuthorizationHeader()).agent(userAgent);
+ .header(HttpHeader.AUTHORIZATION, authorizationProvider.getAuthorizationHeader()).agent(userAgent);
if (logger.isTraceEnabled()) {
logger.trace("GET request for {}", BASE_URL + path);
}
int status = 0;
try {
Request request = httpClient.newRequest(BASE_URL + path).method(HttpMethod.GET)
- .header(HttpHeader.AUTHORIZATION, getAuthorizationHeader()).agent(userAgent);
+ .header(HttpHeader.AUTHORIZATION, authorizationProvider.getAuthorizationHeader()).agent(userAgent);
if (logger.isTraceEnabled()) {
logger.trace("GET request for {}", BASE_URL + path);
}
throws IndegoAuthenticationException, IndegoException {
try {
Request request = httpClient.newRequest(BASE_URL + path).method(method)
- .header(HttpHeader.AUTHORIZATION, getAuthorizationHeader())
+ .header(HttpHeader.AUTHORIZATION, authorizationProvider.getAuthorizationHeader())
.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_HEADER).agent(userAgent);
if (requestDto != null) {
String payload = gson.toJson(requestDto);
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidResponseException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoTimeoutException;
-import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.library.types.RawType;
/**
* Initialize the controller instance.
*
* @param httpClient the HttpClient for communicating with the service
- * @param oAuthClientService the OAuthClientService for authorization
+ * @param authorizationProvider the AuthorizationProvider for authenticating with the service
* @param serialNumber the serial number of the device instance
*/
- public IndegoDeviceController(HttpClient httpClient, OAuthClientService oAuthClientService, String serialNumber) {
- super(httpClient, oAuthClientService);
+ public IndegoDeviceController(HttpClient httpClient, AuthorizationProvider authorizationProvider,
+ String serialNumber) {
+ super(httpClient, authorizationProvider);
if (serialNumber.isBlank()) {
throw new IllegalArgumentException("Serial number must be provided");
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.boschindego.internal.AuthorizationController;
+import org.openhab.binding.boschindego.internal.AuthorizationListener;
+import org.openhab.binding.boschindego.internal.AuthorizationProvider;
import org.openhab.binding.boschindego.internal.IndegoController;
import org.openhab.binding.boschindego.internal.discovery.IndegoDiscoveryService;
import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
-import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthException;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
-public class BoschAccountHandler extends BaseBridgeHandler {
+public class BoschAccountHandler extends BaseBridgeHandler implements AuthorizationListener {
private final Logger logger = LoggerFactory.getLogger(BoschAccountHandler.class);
private final OAuthFactory oAuthFactory;
+ private final Set<AuthorizationListener> authorizationListeners = ConcurrentHashMap.newKeySet();
private OAuthClientService oAuthClientService;
+ private AuthorizationController authorizationController;
private IndegoController controller;
public BoschAccountHandler(Bridge bridge, HttpClient httpClient, OAuthFactory oAuthFactory) {
this.oAuthFactory = oAuthFactory;
- oAuthClientService = oAuthFactory.createOAuthClientService(getThing().getUID().getAsString(), BSK_TOKEN_URI,
+ oAuthClientService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(), BSK_TOKEN_URI,
BSK_AUTH_URI, BSK_CLIENT_ID, null, BSK_SCOPE, false);
- controller = new IndegoController(httpClient, oAuthClientService);
+ authorizationController = new AuthorizationController(oAuthClientService, this);
+ controller = new IndegoController(httpClient, authorizationController);
}
@Override
public void initialize() {
+ OAuthClientService oAuthClientService = oAuthFactory.getOAuthClientService(thing.getUID().getAsString());
+ if (oAuthClientService == null) {
+ throw new IllegalStateException("OAuth handle doesn't exist");
+ }
+ authorizationController.setOAuthClientService(oAuthClientService);
+ this.oAuthClientService = oAuthClientService;
+
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(() -> {
try {
- AccessTokenResponse accessTokenResponse = this.oAuthClientService.getAccessTokenResponse();
- if (accessTokenResponse == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
- "@text/offline.conf-error.oauth2-unauthorized");
- } else {
- updateStatus(ThingStatus.ONLINE);
- }
+ authorizationController.getAccessToken();
+ updateStatus(ThingStatus.ONLINE);
} catch (OAuthException | OAuthResponseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.conf-error.oauth2-unauthorized");
@Override
public void dispose() {
- oAuthFactory.ungetOAuthService(this.getThing().getUID().getAsString());
+ oAuthFactory.ungetOAuthService(thing.getUID().getAsString());
+ authorizationListeners.clear();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
+ @Override
+ public void handleRemoval() {
+ oAuthFactory.deleteServiceAndAccessToken(thing.getUID().getAsString());
+ super.handleRemoval();
+ }
+
+ public AuthorizationProvider getAuthorizationProvider() {
+ return authorizationController;
+ }
+
+ public void registerAuthorizationListener(AuthorizationListener listener) {
+ if (!authorizationListeners.add(listener)) {
+ throw new IllegalStateException("Attempt to register already registered authorization listener");
+ }
+ }
+
+ public void unregisterAuthorizationListener(AuthorizationListener listener) {
+ if (!authorizationListeners.remove(listener)) {
+ throw new IllegalStateException("Attempt to unregister authorization listener which is not registered");
+ }
+ }
+
+ public void onSuccessfulAuthorization() {
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ public void onFailedAuthorization(Throwable throwable) {
+ logger.debug("Authorization failure", throwable);
+ if (throwable instanceof IndegoAuthenticationException) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "@text/offline.comm-error.authentication-failure");
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, throwable.getMessage());
+ }
+ }
+
+ public void onAuthorizationFlowCompleted() {
+ // Ignore
+ }
+
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return List.of(IndegoDiscoveryService.class);
logger.info("Authorization completed successfully");
- updateStatus(ThingStatus.ONLINE);
- }
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/online.authorization-completed");
- public OAuthClientService getOAuthClientService() {
- return oAuthClientService;
+ authorizationListeners.forEach(l -> l.onAuthorizationFlowCompleted());
}
public Collection<DevicePropertiesResponse> getDevices() throws IndegoException {
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.boschindego.internal.AuthorizationListener;
+import org.openhab.binding.boschindego.internal.AuthorizationProvider;
import org.openhab.binding.boschindego.internal.BoschIndegoTranslationProvider;
import org.openhab.binding.boschindego.internal.DeviceStatus;
import org.openhab.binding.boschindego.internal.IndegoDeviceController;
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
import org.openhab.binding.boschindego.internal.exceptions.IndegoTimeoutException;
-import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
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.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
* @author Jacob Laursen - Refactoring, bugfixing and removal of dependency towards abandoned library
*/
@NonNullByDefault
-public class BoschIndegoHandler extends BaseThingHandler {
+public class BoschIndegoHandler extends BaseThingHandler implements AuthorizationListener {
private static final String MAP_POSITION_STROKE_COLOR = "#8c8b6d";
private static final String MAP_POSITION_FILL_COLOR = "#fff701";
private final TimeZoneProvider timeZoneProvider;
private Instant devicePropertiesUpdated = Instant.MIN;
- private @NonNullByDefault({}) OAuthClientService oAuthClientService;
+ private @NonNullByDefault({}) AuthorizationProvider authorizationProvider;
private @NonNullByDefault({}) IndegoDeviceController controller;
private @Nullable ScheduledFuture<?> statePollFuture;
private @Nullable ScheduledFuture<?> cuttingTimePollFuture;
return;
}
- ThingHandler handler = bridge.getHandler();
- if (handler instanceof BoschAccountHandler accountHandler) {
- this.oAuthClientService = accountHandler.getOAuthClientService();
+ if (bridge.getHandler() instanceof BoschAccountHandler accountHandler) {
+ authorizationProvider = accountHandler.getAuthorizationProvider();
+ accountHandler.registerAuthorizationListener(this);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.conf-error.missing-bridge");
devicePropertiesUpdated = Instant.MIN;
updateProperty(Thing.PROPERTY_SERIAL_NUMBER, config.serialNumber);
- controller = new IndegoDeviceController(httpClient, oAuthClientService, config.serialNumber);
+ controller = new IndegoDeviceController(httpClient, authorizationProvider, config.serialNumber);
updateStatus(ThingStatus.UNKNOWN);
previousStateCode = Optional.empty();
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
&& getThing().getStatusInfo().getStatus() == ThingStatus.OFFLINE) {
- // Trigger immediate state refresh upon authorization success.
- rescheduleStatePoll(0, stateInactiveRefreshIntervalSeconds, true);
+ updateStatus(ThingStatus.UNKNOWN);
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
+ public void onSuccessfulAuthorization() {
+ // Ignore
+ }
+
+ public void onFailedAuthorization(Throwable throwable) {
+ // Ignore
+ }
+
+ public void onAuthorizationFlowCompleted() {
+ // Trigger immediate state refresh upon authorization success.
+ rescheduleStatePoll(0, stateInactiveRefreshIntervalSeconds, true);
+ }
+
private boolean rescheduleStatePoll(int delaySeconds, int refreshIntervalSeconds, boolean force) {
ScheduledFuture<?> statePollFuture = this.statePollFuture;
if (statePollFuture != null) {
@Override
public void dispose() {
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ if (bridge.getHandler() instanceof BoschAccountHandler accountHandler) {
+ accountHandler.unregisterAuthorizationListener(this);
+ }
+ }
+
ScheduledFuture<?> pollFuture = this.statePollFuture;
if (pollFuture != null) {
pollFuture.cancel(true);
sendCommand(((DecimalType) command).intValue());
}
} catch (IndegoAuthenticationException e) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "@text/offline.comm-error.authentication-failure");
+ // Ignore, will be handled by bridge
} catch (IndegoTimeoutException e) {
updateStatus(lastOperatingDataStatus = ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.comm-error.unreachable");
try {
refreshState();
} catch (IndegoAuthenticationException e) {
- logger.warn("Failed to authenticate: {}", e.getMessage());
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "@text/offline.comm-error.authentication-failure");
+ // Ignore, will be handled by bridge
} catch (IndegoTimeoutException e) {
updateStatus(lastOperatingDataStatus = ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.comm-error.unreachable");
refreshLastCuttingTime();
refreshNextCuttingTime();
} catch (IndegoAuthenticationException e) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "@text/offline.comm-error.authentication-failure");
+ // Ignore, will be handled by bridge
} catch (IndegoException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
try {
refreshNextCuttingTime();
} catch (IndegoAuthenticationException e) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "@text/offline.comm-error.authentication-failure");
+ // Ignore, will be handled by bridge
} catch (IndegoException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
offline.comm-error.oauth2-authorization-failed = Failed to authorize
offline.comm-error.authentication-failure = Failed to authenticate with Bosch SingleKey ID
offline.comm-error.unreachable = Device is unreachable
+online.authorization-completed = Authorization completed
# indego states