]> git.basschouten.com Git - openhab-addons.git/blob
0fdafd9ca7d3050e93f382c77dd1effba694b90c
[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.api;
14
15 import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*;
16 import static org.openhab.binding.tapocontrol.internal.constants.TapoErrorCode.*;
17
18 import java.util.UUID;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.eclipse.jetty.client.api.ContentResponse;
26 import org.eclipse.jetty.client.api.Request;
27 import org.eclipse.jetty.client.util.StringContentProvider;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler;
30 import org.openhab.binding.tapocontrol.internal.helpers.PayloadBuilder;
31 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.gson.Gson;
36 import com.google.gson.JsonArray;
37 import com.google.gson.JsonObject;
38
39 /**
40  * Handler class for TAPO-Cloud connections.
41  *
42  * @author Christian Wild - Initial contribution
43  */
44 @NonNullByDefault
45 public class TapoCloudConnector {
46     private final Logger logger = LoggerFactory.getLogger(TapoCloudConnector.class);
47     private final TapoBridgeHandler bridge;
48     private final Gson gson = new Gson();
49     private final HttpClient httpClient;
50
51     private String token = "";
52     private String url = TAPO_CLOUD_URL;
53     private String uid;
54
55     /**
56      * INIT CLASS
57      * 
58      */
59     public TapoCloudConnector(TapoBridgeHandler bridge, HttpClient httpClient) {
60         this.bridge = bridge;
61         this.httpClient = httpClient;
62         this.uid = bridge.getUID().getAsString();
63     }
64
65     /**
66      * handle error
67      * 
68      * @param tapoError TapoErrorHandler
69      */
70     protected void handleError(TapoErrorHandler tapoError) {
71         this.bridge.setError(tapoError);
72     }
73
74     /***********************************
75      *
76      * HTTP (Cloud)-Actions
77      *
78      ************************************/
79
80     /**
81      * LOGIN TO CLOUD (get Token)
82      * 
83      * @param username unencrypted username
84      * @param password unencrypted password
85      * @return true if login was successfull
86      */
87     public Boolean login(String username, String password) {
88         this.token = getToken(username, password, UUID.randomUUID().toString());
89         this.url = TAPO_CLOUD_URL + "?token=" + token;
90         return !this.token.isBlank();
91     }
92
93     /**
94      * logout
95      */
96     public void logout() {
97         this.token = "";
98     }
99
100     /**
101      * GET TOKEN FROM TAPO-CLOUD
102      * 
103      * @param email
104      * @param password
105      * @param terminalUUID
106      * @return
107      */
108     private String getToken(String email, String password, String terminalUUID) {
109         String token = "";
110
111         /* create login payload */
112         PayloadBuilder plBuilder = new PayloadBuilder();
113         plBuilder.method = "login";
114         plBuilder.addParameter("appType", TAPO_APP_TYPE);
115         plBuilder.addParameter("cloudUserName", email);
116         plBuilder.addParameter("cloudPassword", password);
117         plBuilder.addParameter("terminalUUID", terminalUUID);
118         String payload = plBuilder.getPayload();
119
120         ContentResponse response = sendCloudRequest(TAPO_CLOUD_URL, payload);
121         if (response != null) {
122             token = getTokenFromResponse(response);
123         }
124         return token;
125     }
126
127     private String getTokenFromResponse(ContentResponse response) {
128         /* work with response */
129         if (response.getStatus() == 200) {
130             String rBody = response.getContentAsString();
131             JsonObject jsonObject = gson.fromJson(rBody, JsonObject.class);
132             if (jsonObject != null) {
133                 Integer errorCode = jsonObject.get("error_code").getAsInt();
134                 if (errorCode == 0) {
135                     token = jsonObject.getAsJsonObject("result").get("token").getAsString();
136                 } else {
137                     /* return errorcode from device */
138                     String msg = jsonObject.get("msg").getAsString();
139                     handleError(new TapoErrorHandler(errorCode, msg));
140                     logger.trace("cloud returns error: '{}'", rBody);
141                 }
142             } else {
143                 handleError(new TapoErrorHandler(ERR_API_JSON_DECODE_FAIL));
144                 logger.trace("unexpected json-response '{}'", rBody);
145             }
146         } else {
147             handleError(new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE));
148             logger.warn("invalid response while login");
149             token = "";
150         }
151         return token;
152     }
153
154     /**
155      * 
156      * @return JsonArray with deviceList
157      */
158     public JsonArray getDeviceList() {
159         /* create payload */
160         PayloadBuilder plBuilder = new PayloadBuilder();
161         plBuilder.method = "getDeviceList";
162         String payload = plBuilder.getPayload();
163
164         ContentResponse response = sendCloudRequest(this.url, payload);
165         if (response != null) {
166             return getDeviceListFromResponse(response);
167         }
168         return new JsonArray();
169     }
170
171     /**
172      * get DeviceList from Contenresponse
173      * 
174      * @param response
175      * @return
176      */
177     private JsonArray getDeviceListFromResponse(ContentResponse response) {
178         /* work with response */
179         if (response.getStatus() == 200) {
180             String rBody = response.getContentAsString();
181             JsonObject jsonObject = gson.fromJson(rBody, JsonObject.class);
182             if (jsonObject != null) {
183                 /* get errocode (0=success) */
184                 Integer errorCode = jsonObject.get("error_code").getAsInt();
185                 if (errorCode == 0) {
186                     JsonObject result = jsonObject.getAsJsonObject("result");
187                     return result.getAsJsonArray("deviceList");
188                 } else {
189                     /* return errorcode from device */
190                     handleError(new TapoErrorHandler(errorCode, "device answers with errorcode"));
191                     logger.trace("cloud returns error: '{}'", rBody);
192                 }
193             } else {
194                 logger.trace("enexpected json-response '{}'", rBody);
195             }
196         } else {
197             logger.trace("response error '{}'", response.getContentAsString());
198         }
199         return new JsonArray();
200     }
201
202     /***********************************
203      *
204      * HTTP-ACTIONS
205      *
206      ************************************/
207     /**
208      * SEND SYNCHRON HTTP-REQUEST
209      * 
210      * @param url url request is sent to
211      * @param payload payload (String) to send
212      * @return ContentResponse of request
213      */
214     @Nullable
215     protected ContentResponse sendCloudRequest(String url, String payload) {
216         Request httpRequest = httpClient.newRequest(url).method(HttpMethod.POST.toString());
217         httpRequest.timeout(TAPO_HTTP_CLOUD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
218
219         /* set header */
220         httpRequest.header("content-type", CONTENT_TYPE_JSON);
221         httpRequest.header("Accept", CONTENT_TYPE_JSON);
222
223         /* add request body */
224         httpRequest.content(new StringContentProvider(payload, CONTENT_CHARSET), CONTENT_TYPE_JSON);
225
226         try {
227             return httpRequest.send();
228         } catch (InterruptedException e) {
229             logger.debug("({}) sending request interrupted: {}", uid, e.toString());
230             handleError(new TapoErrorHandler(e));
231         } catch (TimeoutException e) {
232             logger.debug("({}) sending request timeout: {}", uid, e.toString());
233             handleError(new TapoErrorHandler(ERR_BINDING_CONNECT_TIMEOUT, e.toString()));
234         } catch (Exception e) {
235             logger.debug("({}) sending request failed: {}", uid, e.toString());
236             handleError(new TapoErrorHandler(e));
237         }
238         return null;
239     }
240 }