]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt.homie] build a per-thing thing type (#15893)
authorCody Cutrer <cody@cutrer.us>
Mon, 1 Apr 2024 15:31:35 +0000 (09:31 -0600)
committerGitHub <noreply@github.com>
Mon, 1 Apr 2024 15:31:35 +0000 (17:31 +0200)
* [mqtt.homie] build a per-thing thing type

Signed-off-by: Cody Cutrer <cody@cutrer.us>
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/MqttChannelStateDescriptionProvider.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/generic/internal/MqttBindingConstants.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/generic/internal/MqttThingHandlerFactory.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Device.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Node.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Property.java
bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/config/homie-channel-config.xml [deleted file]
bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/thing/homie-channels.xml [new file with mode: 0644]
bundles/org.openhab.binding.mqtt.homie/src/test/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandlerTests.java
itests/org.openhab.binding.mqtt.homie.tests/src/main/java/org/openhab/binding/mqtt/homie/HomieImplementationTest.java

index 232c23fafe8a105e26e87b55ff6013005d3c7371..984039b7787607054d5d8eb5c529dd828da394f8 100644 (file)
@@ -22,7 +22,9 @@ import org.openhab.binding.mqtt.generic.internal.MqttThingHandlerFactory;
 import org.openhab.binding.mqtt.generic.internal.handler.GenericMQTTThingHandler;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
 import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.openhab.core.types.CommandDescription;
 import org.openhab.core.types.StateDescription;
 import org.osgi.service.component.annotations.Component;
 import org.slf4j.Logger;
@@ -37,11 +39,14 @@ import org.slf4j.LoggerFactory;
  *
  * @author David Graeff - Initial contribution
  */
-@Component(service = { DynamicStateDescriptionProvider.class, MqttChannelStateDescriptionProvider.class })
+@Component(service = { DynamicStateDescriptionProvider.class, DynamicCommandDescriptionProvider.class,
+        MqttChannelStateDescriptionProvider.class })
 @NonNullByDefault
-public class MqttChannelStateDescriptionProvider implements DynamicStateDescriptionProvider {
+public class MqttChannelStateDescriptionProvider
+        implements DynamicStateDescriptionProvider, DynamicCommandDescriptionProvider {
 
-    private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
+    private final Map<ChannelUID, StateDescription> stateDescriptions = new ConcurrentHashMap<>();
+    private final Map<ChannelUID, CommandDescription> commandDescriptions = new ConcurrentHashMap<>();
     private final Logger logger = LoggerFactory.getLogger(MqttChannelStateDescriptionProvider.class);
 
     /**
@@ -53,33 +58,55 @@ public class MqttChannelStateDescriptionProvider implements DynamicStateDescript
      */
     public void setDescription(ChannelUID channelUID, StateDescription description) {
         logger.debug("Adding state description for channel {}", channelUID);
-        descriptions.put(channelUID, description);
+        stateDescriptions.put(channelUID, description);
+    }
+
+    /**
+     * Set a command description for a channel.
+     * A previous description, if existed, will be replaced.
+     *
+     * @param channelUID channel UID
+     * @param description command description for the channel
+     */
+    public void setDescription(ChannelUID channelUID, CommandDescription description) {
+        logger.debug("Adding state description for channel {}", channelUID);
+        commandDescriptions.put(channelUID, description);
     }
 
     /**
      * Clear all registered state descriptions
      */
     public void removeAllDescriptions() {
-        logger.debug("Removing all state descriptions");
-        descriptions.clear();
+        logger.debug("Removing all descriptions");
+        stateDescriptions.clear();
+        commandDescriptions.clear();
     }
 
     @Override
     public @Nullable StateDescription getStateDescription(Channel channel,
             @Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
-        StateDescription description = descriptions.get(channel.getUID());
+        StateDescription description = stateDescriptions.get(channel.getUID());
         if (description != null) {
             logger.trace("Providing state description for channel {}", channel.getUID());
         }
         return description;
     }
 
+    @Override
+    public @Nullable CommandDescription getCommandDescription(Channel channel,
+            @Nullable CommandDescription originalCommandDescription, @Nullable Locale locale) {
+        CommandDescription description = commandDescriptions.get(channel.getUID());
+        logger.trace("Providing command description for channel {}", channel.getUID());
+        return description;
+    }
+
     /**
-     * Removes the given channel state description.
+     * Removes the given channel description.
      *
      * @param channel The channel
      */
     public void remove(ChannelUID channel) {
-        descriptions.remove(channel);
+        stateDescriptions.remove(channel);
+        commandDescriptions.remove(channel);
     }
 }
index ed09177b6fff7044291ac18ca80224311d26417c..9e79d75054974ca37c5994f4f6c221a2e558b146 100644 (file)
@@ -29,7 +29,15 @@ public class MqttBindingConstants {
     // List of all Thing Type UIDs
     public static final ThingTypeUID HOMIE300_MQTT_THING = new ThingTypeUID(BINDING_ID, "homie300");
 
-    public static final String CONFIG_HOMIE_CHANNEL = "channel-type:mqtt:homie-channel";
+    public static final String CHANNEL_TYPE_HOMIE_PREFIX = "homie-";
+    public static final String CHANNEL_TYPE_HOMIE_STRING = "homie-string";
+    public static final String CHANNEL_TYPE_HOMIE_TRIGGER = "homie-trigger";
+
+    public static final String CHANNEL_PROPERTY_DATATYPE = "datatype";
+    public static final String CHANNEL_PROPERTY_SETTABLE = "settable";
+    public static final String CHANNEL_PROPERTY_RETAINED = "retained";
+    public static final String CHANNEL_PROPERTY_FORMAT = "format";
+    public static final String CHANNEL_PROPERTY_UNIT = "unit";
 
     public static final String HOMIE_PROPERTY_VERSION = "homieversion";
     public static final String HOMIE_PROPERTY_HEARTBEAT_INTERVAL = "heartbeat_interval";
index 66199c5175de95b695487d062f42a0419e3f28a6..30a7a64e1369fc78825fc7ed626cff15440aeab9 100644 (file)
@@ -16,6 +16,7 @@ import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
 import org.openhab.binding.mqtt.homie.internal.handler.HomieThingHandler;
@@ -24,12 +25,11 @@ import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
 import org.openhab.core.transform.TransformationHelper;
 import org.openhab.core.transform.TransformationService;
-import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 
 /**
@@ -41,43 +41,40 @@ import org.osgi.service.component.annotations.Reference;
 @Component(service = ThingHandlerFactory.class)
 @NonNullByDefault
 public class MqttThingHandlerFactory extends BaseThingHandlerFactory implements TransformationServiceProvider {
-    private @NonNullByDefault({}) MqttChannelTypeProvider typeProvider;
-    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set
-            .of(MqttBindingConstants.HOMIE300_MQTT_THING);
-
-    @Override
-    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
-        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
-    }
+    private final MqttChannelTypeProvider typeProvider;
+    private final MqttChannelStateDescriptionProvider stateDescriptionProvider;
+    private final ChannelTypeRegistry channelTypeRegistry;
 
     @Activate
-    @Override
-    protected void activate(ComponentContext componentContext) {
-        super.activate(componentContext);
+    public MqttThingHandlerFactory(final @Reference MqttChannelTypeProvider typeProvider,
+            final @Reference MqttChannelStateDescriptionProvider stateDescriptionProvider,
+            final @Reference ChannelTypeRegistry channelTypeRegistry) {
+        this.typeProvider = typeProvider;
+        this.stateDescriptionProvider = stateDescriptionProvider;
+        this.channelTypeRegistry = channelTypeRegistry;
     }
 
-    @Deactivate
-    @Override
-    protected void deactivate(ComponentContext componentContext) {
-        super.deactivate(componentContext);
-    }
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set
+            .of(MqttBindingConstants.HOMIE300_MQTT_THING);
 
-    @Reference
-    protected void setChannelProvider(MqttChannelTypeProvider provider) {
-        this.typeProvider = provider;
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || isHomieDynamicType(thingTypeUID);
     }
 
-    protected void unsetChannelProvider(MqttChannelTypeProvider provider) {
-        this.typeProvider = null;
+    private boolean isHomieDynamicType(ThingTypeUID thingTypeUID) {
+        return MqttBindingConstants.BINDING_ID.equals(thingTypeUID.getBindingId())
+                && thingTypeUID.getId().startsWith(MqttBindingConstants.HOMIE300_MQTT_THING.getId());
     }
 
     @Override
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
-        if (thingTypeUID.equals(MqttBindingConstants.HOMIE300_MQTT_THING)) {
-            return new HomieThingHandler(thing, typeProvider, MqttBindingConstants.HOMIE_DEVICE_TIMEOUT_MS,
-                    MqttBindingConstants.HOMIE_SUBSCRIBE_TIMEOUT_MS, MqttBindingConstants.HOMIE_ATTRIBUTE_TIMEOUT_MS);
+        if (supportsThingType(thingTypeUID)) {
+            return new HomieThingHandler(thing, typeProvider, stateDescriptionProvider, channelTypeRegistry,
+                    MqttBindingConstants.HOMIE_DEVICE_TIMEOUT_MS, MqttBindingConstants.HOMIE_SUBSCRIBE_TIMEOUT_MS,
+                    MqttBindingConstants.HOMIE_ATTRIBUTE_TIMEOUT_MS);
         }
         return null;
     }
index 923038be5afbfeb15a5cf008965e63738888ae9f..cb305be7841f87891b4483f50f2a02fb9fe67b53 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.mqtt.homie.internal.handler;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledFuture;
@@ -23,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.AbstractMQTTThingHandler;
 import org.openhab.binding.mqtt.generic.ChannelState;
+import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
 import org.openhab.binding.mqtt.generic.tools.DelayedBatchProcessing;
 import org.openhab.binding.mqtt.homie.generic.internal.MqttBindingConstants;
@@ -39,6 +41,12 @@ import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.type.ChannelGroupDefinition;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
+import org.openhab.core.thing.type.ThingType;
+import org.openhab.core.types.CommandDescription;
+import org.openhab.core.types.StateDescription;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,6 +61,8 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
     private final Logger logger = LoggerFactory.getLogger(HomieThingHandler.class);
     protected Device device;
     protected final MqttChannelTypeProvider channelTypeProvider;
+    protected final MqttChannelStateDescriptionProvider stateDescriptionProvider;
+    protected final ChannelTypeRegistry channelTypeRegistry;
     /** The timeout per attribute field subscription */
     protected final int attributeReceiveTimeout;
     protected final int subscribeTimeout;
@@ -67,16 +77,21 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
      *
      * @param thing The thing of this handler
      * @param channelTypeProvider A channel type provider
+     * @param stateDescriptionProvider A state description provider
+     * @param channelTypeRegistry The channel type registry
      * @param deviceTimeout Timeout for the entire device subscription. In milliseconds.
      * @param subscribeTimeout Timeout for an entire attribute class subscription and receive. In milliseconds.
      *            Even a slow remote device will publish a full node or property within 100ms.
      * @param attributeReceiveTimeout The timeout per attribute field subscription. In milliseconds.
      *            One attribute subscription and receiving should not take longer than 50ms.
      */
-    public HomieThingHandler(Thing thing, MqttChannelTypeProvider channelTypeProvider, int deviceTimeout,
-            int subscribeTimeout, int attributeReceiveTimeout) {
+    public HomieThingHandler(Thing thing, MqttChannelTypeProvider channelTypeProvider,
+            MqttChannelStateDescriptionProvider stateDescriptionProvider, ChannelTypeRegistry channelTypeRegistry,
+            int deviceTimeout, int subscribeTimeout, int attributeReceiveTimeout) {
         super(thing, deviceTimeout);
         this.channelTypeProvider = channelTypeProvider;
+        this.stateDescriptionProvider = stateDescriptionProvider;
+        this.channelTypeRegistry = channelTypeRegistry;
         this.deviceTimeout = deviceTimeout;
         this.subscribeTimeout = subscribeTimeout;
         this.attributeReceiveTimeout = attributeReceiveTimeout;
@@ -105,6 +120,17 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
             return;
         }
         device.initialize(config.basetopic, config.deviceid, thing.getChannels());
+
+        updateThingType();
+        if (getThing().getThingTypeUID().equals(MqttBindingConstants.HOMIE300_MQTT_THING)) {
+            logger.debug("Migrating Homie thing {} from generic type to dynamic type {}", getThing().getUID(),
+                    device.thingTypeUID);
+            changeThingType(device.thingTypeUID, getConfig());
+            return;
+        } else {
+            updateChannels();
+        }
+
         super.initialize();
     }
 
@@ -143,6 +169,7 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
         }
         delayedProcessing.join();
         device.stop();
+        channelTypeProvider.removeThingType(device.thingTypeUID);
         super.stop();
     }
 
@@ -195,7 +222,7 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
 
     @Override
     public void propertyRemoved(Property property) {
-        channelTypeProvider.removeChannelType(property.channelTypeUID);
+        stateDescriptionProvider.remove(property.getChannelUID());
         delayedProcessing.accept(property);
     }
 
@@ -207,7 +234,16 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
 
     @Override
     public void propertyAddedOrChanged(Property property) {
-        channelTypeProvider.setChannelType(property.channelTypeUID, property.getType());
+        ChannelUID channelUID = property.getChannelUID();
+        stateDescriptionProvider.remove(channelUID);
+        StateDescription stateDescription = property.getStateDescription();
+        if (stateDescription != null) {
+            stateDescriptionProvider.setDescription(channelUID, stateDescription);
+        }
+        CommandDescription commandDescription = property.getCommandDescription();
+        if (commandDescription != null) {
+            stateDescriptionProvider.setDescription(channelUID, commandDescription);
+        }
         delayedProcessing.accept(property);
     }
 
@@ -220,10 +256,9 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
         if (!device.isInitialized()) {
             return;
         }
-        List<Channel> channels = device.nodes().stream().flatMap(n -> n.properties.stream()).map(Property::getChannel)
-                .collect(Collectors.toList());
-        updateThing(editThing().withChannels(channels).build());
         updateProperty(MqttBindingConstants.HOMIE_PROPERTY_VERSION, device.attributes.homie);
+        updateThingType();
+        updateChannels();
         final MqttBrokerConnection connection = this.connection;
         if (connection != null) {
             device.startChannels(connection, scheduler, attributeReceiveTimeout, this).thenRun(() -> {
@@ -249,4 +284,31 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
     protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
         // not used here
     }
+
+    private void updateThingType() {
+        // Make sure any dynamic channel types exist (i.e. ones created for a number channel with a specific dimension)
+        device.nodes.stream().flatMap(n -> n.properties.stream()).map(Property::getChannelType).filter(Objects::nonNull)
+                .forEach(ct -> channelTypeProvider.setChannelType(ct.getUID(), ct));
+
+        // if this is a dynamic type, then we update the type
+        ThingTypeUID typeID = device.thingTypeUID;
+        if (!MqttBindingConstants.HOMIE300_MQTT_THING.equals(typeID)) {
+            device.nodes.stream()
+                    .forEach(n -> channelTypeProvider.setChannelGroupType(n.channelGroupTypeUID, n.type()));
+
+            List<ChannelGroupDefinition> groupDefs = device.nodes().stream().map(Node::getChannelGroupDefinition)
+                    .collect(Collectors.toList());
+            var builder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMIE300_MQTT_THING)
+                    .withChannelGroupDefinitions(groupDefs);
+            ThingType thingType = builder.build();
+
+            channelTypeProvider.setThingType(typeID, thingType);
+        }
+    }
+
+    private void updateChannels() {
+        List<Channel> channels = device.nodes().stream().flatMap(n -> n.properties.stream())
+                .map(p -> p.getChannel(channelTypeRegistry)).collect(Collectors.toList());
+        updateThing(editThing().withChannels(channels).build());
+    }
 }
index a1aab167f599d4a0fd372d7e6820642f60883af5..47dd8a524938ccb4d0220cfb1d6f04ec2b670461 100644 (file)
@@ -22,13 +22,14 @@ import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.mqtt.generic.ChannelConfig;
 import org.openhab.binding.mqtt.generic.mapping.AbstractMqttAttributeClass;
 import org.openhab.binding.mqtt.generic.tools.ChildMap;
+import org.openhab.binding.mqtt.homie.generic.internal.MqttBindingConstants;
 import org.openhab.binding.mqtt.homie.internal.handler.HomieThingHandler;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.util.UIDUtils;
 import org.slf4j.Logger;
@@ -61,6 +62,7 @@ public class Device implements AbstractMqttAttributeClass.AttributeChanged {
 
     // The corresponding ThingUID and callback of this device object
     public final ThingUID thingUID;
+    public ThingTypeUID thingTypeUID = MqttBindingConstants.HOMIE300_MQTT_THING;
     private final DeviceCallback callback;
 
     // Unique identifier and topic
@@ -76,10 +78,7 @@ public class Device implements AbstractMqttAttributeClass.AttributeChanged {
      * @param attributes The device attributes object
      */
     public Device(ThingUID thingUID, DeviceCallback callback, DeviceAttributes attributes) {
-        this.thingUID = thingUID;
-        this.callback = callback;
-        this.attributes = attributes;
-        this.nodes = new ChildMap<>();
+        this(thingUID, callback, attributes, new ChildMap<>());
     }
 
     /**
@@ -204,13 +203,11 @@ public class Device implements AbstractMqttAttributeClass.AttributeChanged {
     public void initialize(String baseTopic, String deviceID, List<Channel> channels) {
         this.topic = baseTopic + "/" + deviceID;
         this.deviceID = deviceID;
+        this.thingTypeUID = new ThingTypeUID(MqttBindingConstants.BINDING_ID,
+                MqttBindingConstants.HOMIE300_MQTT_THING.getId() + "_" + UIDUtils.encode(topic));
+
         nodes.clear();
         for (Channel channel : channels) {
-            final ChannelConfig channelConfig = channel.getConfiguration().as(ChannelConfig.class);
-            if (!channelConfig.commandTopic.isEmpty() && !channelConfig.retained) {
-                logger.warn("Channel {} in device {} is missing the 'retained' flag. Check your configuration.",
-                        channel.getUID(), deviceID);
-            }
             final String channelGroupId = channel.getUID().getGroupId();
             if (channelGroupId == null) {
                 continue;
@@ -223,9 +220,43 @@ public class Device implements AbstractMqttAttributeClass.AttributeChanged {
                 node.nodeRestoredFromConfig();
                 nodes.put(nodeID, node);
             }
-            // Restores the properties attribute object via the channels configuration.
-            Property property = node.createProperty(propertyID,
-                    channel.getConfiguration().as(PropertyAttributes.class));
+            // Restores the property's attributes object via the channel's config and properties.
+            // (config is only for backwards compatibility before properties were used)
+            var channelConfig = channel.getConfiguration();
+            PropertyAttributes attributes = channelConfig.as(PropertyAttributes.class);
+
+            var channelProperties = channel.getProperties();
+            String channelId = channel.getChannelTypeUID().getId();
+
+            String datatype = channelProperties.get(MqttBindingConstants.CHANNEL_PROPERTY_DATATYPE);
+            if (datatype != null) {
+                attributes.datatype = PropertyAttributes.DataTypeEnum.valueOf(datatype);
+            } else if (channelId.startsWith(MqttBindingConstants.CHANNEL_TYPE_HOMIE_PREFIX)) {
+                attributes.datatype = PropertyAttributes.DataTypeEnum
+                        .valueOf(channelId.substring(MqttBindingConstants.CHANNEL_TYPE_HOMIE_PREFIX.length()) + "_");
+            }
+            String label = channel.getLabel();
+            if (label != null) {
+                attributes.name = label;
+            }
+            String settable = channelProperties.get(MqttBindingConstants.CHANNEL_PROPERTY_SETTABLE);
+            if (settable != null) {
+                attributes.settable = Boolean.valueOf(settable);
+            }
+            String retained = channelProperties.get(MqttBindingConstants.CHANNEL_PROPERTY_RETAINED);
+            if (retained != null) {
+                attributes.retained = Boolean.valueOf(retained);
+            }
+            String unit = channelProperties.get(MqttBindingConstants.CHANNEL_PROPERTY_UNIT);
+            if (unit != null) {
+                attributes.unit = unit;
+            }
+            String format = channelProperties.get(MqttBindingConstants.CHANNEL_PROPERTY_FORMAT);
+            if (format != null) {
+                attributes.format = format;
+            }
+
+            Property property = node.createProperty(propertyID, attributes);
             property.attributesReceived();
 
             node.properties.put(propertyID, property);
index de5bc075e4f7a3262d119aa280f4465c2473dfcd..d9d9592250e4a679c0e8b3bcc29d150ee621ade2 100644 (file)
@@ -29,7 +29,7 @@ import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.thing.ChannelGroupUID;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.type.ChannelDefinition;
-import org.openhab.core.thing.type.ChannelDefinitionBuilder;
+import org.openhab.core.thing.type.ChannelGroupDefinition;
 import org.openhab.core.thing.type.ChannelGroupType;
 import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
 import org.openhab.core.thing.type.ChannelGroupTypeUID;
@@ -101,6 +101,7 @@ public class Node implements AbstractMqttAttributeClass.AttributeChanged {
 
     public void nodeRestoredFromConfig() {
         initialized = true;
+        attributes.name = nodeID;
     }
 
     /**
@@ -118,12 +119,15 @@ public class Node implements AbstractMqttAttributeClass.AttributeChanged {
      */
     public ChannelGroupType type() {
         final List<ChannelDefinition> channelDefinitions = properties.stream()
-                .map(c -> new ChannelDefinitionBuilder(c.propertyID, c.channelTypeUID).build())
-                .collect(Collectors.toList());
+                .map(p -> Objects.requireNonNull(p.getChannelDefinition())).collect(Collectors.toList());
         return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, attributes.name)
                 .withChannelDefinitions(channelDefinitions).build();
     }
 
+    public ChannelGroupDefinition getChannelGroupDefinition() {
+        return new ChannelGroupDefinition(channelGroupUID.getId(), channelGroupTypeUID, attributes.name, null);
+    }
+
     /**
      * Return the channel group UID.
      */
index cb1d398f5ab2fe50a9988cc69d5f2bc5af0c9fbb..37937074758117def5c3dd5834c4d45b15449405 100644 (file)
@@ -14,14 +14,18 @@ package org.openhab.binding.mqtt.homie.internal.homie300;
 
 import java.math.BigDecimal;
 import java.math.MathContext;
-import java.net.URI;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import javax.measure.Unit;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.ChannelConfigBuilder;
@@ -37,16 +41,21 @@ import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.generic.values.Value;
 import org.openhab.binding.mqtt.homie.generic.internal.MqttBindingConstants;
 import org.openhab.binding.mqtt.homie.internal.homie300.PropertyAttributes.DataTypeEnum;
-import org.openhab.core.config.core.Configuration;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.CommonTriggerEvents;
 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
 import org.openhab.core.thing.binding.builder.ChannelBuilder;
 import org.openhab.core.thing.type.AutoUpdatePolicy;
+import org.openhab.core.thing.type.ChannelDefinition;
+import org.openhab.core.thing.type.ChannelDefinitionBuilder;
 import org.openhab.core.thing.type.ChannelType;
 import org.openhab.core.thing.type.ChannelTypeBuilder;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
 import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.CommandDescription;
+import org.openhab.core.types.StateDescription;
 import org.openhab.core.types.util.UnitUtils;
 import org.openhab.core.util.UIDUtils;
 import org.slf4j.Logger;
@@ -67,9 +76,11 @@ public class Property implements AttributeChanged {
     // Runtime state
     protected @Nullable ChannelState channelState;
     public final ChannelUID channelUID;
-    public final ChannelTypeUID channelTypeUID;
-    private ChannelType type;
-    private Channel channel;
+    public ChannelTypeUID channelTypeUID;
+    private @Nullable ChannelType channelType = null;
+    private @Nullable ChannelDefinition channelDefinition = null;
+    private @Nullable StateDescription stateDescription = null;
+    private @Nullable CommandDescription commandDescription = null;
     private final String topic;
     private final DeviceCallback callback;
     protected boolean initialized = false;
@@ -89,9 +100,8 @@ public class Property implements AttributeChanged {
         this.parentNode = node;
         this.propertyID = propertyID;
         channelUID = new ChannelUID(node.uid(), UIDUtils.encode(propertyID));
-        channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, UIDUtils.encode(this.topic));
-        type = ChannelTypeBuilder.trigger(channelTypeUID, "dummy").build(); // Dummy value
-        channel = ChannelBuilder.create(channelUID, "dummy").build();// Dummy value
+        channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
+                MqttBindingConstants.CHANNEL_TYPE_HOMIE_STRING);
     }
 
     /**
@@ -128,52 +138,11 @@ public class Property implements AttributeChanged {
      * ChannelState are determined.
      */
     public void attributesReceived() {
-        createChannelFromAttribute();
+        createChannelTypeFromAttributes();
         callback.propertyAddedOrChanged(this);
     }
 
-    /**
-     * Creates the ChannelType of the Homie property.
-     *
-     * @param attributes Attributes of the property.
-     * @param channelState ChannelState of the property.
-     *
-     * @return Returns the ChannelType to be used to build the Channel.
-     */
-    private ChannelType createChannelType(PropertyAttributes attributes, ChannelState channelState) {
-        // Retained property -> State channel
-        if (attributes.retained) {
-            return ChannelTypeBuilder.state(channelTypeUID, attributes.name, channelState.getItemType())
-                    .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HOMIE_CHANNEL))
-                    .withStateDescriptionFragment(
-                            channelState.getCache().createStateDescription(!attributes.settable).build())
-                    .build();
-        } else {
-            // Non-retained and settable property -> State channel
-            if (attributes.settable) {
-                return ChannelTypeBuilder.state(channelTypeUID, attributes.name, channelState.getItemType())
-                        .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HOMIE_CHANNEL))
-                        .withCommandDescription(channelState.getCache().createCommandDescription().build())
-                        .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
-            }
-            // Non-retained and non settable property -> Trigger channel
-            if (attributes.datatype.equals(DataTypeEnum.enum_)) {
-                if (attributes.format.contains("PRESSED") && attributes.format.contains("RELEASED")) {
-                    return DefaultSystemChannelTypeProvider.SYSTEM_RAWBUTTON;
-                } else if (attributes.format.contains("SHORT_PRESSED") && attributes.format.contains("LONG_PRESSED")
-                        && attributes.format.contains("DOUBLE_PRESSED")) {
-                    return DefaultSystemChannelTypeProvider.SYSTEM_BUTTON;
-                } else if (attributes.format.contains("DIR1_PRESSED") && attributes.format.contains("DIR1_RELEASED")
-                        && attributes.format.contains("DIR2_PRESSED") && attributes.format.contains("DIR2_RELEASED")) {
-                    return DefaultSystemChannelTypeProvider.SYSTEM_RAWROCKER;
-                }
-            }
-            return ChannelTypeBuilder.trigger(channelTypeUID, attributes.name)
-                    .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HOMIE_CHANNEL)).build();
-        }
-    }
-
-    public void createChannelFromAttribute() {
+    private void createChannelTypeFromAttributes() {
         final String commandTopic = topic + "/set";
         final String stateTopic = topic;
 
@@ -184,6 +153,12 @@ public class Property implements AttributeChanged {
             attributes.name = propertyID;
         }
 
+        Unit<?> unit = UnitUtils.parseUnit(attributes.unit);
+        String dimension = null;
+        if (unit != null) {
+            dimension = UnitUtils.getDimensionName(unit);
+        }
+
         switch (attributes.datatype) {
             case boolean_:
                 value = new OnOffValue("true", "false");
@@ -218,7 +193,7 @@ public class Property implements AttributeChanged {
                 if (attributes.unit.contains("%") && attributes.settable) {
                     value = new PercentageValue(min, max, step, null, null);
                 } else {
-                    value = new NumberValue(min, max, step, UnitUtils.parseUnit(attributes.unit));
+                    value = new NumberValue(min, max, step, unit);
                 }
                 break;
             case datetime_:
@@ -245,12 +220,86 @@ public class Property implements AttributeChanged {
         final ChannelState channelState = new ChannelState(b.build(), channelUID, value, callback);
         this.channelState = channelState;
 
-        final ChannelType type = createChannelType(attributes, channelState);
-        this.type = type;
+        Map<String, String> channelProperties = new HashMap<>();
+
+        if (attributes.settable) {
+            channelProperties.put(MqttBindingConstants.CHANNEL_PROPERTY_SETTABLE,
+                    Boolean.toString(attributes.settable));
+        }
+        if (!attributes.retained) {
+            channelProperties.put(MqttBindingConstants.CHANNEL_PROPERTY_RETAINED,
+                    Boolean.toString(attributes.retained));
+        }
+
+        if (!attributes.format.isEmpty()) {
+            channelProperties.put(MqttBindingConstants.CHANNEL_PROPERTY_FORMAT, attributes.format);
+        }
+
+        this.channelType = null;
+        if (!attributes.retained && !attributes.settable) {
+            channelProperties.put(MqttBindingConstants.CHANNEL_PROPERTY_DATATYPE, attributes.datatype.toString());
+            if (attributes.datatype.equals(DataTypeEnum.enum_)) {
+                if (attributes.format.contains(CommonTriggerEvents.PRESSED)
+                        && attributes.format.contains(CommonTriggerEvents.RELEASED)) {
+                    this.channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_RAWBUTTON;
+                } else if (attributes.format.contains(CommonTriggerEvents.SHORT_PRESSED)
+                        && attributes.format.contains(CommonTriggerEvents.LONG_PRESSED)
+                        && attributes.format.contains(CommonTriggerEvents.DOUBLE_PRESSED)) {
+                    this.channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_BUTTON;
+                } else if (attributes.format.contains(CommonTriggerEvents.DIR1_PRESSED)
+                        && attributes.format.contains(CommonTriggerEvents.DIR1_RELEASED)
+                        && attributes.format.contains(CommonTriggerEvents.DIR2_PRESSED)
+                        && attributes.format.contains(CommonTriggerEvents.DIR2_RELEASED)) {
+                    this.channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_RAWROCKER;
+                } else {
+                    this.channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_TRIGGER;
+                }
+            } else {
+                this.channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_TRIGGER;
+            }
+        } else {
+            if (!attributes.unit.isEmpty()) {
+                channelProperties.put(MqttBindingConstants.CHANNEL_PROPERTY_UNIT, attributes.unit);
+            }
+
+            String channelTypeId;
+
+            if (attributes.datatype.equals(DataTypeEnum.unknown)) {
+                channelTypeId = MqttBindingConstants.CHANNEL_TYPE_HOMIE_STRING;
+            } else if (dimension != null) {
+                channelTypeId = MqttBindingConstants.CHANNEL_TYPE_HOMIE_PREFIX + "number-" + dimension.toLowerCase();
+                channelProperties.put(MqttBindingConstants.CHANNEL_PROPERTY_DATATYPE, attributes.datatype.toString());
+            } else {
+                channelTypeId = MqttBindingConstants.CHANNEL_TYPE_HOMIE_PREFIX + attributes.datatype.toString();
+                channelTypeId = channelTypeId.substring(0, channelTypeId.length() - 1);
+            }
+            this.channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, channelTypeId);
+            if (dimension != null) {
+                this.channelType = ChannelTypeBuilder.state(channelTypeUID, dimension + " Value", "Number:" + dimension)
+                        .build();
+            }
+
+            if (attributes.retained) {
+                this.commandDescription = null;
+                this.stateDescription = channelState.getCache().createStateDescription(!attributes.settable).build()
+                        .toStateDescription();
+            } else if (attributes.settable) {
+                this.commandDescription = channelState.getCache().createCommandDescription().build();
+                this.stateDescription = null;
+            } else {
+                this.commandDescription = null;
+                this.stateDescription = null;
+            }
+        }
+
+        var builder = new ChannelDefinitionBuilder(UIDUtils.encode(propertyID), channelTypeUID)
+                .withLabel(attributes.name).withProperties(channelProperties);
+
+        if (attributes.settable && !attributes.retained) {
+            builder.withAutoUpdatePolicy(AutoUpdatePolicy.VETO);
+        }
 
-        this.channel = ChannelBuilder.create(channelUID, type.getItemType()).withType(type.getUID())
-                .withKind(type.getKind()).withLabel(attributes.name)
-                .withConfiguration(new Configuration(attributes.asMap())).build();
+        this.channelDefinition = builder.build();
     }
 
     /**
@@ -266,8 +315,14 @@ public class Property implements AttributeChanged {
         return attributes.unsubscribe();
     }
 
+    public ChannelUID getChannelUID() {
+        return channelUID;
+    }
+
     /**
-     * @return Returns the channelState. You should have called
+     * @return Returns the channelState.
+     * 
+     *         You should have called
      *         {@link Property#subscribe(MqttBrokerConnection, ScheduledExecutorService, int)}
      *         and waited for the future to complete before calling this Getter.
      */
@@ -275,6 +330,50 @@ public class Property implements AttributeChanged {
         return channelState;
     }
 
+    /**
+     * @return Returns the channelType, if a dynamic one is necessary.
+     *
+     *         You should have called
+     *         {@link Property#subscribe(AbstractMqttAttributeClass, int)}
+     *         and waited for the future to complete before calling this Getter.
+     */
+    public @Nullable ChannelType getChannelType() {
+        return channelType;
+    }
+
+    /**
+     * @return Returns the ChannelDefinition.
+     * 
+     *         You should have called
+     *         {@link Property#subscribe(AbstractMqttAttributeClass, int)}
+     *         and waited for the future to complete before calling this Getter.
+     */
+    public @Nullable ChannelDefinition getChannelDefinition() {
+        return channelDefinition;
+    }
+
+    /**
+     * @return Returns the StateDescription.
+     *
+     *         You should have called
+     *         {@link Property#subscribe(AbstractMqttAttributeClass, int)}
+     *         and waited for the future to complete before calling this Getter.
+     */
+    public @Nullable StateDescription getStateDescription() {
+        return stateDescription;
+    }
+
+    /**
+     * @return Returns the CommandDescription.
+     *
+     *         You should have called
+     *         {@link Property#subscribe(AbstractMqttAttributeClass, int)}
+     *         and waited for the future to complete before calling this Getter.
+     */
+    public @Nullable CommandDescription getCommandDescription() {
+        return commandDescription;
+    }
+
     /**
      * Subscribes to the state topic on the given connection and informs about updates on the given listener.
      *
@@ -297,19 +396,15 @@ public class Property implements AttributeChanged {
     }
 
     /**
-     * @return Returns the channel type of this property.
-     *         The type is a dummy only if {@link #channelState} has not been set yet.
+     * @return Create a channel for this property.
      */
-    public ChannelType getType() {
-        return type;
-    }
+    public Channel getChannel(ChannelTypeRegistry channelTypeRegistry) {
+        ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
 
-    /**
-     * @return Returns the channel of this property.
-     *         The channel is a dummy only if {@link #channelState} has not been set yet.
-     */
-    public Channel getChannel() {
-        return channel;
+        return ChannelBuilder.create(channelUID, channelType.getItemType()).withType(channelTypeUID)
+                .withKind(channelType.getKind()).withLabel(Objects.requireNonNull(channelDefinition.getLabel()))
+                .withProperties(channelDefinition.getProperties())
+                .withAutoUpdatePolicy(channelDefinition.getAutoUpdatePolicy()).build();
     }
 
     @Override
diff --git a/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/config/homie-channel-config.xml b/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/config/homie-channel-config.xml
deleted file mode 100644 (file)
index 7bb8d25..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<config-description:config-descriptions
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
-
-       <config-description uri="channel-type:mqtt:homie-channel">
-               <parameter name="unit" type="text">
-                       <label>Unit</label>
-                       <description>The channels unit</description>
-                       <default></default>
-               </parameter>
-               <parameter name="name" type="text">
-                       <label>Name</label>
-                       <description>The channel name</description>
-                       <default></default>
-               </parameter>
-               <parameter name="settable" type="text">
-                       <label>Settable</label>
-                       <description>Is this channel writable?</description>
-                       <default>true</default>
-               </parameter>
-               <parameter name="retained" type="text">
-                       <label>Retained</label>
-                       <description>If set to false, the resulting channel will be a trigger channel (stateless), useful for non-permanent
-                               events. This flag corresponds to the retained option for MQTT publish.</description>
-                       <default>true</default>
-               </parameter>
-               <parameter name="format" type="text">
-                       <label>Format</label>
-                       <description>The output format.</description>
-                       <default></default>
-               </parameter>
-               <parameter name="datatype" type="text">
-                       <label>Data Type</label>
-                       <description>The data type of this channel.</description>
-                       <default>unknown</default>
-                       <options>
-                               <option value="integer_">Integer</option>
-                               <option value="float_">Float</option>
-                               <option value="boolean_">Boolean</option>
-                               <option value="string_">String</option>
-                               <option value="enum_">Enumeration</option>
-                               <option value="color_">Colour</option>
-                               <option value="datetime_">DateTime</option>
-                       </options>
-               </parameter>
-       </config-description>
-</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/thing/homie-channels.xml b/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/thing/homie-channels.xml
new file mode 100644 (file)
index 0000000..8bee833
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="mqtt"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-type id="homie-boolean">
+               <item-type>Switch</item-type>
+               <label>Boolean</label>
+       </channel-type>
+
+       <channel-type id="homie-color">
+               <item-type>Color</item-type>
+               <label>Color Value (Red,Green,Blue)</label>
+       </channel-type>
+
+       <channel-type id="homie-datetime">
+               <item-type>DateTime</item-type>
+               <label>Date/Time Value</label>
+               <description>Current date and/or time</description>
+       </channel-type>
+
+       <channel-type id="homie-enum">
+               <item-type>String</item-type>
+               <label>Enum Value</label>
+       </channel-type>
+
+       <channel-type id="homie-integer">
+               <item-type>Number</item-type>
+               <label>Integer Value</label>
+       </channel-type>
+
+       <channel-type id="homie-float">
+               <item-type>Number</item-type>
+               <label>Float Value</label>
+       </channel-type>
+
+       <channel-type id="homie-string">
+               <item-type>String</item-type>
+               <label>Text Value</label>
+       </channel-type>
+
+       <channel-type id="homie-trigger">
+               <kind>trigger</kind>
+               <label>Trigger</label>
+       </channel-type>
+</thing:thing-descriptions>
index ac3abd24a85e3ab71973ccbcf0faf5270c358a0e..09d09c6a2b2d2a0a82679aef52b43ccb8da4fb83 100644 (file)
@@ -41,6 +41,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 import org.openhab.binding.mqtt.generic.ChannelState;
+import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
 import org.openhab.binding.mqtt.generic.mapping.AbstractMqttAttributeClass;
 import org.openhab.binding.mqtt.generic.mapping.SubscribeFieldToMQTTtopic;
@@ -66,9 +67,13 @@ import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.binding.ThingHandlerCallback;
 import org.openhab.core.thing.binding.builder.ThingBuilder;
 import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.type.ChannelType;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
+import org.openhab.core.thing.type.ThingTypeBuilder;
 import org.openhab.core.thing.type.ThingTypeRegistry;
 import org.openhab.core.types.RefreshType;
 
@@ -88,11 +93,14 @@ public class HomieThingHandlerTests {
     private @Mock @NonNullByDefault({}) ScheduledExecutorService schedulerMock;
     private @Mock @NonNullByDefault({}) ScheduledFuture<?> scheduledFutureMock;
     private @Mock @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistryMock;
+    private @Mock @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistryMock;
+    private @Mock @NonNullByDefault({}) ChannelType channelTypeMock;
 
     private @NonNullByDefault({}) Thing thing;
     private @NonNullByDefault({}) HomieThingHandler thingHandler;
 
-    private final MqttChannelTypeProvider channelTypeProvider = new MqttChannelTypeProvider(thingTypeRegistryMock);
+    private final MqttChannelTypeProvider channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistryMock));
+    private final MqttChannelStateDescriptionProvider stateDescriptionProvider = new MqttChannelStateDescriptionProvider();
 
     private final String deviceID = ThingChannelConstants.TEST_HOMIE_THING.getId();
     private final String deviceTopic = "homie/" + deviceID;
@@ -108,8 +116,11 @@ public class HomieThingHandlerTests {
         config.put("basetopic", "homie");
         config.put("deviceid", deviceID);
 
-        thing = ThingBuilder.create(MqttBindingConstants.HOMIE300_MQTT_THING, TEST_HOMIE_THING.getId())
-                .withConfiguration(config).build();
+        ThingTypeUID type = new ThingTypeUID(MqttBindingConstants.BINDING_ID,
+                MqttBindingConstants.HOMIE300_MQTT_THING.getId() + "_dynamic");
+        doAnswer(i -> ThingTypeBuilder.instance(type, "Homie Thing")).when(channelTypeProvider).derive(any(), any());
+
+        thing = ThingBuilder.create(type, TEST_HOMIE_THING.getId()).withConfiguration(config).build();
         thing.setStatusInfo(thingStatus);
 
         // Return the mocked connection object if the bridge handler is asked for it
@@ -124,7 +135,8 @@ public class HomieThingHandlerTests {
         doReturn(false).when(scheduledFutureMock).isDone();
         doReturn(scheduledFutureMock).when(schedulerMock).schedule(any(Runnable.class), anyLong(), any(TimeUnit.class));
 
-        final HomieThingHandler handler = new HomieThingHandler(thing, channelTypeProvider, 1000, 30, 5);
+        final HomieThingHandler handler = new HomieThingHandler(thing, channelTypeProvider, stateDescriptionProvider,
+                channelTypeRegistryMock, 1000, 30, 5);
         thingHandler = spy(handler);
         thingHandler.setCallback(callbackMock);
         final Device device = new Device(thing.getUID(), thingHandler, spy(new DeviceAttributes()),
@@ -314,6 +326,10 @@ public class HomieThingHandlerTests {
         thingHandler.device.initialize("homie", "device", new ArrayList<>());
         ThingHandlerHelper.setConnection(thingHandler, connectionMock);
 
+        doReturn("String").when(channelTypeMock).getItemType();
+        doReturn(ChannelKind.STATE).when(channelTypeMock).getKind();
+        doReturn(channelTypeMock).when(channelTypeRegistryMock).getChannelType(any());
+
         // Create mocked homie device tree with one node and one property
         doAnswer(this::createSubscriberAnswer).when(thingHandler.device.attributes).createSubscriber(any(), any(),
                 any(), anyBoolean());
index 1f418ed28d219ac3911015cd67a215dd1921c1a8..5e325cdfa053603c6e3328ff33fee1ed2f39addf 100644 (file)
@@ -281,8 +281,8 @@ public class HomieImplementationTest extends MqttOSGiTest {
         assertThat(property.attributes.format, is("-100:100"));
         verify(property).attributesReceived();
         assertNotNull(property.getChannelState());
-        assertThat(property.getType().getState().getMinimum().intValue(), is(-100));
-        assertThat(property.getType().getState().getMaximum().intValue(), is(100));
+        assertThat(property.getStateDescription().getMinimum().intValue(), is(-100));
+        assertThat(property.getStateDescription().getMaximum().intValue(), is(100));
 
         // Check property and property attributes
         Property propertyBell = node.properties.get("doorbell");