]> git.basschouten.com Git - openhab-addons.git/blob
6e310ea8d372f46dafd541953e1fb14d8c9a0dec
[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 import java.util.function.Consumer;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
27 import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
28 import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
29 import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
30 import org.openhab.binding.deconz.internal.types.ResourceType;
31 import org.openhab.core.thing.*;
32 import org.openhab.core.thing.binding.BaseThingHandler;
33 import org.openhab.core.types.Command;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import com.google.gson.Gson;
38
39 /**
40  * This base thing doesn't establish any connections, that is done by the bridge Thing.
41  *
42  * It waits for the bridge to come online, grab the websocket connection and bridge configuration
43  * and registers to the websocket connection as a listener.
44  **
45  * @author David Graeff - Initial contribution
46  * @author Jan N. Klug - Refactored to abstract class
47  */
48 @NonNullByDefault
49 public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
50         implements WebSocketMessageListener {
51     private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
52     protected final ResourceType resourceType;
53     protected ThingConfig config = new ThingConfig();
54     protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
55     protected final Gson gson;
56     private @Nullable ScheduledFuture<?> initializationJob;
57     protected @Nullable WebSocketConnection connection;
58     protected @Nullable AsyncHttpClient http;
59
60     public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
61         super(thing);
62         this.gson = gson;
63         this.resourceType = resourceType;
64     }
65
66     /**
67      * Stops the API request
68      */
69     private void stopInitializationJob() {
70         ScheduledFuture<?> future = initializationJob;
71         if (future != null) {
72             future.cancel(true);
73             initializationJob = null;
74         }
75     }
76
77     private void registerListener() {
78         WebSocketConnection conn = connection;
79         if (conn != null) {
80             conn.registerListener(resourceType, config.id, this);
81         }
82     }
83
84     private void unregisterListener() {
85         WebSocketConnection conn = connection;
86         if (conn != null) {
87             conn.unregisterListener(resourceType, config.id);
88         }
89     }
90
91     @Override
92     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
93         if (config.id.isEmpty()) {
94             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
95             return;
96         }
97
98         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
99             // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
100             // register the listener
101             Bridge bridge = getBridge();
102             if (bridge == null) {
103                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
104                 return;
105             }
106             DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
107             if (bridgeHandler == null) {
108                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
109                 return;
110             }
111
112             final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
113             this.connection = webSocketConnection;
114             this.http = bridgeHandler.getHttp();
115             this.bridgeConfig = bridgeHandler.getBridgeConfig();
116
117             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
118
119             // Real-time data
120             registerListener();
121
122             // get initial values
123             requestState(this::processStateResponse);
124         } else {
125             // if the bridge is not ONLINE, we assume communication is not possible, so we unregister the listener and
126             // set the thing status to OFFLINE
127             unregisterListener();
128             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
129         }
130     }
131
132     protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
133
134     /**
135      * processes a newly received (initial) state response
136      *
137      * MUST set the thing status!
138      *
139      * @param stateResponse
140      */
141     protected abstract void processStateResponse(@Nullable T stateResponse);
142
143     /**
144      * Perform a request to the REST API for retrieving the full light state with all data and configuration.
145      */
146     protected void requestState(Consumer<@Nullable T> processor) {
147         AsyncHttpClient asyncHttpClient = http;
148         if (asyncHttpClient == null) {
149             return;
150         }
151
152         String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
153                 resourceType.getIdentifier(), config.id);
154         logger.trace("Requesting URL for initial data: {}", url);
155
156         // Get initial data
157         asyncHttpClient.get(url, bridgeConfig.timeout).thenApply(this::parseStateResponse).exceptionally(e -> {
158             if (e instanceof SocketTimeoutException || e instanceof TimeoutException
159                     || e instanceof CompletionException) {
160                 logger.debug("Get new state failed: ", e);
161             } else {
162                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
163             }
164
165             stopInitializationJob();
166             initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
167                     TimeUnit.SECONDS);
168
169             return null;
170         }).thenAccept(processor);
171     }
172
173     /**
174      * sends a command to the bridge
175      *
176      * @param object must be serializable and contain the command
177      * @param originalCommand the original openHAB command (used for logging purposes)
178      * @param channelUID the channel that this command was send to (used for logging purposes)
179      * @param acceptProcessing additional processing after the command was successfully send (might be null)
180      */
181     protected void sendCommand(Object object, Command originalCommand, ChannelUID channelUID,
182             @Nullable Runnable acceptProcessing) {
183         AsyncHttpClient asyncHttpClient = http;
184         if (asyncHttpClient == null) {
185             return;
186         }
187         String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
188                 resourceType.getIdentifier(), config.id, resourceType.getCommandUrl());
189
190         String json = gson.toJson(object);
191         logger.trace("Sending {} to {} {} via {}", json, resourceType, config.id, url);
192
193         asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
194             if (acceptProcessing != null) {
195                 acceptProcessing.run();
196             }
197             if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
198                 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
199                         v.getResponseCode(), v.getBody());
200             } else {
201                 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
202             }
203         }).exceptionally(e -> {
204             logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
205                     e.getMessage());
206             return null;
207         });
208     }
209
210     @Override
211     public void dispose() {
212         stopInitializationJob();
213         unregisterListener();
214         super.dispose();
215     }
216
217     @Override
218     public void initialize() {
219         config = getConfigAs(ThingConfig.class);
220
221         Bridge bridge = getBridge();
222         if (bridge != null) {
223             bridgeStatusChanged(bridge.getStatusInfo());
224         }
225     }
226 }