2 * Copyright (c) 2010-2020 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.deconz.internal.handler;
15 import static org.openhab.binding.deconz.internal.Util.buildUrl;
17 import java.net.SocketTimeoutException;
18 import java.util.concurrent.CompletionException;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.TimeoutException;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
26 import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
27 import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
28 import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.ThingStatusInfo;
34 import org.openhab.core.thing.binding.BaseThingHandler;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 import com.google.gson.Gson;
41 * This base thing doesn't establish any connections, that is done by the bridge Thing.
43 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
44 * and registers to the websocket connection as a listener.
46 * A REST API call is made to get the initial light/rollershutter state.
48 * @author David Graeff - Initial contribution
49 * @author Jan N. Klug - Refactored to abstract class
52 public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
53 implements WebSocketMessageListener {
54 private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
55 protected ThingConfig config = new ThingConfig();
56 protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
57 protected final Gson gson;
58 private @Nullable ScheduledFuture<?> initializationJob;
59 protected @Nullable WebSocketConnection connection;
60 protected @Nullable AsyncHttpClient http;
62 public DeconzBaseThingHandler(Thing thing, Gson gson) {
68 * Stops the API request
70 private void stopInitializationJob() {
71 ScheduledFuture<?> future = initializationJob;
74 initializationJob = null;
78 protected abstract void registerListener();
80 protected abstract void unregisterListener();
83 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
84 if (config.id.isEmpty()) {
85 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
89 if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
90 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
95 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
99 Bridge bridge = getBridge();
100 if (bridge == null) {
101 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
104 DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
105 if (bridgeHandler == null) {
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
110 final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
111 this.connection = webSocketConnection;
112 this.http = bridgeHandler.getHttp();
113 this.bridgeConfig = bridgeHandler.getBridgeConfig();
115 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
120 // get initial values
124 protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
127 * processes a newly received state response
129 * MUST set the thing status!
131 * @param stateResponse
133 protected abstract void processStateResponse(@Nullable T stateResponse);
136 * call requestState(type) in this method only
138 protected abstract void requestState();
141 * Perform a request to the REST API for retrieving the full light state with all data and configuration.
143 protected void requestState(String type) {
144 AsyncHttpClient asyncHttpClient = http;
145 if (asyncHttpClient == null) {
149 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey, type, config.id);
150 logger.trace("Requesting URL for initial data: {}", url);
153 asyncHttpClient.get(url, bridgeConfig.timeout).thenApply(this::parseStateResponse).exceptionally(e -> {
154 if (e instanceof SocketTimeoutException || e instanceof TimeoutException
155 || e instanceof CompletionException) {
156 logger.debug("Get new state failed: ", e);
158 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
161 stopInitializationJob();
162 initializationJob = scheduler.schedule((Runnable) this::requestState, 10, TimeUnit.SECONDS);
165 }).thenAccept(this::processStateResponse);
169 public void dispose() {
170 stopInitializationJob();
171 WebSocketConnection webSocketConnection = connection;
172 if (webSocketConnection != null) {
173 webSocketConnection.unregisterLightListener(config.id);
179 public void initialize() {
180 config = getConfigAs(ThingConfig.class);
182 Bridge bridge = getBridge();
183 if (bridge != null) {
184 bridgeStatusChanged(bridge.getStatusInfo());