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