]> git.basschouten.com Git - openhab-addons.git/blob
205e348640a8b6c7b05d1f516a4906f3a0e25be6
[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.protocol.passthrough;
14
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.*;
20
21 import java.util.Objects;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
25
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;
43
44 /**
45  * Handler class for TAPO-PASSTHROUGH-Protocol
46  *
47  * @author Christian Wild - Initial contribution
48  */
49 @NonNullByDefault
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;
54
55     /***********************
56      * Init Class
57      **********************/
58
59     public PassthroughProtocol(TapoConnectorInterface httpDelegator) {
60         this.httpDelegator = httpDelegator;
61         uid = httpDelegator.getThingUID() + " / HTTP-Passtrhough";
62     }
63
64     /***********************
65      * Login Handling
66      **********************/
67
68     @Override
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");
72     }
73
74     @Override
75     public void logout() {
76         logger.trace("({}) logout not implemented", uid);
77     }
78
79     @Override
80     public boolean isLoggedIn() {
81         logger.debug("({}) isLoggedIn not implemented", uid);
82         return false;
83     }
84
85     /***********************
86      * Request Sender
87      **********************/
88
89     /*
90      * send synchronous request - response will be handled in [responseReceived()] function
91      */
92     @Override
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);
97
98         Request httpRequest = httpDelegator.getHttpClient().newRequest(url).method(HttpMethod.POST);
99
100         /* set header */
101         httpRequest = setHeaders(httpRequest);
102         httpRequest.timeout(TAPO_HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
103
104         /* add request body */
105         httpRequest.content(new StringContentProvider(tapoRequest.toString(), CONTENT_CHARSET), CONTENT_TYPE_JSON);
106
107         try {
108             responseReceived(httpRequest.send(), tapoRequest.method());
109         } catch (TapoErrorHandler tapoError) {
110             logger.debug("({}) sendRequest exception'{}'", uid, tapoError.toString());
111             throw tapoError;
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(), ""));
121         }
122     }
123
124     /*
125      * send asynchronous request - response will be handled in [asyncResponseReceived()] function
126      */
127     @Override
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());
133
134         Request httpRequest = httpDelegator.getHttpClient().newRequest(url).method(HttpMethod.POST);
135
136         /* set header */
137         httpRequest = setHeaders(httpRequest);
138
139         /* add request body */
140         httpRequest.content(new StringContentProvider(tapoRequest.toString(), CONTENT_CHARSET), CONTENT_TYPE_JSON);
141
142         httpRequest.timeout(TAPO_HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(new BufferingResponseListener() {
143             @NonNullByDefault({})
144             @Override
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));
154                     } else {
155                         logger.debug("({}) sendAsyncRequest failed'{}'", uid, errorMessage);
156                         httpDelegator.handleError(new TapoErrorHandler(new Exception(e), errorMessage));
157                     }
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()));
161                 } else {
162                     /* request successful */
163                     String rBody = getContentAsString();
164                     try {
165                         asyncResponseReceived(rBody, command);
166                     } catch (TapoErrorHandler tapoError) {
167                         httpDelegator.handleError(tapoError);
168                     }
169                 }
170             }
171         });
172     }
173
174     /************************
175      * RESPONSE HANDLERS
176      ************************/
177
178     /**
179      * handle synchronous request-response - pushes TapoResponse to [httpDelegator.handleResponse()]-function
180      */
181     @Override
182     public void responseReceived(ContentResponse response, String command) throws TapoErrorHandler {
183         logger.trace("({}) received response: {}", uid, response.getContentAsString());
184         TapoResponse tapoResponse = getTapoResponse(response);
185         if (!tapoResponse.hasError()) {
186             switch (command) {
187                 default:
188                     httpDelegator.handleResponse(tapoResponse, command);
189                     httpDelegator.responsePasstrough(response.getContentAsString(), command);
190             }
191         } else {
192             logger.debug("({}) response returned error: {} ({})", uid, tapoResponse.message(),
193                     tapoResponse.errorCode());
194             httpDelegator.handleError(new TapoErrorHandler(tapoResponse.errorCode()));
195         }
196     }
197
198     /**
199      * handle asynchronous request-response - pushes TapoResponse to [httpDelegator.handleResponse()]-function
200      */
201     @Override
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);
207         } else {
208             httpDelegator.handleError(new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, command));
209         }
210     }
211
212     /************************
213      * 
214      * PRIVATE HELPERS
215      * 
216      ************************/
217
218     /**
219      * Get Tapo-Response from Contentresponse
220      */
221     private TapoResponse getTapoResponse(ContentResponse response) throws TapoErrorHandler {
222         if (response.getStatus() == 200) {
223             return getTapoResponse(response.getContentAsString());
224         } else {
225             String reason = response.getStatus() + " " + response.getReason();
226             logger.debug("({}) invalid response received - {}", uid, reason);
227             throw new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, reason);
228         }
229     }
230
231     /**
232      * Get Tapo-Response from responsestring
233      */
234     private TapoResponse getTapoResponse(String responseString) throws TapoErrorHandler {
235         if (isValidJson(responseString)) {
236             return Objects.requireNonNull(GSON.fromJson(responseString, TapoResponse.class));
237         } else {
238             logger.debug("({}) invalid response received", uid);
239             throw new TapoErrorHandler(ERR_BINDING_HTTP_RESPONSE, "invalid response receicved");
240         }
241     }
242
243     /**
244      * Get Url requests are sent to
245      */
246     public String getUrl() {
247         return httpDelegator.getBaseUrl();
248     }
249
250     /*
251      * Set HTTP-Headers
252      */
253     public Request setHeaders(Request httpRequest) {
254         httpRequest.header("Accept", CONTENT_TYPE_JSON);
255         return httpRequest;
256     }
257 }