From 62b2d8ce848b1cd1bbffd065b94913d43e9ae303 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Sun, 24 Dec 2023 10:50:33 +0300 Subject: [PATCH] [TapoCamera] added reconnect when encryption key is incorrect, added thing label for logging Signed-off-by: Dmitry P. (d51x) --- .../internal/TapoCameraHandler.java | 20 ++- .../internal/TapoCameraHandlerFactory.java | 2 +- .../internal/api/TapoCameraApiFactory.java | 4 +- .../internal/api/TapoCameraApiImpl.java | 121 +++++++++--------- 4 files changed, 78 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandler.java b/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandler.java index 6df98e0..95fd2a1 100644 --- a/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandler.java +++ b/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandler.java @@ -293,7 +293,7 @@ private void getSupportedFeatures(ModuleSpec moduleSpec) { } private Future connectCamera(int wait) { - logger.warn("Try connect after: {} sec", wait); + logger.warn("{}: Try connect after: {} sec", thing.getLabel(), wait); return scheduler.schedule(() -> { updateStatus(ThingStatus.OFFLINE); boolean thingReachable = deviceAuth(); @@ -319,10 +319,14 @@ private Future connectCamera(int wait) { } private void reconnect() { - logger.debug("Try to reconnect"); + reconnect(""); + } + + private void reconnect(@Nullable String message) { + logger.debug("{}: Try to reconnect", thing.getLabel()); cancelInitJob(); cancelPollingJob(); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); initJob = connectCamera(config.reconnectInterval); } @@ -332,7 +336,7 @@ private Boolean deviceAuth() { return api.auth(config.username, pass); } catch (ApiException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - reconnect(); + reconnect(e.getMessage()); throw new RuntimeException(e); } } @@ -368,7 +372,7 @@ private Future getCameraParameters(Integer interval) { } private void pollingCamera() { - logger.debug("get camera parameters"); + logger.debug("{}: get camera parameters", thing.getLabel()); try { if (!api.isAuth()) { reconnect(); @@ -381,7 +385,9 @@ private void pollingCamera() { } } catch (Exception e) { - logger.error("Tapo Camera Exception: {}", e.getMessage()); + logger.error("{}: Tapo Camera Exception: {}", thing.getLabel(), e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + reconnect(); } } @@ -560,7 +566,7 @@ else if (data instanceof LastAlarmInfo) { public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription stateDescription, @Nullable Locale locale) { if (this.getThing().getChannel(CHANNEL_GOTO_PRESETS.getName()).equals(channel)) { - logger.debug("requested state for {}", channel.getUID()); + logger.debug("{}: requested state for {}", thing.getLabel(), channel.getUID()); } return null; } diff --git a/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandlerFactory.java b/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandlerFactory.java index c83e57f..1064297 100644 --- a/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandlerFactory.java +++ b/src/main/java/org/openhab/binding/tapocamera/internal/TapoCameraHandlerFactory.java @@ -68,7 +68,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - TapoCameraApi api = apiFactory.getApi(); + TapoCameraApi api = apiFactory.getApi(thing.getLabel()); if (THING_TYPE_CAMERA.equals(thingTypeUID)) { return new TapoCameraHandler(thing, api); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { diff --git a/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiFactory.java b/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiFactory.java index 010a1a5..91a5f52 100644 --- a/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiFactory.java +++ b/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiFactory.java @@ -77,8 +77,8 @@ public TapoCameraApiFactory() { * * @return the api */ - public TapoCameraApi getApi() { - return new TapoCameraApiImpl(httpClient); + public TapoCameraApi getApi(String tag) { + return new TapoCameraApiImpl(httpClient, tag); } public TapoCameraCloudApi getCloudApi() { diff --git a/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiImpl.java b/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiImpl.java index 5484537..dbc7979 100644 --- a/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiImpl.java +++ b/src/main/java/org/openhab/binding/tapocamera/internal/api/TapoCameraApiImpl.java @@ -73,14 +73,16 @@ public class TapoCameraApiImpl implements TapoCameraApi { private String cnonce = "C167F0A5"; private static Gson gson = new Gson(); private final HttpClient httpClient; + private String tag = ""; /** * Instantiates a new Tapo camera api. * * @param httpClient the http client */ - public TapoCameraApiImpl(HttpClient httpClient) { + public TapoCameraApiImpl(HttpClient httpClient, String tag) { this.httpClient = httpClient; + this.tag = tag; } @Override @@ -122,24 +124,24 @@ private Boolean getIsSecureConnection(String username) throws ApiException { ContentResponse contentResponse = request.send(); JsonElement json = JsonParser.parseString(contentResponse.getContentAsString()); ApiResponse response = gson.fromJson(json, ApiResponse.class); - logger.info("response: {}", response); + logger.info("{}: response: {}", tag, response); if (response.errorCode == ApiErrorCodes.ERROR_40413.getCode()) { JsonObject data = (JsonObject) response.result.get("data"); if (data.has("encrypt_type")) { List list = new ArrayList<>(); - logger.info("data: {}", data); + logger.info("{}: data: {}", tag, data); return true; } } } catch (TimeoutException e) { - logger.error("TimeoutException: {}", e.getMessage()); + logger.error("{}: TimeoutException: {}", tag, e.getMessage()); throw new ApiException(String.format("TimeoutException: %s", e.getMessage())); } catch (InterruptedException e) { - logger.error("InterruptedException: {}", e.getMessage()); + logger.error("{}: InterruptedException: {}", tag, e.getMessage()); throw new ApiException(String.format("InterruptedException: %s", e.getMessage())); } catch (ExecutionException e) { - logger.error("ExecutionException: {}", e.getMessage()); + logger.error("{}: ExecutionException: {}", tag, e.getMessage()); throw new ApiException(String.format("ExecutionException: %s", e.getMessage())); } return false; @@ -157,7 +159,7 @@ public Boolean auth(String username, String password) throws ApiException { isSecureConnection = getIsSecureConnection(username); Request request = httpClient.newRequest(hostname); - logger.info("hostname: {}", hostname); + logger.trace("{}: hostname: {}", tag, hostname); setHeaders(request); request.method(HttpMethod.POST); @@ -166,7 +168,7 @@ public Boolean auth(String username, String password) throws ApiException { JsonObject jsonParams = new JsonObject(); if (isSecureConnection) { - logger.info("Connection is secure"); + logger.debug("{}: Connection is secure", tag); // SecureRandom csprng = new SecureRandom(); // byte[] randomBytes = new byte[8]; // csprng.nextBytes(randomBytes); @@ -180,7 +182,7 @@ public Boolean auth(String username, String password) throws ApiException { // jsonParams.addProperty("digest_passwd", digestPassword2); jsonParams.addProperty("username", username); } else { - logger.info("Connection is insecure"); + logger.debug("{}: Connection is insecure", tag); passwordHash = ApiUtils.getPasswordHash(password); jsonParams.addProperty("hashed", Boolean.TRUE); jsonParams.addProperty("password", passwordHash); @@ -192,38 +194,38 @@ public Boolean auth(String username, String password) throws ApiException { jsonAuth.add("params", jsonParams); String body = jsonAuth.toString(); - logger.info("body: {}", body); + logger.trace("{}: body: {}", tag, body); request.content(new StringContentProvider(body)); try { ContentResponse contentResponse = request.send(); JsonElement json = JsonParser.parseString(contentResponse.getContentAsString()); ApiResponse response = gson.fromJson(json, ApiResponse.class); - logger.info("response: {}", response); + logger.trace("{}: response: {}", tag, response); if (isSecureConnection) { // get nonce and make new request - logger.info("Processing secure response"); + logger.debug("{}: Processing secure response", tag); if (response.result.has("data")) { JsonObject data = response.result.getAsJsonObject("data"); if (data.has("nonce") && data.has("device_confirm")) { nonce = data.get("nonce").getAsString(); - logger.info("received nonce: {}", nonce); + logger.trace("{}: received nonce: {}", tag, nonce); String deviceConfirm = data.get("device_confirm").getAsString(); Boolean validated = validateDeviceConfirm(passwordHash, nonce, deviceConfirm); if (validated) { - logger.info("sha256: {}", passwordHash); - logger.info("cnonce: {}", cnonce); - logger.info("nonce: {}", nonce); + logger.trace("{}: sha256: {}", tag, passwordHash); + logger.trace("{}: cnonce: {}", tag, cnonce); + logger.trace("{}: nonce: {}", tag, nonce); String toNewPass = passwordHash + cnonce + nonce; - logger.info("sha256 + cnonce + nonce: {}", toNewPass); + logger.trace("{}: sha256 + cnonce + nonce: {}", tag, toNewPass); String digestPassword = ApiUtils.getPasswordHashSHA256(toNewPass); - logger.info("sha256-2: {}", digestPassword); + logger.trace("{}: sha256-2: {}", tag, digestPassword); String digestPassword2 = digestPassword + cnonce + nonce; - logger.info("sha256-2 + cnonce + nonce: {}", digestPassword2); + logger.trace("{}: sha256-2 + cnonce + nonce: {}", tag, digestPassword2); // jsonParams.addProperty("cnonce", ApiUtils.bytesToHex(randomBytes)); jsonParams.addProperty("cnonce", cnonce); @@ -236,7 +238,7 @@ public Boolean auth(String username, String password) throws ApiException { jsonAuth.add("params", jsonParams); body = jsonAuth.toString(); - logger.info("body: {}", body); + logger.debug("{}: body: {}", tag, body); Request request2 = httpClient.newRequest(hostname); setHeaders(request2); request2.method(HttpMethod.POST); @@ -246,42 +248,42 @@ public Boolean auth(String username, String password) throws ApiException { JsonElement json2 = JsonParser.parseString(contentResponse2.getContentAsString()); ApiResponse response2 = gson.fromJson(json2, ApiResponse.class); - logger.info("response2: {}", response2); + logger.debug("{}: response2: {}", tag, response2); // response: ApiResponse{errorCode=0, // result={"stok":"4b67dc6f7035104ff9922edef174164f","user_group":"root","start_seq":535}} if (response2.errorCode == 0 && response2.result.has("stok") && response2.result.has("start_seq")) { token = response2.result.get("stok").getAsString(); - logger.info("token: {}", token); + logger.debug("{}: token: {}", tag, token); startSeq = response2.result.get("start_seq").getAsLong(); - logger.info("start_seq: {}", startSeq); + logger.debug("{}: start_seq: {}", tag, startSeq); lsk = generateEncryptionToken("lsk", passwordHash, nonce); - logger.info("lsk: {}", lsk); + logger.debug("{}: lsk: {}", tag, lsk); ivb = generateEncryptionToken("ivb", passwordHash, nonce); - logger.info("ivb: {}", ivb); + logger.debug("{}: ivb: {}", tag, ivb); result = true; } } catch (TimeoutException e) { - logger.error("TimeoutException: {}", e.getMessage()); + logger.error("{}: TimeoutException: {}", tag, e.getMessage()); this.token = ""; throw new ApiException(String.format("TimeoutException: %s", e.getMessage())); } catch (InterruptedException e) { - logger.error("InterruptedException: {}", e.getMessage()); + logger.error("{}: InterruptedException: {}", tag, e.getMessage()); throw new ApiException(String.format("InterruptedException: %s", e.getMessage())); } catch (ExecutionException e) { - logger.error("ExecutionException: {}", e.getMessage()); + logger.error("{}: ExecutionException: {}", tag, e.getMessage()); this.token = ""; throw new ApiException(String.format("ExecutionException: %s", e.getMessage())); } } } else { - logger.error("Empty nonce or device_confirm, error code: {}", response.errorCode); + logger.error("{}: Empty nonce or device_confirm, error code: {}", tag, response.errorCode); this.token = ""; throw new ApiException(String.format("error code: %d", response.errorCode)); } } else { - logger.error("Invalid token, error code: {}", response.errorCode); + logger.error("{}: Invalid token, error code: {}", tag, response.errorCode); this.token = ""; throw new ApiException(String.format("error code: %d", response.errorCode)); } @@ -295,24 +297,24 @@ public Boolean auth(String username, String password) throws ApiException { } else { // TODO: log.error && throw new Exception if (response.errorCode == ApiErrorCodes.ERROR_40401.getCode()) { - logger.error("Invalid token, error code: {}", response.errorCode); + logger.error("{}: Invalid token, error code: {}", tag, response.errorCode); this.token = ""; } else { - logger.error("Error in response, error code: {}", response.errorCode); + logger.error("{}: Error in response, error code: {}", tag, response.errorCode); throw new ApiException(String.format("error code: %d", response.errorCode)); } } } } catch (TimeoutException e) { - logger.error("TimeoutException: {}", e.getMessage()); + logger.error("{}: TimeoutException: {}", tag, e.getMessage()); this.token = ""; throw new ApiException(String.format("TimeoutException: %s", e.getMessage())); } catch (InterruptedException e) { - logger.error("InterruptedException: {}", e.getMessage()); + logger.error("{}: InterruptedException: {}", tag, e.getMessage()); throw new ApiException(String.format("InterruptedException: %s", e.getMessage())); } catch (ExecutionException e) { - logger.error("ExecutionException: {}", e.getMessage()); + logger.error("{}: ExecutionException: {}", tag, e.getMessage()); this.token = ""; throw new ApiException(String.format("ExecutionException: %s", e.getMessage())); } @@ -339,7 +341,7 @@ private Integer getUserId() { result = multi.get(0).result.get("user_id").getAsInt(); } } else { - logger.error("TapoCameraApi error {}", response.errorCode); + logger.error("{}: TapoCameraApi error {}", tag, response.errorCode); } return result; } @@ -347,21 +349,21 @@ private Integer getUserId() { private void makeEncryptedRequest(Request originRequest, String data, Long seq, String passwordHash, String lsk, String ivb) { String encrypted = ApiUtils.encryptRequest(data, lsk, ivb); - logger.trace("encrypted data: {}", encrypted); + logger.trace("{}: encrypted data: {}", tag, encrypted); JsonObject jsonParams = new JsonObject(); jsonParams.addProperty("request", encrypted); JsonObject encryptedRequest = new JsonObject(); encryptedRequest.addProperty("method", "securePassthrough"); encryptedRequest.add("params", jsonParams); - logger.debug("encrypted request: {}", encryptedRequest); + logger.trace("{}: encrypted request: {}", tag, encryptedRequest); originRequest.content(new StringContentProvider(encryptedRequest.toString())); originRequest.header("Seq", seq.toString()); - String tag = generateTag(encryptedRequest.toString(), passwordHash, cnonce); - logger.debug("Tapo tag: {}", tag); + String tapoTag = generateTag(encryptedRequest.toString(), passwordHash, cnonce); + logger.debug("{}: Tapo tag: {}", tag, tapoTag); - originRequest.header("Tapo_tag", tag); + originRequest.header("Tapo_tag", tapoTag); startSeq += 1; } @@ -372,7 +374,7 @@ public ApiResponse sendMultipleRequest(String token, String data) { setHeaders(request); request.method(HttpMethod.POST); if (isSecureConnection) { - logger.debug("sendMultipleRequest data: {}", data); + logger.debug("{}: sendMultipleRequest data: {}", tag, data); makeEncryptedRequest(request, data, startSeq, passwordHash, lsk, ivb); } else { request.content(new StringContentProvider(data)); @@ -384,27 +386,27 @@ public ApiResponse sendMultipleRequest(String token, String data) { if (isSecureConnection) { String result = response.result.get("response").getAsString(); result = ApiUtils.decryptResponse(result, lsk, ivb); - logger.debug("decrypted response: {}", result); + logger.debug("{}: decrypted response: {}", tag, result); JsonObject json = JsonParser.parseString(result).getAsJsonObject(); response.result = json.getAsJsonObject("result"); } return response; } else { // TODO: log.error && throw new Exception - logger.error("Error in response, error code: {}", response.errorCode); + logger.error("{}: Error in response, error code: {}", tag, response.errorCode); this.token = ""; return new ApiResponse(); } } catch (TimeoutException e) { this.token = ""; - logger.error("TimeoutException: {}", e.getMessage()); + logger.error("{}: TimeoutException: {}", tag, e.getMessage()); return null; } catch (InterruptedException e) { - logger.error("InterruptedException: {}", e.getMessage()); + logger.error("{}: InterruptedException: {}", tag, e.getMessage()); return null; } catch (ExecutionException e) { this.token = ""; - logger.error("ExecutionException: {}", e.getMessage()); + logger.error("{}: ExecutionException: {}", tag, e.getMessage()); return null; } } @@ -415,7 +417,7 @@ public Object sendSingleRequest(String token, String data) { setHeaders(request); request.method(HttpMethod.POST); if (isSecureConnection) { - logger.debug("sendSingleRequest data: {}", data); + logger.debug("{}: sendSingleRequest data: {}", tag, data); makeEncryptedRequest(request, data, startSeq, passwordHash, lsk, ivb); } else { request.content(new StringContentProvider(data)); @@ -428,12 +430,13 @@ public Object sendSingleRequest(String token, String data) { if (isSecureConnection) { String result = response.get("result").getAsJsonObject().get("response").getAsString(); result = ApiUtils.decryptResponse(result, lsk, ivb); - logger.debug("decrypted response: {}", result); + logger.debug("{}: decrypted response: {}", tag, result); JsonObject json = JsonParser.parseString(result).getAsJsonObject(); if (json.has("error_code") && json.get("error_code").getAsInt() == 0) { response = json; } else { - logger.error("Error in response, error code: {}, {}", json.get("error_code").getAsInt(), + logger.error("{}: Error in response, error code: {}, {}", tag, + json.get("error_code").getAsInt(), ApiErrorCodes.getErrorByCode(json.get("error_code").getAsInt()).getMessage()); response.addProperty("error_code", json.get("error_code").getAsInt()); } @@ -441,19 +444,19 @@ public Object sendSingleRequest(String token, String data) { return response; } else { // TODO: log.error && throw new Exception - logger.error("Error in response, error code: {}", response.get("error_code").getAsInt()); + logger.error("{}: Error in response, error code: {}", tag, response.get("error_code").getAsInt()); this.token = ""; return new Object(); } } catch (TimeoutException e) { - logger.error("TimeoutException: {}", e.getMessage()); + logger.error("{}: TimeoutException: {}", tag, e.getMessage()); this.token = ""; return null; } catch (InterruptedException e) { - logger.error("InterruptedException: {}", e.getMessage()); + logger.error("{}: InterruptedException: {}", tag, e.getMessage()); return null; } catch (ExecutionException e) { - logger.error("ExecutionException: {}", e.getMessage()); + logger.error("{}: ExecutionException: {}", tag, e.getMessage()); this.token = ""; return null; } @@ -493,7 +496,7 @@ public Object processSingleResponse(Object data, String moduleName, String secti return gson.fromJson(obj, getClassByModuleAndSection(moduleName, section)); } } else { - logger.error("Module {} not found in response", moduleName); + logger.error("{}: Module {} not found in response", tag, moduleName); } return null; } @@ -511,7 +514,7 @@ public List processSingleResponse(Object data, String moduleName, List processMultipleResponses(ApiResponse response) { }); } } else { - logger.error("TapoCameraApi error {}", response.errorCode); + logger.error("{}: TapoCameraApi error {}", tag, response.errorCode); } return results; } @@ -932,10 +935,10 @@ public List getChangebleParameters() { return processMultipleResponses(response); } else if (response.errorCode == ApiErrorCodes.ERROR_40401.getCode()) { // TODO: invalid token - logger.error("Invalid token, error code: {}", response.errorCode); + logger.error("{}: Invalid token, error code: {}", tag, response.errorCode); this.token = ""; } else { - logger.error("Error in response, error code: {}", response.errorCode); + logger.error("{}: Error in response, error code: {}", tag, response.errorCode); this.token = ""; } return result;