]> git.basschouten.com Git - openhab-addons.git/commitdiff
[miio] dynamically generate channelTypes (#9158)
authorMarcel <marcel@verpaalen.com>
Fri, 4 Dec 2020 21:01:18 +0000 (13:01 -0800)
committerGitHub <noreply@github.com>
Fri, 4 Dec 2020 21:01:18 +0000 (13:01 -0800)
* [miio] dynamically generate channelTypes

Simplify the json database creation for new models and less chance for
errors
Related to #7276

Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
Co-authored-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoHandlerFactory.java
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/BasicChannelTypeProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/MiIoBasicChannel.java
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/OptionsValueListDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/StateDescriptionDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoBasicHandler.java

index b08b0cf23d86679108b80422f63161369118ec6f..45a18a2ec0e23760053ee0da4130363c74ddbf14 100644 (file)
@@ -19,6 +19,7 @@ import java.util.concurrent.ScheduledExecutorService;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.miio.internal.basic.BasicChannelTypeProvider;
 import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
 import org.openhab.binding.miio.internal.cloud.CloudConnector;
 import org.openhab.binding.miio.internal.handler.MiIoBasicHandler;
@@ -52,11 +53,12 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
     private MiIoDatabaseWatchService miIoDatabaseWatchService;
     private CloudConnector cloudConnector;
     private ChannelTypeRegistry channelTypeRegistry;
+    private BasicChannelTypeProvider basicChannelTypeProvider;
 
     @Activate
     public MiIoHandlerFactory(@Reference ChannelTypeRegistry channelTypeRegistry,
             @Reference MiIoDatabaseWatchService miIoDatabaseWatchService, @Reference CloudConnector cloudConnector,
-            Map<String, Object> properties) {
+            @Reference BasicChannelTypeProvider basicChannelTypeProvider, Map<String, Object> properties) {
         this.miIoDatabaseWatchService = miIoDatabaseWatchService;
         this.cloudConnector = cloudConnector;
         @Nullable
@@ -68,6 +70,7 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
         cloudConnector.setCredentials(username, password, country);
         scheduler.submit(() -> cloudConnector.isConnected());
         this.channelTypeRegistry = channelTypeRegistry;
+        this.basicChannelTypeProvider = basicChannelTypeProvider;
     }
 
     @Override
@@ -82,7 +85,8 @@ public class MiIoHandlerFactory extends BaseThingHandlerFactory {
             return new MiIoGenericHandler(thing, miIoDatabaseWatchService, cloudConnector);
         }
         if (thingTypeUID.equals(THING_TYPE_BASIC)) {
-            return new MiIoBasicHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
+            return new MiIoBasicHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry,
+                    basicChannelTypeProvider);
         }
         if (thingTypeUID.equals(THING_TYPE_VACUUM)) {
             return new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry);
diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/BasicChannelTypeProvider.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/BasicChannelTypeProvider.java
new file mode 100644 (file)
index 0000000..edefc8a
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2010-2020 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.basic;
+
+import static org.openhab.binding.miio.internal.MiIoBindingConstants.BINDING_ID;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.type.ChannelType;
+import org.openhab.core.thing.type.ChannelTypeBuilder;
+import org.openhab.core.thing.type.ChannelTypeProvider;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.thing.type.StateChannelTypeBuilder;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.StateOption;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provide channelTypes for Mi IO Basic devices
+ *
+ * @author Marcel Verpaalen - Initial contribution
+ */
+@Component(service = { ChannelTypeProvider.class, BasicChannelTypeProvider.class })
+@NonNullByDefault
+public class BasicChannelTypeProvider implements ChannelTypeProvider {
+    private final Map<String, ChannelType> channelTypes = new ConcurrentHashMap<>();
+    private final Logger logger = LoggerFactory.getLogger(BasicChannelTypeProvider.class);
+
+    @Override
+    public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
+        return channelTypes.values();
+    }
+
+    @Override
+    public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
+        if (channelTypes.containsKey(channelTypeUID.getAsString())) {
+            return channelTypes.get(channelTypeUID.getAsString());
+        }
+        return null;
+    }
+
+    public void addChannelType(MiIoBasicChannel miChannel, String model) {
+        ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID,
+                model.toUpperCase().replace(".", "_") + "_" + miChannel.getChannel());
+        logger.debug("Adding channel definitions for {} -> {}", channelTypeUID, miChannel.getFriendlyName());
+        try {
+            final StateDescriptionDTO stateDescription = miChannel.getStateDescription();
+            StateChannelTypeBuilder channelTypeBuilder = ChannelTypeBuilder.state(channelTypeUID,
+                    miChannel.getFriendlyName(), miChannel.getType()); //
+            if (stateDescription != null) {
+                StateDescriptionFragmentBuilder sdf = StateDescriptionFragmentBuilder.create();
+                final BigDecimal maximum = stateDescription.getMaximum();
+                if (maximum != null) {
+                    sdf.withMaximum(maximum);
+                }
+                final BigDecimal minimum = stateDescription.getMinimum();
+                if (minimum != null) {
+                    sdf.withMinimum(minimum);
+                }
+                final BigDecimal step = stateDescription.getStep();
+                if (step != null) {
+                    sdf.withStep(step);
+                }
+                final String pattern = stateDescription.getPattern();
+                if (pattern != null) {
+                    sdf.withPattern(pattern);
+                }
+                final Boolean readOnly = stateDescription.getReadOnly();
+                if (readOnly != null) {
+                    sdf.withReadOnly(readOnly);
+                }
+                List<OptionsValueListDTO> optionList = stateDescription.getOptions();
+                if (optionList != null) {
+                    List<StateOption> options = new ArrayList<>();
+                    for (OptionsValueListDTO option : optionList) {
+                        String value = option.getValue();
+                        if (value != null) {
+                            options.add(new StateOption(value, option.getLabel()));
+                        }
+                    }
+                    sdf.withOptions(options);
+                }
+                channelTypeBuilder.withStateDescriptionFragment(sdf.build());
+                logger.debug("added stateDescription: {}", sdf);
+            }
+            final String category = miChannel.getCategory();
+            if (category != null) {
+                channelTypeBuilder.withCategory(category);
+            }
+            final LinkedHashSet<String> tags = miChannel.getTags();
+            if (tags != null && tags.size() > 0) {
+                channelTypeBuilder.withTags(tags);
+            }
+            channelTypes.put(channelTypeUID.getAsString(), channelTypeBuilder.build());
+        } catch (Exception e) {
+            logger.warn("Failed creating channelType {}: {} ", channelTypeUID, e.getMessage());
+        }
+    }
+}
index 89994ec66cf68e89b17c6338eac136803d396230..97b7ac5e8dab684d28fc9ebde075fff56e3598eb 100644 (file)
@@ -16,6 +16,7 @@ import static org.openhab.binding.miio.internal.MiIoBindingConstants.BINDING_ID;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -56,6 +57,9 @@ public class MiIoBasicChannel {
     @SerializedName("unit")
     @Expose
     private @Nullable String unit;
+    @SerializedName("stateDescription")
+    @Expose
+    private @Nullable StateDescriptionDTO stateDescription;
     @SerializedName("refresh")
     @Expose
     private @Nullable Boolean refresh;
@@ -71,6 +75,12 @@ public class MiIoBasicChannel {
     @SerializedName("actions")
     @Expose
     private @Nullable List<MiIoDeviceAction> miIoDeviceActions = new ArrayList<>();
+    @SerializedName("category")
+    @Expose
+    private @Nullable String category;
+    @SerializedName("tags")
+    @Expose
+    private @Nullable LinkedHashSet<String> tags;
     @SerializedName("readmeComment")
     @Expose
     private @Nullable String readmeComment;
@@ -167,6 +177,14 @@ public class MiIoBasicChannel {
         this.unit = unit;
     }
 
+    public @Nullable StateDescriptionDTO getStateDescription() {
+        return stateDescription;
+    }
+
+    public void setStateDescription(@Nullable StateDescriptionDTO stateDescription) {
+        this.stateDescription = stateDescription;
+    }
+
     public Boolean getRefresh() {
         final @Nullable Boolean rf = refresh;
         return rf != null && rf.booleanValue() && !getProperty().isEmpty();
@@ -211,6 +229,22 @@ public class MiIoBasicChannel {
         this.transfortmation = transfortmation;
     }
 
+    public @Nullable String getCategory() {
+        return category;
+    }
+
+    public void setCategory(String category) {
+        this.category = category;
+    }
+
+    public @Nullable LinkedHashSet<String> getTags() {
+        return tags;
+    }
+
+    public void setTags(LinkedHashSet<String> tags) {
+        this.tags = tags;
+    }
+
     public String getReadmeComment() {
         final String readmeComment = this.readmeComment;
         return (readmeComment != null) ? readmeComment : "";
diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/OptionsValueListDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/OptionsValueListDTO.java
new file mode 100644 (file)
index 0000000..ed0caba
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2020 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.basic;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Mapping properties from json for channel options
+ *
+ * @author Marcel Verpaalen - Initial contribution
+ */
+@NonNullByDefault
+public class OptionsValueListDTO {
+
+    @SerializedName("value")
+    @Expose
+    public @Nullable String value;
+
+    @SerializedName("label")
+    @Expose
+    public @Nullable String label;
+
+    public @Nullable String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public @Nullable String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+}
diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/StateDescriptionDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/StateDescriptionDTO.java
new file mode 100644 (file)
index 0000000..c953d73
--- /dev/null
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2010-2020 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.basic;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Mapping properties from json for state descriptions
+ *
+ * @author Marcel Verpaalen - Initial contribution
+ */
+@NonNullByDefault
+public class StateDescriptionDTO {
+
+    @SerializedName("minimum")
+    @Expose
+    @Nullable
+    private BigDecimal minimum;
+    @SerializedName("maximum")
+    @Expose
+    @Nullable
+    private BigDecimal maximum;
+    @SerializedName("step")
+    @Expose
+    @Nullable
+    private BigDecimal step;
+    @SerializedName("pattern")
+    @Expose
+    @Nullable
+    private String pattern;
+    @SerializedName("readOnly")
+    @Expose
+    @Nullable
+    private Boolean readOnly;
+    @SerializedName("options")
+    @Expose
+    @Nullable
+    public List<OptionsValueListDTO> options = null;
+
+    @Nullable
+    public BigDecimal getMinimum() {
+        return minimum;
+    }
+
+    public void setMinimum(BigDecimal minimum) {
+        this.minimum = minimum;
+    }
+
+    @Nullable
+    public BigDecimal getMaximum() {
+        return maximum;
+    }
+
+    public void setMaximum(BigDecimal maximum) {
+        this.maximum = maximum;
+    }
+
+    @Nullable
+    public BigDecimal getStep() {
+        return step;
+    }
+
+    public void setStep(BigDecimal step) {
+        this.step = step;
+    }
+
+    @Nullable
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    @Nullable
+    public Boolean getReadOnly() {
+        return readOnly;
+    }
+
+    public void setReadOnly(Boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
+    @Nullable
+    public List<OptionsValueListDTO> getOptions() {
+        return options;
+    }
+
+    public void setOptions(List<OptionsValueListDTO> options) {
+        this.options = options;
+    }
+}
index b6d12d8775d305e63391d7eff34e40d894a8d41e..bffcdb8addb45b14c858b86473c1bca44b955b06 100644 (file)
@@ -19,6 +19,7 @@ import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -34,6 +35,7 @@ import org.openhab.binding.miio.internal.MiIoQuantiyTypes;
 import org.openhab.binding.miio.internal.MiIoSendCommand;
 import org.openhab.binding.miio.internal.Utils;
 import org.openhab.binding.miio.internal.basic.ActionConditions;
+import org.openhab.binding.miio.internal.basic.BasicChannelTypeProvider;
 import org.openhab.binding.miio.internal.basic.CommandParameterType;
 import org.openhab.binding.miio.internal.basic.Conversions;
 import org.openhab.binding.miio.internal.basic.MiIoBasicChannel;
@@ -95,11 +97,14 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
     private @Nullable MiIoBasicDevice miioDevice;
     private Map<ChannelUID, MiIoBasicChannel> actions = new HashMap<>();
     private ChannelTypeRegistry channelTypeRegistry;
+    private BasicChannelTypeProvider basicChannelTypeProvider;
 
     public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
-            CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) {
+            CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry,
+            BasicChannelTypeProvider basicChannelTypeProvider) {
         super(thing, miIoDatabaseWatchService, cloudConnector);
         this.channelTypeRegistry = channelTypeRegistry;
+        this.basicChannelTypeProvider = basicChannelTypeProvider;
     }
 
     @Override
@@ -259,7 +264,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
                 updateData();
             }, 3000, TimeUnit.MILLISECONDS);
         } else {
-            logger.debug("Actions not loaded yet");
+            logger.debug("Actions not loaded yet, or none available");
         }
     }
 
