]> git.basschouten.com Git - openhab-addons.git/blob
bc12f8c2f058308f551c0302bfbfb7a28ba06983
[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.vesync.internal.handlers;
14
15 import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.concurrent.CopyOnWriteArrayList;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import javax.validation.constraints.NotNull;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.vesync.internal.VeSyncBridgeConfiguration;
30 import org.openhab.binding.vesync.internal.api.IHttpClientProvider;
31 import org.openhab.binding.vesync.internal.api.VeSyncV2ApiHelper;
32 import org.openhab.binding.vesync.internal.discovery.DeviceMetaDataUpdatedHandler;
33 import org.openhab.binding.vesync.internal.discovery.VeSyncDiscoveryService;
34 import org.openhab.binding.vesync.internal.dto.requests.VeSyncAuthenticatedRequest;
35 import org.openhab.binding.vesync.internal.dto.responses.VeSyncManagedDeviceBase;
36 import org.openhab.binding.vesync.internal.dto.responses.VeSyncUserSession;
37 import org.openhab.binding.vesync.internal.exceptions.AuthenticationException;
38 import org.openhab.binding.vesync.internal.exceptions.DeviceUnknownException;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingUID;
45 import org.openhab.core.thing.binding.BaseBridgeHandler;
46 import org.openhab.core.thing.binding.ThingHandler;
47 import org.openhab.core.thing.binding.ThingHandlerService;
48 import org.openhab.core.types.Command;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * The {@link VeSyncBridgeHandler} is responsible for handling the bridge things created to use the VeSync
54  * API. This way, the user credentials may be entered only once.
55  *
56  * @author David Goodyear - Initial Contribution
57  */
58 @NonNullByDefault
59 public class VeSyncBridgeHandler extends BaseBridgeHandler implements VeSyncClient {
60
61     private static final int DEFAULT_DEVICE_SCAN_INTERVAL = 600;
62     private static final int DEFAULT_DEVICE_SCAN_RECOVERY_INTERVAL = 60;
63     private static final int DEFAULT_DEVICE_SCAN_DISABLED = -1;
64
65     private final Logger logger = LoggerFactory.getLogger(VeSyncBridgeHandler.class);
66
67     private @Nullable ScheduledFuture<?> backgroundDiscoveryPollingJob;
68
69     protected final VeSyncV2ApiHelper api = new VeSyncV2ApiHelper();
70     private IHttpClientProvider httpClientProvider;
71
72     private volatile int backgroundScanTime = -1;
73     private final Object scanConfigLock = new Object();
74
75     public VeSyncBridgeHandler(Bridge bridge, @NotNull IHttpClientProvider httpClientProvider) {
76         super(bridge);
77         this.httpClientProvider = httpClientProvider;
78     }
79
80     public ThingUID getUID() {
81         return thing.getUID();
82     }
83
84     protected void checkIfIncreaseScanRateRequired() {
85         logger.trace("Checking if increased background scanning for new devices / base information is required");
86         boolean frequentScanReq = false;
87         for (Thing th : getThing().getThings()) {
88             ThingHandler handler = th.getHandler();
89             if (handler instanceof VeSyncBaseDeviceHandler) {
90                 if (((VeSyncBaseDeviceHandler) handler).requiresMetaDataFrequentUpdates()) {
91                     frequentScanReq = true;
92                     break;
93                 }
94             }
95         }
96
97         if (!frequentScanReq
98                 && api.getMacLookupMap().values().stream().anyMatch(x -> "offline".equals(x.connectionStatus))) {
99             frequentScanReq = true;
100         }
101
102         if (frequentScanReq) {
103             setBackgroundScanInterval(DEFAULT_DEVICE_SCAN_RECOVERY_INTERVAL);
104         } else {
105             setBackgroundScanInterval(DEFAULT_DEVICE_SCAN_INTERVAL);
106         }
107     }
108
109     protected void setBackgroundScanInterval(final int seconds) {
110         synchronized (scanConfigLock) {
111             ScheduledFuture<?> job = backgroundDiscoveryPollingJob;
112             if (backgroundScanTime != seconds) {
113                 if (seconds > 0) {
114                     logger.trace("Scheduling background scanning for new devices / base information every {} seconds",
115                             seconds);
116                 } else {
117                     logger.trace("Disabling background scanning for new devices / base information");
118                 }
119                 // Cancel the current scan's and re-schedule as required
120                 if (job != null && !job.isCancelled()) {
121                     job.cancel(true);
122                     backgroundDiscoveryPollingJob = null;
123                 }
124                 if (seconds > 0) {
125                     backgroundDiscoveryPollingJob = scheduler.scheduleWithFixedDelay(
126                             this::runDeviceScanSequenceNoAuthErrors, seconds, seconds, TimeUnit.SECONDS);
127                 }
128                 backgroundScanTime = seconds;
129             }
130         }
131     }
132
133     public void registerMetaDataUpdatedHandler(DeviceMetaDataUpdatedHandler dmduh) {
134         handlers.add(dmduh);
135     }
136
137     public void unregisterMetaDataUpdatedHandler(DeviceMetaDataUpdatedHandler dmduh) {
138         handlers.remove(dmduh);
139     }
140
141     private final CopyOnWriteArrayList<DeviceMetaDataUpdatedHandler> handlers = new CopyOnWriteArrayList<>();
142
143     public void runDeviceScanSequenceNoAuthErrors() {
144         try {
145             runDeviceScanSequence();
146             updateStatus(ThingStatus.ONLINE);
147         } catch (AuthenticationException ae) {
148             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Check login credentials");
149         }
150     }
151
152     public void runDeviceScanSequence() throws AuthenticationException {
153         logger.trace("Scanning for new devices / base information now");
154         api.discoverDevices();
155         handlers.forEach(x -> x.handleMetadataRetrieved(this));
156         checkIfIncreaseScanRateRequired();
157
158         this.updateThings();
159     }
160
161     public java.util.stream.Stream<@NotNull VeSyncManagedDeviceBase> getAirPurifiersMetadata() {
162         return api.getMacLookupMap().values().stream().filter(x -> !VeSyncBaseDeviceHandler
163                 .getDeviceFamilyMetadata(x.getDeviceType(), VeSyncDeviceAirPurifierHandler.DEV_TYPE_FAMILY_AIR_PURIFIER,
164                         VeSyncDeviceAirPurifierHandler.SUPPORTED_MODEL_FAMILIES)
165                 .equals(VeSyncBaseDeviceHandler.UNKNOWN));
166     }
167
168     public java.util.stream.Stream<@NotNull VeSyncManagedDeviceBase> getAirHumidifiersMetadata() {
169         return api.getMacLookupMap().values().stream()
170                 .filter(x -> !VeSyncBaseDeviceHandler
171                         .getDeviceFamilyMetadata(x.getDeviceType(),
172                                 VeSyncDeviceAirHumidifierHandler.DEV_TYPE_FAMILY_AIR_HUMIDIFIER,
173                                 VeSyncDeviceAirHumidifierHandler.SUPPORTED_MODEL_FAMILIES)
174                         .equals(VeSyncBaseDeviceHandler.UNKNOWN));
175     }
176
177     protected void updateThings() {
178         final VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
179         getThing().getThings().forEach((th) -> updateThing(config, th.getHandler()));
180     }
181
182     public void updateThing(ThingHandler handler) {
183         final VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
184         updateThing(config, handler);
185     }
186
187     private void updateThing(VeSyncBridgeConfiguration config, @Nullable ThingHandler handler) {
188         if (handler instanceof VeSyncBaseDeviceHandler) {
189             ((VeSyncBaseDeviceHandler) handler).updateDeviceMetaData();
190             ((VeSyncBaseDeviceHandler) handler).updateBridgeBasedPolls(config);
191         }
192     }
193
194     @Override
195     public Collection<Class<? extends ThingHandlerService>> getServices() {
196         return Collections.singleton(VeSyncDiscoveryService.class);
197     }
198
199     @Override
200     public void initialize() {
201         api.setHttpClient(httpClientProvider.getHttpClient());
202
203         VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class);
204
205         scheduler.submit(() -> {
206             final String passwordMd5 = VeSyncV2ApiHelper.calculateMd5(config.password);
207
208             try {
209                 api.login(config.username, passwordMd5, "Europe/London");
210                 api.updateBridgeData(this);
211                 runDeviceScanSequence();
212                 updateStatus(ThingStatus.ONLINE);
213             } catch (final AuthenticationException ae) {
214                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Check login credentials");
215                 // The background scan will keep trying to authenticate in case the users credentials are updated on the
216                 // veSync servers,
217                 // to match the binding's configuration.
218             }
219         });
220     }
221
222     @Override
223     public void dispose() {
224         setBackgroundScanInterval(DEFAULT_DEVICE_SCAN_DISABLED);
225         api.setHttpClient(null);
226     }
227
228     @Override
229     public void handleCommand(ChannelUID channelUID, Command command) {
230         logger.warn("Handling command for VeSync bridge handler.");
231     }
232
233     public void handleNewUserSession(final @Nullable VeSyncUserSession userSessionData) {
234         final Map<String, String> newProps = new HashMap<>();
235         if (userSessionData != null) {
236             newProps.put(DEVICE_PROP_BRIDGE_REG_TS, userSessionData.registerTime);
237             newProps.put(DEVICE_PROP_BRIDGE_COUNTRY_CODE, userSessionData.countryCode);
238             newProps.put(DEVICE_PROP_BRIDGE_ACCEPT_LANG, userSessionData.acceptLanguage);
239         }
240         this.updateProperties(newProps);
241     }
242
243     public String reqV2Authorized(final String url, final String macId, final VeSyncAuthenticatedRequest requestData)
244             throws AuthenticationException, DeviceUnknownException {
245         return api.reqV2Authorized(url, macId, requestData);
246     }
247 }