2 * Copyright (c) 2010-2024 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.TimeUnit;
20 import java.util.concurrent.TimeoutException;
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;
35 import com.google.gson.Gson;
36 import com.google.gson.JsonArray;
37 import com.google.gson.JsonObject;
40 * Handler class for TAPO-Cloud connections.
42 * @author Christian Wild - Initial contribution
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;
51 private String token = "";
52 private String url = TAPO_CLOUD_URL;
59 public TapoCloudConnector(TapoBridgeHandler bridge, HttpClient httpClient) {
61 this.httpClient = httpClient;
62 this.uid = bridge.getUID().getAsString();
68 * @param tapoError TapoErrorHandler
70 protected void handleError(TapoErrorHandler tapoError) {
71 this.bridge.setError(tapoError);
74 /***********************************
76 * HTTP (Cloud)-Actions
78 ************************************/
81 * LOGIN TO CLOUD (get Token)
83 * @param username unencrypted username
84 * @param password unencrypted password
85 * @return true if login was successfull
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();
96 public void logout() {
101 * GET TOKEN FROM TAPO-CLOUD
105 * @param terminalUUID
108 private String getToken(String email, String password, String terminalUUID) {
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();
120 ContentResponse response = sendCloudRequest(TAPO_CLOUD_URL, payload);
121 if (response != null) {
122 token = getTokenFromResponse(response);
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();
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);
143 handleError(new TapoErrorHandler(ERR_API_JSON_DECODE_FAIL));
144 logger.trace("unexpected json-response '{}'", rBody);
147 handleError(new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE));
148 logger.warn("invalid response while login");
156 * @return JsonArray with deviceList
158 public JsonArray getDeviceList() {
160 PayloadBuilder plBuilder = new PayloadBuilder();
161 plBuilder.method = "getDeviceList";
162 String payload = plBuilder.getPayload();
164 ContentResponse response = sendCloudRequest(this.url, payload);
165 if (response != null) {
166 return getDeviceListFromResponse(response);
168 return new JsonArray();
172 * get DeviceList from Contenresponse
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");
189 /* return errorcode from device */
190 handleError(new TapoErrorHandler(errorCode, "device answers with errorcode"));
191 logger.trace("cloud returns error: '{}'", rBody);
194 logger.trace("enexpected json-response '{}'", rBody);
197 logger.trace("response error '{}'", response.getContentAsString());
199 return new JsonArray();
202 /***********************************
206 ************************************/
208 * SEND SYNCHRON HTTP-REQUEST
210 * @param url url request is sent to
211 * @param payload payload (String) to send
212 * @return ContentResponse of request
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);
220 httpRequest.header("content-type", CONTENT_TYPE_JSON);
221 httpRequest.header("Accept", CONTENT_TYPE_JSON);
223 /* add request body */
224 httpRequest.content(new StringContentProvider(payload, CONTENT_CHARSET), CONTENT_TYPE_JSON);
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));