2 * Copyright (c) 2010-2024 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;
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 TapoBridgeConfiguration config = new TapoBridgeConfiguration();
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.credentials = new TapoCredentials();
68 this.uid = thing.getUID().toString();
69 this.httpClient = httpClient;
72 /***********************************
74 * BRIDGE INITIALIZATION
76 ************************************/
80 * set credentials and login cloud
82 public void initialize() {
83 this.config = getConfigAs(TapoBridgeConfiguration.class);
84 this.credentials = new TapoCredentials(config.username, config.password);
91 private void activateBridge() {
92 // set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
93 updateStatus(ThingStatus.UNKNOWN);
95 // background initialization (delay it a little bit):
96 this.startupJob = scheduler.schedule(this::delayedStartUp, 1000, TimeUnit.MILLISECONDS);
100 public void handleCommand(ChannelUID channelUID, Command command) {
101 logger.debug("{} Bridge doesn't handle command: {}", this.uid, command);
105 public void dispose() {
106 stopScheduler(this.startupJob);
107 stopScheduler(this.pollingJob);
108 stopScheduler(this.discoveryJob);
113 * ACTIVATE DISCOVERY SERVICE
116 public Collection<Class<? extends ThingHandlerService>> getServices() {
117 return Set.of(TapoDiscoveryService.class);
121 * Set DiscoveryService
123 * @param discoveryService
125 public void setDiscoveryService(TapoDiscoveryService discoveryService) {
126 this.discoveryService = discoveryService;
129 /***********************************
133 ************************************/
136 * delayed OneTime StartupJob
138 private void delayedStartUp() {
140 startCloudScheduler();
141 startDiscoveryScheduler();
145 * Start CloudLogin Scheduler
147 protected void startCloudScheduler() {
148 int pollingInterval = config.reconnectInterval;
149 TimeUnit timeUnit = TimeUnit.MINUTES;
150 if (pollingInterval > 0) {
151 logger.debug("{} starting cloudScheduler with interval {} {}", this.uid, pollingInterval, timeUnit);
153 this.pollingJob = scheduler.scheduleWithFixedDelay(this::loginCloud, pollingInterval, pollingInterval,
156 logger.debug("({}) cloudScheduler disabled with config '0'", uid);
157 stopScheduler(this.pollingJob);
162 * Start DeviceDiscovery Scheduler
164 protected void startDiscoveryScheduler() {
165 int pollingInterval = config.discoveryInterval;
166 TimeUnit timeUnit = TimeUnit.MINUTES;
167 if (config.cloudDiscovery && pollingInterval > 0) {
168 logger.debug("{} starting discoveryScheduler with interval {} {}", this.uid, pollingInterval, timeUnit);
170 this.discoveryJob = scheduler.scheduleWithFixedDelay(this::discoverDevices, 0, pollingInterval, timeUnit);
172 logger.debug("({}) discoveryScheduler disabled with config '0'", uid);
173 stopScheduler(this.discoveryJob);
180 * @param scheduler {@code ScheduledFeature<?>} which schould be stopped
182 protected void stopScheduler(@Nullable ScheduledFuture<?> scheduler) {
183 if (scheduler != null) {
184 scheduler.cancel(true);
189 /***********************************
193 ************************************/
195 * return device Error
199 public TapoErrorHandler getError() {
200 return this.bridgeError;
206 * @param tapoError TapoErrorHandler-Object
208 public void setError(TapoErrorHandler tapoError) {
209 this.bridgeError.set(tapoError);
212 /***********************************
214 * BRIDGE COMMUNICATIONS
216 ************************************/
223 public boolean loginCloud() {
224 bridgeError.reset(); // reset ErrorHandler
225 if (!config.username.isBlank() && !config.password.isBlank()) {
226 logger.debug("{} login with user {}", this.uid, config.username);
227 if (cloudConnector.login(config.username, config.password)) {
228 updateStatus(ThingStatus.ONLINE);
231 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, bridgeError.getMessage());
234 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "credentials not set");
239 /***********************************
243 ************************************/
246 * START DEVICE DISCOVERY
248 public void discoverDevices() {
249 this.discoveryService.startScan();
253 * GET DEVICELIST CONNECTED TO BRIDGE
257 public JsonArray getDeviceList() {
258 JsonArray deviceList = new JsonArray();
259 if (config.cloudDiscovery) {
260 logger.trace("{} discover devicelist from cloud", this.uid);
261 deviceList = getDeviceListCloud();
263 logger.info("{} Discovery disabled in bridge settings ", this.uid);
269 * GET DEVICELIST FROM CLOUD
270 * returns all devices stored in cloud
272 * @return deviceList from cloud
274 private JsonArray getDeviceListCloud() {
275 logger.trace("{} getDeviceList from cloud", this.uid);
276 bridgeError.reset(); // reset ErrorHandler
277 JsonArray deviceList = new JsonArray();
279 deviceList = this.cloudConnector.getDeviceList();
284 /***********************************
288 ************************************/
290 public TapoCredentials getCredentials() {
291 return this.credentials;
294 public HttpClient getHttpClient() {
295 return this.httpClient;
298 public ThingUID getUID() {
299 return getThing().getUID();
302 public TapoBridgeConfiguration getBridgeConfig() {