2 * Copyright (c) 2010-2021 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 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;
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.*;
28 import org.openhab.core.thing.binding.BaseThingHandler;
29 import org.openhab.core.types.Command;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
33 import com.google.gson.Gson;
36 * This base thing doesn't establish any connections, that is done by the bridge Thing.
38 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
39 * and registers to the websocket connection as a listener.
41 * @author David Graeff - Initial contribution
42 * @author Jan N. Klug - Refactored to abstract class
45 public abstract class DeconzBaseThingHandler extends BaseThingHandler implements WebSocketMessageListener {
46 private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
47 protected final ResourceType resourceType;
48 protected ThingConfig config = new ThingConfig();
49 protected final Gson gson;
50 private @Nullable ScheduledFuture<?> initializationJob;
51 protected @Nullable WebSocketConnection connection;
53 public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
56 this.resourceType = resourceType;
60 * Stops the API request
62 private void stopInitializationJob() {
63 ScheduledFuture<?> future = initializationJob;
66 initializationJob = null;
70 private void registerListener() {
71 WebSocketConnection conn = connection;
73 conn.registerListener(resourceType, config.id, this);
77 private void unregisterListener() {
78 WebSocketConnection conn = connection;
80 conn.unregisterListener(resourceType, config.id);
84 private @Nullable DeconzBridgeHandler getBridgeHandler() {
85 Bridge bridge = getBridge();
87 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
90 return (DeconzBridgeHandler) bridge.getHandler();
94 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
95 if (config.id.isEmpty()) {
96 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
100 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
101 // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
102 // register the listener
103 DeconzBridgeHandler bridgeHandler = getBridgeHandler();
104 if (bridgeHandler == null) {
105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
109 final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
110 this.connection = webSocketConnection;
112 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
117 // get initial values
118 requestState(this::processStateResponse);
120 // if the bridge is not ONLINE, we assume communication is not possible, so we unregister the listener and
121 // set the thing status to OFFLINE
122 unregisterListener();
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
128 * processes a newly received (initial) state response
130 * MUST set the thing status!
132 * @param stateResponse
134 protected abstract void processStateResponse(DeconzBaseMessage stateResponse);
137 * Perform a request to the REST API for retrieving the full light state with all data and configuration.
139 protected void requestState(Consumer<DeconzBaseMessage> processor) {
140 DeconzBridgeHandler bridgeHandler = getBridgeHandler();
141 if (bridgeHandler != null) {
142 bridgeHandler.getBridgeFullState()
143 .thenAccept(f -> f.map(s -> s.getMessage(resourceType, config.id)).ifPresentOrElse(message -> {
144 logger.trace("{} processing {}", thing.getUID(), message);
145 processor.accept(message);
147 if (initializationJob != null) {
148 stopInitializationJob();
149 initializationJob = scheduler.schedule(() -> requestState(this::processStateResponse), 10,
157 * sends a command to the bridge with the default command URL
159 * @param object must be serializable and contain the command
160 * @param originalCommand the original openHAB command (used for logging purposes)
161 * @param channelUID the channel that this command was send to (used for logging purposes)
162 * @param acceptProcessing additional processing after the command was successfully send (might be null)
164 protected void sendCommand(@Nullable Object object, Command originalCommand, ChannelUID channelUID,
165 @Nullable Runnable acceptProcessing) {
166 sendCommand(object, originalCommand, channelUID, resourceType.getCommandUrl(), acceptProcessing);
170 * sends a command to the bridge with a caller-defined command URL
172 * @param object must be serializable and contain the command
173 * @param originalCommand the original openHAB command (used for logging purposes)
174 * @param channelUID the channel that this command was send to (used for logging purposes)
175 * @param commandUrl the command URL
176 * @param acceptProcessing additional processing after the command was successfully send (might be null)
178 protected void sendCommand(@Nullable Object object, Command originalCommand, ChannelUID channelUID,
179 String commandUrl, @Nullable Runnable acceptProcessing) {
180 DeconzBridgeHandler bridgeHandler = getBridgeHandler();
181 if (bridgeHandler == null) {
184 String endpoint = Stream.of(resourceType.getIdentifier(), config.id, commandUrl)
185 .collect(Collectors.joining("/"));
187 bridgeHandler.sendObject(endpoint, object).thenAccept(v -> {
188 if (acceptProcessing != null) {
189 acceptProcessing.run();
191 if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
192 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
193 v.getResponseCode(), v.getBody());
195 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
197 }).exceptionally(e -> {
198 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
205 public void dispose() {
206 stopInitializationJob();
207 unregisterListener();
212 public void initialize() {
213 config = getConfigAs(ThingConfig.class);
215 Bridge bridge = getBridge();
216 if (bridge != null) {
217 bridgeStatusChanged(bridge.getStatusInfo());