]> git.basschouten.com Git - openhab-addons.git/blob
0bd732ebc2bf2521edf5817ac280f922247e3220
[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.ecobee.internal.handler;
14
15 import static org.openhab.binding.ecobee.internal.EcobeeBindingConstants.CONFIG_THERMOSTAT_ID;
16
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.CopyOnWriteArraySet;
25 import java.util.concurrent.Future;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicInteger;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.openhab.binding.ecobee.internal.api.EcobeeApi;
33 import org.openhab.binding.ecobee.internal.config.EcobeeAccountConfiguration;
34 import org.openhab.binding.ecobee.internal.discovery.EcobeeDiscoveryService;
35 import org.openhab.binding.ecobee.internal.dto.SelectionDTO;
36 import org.openhab.binding.ecobee.internal.dto.thermostat.ThermostatDTO;
37 import org.openhab.binding.ecobee.internal.dto.thermostat.ThermostatUpdateRequestDTO;
38 import org.openhab.binding.ecobee.internal.dto.thermostat.summary.SummaryResponseDTO;
39 import org.openhab.binding.ecobee.internal.function.FunctionRequest;
40 import org.openhab.core.auth.client.oauth2.OAuthFactory;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.binding.BaseBridgeHandler;
47 import org.openhab.core.thing.binding.ThingHandler;
48 import org.openhab.core.thing.binding.ThingHandlerService;
49 import org.openhab.core.types.Command;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * The {@link EcobeeAccountBridgeHandler} is responsible for managing
55  * communication with the Ecobee API.
56  *
57  * @author Mark Hilbush - Initial contribution
58  */
59 @NonNullByDefault
60 public class EcobeeAccountBridgeHandler extends BaseBridgeHandler {
61
62     private static final int REFRESH_STARTUP_DELAY_SECONDS = 3;
63     private static final int REFRESH_INTERVAL_SECONDS = 1;
64     private static final int DEFAULT_REFRESH_INTERVAL_NORMAL_SECONDS = 20;
65     private static final int DEFAULT_REFRESH_INTERVAL_QUICK_SECONDS = 5;
66     private static final int DEFAULT_API_TIMEOUT_SECONDS = 20;
67
68     private final Logger logger = LoggerFactory.getLogger(EcobeeAccountBridgeHandler.class);
69
70     private final OAuthFactory oAuthFactory;
71     private final HttpClient httpClient;
72
73     private @NonNullByDefault({}) EcobeeApi api;
74     private @NonNullByDefault({}) String apiKey;
75     private int refreshIntervalNormal;
76     private int refreshIntervalQuick;
77     private int apiTimeout;
78     private boolean discoveryEnabled;
79
80     private final Map<String, EcobeeThermostatBridgeHandler> thermostatHandlers = new ConcurrentHashMap<>();
81     private final Set<String> thermostatIds = new CopyOnWriteArraySet<>();
82
83     private @Nullable Future<?> refreshThermostatsJob;
84     private final AtomicInteger refreshThermostatsCounter = new AtomicInteger(REFRESH_STARTUP_DELAY_SECONDS);
85
86     private @Nullable SummaryResponseDTO previousSummary;
87
88     public EcobeeAccountBridgeHandler(final Bridge bridge, OAuthFactory oAuthFactory, HttpClient httpClient) {
89         super(bridge);
90         this.oAuthFactory = oAuthFactory;
91         this.httpClient = httpClient;
92     }
93
94     @Override
95     public void initialize() {
96         logger.debug("AccountBridge: Initializing");
97
98         EcobeeAccountConfiguration config = getConfigAs(EcobeeAccountConfiguration.class);
99         apiKey = config.apiKey;
100
101         Integer value;
102         value = config.refreshIntervalNormal;
103         refreshIntervalNormal = value == null ? DEFAULT_REFRESH_INTERVAL_NORMAL_SECONDS : value;
104
105         value = config.refreshIntervalQuick;
106         refreshIntervalQuick = value == null ? DEFAULT_REFRESH_INTERVAL_QUICK_SECONDS : value;
107
108         value = config.apiTimeout;
109         apiTimeout = (value == null ? DEFAULT_API_TIMEOUT_SECONDS : value) * 1000;
110
111         Boolean booleanValue = config.discoveryEnabled;
112         discoveryEnabled = booleanValue == null ? false : booleanValue.booleanValue();
113         logger.debug("AccountBridge: Thermostat and sensor discovery is {}", discoveryEnabled ? "enabled" : "disabled");
114
115         api = new EcobeeApi(this, apiKey, apiTimeout, oAuthFactory, httpClient);
116
117         scheduleRefreshJob();
118         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking authorization");
119     }
120
121     @Override
122     public void dispose() {
123         cancelRefreshJob();
124         api.closeOAuthClientService();
125         logger.debug("AccountBridge: Disposing");
126     }
127
128     @Override
129     public void handleCommand(ChannelUID channelUID, Command command) {
130     }
131
132     @Override
133     public Collection<Class<? extends ThingHandlerService>> getServices() {
134         return Collections.singleton(EcobeeDiscoveryService.class);
135     }
136
137     @Override
138     public void childHandlerInitialized(ThingHandler thermostatHandler, Thing thermostatThing) {
139         String thermostatId = (String) thermostatThing.getConfiguration().get(CONFIG_THERMOSTAT_ID);
140         thermostatHandlers.put(thermostatId, (EcobeeThermostatBridgeHandler) thermostatHandler);
141         thermostatIds.add(thermostatId);
142         scheduleQuickPoll();
143         logger.debug("AccountBridge: Adding thermostat handler for {} with id {}", thermostatThing.getUID(),
144                 thermostatId);
145     }
146
147     @Override
148     public void childHandlerDisposed(ThingHandler thermostatHandler, Thing thermostatThing) {
149         String thermostatId = (String) thermostatThing.getConfiguration().get(CONFIG_THERMOSTAT_ID);
150         thermostatHandlers.remove(thermostatId);
151         thermostatIds.remove(thermostatId);
152         logger.debug("AccountBridge: Removing thermostat handler for {} with id {}", thermostatThing.getUID(),
153                 thermostatId);
154     }
155
156     public boolean isBackgroundDiscoveryEnabled() {
157         return discoveryEnabled;
158     }
159
160     public void updateBridgeStatus(ThingStatus status) {
161         updateStatus(status);
162     }
163
164     public void updateBridgeStatus(ThingStatus status, ThingStatusDetail statusDetail, String statusMessage) {
165         updateStatus(status, statusDetail, statusMessage);
166     }
167
168     public boolean performThermostatFunction(FunctionRequest request) {
169         boolean success = api.performThermostatFunction(request);
170         if (success) {
171             scheduleQuickPoll();
172         }
173         return success;
174     }
175
176     public boolean performThermostatUpdate(ThermostatUpdateRequestDTO request) {
177         boolean success = api.performThermostatUpdate(request);
178         if (success) {
179             scheduleQuickPoll();
180         }
181         return success;
182     }
183
184     public SelectionDTO getSelection() {
185         SelectionDTO mergedSelection = new SelectionDTO();
186         for (EcobeeThermostatBridgeHandler handler : new ArrayList<EcobeeThermostatBridgeHandler>(
187                 thermostatHandlers.values())) {
188             SelectionDTO selection = handler.getSelection();
189             logger.trace("AccountBridge: Thermostat {} selection: {}", handler.getThing().getUID(), selection);
190             mergedSelection.mergeSelection(selection);
191         }
192         return mergedSelection;
193     }
194
195     public void markOnline() {
196         updateStatus(ThingStatus.ONLINE);
197     }
198
199     public List<ThermostatDTO> getRegisteredThermostats() {
200         return api.queryRegisteredThermostats();
201     }
202
203     /*
204      * The refresh job updates the thermostat channels on the refresh interval set in the thermostat thing config.
205      * The thermostat update process involves first running a thermostat summary transaction to
206      * determine if any thermostat data has changed since the last summary. If any change is detected,
207      * a full query of the thermostats is performed.
208      */
209     private void refresh() {
210         refreshThermostats();
211     }
212
213     @SuppressWarnings("null")
214     private void refreshThermostats() {
215         if (refreshThermostatsCounter.getAndDecrement() == 0) {
216             refreshThermostatsCounter.set(refreshIntervalNormal);
217             SummaryResponseDTO summary = api.performThermostatSummaryQuery();
218             if (summary != null && summary.hasChanged(previousSummary) && !thermostatIds.isEmpty()) {
219                 for (ThermostatDTO thermostat : api.performThermostatQuery(thermostatIds)) {
220                     EcobeeThermostatBridgeHandler handler = thermostatHandlers.get(thermostat.identifier);
221                     if (handler != null) {
222                         handler.updateChannels(thermostat);
223                     }
224                 }
225             }
226             previousSummary = summary;
227         }
228     }
229
230     private void scheduleQuickPoll() {
231         if (refreshThermostatsCounter.get() > refreshIntervalQuick) {
232             logger.debug("AccountBridge: Scheduling quick poll");
233             refreshThermostatsCounter.set(refreshIntervalQuick);
234             forceFullNextPoll();
235         }
236     }
237
238     private void scheduleRefreshJob() {
239         logger.debug("AccountBridge: Scheduling thermostat refresh job");
240         cancelRefreshJob();
241         refreshThermostatsCounter.set(0);
242         refreshThermostatsJob = scheduler.scheduleWithFixedDelay(this::refresh, REFRESH_STARTUP_DELAY_SECONDS,
243                 REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
244     }
245
246     private void cancelRefreshJob() {
247         Future<?> localRefreshThermostatsJob = refreshThermostatsJob;
248         if (localRefreshThermostatsJob != null) {
249             forceFullNextPoll();
250             localRefreshThermostatsJob.cancel(true);
251             logger.debug("AccountBridge: Canceling thermostat refresh job");
252         }
253     }
254
255     private void forceFullNextPoll() {
256         previousSummary = null;
257     }
258 }