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