From e0a95937aa56e6cc8bb09e213f27fedbdf181a17 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 24 Apr 2023 15:33:26 +0200 Subject: [PATCH 1/5] [ecovacs] Allow loading device descriptions from a user file If a new device is released within our release cycle, getting the description for that device into the binding was impossible for users before. With this change, they can create an override file in their installation (with contents we'll need to supply to them), which serves as a stop-gap solution until the next OH release. Signed-off-by: Danny Baumann --- .../internal/api/impl/EcovacsApiImpl.java | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java index 2cf1f784b9833..b9c01937517ac 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java @@ -12,9 +12,13 @@ */ package org.openhab.binding.ecovacs.internal.api.impl; -import java.io.InputStream; +import java.io.IOException; import java.io.InputStreamReader; +import java.io.Reader; import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -25,7 +29,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; @@ -64,10 +67,12 @@ import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalLoginResponse; import org.openhab.binding.ecovacs.internal.api.util.DataParsingException; import org.openhab.binding.ecovacs.internal.api.util.MD5Util; +import org.openhab.core.OpenHAB; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; @@ -162,12 +167,11 @@ private PortalLoginResponse portalLogin(AuthCode authCode, AccessData accessData @Override public List getDevices() throws EcovacsApiException, InterruptedException { - List descriptions = getSupportedDeviceList(); + Map descriptions = getSupportedDeviceDescs(); List products = null; List devices = new ArrayList<>(); for (Device dev : getDeviceList()) { - Optional descOpt = descriptions.stream() - .filter(d -> dev.getDeviceClass().equals(d.deviceClass)).findFirst(); + Optional descOpt = Optional.ofNullable(descriptions.get(dev.getDeviceClass())); if (!descOpt.isPresent()) { if (products == null) { products = getIotProductMap(); @@ -188,29 +192,58 @@ public List getDevices() throws EcovacsApiException, InterruptedE return devices; } - private List getSupportedDeviceList() { + // maps device class -> device description + private Map getSupportedDeviceDescs() { + Map descs = new HashMap<>(); ClassLoader cl = Objects.requireNonNull(getClass().getClassLoader()); - InputStream is = cl.getResourceAsStream("devices/supported_device_list.json"); - JsonReader reader = new JsonReader(new InputStreamReader(is)); - Type type = new TypeToken>() { - }.getType(); - List descs = gson.fromJson(reader, type); - return descs.stream().map(desc -> { - final DeviceDescription result; + try (Reader reader = new InputStreamReader(cl.getResourceAsStream("devices/supported_device_list.json"))) { + for (DeviceDescription desc : loadSupportedDeviceData(reader)) { + descs.put(desc.deviceClass, desc); + } + logger.trace("Loaded {} built-in device descriptions", descs.size()); + } catch (IOException | JsonSyntaxException e) { + logger.warn("Failed loading built-in device descriptions", e); + } + + Path customDescsPath = Paths.get(OpenHAB.getUserDataFolder(), "ecovacs").resolve("custom_device_descs.json"); + if (Files.exists(customDescsPath)) { + try (Reader reader = Files.newBufferedReader(customDescsPath)) { + int builtins = descs.size(); + for (DeviceDescription desc : loadSupportedDeviceData(reader)) { + if (descs.containsKey(desc.deviceClass)) { + logger.trace("Overriding built-in description for {} with custom description", + desc.deviceClass); + } + descs.put(desc.deviceClass, desc); + } + logger.trace("Loaded {} custom device descriptions", descs.size() - builtins); + } catch (IOException | JsonSyntaxException e) { + logger.warn("Failed loading custom device descriptions from {}", customDescsPath, e); + } + } + + descs.entrySet().forEach(descEntry -> { + DeviceDescription desc = descEntry.getValue(); if (desc.deviceClassLink != null) { - Optional linkedDescOpt = descs.stream() - .filter(d -> d.deviceClass.equals(desc.deviceClassLink)).findFirst(); + Optional linkedDescOpt = Optional.ofNullable(descs.get(desc.deviceClassLink)); if (!linkedDescOpt.isPresent()) { - throw new IllegalStateException( - "Desc " + desc.deviceClass + " links unknown desc " + desc.deviceClassLink); + logger.warn("Device description {} links unknown description {}", desc.deviceClass, + desc.deviceClassLink); } - result = desc.resolveLinkWith(linkedDescOpt.get()); - } else { - result = desc; + desc = desc.resolveLinkWith(linkedDescOpt.get()); + descEntry.setValue(desc); } - result.addImplicitCapabilities(); - return result; - }).collect(Collectors.toList()); + desc.addImplicitCapabilities(); + }); + + return descs; + } + + private List loadSupportedDeviceData(Reader input) throws IOException { + JsonReader reader = new JsonReader(input); + Type type = new TypeToken>() { + }.getType(); + return gson.fromJson(reader, type); } private List getDeviceList() throws EcovacsApiException, InterruptedException { From 6f4e38bc088aded0862680319a5c471048e1d31f Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 24 Apr 2023 15:38:30 +0200 Subject: [PATCH 2/5] [ecovavs] Add missing device definition Signed-off-by: Danny Baumann --- .../src/main/resources/devices/supported_device_list.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json b/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json index 36985c9325559..30c1c815f4cca 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json +++ b/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json @@ -304,6 +304,11 @@ "deviceClass": "yu362x", "deviceClassLink": "h18jkh" }, + { + "modelName": "DEEBOT N8 PRO CARE", + "deviceClass": "s1f8g7", + "deviceClassLink": "h18jkh" + }, { "modelName": "DEEBOT OZMO T8+", From 0ed3bb566d56b24ae5106d7211fb80938ee5edfc Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 28 Apr 2023 09:49:30 +0200 Subject: [PATCH 3/5] Adjust to reviewer suggestions Signed-off-by: Danny Baumann --- bundles/org.openhab.binding.ecovacs/README.md | 17 +++++++++++++++++ .../internal/api/impl/EcovacsApiImpl.java | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.ecovacs/README.md b/bundles/org.openhab.binding.ecovacs/README.md index 98284ba23de56..1e29734dfd2e9 100644 --- a/bundles/org.openhab.binding.ecovacs/README.md +++ b/bundles/org.openhab.binding.ecovacs/README.md @@ -173,3 +173,20 @@ Bridge ecovacs:ecovacsapi:ecovacsapi [ email="your.email@provider.com", password } ``` +## Adding support for unsupported models + +When encountering an unsupported model during discovery, the binding creates a log message like this one: + +``` +2023-04-21 12:02:39.607 [INFO ] [acs.internal.api.impl.EcovacsApiImpl] - Found unsupported device DEEBOT N8 PRO CARE (class s1f8g7, company eco-ng), ignoring. +``` + +In such a case, please [create an issue on GitHub](https://github.com/openhab/openhab-addons/issues), listing the contents of the log line. +In addition to that, If the model is similar to an already supported one, you can try to add the support for yourself (until getting an updated binding). +For doing so, you can follow the following steps: + +- in the `/evocacs` folder, create a file named `custom_device_descs.json` +- the format of that file is the same as [the built-in device list](https://raw.githubusercontent.com/openhab/openhab-addons/main/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json) +- for a model that is very similar to an existing one, create an entry with `modelName`, `deviceClass` (from the log line) and `deviceClassLink` (`deviceClass` of the similar model) +- for other models, you can also try experimenting with creating a full entry, but it's likely that the binding code will need to be updated in that case + diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java index b9c01937517ac..2411fe5041ff2 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java @@ -210,11 +210,11 @@ private Map getSupportedDeviceDescs() { try (Reader reader = Files.newBufferedReader(customDescsPath)) { int builtins = descs.size(); for (DeviceDescription desc : loadSupportedDeviceData(reader)) { - if (descs.containsKey(desc.deviceClass)) { + DeviceDescription builtinDesc = descs.put(desc.deviceClass, desc); + if (builtinDesc != null) { logger.trace("Overriding built-in description for {} with custom description", desc.deviceClass); } - descs.put(desc.deviceClass, desc); } logger.trace("Loaded {} custom device descriptions", descs.size() - builtins); } catch (IOException | JsonSyntaxException e) { From a9814c393373dab434f0cae47e1a8257b443ec07 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 28 Apr 2023 10:04:06 +0200 Subject: [PATCH 4/5] Improve wording Signed-off-by: Danny Baumann --- bundles/org.openhab.binding.ecovacs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.ecovacs/README.md b/bundles/org.openhab.binding.ecovacs/README.md index 1e29734dfd2e9..cf8c8afe8e488 100644 --- a/bundles/org.openhab.binding.ecovacs/README.md +++ b/bundles/org.openhab.binding.ecovacs/README.md @@ -182,7 +182,7 @@ When encountering an unsupported model during discovery, the binding creates a l ``` In such a case, please [create an issue on GitHub](https://github.com/openhab/openhab-addons/issues), listing the contents of the log line. -In addition to that, If the model is similar to an already supported one, you can try to add the support for yourself (until getting an updated binding). +In addition to that, If the model is similar to an already supported one, you can try to add the support yourself (until getting an updated binding). For doing so, you can follow the following steps: - in the `/evocacs` folder, create a file named `custom_device_descs.json` From 40ebf5b2cca6dbb585cf29433d34c278850470dc Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 1 May 2023 14:45:25 +0200 Subject: [PATCH 5/5] README fixes Signed-off-by: Danny Baumann --- bundles/org.openhab.binding.ecovacs/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.ecovacs/README.md b/bundles/org.openhab.binding.ecovacs/README.md index cf8c8afe8e488..3360cf0ee965c 100644 --- a/bundles/org.openhab.binding.ecovacs/README.md +++ b/bundles/org.openhab.binding.ecovacs/README.md @@ -182,11 +182,11 @@ When encountering an unsupported model during discovery, the binding creates a l ``` In such a case, please [create an issue on GitHub](https://github.com/openhab/openhab-addons/issues), listing the contents of the log line. -In addition to that, If the model is similar to an already supported one, you can try to add the support yourself (until getting an updated binding). +In addition to that, if the model is similar to an already supported one, you can try to add the support yourself (until getting an updated binding). For doing so, you can follow the following steps: -- in the `/evocacs` folder, create a file named `custom_device_descs.json` -- the format of that file is the same as [the built-in device list](https://raw.githubusercontent.com/openhab/openhab-addons/main/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json) +- create the folder `/evocacs` (if not done previously) +- create a file named `custom_device_descs.json`, whose format of that file is the same as [the built-in device list](https://raw.githubusercontent.com/openhab/openhab-addons/main/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json) - for a model that is very similar to an existing one, create an entry with `modelName`, `deviceClass` (from the log line) and `deviceClassLink` (`deviceClass` of the similar model) - for other models, you can also try experimenting with creating a full entry, but it's likely that the binding code will need to be updated in that case