]> git.basschouten.com Git - openhab-addons.git/blob
2a3ae3e34c9109be3763c8d9fb005f9af23e163b
[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     /**
133      * parse the initial state response message
134      *
135      * @param r AsyncHttpClient.Result with the state response result
136      * @return a message of the correct type
137      */
138     protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
139
140     /**
141      * processes a newly received (initial) state response
142      *
143      * MUST set the thing status!
144      *
145      * @param stateResponse
146      */
147     protected abstract void processStateResponse(@Nullable T stateResponse);
148
149     /**
150      * Perform a request to the REST API for retrieving the full light state with all data and configuration.
151      */
152     protected void requestState(Consumer<@Nullable T> processor) {
153         AsyncHttpClient asyncHttpClient = http;
154         if (asyncHttpClient == null) {
155             return;
156         }
157
158         String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
159                 resourceType.getIdentifier(), config.id);
160         logger.trace("Requesting URL for initial data: {}", url);
161
162         // Get initial data
163         asyncHttpClient.get(url, bridgeConfig.timeout).thenApply(this::parseStateResponse).exceptionally(e -> {
164             if (e instanceof SocketTimeoutException || e instanceof TimeoutException
165                     || e instanceof CompletionException) {
166                 logger.debug("Get new state failed: ", e);
167             } else {
168                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
169             }
170
171             stopInitializationJob();
172             initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
173                     TimeUnit.SECONDS);
174
175             return null;
176         }).thenAccept(processor);
177     }
178
179     /**
180      * sends a command to the bridge
181      *
182      * @param object must be serializable and contain the command
183      * @param originalCommand the original openHAB command (used for logging purposes)
184      * @param channelUID the channel that this command was send to (used for logging purposes)
185      * @param acceptProcessing additional processing after the command was successfully send (might be null)
186      */
187     protected void sendCommand(Object object, Command originalCommand, ChannelUID channelUID,
188             @Nullable Runnable acceptProcessing) {
189         AsyncHttpClient asyncHttpClient = http;
190         if (asyncHttpClient == null) {
191             return;
192         }
193         String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
194                 resourceType.getIdentifier(), config.id, resourceType.getCommandUrl());
195
196         String json = gson.toJson(object);
197         logger.trace("Sending {} to {} {} via {}", json, resourceType, config.id, url);
198
199         asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
200             if (acceptProcessing != null) {
201                 acceptProcessing.run();
202             }
203             if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
204                 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
205                         v.getResponseCode(), v.getBody());
206             } else {
207                 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
208             }
209         }).exceptionally(e -> {
210             logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
211                     e.getMessage());
212             return null;
213         });
214     }
215
216     @Override
217     public void dispose() {
218         stopInitializationJob();
219         unregisterListener();
220         super.dispose();
221     }
222
223     @Override
224     public void initialize() {
225         config = getConfigAs(ThingConfig.class);
226
227         Bridge bridge = getBridge();
228         if (bridge != null) {
229             bridgeStatusChanged(bridge.getStatusInfo());
230         }
231     }
232 }