]> git.basschouten.com Git - openhab-addons.git/blob
a466e2d98525ecc17a3db03532955fd3454381ac
[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.tapocontrol.internal.api;
14
15 import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*;
16 import static org.openhab.binding.tapocontrol.internal.constants.TapoErrorConstants.*;
17 import static org.openhab.binding.tapocontrol.internal.constants.TapoThingConstants.*;
18 import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*;
19
20 import java.net.InetAddress;
21 import java.util.HashMap;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler;
25 import org.openhab.binding.tapocontrol.internal.device.TapoDevice;
26 import org.openhab.binding.tapocontrol.internal.helpers.PayloadBuilder;
27 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
28 import org.openhab.binding.tapocontrol.internal.structures.TapoDeviceInfo;
29 import org.openhab.binding.tapocontrol.internal.structures.TapoEnergyData;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import com.google.gson.Gson;
34 import com.google.gson.JsonObject;
35
36 /**
37  * Handler class for TAPO Smart Home device connections.
38  * This class uses asynchronous HttpClient-Requests
39  *
40  * @author Christian Wild - Initial contribution
41  */
42 @NonNullByDefault
43 public class TapoDeviceConnector extends TapoDeviceHttpApi {
44     private final Logger logger = LoggerFactory.getLogger(TapoDeviceConnector.class);
45     private final String uid;
46     private final TapoDevice device;
47     private TapoDeviceInfo deviceInfo;
48     private TapoEnergyData energyData;
49     private Gson gson;
50     private long lastQuery = 0L;
51     private long lastSent = 0L;
52     private long lastLogin = 0L;
53
54     /**
55      * INIT CLASS
56      *
57      * @param config TapoControlConfiguration class
58      */
59     public TapoDeviceConnector(TapoDevice device, TapoBridgeHandler bridgeThingHandler) {
60         super(device, bridgeThingHandler);
61         this.device = device;
62         this.gson = new Gson();
63         this.deviceInfo = new TapoDeviceInfo();
64         this.energyData = new TapoEnergyData();
65         this.uid = device.getThingUID().getAsString();
66     }
67
68     /***********************************
69      *
70      * LOGIN FUNCTIONS
71      *
72      ************************************/
73     /**
74      * login
75      *
76      * @return true if success
77      */
78     public boolean login() {
79         if (this.pingDevice()) {
80             logger.trace("({}) sending login to url '{}'", uid, deviceURL);
81
82             long now = System.currentTimeMillis();
83             if (now > this.lastLogin + TAPO_LOGIN_MIN_GAP_MS) {
84                 this.lastLogin = now;
85                 unsetToken();
86                 unsetCookie();
87
88                 /* create ssl-handschake (cookie) */
89                 String cookie = createHandshake();
90                 if (!cookie.isBlank()) {
91                     setCookie(cookie);
92                     String token = queryToken();
93                     setToken(token);
94                 }
95             } else {
96                 logger.trace("({}) not done cause of min_gap '{}'", uid, TAPO_LOGIN_MIN_GAP_MS);
97             }
98             return this.loggedIn();
99         } else {
100             logger.debug("({}) no ping while login '{}'", uid, this.ipAddress);
101             handleError(new TapoErrorHandler(ERR_DEVICE_OFFLINE, "no ping while login"));
102             return false;
103         }
104     }
105
106     /***********************************
107      *
108      * DEVICE ACTIONS
109      *
110      ************************************/
111
112     /**
113      * send custom command to device
114      * 
115      * @param plBuilder Payloadbuilder with unencrypted payload
116      */
117     public void sendCustomQuery(String queryMethod) {
118         /* create payload */
119         PayloadBuilder plBuilder = new PayloadBuilder();
120         plBuilder.method = queryMethod;
121         sendCustomPayload(plBuilder);
122     }
123
124     /**
125      * send custom command to device
126      * 
127      * @param plBuilder Payloadbuilder with unencrypted payload
128      */
129     public void sendCustomPayload(PayloadBuilder plBuilder) {
130         long now = System.currentTimeMillis();
131         if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) {
132             String payload = plBuilder.getPayload();
133             sendSecurePasstrhroug(payload, DEVICE_CMD_CUSTOM);
134         } else {
135             logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastSent);
136         }
137     }
138
139     /**
140      * send "set_device_info" command to device
141      *
142      * @param name Name of command to send
143      * @param value Value to send to control
144      */
145     public void sendDeviceCommand(String name, Object value) {
146         long now = System.currentTimeMillis();
147         if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) {
148             this.lastSent = now;
149
150             /* create payload */
151             PayloadBuilder plBuilder = new PayloadBuilder();
152             plBuilder.method = DEVICE_CMD_SETINFO;
153             plBuilder.addParameter(name, value);
154             String payload = plBuilder.getPayload();
155
156             sendSecurePasstrhroug(payload, DEVICE_CMD_SETINFO);
157         } else {
158             logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastSent);
159         }
160     }
161
162     /**
163      * send multiple "set_device_info" commands to device
164      *
165      * @param map HashMap<String, Object> (name, value of parameter)
166      */
167     public void sendDeviceCommands(HashMap<String, Object> map) {
168         long now = System.currentTimeMillis();
169         if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) {
170             this.lastSent = now;
171
172             /* create payload */
173             PayloadBuilder plBuilder = new PayloadBuilder();
174             plBuilder.method = DEVICE_CMD_SETINFO;
175             for (HashMap.Entry<String, Object> entry : map.entrySet()) {
176                 plBuilder.addParameter(entry.getKey(), entry.getValue());
177             }
178             String payload = plBuilder.getPayload();
179
180             sendSecurePasstrhroug(payload, DEVICE_CMD_SETINFO);
181         } else {
182             logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastSent);
183         }
184     }
185
186     /**
187      * Query Info from Device adn refresh deviceInfo
188      */
189     public void queryInfo() {
190         queryInfo(false);
191     }
192
193     /**
194      * Query Info from Device adn refresh deviceInfo
195      * 
196      * @param ignoreGap ignore gap to last query. query anyway
197      */
198     public void queryInfo(boolean ignoreGap) {
199         logger.trace("({}) DeviceConnetor_queryInfo from '{}'", uid, deviceURL);
200         long now = System.currentTimeMillis();
201         if (ignoreGap || now > this.lastQuery + TAPO_SEND_MIN_GAP_MS) {
202             this.lastQuery = now;
203
204             /* create payload */
205             PayloadBuilder plBuilder = new PayloadBuilder();
206             plBuilder.method = DEVICE_CMD_GETINFO;
207             String payload = plBuilder.getPayload();
208
209             sendSecurePasstrhroug(payload, DEVICE_CMD_GETINFO);
210         } else {
211             logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastQuery);
212         }
213     }
214
215     /**
216      * Get energy usage from device
217      */
218     public void getEnergyUsage() {
219         logger.trace("({}) DeviceConnetor_getEnergyUsage from '{}'", uid, deviceURL);
220
221         /* create payload */
222         PayloadBuilder plBuilder = new PayloadBuilder();
223         plBuilder.method = DEVICE_CMD_GETENERGY;
224         String payload = plBuilder.getPayload();
225
226         sendSecurePasstrhroug(payload, DEVICE_CMD_GETENERGY);
227     }
228
229     /**
230      * SEND SECUREPASSTHROUGH
231      * encprypt payload and send to device
232      * 
233      * @param payload payload sent to device
234      * @param command command executed - this will handle result
235      */
236     protected void sendSecurePasstrhroug(String payload, String command) {
237         /* encrypt payload */
238         logger.trace("({}) encrypting payload '{}'", uid, payload);
239         String encryptedPayload = encryptPayload(payload);
240
241         /* create secured payload */
242         PayloadBuilder plBuilder = new PayloadBuilder();
243         plBuilder.method = "securePassthrough";
244         plBuilder.addParameter("request", encryptedPayload);
245         String securePassthroughPayload = plBuilder.getPayload();
246
247         sendAsyncRequest(deviceURL, securePassthroughPayload, command);
248     }
249
250     /***********************************
251      *
252      * HANDLE RESPONSES
253      *
254      ************************************/
255
256     /**
257      * Handle SuccessResponse (setDeviceInfo)
258      * 
259      * @param responseBody String with responseBody from device
260      */
261     @Override
262     protected void handleSuccessResponse(String responseBody) {
263         JsonObject jsnResult = getJsonFromResponse(responseBody);
264         Integer errorCode = jsonObjectToInt(jsnResult, "error_code", ERR_JSON_DECODE_FAIL);
265         if (errorCode != 0) {
266             logger.debug("({}) set deviceInfo not successful: {}", uid, jsnResult);
267             this.device.handleConnectionState();
268         }
269         this.device.responsePasstrough(responseBody);
270     }
271
272     /**
273      * 
274      * handle JsonResponse (getDeviceInfo)
275      * 
276      * @param responseBody String with responseBody from device
277      */
278     @Override
279     protected void handleDeviceResult(String responseBody) {
280         JsonObject jsnResult = getJsonFromResponse(responseBody);
281         if (jsnResult.has(DEVICE_PROPERTY_ID)) {
282             this.deviceInfo = new TapoDeviceInfo(jsnResult);
283             this.device.setDeviceInfo(deviceInfo);
284         } else {
285             this.deviceInfo = new TapoDeviceInfo();
286             this.device.handleConnectionState();
287         }
288         this.device.responsePasstrough(responseBody);
289     }
290
291     /**
292      * handle JsonResponse (getEnergyData)
293      * 
294      * @param responseBody String with responseBody from device
295      */
296     @Override
297     protected void handleEnergyResult(String responseBody) {
298         JsonObject jsnResult = getJsonFromResponse(responseBody);
299         if (jsnResult.has(ENERGY_PROPERTY_POWER)) {
300             this.energyData = new TapoEnergyData(jsnResult);
301             this.device.setEnergyData(energyData);
302         } else {
303             this.energyData = new TapoEnergyData();
304         }
305         this.device.responsePasstrough(responseBody);
306     }
307
308     /**
309      * handle custom response
310      * 
311      * @param responseBody String with responseBody from device
312      */
313     @Override
314     protected void handleCustomResponse(String responseBody) {
315         this.device.responsePasstrough(responseBody);
316     }
317
318     /**
319      * handle error
320      * 
321      * @param te TapoErrorHandler
322      */
323     @Override
324     protected void handleError(TapoErrorHandler tapoError) {
325         this.device.setError(tapoError);
326     }
327
328     /**
329      * get Json from response
330      * 
331      * @param responseBody
332      * @return JsonObject with result
333      */
334     private JsonObject getJsonFromResponse(String responseBody) {
335         JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class);
336         /* get errocode (0=success) */
337         if (jsonObject != null) {
338             Integer errorCode = jsonObjectToInt(jsonObject, "error_code");
339             if (errorCode == 0) {
340                 /* decrypt response */
341                 jsonObject = gson.fromJson(responseBody, JsonObject.class);
342                 logger.trace("({}) received result: {}", uid, responseBody);
343                 if (jsonObject != null) {
344                     /* return result if set / else request was successful */
345                     if (jsonObject.has("result")) {
346                         return jsonObject.getAsJsonObject("result");
347                     } else {
348                         return jsonObject;
349                     }
350                 }
351             } else {
352                 /* return errorcode from device */
353                 TapoErrorHandler te = new TapoErrorHandler(errorCode, "device answers with errorcode");
354                 logger.debug("({}) device answers with errorcode {} - {}", uid, errorCode, te.getMessage());
355                 handleError(te);
356                 return jsonObject;
357             }
358         }
359         logger.debug("({}) sendPayload exception {}", uid, responseBody);
360         handleError(new TapoErrorHandler(ERR_HTTP_RESPONSE));
361         return new JsonObject();
362     }
363
364     /***********************************
365      *
366      * GET RESULTS
367      *
368      ************************************/
369
370     /**
371      * Check if device is online
372      * 
373      * @return true if device is online
374      */
375     public Boolean isOnline() {
376         return isOnline(false);
377     }
378
379     /**
380      * Check if device is online
381      * 
382      * @param raiseError if true
383      * @return true if device is online
384      */
385     public Boolean isOnline(Boolean raiseError) {
386         if (pingDevice()) {
387             return true;
388         } else {
389             logger.trace("({})  device is offline (no ping)", uid);
390             if (raiseError) {
391                 handleError(new TapoErrorHandler(ERR_DEVICE_OFFLINE));
392             }
393             logout();
394             return false;
395         }
396     }
397
398     /**
399      * IP-Adress
400      * 
401      * @return String ipAdress
402      */
403     public String getIP() {
404         return this.ipAddress;
405     }
406
407     /**
408      * PING IP Adress
409      * 
410      * @return true if ping successfull
411      */
412     public Boolean pingDevice() {
413         try {
414             InetAddress address = InetAddress.getByName(this.ipAddress);
415             return address.isReachable(TAPO_PING_TIMEOUT_MS);
416         } catch (Exception e) {
417             logger.debug("({}) InetAdress throws: {}", uid, e.getMessage());
418             return false;
419         }
420     }
421 }