]> git.basschouten.com Git - openhab-addons.git/blob
bd3ff79df9f81486f87bf1dc6078e9952e5c1b1b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.draytonwiser.internal.handler;
14
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
17 import java.util.function.Supplier;
18
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;
39
40 /**
41  * The {@link DraytonWiserThingHandler} is responsible for handling commands, which are
42  * sent to one of the channels.
43  *
44  * @author Andrew Schofield - Initial contribution
45  * @author Hilbrand Bouwkamp - Moved generic code from subclasses to this class
46  */
47 @NonNullByDefault
48 abstract class DraytonWiserThingHandler<T> extends BaseThingHandler implements DraytonWiserRefreshListener {
49
50     private final Logger logger = LoggerFactory.getLogger(getClass());
51
52     private @Nullable DraytonWiserApi api;
53     private @Nullable T data;
54     private @Nullable DraytonWiserDTO draytonWiserDTO;
55     private @Nullable ScheduledFuture<?> handleCommandRefreshFuture;
56
57     protected DraytonWiserThingHandler(final Thing thing) {
58         super(thing);
59     }
60
61     @Override
62     public void initialize() {
63         final HeatHubHandler bridgeHandler = getHeatHubHandler();
64
65         if (bridgeHandler == null) {
66             api = null;
67         } else {
68             api = bridgeHandler.getApi();
69             updateStatus(ThingStatus.UNKNOWN);
70         }
71     }
72
73     @Override
74     public final void handleCommand(final ChannelUID channelUID, final Command command) {
75         final HeatHubHandler heatHubHandler = getHeatHubHandler();
76
77         if (heatHubHandler == null) {
78             return; // if null status will be updated to offline
79         }
80         if (command instanceof RefreshType) {
81             heatHubHandler.refresh();
82         } else {
83             final DraytonWiserApi api = this.api;
84
85             if (api != null && data != null) {
86                 try {
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);
95                 }
96             }
97         }
98     }
99
100     private void disposehandleCommandRefreshFuture(final boolean force) {
101         final ScheduledFuture<?> future = handleCommandRefreshFuture;
102
103         if (future != null) {
104             future.cancel(force);
105         }
106     }
107
108     @Override
109     public final void dispose() {
110         disposehandleCommandRefreshFuture(true);
111     }
112
113     /**
114      * Performs the actual command. This method is only called when api and device cache are not null.
115      *
116      * @param channelId Channel id part of the Channel UID
117      * @param command the command to perform
118      * @throws DraytonWiserApiException
119      */
120     protected abstract void handleCommand(String channelId, Command command) throws DraytonWiserApiException;
121
122     @Override
123     public final void onRefresh(final DraytonWiserDTO draytonWiserDTO) {
124         this.draytonWiserDTO = draytonWiserDTO;
125         try {
126             if (ThingHandlerHelper.isHandlerInitialized(this)) {
127                 data = api == null ? null : collectData(draytonWiserDTO);
128                 refresh();
129                 if (data == null) {
130                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
131                             "No data received");
132                 } else {
133                     if (getThing().getStatus() != ThingStatus.ONLINE) {
134                         updateStatus(ThingStatus.ONLINE);
135                     }
136                 }
137             }
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());
141         }
142     }
143
144     /**
145      * Called to refresh the channels state.
146      */
147     protected abstract void refresh();
148
149     /**
150      * Conditionally updates the state. If no data or no api set the state will be set to UNDEF.
151      *
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
154      */
155     protected void updateState(final String channelId, final Supplier<State> stateFunction) {
156         final State state = api == null || data == null ? UnDefType.UNDEF : stateFunction.get();
157
158         updateState(channelId, state);
159     }
160
161     /**
162      * Returns the handler specific data object only if all data is available.
163      * If not all data is available it should return null.
164      *
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
168      */
169     protected abstract @Nullable T collectData(DraytonWiserDTO draytonWiserDTO) throws DraytonWiserApiException;
170
171     protected DraytonWiserApi getApi() {
172         final DraytonWiserApi api = this.api;
173
174         if (api == null) {
175             throw new IllegalStateException("API not set");
176         }
177         return api;
178     }
179
180     protected T getData() {
181         final @Nullable T data = this.data;
182
183         if (data == null) {
184             throw new IllegalStateException("Data not set");
185         }
186         return data;
187     }
188
189     protected DraytonWiserDTO getDraytonWiserDTO() {
190         final DraytonWiserDTO draytonWiserDTO = this.draytonWiserDTO;
191
192         if (draytonWiserDTO == null) {
193             throw new IllegalStateException("DraytonWiserDTO not set");
194         }
195         return draytonWiserDTO;
196     }
197
198     @Override
199     public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
200         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
201             if (getThing().getStatus() != ThingStatus.ONLINE) {
202                 final HeatHubHandler bridgeHandler = getHeatHubHandler();
203
204                 api = bridgeHandler == null ? null : bridgeHandler.getApi();
205                 updateStatus(ThingStatus.UNKNOWN);
206             }
207         } else {
208             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
209         }
210     }
211
212     private @Nullable HeatHubHandler getHeatHubHandler() {
213         final Bridge bridge = getBridge();
214
215         if (bridge == null) {
216             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
217             return null;
218         } else {
219             return (HeatHubHandler) bridge.getHandler();
220         }
221     }
222 }