From f65b2f9150ffbee0e72ab20180cec2486d578e71 Mon Sep 17 00:00:00 2001 From: Marcel Verpaalen Date: Mon, 14 Nov 2022 17:05:23 +0100 Subject: [PATCH] [miio] cloud login issues improvement * Small change in debug loggin is login issues are expected * Additional trouble shooting steps in readme * Fix duplicate name in device list preventing readme maker running * Catch a json exception in readme maker close #13611 close #7459 Signed-off-by: Marcel Verpaalen --- .../org.openhab.binding.miio/README.base.md | 8 +- bundles/org.openhab.binding.miio/README.md | 77 ++++++++--- .../miio/internal/cloud/CloudLogin1DTO.java | 122 ++++++++++++++++++ .../miio/internal/cloud/MiCloudConnector.java | 12 +- .../resources/OH-INF/i18n/basic.properties | 11 ++ .../src/main/resources/misc/device_names.json | 1 - .../binding/miio/internal/ReadmeHelper.java | 3 +- 7 files changed, 208 insertions(+), 26 deletions(-) create mode 100644 bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudLogin1DTO.java diff --git a/bundles/org.openhab.binding.miio/README.base.md b/bundles/org.openhab.binding.miio/README.base.md index ef881f4440f1d..f9b271a652454 100644 --- a/bundles/org.openhab.binding.miio/README.base.md +++ b/bundles/org.openhab.binding.miio/README.base.md @@ -172,7 +172,13 @@ Firmware of the device don't accept commands coming from other subnets. Set the communication in the thing configuration to 'cloud'. _Cloud connectivity is not working_ -The most common problem is a wrong userId/password. Try to fix your userId/password. +The most common problem is a wrong userId/password. Try to fix your userId/password. The login userId & password are entered in the binding config page. +If the problem persists you can try the following: + +* Your ip might need to be validated/confirmed. Logon to https://account.xiaomi.com/ **from the ip of your openHAB server** with a browser. +* In the logging you find a location url. Try to login (just after it fails) with your browser. +* Several users also reported success by resetting their Xiaomi password. + If it still fails, you're bit out of luck. You may try to restart openHAB (not just the binding) to clean the cookies. As the cloud logon process is still little understood, your only luck might be to enable trace logging and see if you can translate the Chinese error code that it returns. diff --git a/bundles/org.openhab.binding.miio/README.md b/bundles/org.openhab.binding.miio/README.md index 85d5044a6e0a4..ac9b34ac3b720 100644 --- a/bundles/org.openhab.binding.miio/README.md +++ b/bundles/org.openhab.binding.miio/README.md @@ -172,7 +172,13 @@ Firmware of the device don't accept commands coming from other subnets. Set the communication in the thing configuration to 'cloud'. _Cloud connectivity is not working_ -The most common problem is a wrong userId/password. Try to fix your userId/password. +The most common problem is a wrong userId/password. Try to fix your userId/password. The login userId & password are entered in the binding config page. +If the problem persists you can try the following: + +* Your ip might need to be validated/confirmed. Logon to https://account.xiaomi.com/ **from the ip of your openHAB server** with a browser. +* In the logging you find a location url. Try to login (just after it fails) with your browser. +* Several users also reported success by resetting their Xiaomi password. + If it still fails, you're bit out of luck. You may try to restart openHAB (not just the binding) to clean the cookies. As the cloud logon process is still little understood, your only luck might be to enable trace logging and see if you can translate the Chinese error code that it returns. @@ -184,7 +190,7 @@ This will change the communication method and the Mi IO binding can communicate # Mi IO Devices -Currently the miio binding supports more than 330 different models. +Currently the miio binding supports more than 340 different models. | Device | ThingType | Device Model | Supported | Remark | |------------------------------------|------------------|------------------------|--------------|------------| @@ -347,6 +353,17 @@ Currently the miio binding supports more than 330 different models. | Roborock S7 | miio:vacuum | [roborock.vacuum.a15](#roborock-vacuum-a15) | Yes | | | Roborock S4 Max | miio:vacuum | [roborock.vacuum.a19](#roborock-vacuum-a19) | Yes | | | Roborock T7S Plus | miio:vacuum | [roborock.vacuum.a23](#roborock-vacuum-a23) | Yes | | +| Roborock G10S Pro | miio:vacuum | [roborock.vacuum.a26](#roborock-vacuum-a26) | Yes | | +| Roborock S7 MaxV | miio:vacuum | [roborock.vacuum.a27](#roborock-vacuum-a27) | Yes | | +| Roborock G10 | miio:vacuum | [roborock.vacuum.a29](#roborock-vacuum-a29) | Yes | | +| Roborock G10 | miio:vacuum | [roborock.vacuum.a30](#roborock-vacuum-a30) | Yes | | +| Roborock Q5 | miio:vacuum | [roborock.vacuum.a34](#roborock-vacuum-a34) | Yes | | +| Roborock T8 | miio:vacuum | [roborock.vacuum.a37](#roborock-vacuum-a37) | Yes | | +| Roborock Q7 Max | miio:vacuum | [roborock.vacuum.a38](#roborock-vacuum-a38) | Yes | | +| Roborock Q7 | miio:vacuum | [roborock.vacuum.a40](#roborock-vacuum-a40) | Yes | | +| Roborock G10S | miio:vacuum | [roborock.vacuum.a46](#roborock-vacuum-a46) | Yes | | +| Roborock T8 Plus | miio:vacuum | [roborock.vacuum.a52](#roborock-vacuum-a52) | Yes | | +| Roborock S7 Pro Ultra | miio:vacuum | [roborock.vacuum.a62](#roborock-vacuum-a62) | Yes | | | Xiaowa C1 | miio:vacuum | [roborock.vacuum.c1](#roborock-vacuum-c1) | Yes | | | Roborock Xiaowa E Series Vacuum v2 | miio:unsupported | roborock.vacuum.e2 | No | | | Mi Robot Vacuum 1S | miio:vacuum | [roborock.vacuum.m1s](#roborock-vacuum-m1s) | Yes | | @@ -2653,14 +2670,15 @@ Note, not all the values need to be in the json file, e.g. a subset of the param | Channel | Type | Description | Comment | |----------------------|----------------------|------------------------------------------|------------| | power | Switch | Power | | -| powerUsage | Number | Power Consumption | | +| mode | String | Mode | Value mapping `["normal"="Normal","green"="Green"]` | +| powerUsage | Number:Power | Power Consumption | | +| voltage | Number:ElectricPotential | Voltage | | | led | Switch | wifi LED | | -| power_price | Number | power_price | | -| current | Number | Current | | +| power_price | Number | Power Price | | +| power_factor | Number | Power Factor | | +| current | Number:ElectricCurrent | Current | | +| elec_leakage | Number:ElectricCurrent | Electic Leakage | | | temperature | Number:Temperature | Temperature | | -| lp_autooff | Number | Low Power Auto Off | | -| lp_autooff_delay | Number | Low Power Limit Time | | -| lp_threshold | Number | Low Power Threshold | | ### ROIDMI EVE vacuum (roidmi.vacuum.v60) Channels @@ -4264,6 +4282,8 @@ Note, not all the values need to be in the json file, e.g. a subset of the param | colorTemperature | Number | Color Temperature | | | colorMode | Number | Color Mode | Note, currently only supporting switching to RGB or CT mode. Value mapping `["0"="Default","2"="CT mode","1"="RGB mode","3"="HSV mode","4"="Color Flow mode","5"="Night Light mode"]` | | name | String | Name | | +| customScene | String | Set Scene | | +| nightlightBrightness | Number | Nightlight Brightness | | ### Yeelight Lightstrip (yeelink.light.strip1) Channels @@ -5240,15 +5260,21 @@ Note, not all the values need to be in the json file, e.g. a subset of the param | Channel | Type | Description | Comment | |----------------------|----------------------|------------------------------------------|------------| | power | Switch | Power | | -| mode | String | Mode | | +| mode | String | Mode | Value mapping `["auto"="Auto","favorite"="Favorite","silent"="Silent","high"="High","medium"="Medium","idle"="Idle","strong"="Strong"]` | | humidity | Number:Dimensionless | Humidity | | | aqi | Number | Air Quality Index | | -| brightness | Dimmer | Brightness | | +| averageaqi | Number | Average Air Quality Index | | | led | Switch | LED Status | | -| act_det | Switch | Air AutoDetect | | | buzzer | Switch | Buzzer Status | | | filtermaxlife | Number | Filter Max Life | | -| filterlive | Number | Filter Life | | +| filterhours | Number:Time | Filter Hours used | | +| usedhours | Number:Time | Run Time | | +| motorspeed | Number | Motor Speed | | +| filterlife | Number | Filter Life | | +| favoritelevel | Number | Favorite Level | Value mapping `["0"="Favorite 0","1"="Favorite 1","2"="Favorite 2","3"="Favorite 3","4"="Favorite 4","5"="Favorite 5","6"="Favorite 6","7"="Favorite 7","8"="Favorite 8","9"="Favorite 9","10"="Favorite 10","11"="Favorite 11","12"="Favorite 13","13"="Favorite 13","14"="Favorite 14","15"="Favorite 15"]` | +| temperature | Number:Temperature | Temperature | | +| purifyvolume | Number:Volume | Purified Volume | | +| childlock | Switch | Child Lock | | ### Mi Air Purifier v2 (zhimi.airpurifier.v2) Channels @@ -8328,14 +8354,15 @@ note: Autogenerated example. Replace the id (powerstrip) in the channel with you ``` Group G_powerstrip "CHINGMI Smart Power Strip v1" Switch power "Power" (G_powerstrip) {channel="miio:basic:powerstrip:power"} -Number powerUsage "Power Consumption" (G_powerstrip) {channel="miio:basic:powerstrip:powerUsage"} +String mode "Mode" (G_powerstrip) {channel="miio:basic:powerstrip:mode"} +Number:Power powerUsage "Power Consumption" (G_powerstrip) {channel="miio:basic:powerstrip:powerUsage"} +Number:ElectricPotential voltage "Voltage" (G_powerstrip) {channel="miio:basic:powerstrip:voltage"} Switch led "wifi LED" (G_powerstrip) {channel="miio:basic:powerstrip:led"} -Number power_price "power_price" (G_powerstrip) {channel="miio:basic:powerstrip:power_price"} -Number current "Current" (G_powerstrip) {channel="miio:basic:powerstrip:current"} +Number power_price "Power Price" (G_powerstrip) {channel="miio:basic:powerstrip:power_price"} +Number power_factor "Power Factor" (G_powerstrip) {channel="miio:basic:powerstrip:power_factor"} +Number:ElectricCurrent current "Current" (G_powerstrip) {channel="miio:basic:powerstrip:current"} +Number:ElectricCurrent elec_leakage "Electic Leakage" (G_powerstrip) {channel="miio:basic:powerstrip:elec_leakage"} Number:Temperature temperature "Temperature" (G_powerstrip) {channel="miio:basic:powerstrip:temperature"} -Number lp_autooff "Low Power Auto Off" (G_powerstrip) {channel="miio:basic:powerstrip:lp_autooff"} -Number lp_autooff_delay "Low Power Limit Time" (G_powerstrip) {channel="miio:basic:powerstrip:lp_autooff_delay"} -Number lp_threshold "Low Power Threshold" (G_powerstrip) {channel="miio:basic:powerstrip:lp_threshold"} ``` ### ROIDMI EVE vacuum (roidmi.vacuum.v60) item file lines @@ -10239,6 +10266,8 @@ Number:Time delayoff "Shutdown Timer" (G_light) {channel="miio:basic:light:delay Number colorTemperature "Color Temperature" (G_light) {channel="miio:basic:light:colorTemperature"} Number colorMode "Color Mode" (G_light) {channel="miio:basic:light:colorMode"} String name "Name" (G_light) {channel="miio:basic:light:name"} +String customScene "Set Scene" (G_light) {channel="miio:basic:light:customScene"} +Number nightlightBrightness "Nightlight Brightness" (G_light) {channel="miio:basic:light:nightlightBrightness"} ``` ### Yeelight Lightstrip (yeelink.light.strip1) item file lines @@ -11341,12 +11370,18 @@ Switch power "Power" (G_airpurifier) {channel="miio:basic:airpurifier:power"} String mode "Mode" (G_airpurifier) {channel="miio:basic:airpurifier:mode"} Number:Dimensionless humidity "Humidity" (G_airpurifier) {channel="miio:basic:airpurifier:humidity"} Number aqi "Air Quality Index" (G_airpurifier) {channel="miio:basic:airpurifier:aqi"} -Dimmer brightness "Brightness" (G_airpurifier) {channel="miio:basic:airpurifier:brightness"} +Number averageaqi "Average Air Quality Index" (G_airpurifier) {channel="miio:basic:airpurifier:averageaqi"} Switch led "LED Status" (G_airpurifier) {channel="miio:basic:airpurifier:led"} -Switch act_det "Air AutoDetect" (G_airpurifier) {channel="miio:basic:airpurifier:act_det"} Switch buzzer "Buzzer Status" (G_airpurifier) {channel="miio:basic:airpurifier:buzzer"} Number filtermaxlife "Filter Max Life" (G_airpurifier) {channel="miio:basic:airpurifier:filtermaxlife"} -Number filterlive "Filter Life" (G_airpurifier) {channel="miio:basic:airpurifier:filterlive"} +Number:Time filterhours "Filter Hours used" (G_airpurifier) {channel="miio:basic:airpurifier:filterhours"} +Number:Time usedhours "Run Time" (G_airpurifier) {channel="miio:basic:airpurifier:usedhours"} +Number motorspeed "Motor Speed" (G_airpurifier) {channel="miio:basic:airpurifier:motorspeed"} +Number filterlife "Filter Life" (G_airpurifier) {channel="miio:basic:airpurifier:filterlife"} +Number favoritelevel "Favorite Level" (G_airpurifier) {channel="miio:basic:airpurifier:favoritelevel"} +Number:Temperature temperature "Temperature" (G_airpurifier) {channel="miio:basic:airpurifier:temperature"} +Number:Volume purifyvolume "Purified Volume" (G_airpurifier) {channel="miio:basic:airpurifier:purifyvolume"} +Switch childlock "Child Lock" (G_airpurifier) {channel="miio:basic:airpurifier:childlock"} ``` ### Mi Air Purifier v2 (zhimi.airpurifier.v2) item file lines diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudLogin1DTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudLogin1DTO.java new file mode 100644 index 0000000000000..4008c46a4865f --- /dev/null +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudLogin1DTO.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.miio.internal.cloud; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +/** + * This DTO class wraps the login step 1 json structure + * + * @author Marcel Verpaalen - Initial contribution + */ +public class CloudLogin1DTO { + @SerializedName("serviceParam") + @Expose + private String serviceParam; + @SerializedName("qs") + @Expose + private String qs; + @SerializedName("code") + @Expose + private Integer code; + @SerializedName("description") + @Expose + private String description; + @SerializedName("securityStatus") + @Expose + private Integer securityStatus; + @SerializedName("_sign") + @Expose + private String sign; + @SerializedName("sid") + @Expose + private String sid; + @SerializedName("result") + @Expose + private String result; + @SerializedName("captchaUrl") + @Expose + private String captchaUrl; + @SerializedName("callback") + @Expose + private String callback; + @SerializedName("location") + @Expose + private String location; + @SerializedName("pwd") + @Expose + private Integer pwd; + @SerializedName("child") + @Expose + private Integer child; + @SerializedName("desc") + @Expose + private String desc; + + public String getServiceParam() { + return serviceParam; + } + + public String getQs() { + return qs; + } + + public Integer getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public Integer getSecurityStatus() { + return securityStatus; + } + + public String getSign() { + return sign; + } + + public String getSid() { + return sid; + } + + public String getResult() { + return result; + } + + public Object getCaptchaUrl() { + return captchaUrl; + } + + public String getCallback() { + return callback; + } + + public String getLocation() { + return location; + } + + public Integer getPwd() { + return pwd; + } + + public Integer getChild() { + return child; + } + + public String getDesc() { + return desc; + } +} diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java index 9f3bc649101c7..c1b7a84c3cffa 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java @@ -411,8 +411,9 @@ private String loginStep1() throws InterruptedException, TimeoutException, Execu logger.trace("Xiaomi Login step 1 response = {}", responseStep1); try { JsonElement resp = JsonParser.parseString(parseJson(content)); - if (resp.isJsonObject() && resp.getAsJsonObject().has("_sign")) { - String sign = resp.getAsJsonObject().get("_sign").getAsString(); + CloudLogin1DTO jsonResp = GSON.fromJson(resp, CloudLogin1DTO.class); + final String sign = jsonResp.getSign(); + if (sign != null && !jsonResp.getSign().isBlank()) { logger.trace("Xiaomi Login step 1 sign = {}", sign); return sign; } else { @@ -472,6 +473,13 @@ private String loginStep2(String sign) throws MiIoCryptoException, InterruptedEx logger.trace("Xiaomi login passToken = {}", passToken); logger.trace("Xiaomi login location = {}", location); logger.trace("Xiaomi login code = {}", code); + if (0 != jsonResp.getSecurityStatus()) { + logger.debug("Xiaomi Cloud Step2 response: {}", parseJson(content2)); + logger.debug( + "Xiaomi Login code: {} \r\nSecurityStatus: {}\r\nPwd code: {}\r\nLocation logon URL: {}\r\nIn case of login issues check userId/password details are correct.\r\n" + + "If login details are correct, try to logon using browser from the openHAB ip using the browser. Alternatively try to complete logon with above URL.", + jsonResp.getCode(), jsonResp.getSecurityStatus(), jsonResp.getPwd(), jsonResp.getLocation()); + } if (logger.isTraceEnabled()) { dumpCookies(url, false); } diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/basic.properties b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/basic.properties index 558fce83bc6fc..27b2105228d50 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/basic.properties +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/basic.properties @@ -161,6 +161,17 @@ thing.roborock.vacuum.a14 = Roborock T7S thing.roborock.vacuum.a15 = Roborock S7 thing.roborock.vacuum.a19 = Roborock S4 Max thing.roborock.vacuum.a23 = Roborock T7S Plus +thing.roborock.vacuum.a26 = Roborock G10S Pro +thing.roborock.vacuum.a27 = Roborock S7 MaxV +thing.roborock.vacuum.a29 = Roborock G10 +thing.roborock.vacuum.a30 = Roborock G10 +thing.roborock.vacuum.a34 = Roborock Q5 +thing.roborock.vacuum.a37 = Roborock T8 +thing.roborock.vacuum.a38 = Roborock Q7 Max +thing.roborock.vacuum.a40 = Roborock Q7 +thing.roborock.vacuum.a46 = Roborock G10S +thing.roborock.vacuum.a52 = Roborock T8 Plus +thing.roborock.vacuum.a62 = Roborock S7 Pro Ultra thing.roborock.vacuum.c1 = Xiaowa C1 thing.roborock.vacuum.e2 = Roborock Xiaowa E Series Vacuum v2 thing.roborock.vacuum.m1s = Mi Robot Vacuum 1S diff --git a/bundles/org.openhab.binding.miio/src/main/resources/misc/device_names.json b/bundles/org.openhab.binding.miio/src/main/resources/misc/device_names.json index 5316fe681c90d..c856d038cb031 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/misc/device_names.json +++ b/bundles/org.openhab.binding.miio/src/main/resources/misc/device_names.json @@ -1550,7 +1550,6 @@ "ijai.vacuum.v19": "Xiaomi Robot Vacuum-Mop 2S", "ijai.vacuum.v2": "Mi Robot Vacuum-Mop 2", "ijai.vacuum.v3": "Mi Robot Vacuum-Mop 2 Pro", - "ijai.vacuum.v19": "Mi Robot Vacuum-Mop 2S", "ijomoo.airer.mja6": "JOMOO smart clothes dryer -A6090", "ijomoo.bhf_light.jd071": "JOMOO Smart Bath Heater JD071", "ijomoo.toilet.i90": "JOMOO Smart Toilet I90", diff --git a/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java b/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java index a16a707ea6f50..c5b374771b5f1 100644 --- a/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java +++ b/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java @@ -51,6 +51,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonReader; /** @@ -260,7 +261,7 @@ private void checkDatabaseEntrys() { try { JsonReader reader = new JsonReader(new FileReader(DEVICE_NAMES_FILE)); names = gson.fromJson(reader, names.getClass()); - } catch (IOException e) { + } catch (JsonSyntaxException | IOException e) { LOGGER.info("Error reading name list {}: ", DEVICE_NAMES_FILE, e.getMessage()); }