2 * Copyright (c) 2010-2023 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.api.TapoUDP;
26 import org.openhab.binding.tapocontrol.internal.helpers.TapoCredentials;
27 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
28 import org.openhab.binding.tapocontrol.internal.structures.TapoBridgeConfiguration;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingUID;
35 import org.openhab.core.thing.binding.BaseBridgeHandler;
36 import org.openhab.core.thing.binding.ThingHandlerService;
37 import org.openhab.core.types.Command;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.google.gson.JsonArray;
44 * The {@link TapoBridgeHandler} is responsible for handling commands, which are
45 * sent to one of the channels with a bridge.
47 * @author Christian Wild - Initial contribution
50 public class TapoBridgeHandler extends BaseBridgeHandler {
51 private final Logger logger = LoggerFactory.getLogger(TapoBridgeHandler.class);
52 private final TapoErrorHandler bridgeError = new TapoErrorHandler();
53 private final HttpClient httpClient;
54 private TapoBridgeConfiguration config;
55 private @Nullable ScheduledFuture<?> startupJob;
56 private @Nullable ScheduledFuture<?> pollingJob;
57 private @Nullable ScheduledFuture<?> discoveryJob;
58 private @NonNullByDefault({}) TapoCloudConnector cloudConnector;
59 private @NonNullByDefault({}) TapoDiscoveryService discoveryService;
60 private TapoCredentials credentials;
64 public TapoBridgeHandler(Bridge bridge, HttpClient httpClient) {
66 Thing thing = getThing();
67 this.cloudConnector = new TapoCloudConnector(this, httpClient);
68 this.config = new TapoBridgeConfiguration();
69 this.credentials = new TapoCredentials();
70 this.uid = thing.getUID().toString();
71 this.httpClient = httpClient;
74 /***********************************
76 * BRIDGE INITIALIZATION
78 ************************************/
82 * set credentials and login cloud
84 public void initialize() {
85 this.config = getConfigAs(TapoBridgeConfiguration.class);
86 this.credentials = new TapoCredentials(config.username, config.password);
93 private void activateBridge() {
94 // set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
95 updateStatus(ThingStatus.UNKNOWN);
97 // background initialization (delay it a little bit):
98 this.startupJob = scheduler.schedule(this::delayedStartUp, 1000, TimeUnit.MILLISECONDS);
102 public void handleCommand(ChannelUID channelUID, Command command) {
103 logger.debug("{} Bridge doesn't handle command: {}", this.uid, command);
107 public void dispose() {
108 stopScheduler(this.startupJob);
109 stopScheduler(this.pollingJob);
110 stopScheduler(this.discoveryJob);
115 * ACTIVATE DISCOVERY SERVICE
118 public Collection<Class<? extends ThingHandlerService>> getServices() {
119 return Set.of(TapoDiscoveryService.class);
123 * Set DiscoveryService
125 * @param discoveryService
127 public void setDiscoveryService(TapoDiscoveryService discoveryService) {
128 this.discoveryService = discoveryService;
131 /***********************************
135 ************************************/
138 * delayed OneTime StartupJob
140 private void delayedStartUp() {
142 startCloudScheduler();
143 startDiscoveryScheduler();
147 * Start CloudLogin Scheduler
149 protected void startCloudScheduler() {
150 Integer pollingInterval = config.reconnectInterval;
151 if (pollingInterval > 0) {
152 logger.trace("{} starting bridge cloud sheduler", this.uid);
154 this.pollingJob = scheduler.scheduleWithFixedDelay(this::loginCloud, pollingInterval, pollingInterval,
157 stopScheduler(this.pollingJob);
162 * Start DeviceDiscovery Scheduler
164 protected void startDiscoveryScheduler() {
165 Integer pollingInterval = config.discoveryInterval;
166 if (config.cloudDiscovery && pollingInterval > 0) {
167 logger.trace("{} starting bridge discovery sheduler", this.uid);
169 this.discoveryJob = scheduler.scheduleWithFixedDelay(this::discoverDevices, 0, pollingInterval,
172 stopScheduler(this.discoveryJob);
179 * @param scheduler ScheduledFeature<?> which schould be stopped
181 protected void stopScheduler(@Nullable ScheduledFuture<?> scheduler) {
182 if (scheduler != null) {
183 scheduler.cancel(true);
188 /***********************************
192 ************************************/
194 * return device Error
198 public TapoErrorHandler getError() {
199 return this.bridgeError;
205 * @param tapoError TapoErrorHandler-Object
207 public void setError(TapoErrorHandler tapoError) {
208 this.bridgeError.set(tapoError);
211 /***********************************
213 * BRIDGE COMMUNICATIONS
215 ************************************/
222 public boolean loginCloud() {
223 bridgeError.reset(); // reset ErrorHandler
224 if (!config.username.isBlank() && !config.password.isBlank()) {
225 logger.debug("{} login with user {}", this.uid, config.username);
226 if (cloudConnector.login(config.username, config.password)) {
227 updateStatus(ThingStatus.ONLINE);
230 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, bridgeError.getMessage());
233 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "credentials not set");
238 /***********************************
242 ************************************/
245 * START DEVICE DISCOVERY
247 public void discoverDevices() {
248 this.discoveryService.startScan();
252 * GET DEVICELIST CONNECTED TO BRIDGE
256 public JsonArray getDeviceList() {
257 JsonArray deviceList = new JsonArray();
258 if (config.cloudDiscovery) {
259 logger.trace("{} discover devicelist from cloud", this.uid);
260 deviceList = getDeviceListCloud();
261 } else if (config.udpDiscovery) {
262 logger.trace("{} discover devicelist from udp", this.uid);
263 deviceList = getDeviceListUDP();
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();
286 * return devices discovered by UDP
288 * @return deviceList from udp
290 public JsonArray getDeviceListUDP() {
291 bridgeError.reset(); // reset ErrorHandler
292 TapoUDP udpDiscovery = new TapoUDP(credentials);
293 return udpDiscovery.udpScan();
296 /***********************************
300 ************************************/
302 public TapoCredentials getCredentials() {
303 return this.credentials;
306 public HttpClient getHttpClient() {
307 return this.httpClient;
310 public ThingUID getUID() {
311 return getThing().getUID();
314 public TapoBridgeConfiguration getBridgeConfig() {