]> git.basschouten.com Git - openhab-addons.git/blob
254eb5ec9c3a7452fa91886ff36b85c55671223d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.tapocontrol.internal.device;
14
15 import java.util.Collection;
16 import java.util.Set;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
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;
40
41 import com.google.gson.JsonArray;
42
43 /**
44  * The {@link TapoBridgeHandler} is responsible for handling commands, which are
45  * sent to one of the channels with a bridge.
46  *
47  * @author Christian Wild - Initial contribution
48  */
49 @NonNullByDefault
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;
61
62     private String uid;
63
64     public TapoBridgeHandler(Bridge bridge, HttpClient httpClient) {
65         super(bridge);
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;
72     }
73
74     /***********************************
75      *
76      * BRIDGE INITIALIZATION
77      *
78      ************************************/
79     @Override
80     /**
81      * INIT BRIDGE
82      * set credentials and login cloud
83      */
84     public void initialize() {
85         this.config = getConfigAs(TapoBridgeConfiguration.class);
86         this.credentials = new TapoCredentials(config.username, config.password);
87         activateBridge();
88     }
89
90     /**
91      * ACTIVATE BRIDGE
92      */
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);
96
97         // background initialization (delay it a little bit):
98         this.startupJob = scheduler.schedule(this::delayedStartUp, 1000, TimeUnit.MILLISECONDS);
99     }
100
101     @Override
102     public void handleCommand(ChannelUID channelUID, Command command) {
103         logger.debug("{} Bridge doesn't handle command: {}", this.uid, command);
104     }
105
106     @Override
107     public void dispose() {
108         stopScheduler(this.startupJob);
109         stopScheduler(this.pollingJob);
110         stopScheduler(this.discoveryJob);
111         super.dispose();
112     }
113
114     /**
115      * ACTIVATE DISCOVERY SERVICE
116      */
117     @Override
118     public Collection<Class<? extends ThingHandlerService>> getServices() {
119         return Set.of(TapoDiscoveryService.class);
120     }
121
122     /**
123      * Set DiscoveryService
124      * 
125      * @param discoveryService
126      */
127     public void setDiscoveryService(TapoDiscoveryService discoveryService) {
128         this.discoveryService = discoveryService;
129     }
130
131     /***********************************
132      *
133      * SCHEDULER
134      *
135      ************************************/
136
137     /**
138      * delayed OneTime StartupJob
139      */
140     private void delayedStartUp() {
141         loginCloud();
142         startCloudScheduler();
143         startDiscoveryScheduler();
144     }
145
146     /**
147      * Start CloudLogin Scheduler
148      */
149     protected void startCloudScheduler() {
150         Integer pollingInterval = config.reconnectInterval;
151         if (pollingInterval > 0) {
152             logger.trace("{} starting bridge cloud sheduler", this.uid);
153
154             this.pollingJob = scheduler.scheduleWithFixedDelay(this::loginCloud, pollingInterval, pollingInterval,
155                     TimeUnit.MINUTES);
156         } else {
157             stopScheduler(this.pollingJob);
158         }
159     }
160
161     /**
162      * Start DeviceDiscovery Scheduler
163      */
164     protected void startDiscoveryScheduler() {
165         Integer pollingInterval = config.discoveryInterval;
166         if (config.cloudDiscovery && pollingInterval > 0) {
167             logger.trace("{} starting bridge discovery sheduler", this.uid);
168
169             this.discoveryJob = scheduler.scheduleWithFixedDelay(this::discoverDevices, 0, pollingInterval,
170                     TimeUnit.MINUTES);
171         } else {
172             stopScheduler(this.discoveryJob);
173         }
174     }
175
176     /**
177      * Stop scheduler
178      * 
179      * @param scheduler ScheduledFeature<?> which schould be stopped
180      */
181     protected void stopScheduler(@Nullable ScheduledFuture<?> scheduler) {
182         if (scheduler != null) {
183             scheduler.cancel(true);
184             scheduler = null;
185         }
186     }
187
188     /***********************************
189      *
190      * ERROR HANDLER
191      *
192      ************************************/
193     /**
194      * return device Error
195      * 
196      * @return
197      */
198     public TapoErrorHandler getError() {
199         return this.bridgeError;
200     }
201
202     /**
203      * set device error
204      * 
205      * @param tapoError TapoErrorHandler-Object
206      */
207     public void setError(TapoErrorHandler tapoError) {
208         this.bridgeError.set(tapoError);
209     }
210
211     /***********************************
212      *
213      * BRIDGE COMMUNICATIONS
214      *
215      ************************************/
216
217     /**
218      * Login to Cloud
219      * 
220      * @return
221      */
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);
228                 return true;
229             } else {
230                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, bridgeError.getMessage());
231             }
232         } else {
233             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "credentials not set");
234         }
235         return false;
236     }
237
238     /***********************************
239      *
240      * DEVICE DISCOVERY
241      *
242      ************************************/
243
244     /**
245      * START DEVICE DISCOVERY
246      */
247     public void discoverDevices() {
248         this.discoveryService.startScan();
249     }
250
251     /**
252      * GET DEVICELIST CONNECTED TO BRIDGE
253      * 
254      * @return devicelist
255      */
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();
264         }
265         return deviceList;
266     }
267
268     /**
269      * GET DEVICELIST FROM CLOUD
270      * returns all devices stored in cloud
271      * 
272      * @return deviceList from cloud
273      */
274     private JsonArray getDeviceListCloud() {
275         logger.trace("{} getDeviceList from cloud", this.uid);
276         bridgeError.reset(); // reset ErrorHandler
277         JsonArray deviceList = new JsonArray();
278         if (loginCloud()) {
279             deviceList = this.cloudConnector.getDeviceList();
280         }
281         return deviceList;
282     }
283
284     /**
285      * GET DEVICELIST UDP
286      * return devices discovered by UDP
287      * 
288      * @return deviceList from udp
289      */
290     public JsonArray getDeviceListUDP() {
291         bridgeError.reset(); // reset ErrorHandler
292         TapoUDP udpDiscovery = new TapoUDP(credentials);
293         return udpDiscovery.udpScan();
294     }
295
296     /***********************************
297      *
298      * BRIDGE GETTERS
299      *
300      ************************************/
301
302     public TapoCredentials getCredentials() {
303         return this.credentials;
304     }
305
306     public HttpClient getHttpClient() {
307         return this.httpClient;
308     }
309
310     public ThingUID getUID() {
311         return getThing().getUID();
312     }
313
314     public TapoBridgeConfiguration getBridgeConfig() {
315         return this.config;
316     }
317 }