}
```
+## 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 `<OPENHAB_USERDATA>/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
+
*/
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;
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;
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;
@Override
public List<EcovacsDevice> getDevices() throws EcovacsApiException, InterruptedException {
- List<DeviceDescription> descriptions = getSupportedDeviceList();
+ Map<String, DeviceDescription> descriptions = getSupportedDeviceDescs();
List<IotProduct> products = null;
List<EcovacsDevice> devices = new ArrayList<>();
for (Device dev : getDeviceList()) {
- Optional<DeviceDescription> descOpt = descriptions.stream()
- .filter(d -> dev.getDeviceClass().equals(d.deviceClass)).findFirst();
+ Optional<DeviceDescription> descOpt = Optional.ofNullable(descriptions.get(dev.getDeviceClass()));
if (!descOpt.isPresent()) {
if (products == null) {
products = getIotProductMap();
return devices;
}
- private List<DeviceDescription> getSupportedDeviceList() {
+ // maps device class -> device description
+ private Map<String, DeviceDescription> getSupportedDeviceDescs() {
+ Map<String, DeviceDescription> 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<List<DeviceDescription>>() {
- }.getType();
- List<DeviceDescription> 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)) {
+ 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<DeviceDescription> linkedDescOpt = descs.stream()
- .filter(d -> d.deviceClass.equals(desc.deviceClassLink)).findFirst();
+ Optional<DeviceDescription> 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<DeviceDescription> loadSupportedDeviceData(Reader input) throws IOException {
+ JsonReader reader = new JsonReader(input);
+ Type type = new TypeToken<List<DeviceDescription>>() {
+ }.getType();
+ return gson.fromJson(reader, type);
}
private List<Device> getDeviceList() throws EcovacsApiException, InterruptedException {