2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.tapocontrol.internal.api;
15 import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*;
16 import static org.openhab.binding.tapocontrol.internal.constants.TapoErrorCode.*;
18 import java.util.UUID;
19 import java.util.concurrent.TimeoutException;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.eclipse.jetty.client.api.ContentResponse;
25 import org.eclipse.jetty.client.api.Request;
26 import org.eclipse.jetty.client.util.StringContentProvider;
27 import org.eclipse.jetty.http.HttpMethod;
28 import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler;
29 import org.openhab.binding.tapocontrol.internal.helpers.PayloadBuilder;
30 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.google.gson.Gson;
35 import com.google.gson.JsonArray;
36 import com.google.gson.JsonObject;
39 * Handler class for TAPO-Cloud connections.
41 * @author Christian Wild - Initial contribution
44 public class TapoCloudConnector {
45 private final Logger logger = LoggerFactory.getLogger(TapoCloudConnector.class);
46 private final TapoBridgeHandler bridge;
47 private final Gson gson = new Gson();
48 private final HttpClient httpClient;
50 private String token = "";
51 private String url = TAPO_CLOUD_URL;
58 public TapoCloudConnector(TapoBridgeHandler bridge, HttpClient httpClient) {
60 this.httpClient = httpClient;
61 this.uid = bridge.getUID().getAsString();
67 * @param tapoError TapoErrorHandler
69 protected void handleError(TapoErrorHandler tapoError) {
70 this.bridge.setError(tapoError);
73 /***********************************
75 * HTTP (Cloud)-Actions
77 ************************************/
80 * LOGIN TO CLOUD (get Token)
82 * @param username unencrypted username
83 * @param password unencrypted password
84 * @return true if login was successfull
86 public Boolean login(String username, String password) {
87 this.token = getToken(username, password, UUID.randomUUID().toString());
88 this.url = TAPO_CLOUD_URL + "?token=" + token;
89 return !this.token.isBlank();
95 public void logout() {
100 * GET TOKEN FROM TAPO-CLOUD
104 * @param terminalUUID
107 private String getToken(String email, String password, String terminalUUID) {
110 /* create login payload */
111 PayloadBuilder plBuilder = new PayloadBuilder();
112 plBuilder.method = "login";
113 plBuilder.addParameter("appType", TAPO_APP_TYPE);
114 plBuilder.addParameter("cloudUserName", email);
115 plBuilder.addParameter("cloudPassword", password);
116 plBuilder.addParameter("terminalUUID", terminalUUID);
117 String payload = plBuilder.getPayload();
119 ContentResponse response = sendCloudRequest(TAPO_CLOUD_URL, payload);
120 if (response != null) {
121 token = getTokenFromResponse(response);
126 private String getTokenFromResponse(ContentResponse response) {
127 /* work with response */
128 if (response.getStatus() == 200) {
129 String rBody = response.getContentAsString();
130 JsonObject jsonObject = gson.fromJson(rBody, JsonObject.class);
131 if (jsonObject != null) {
132 Integer errorCode = jsonObject.get("error_code").getAsInt();
133 if (errorCode == 0) {
134 token = jsonObject.getAsJsonObject("result").get("token").getAsString();
136 /* return errorcode from device */
137 String msg = jsonObject.get("msg").getAsString();
138 handleError(new TapoErrorHandler(errorCode, msg));
139 logger.trace("cloud returns error: '{}'", rBody);
142 handleError(new TapoErrorHandler(ERR_API_JSON_DECODE_FAIL));
143 logger.trace("unexpected json-response '{}'", rBody);
146 handleError(new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE));
147 logger.warn("invalid response while login");
155 * @return JsonArray with deviceList
157 public JsonArray getDeviceList() {
159 PayloadBuilder plBuilder = new PayloadBuilder();
160 plBuilder.method = "getDeviceList";
161 String payload = plBuilder.getPayload();
163 ContentResponse response = sendCloudRequest(this.url, payload);
164 if (response != null) {
165 return getDeviceListFromResponse(response);
167 return new JsonArray();
171 * get DeviceList from Contenresponse
176 private JsonArray getDeviceListFromResponse(ContentResponse response) {
177 /* work with response */
178 if (response.getStatus() == 200) {
179 String rBody = response.getContentAsString();
180 JsonObject jsonObject = gson.fromJson(rBody, JsonObject.class);
181 if (jsonObject != null) {
182 /* get errocode (0=success) */
183 Integer errorCode = jsonObject.get("error_code").getAsInt();
184 if (errorCode == 0) {
185 JsonObject result = jsonObject.getAsJsonObject("result");
186 return result.getAsJsonArray("deviceList");
188 /* return errorcode from device */
189 handleError(new TapoErrorHandler(errorCode, "device answers with errorcode"));
190 logger.trace("cloud returns error: '{}'", rBody);
193 logger.trace("enexpected json-response '{}'", rBody);
196 logger.trace("response error '{}'", response.getContentAsString());
198 return new JsonArray();
201 /***********************************
205 ************************************/
207 * SEND SYNCHRON HTTP-REQUEST
209 * @param url url request is sent to
210 * @param payload payload (String) to send
211 * @return ContentResponse of request
214 protected ContentResponse sendCloudRequest(String url, String payload) {
215 Request httpRequest = httpClient.newRequest(url).method(HttpMethod.POST.toString());
218 httpRequest.header("content-type", CONTENT_TYPE_JSON);
219 httpRequest.header("Accept", CONTENT_TYPE_JSON);
221 /* add request body */
222 httpRequest.content(new StringContentProvider(payload, CONTENT_CHARSET), CONTENT_TYPE_JSON);
225 return httpRequest.send();
226 } catch (InterruptedException e) {
227 logger.debug("({}) sending request interrupted: {}", uid, e.toString());
228 handleError(new TapoErrorHandler(e));
229 } catch (TimeoutException e) {
230 logger.debug("({}) sending request timeout: {}", uid, e.toString());
231 handleError(new TapoErrorHandler(ERR_BINDING_CONNECT_TIMEOUT, e.toString()));
232 } catch (Exception e) {
233 logger.debug("({}) sending request failed: {}", uid, e.toString());
234 handleError(new TapoErrorHandler(e));