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.Bridge;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.ThingStatusInfo;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.types.Command;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 import com.google.gson.Gson;
41 * This base thing doesn't establish any connections, that is done by the bridge Thing.
43 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
44 * and registers to the websocket connection as a listener.
46 * @author David Graeff - Initial contribution
47 * @author Jan N. Klug - Refactored to abstract class
50 public abstract class DeconzBaseThingHandler extends BaseThingHandler implements WebSocketMessageListener {
51 private final Logger logger = LoggerFactory.getLogger(DeconzBaseThingHandler.class);
52 protected final ResourceType resourceType;
53 protected ThingConfig config = new ThingConfig();
54 protected final Gson gson;
55 private @Nullable ScheduledFuture<?> initializationJob;
56 protected @Nullable WebSocketConnection connection;
58 public DeconzBaseThingHandler(Thing thing, Gson gson, ResourceType resourceType) {
61 this.resourceType = resourceType;
65 * Stops the API request
67 private void stopInitializationJob() {
68 ScheduledFuture<?> future = initializationJob;
71 initializationJob = null;
75 private void registerListener() {
76 WebSocketConnection conn = connection;
78 conn.registerListener(resourceType, config.id, this);
82 private void unregisterListener() {
83 WebSocketConnection conn = connection;
85 conn.unregisterListener(resourceType, config.id);
89 private @Nullable DeconzBridgeHandler getBridgeHandler() {
90 Bridge bridge = getBridge();
92 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
95 return (DeconzBridgeHandler) bridge.getHandler();
99 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
100 if (config.id.isEmpty()) {
101 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "ID not set");
105 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
106 // the bridge is ONLINE, we can communicate with the gateway, so we update the connection parameters and
107 // register the listener
108 DeconzBridgeHandler bridgeHandler = getBridgeHandler();
109 if (bridgeHandler == null) {
110 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
114 final WebSocketConnection webSocketConnection = bridgeHandler.getWebsocketConnection();
115 this.connection = webSocketConnection;
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 with the default command URL
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(@Nullable Object object, Command originalCommand, ChannelUID channelUID,
170 @Nullable Runnable acceptProcessing) {
171 sendCommand(object, originalCommand, channelUID, resourceType.getCommandUrl(), acceptProcessing);
175 * sends a command to the bridge with a caller-defined command URL
177 * @param object must be serializable and contain the command
178 * @param originalCommand the original openHAB command (used for logging purposes)
179 * @param channelUID the channel that this command was send to (used for logging purposes)
180 * @param commandUrl the command URL
181 * @param acceptProcessing additional processing after the command was successfully send (might be null)
183 protected void sendCommand(@Nullable Object object, Command originalCommand, ChannelUID channelUID,
184 String commandUrl, @Nullable Runnable acceptProcessing) {
185 DeconzBridgeHandler bridgeHandler = getBridgeHandler();
186 if (bridgeHandler == null) {
189 String endpoint = Stream.of(resourceType.getIdentifier(), config.id, commandUrl)
190 .collect(Collectors.joining("/"));
192 bridgeHandler.sendObject(endpoint, object).thenAccept(v -> {
193 if (acceptProcessing != null) {
194 acceptProcessing.run();
196 if (v.getResponseCode() != java.net.HttpURLConnection.HTTP_OK) {
197 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID,
198 v.getResponseCode(), v.getBody());
200 logger.trace("Result code={}, body={}", v.getResponseCode(), v.getBody());
202 }).exceptionally(e -> {
203 logger.warn("Sending command {} to channel {} failed: {} - {}", originalCommand, channelUID, e.getClass(),
210 public void dispose() {
211 stopInitializationJob();
212 unregisterListener();
217 public void initialize() {
218 config = getConfigAs(ThingConfig.class);
220 Bridge bridge = getBridge();
221 if (bridge != null) {
222 bridgeStatusChanged(bridge.getStatusInfo());