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.protocol.passthrough;
15 import static org.openhab.binding.tapocontrol.internal.TapoControlHandlerFactory.GSON;
16 import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*;
17 import static org.openhab.binding.tapocontrol.internal.constants.TapoErrorCode.*;
18 import static org.openhab.binding.tapocontrol.internal.helpers.utils.JsonUtils.*;
19 import static org.openhab.binding.tapocontrol.internal.helpers.utils.TapoUtils.*;
21 import java.util.Objects;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jetty.client.HttpResponse;
28 import org.eclipse.jetty.client.api.ContentResponse;
29 import org.eclipse.jetty.client.api.Request;
30 import org.eclipse.jetty.client.api.Result;
31 import org.eclipse.jetty.client.util.BufferingResponseListener;
32 import org.eclipse.jetty.client.util.StringContentProvider;
33 import org.eclipse.jetty.http.HttpMethod;
34 import org.openhab.binding.tapocontrol.internal.api.TapoConnectorInterface;
35 import org.openhab.binding.tapocontrol.internal.api.protocol.TapoProtocolInterface;
36 import org.openhab.binding.tapocontrol.internal.dto.TapoBaseRequestInterface;
37 import org.openhab.binding.tapocontrol.internal.dto.TapoRequest;
38 import org.openhab.binding.tapocontrol.internal.dto.TapoResponse;
39 import org.openhab.binding.tapocontrol.internal.helpers.TapoCredentials;
40 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * Handler class for TAPO-PASSTHROUGH-Protocol
47 * @author Christian Wild - Initial contribution
50 public class PassthroughProtocol implements TapoProtocolInterface {
51 private final Logger logger = LoggerFactory.getLogger(PassthroughProtocol.class);
52 private final TapoConnectorInterface httpDelegator;
53 private final String uid;
55 /***********************
57 **********************/
59 public PassthroughProtocol(TapoConnectorInterface httpDelegator) {
60 this.httpDelegator = httpDelegator;
61 uid = httpDelegator.getThingUID() + " / HTTP-Passtrhough";
64 /***********************
66 **********************/
69 public boolean login(TapoCredentials tapoCredentials) throws TapoErrorHandler {
70 logger.debug("({}) login not implemented", uid);
71 throw new TapoErrorHandler(ERR_BINDING_NOT_IMPLEMENTED, "NOT NEEDED");
75 public void logout() {
76 logger.trace("({}) logout not implemented", uid);
80 public boolean isLoggedIn() {
81 logger.debug("({}) isLoggedIn not implemented", uid);
85 /***********************
87 **********************/
90 * send synchronous request - response will be handled in [responseReceived()] function
93 public void sendRequest(TapoRequest tapoRequest) throws TapoErrorHandler {
94 String url = getUrl();
95 logger.trace("({}) sending encrypted request to '{}' ", uid, url);
96 logger.trace("({}) unencrypted request: '{}'", uid, tapoRequest);
98 Request httpRequest = httpDelegator.getHttpClient().newRequest(url).method(HttpMethod.POST.toString());
101 httpRequest = setHeaders(httpRequest);
102 httpRequest.timeout(TAPO_HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
104 /* add request body */
105 httpRequest.content(new StringContentProvider(tapoRequest.toString(), CONTENT_CHARSET), CONTENT_TYPE_JSON);
108 responseReceived(httpRequest.send(), tapoRequest.method());
109 } catch (TapoErrorHandler tapoError) {
110 logger.debug("({}) sendRequest exception'{}'", uid, tapoError.toString());
112 } catch (TimeoutException e) {
113 logger.debug("({}) sendRequest timeout'{}'", uid, e.getMessage());
114 throw new TapoErrorHandler(ERR_BINDING_CONNECT_TIMEOUT, getValueOrDefault(e.getMessage(), ""));
115 } catch (InterruptedException e) {
116 logger.debug("({}) sendRequest interrupted'{}'", uid, e.getMessage());
117 throw new TapoErrorHandler(ERR_BINDING_SEND_REQUEST, getValueOrDefault(e.getMessage(), ""));
118 } catch (ExecutionException e) {
119 logger.debug("({}) sendRequest exception'{}'", uid, e.getMessage());
120 throw new TapoErrorHandler(ERR_BINDING_SEND_REQUEST, getValueOrDefault(e.getMessage(), ""));
125 * send asynchronous request - response will be handled in [asyncResponseReceived()] function
128 public void sendAsyncRequest(TapoBaseRequestInterface tapoRequest) throws TapoErrorHandler {
129 String url = getUrl();
130 String command = tapoRequest.method();
131 logger.trace("({}) sendAsncRequest to '{}'", uid, url);
132 logger.trace("({}) command/payload: '{}''{}'", uid, command, tapoRequest.params());
134 Request httpRequest = httpDelegator.getHttpClient().newRequest(url).method(HttpMethod.POST.toString());
137 httpRequest = setHeaders(httpRequest);
139 /* add request body */
140 httpRequest.content(new StringContentProvider(tapoRequest.toString(), CONTENT_CHARSET), CONTENT_TYPE_JSON);
142 httpRequest.timeout(TAPO_HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(new BufferingResponseListener() {
143 @NonNullByDefault({})
145 public void onComplete(Result result) {
146 final HttpResponse response = (HttpResponse) result.getResponse();
147 if (result.getFailure() != null) {
148 /* handle result errors */
149 Throwable e = result.getFailure();
150 String errorMessage = getValueOrDefault(e.getMessage(), "");
151 if (e instanceof TimeoutException) {
152 logger.debug("({}) sendAsyncRequest timeout'{}'", uid, errorMessage);
153 httpDelegator.handleError(new TapoErrorHandler(ERR_BINDING_CONNECT_TIMEOUT, errorMessage));
155 logger.debug("({}) sendAsyncRequest failed'{}'", uid, errorMessage);
156 httpDelegator.handleError(new TapoErrorHandler(new Exception(e), errorMessage));
158 } else if (response.getStatus() != 200) {
159 logger.debug("({}) sendAsyncRequest response error'{}'", uid, response.getStatus());
160 httpDelegator.handleError(new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, getContentAsString()));
162 /* request successful */
163 String rBody = getContentAsString();
165 asyncResponseReceived(rBody, command);
166 } catch (TapoErrorHandler tapoError) {
167 httpDelegator.handleError(tapoError);
174 /************************
176 ************************/
179 * handle synchronous request-response - pushes TapoResponse to [httpDelegator.handleResponse()]-function
182 public void responseReceived(ContentResponse response, String command) throws TapoErrorHandler {
183 logger.trace("({}) recived response: {}", uid, response.getContentAsString());
184 TapoResponse tapoResponse = getTapoResponse(response);
185 if (!tapoResponse.hasError()) {
188 httpDelegator.handleResponse(tapoResponse, command);
189 httpDelegator.responsePasstrough(response.getContentAsString(), command);
192 logger.debug("({}) response returned error: {} ({})", uid, tapoResponse.message(),
193 tapoResponse.errorCode());
194 httpDelegator.handleError(new TapoErrorHandler(tapoResponse.errorCode()));
199 * handle asynchronous request-response - pushes TapoResponse to [httpDelegator.handleResponse()]-function
202 public void asyncResponseReceived(String responseBody, String command) throws TapoErrorHandler {
203 logger.trace("({}) asyncResponseReceived '{}'", uid, responseBody);
204 TapoResponse tapoResponse = getTapoResponse(responseBody);
205 if (!tapoResponse.hasError()) {
206 httpDelegator.handleResponse(tapoResponse, command);
208 httpDelegator.handleError(new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, command));
212 /************************
216 ************************/
219 * Get Tapo-Response from Contentresponse
221 private TapoResponse getTapoResponse(ContentResponse response) throws TapoErrorHandler {
222 if (response.getStatus() == 200) {
223 return getTapoResponse(response.getContentAsString());
225 logger.debug("({}) invalid response received", uid);
226 throw new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, "invalid response receicved");
231 * Get Tapo-Response from responsestring
233 private TapoResponse getTapoResponse(String responseString) throws TapoErrorHandler {
234 if (isValidJson(responseString)) {
235 return Objects.requireNonNull(GSON.fromJson(responseString, TapoResponse.class));
237 logger.debug("({}) invalid response received", uid);
238 throw new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, "invalid response receicved");
243 * Get Url requests are sent to
245 public String getUrl() {
246 return httpDelegator.getBaseUrl();
252 public Request setHeaders(Request httpRequest) {
253 httpRequest.header("content-type", CONTENT_TYPE_JSON);
254 httpRequest.header("Accept", CONTENT_TYPE_JSON);