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