2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.deconz.internal.handler;
15 import static org.openhab.binding.deconz.internal.Util.buildUrl;
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 import java.util.function.Consumer;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
27 import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
28 import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
29 import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
30 import org.openhab.binding.deconz.internal.types.ResourceType;
31 import org.openhab.core.thing.*;
32 import org.openhab.core.thing.binding.BaseThingHandler;
33 import org.openhab.core.types.Command;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
37 import com.google.gson.Gson;
40 * This base thing doesn't establish any connections, that is done by the bridge Thing.
42 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
43 * and registers to the websocket connection as a listener.
45 * @author David Graeff - Initial contribution
46 * @author Jan N. Klug - Refactored to abstract class
49 public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
50 implements WebSocketMessageListener {
51 private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
52 protected final ResourceType resourceType;
53 protected ThingConfig config = new ThingConfig();
54 protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
55 protected final Gson gson;
56 private @Nullable ScheduledFuture<?> initializationJob;
57 protected @Nullable WebSocketConnection connection;
58 protected @Nullable AsyncHttpClient http;
60 public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
63 this.resourceType = resourceType;
67 * Stops the API request
69 private void stopInitializationJob() {
70 ScheduledFuture<?> future = initializationJob;
73 initializationJob = null;
77 private void registerListener() {
78 WebSocketConnection conn = connection;
80 conn.registerListener(resourceType, config.id, this);
84 private void unregisterListener() {
85 WebSocketConnection conn = connection;
87 conn.unregisterListener(resourceType, config.id);
92 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
93 if (config.id.isEmpty()) {
94 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
98 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
99 // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
100 // register the listener
101 Bridge bridge = getBridge();
102 if (bridge == null) {
103 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
106 DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
107 if (bridgeHandler == null) {
108 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
112 final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
113 this.connection = webSocketConnection;
114 this.http = bridgeHandler.getHttp();
115 this.bridgeConfig = bridgeHandler.getBridgeConfig();
117 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
122 // get initial values
123 requestState(this::processStateResponse);
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);
132 protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
135 * processes a newly received (initial) state response
137 * MUST set the thing status!
139 * @param stateResponse
141 protected abstract void processStateResponse(@Nullable T stateResponse);
144 * Perform a request to the REST API for retrieving the full light state with all data and configuration.
146 protected void requestState(Consumer<@Nullable T> processor) {
147 AsyncHttpClient asyncHttpClient = http;
148 if (asyncHttpClient == null) {
152 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
153 resourceType.getIdentifier(), config.id);
154 logger.trace("Requesting URL for initial data: {}", url);
157 asyncHttpClient.get(url, bridgeConfig.timeout).thenApply(this::parseStateResponse).exceptionally(e -> {
158 if (e instanceof SocketTimeoutException || e instanceof TimeoutException
159 || e instanceof CompletionException) {
160 logger.debug("Get new state failed: ", e);
162 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
165 stopInitializationJob();
166 initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
170 }).thenAccept(processor);
174 * sends a command to the bridge
176 * @param object must be serializable and contain the command
177 * @param originalCommand the original openHAB command (used for logging purposes)
178 * @param channelUID the channel that this command was send to (used for logging purposes)
179 * @param acceptProcessing additional processing after the command was successfully send (might be null)
181 protected void sendCommand(Object object, Command originalCommand, ChannelUID channelUID,
182 @Nullable Runnable acceptProcessing) {
183 AsyncHttpClient asyncHttpClient = http;
184 if (asyncHttpClient == null) {
187 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
188 resourceType.getIdentifier(), config.id, resourceType.getCommandUrl());
190 String json = gson.toJson(object);
191 logger.trace("Sending {} to {} {} via {}", json, resourceType, config.id, url);
193 asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
194 if (acceptProcessing != null) {
195 acceptProcessing.run();
197 if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
198 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
199 v.getResponseCode(), v.getBody());
201 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
203 }).exceptionally(e -> {
204 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
211 public void dispose() {
212 stopInitializationJob();
213 unregisterListener();
218 public void initialize() {
219 config = getConfigAs(ThingConfig.class);
221 Bridge bridge = getBridge();
222 if (bridge != null) {
223 bridgeStatusChanged(bridge.getStatusInfo());