diff --git a/bundles/org.openhab.binding.ecovacs/README.md b/bundles/org.openhab.binding.ecovacs/README.md index 98284ba23de..3360cf0ee96 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 yourself (until getting an updated binding). +For doing so, you can follow the following steps: + +- 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 + 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 2cf1f784b98..2411fe5041f 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.Optional; 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.PortalI 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 @@ public final class EcovacsApiImpl implements EcovacsApi { @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 final class EcovacsApiImpl implements EcovacsApi { 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)); + 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)) { + DeviceDescription builtinDesc = descs.put(desc.deviceClass, desc); + if (builtinDesc != null) { + logger.trace("Overriding built-in description for {} with custom description", + desc.deviceClass); + } + } + 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 = Optional.ofNullable(descs.get(desc.deviceClassLink)); + if (!linkedDescOpt.isPresent()) { + logger.warn("Device description {} links unknown description {}", desc.deviceClass, + desc.deviceClassLink); + } + desc = desc.resolveLinkWith(linkedDescOpt.get()); + descEntry.setValue(desc); + } + desc.addImplicitCapabilities(); + }); + + return descs; + } + + private List loadSupportedDeviceData(Reader input) throws IOException { + JsonReader reader = new JsonReader(input); Type type = new TypeToken>() { }.getType(); - List descs = gson.fromJson(reader, type); - return descs.stream().map(desc -> { - final DeviceDescription result; - if (desc.deviceClassLink != null) { - Optional linkedDescOpt = descs.stream() - .filter(d -> d.deviceClass.equals(desc.deviceClassLink)).findFirst(); - if (!linkedDescOpt.isPresent()) { - throw new IllegalStateException( - "Desc " + desc.deviceClass + " links unknown desc " + desc.deviceClassLink); - } - result = desc.resolveLinkWith(linkedDescOpt.get()); - } else { - result = desc; - } - result.addImplicitCapabilities(); - return result; - }).collect(Collectors.toList()); + return gson.fromJson(reader, type); } private List getDeviceList() throws EcovacsApiException, InterruptedException { 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 36985c93255..30c1c815f4c 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+",