2 * Copyright (c) 2010-2023 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.draytonwiser.internal.handler;
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
17 import java.util.function.Supplier;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.draytonwiser.internal.DraytonWiserRefreshListener;
22 import org.openhab.binding.draytonwiser.internal.api.DraytonWiserApi;
23 import org.openhab.binding.draytonwiser.internal.api.DraytonWiserApiException;
24 import org.openhab.binding.draytonwiser.internal.model.DraytonWiserDTO;
25 import org.openhab.core.thing.Bridge;
26 import org.openhab.core.thing.ChannelUID;
27 import org.openhab.core.thing.Thing;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingStatusDetail;
30 import org.openhab.core.thing.ThingStatusInfo;
31 import org.openhab.core.thing.binding.BaseThingHandler;
32 import org.openhab.core.thing.util.ThingHandlerHelper;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.State;
36 import org.openhab.core.types.UnDefType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link DraytonWiserThingHandler} is responsible for handling commands, which are
42 * sent to one of the channels.
44 * @author Andrew Schofield - Initial contribution
45 * @author Hilbrand Bouwkamp - Moved generic code from subclasses to this class
48 abstract class DraytonWiserThingHandler<T> extends BaseThingHandler implements DraytonWiserRefreshListener {
50 private final Logger logger = LoggerFactory.getLogger(getClass());
52 private @Nullable DraytonWiserApi api;
53 private @Nullable T data;
54 private @Nullable DraytonWiserDTO draytonWiserDTO;
55 private @Nullable ScheduledFuture<?> handleCommandRefreshFuture;
57 protected DraytonWiserThingHandler(final Thing thing) {
62 public void initialize() {
63 final HeatHubHandler bridgeHandler = getHeatHubHandler();
65 if (bridgeHandler == null) {
68 api = bridgeHandler.getApi();
69 updateStatus(ThingStatus.UNKNOWN);
74 public final void handleCommand(final ChannelUID channelUID, final Command command) {
75 final HeatHubHandler heatHubHandler = getHeatHubHandler();
77 if (heatHubHandler == null) {
78 return; // if null status will be updated to offline
80 if (command instanceof RefreshType) {
81 heatHubHandler.refresh();
83 final DraytonWiserApi api = this.api;
85 if (api != null && data != null) {
87 handleCommand(channelUID.getId(), command);
88 // cancel previous refresh, but wait for it to finish, so no forced cancel
89 disposehandleCommandRefreshFuture(false);
90 // update the state after the heathub has had time to react
91 handleCommandRefreshFuture = scheduler.schedule(heatHubHandler::refresh, 5, TimeUnit.SECONDS);
92 } catch (final DraytonWiserApiException e) {
93 logger.warn("Failed to handle command {} for channel {}: {}", command, channelUID, e.getMessage());
94 logger.trace("DraytonWiserApiException", e);
100 private void disposehandleCommandRefreshFuture(final boolean force) {
101 final ScheduledFuture<?> future = handleCommandRefreshFuture;
103 if (future != null) {
104 future.cancel(force);
109 public final void dispose() {
110 disposehandleCommandRefreshFuture(true);
114 * Performs the actual command. This method is only called when api and device cache are not null.
116 * @param channelId Channel id part of the Channel UID
117 * @param command the command to perform
118 * @throws DraytonWiserApiException
120 protected abstract void handleCommand(String channelId, Command command) throws DraytonWiserApiException;
123 public final void onRefresh(final DraytonWiserDTO draytonWiserDTO) {
124 this.draytonWiserDTO = draytonWiserDTO;
126 if (ThingHandlerHelper.isHandlerInitialized(this)) {
127 data = api == null ? null : collectData(draytonWiserDTO);
130 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
133 if (getThing().getStatus() != ThingStatus.ONLINE) {
134 updateStatus(ThingStatus.ONLINE);
138 } catch (final RuntimeException | DraytonWiserApiException e) {
139 logger.debug("Exception occurred during refresh: {}", e.getMessage(), e);
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
145 * Called to refresh the channels state.
147 protected abstract void refresh();
150 * Conditionally updates the state. If no data or no api set the state will be set to UNDEF.
152 * @param channelId String id of the channel to update
153 * @param stateFunction function to return the state, called when api and data are available
155 protected void updateState(final String channelId, final Supplier<State> stateFunction) {
156 final State state = api == null || data == null ? UnDefType.UNDEF : stateFunction.get();
158 updateState(channelId, state);
162 * Returns the handler specific data object only if all data is available.
163 * If not all data is available it should return null.
165 * @param draytonWiserDTO data object with domain data as received from the hub
166 * @return handler data object if available else null
167 * @throws DraytonWiserApiException
169 protected abstract @Nullable T collectData(DraytonWiserDTO draytonWiserDTO) throws DraytonWiserApiException;
171 protected DraytonWiserApi getApi() {
172 final DraytonWiserApi api = this.api;
175 throw new IllegalStateException("API not set");
180 protected T getData() {
181 final @Nullable T data = this.data;
184 throw new IllegalStateException("Data not set");
189 protected DraytonWiserDTO getDraytonWiserDTO() {
190 final DraytonWiserDTO draytonWiserDTO = this.draytonWiserDTO;
192 if (draytonWiserDTO == null) {
193 throw new IllegalStateException("DraytonWiserDTO not set");
195 return draytonWiserDTO;
199 public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
200 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
201 if (getThing().getStatus() != ThingStatus.ONLINE) {
202 final HeatHubHandler bridgeHandler = getHeatHubHandler();
204 api = bridgeHandler == null ? null : bridgeHandler.getApi();
205 updateStatus(ThingStatus.UNKNOWN);
208 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
212 private @Nullable HeatHubHandler getHeatHubHandler() {
213 final Bridge bridge = getBridge();
215 if (bridge == null) {
216 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
219 return (HeatHubHandler) bridge.getHandler();