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.tapocontrol.internal.device;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.openhab.binding.tapocontrol.internal.TapoDiscoveryService;
24 import org.openhab.binding.tapocontrol.internal.api.TapoCloudConnector;
25 import org.openhab.binding.tapocontrol.internal.helpers.TapoCredentials;
26 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
27 import org.openhab.binding.tapocontrol.internal.structures.TapoBridgeConfiguration;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.ThingUID;
34 import org.openhab.core.thing.binding.BaseBridgeHandler;
35 import org.openhab.core.thing.binding.ThingHandlerService;
36 import org.openhab.core.types.Command;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import com.google.gson.JsonArray;
43 * The {@link TapoBridgeHandler} is responsible for handling commands, which are
44 * sent to one of the channels with a bridge.
46 * @author Christian Wild - Initial contribution
49 public class TapoBridgeHandler extends BaseBridgeHandler {
50 private final Logger logger = LoggerFactory.getLogger(TapoBridgeHandler.class);
51 private final TapoErrorHandler bridgeError = new TapoErrorHandler();
52 private final TapoBridgeConfiguration config;
53 private final HttpClient httpClient;
54 private @Nullable ScheduledFuture<?> startupJob;
55 private @Nullable ScheduledFuture<?> pollingJob;
56 private @Nullable ScheduledFuture<?> discoveryJob;
57 private @NonNullByDefault({}) TapoCloudConnector cloudConnector;
58 private @NonNullByDefault({}) TapoDiscoveryService discoveryService;
59 private TapoCredentials credentials;
63 public TapoBridgeHandler(Bridge bridge, HttpClient httpClient) {
65 Thing thing = getThing();
66 this.cloudConnector = new TapoCloudConnector(this, httpClient);
67 this.config = new TapoBridgeConfiguration(thing);
68 this.credentials = new TapoCredentials();
69 this.uid = thing.getUID().toString();
70 this.httpClient = httpClient;
73 /***********************************
75 * BRIDGE INITIALIZATION
77 ************************************/
81 * set credentials and login cloud
83 public void initialize() {
84 this.config.loadSettings();
85 this.credentials = new TapoCredentials(config.username, config.password);
92 private void activateBridge() {
93 // set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
94 updateStatus(ThingStatus.UNKNOWN);
96 // background initialization (delay it a little bit):
97 this.startupJob = scheduler.schedule(this::delayedStartUp, 1000, TimeUnit.MILLISECONDS);
101 public void handleCommand(ChannelUID channelUID, Command command) {
102 logger.debug("{} Bridge doesn't handle command: {}", this.uid, command);
106 public void dispose() {
107 stopScheduler(this.startupJob);
108 stopScheduler(this.pollingJob);
109 stopScheduler(this.discoveryJob);
114 * ACTIVATE DISCOVERY SERVICE
117 public Collection<Class<? extends ThingHandlerService>> getServices() {
118 return Collections.singleton(TapoDiscoveryService.class);
122 * Set DiscoveryService
124 * @param discoveryService
126 public void setDiscoveryService(TapoDiscoveryService discoveryService) {
127 this.discoveryService = discoveryService;
130 /***********************************
134 ************************************/
137 * delayed OneTime StartupJob
139 private void delayedStartUp() {
141 startCloudScheduler();
142 startDiscoveryScheduler();
146 * Start CloudLogin Scheduler
148 protected void startCloudScheduler() {
149 Integer pollingInterval = config.cloudReconnectIntervalM;
150 if (pollingInterval > 0) {
151 logger.trace("{} starting bridge cloud sheduler", this.uid);
153 this.pollingJob = scheduler.scheduleWithFixedDelay(this::loginCloud, pollingInterval, pollingInterval,
156 stopScheduler(this.pollingJob);
161 * Start DeviceDiscovery Scheduler
163 protected void startDiscoveryScheduler() {
164 Integer pollingInterval = config.discoveryIntervalM;
165 if (config.cloudDiscoveryEnabled && pollingInterval > 0) {
166 logger.trace("{} starting bridge discovery sheduler", this.uid);
168 this.discoveryJob = scheduler.scheduleWithFixedDelay(this::discoverDevices, 0, pollingInterval,
171 stopScheduler(this.discoveryJob);
178 * @param scheduler ScheduledFeature<?> which schould be stopped
180 protected void stopScheduler(@Nullable ScheduledFuture<?> scheduler) {
181 if (scheduler != null) {
182 scheduler.cancel(true);
187 /***********************************
191 ************************************/
193 * return device Error
197 public TapoErrorHandler getError() {
198 return this.bridgeError;
204 * @param tapoError TapoErrorHandler-Object
206 public void setError(TapoErrorHandler tapoError) {
207 this.bridgeError.set(tapoError);
210 /***********************************
212 * BRIDGE COMMUNICATIONS
214 ************************************/
221 public boolean loginCloud() {
222 bridgeError.reset(); // reset ErrorHandler
223 if (!config.username.isBlank() && !config.password.isBlank()) {
224 logger.debug("{} login with user {}", this.uid, config.username);
225 if (cloudConnector.login(config.username, config.password)) {
226 updateStatus(ThingStatus.ONLINE);
229 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, bridgeError.getMessage());
232 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "credentials not set");
237 /***********************************
241 ************************************/
244 * START DEVICE DISCOVERY
246 public void discoverDevices() {
247 this.discoveryService.startScan();
251 * GET DEVICELIST CONNECTED TO BRIDGE
255 public JsonArray getDeviceList() {
256 JsonArray deviceList = new JsonArray();
257 if (config.cloudDiscoveryEnabled) {
258 logger.trace("{} discover devicelist from cloud", this.uid);
259 deviceList = getDeviceListCloud();
265 * GET DEVICELIST FROM CLOUD
266 * returns all devices stored in cloud
268 * @return deviceList from cloud
270 private JsonArray getDeviceListCloud() {
271 logger.trace("{} getDeviceList from cloud", this.uid);
272 bridgeError.reset(); // reset ErrorHandler
273 JsonArray deviceList = new JsonArray();
275 deviceList = this.cloudConnector.getDeviceList();
280 /***********************************
284 ************************************/
286 public TapoCredentials getCredentials() {
287 return this.credentials;
290 public HttpClient getHttpClient() {
291 return this.httpClient;
294 public ThingUID getUID() {
295 return getThing().getUID();
298 public TapoBridgeConfiguration getBridgeConfig() {