]> git.basschouten.com Git - openhab-addons.git/blob
9f40f49022676a45c1cb0afb2049cabc6e8b3cfa
[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.Bridge;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.ThingStatusInfo;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.types.Command;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import com.google.gson.Gson;
39
40 /**
41  * This base thing doesn't establish any connections, that is done by the bridge Thing.
42  *
43  * It waits for the bridge to come online, grab the websocket connection and bridge configuration
44  * and registers to the websocket connection as a listener.
45  **
46  * @author David Graeff - Initial contribution
47  * @author Jan N. Klug - Refactored to abstract class
48  */
49 @NonNullByDefault
50 public abstract class DeconzBaseThingHandler extends BaseThingHandler implements WebSocketMessageListener {
51     private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
52     protected final ResourceType resourceType;
53     protected ThingConfig config = new ThingConfig();
54     protected final Gson gson;
55     private @Nullable ScheduledFuture<?> initializationJob;
56     protected @Nullable WebSocketConnection connection;
57
58     public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
59         super(thing);
60         this.gson = gson;
61         this.resourceType = resourceType;
62     }
63
64     /**
65      * Stops the API request
66      */
67     private void stopInitializationJob() {
68         ScheduledFuture<?> future = initializationJob;
69         if (future != null) {
70             future.cancel(true);
71             initializationJob = null;
72         }
73     }
74
75     private void registerListener() {
76         WebSocketConnection conn = connection;
77         if (conn != null) {
78             conn.registerListener(resourceType, config.id, this);
79         }
80     }
81
82     private void unregisterListener() {
83         WebSocketConnection conn = connection;
84         if (conn != null) {
85             conn.unregisterListener(resourceType, config.id);
86         }
87     }
88
89     private @Nullable DeconzBridgeHandler getBridgeHandler() {
90         Bridge bridge = getBridge();
91         if (bridge == null) {
92             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
93             return null;
94         }
95         return (DeconzBridgeHandler) bridge.getHandler();
96     }
97
98     @Override
99     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
100         if (config.id.isEmpty()) {
101             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
102             return;
103         }
104
105         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
106             // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
107             // register the listener
108             DeconzBridgeHandler bridgeHandler = getBridgeHandler();
109             if (bridgeHandler == null) {
110                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
111                 return;
112             }
113
114             final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
115             this.connection = webSocketConnection;
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         DeconzBridgeHandler bridgeHandler = getBridgeHandler();
186         if (bridgeHandler == null) {
187             return;
188         }
189         String endpoint = Stream.of(resourceType.getIdentifier(), config.id, commandUrl)
190                 .collect(Collectors.joining("/"));
191
192         bridgeHandler.sendObject(endpoint, object).thenAccept(v -> {
193             if (acceptProcessing != null) {
194                 acceptProcessing.run();
195             }
196             if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
197                 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
198                         v.getResponseCode(), v.getBody());
199             } else {
200                 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
201             }
202         }).exceptionally(e -> {
203             logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
204                     e.getMessage());
205             return null;
206         });
207     }
208
209     @Override
210     public void dispose() {
211         stopInitializationJob();
212         unregisterListener();
213         super.dispose();
214     }
215
216     @Override
217     public void initialize() {
218         config = getConfigAs(ThingConfig.class);
219
220         Bridge bridge = getBridge();
221         if (bridge != null) {
222             bridgeStatusChanged(bridge.getStatusInfo());
223         }
224     }
225 }