2 * Copyright (c) 2010-2022 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.TapoErrorConstants.*;
17 import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*;
19 import java.net.InetAddress;
20 import java.util.HashMap;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler;
24 import org.openhab.binding.tapocontrol.internal.device.TapoDevice;
25 import org.openhab.binding.tapocontrol.internal.helpers.PayloadBuilder;
26 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
27 import org.openhab.binding.tapocontrol.internal.structures.TapoDeviceInfo;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import com.google.gson.Gson;
32 import com.google.gson.JsonObject;
35 * Handler class for TAPO Smart Home device connections.
36 * This class uses asynchronous HttpClient-Requests
38 * @author Christian Wild - Initial contribution
41 public class TapoDeviceConnector extends TapoDeviceHttpApi {
42 private final Logger logger = LoggerFactory.getLogger(TapoDeviceConnector.class);
43 private final String uid;
44 private final TapoDevice device;
45 private TapoDeviceInfo deviceInfo;
47 private long lastQuery = 0L;
48 private long lastSent = 0L;
49 private long lastLogin = 0L;
54 * @param config TapoControlConfiguration class
56 public TapoDeviceConnector(TapoDevice device, TapoBridgeHandler bridgeThingHandler) {
57 super(device, bridgeThingHandler);
59 this.gson = new Gson();
60 this.deviceInfo = new TapoDeviceInfo();
61 this.uid = device.getThingUID().getAsString();
64 /***********************************
68 ************************************/
72 * @return true if success
74 public boolean login() {
75 if (this.pingDevice()) {
76 logger.trace("({}) sending login to url '{}'", uid, deviceURL);
78 long now = System.currentTimeMillis();
79 if (now > this.lastLogin + TAPO_LOGIN_MIN_GAP_MS) {
84 /* create ssl-handschake (cookie) */
85 String cookie = createHandshake();
86 if (!cookie.isBlank()) {
88 String token = queryToken();
92 logger.trace("({}) not done cause of min_gap '{}'", uid, TAPO_LOGIN_MIN_GAP_MS);
94 return this.loggedIn();
96 logger.debug("({}) no ping while login '{}'", uid, this.ipAddress);
97 handleError(new TapoErrorHandler(ERR_DEVICE_OFFLINE, "no ping while login"));
102 /***********************************
106 ************************************/
109 * send custom command to device
111 * @param plBuilder Payloadbuilder with unencrypted payload
113 public void sendCustomQuery(String queryMethod) {
115 PayloadBuilder plBuilder = new PayloadBuilder();
116 plBuilder.method = queryMethod;
117 sendCustomPayload(plBuilder);
121 * send custom command to device
123 * @param plBuilder Payloadbuilder with unencrypted payload
125 public void sendCustomPayload(PayloadBuilder plBuilder) {
126 long now = System.currentTimeMillis();
127 if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) {
128 String payload = plBuilder.getPayload();
129 sendSecurePasstrhroug(payload, DEVICE_CMD_CUSTOM);
131 logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastSent);
136 * send "set_device_info" command to device
138 * @param name Name of command to send
139 * @param value Value to send to control
141 public void sendDeviceCommand(String name, Object value) {
142 long now = System.currentTimeMillis();
143 if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) {
147 PayloadBuilder plBuilder = new PayloadBuilder();
148 plBuilder.method = DEVICE_CMD_SETINFO;
149 plBuilder.addParameter(name, value);
150 String payload = plBuilder.getPayload();
152 sendSecurePasstrhroug(payload, DEVICE_CMD_SETINFO);
154 logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastSent);
159 * send multiple "set_device_info" commands to device
161 * @param map HashMap<String, Object> (name, value of parameter)
163 public void sendDeviceCommands(HashMap<String, Object> map) {
164 long now = System.currentTimeMillis();
165 if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) {
169 PayloadBuilder plBuilder = new PayloadBuilder();
170 plBuilder.method = DEVICE_CMD_SETINFO;
171 for (HashMap.Entry<String, Object> entry : map.entrySet()) {
172 plBuilder.addParameter(entry.getKey(), entry.getValue());
174 String payload = plBuilder.getPayload();
176 sendSecurePasstrhroug(payload, DEVICE_CMD_SETINFO);
178 logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastSent);
183 * Query Info from Device adn refresh deviceInfo
185 public void queryInfo() {
190 * Query Info from Device adn refresh deviceInfo
192 * @param ignoreGap ignore gap to last query. query anyway
194 public void queryInfo(boolean ignoreGap) {
195 logger.trace("({}) DeviceConnetor_queryInfo from '{}'", uid, deviceURL);
196 long now = System.currentTimeMillis();
197 if (ignoreGap || now > this.lastQuery + TAPO_SEND_MIN_GAP_MS) {
198 this.lastQuery = now;
201 PayloadBuilder plBuilder = new PayloadBuilder();
202 plBuilder.method = DEVICE_CMD_GETINFO;
203 String payload = plBuilder.getPayload();
205 sendSecurePasstrhroug(payload, DEVICE_CMD_GETINFO);
207 logger.debug("({}) command not sent becauso of min_gap: {}", uid, now + " <- " + lastQuery);
212 * SEND SECUREPASSTHROUGH
213 * encprypt payload and send to device
215 * @param payload payload sent to device
216 * @param command command executed - this will handle result
218 protected void sendSecurePasstrhroug(String payload, String command) {
219 /* encrypt payload */
220 String encryptedPayload = encryptPayload(payload);
222 /* create secured payload */
223 PayloadBuilder plBuilder = new PayloadBuilder();
224 plBuilder.method = "securePassthrough";
225 plBuilder.addParameter("request", encryptedPayload);
226 String securePassthroughPayload = plBuilder.getPayload();
228 sendAsyncRequest(deviceURL, securePassthroughPayload, command);
231 /***********************************
235 ************************************/
238 * Handle SuccessResponse (setDeviceInfo)
240 * @param responseBody String with responseBody from device
243 protected void handleSuccessResponse(String responseBody) {
244 JsonObject jsnResult = getJsonFromResponse(responseBody);
245 Integer errorCode = jsonObjectToInt(jsnResult, "error_code", ERR_JSON_DECODE_FAIL);
246 if (errorCode != 0) {
247 logger.debug("({}) set deviceInfo not succesfull: {}", uid, jsnResult);
248 this.device.handleConnectionState();
250 this.device.responsePasstrough(responseBody);
254 * handle JsonResponse (getDeviceInfo)
256 * @param responseBody String with responseBody from device
259 protected void handleDeviceResult(String responseBody) {
260 JsonObject jsnResult = getJsonFromResponse(responseBody);
261 if (jsnResult.has("device_id")) {
262 this.deviceInfo = new TapoDeviceInfo(jsnResult);
263 this.device.setDeviceInfo(deviceInfo);
265 this.deviceInfo = new TapoDeviceInfo();
266 this.device.handleConnectionState();
268 this.device.responsePasstrough(responseBody);
272 * handle custom response
274 * @param responseBody String with responseBody from device
277 protected void handleCustomResponse(String responseBody) {
278 this.device.responsePasstrough(responseBody);
284 * @param te TapoErrorHandler
287 protected void handleError(TapoErrorHandler tapoError) {
288 this.device.setError(tapoError);
292 * get Json from response
294 * @param responseBody
295 * @return JsonObject with result
297 private JsonObject getJsonFromResponse(String responseBody) {
298 JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class);
299 /* get errocode (0=success) */
300 if (jsonObject != null) {
301 Integer errorCode = jsonObjectToInt(jsonObject, "error_code");
302 if (errorCode == 0) {
303 /* decrypt response */
304 jsonObject = gson.fromJson(responseBody, JsonObject.class);
305 logger.trace("({}) received result: {}", uid, responseBody);
306 if (jsonObject != null) {
307 /* return result if set / else request was successfull */
308 if (jsonObject.has("result")) {
309 return jsonObject.getAsJsonObject("result");
315 /* return errorcode from device */
316 TapoErrorHandler te = new TapoErrorHandler(errorCode, "device answers with errorcode");
317 logger.debug("({}) device answers with errorcode {} - {}", uid, errorCode, te.getMessage());
322 logger.debug("({}) sendPayload exception {}", uid, responseBody);
323 handleError(new TapoErrorHandler(ERR_HTTP_RESPONSE));
324 return new JsonObject();
327 /***********************************
331 ************************************/
334 * Check if device is online
336 * @return true if device is online
338 public Boolean isOnline() {
339 return isOnline(false);
343 * Check if device is online
345 * @param raiseError if true
346 * @return true if device is online
348 public Boolean isOnline(Boolean raiseError) {
352 logger.trace("({}) device is offline (no ping)", uid);
354 handleError(new TapoErrorHandler(ERR_DEVICE_OFFLINE));
364 * @return String ipAdress
366 public String getIP() {
367 return this.ipAddress;
373 * @return true if ping successfull
375 public Boolean pingDevice() {
377 InetAddress address = InetAddress.getByName(this.ipAddress);
378 return address.isReachable(TAPO_PING_TIMEOUT_MS);
379 } catch (Exception e) {
380 logger.debug("({}) InetAdress throws: {}", uid, e.getMessage());