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;
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.AsyncHttpClient;
27 import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
28 import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
29 import org.openhab.binding.deconz.internal.types.ResourceType;
30 import org.openhab.core.thing.*;
31 import org.openhab.core.thing.binding.BaseThingHandler;
32 import org.openhab.core.types.Command;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 import com.google.gson.Gson;
39 * This base thing doesn't establish any connections, that is done by the bridge Thing.
41 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
42 * and registers to the websocket connection as a listener.
44 * @author David Graeff - Initial contribution
45 * @author Jan N. Klug - Refactored to abstract class
48 public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extends BaseThingHandler
49 implements WebSocketMessageListener {
50 private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
51 protected final ResourceType resourceType;
52 protected ThingConfig config = new ThingConfig();
53 protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
54 protected final Gson gson;
55 private @Nullable ScheduledFuture<?> initializationJob;
56 protected @Nullable WebSocketConnection connection;
57 protected @Nullable AsyncHttpClient http;
59 public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
62 this.resourceType = resourceType;
66 * Stops the API request
68 private void stopInitializationJob() {
69 ScheduledFuture<?> future = initializationJob;
72 initializationJob = null;
76 private void registerListener() {
77 WebSocketConnection conn = connection;
79 conn.registerListener(resourceType, config.id, this);
83 private void unregisterListener() {
84 WebSocketConnection conn = connection;
86 conn.unregisterListener(resourceType, config.id);
91 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
92 if (config.id.isEmpty()) {
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
97 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
98 // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
99 // register the listener
100 Bridge bridge = getBridge();
101 if (bridge == null) {
102 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
105 DeconzBridgeHandler bridgeHandler = (DeconzBridgeHandler) bridge.getHandler();
106 if (bridgeHandler == null) {
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
111 final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
112 this.connection = webSocketConnection;
113 this.http = bridgeHandler.getHttp();
114 this.bridgeConfig = bridgeHandler.getBridgeConfig();
116 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
121 // get initial values
124 // if the bridge is not ONLINE, we assume communication is not possible, so we unregister the listener and
125 // set the thing status to OFFLINE
126 unregisterListener();
127 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
131 protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
134 * processes a newly received state response
136 * MUST set the thing status!
138 * @param stateResponse
140 protected abstract void processStateResponse(@Nullable T stateResponse);
143 * Perform a request to the REST API for retrieving the full light state with all data and configuration.
145 protected void requestState() {
146 AsyncHttpClient asyncHttpClient = http;
147 if (asyncHttpClient == null) {
151 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
152 resourceType.getIdentifier(), config.id);
153 logger.trace("Requesting URL for initial data: {}", url);
156 asyncHttpClient.get(url, bridgeConfig.timeout).thenApply(this::parseStateResponse).exceptionally(e -> {
157 if (e instanceof SocketTimeoutException || e instanceof TimeoutException
158 || e instanceof CompletionException) {
159 logger.debug("Get new state failed: ", e);
161 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
164 stopInitializationJob();
165 initializationJob = scheduler.schedule((Runnable) this::requestState, 10, TimeUnit.SECONDS);
168 }).thenAccept(this::processStateResponse);
172 * sends a command to the bridge
174 * @param object must be serializable and contain the command
175 * @param originalCommand the original openHAB command (used for logging purposes)
176 * @param channelUID the channel that this command was send to (used for logging purposes)
177 * @param acceptProcessing additional processing after the command was successfully send (might be null)
179 protected void sendCommand(Object object, Command originalCommand, ChannelUID channelUID,
180 @Nullable Runnable acceptProcessing) {
181 AsyncHttpClient asyncHttpClient = http;
182 if (asyncHttpClient == null) {
185 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
186 resourceType.getIdentifier(), config.id, resourceType.getCommandUrl());
188 String json = gson.toJson(object);
189 logger.trace("Sending {} to {} {} via {}", json, resourceType, config.id, url);
191 asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
192 if (acceptProcessing != null) {
193 acceptProcessing.run();
195 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
196 }).exceptionally(e -> {
197 logger.debug("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
204 public void dispose() {
205 stopInitializationJob();
206 unregisterListener();
211 public void initialize() {
212 config = getConfigAs(ThingConfig.class);
214 Bridge bridge = getBridge();
215 if (bridge != null) {
216 bridgeStatusChanged(bridge.getStatusInfo());