2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.windcentrale.internal.handler;
15 import static java.util.function.Predicate.not;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Objects;
20 import java.util.concurrent.CopyOnWriteArrayList;
21 import java.util.concurrent.Future;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.windcentrale.internal.WindcentraleDiscoveryService;
28 import org.openhab.binding.windcentrale.internal.api.RequestListener;
29 import org.openhab.binding.windcentrale.internal.api.TokenProvider;
30 import org.openhab.binding.windcentrale.internal.api.WindcentraleAPI;
31 import org.openhab.binding.windcentrale.internal.config.AccountConfiguration;
32 import org.openhab.binding.windcentrale.internal.exception.FailedGettingDataException;
33 import org.openhab.binding.windcentrale.internal.exception.InvalidAccessTokenException;
34 import org.openhab.binding.windcentrale.internal.listener.ThingStatusListener;
35 import org.openhab.core.io.net.http.HttpClientFactory;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseBridgeHandler;
41 import org.openhab.core.thing.binding.ThingHandlerService;
42 import org.openhab.core.types.Command;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * The {@link WindcentraleAccountHandler} provides the {@link WindcentraleAPI} instance used by the windmill handlers.
49 * @author Wouter Born - Initial contribution
52 public class WindcentraleAccountHandler extends BaseBridgeHandler {
54 private final Logger logger = LoggerFactory.getLogger(WindcentraleAccountHandler.class);
56 private final HttpClientFactory httpClientFactory;
57 private final List<ThingStatusListener> thingStatusListeners = new CopyOnWriteArrayList<>();
59 private @Nullable WindcentraleAPI api;
60 private @Nullable Exception apiException;
61 private @Nullable Future<?> initializeFuture;
63 private final RequestListener requestListener = new RequestListener() {
65 public void onError(Exception exception) {
66 apiException = exception;
67 logger.debug("API exception occurred");
72 public void onSuccess() {
73 if (apiException != null) {
75 logger.debug("API exception cleared");
81 public WindcentraleAccountHandler(Bridge bridge, HttpClientFactory httpClientFactory) {
83 this.httpClientFactory = httpClientFactory;
86 public void addThingStatusListener(ThingStatusListener listener) {
87 thingStatusListeners.add(listener);
88 listener.thingStatusChanged(thing, thing.getStatus());
92 public void dispose() {
93 Future<?> localFuture = initializeFuture;
94 if (localFuture != null) {
95 localFuture.cancel(true);
96 initializeFuture = null;
99 WindcentraleAPI localAPI = api;
100 if (localAPI != null) {
106 public @Nullable WindcentraleAPI getAPI() {
111 public void initialize() {
112 updateStatus(ThingStatus.UNKNOWN);
114 initializeFuture = scheduler.submit(() -> {
115 api = initializeAPI();
120 private WindcentraleAPI initializeAPI() {
121 AccountConfiguration config = getConfigAs(AccountConfiguration.class);
122 TokenProvider tokenProvider = new TokenProvider(httpClientFactory, config.username, config.password);
124 WindcentraleAPI api = new WindcentraleAPI(httpClientFactory, tokenProvider);
125 api.addRequestListener(requestListener);
131 } catch (FailedGettingDataException | InvalidAccessTokenException e) {
138 public Collection<Class<? extends ThingHandlerService>> getServices() {
139 return List.of(WindcentraleDiscoveryService.class);
143 public void handleCommand(ChannelUID channelUID, Command command) {
146 public void removeThingStatusListener(ThingStatusListener listener) {
147 thingStatusListeners.remove(listener);
151 protected void updateStatus(ThingStatus status, ThingStatusDetail detail, @Nullable String comment) {
152 ThingStatus oldStatus = thing.getStatus();
153 super.updateStatus(status, detail, comment);
154 ThingStatus newStatus = thing.getStatus();
156 if (!oldStatus.equals(newStatus)) {
157 logger.debug("Updating listeners with status {}", status);
158 for (ThingStatusListener listener : thingStatusListeners) {
159 listener.thingStatusChanged(thing, status);
164 private void updateThingStatus() {
165 Exception e = apiException;
167 if (e instanceof InvalidAccessTokenException) {
168 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
170 Throwable cause = e.getCause();
171 String description = Stream
172 .of(Objects.requireNonNullElse(e.getMessage(), ""),
173 cause == null ? "" : Objects.requireNonNullElse(cause.getMessage(), ""))
174 .filter(not(String::isBlank)) //
175 .collect(Collectors.joining(": "));
176 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
179 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);