]> git.basschouten.com Git - openhab-addons.git/blob
4ecf2f27bcb5dac0a76d9cc79521da469d57e41c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.deconz.internal.handler;
14
15 import static org.openhab.binding.deconz.internal.Util.buildUrl;
16
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;
22
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;
37
38 import com.google.gson.Gson;
39
40 /**
41  * This base thing doesn't establish any connections, that is done by the bridge Thing.
42  *
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.
45  *
46  * A REST API call is made to get the initial light/rollershutter state.
47  *
48  * @author David Graeff - Initial contribution
49  * @author Jan N. Klug - Refactored to abstract class
50  */
51 @NonNullByDefault
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;
61
62     public DeconzBaseThingHandler(Thing thing, Gson gson) {
63         super(thing);
64         this.gson = gson;
65     }
66
67     /**
68      * Stops the API request
69      */
70     private void stopInitializationJob() {
71         ScheduledFuture<?> future = initializationJob;
72         if (future != null) {
73             future.cancel(true);
74             initializationJob = null;
75         }
76     }
77
78     protected abstract void registerListener();
79
80     protected abstract void unregisterListener();
81
82     @Override
83     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
84         if (config.id.isEmpty()) {
85             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
86             return;
87         }
88
89         if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
90             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
91             unregisterListener();
92             return;
93         }
94
95         if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
96             return;
97         }
98
99         Bridge bridge = getBridge();
100         if (bridge == null) {
101             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
102             return;
103         }
104         DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
105         if (bridgeHandler == null) {
106             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
107             return;
108         }
109
110         final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
111         this.connection = webSocketConnection;
112         this.http = bridgeHandler.getHttp();
113         this.bridgeConfig = bridgeHandler.getBridgeConfig();
114
115         updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
116
117         // Real-time data
118         registerListener();
119
120         // get initial values
121         requestState();
122     }
123
124     protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
125
126     /**
127      * processes a newly received state response
128      *
129      * MUST set the thing status!
130      *
131      * @param stateResponse
132      */
133     protected abstract void processStateResponse(@Nullable T stateResponse);
134
135     /**
136      * call requestState(type) in this method only
137      */
138     protected abstract void requestState();
139
140     /**
141      * Perform a request to the REST API for retrieving the full light state with all data and configuration.
142      */
143     protected void requestState(String type) {
144         AsyncHttpClient asyncHttpClient = http;
145         if (asyncHttpClient == null) {
146             return;
147         }
148
149         String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey, type, config.id);
150         logger.trace("Requesting URL for initial data: {}", url);
151
152         // Get initial data
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);
157             } else {
158                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
159             }
160
161             stopInitializationJob();
162             initializationJob = scheduler.schedule((Runnable) this::requestState, 10, TimeUnit.SECONDS);
163
164             return null;
165         }).thenAccept(this::processStateResponse);
166     }
167
168     @Override
169     public void dispose() {
170         stopInitializationJob();
171         WebSocketConnection webSocketConnection = connection;
172         if (webSocketConnection != null) {
173             webSocketConnection.unregisterLightListener(config.id);
174         }
175         super.dispose();
176     }
177
178     @Override
179     public void initialize() {
180         config = getConfigAs(ThingConfig.class);
181
182         Bridge bridge = getBridge();
183         if (bridge != null) {
184             bridgeStatusChanged(bridge.getStatusInfo());
185         }
186     }
187 }