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