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