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.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19 import java.util.function.Consumer;
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.AsyncHttpClient;
25 import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
26 import org.openhab.binding.deconz.internal.netutils.WebSocketMessageListener;
27 import org.openhab.binding.deconz.internal.types.ResourceType;
28 import org.openhab.core.thing.*;
29 import org.openhab.core.thing.binding.BaseThingHandler;
30 import org.openhab.core.types.Command;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.google.gson.Gson;
37 * This base thing doesn't establish any connections, that is done by the bridge Thing.
39 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
40 * and registers to the websocket connection as a listener.
42 * @author David Graeff - Initial contribution
43 * @author Jan N. Klug - Refactored to abstract class
46 public abstract class DeconzBaseThingHandler extends BaseThingHandler implements WebSocketMessageListener {
47 private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
48 protected final ResourceType resourceType;
49 protected ThingConfig config = new ThingConfig();
50 protected DeconzBridgeConfig bridgeConfig = new DeconzBridgeConfig();
51 protected final Gson gson;
52 private @Nullable ScheduledFuture<?> initializationJob;
53 protected @Nullable WebSocketConnection connection;
54 protected @Nullable AsyncHttpClient http;
56 public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
59 this.resourceType = resourceType;
63 * Stops the API request
65 private void stopInitializationJob() {
66 ScheduledFuture<?> future = initializationJob;
69 initializationJob = null;
73 private void registerListener() {
74 WebSocketConnection conn = connection;
76 conn.registerListener(resourceType, config.id, this);
80 private void unregisterListener() {
81 WebSocketConnection conn = connection;
83 conn.unregisterListener(resourceType, config.id);
87 private @Nullable DeconzBridgeHandler getBridgeHandler() {
88 Bridge bridge = getBridge();
90 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
93 return (DeconzBridgeHandler) bridge.getHandler();
97 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
98 if (config.id.isEmpty()) {
99 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
103 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
104 // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
105 // register the listener
106 DeconzBridgeHandler bridgeHandler = getBridgeHandler();
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);
133 * processes a newly received (initial) state response
135 * MUST set the thing status!
137 * @param stateResponse
139 protected abstract void processStateResponse(DeconzBaseMessage stateResponse);
142 * Perform a request to the REST API for retrieving the full light state with all data and configuration.
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);
152 if (initializationJob != null) {
153 stopInitializationJob();
154 initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
162 * sends a command to the bridge
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)
169 protected void sendCommand(Object object, Command originalCommand, ChannelUID channelUID,
170 @Nullable Runnable acceptProcessing) {
171 AsyncHttpClient asyncHttpClient = http;
172 if (asyncHttpClient == null) {
175 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey,
176 resourceType.getIdentifier(), config.id, resourceType.getCommandUrl());
178 String json = gson.toJson(object);
179 logger.trace("Sending {} to {} {} via {}", json, resourceType, config.id, url);
181 asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
182 if (acceptProcessing != null) {
183 acceptProcessing.run();
185 if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
186 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
187 v.getResponseCode(), v.getBody());
189 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
191 }).exceptionally(e -> {
192 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
199 public void dispose() {
200 stopInitializationJob();
201 unregisterListener();
206 public void initialize() {
207 config = getConfigAs(ThingConfig.class);
209 Bridge bridge = getBridge();
210 if (bridge != null) {
211 bridgeStatusChanged(bridge.getStatusInfo());