@@ -409,8 +414,8 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
                 for (MiIoBasicChannel miChannel : device.getDevice().getChannels()) {
                     logger.debug("properties {}", miChannel);
                     if (!miChannel.getType().isEmpty()) {
-                        ChannelUID channelUID = addChannel(thingBuilder, miChannel.getChannel(),
-                                miChannel.getChannelType(), miChannel.getType(), miChannel.getFriendlyName());
+                        basicChannelTypeProvider.addChannelType(miChannel, deviceName);
+                        ChannelUID channelUID = addChannel(thingBuilder, miChannel, deviceName);
                         if (channelUID != null) {
                             actions.put(channelUID, miChannel);
                             channelsAdded++;
@@ -440,9 +445,10 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
         return false;
     }
 
-    private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, @Nullable String channel, String channelType,
-            @Nullable String datatype, String friendlyName) {
-        if (channel == null || channel.isEmpty() || datatype == null || datatype.isEmpty()) {
+    private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, MiIoBasicChannel miChannel, String model) {
+        String channel = miChannel.getChannel();
+        String dataType = miChannel.getType();
+        if (channel.isEmpty() || dataType.isEmpty()) {
             logger.info("Channel '{}', UID '{}' cannot be added incorrectly configured database. ", channel,
                     getThing().getUID());
             return null;
@@ -455,22 +461,30 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
             logger.info("Channel '{}' for thing {} already exist... removing", channel, getThing().getUID());
             thingBuilder.withoutChannel(new ChannelUID(getThing().getUID(), channel));
         }
-        ChannelBuilder newChannel = ChannelBuilder.create(channelUID, datatype).withLabel(friendlyName);
-        boolean useGenericChannelType = false;
-        if (!channelType.isBlank()) {
-            ChannelTypeUID channelTypeUID = new ChannelTypeUID(channelType);
+        ChannelBuilder newChannel = ChannelBuilder.create(channelUID, dataType).withLabel(miChannel.getFriendlyName());
+        boolean useGeneratedChannelType = false;
+        if (!miChannel.getChannelType().isBlank()) {
+            ChannelTypeUID channelTypeUID = new ChannelTypeUID(miChannel.getChannelType());
             if (channelTypeRegistry.getChannelType(channelTypeUID) != null) {
                 newChannel = newChannel.withType(channelTypeUID);
+                final LinkedHashSet<String> tags = miChannel.getTags();
+                if (tags != null && tags.size() > 0) {
+                    newChannel.withDefaultTags(tags);
+                }
             } else {
-                logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID,
-                        getThing().getUID());
-                useGenericChannelType = true;
+                logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID, model);
+                useGeneratedChannelType = true;
             }
         } else {
-            useGenericChannelType = true;
+            useGeneratedChannelType = true;
         }
-        if (useGenericChannelType) {
-            newChannel = newChannel.withType(new ChannelTypeUID(BINDING_ID, datatype.toLowerCase()));
+        if (useGeneratedChannelType) {
+            newChannel = newChannel
+                    .withType(new ChannelTypeUID(BINDING_ID, model.toUpperCase().replace(".", "_") + "_" + channel));
+            final LinkedHashSet<String> tags = miChannel.getTags();
+            if (tags != null && tags.size() > 0) {
+                newChannel.withDefaultTags(tags);
+            }
         }
         thingBuilder.withChannel(newChannel.build());
         return channelUID;