]> git.basschouten.com Git - openhab-addons.git/blob
55f82ff1507445c2dba7eccac8344bd0708591f4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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         String encryptedPayload = encryptPayload(payload);
239
240         /* create secured payload */
241         PayloadBuilder plBuilder = new PayloadBuilder();
242         plBuilder.method = "securePassthrough";
243         plBuilder.addParameter("request", encryptedPayload);
244         String securePassthroughPayload = plBuilder.getPayload();
245
246         sendAsyncRequest(deviceURL, securePassthroughPayload, command);
247     }
248
249     /***********************************
250      *
251      * HANDLE RESPONSES
252      *
253      ************************************/
254
255     /**
256      * Handle SuccessResponse (setDeviceInfo)
257      * 
258      * @param responseBody String with responseBody from device
259      */
260     @Override
261     protected void handleSuccessResponse(String responseBody) {
262         JsonObject jsnResult = getJsonFromResponse(responseBody);
263         Integer errorCode = jsonObjectToInt(jsnResult, "error_code", ERR_JSON_DECODE_FAIL);
264         if (errorCode != 0) {
265             logger.debug("({}) set deviceInfo not succesfull: {}", uid, jsnResult);
266             this.device.handleConnectionState();
267         }
268         this.device.responsePasstrough(responseBody);
269     }
270
271     /**
272      * 
273      * handle JsonResponse (getDeviceInfo)
274      * 
275      * @param responseBody String with responseBody from device
276      */
277     @Override
278     protected void handleDeviceResult(String responseBody) {
279         JsonObject jsnResult = getJsonFromResponse(responseBody);
280         if (jsnResult.has(DEVICE_PROPERTY_ID)) {
281             this.deviceInfo = new TapoDeviceInfo(jsnResult);
282             this.device.setDeviceInfo(deviceInfo);
283         } else {
284             this.deviceInfo = new TapoDeviceInfo();
285             this.device.handleConnectionState();
286         }
287         this.device.responsePasstrough(responseBody);
288     }
289
290     /**
291      * handle JsonResponse (getEnergyData)
292      * 
293      * @param responseBody String with responseBody from device
294      */
295     @Override
296     protected void handleEnergyResult(String responseBody) {
297         JsonObject jsnResult = getJsonFromResponse(responseBody);
298         if (jsnResult.has(ENERGY_PROPERTY_POWER)) {
299             this.energyData = new TapoEnergyData(jsnResult);
300             this.device.setEnergyData(energyData);
301         } else {
302             this.energyData = new TapoEnergyData();
303         }
304         this.device.responsePasstrough(responseBody);
305     }
306
307     /**
308      * handle custom response
309      * 
310      * @param responseBody String with responseBody from device
311      */
312     @Override
313     protected void handleCustomResponse(String responseBody) {
314         this.device.responsePasstrough(responseBody);
315     }
316
317     /**
318      * handle error
319      * 
320      * @param te TapoErrorHandler
321      */
322     @Override
323     protected void handleError(TapoErrorHandler tapoError) {
324         this.device.setError(tapoError);
325     }
326
327     /**
328      * get Json from response
329      * 
330      * @param responseBody
331      * @return JsonObject with result
332      */
333     private JsonObject getJsonFromResponse(String responseBody) {
334         JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class);
335         /* get errocode (0=success) */
336         if (jsonObject != null) {
337             Integer errorCode = jsonObjectToInt(jsonObject, "error_code");
338             if (errorCode == 0) {
339                 /* decrypt response */
340                 jsonObject = gson.fromJson(responseBody, JsonObject.class);
341                 logger.trace("({}) received result: {}", uid, responseBody);
342                 if (jsonObject != null) {
343                     /* return result if set / else request was successfull */
344                     if (jsonObject.has("result")) {
345                         return jsonObject.getAsJsonObject("result");
346                     } else {
347                         return jsonObject;
348                     }
349                 }
350             } else {
351                 /* return errorcode from device */
352                 TapoErrorHandler te = new TapoErrorHandler(errorCode, "device answers with errorcode");
353                 logger.debug("({}) device answers with errorcode {} - {}", uid, errorCode, te.getMessage());
354                 handleError(te);
355                 return jsonObject;
356             }
357         }
358         logger.debug("({}) sendPayload exception {}", uid, responseBody);
359         handleError(new TapoErrorHandler(ERR_HTTP_RESPONSE));
360         return new JsonObject();
361     }
362
363     /***********************************
364      *
365      * GET RESULTS
366      *
367      ************************************/
368
369     /**
370      * Check if device is online
371      * 
372      * @return true if device is online
373      */
374     public Boolean isOnline() {
375         return isOnline(false);
376     }
377
378     /**
379      * Check if device is online
380      * 
381      * @param raiseError if true
382      * @return true if device is online
383      */
384     public Boolean isOnline(Boolean raiseError) {
385         if (pingDevice()) {
386             return true;
387         } else {
388             logger.trace("({})  device is offline (no ping)", uid);
389             if (raiseError) {
390                 handleError(new TapoErrorHandler(ERR_DEVICE_OFFLINE));
391             }
392             logout();
393             return false;
394         }
395     }
396
397     /**
398      * IP-Adress
399      * 
400      * @return String ipAdress
401      */
402     public String getIP() {
403         return this.ipAddress;
404     }
405
406     /**
407      * PING IP Adress
408      * 
409      * @return true if ping successfull
410      */
411     public Boolean pingDevice() {
412         try {
413             InetAddress address = InetAddress.getByName(this.ipAddress);
414             return address.isReachable(TAPO_PING_TIMEOUT_MS);
415         } catch (Exception e) {
416             logger.debug("({}) InetAdress throws: {}", uid, e.getMessage());
417             return false;
418         }
419     }
420 }