]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt] Simplify homeassistant thing types, and use AbstractStorageBasedTypeProvider...
authorCody Cutrer <cody@cutrer.us>
Mon, 12 Aug 2024 19:23:24 +0000 (13:23 -0600)
committerGitHub <noreply@github.com>
Mon, 12 Aug 2024 19:23:24 +0000 (21:23 +0200)
* [mqtt.homeassistant] don't dynamically generate channel types

Signed-off-by: Cody Cutrer <cody@cutrer.us>
46 files changed:
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/MqttChannelTypeProvider.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/tools/ChildMap.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttBindingConstants.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannelType.java [new file with mode: 0644]
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractRawSchemaLight.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Update.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml
bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties
bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/thing/homeassistant-channels.xml [new file with mode: 0644]
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.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/Node.java
bundles/org.openhab.binding.mqtt.homie/src/test/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandlerTests.java
itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/DiscoverComponentsTest.java
itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java

index 1991d003f7a684c928d4da2cee99c0e73e77f250..322497df4b7439682e4fa7e9b4c10c8660790ae0 100644 (file)
@@ -14,22 +14,19 @@ package org.openhab.binding.mqtt.generic;
 
 import java.net.URI;
 import java.util.Collection;
-import java.util.Locale;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.internal.MqttThingHandlerFactory;
+import org.openhab.core.storage.StorageService;
 import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.AbstractStorageBasedTypeProvider;
 import org.openhab.core.thing.binding.ThingTypeProvider;
 import org.openhab.core.thing.type.ChannelGroupType;
 import org.openhab.core.thing.type.ChannelGroupTypeProvider;
 import org.openhab.core.thing.type.ChannelGroupTypeUID;
-import org.openhab.core.thing.type.ChannelType;
 import org.openhab.core.thing.type.ChannelTypeProvider;
-import org.openhab.core.thing.type.ChannelTypeUID;
 import org.openhab.core.thing.type.ThingType;
 import org.openhab.core.thing.type.ThingTypeBuilder;
 import org.openhab.core.thing.type.ThingTypeRegistry;
@@ -45,89 +42,24 @@ import org.osgi.service.component.annotations.Reference;
  * This provider is started on-demand only, as soon as {@link MqttThingHandlerFactory} or an extension requires it.
  *
  * @author David Graeff - Initial contribution
+ * @author Cody Cutrer - Use AbstractStorageBasedTypeProvider
  *
  */
 @NonNullByDefault
 @Component(immediate = false, service = { ThingTypeProvider.class, ChannelTypeProvider.class,
         ChannelGroupTypeProvider.class, MqttChannelTypeProvider.class })
-public class MqttChannelTypeProvider implements ThingTypeProvider, ChannelGroupTypeProvider, ChannelTypeProvider {
-    private final ThingTypeRegistry typeRegistry;
-
-    private final Map<ChannelTypeUID, ChannelType> types = new ConcurrentHashMap<>();
-    private final Map<ChannelGroupTypeUID, ChannelGroupType> groups = new ConcurrentHashMap<>();
-    private final Map<ThingTypeUID, ThingType> things = new ConcurrentHashMap<>();
+public class MqttChannelTypeProvider extends AbstractStorageBasedTypeProvider {
+    private final ThingTypeRegistry thingTypeRegistry;
 
     @Activate
-    public MqttChannelTypeProvider(@Reference ThingTypeRegistry typeRegistry) {
-        super();
-        this.typeRegistry = typeRegistry;
-    }
-
-    @Override
-    public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
-        return types.values();
-    }
-
-    @Override
-    public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
-        return types.get(channelTypeUID);
-    }
-
-    @Override
-    public @Nullable ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID,
-            @Nullable Locale locale) {
-        return groups.get(channelGroupTypeUID);
-    }
-
-    @Override
-    public Collection<ChannelGroupType> getChannelGroupTypes(@Nullable Locale locale) {
-        return groups.values();
-    }
-
-    @Override
-    public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
-        return things.values();
-    }
-
-    public Set<ThingTypeUID> getThingTypeUIDs() {
-        return things.keySet();
-    }
-
-    @Override
-    public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) {
-        return things.get(thingTypeUID);
-    }
-
-    public void removeChannelType(ChannelTypeUID uid) {
-        types.remove(uid);
-    }
-
-    public void removeChannelGroupType(ChannelGroupTypeUID uid) {
-        groups.remove(uid);
-    }
-
-    public void setChannelGroupType(ChannelGroupTypeUID uid, ChannelGroupType type) {
-        groups.put(uid, type);
-    }
-
-    public void setChannelType(ChannelTypeUID uid, ChannelType type) {
-        types.put(uid, type);
-    }
-
-    public void removeThingType(ThingTypeUID uid) {
-        things.remove(uid);
-    }
-
-    public void setThingType(ThingTypeUID uid, ThingType type) {
-        things.put(uid, type);
-    }
-
-    public void setThingTypeIfAbsent(ThingTypeUID uid, ThingType type) {
-        things.putIfAbsent(uid, type);
+    public MqttChannelTypeProvider(@Reference ThingTypeRegistry thingTypeRegistry,
+            @Reference StorageService storageService) {
+        super(storageService);
+        this.thingTypeRegistry = thingTypeRegistry;
     }
 
     public ThingTypeBuilder derive(ThingTypeUID newTypeId, ThingTypeUID baseTypeId) {
-        ThingType baseType = typeRegistry.getThingType(baseTypeId);
+        ThingType baseType = thingTypeRegistry.getThingType(baseTypeId);
 
         ThingTypeBuilder result = ThingTypeBuilder.instance(newTypeId, baseType.getLabel())
                 .withChannelGroupDefinitions(baseType.getChannelGroupDefinitions())
@@ -155,4 +87,25 @@ public class MqttChannelTypeProvider implements ThingTypeProvider, ChannelGroupT
 
         return result;
     }
+
+    public void updateChannelGroupTypesForPrefix(String prefix, Collection<ChannelGroupType> types) {
+        Collection<ChannelGroupType> oldCgts = channelGroupTypesForPrefix(prefix);
+
+        Set<ChannelGroupTypeUID> oldUids = oldCgts.stream().map(ChannelGroupType::getUID).collect(Collectors.toSet());
+        Collection<ChannelGroupTypeUID> uids = types.stream().map(ChannelGroupType::getUID).toList();
+
+        oldUids.removeAll(uids);
+        // oldUids now contains only UIDs that no longer exist. so remove them
+        oldUids.forEach(this::removeChannelGroupType);
+        types.forEach(this::putChannelGroupType);
+    }
+
+    public void removeChannelGroupTypesForPrefix(String prefix) {
+        channelGroupTypesForPrefix(prefix).forEach(cgt -> removeChannelGroupType(cgt.getUID()));
+    }
+
+    private Collection<ChannelGroupType> channelGroupTypesForPrefix(String prefix) {
+        return getChannelGroupTypes(null).stream().filter(cgt -> cgt.getUID().getId().startsWith(prefix + "_"))
+                .toList();
+    }
 }
index 03d095059ee47dbf8f9cf142c58f6d351d93d190..952da6e39fea6c7dbed62846c5ed1bcea1bda790 100644 (file)
  */
 package org.openhab.binding.mqtt.generic.tools;
 
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
@@ -64,6 +67,24 @@ public class ChildMap<T> {
         return map.values().stream();
     }
 
+    /**
+     * Streams the objects in this map in the order of the given keys.
+     * 
+     * Extraneous keys are ignored, and missing keys are included at the end in unspecified order.
+     * 
+     * @param order The keys in the order they should be streamed
+     */
+    public Stream<T> stream(Collection<String> order) {
+        // need to make a copy to avoid editing `map` itself
+        Set<String> missingKeys = new HashSet<>(map.keySet());
+        missingKeys.removeAll(order);
+        Stream<T> result = order.stream().map(k -> map.get(k)).filter(Objects::nonNull).map(Objects::requireNonNull);
+        if (!missingKeys.isEmpty()) {
+            result = Stream.concat(result, missingKeys.stream().map(k -> map.get(k)).map(Objects::requireNonNull));
+        }
+        return result;
+    }
+
     /**
      * Modifies the map in way that it matches the entries of the given childIDs.
      *
@@ -134,4 +155,8 @@ public class ChildMap<T> {
     public void put(String key, T value) {
         map.put(key, value);
     }
+
+    public Set<String> keySet() {
+        return map.keySet();
+    }
 }
index bd5c0ddcfc12c8b5f32dbdb76d8bad45d6536b19..7de63cc519b7c0ca44a77f240c9eb42c4a4bff26 100644 (file)
@@ -28,6 +28,4 @@ public class MqttBindingConstants {
 
     // List of all Thing Type UIDs
     public static final ThingTypeUID HOMEASSISTANT_MQTT_THING = new ThingTypeUID(BINDING_ID, "homeassistant");
-
-    public static final String CONFIG_HA_CHANNEL = "channel-type:mqtt:ha-channel";
 }
index 44e6295669676b49ddf4867aaed8b0f51c9a6be3..b28fadcab1bcd01276cea6dea42bfd7126d6c042 100644 (file)
@@ -18,6 +18,7 @@ import java.util.stream.Stream;
 
 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.homeassistant.internal.handler.HomeAssistantThingHandler;
@@ -26,12 +27,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;
 
 /**
@@ -43,10 +43,22 @@ import org.osgi.service.component.annotations.Reference;
 @Component(service = ThingHandlerFactory.class)
 @NonNullByDefault
 public class MqttThingHandlerFactory extends BaseThingHandlerFactory implements TransformationServiceProvider {
-    private @NonNullByDefault({}) MqttChannelTypeProvider typeProvider;
+    private final MqttChannelTypeProvider typeProvider;
+    private final MqttChannelStateDescriptionProvider stateDescriptionProvider;
+    private final ChannelTypeRegistry channelTypeRegistry;
+
     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
             .of(MqttBindingConstants.HOMEASSISTANT_MQTT_THING).collect(Collectors.toSet());
 
+    @Activate
+    public MqttThingHandlerFactory(final @Reference MqttChannelTypeProvider typeProvider,
+            final @Reference MqttChannelStateDescriptionProvider stateDescriptionProvider,
+            final @Reference ChannelTypeRegistry channelTypeRegistry) {
+        this.typeProvider = typeProvider;
+        this.stateDescriptionProvider = stateDescriptionProvider;
+        this.channelTypeRegistry = channelTypeRegistry;
+    }
+
     @Override
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
         return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || isHomeassistantDynamicType(thingTypeUID);
@@ -57,33 +69,13 @@ public class MqttThingHandlerFactory extends BaseThingHandlerFactory implements
                 && thingTypeUID.getId().startsWith(MqttBindingConstants.HOMEASSISTANT_MQTT_THING.getId());
     }
 
-    @Activate
-    @Override
-    protected void activate(ComponentContext componentContext) {
-        super.activate(componentContext);
-    }
-
-    @Deactivate
-    @Override
-    protected void deactivate(ComponentContext componentContext) {
-        super.deactivate(componentContext);
-    }
-
-    @Reference
-    protected void setChannelProvider(MqttChannelTypeProvider provider) {
-        this.typeProvider = provider;
-    }
-
-    protected void unsetChannelProvider(MqttChannelTypeProvider provider) {
-        this.typeProvider = null;
-    }
-
     @Override
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
         if (supportsThingType(thingTypeUID)) {
-            return new HomeAssistantThingHandler(thing, typeProvider, this, 10000, 2000);
+            return new HomeAssistantThingHandler(thing, typeProvider, stateDescriptionProvider, channelTypeRegistry,
+                    this, 10000, 2000);
         }
         return null;
     }
index 41f66df011ea07c25e3c8d53782f68562e94e518..24fcde68346923b684b79de786f8ca28d9995e1c 100644 (file)
@@ -12,7 +12,7 @@
  */
 package org.openhab.binding.mqtt.homeassistant.internal;
 
-import java.net.URI;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.function.Predicate;
@@ -23,10 +23,8 @@ import org.openhab.binding.mqtt.generic.ChannelConfigBuilder;
 import org.openhab.binding.mqtt.generic.ChannelState;
 import org.openhab.binding.mqtt.generic.ChannelStateTransformation;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
-import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
 import org.openhab.binding.mqtt.generic.values.Value;
-import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
 import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
@@ -36,12 +34,12 @@ 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.ChannelKind;
 import org.openhab.core.thing.type.ChannelType;
-import org.openhab.core.thing.type.ChannelTypeBuilder;
 import org.openhab.core.thing.type.ChannelTypeUID;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.CommandDescription;
-import org.openhab.core.types.StateDescriptionFragment;
+import org.openhab.core.types.StateDescription;
 
 /**
  * An {@link AbstractComponent}s derived class consists of one or multiple channels.
@@ -62,28 +60,22 @@ import org.openhab.core.types.StateDescriptionFragment;
 public class ComponentChannel {
     private static final String JINJA = "JINJA";
 
-    private final ChannelUID channelUID;
     private final ChannelState channelState;
     private final Channel channel;
-    private final ChannelType type;
-    private final ChannelTypeUID channelTypeUID;
+    private final @Nullable StateDescription stateDescription;
+    private final @Nullable CommandDescription commandDescription;
     private final ChannelStateUpdateListener channelStateUpdateListener;
 
-    private ComponentChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type,
-            ChannelTypeUID channelTypeUID, ChannelStateUpdateListener channelStateUpdateListener) {
+    private ComponentChannel(ChannelState channelState, Channel channel, @Nullable StateDescription stateDescription,
+            @Nullable CommandDescription commandDescription, ChannelStateUpdateListener channelStateUpdateListener) {
         super();
-        this.channelUID = channelUID;
         this.channelState = channelState;
         this.channel = channel;
-        this.type = type;
-        this.channelTypeUID = channelTypeUID;
+        this.stateDescription = stateDescription;
+        this.commandDescription = commandDescription;
         this.channelStateUpdateListener = channelStateUpdateListener;
     }
 
-    public ChannelUID getChannelUID() {
-        return channelUID;
-    }
-
     public Channel getChannel() {
         return channel;
     }
@@ -92,6 +84,14 @@ public class ComponentChannel {
         return channelState;
     }
 
+    public @Nullable StateDescription getStateDescription() {
+        return stateDescription;
+    }
+
+    public @Nullable CommandDescription getCommandDescription() {
+        return commandDescription;
+    }
+
     public CompletableFuture<@Nullable Void> stop() {
         return channelState.stop();
     }
@@ -104,16 +104,9 @@ public class ComponentChannel {
         return channelState.start(connection, scheduler, timeout);
     }
 
-    public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
-        channelTypeProvider.setChannelType(channelTypeUID, type);
-    }
-
-    public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
-        channelTypeProvider.removeChannelType(channelTypeUID);
-    }
-
-    public ChannelDefinition type() {
-        return new ChannelDefinitionBuilder(channelUID.getId(), channelTypeUID).build();
+    public ChannelDefinition channelDefinition() {
+        return new ChannelDefinitionBuilder(channel.getUID().getId(),
+                Objects.requireNonNull(channel.getChannelTypeUID())).withLabel(channel.getLabel()).build();
     }
 
     public void resetState() {
@@ -123,6 +116,7 @@ public class ComponentChannel {
     public static class Builder {
         private final AbstractComponent<?> component;
         private final String channelID;
+        private ChannelTypeUID channelTypeUID;
         private final Value valueState;
         private final String label;
         private final ChannelStateUpdateListener channelStateUpdateListener;
@@ -141,10 +135,11 @@ public class ComponentChannel {
 
         private String format = "%s";
 
-        public Builder(AbstractComponent<?> component, String channelID, Value valueState, String label,
-                ChannelStateUpdateListener channelStateUpdateListener) {
+        public Builder(AbstractComponent<?> component, String channelID, ChannelTypeUID channelTypeUID,
+                Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) {
             this.component = component;
             this.channelID = channelID;
+            this.channelTypeUID = channelTypeUID;
             this.valueState = valueState;
             this.label = label;
             this.isAdvanced = false;
@@ -229,12 +224,8 @@ public class ComponentChannel {
             ChannelUID channelUID;
             ChannelState channelState;
             Channel channel;
-            ChannelType type;
-            ChannelTypeUID channelTypeUID;
 
             channelUID = component.buildChannelUID(channelID);
-            channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
-                    channelUID.getGroupId() + "_" + channelID);
             channelState = new HomeAssistantChannelState(
                     ChannelConfigBuilder.create().withRetain(retain).withQos(qos).withStateTopic(stateTopic)
                             .withCommandTopic(commandTopic).makeTrigger(trigger).withFormatter(format).build(),
@@ -244,29 +235,31 @@ public class ComponentChannel {
             if (!component.isEnabledByDefault()) {
                 isAdvanced = true;
             }
+            if (isAdvanced) {
+                channelTypeUID = new ChannelTypeUID(channelTypeUID.getBindingId(),
+                        channelTypeUID.getId() + "-advanced");
+            }
 
-            ChannelTypeBuilder typeBuilder;
+            ChannelKind kind;
+            StateDescription stateDescription = null;
+            CommandDescription commandDescription = null;
             if (this.trigger) {
-                typeBuilder = ChannelTypeBuilder.trigger(channelTypeUID, label);
+                kind = ChannelKind.TRIGGER;
             } else {
-                StateDescriptionFragment stateDescription = valueState.createStateDescription(commandTopic == null)
-                        .build();
-                CommandDescription commandDescription = valueState.createCommandDescription().build();
-                typeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType())
-                        .withStateDescriptionFragment(stateDescription).withCommandDescription(commandDescription);
+                kind = ChannelKind.STATE;
+                stateDescription = valueState.createStateDescription(commandTopic == null).build().toStateDescription();
+                commandDescription = valueState.createCommandDescription().build();
             }
-            type = typeBuilder.withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL))
-                    .isAdvanced(isAdvanced).build();
 
             Configuration configuration = new Configuration();
             configuration.put("config", component.getChannelConfigurationJson());
             component.getHaID().toConfig(configuration);
 
             channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
-                    .withKind(type.getKind()).withLabel(label).withConfiguration(configuration)
+                    .withKind(kind).withLabel(label).withConfiguration(configuration)
                     .withAutoUpdatePolicy(autoUpdatePolicy).build();
 
-            ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID,
+            ComponentChannel result = new ComponentChannel(channelState, channel, stateDescription, commandDescription,
                     channelStateUpdateListener);
 
             TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider();
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannelType.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannelType.java
new file mode 100644 (file)
index 0000000..b99f868
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2024 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.mqtt.homeassistant.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
+import org.openhab.core.thing.type.ChannelTypeUID;
+
+/**
+ * The types of HomeAssistant channels components.
+ *
+ * @author Cody Cutrer - Initial contribution
+ */
+@NonNullByDefault
+public enum ComponentChannelType {
+    COLOR("ha-color"),
+    DIMMER("ha-dimmer"),
+    IMAGE("ha-image"),
+    NUMBER("ha-number"),
+    ROLLERSHUTTER("ha-rollershutter"),
+    STRING("ha-string"),
+    SWITCH("ha-switch"),
+    TRIGGER("ha-trigger");
+
+    final ChannelTypeUID channelTypeUID;
+
+    ComponentChannelType(String id) {
+        channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, id);
+    }
+
+    public ChannelTypeUID getChannelTypeUID() {
+        return channelTypeUID;
+    }
+}
index 96724530bc8ef90c13179841e39abed0476b05e3..1907e800775237f90327aee785e09c441c2574e3 100644 (file)
@@ -53,6 +53,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
     private final ChannelStateUpdateListener updateListener;
     private final AvailabilityTracker tracker;
     private final TransformationServiceProvider transformationServiceProvider;
+    private final boolean newStyleChannels;
 
     protected final CompletableFuture<@Nullable Void> discoverFinishedFuture = new CompletableFuture<>();
     private final Gson gson;
@@ -79,13 +80,14 @@ public class DiscoverComponents implements MqttMessageSubscriber {
      */
     public DiscoverComponents(ThingUID thingUID, ScheduledExecutorService scheduler,
             ChannelStateUpdateListener channelStateUpdateListener, AvailabilityTracker tracker, Gson gson,
-            TransformationServiceProvider transformationServiceProvider) {
+            TransformationServiceProvider transformationServiceProvider, boolean newStyleChannels) {
         this.thingUID = thingUID;
         this.scheduler = scheduler;
         this.updateListener = channelStateUpdateListener;
         this.gson = gson;
         this.tracker = tracker;
         this.transformationServiceProvider = transformationServiceProvider;
+        this.newStyleChannels = newStyleChannels;
     }
 
     @Override
@@ -101,7 +103,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
         if (config.length() > 0) {
             try {
                 component = ComponentFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler,
-                        gson, transformationServiceProvider);
+                        gson, transformationServiceProvider, newStyleChannels);
                 component.setConfigSeen();
 
                 logger.trace("Found HomeAssistant component {}", haID);
index 7a2ca110c9e0c9ba56b444d1c34533108f6bef72..829cbb54fe5091c60104a95b7f4dd1ed556f60e8 100644 (file)
@@ -187,9 +187,19 @@ public class HaID {
      *
      * @return group id
      */
-    public String getGroupId(@Nullable final String uniqueId) {
+    public String getGroupId(@Nullable final String uniqueId, boolean newStyleChannels) {
         String result = uniqueId;
 
+        // newStyleChannels are auto-discovered things with openHAB >= 4.3.0
+        // assuming the topic has both a node ID and an object ID, simply use
+        // the component type and object ID - without encoding(!)
+        // since the only character allowed in object IDs but not allowed in UID
+        // is `-`. It also doesn't need to be reversible, so it's okay to just
+        // collapse `-` to `_`.
+        if (!nodeID.isBlank() && newStyleChannels) {
+            return component + "_" + objectID.replace('-', '_');
+        }
+
         // the null test is only here so the compile knows, result is not null afterwards
         if (result == null || result.isBlank()) {
             StringBuilder str = new StringBuilder();
index ca901bcba959018290c93acd04d155852b69f1d9..befc19a5c7d87d4b47c02445e5bec59b84beac7f 100644 (file)
@@ -19,18 +19,18 @@ import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.AvailabilityTracker;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
-import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
+import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
 import org.openhab.binding.mqtt.generic.values.Value;
 import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.HaID;
 import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
@@ -38,6 +38,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Availability;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AvailabilityMode;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
+import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelGroupUID;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.type.ChannelDefinition;
@@ -45,6 +46,8 @@ 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;
+import org.openhab.core.types.CommandDescription;
+import org.openhab.core.types.StateDescription;
 
 import com.google.gson.Gson;
 
@@ -61,7 +64,6 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
 
     // Component location fields
     protected final ComponentConfiguration componentConfiguration;
-    protected final @Nullable ChannelGroupTypeUID channelGroupTypeUID;
     protected final @Nullable ChannelGroupUID channelGroupUID;
     protected final HaID haID;
 
@@ -76,15 +78,28 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
     protected final C channelConfiguration;
 
     protected boolean configSeen;
+    protected final boolean singleChannelComponent;
+    protected final String groupId;
+    protected final String uniqueId;
+
+    public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz,
+            boolean newStyleChannels) {
+        this(componentConfiguration, clazz, newStyleChannels, false);
+    }
 
     /**
      * Creates component based on generic configuration and component configuration type.
      *
      * @param componentConfiguration generic componentConfiguration with not parsed JSON config
      * @param clazz target configuration type
+     * @param newStyleChannels if new style channels should be used
+     * @param singleChannelComponent if this component only ever has one channel, so should never be in a group
+     *            (only if newStyleChannels is true)
      */
-    public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
+    public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz,
+            boolean newStyleChannels, boolean singleChannelComponent) {
         this.componentConfiguration = componentConfiguration;
+        this.singleChannelComponent = newStyleChannels && singleChannelComponent;
 
         this.channelConfigurationJson = componentConfiguration.getConfigJSON();
         this.channelConfiguration = componentConfiguration.getConfig(clazz);
@@ -94,14 +109,15 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
 
         String name = channelConfiguration.getName();
         if (name != null && !name.isEmpty()) {
-            String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
+            groupId = this.haID.getGroupId(channelConfiguration.getUniqueId(), newStyleChannels);
 
-            this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
-            this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
+            this.channelGroupUID = this.singleChannelComponent ? null
+                    : new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
         } else {
-            this.channelGroupTypeUID = null;
+            this.groupId = this.singleChannelComponent ? haID.component : "";
             this.channelGroupUID = null;
         }
+        uniqueId = this.haID.getGroupId(channelConfiguration.getUniqueId(), false);
 
         this.configSeen = false;
 
@@ -138,9 +154,13 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
         }
     }
 
-    protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label,
-            ChannelStateUpdateListener channelStateUpdateListener) {
-        return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener);
+    protected ComponentChannel.Builder buildChannel(String channelID, ComponentChannelType channelType,
+            Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) {
+        if (singleChannelComponent) {
+            channelID = groupId;
+        }
+        return new ComponentChannel.Builder(this, channelID, channelType.getChannelTypeUID(), valueState, label,
+                channelStateUpdateListener);
     }
 
     public void setConfigSeen() {
@@ -177,30 +197,21 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
     }
 
     /**
-     * Add all channel types to the channel type provider.
+     * Add all state and command descriptions to the state description provider.
      *
-     * @param channelTypeProvider The channel type provider
+     * @param stateDescriptionProvider The state description provider
      */
-    public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
-        ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
-        if (groupTypeUID != null) {
-            channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType()));
-        }
-        channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
-    }
-
-    /**
-     * Removes all channels from the channel type provider.
-     * Call this if the corresponding Thing handler gets disposed.
-     *
-     * @param channelTypeProvider The channel type provider
-     */
-    public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
-        channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider));
-        ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
-        if (groupTypeUID != null) {
-            channelTypeProvider.removeChannelGroupType(groupTypeUID);
-        }
+    public void addStateDescriptions(MqttChannelStateDescriptionProvider stateDescriptionProvider) {
+        channels.values().forEach(channel -> {
+            StateDescription stateDescription = channel.getStateDescription();
+            if (stateDescription != null) {
+                stateDescriptionProvider.setDescription(channel.getChannel().getUID(), stateDescription);
+            }
+            CommandDescription commandDescription = channel.getCommandDescription();
+            if (commandDescription != null) {
+                stateDescriptionProvider.setDescription(channel.getChannel().getUID(), commandDescription);
+            }
+        });
     }
 
     public ChannelUID buildChannelUID(String channelID) {
@@ -211,18 +222,8 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
         return new ChannelUID(componentConfiguration.getThingUID(), channelID);
     }
 
-    /**
-     * Each HomeAssistant component corresponds to a Channel Group Type.
-     */
-    public @Nullable ChannelGroupTypeUID getGroupTypeUID() {
-        return channelGroupTypeUID;
-    }
-
-    /**
-     * The unique id of this component.
-     */
-    public @Nullable ChannelGroupUID getGroupUID() {
-        return channelGroupUID;
+    public String getGroupId() {
+        return groupId;
     }
 
     /**
@@ -270,19 +271,27 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
     /**
      * Return the channel group type.
      */
-    public @Nullable ChannelGroupType getType() {
-        ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
-        if (groupTypeUID == null) {
+    public @Nullable ChannelGroupType getChannelGroupType(String prefix) {
+        if (channelGroupUID == null) {
             return null;
         }
-        final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
-                .collect(Collectors.toList());
-        return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
-                .build();
+        return ChannelGroupTypeBuilder.instance(getChannelGroupTypeUID(prefix), getName())
+                .withChannelDefinitions(getAllChannelDefinitions()).build();
+    }
+
+    public List<ChannelDefinition> getChannelDefinitions() {
+        if (channelGroupUID != null) {
+            return List.of();
+        }
+        return getAllChannelDefinitions();
+    }
+
+    private List<ChannelDefinition> getAllChannelDefinitions() {
+        return channels.values().stream().map(ComponentChannel::channelDefinition).toList();
     }
 
-    public List<ChannelDefinition> getChannels() {
-        return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
+    public List<Channel> getChannels() {
+        return channels.values().stream().map(ComponentChannel::getChannel).toList();
     }
 
     /**
@@ -296,12 +305,15 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
     /**
      * Return the channel group definition for this component.
      */
-    public @Nullable ChannelGroupDefinition getGroupDefinition() {
-        ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
-        if (groupTypeUID == null) {
+    public @Nullable ChannelGroupDefinition getGroupDefinition(String prefix) {
+        if (channelGroupUID == null) {
             return null;
         }
-        return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null);
+        return new ChannelGroupDefinition(channelGroupUID.getId(), getChannelGroupTypeUID(prefix), getName(), null);
+    }
+
+    public boolean hasGroup() {
+        return channelGroupUID != null;
     }
 
     public HaID getHaID() {
@@ -324,4 +336,12 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
     public Gson getGson() {
         return componentConfiguration.getGson();
     }
+
+    public C getChannelConfiguration() {
+        return channelConfiguration;
+    }
+
+    private ChannelGroupTypeUID getChannelGroupTypeUID(String prefix) {
+        return new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, prefix + "_" + uniqueId);
+    }
 }
index 2cce0c4ce8c96061ec5478f6bd17f8482460314f..c7a43e57bbd8ce3a9bb9225989751b3324016a17 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.PercentType;
@@ -31,11 +32,12 @@ abstract class AbstractRawSchemaLight extends Light {
 
     protected ComponentChannel rawChannel;
 
-    public AbstractRawSchemaLight(ComponentFactory.ComponentConfiguration builder) {
-        super(builder);
-        hiddenChannels.add(rawChannel = buildChannel(RAW_CHANNEL_ID, new TextValue(), "Raw state", this)
-                .stateTopic(channelConfiguration.stateTopic).commandTopic(channelConfiguration.commandTopic,
-                        channelConfiguration.isRetain(), channelConfiguration.getQos())
+    public AbstractRawSchemaLight(ComponentFactory.ComponentConfiguration builder, boolean newStyleChannels) {
+        super(builder, newStyleChannels);
+        hiddenChannels.add(rawChannel = buildChannel(RAW_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(),
+                "Raw state", this).stateTopic(channelConfiguration.stateTopic)
+                .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
+                        channelConfiguration.getQos())
                 .build(false));
     }
 
index 961d4ee3e1e4e1a55708832e340de55e5d369ecd..026ce5048556cb871f984a109b7191b481b17a3f 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 
 import com.google.gson.annotations.SerializedName;
@@ -68,28 +69,30 @@ public class AlarmControlPanel extends AbstractComponent<AlarmControlPanel.Chann
         protected String payloadArmAway = "ARM_AWAY";
     }
 
-    public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         final String[] stateEnum = { channelConfiguration.stateDisarmed, channelConfiguration.stateArmedHome,
                 channelConfiguration.stateArmedAway, channelConfiguration.statePending,
                 channelConfiguration.stateTriggered };
-        buildChannel(STATE_CHANNEL_ID, new TextValue(stateEnum), getName(), componentConfiguration.getUpdateListener())
+        buildChannel(STATE_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(stateEnum), getName(),
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())//
                 .build();
 
         String commandTopic = channelConfiguration.commandTopic;
         if (commandTopic != null) {
-            buildChannel(SWITCH_DISARM_CHANNEL_ID, new TextValue(new String[] { channelConfiguration.payloadDisarm }),
-                    getName(), componentConfiguration.getUpdateListener())
+            buildChannel(SWITCH_DISARM_CHANNEL_ID, ComponentChannelType.STRING,
+                    new TextValue(new String[] { channelConfiguration.payloadDisarm }), getName(),
+                    componentConfiguration.getUpdateListener())
                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
 
-            buildChannel(SWITCH_ARM_HOME_CHANNEL_ID,
+            buildChannel(SWITCH_ARM_HOME_CHANNEL_ID, ComponentChannelType.STRING,
                     new TextValue(new String[] { channelConfiguration.payloadArmHome }), getName(),
                     componentConfiguration.getUpdateListener())
                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
 
-            buildChannel(SWITCH_ARM_AWAY_CHANNEL_ID,
+            buildChannel(SWITCH_ARM_AWAY_CHANNEL_ID, ComponentChannelType.STRING,
                     new TextValue(new String[] { channelConfiguration.payloadArmAway }), getName(),
                     componentConfiguration.getUpdateListener())
                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
index 2d636e95254f5e9729362610c62eb4da0dc21f99..b6df93f18d6cf7a63be883a7d8ddc4cd84df02cf 100644 (file)
@@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
 import org.openhab.binding.mqtt.generic.values.OnOffValue;
 import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
 import org.openhab.binding.mqtt.homeassistant.internal.listener.OffDelayUpdateStateListener;
@@ -67,12 +68,13 @@ public class BinarySensor extends AbstractComponent<BinarySensor.ChannelConfigur
         protected @Nullable List<String> jsonAttributes;
     }
 
-    public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
 
-        buildChannel(SENSOR_CHANNEL_ID, value, "value", getListener(componentConfiguration, value))
+        buildChannel(SENSOR_CHANNEL_ID, ComponentChannelType.SWITCH, value, getName(),
+                getListener(componentConfiguration, value))
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
     }
index c54fcd48b56069b8ff1a05189dc2800bd0603782..cff40ea8e4ade0c990b8d48532c5740981c6935d 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.thing.type.AutoUpdatePolicy;
 
@@ -46,12 +47,13 @@ public class Button extends AbstractComponent<Button.ChannelConfiguration> {
         protected String payloadPress = "PRESS";
     }
 
-    public Button(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Button(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         TextValue value = new TextValue(new String[] { channelConfiguration.payloadPress });
 
-        buildChannel(BUTTON_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
+        buildChannel(BUTTON_CHANNEL_ID, ComponentChannelType.STRING, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos())
                 .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
index 97fddc02dffc0fa2ec64de17e3463b5615dff23b..c1e934d43245905eda2df02aff743a7e0e5c2270 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.mqtt.generic.values.ImageValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 
 /**
@@ -38,12 +39,12 @@ public class Camera extends AbstractComponent<Camera.ChannelConfiguration> {
         protected String topic = "";
     }
 
-    public Camera(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Camera(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         ImageValue value = new ImageValue();
 
-        buildChannel(CAMERA_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
-                .stateTopic(channelConfiguration.topic).build();
+        buildChannel(CAMERA_CHANNEL_ID, ComponentChannelType.IMAGE, value, getName(),
+                componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build();
     }
 }
index 2b8cc8ad97fa00d4fb84e6d0bdd338f2520b2874..1c7726bc52ced69347f3e7565d214df80bfb9332 100644 (file)
@@ -28,6 +28,7 @@ import org.openhab.binding.mqtt.generic.values.OnOffValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.generic.values.Value;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.library.unit.ImperialUnits;
@@ -210,83 +211,88 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
         protected Boolean sendIfOff = true;
     }
 
-    public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Climate(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         BigDecimal precision = channelConfiguration.precision != null ? channelConfiguration.precision
                 : channelConfiguration.temperatureUnit.getDefaultPrecision();
         final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
 
-        ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
+        ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID, ComponentChannelType.STRING,
                 new TextValue(ACTION_MODES.toArray(new String[0])), updateListener, null, null,
                 channelConfiguration.actionTemplate, channelConfiguration.actionTopic, null);
 
         final Predicate<Command> commandFilter = channelConfiguration.sendIfOff ? null
                 : getCommandFilter(actionChannel);
 
-        buildOptionalChannel(AUX_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.auxCommandTopic,
-                channelConfiguration.auxStateTemplate, channelConfiguration.auxStateTopic, commandFilter);
+        buildOptionalChannel(AUX_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
+                channelConfiguration.auxCommandTopic, channelConfiguration.auxStateTemplate,
+                channelConfiguration.auxStateTopic, commandFilter);
 
-        buildOptionalChannel(AWAY_MODE_CH_ID, new OnOffValue(), updateListener, null,
+        buildOptionalChannel(AWAY_MODE_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
                 channelConfiguration.awayModeCommandTopic, channelConfiguration.awayModeStateTemplate,
                 channelConfiguration.awayModeStateTopic, commandFilter);
 
-        buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID,
+        buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID, ComponentChannelType.NUMBER,
                 new NumberValue(null, null, precision, channelConfiguration.temperatureUnit.getUnit()), updateListener,
                 null, null, channelConfiguration.currentTemperatureTemplate,
                 channelConfiguration.currentTemperatureTopic, commandFilter);
 
-        buildOptionalChannel(FAN_MODE_CH_ID, new TextValue(channelConfiguration.fanModes.toArray(new String[0])),
-                updateListener, channelConfiguration.fanModeCommandTemplate, channelConfiguration.fanModeCommandTopic,
+        buildOptionalChannel(FAN_MODE_CH_ID, ComponentChannelType.STRING,
+                new TextValue(channelConfiguration.fanModes.toArray(new String[0])), updateListener,
+                channelConfiguration.fanModeCommandTemplate, channelConfiguration.fanModeCommandTopic,
                 channelConfiguration.fanModeStateTemplate, channelConfiguration.fanModeStateTopic, commandFilter);
 
         List<String> holdModes = channelConfiguration.holdModes;
         if (holdModes != null && !holdModes.isEmpty()) {
-            buildOptionalChannel(HOLD_CH_ID, new TextValue(holdModes.toArray(new String[0])), updateListener,
+            buildOptionalChannel(HOLD_CH_ID, ComponentChannelType.STRING,
+                    new TextValue(holdModes.toArray(new String[0])), updateListener,
                     channelConfiguration.holdCommandTemplate, channelConfiguration.holdCommandTopic,
                     channelConfiguration.holdStateTemplate, channelConfiguration.holdStateTopic, commandFilter);
         }
 
-        buildOptionalChannel(MODE_CH_ID, new TextValue(channelConfiguration.modes.toArray(new String[0])),
-                updateListener, channelConfiguration.modeCommandTemplate, channelConfiguration.modeCommandTopic,
+        buildOptionalChannel(MODE_CH_ID, ComponentChannelType.STRING,
+                new TextValue(channelConfiguration.modes.toArray(new String[0])), updateListener,
+                channelConfiguration.modeCommandTemplate, channelConfiguration.modeCommandTopic,
                 channelConfiguration.modeStateTemplate, channelConfiguration.modeStateTopic, commandFilter);
 
-        buildOptionalChannel(SWING_CH_ID, new TextValue(channelConfiguration.swingModes.toArray(new String[0])),
-                updateListener, channelConfiguration.swingCommandTemplate, channelConfiguration.swingCommandTopic,
+        buildOptionalChannel(SWING_CH_ID, ComponentChannelType.STRING,
+                new TextValue(channelConfiguration.swingModes.toArray(new String[0])), updateListener,
+                channelConfiguration.swingCommandTemplate, channelConfiguration.swingCommandTopic,
                 channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
 
-        buildOptionalChannel(TEMPERATURE_CH_ID,
+        buildOptionalChannel(TEMPERATURE_CH_ID, ComponentChannelType.NUMBER,
                 new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
                         channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, channelConfiguration.temperatureCommandTemplate,
                 channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
                 channelConfiguration.temperatureStateTopic, commandFilter);
 
-        buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
+        buildOptionalChannel(TEMPERATURE_HIGH_CH_ID, ComponentChannelType.NUMBER,
                 new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
                         channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, channelConfiguration.temperatureHighCommandTemplate,
                 channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
                 channelConfiguration.temperatureHighStateTopic, commandFilter);
 
-        buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
+        buildOptionalChannel(TEMPERATURE_LOW_CH_ID, ComponentChannelType.NUMBER,
                 new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
                         channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, channelConfiguration.temperatureLowCommandTemplate,
                 channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
                 channelConfiguration.temperatureLowStateTopic, commandFilter);
 
-        buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null,
+        buildOptionalChannel(POWER_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
                 channelConfiguration.powerCommandTopic, null, null, null);
     }
 
     @Nullable
-    private ComponentChannel buildOptionalChannel(String channelId, Value valueState,
+    private ComponentChannel buildOptionalChannel(String channelId, ComponentChannelType channelType, Value valueState,
             ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,
             @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic,
             @Nullable Predicate<Command> commandFilter) {
         if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
-            return buildChannel(channelId, valueState, getName(), channelStateUpdateListener)
+            return buildChannel(channelId, channelType, valueState, getName(), channelStateUpdateListener)
                     .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
                             commandTemplate)
index 367f666e48d1ef863d8d2d671c837072325a881d..0c48a72805a9187eadef9775a0db30136d69ea8f 100644 (file)
@@ -48,45 +48,46 @@ public class ComponentFactory {
      */
     public static AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID, String channelConfigurationJSON,
             ChannelStateUpdateListener updateListener, AvailabilityTracker tracker, ScheduledExecutorService scheduler,
-            Gson gson, TransformationServiceProvider transformationServiceProvider) throws ConfigurationException {
+            Gson gson, TransformationServiceProvider transformationServiceProvider, boolean newStyleChannels)
+            throws ConfigurationException {
         ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
                 channelConfigurationJSON, gson, updateListener, tracker, scheduler)
                 .transformationProvider(transformationServiceProvider);
         switch (haID.component) {
             case "alarm_control_panel":
-                return new AlarmControlPanel(componentConfiguration);
+                return new AlarmControlPanel(componentConfiguration, newStyleChannels);
             case "binary_sensor":
-                return new BinarySensor(componentConfiguration);
+                return new BinarySensor(componentConfiguration, newStyleChannels);
             case "button":
-                return new Button(componentConfiguration);
+                return new Button(componentConfiguration, newStyleChannels);
             case "camera":
-                return new Camera(componentConfiguration);
+                return new Camera(componentConfiguration, newStyleChannels);
             case "cover":
-                return new Cover(componentConfiguration);
+                return new Cover(componentConfiguration, newStyleChannels);
             case "fan":
-                return new Fan(componentConfiguration);
+                return new Fan(componentConfiguration, newStyleChannels);
             case "climate":
-                return new Climate(componentConfiguration);
+                return new Climate(componentConfiguration, newStyleChannels);
             case "device_automation":
-                return new DeviceTrigger(componentConfiguration);
+                return new DeviceTrigger(componentConfiguration, newStyleChannels);
             case "light":
-                return Light.create(componentConfiguration);
+                return Light.create(componentConfiguration, newStyleChannels);
             case "lock":
-                return new Lock(componentConfiguration);
+                return new Lock(componentConfiguration, newStyleChannels);
             case "number":
-                return new Number(componentConfiguration);
+                return new Number(componentConfiguration, newStyleChannels);
             case "scene":
-                return new Scene(componentConfiguration);
+                return new Scene(componentConfiguration, newStyleChannels);
             case "select":
-                return new Select(componentConfiguration);
+                return new Select(componentConfiguration, newStyleChannels);
             case "sensor":
-                return new Sensor(componentConfiguration);
+                return new Sensor(componentConfiguration, newStyleChannels);
             case "switch":
-                return new Switch(componentConfiguration);
+                return new Switch(componentConfiguration, newStyleChannels);
             case "update":
-                return new Update(componentConfiguration);
+                return new Update(componentConfiguration, newStyleChannels);
             case "vacuum":
-                return new Vacuum(componentConfiguration);
+                return new Vacuum(componentConfiguration, newStyleChannels);
             default:
                 throw new UnsupportedComponentException("Component '" + haID + "' is unsupported!");
         }
index 55f5b632705f618d8e3f8b251c9419f46edf2527..077a7b94cd51ed42a253fc371ce7fd872e9e0de5 100644 (file)
@@ -17,6 +17,7 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.RollershutterValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.library.types.StopMoveType;
 import org.openhab.core.library.types.StringType;
@@ -84,8 +85,8 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
     @Nullable
     ComponentChannel stateChannel = null;
 
-    public Cover(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Cover(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         String stateTopic = channelConfiguration.stateTopic;
 
@@ -95,13 +96,13 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
             TextValue value = new TextValue(new String[] { channelConfiguration.stateClosed,
                     channelConfiguration.stateClosing, channelConfiguration.stateOpen,
                     channelConfiguration.stateOpening, channelConfiguration.stateStopped });
-            buildChannel(STATE_CHANNEL_ID, value, "State", componentConfiguration.getUpdateListener())
-                    .stateTopic(stateTopic).isAdvanced(true).build();
+            buildChannel(STATE_CHANNEL_ID, ComponentChannelType.STRING, value, "State",
+                    componentConfiguration.getUpdateListener()).stateTopic(stateTopic).isAdvanced(true).build();
         }
 
         if (channelConfiguration.commandTopic != null) {
-            hiddenChannels.add(stateChannel = buildChannel(STATE_CHANNEL_ID, new TextValue(), "State",
-                    componentConfiguration.getUpdateListener())
+            hiddenChannels.add(stateChannel = buildChannel(STATE_CHANNEL_ID, ComponentChannelType.STRING,
+                    new TextValue(), "State", componentConfiguration.getUpdateListener())
                     .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
                     .build(false));
@@ -132,8 +133,8 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
                 channelConfiguration.payloadClose, channelConfiguration.payloadStop, channelConfiguration.stateOpen,
                 channelConfiguration.stateClosed, inverted, channelConfiguration.setPositionTopic == null);
 
-        buildChannel(COVER_CHANNEL_ID, value, "Cover", componentConfiguration.getUpdateListener())
-                .stateTopic(rollershutterStateTopic, stateTemplate)
+        buildChannel(COVER_CHANNEL_ID, ComponentChannelType.ROLLERSHUTTER, value, "Cover",
+                componentConfiguration.getUpdateListener()).stateTopic(rollershutterStateTopic, stateTemplate)
                 .commandTopic(rollershutterCommandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos())
                 .commandFilter(command -> {
                     if (stateChannel == null) {
index 3db47a4051dbdc09fb7eb2a4ce4a74aba99c7546..9d412c1a14ba39f1267a00cd62c921f17c0cd884 100644 (file)
@@ -22,6 +22,7 @@ import org.openhab.binding.mqtt.generic.mapping.ColorMode;
 import org.openhab.binding.mqtt.generic.values.ColorValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.OnOffType;
@@ -53,14 +54,15 @@ public class DefaultSchemaLight extends Light {
     protected @Nullable ComponentChannel rgbChannel;
     protected @Nullable ComponentChannel xyChannel;
 
-    public DefaultSchemaLight(ComponentFactory.ComponentConfiguration builder) {
-        super(builder);
+    public DefaultSchemaLight(ComponentFactory.ComponentConfiguration builder, boolean newStyleChannels) {
+        super(builder, newStyleChannels);
     }
 
     @Override
     protected void buildChannels() {
         ComponentChannel localOnOffChannel;
-        localOnOffChannel = onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, onOffValue, "On/Off State", this)
+        localOnOffChannel = onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, ComponentChannelType.SWITCH, onOffValue,
+                "On/Off State", this)
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.stateValueTemplate)
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos())
@@ -69,8 +71,8 @@ public class DefaultSchemaLight extends Light {
         @Nullable
         ComponentChannel localBrightnessChannel = null;
         if (channelConfiguration.brightnessStateTopic != null || channelConfiguration.brightnessCommandTopic != null) {
-            localBrightnessChannel = brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, brightnessValue,
-                    "Brightness", this)
+            localBrightnessChannel = brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID,
+                    ComponentChannelType.DIMMER, brightnessValue, "Brightness", this)
                     .stateTopic(channelConfiguration.brightnessStateTopic, channelConfiguration.brightnessValueTemplate)
                     .commandTopic(channelConfiguration.brightnessCommandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
@@ -78,20 +80,22 @@ public class DefaultSchemaLight extends Light {
         }
 
         if (channelConfiguration.whiteCommandTopic != null) {
-            buildChannel(WHITE_CHANNEL_ID, brightnessValue, "Go directly to white of a specific brightness", this)
+            buildChannel(WHITE_CHANNEL_ID, ComponentChannelType.DIMMER, brightnessValue,
+                    "Go directly to white of a specific brightness", this)
                     .commandTopic(channelConfiguration.whiteCommandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
                     .isAdvanced(true).build();
         }
 
         if (channelConfiguration.colorModeStateTopic != null) {
-            buildChannel(COLOR_MODE_CHANNEL_ID, new TextValue(), "Current color mode", this)
+            buildChannel(COLOR_MODE_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "Current color mode",
+                    this)
                     .stateTopic(channelConfiguration.colorModeStateTopic, channelConfiguration.colorModeValueTemplate)
                     .build();
         }
 
         if (channelConfiguration.colorTempStateTopic != null || channelConfiguration.colorTempCommandTopic != null) {
-            buildChannel(COLOR_TEMP_CHANNEL_ID, colorTempValue, "Color Temperature", this)
+            buildChannel(COLOR_TEMP_CHANNEL_ID, ComponentChannelType.NUMBER, colorTempValue, "Color Temperature", this)
                     .stateTopic(channelConfiguration.colorTempStateTopic, channelConfiguration.colorTempValueTemplate)
                     .commandTopic(channelConfiguration.colorTempCommandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
@@ -100,7 +104,8 @@ public class DefaultSchemaLight extends Light {
 
         if (effectValue != null
                 && (channelConfiguration.effectStateTopic != null || channelConfiguration.effectCommandTopic != null)) {
-            buildChannel(EFFECT_CHANNEL_ID, Objects.requireNonNull(effectValue), "Lighting Effect", this)
+            buildChannel(EFFECT_CHANNEL_ID, ComponentChannelType.STRING, Objects.requireNonNull(effectValue),
+                    "Lighting Effect", this)
                     .stateTopic(channelConfiguration.effectStateTopic, channelConfiguration.effectValueTemplate)
                     .commandTopic(channelConfiguration.effectCommandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
@@ -109,8 +114,8 @@ public class DefaultSchemaLight extends Light {
 
         if (channelConfiguration.rgbStateTopic != null || channelConfiguration.rgbCommandTopic != null) {
             hasColorChannel = true;
-            hiddenChannels.add(rgbChannel = buildChannel(RGB_CHANNEL_ID, new ColorValue(ColorMode.RGB, null, null, 100),
-                    "RGB state", this)
+            hiddenChannels.add(rgbChannel = buildChannel(RGB_CHANNEL_ID, ComponentChannelType.COLOR,
+                    new ColorValue(ColorMode.RGB, null, null, 100), "RGB state", this)
                     .stateTopic(channelConfiguration.rgbStateTopic, channelConfiguration.rgbValueTemplate)
                     .commandTopic(channelConfiguration.rgbCommandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
@@ -119,35 +124,38 @@ public class DefaultSchemaLight extends Light {
 
         if (channelConfiguration.rgbwStateTopic != null || channelConfiguration.rgbwCommandTopic != null) {
             hasColorChannel = true;
-            hiddenChannels.add(buildChannel(RGBW_CHANNEL_ID, new TextValue(), "RGBW state", this)
-                    .stateTopic(channelConfiguration.rgbwStateTopic, channelConfiguration.rgbwValueTemplate)
-                    .commandTopic(channelConfiguration.rgbwCommandTopic, channelConfiguration.isRetain(),
-                            channelConfiguration.getQos())
-                    .build(false));
+            hiddenChannels
+                    .add(buildChannel(RGBW_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "RGBW state", this)
+                            .stateTopic(channelConfiguration.rgbwStateTopic, channelConfiguration.rgbwValueTemplate)
+                            .commandTopic(channelConfiguration.rgbwCommandTopic, channelConfiguration.isRetain(),
+                                    channelConfiguration.getQos())
+                            .build(false));
         }
 
         if (channelConfiguration.rgbwwStateTopic != null || channelConfiguration.rgbwwCommandTopic != null) {
             hasColorChannel = true;
-            hiddenChannels.add(buildChannel(RGBWW_CHANNEL_ID, new TextValue(), "RGBWW state", this)
-                    .stateTopic(channelConfiguration.rgbwwStateTopic, channelConfiguration.rgbwwValueTemplate)
-                    .commandTopic(channelConfiguration.rgbwwCommandTopic, channelConfiguration.isRetain(),
-                            channelConfiguration.getQos())
-                    .build(false));
+            hiddenChannels.add(
+                    buildChannel(RGBWW_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "RGBWW state", this)
+                            .stateTopic(channelConfiguration.rgbwwStateTopic, channelConfiguration.rgbwwValueTemplate)
+                            .commandTopic(channelConfiguration.rgbwwCommandTopic, channelConfiguration.isRetain(),
+                                    channelConfiguration.getQos())
+                            .build(false));
         }
 
         if (channelConfiguration.xyStateTopic != null || channelConfiguration.xyCommandTopic != null) {
             hasColorChannel = true;
-            hiddenChannels.add(
-                    xyChannel = buildChannel(XY_CHANNEL_ID, new ColorValue(ColorMode.XYY, null, null, 100), "XY State",
-                            this).stateTopic(channelConfiguration.xyStateTopic, channelConfiguration.xyValueTemplate)
-                            .commandTopic(channelConfiguration.xyCommandTopic, channelConfiguration.isRetain(),
-                                    channelConfiguration.getQos())
-                            .build(false));
+            hiddenChannels.add(xyChannel = buildChannel(XY_CHANNEL_ID, ComponentChannelType.COLOR,
+                    new ColorValue(ColorMode.XYY, null, null, 100), "XY State", this)
+                    .stateTopic(channelConfiguration.xyStateTopic, channelConfiguration.xyValueTemplate)
+                    .commandTopic(channelConfiguration.xyCommandTopic, channelConfiguration.isRetain(),
+                            channelConfiguration.getQos())
+                    .build(false));
         }
 
         if (channelConfiguration.hsStateTopic != null || channelConfiguration.hsCommandTopic != null) {
             hasColorChannel = true;
-            hiddenChannels.add(this.hsChannel = buildChannel(HS_CHANNEL_ID, new TextValue(), "Hue and Saturation", this)
+            hiddenChannels.add(this.hsChannel = buildChannel(HS_CHANNEL_ID, ComponentChannelType.STRING,
+                    new TextValue(), "Hue and Saturation", this)
                     .stateTopic(channelConfiguration.hsStateTopic, channelConfiguration.hsValueTemplate)
                     .commandTopic(channelConfiguration.hsCommandTopic, channelConfiguration.isRetain(),
                             channelConfiguration.getQos())
@@ -159,7 +167,7 @@ public class DefaultSchemaLight extends Light {
             if (localBrightnessChannel != null) {
                 hiddenChannels.add(localBrightnessChannel);
             }
-            buildChannel(COLOR_CHANNEL_ID, colorValue, "Color", this)
+            buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this)
                     .commandTopic(DUMMY_TOPIC, channelConfiguration.isRetain(), channelConfiguration.getQos())
                     .commandFilter(this::handleColorCommand).build();
         } else if (localBrightnessChannel != null) {
index 0ff48e027381c686cf22a5f4ab9c6a77575871da..91ea0456b65a4dd738e778a61f3f09e4f1def04b 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
 
@@ -44,8 +45,8 @@ public class DeviceTrigger extends AbstractComponent<DeviceTrigger.ChannelConfig
         protected @Nullable String payload;
     }
 
-    public DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         if (!"trigger".equals(channelConfiguration.automationType)) {
             throw new ConfigurationException("Component:DeviceTrigger must have automation_type 'trigger'");
@@ -65,7 +66,8 @@ public class DeviceTrigger extends AbstractComponent<DeviceTrigger.ChannelConfig
             value = new TextValue();
         }
 
-        buildChannel(channelConfiguration.type, value, getName(), componentConfiguration.getUpdateListener())
+        buildChannel(channelConfiguration.type, ComponentChannelType.TRIGGER, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build();
     }
 }
index 4a0a041d11004cdd22ca03ad183c6d1c377970e4..6b216a2bcb3c1589e78ff666d6eddd6ea5d6756c 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.OnOffValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 
 import com.google.gson.annotations.SerializedName;
@@ -50,11 +51,12 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> {
         protected String payloadOff = "OFF";
     }
 
-    public Fan(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Fan(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
-        buildChannel(SWITCH_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
+        buildChannel(SWITCH_CHANNEL_ID, ComponentChannelType.SWITCH, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos(), channelConfiguration.commandTemplate)
index b678d3830eaf069b1f9b2b515cb33bb5b3a2758c..ca016efd345d95b99dad88d316501b5bc932217d 100644 (file)
@@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
@@ -73,8 +74,8 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
 
     TextValue colorModeValue;
 
-    public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder) {
-        super(builder);
+    public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder, boolean newStyleChannels) {
+        super(builder, newStyleChannels);
         colorModeValue = new TextValue();
     }
 
@@ -84,7 +85,8 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
         if (supportedColorModes != null && supportedColorModes.contains(LightColorMode.COLOR_MODE_COLOR_TEMP)) {
             colorModeValue = new TextValue(
                     supportedColorModes.stream().map(LightColorMode::serializedName).toArray(String[]::new));
-            buildChannel(COLOR_MODE_CHANNEL_ID, colorModeValue, "Color Mode", this).isAdvanced(true).build();
+            buildChannel(COLOR_MODE_CHANNEL_ID, ComponentChannelType.STRING, colorModeValue, "Color Mode", this)
+                    .isAdvanced(true).build();
         }
 
         if (channelConfiguration.colorMode) {
@@ -98,26 +100,27 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
             }
 
             if (supportedColorModes.contains(LightColorMode.COLOR_MODE_COLOR_TEMP)) {
-                buildChannel(COLOR_TEMP_CHANNEL_ID, colorTempValue, "Color Temperature", this)
-                        .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleColorTempCommand(command))
-                        .build();
+                buildChannel(COLOR_TEMP_CHANNEL_ID, ComponentChannelType.NUMBER, colorTempValue, "Color Temperature",
+                        this).commandTopic(DUMMY_TOPIC, true, 1)
+                        .commandFilter(command -> handleColorTempCommand(command)).build();
             }
         }
 
         if (hasColorChannel) {
-            buildChannel(COLOR_CHANNEL_ID, colorValue, "Color", this).commandTopic(DUMMY_TOPIC, true, 1)
-                    .commandFilter(this::handleCommand).build();
-        } else if (channelConfiguration.brightness) {
-            brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, brightnessValue, "Brightness", this)
+            buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this)
                     .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
+        } else if (channelConfiguration.brightness) {
+            brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, ComponentChannelType.DIMMER, brightnessValue,
+                    "Brightness", this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
         } else {
-            onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, onOffValue, "On/Off State", this)
-                    .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
+            onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, ComponentChannelType.SWITCH, onOffValue, "On/Off State",
+                    this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
         }
 
         if (effectValue != null) {
-            buildChannel(EFFECT_CHANNEL_ID, Objects.requireNonNull(effectValue), "Lighting Effect", this)
-                    .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleEffectCommand(command)).build();
+            buildChannel(EFFECT_CHANNEL_ID, ComponentChannelType.STRING, Objects.requireNonNull(effectValue),
+                    "Lighting Effect", this).commandTopic(DUMMY_TOPIC, true, 1)
+                    .commandFilter(command -> handleEffectCommand(command)).build();
 
         }
     }
index ad1c82d6988ffe7ef9ec62fed159a2273084ac5c..db6baee7ba164c45da96ce2ad04629f0cf42d0b6 100644 (file)
@@ -245,21 +245,22 @@ public abstract class Light extends AbstractComponent<Light.ChannelConfiguration
 
     protected final ChannelStateUpdateListener channelStateUpdateListener;
 
-    public static Light create(ComponentFactory.ComponentConfiguration builder) throws UnsupportedComponentException {
+    public static Light create(ComponentFactory.ComponentConfiguration builder, boolean newStyleChannels)
+            throws UnsupportedComponentException {
         String schema = builder.getConfig(ChannelConfiguration.class).schema;
         switch (schema) {
             case DEFAULT_SCHEMA:
-                return new DefaultSchemaLight(builder);
+                return new DefaultSchemaLight(builder, newStyleChannels);
             case JSON_SCHEMA:
-                return new JSONSchemaLight(builder);
+                return new JSONSchemaLight(builder, newStyleChannels);
             default:
                 throw new UnsupportedComponentException(
                         "Component '" + builder.getHaID() + "' of schema '" + schema + "' is not supported!");
         }
     }
 
-    protected Light(ComponentFactory.ComponentConfiguration builder) {
-        super(builder, ChannelConfiguration.class);
+    protected Light(ComponentFactory.ComponentConfiguration builder, boolean newStyleChannels) {
+        super(builder, ChannelConfiguration.class, newStyleChannels);
         this.channelStateUpdateListener = builder.getUpdateListener();
 
         @Nullable
index eab24ceffc7d6397bab73884b76ef9942cbb47f9..ce3317256a620a707d71ace09cfbc64651cbaf47 100644 (file)
@@ -17,6 +17,7 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
 import org.openhab.binding.mqtt.generic.values.OnOffValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.StringType;
@@ -72,8 +73,8 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
     private OnOffValue lockValue;
     private TextValue stateValue;
 
-    public Lock(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Lock(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         this.optimistic = channelConfiguration.optimistic || channelConfiguration.stateTopic.isBlank();
 
@@ -82,7 +83,8 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
                         channelConfiguration.stateUnlocking, channelConfiguration.stateJammed },
                 channelConfiguration.payloadLock, channelConfiguration.payloadUnlock);
 
-        buildChannel(LOCK_CHANNEL_ID, lockValue, "Lock", componentConfiguration.getUpdateListener())
+        buildChannel(LOCK_CHANNEL_ID, ComponentChannelType.SWITCH, lockValue, "Lock",
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos())
@@ -103,7 +105,8 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
         stateValue = new TextValue(new String[] { channelConfiguration.stateJammed, channelConfiguration.stateLocked,
                 channelConfiguration.stateLocking, channelConfiguration.stateUnlocked,
                 channelConfiguration.stateUnlocking }, commands);
-        buildChannel(STATE_CHANNEL_ID, stateValue, "State", componentConfiguration.getUpdateListener())
+        buildChannel(STATE_CHANNEL_ID, ComponentChannelType.STRING, stateValue, "State",
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos())
index 73635d99c6ed2b87e6de609c4f28636bf6006fd7..1d03661f880e757d7521cd10e5d24114bd4c28e9 100644 (file)
@@ -17,6 +17,7 @@ import java.math.BigDecimal;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.NumberValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
 import org.openhab.core.types.util.UnitUtils;
@@ -69,8 +70,8 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
         protected @Nullable String jsonAttributesTemplate;
     }
 
-    public Number(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Number(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic
                 : channelConfiguration.stateTopic.isBlank();
@@ -82,7 +83,8 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
         NumberValue value = new NumberValue(channelConfiguration.min, channelConfiguration.max,
                 channelConfiguration.step, UnitUtils.parseUnit(channelConfiguration.unitOfMeasurement));
 
-        buildChannel(NUMBER_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
+        buildChannel(NUMBER_CHANNEL_ID, ComponentChannelType.NUMBER, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos(), channelConfiguration.commandTemplate)
index 85df85b4796a030118cb7710b4078c1712c3d627..d6e66a2bcb2583ed64c4509c89e1162a38642dd4 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.thing.type.AutoUpdatePolicy;
 
@@ -44,12 +45,13 @@ public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
         protected String payloadOn = "ON";
     }
 
-    public Scene(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Scene(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         TextValue value = new TextValue(new String[] { channelConfiguration.payloadOn });
 
-        buildChannel(SCENE_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
+        buildChannel(SCENE_CHANNEL_ID, ComponentChannelType.STRING, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos())
                 .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
index 0d54c4ca1578303684759b8286b7ab17757c236c..b3ceccafe67c38dbd77363570b23856f172cca18 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.TextValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
 
@@ -54,8 +55,8 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
         protected @Nullable String jsonAttributesTemplate;
     }
 
-    public Select(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Select(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic
                 : channelConfiguration.stateTopic.isBlank();
@@ -66,7 +67,8 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
 
         TextValue value = new TextValue(channelConfiguration.options);
 
-        buildChannel(SELECT_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
+        buildChannel(SELECT_CHANNEL_ID, ComponentChannelType.STRING, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos(), channelConfiguration.commandTemplate)
index 26b6c704d6ae89f0f5c1d95d4478b34197751d94..9472490ca68c0f6c940bb95e7a4db1f27f6729b2 100644 (file)
@@ -21,6 +21,7 @@ import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
 import org.openhab.binding.mqtt.generic.values.NumberValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
 import org.openhab.core.types.util.UnitUtils;
@@ -67,28 +68,32 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
         protected @Nullable List<String> jsonAttributes;
     }
 
-    public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         Value value;
         String uom = channelConfiguration.unitOfMeasurement;
         String sc = channelConfiguration.stateClass;
+        ComponentChannelType type;
 
         if (uom != null && !uom.isBlank()) {
             value = new NumberValue(null, null, null, UnitUtils.parseUnit(uom));
+            type = ComponentChannelType.NUMBER;
         } else if (sc != null && !sc.isBlank()) {
             // see state_class at https://developers.home-assistant.io/docs/core/entity/sensor#properties
             // > If not None, the sensor is assumed to be numerical
             value = new NumberValue(null, null, null, null);
+            type = ComponentChannelType.NUMBER;
         } else {
             value = new TextValue();
+            type = ComponentChannelType.STRING;
         }
 
         String icon = channelConfiguration.getIcon();
 
         boolean trigger = TRIGGER_ICONS.matcher(icon).matches();
 
-        buildChannel(SENSOR_CHANNEL_ID, value, getName(), getListener(componentConfiguration, value))
+        buildChannel(SENSOR_CHANNEL_ID, type, value, getName(), getListener(componentConfiguration, value))
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())//
                 .trigger(trigger).build();
     }
index 83521651f8d7971a23d0259ef8f670471c4476d3..b1288c151d374edf91095200141f4a153553a865 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.values.OnOffValue;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
 
@@ -59,8 +60,8 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
         protected @Nullable String jsonAttributesTemplate;
     }
 
-    public Switch(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Switch(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
 
         boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic
                 : channelConfiguration.stateTopic.isBlank();
@@ -72,7 +73,8 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
         OnOffValue value = new OnOffValue(channelConfiguration.stateOn, channelConfiguration.stateOff,
                 channelConfiguration.payloadOn, channelConfiguration.payloadOff);
 
-        buildChannel(SWITCH_CHANNEL_ID, value, "state", componentConfiguration.getUpdateListener())
+        buildChannel(SWITCH_CHANNEL_ID, ComponentChannelType.SWITCH, value, getName(),
+                componentConfiguration.getUpdateListener())
                 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
                 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
                         channelConfiguration.getQos())
index 035ecd832e694b7447e010b6e5a1d1759efa3731..5ea64b4a24663a6dd73e2d58b097c5a5a7928af7 100644 (file)
@@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.library.types.StringType;
@@ -142,14 +143,14 @@ public class Update extends AbstractComponent<Update.ChannelConfiguration> imple
     private ReleaseState state = new ReleaseState();
     private @Nullable ReleaseStateListener listener = null;
 
-    public Update(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Update(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
 
         TextValue value = new TextValue();
         String commandTopic = channelConfiguration.commandTopic;
         String payloadInstall = channelConfiguration.payloadInstall;
 
-        var builder = buildChannel(UPDATE_CHANNEL_ID, value, getName(), this);
+        var builder = buildChannel(UPDATE_CHANNEL_ID, ComponentChannelType.STRING, value, getName(), this);
         if (channelConfiguration.stateTopic != null) {
             builder.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate());
         }
@@ -162,7 +163,8 @@ public class Update extends AbstractComponent<Update.ChannelConfiguration> imple
 
         if (channelConfiguration.latestVersionTopic != null) {
             value = new TextValue();
-            latestVersionChannel = buildChannel(LATEST_VERSION_CHANNEL_ID, value, getName(), this)
+            latestVersionChannel = buildChannel(LATEST_VERSION_CHANNEL_ID, ComponentChannelType.STRING, value,
+                    getName(), this)
                     .stateTopic(channelConfiguration.latestVersionTopic, channelConfiguration.latestVersionTemplate)
                     .build(false);
         }
index 964c24b3a9e7df954cef3fab6e2a9848830294eb..0c5185267230ac6cfd863c8b80587c0da9b95c34 100644 (file)
@@ -27,6 +27,7 @@ import org.openhab.binding.mqtt.generic.values.PercentageValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.generic.values.Value;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
+import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -188,8 +189,8 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
      *
      * @param componentConfiguration generic componentConfiguration with not parsed JSON config
      */
-    public Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) {
-        super(componentConfiguration, ChannelConfiguration.class);
+    public Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+        super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
         final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
 
         final var allowedSupportedFeatures = channelConfiguration.schema == Schema.LEGACY ? LEGACY_SUPPORTED_FEATURES
@@ -226,8 +227,8 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
             addPayloadToList(deviceSupportedFeatures, FEATURE_START, channelConfiguration.payloadStart, commands);
         }
 
-        buildOptionalChannel(COMMAND_CH_ID, new TextValue(commands.toArray(new String[0])), updateListener, null,
-                channelConfiguration.commandTopic, null, null);
+        buildOptionalChannel(COMMAND_CH_ID, ComponentChannelType.STRING, new TextValue(commands.toArray(new String[0])),
+                updateListener, null, channelConfiguration.commandTopic, null, null);
 
         final var fanSpeedList = channelConfiguration.fanSpeedList;
         if (deviceSupportedFeatures.contains(FEATURE_FAN_SPEED) && fanSpeedList != null && !fanSpeedList.isEmpty()) {
@@ -236,48 +237,50 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
             }
             var fanSpeedValue = new TextValue(fanSpeedList.toArray(new String[0]));
             if (channelConfiguration.schema == Schema.LEGACY) {
-                buildOptionalChannel(FAN_SPEED_CH_ID, fanSpeedValue, updateListener, null,
+                buildOptionalChannel(FAN_SPEED_CH_ID, ComponentChannelType.STRING, fanSpeedValue, updateListener, null,
                         channelConfiguration.setFanSpeedTopic, channelConfiguration.fanSpeedTemplate,
                         channelConfiguration.fanSpeedTopic);
             } else if (deviceSupportedFeatures.contains(FEATURE_STATUS)) {
-                buildOptionalChannel(FAN_SPEED_CH_ID, fanSpeedValue, updateListener, null,
+                buildOptionalChannel(FAN_SPEED_CH_ID, ComponentChannelType.STRING, fanSpeedValue, updateListener, null,
                         channelConfiguration.setFanSpeedTopic, "{{ value_json.fan_speed }}",
                         channelConfiguration.stateTopic);
             } else {
                 LOGGER.info("Status feature is disabled, unable to get fan speed.");
-                buildOptionalChannel(FAN_SPEED_CH_ID, fanSpeedValue, updateListener, null,
+                buildOptionalChannel(FAN_SPEED_CH_ID, ComponentChannelType.STRING, fanSpeedValue, updateListener, null,
                         channelConfiguration.setFanSpeedTopic, null, null);
             }
         }
 
         if (deviceSupportedFeatures.contains(FEATURE_SEND_COMMAND)) {
-            buildOptionalChannel(CUSTOM_COMMAND_CH_ID, new TextValue(), updateListener, null,
-                    channelConfiguration.sendCommandTopic, null, null);
+            buildOptionalChannel(CUSTOM_COMMAND_CH_ID, ComponentChannelType.STRING, new TextValue(), updateListener,
+                    null, channelConfiguration.sendCommandTopic, null, null);
         }
 
         if (channelConfiguration.schema == Schema.LEGACY) {
             // I assume, that if these topics defined in config, then we don't need to check features
-            buildOptionalChannel(BATTERY_LEVEL_CH_ID,
+            buildOptionalChannel(BATTERY_LEVEL_CH_ID, ComponentChannelType.DIMMER,
                     new PercentageValue(BigDecimal.ZERO, BigDecimal.valueOf(100), BigDecimal.ONE, null, null),
                     updateListener, null, null, channelConfiguration.batteryLevelTemplate,
                     channelConfiguration.batteryLevelTopic);
-            buildOptionalChannel(CHARGING_CH_ID, new OnOffValue(TRUE, FALSE), updateListener, null, null,
-                    channelConfiguration.chargingTemplate, channelConfiguration.chargingTopic);
-            buildOptionalChannel(CLEANING_CH_ID, new OnOffValue(TRUE, FALSE), updateListener, null, null,
-                    channelConfiguration.cleaningTemplate, channelConfiguration.cleaningTopic);
-            buildOptionalChannel(DOCKED_CH_ID, new OnOffValue(TRUE, FALSE), updateListener, null, null,
-                    channelConfiguration.dockedTemplate, channelConfiguration.dockedTopic);
-            buildOptionalChannel(ERROR_CH_ID, new TextValue(), updateListener, null, null,
+            buildOptionalChannel(CHARGING_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(TRUE, FALSE),
+                    updateListener, null, null, channelConfiguration.chargingTemplate,
+                    channelConfiguration.chargingTopic);
+            buildOptionalChannel(CLEANING_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(TRUE, FALSE),
+                    updateListener, null, null, channelConfiguration.cleaningTemplate,
+                    channelConfiguration.cleaningTopic);
+            buildOptionalChannel(DOCKED_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(TRUE, FALSE), updateListener,
+                    null, null, channelConfiguration.dockedTemplate, channelConfiguration.dockedTopic);
+            buildOptionalChannel(ERROR_CH_ID, ComponentChannelType.STRING, new TextValue(), updateListener, null, null,
                     channelConfiguration.errorTemplate, channelConfiguration.errorTopic);
         } else {
             if (deviceSupportedFeatures.contains(FEATURE_STATUS)) {
                 // state key is mandatory
-                buildOptionalChannel(STATE_CH_ID,
+                buildOptionalChannel(STATE_CH_ID, ComponentChannelType.STRING,
                         new TextValue(new String[] { STATE_CLEANING, STATE_DOCKED, STATE_PAUSED, STATE_IDLE,
                                 STATE_RETURNING, STATE_ERROR }),
                         updateListener, null, null, "{{ value_json.state }}", channelConfiguration.stateTopic);
                 if (deviceSupportedFeatures.contains(FEATURE_BATTERY)) {
-                    buildOptionalChannel(BATTERY_LEVEL_CH_ID,
+                    buildOptionalChannel(BATTERY_LEVEL_CH_ID, ComponentChannelType.DIMMER,
                             new PercentageValue(BigDecimal.ZERO, BigDecimal.valueOf(100), BigDecimal.ONE, null, null),
                             updateListener, null, null, "{{ value_json.battery_level }}",
                             channelConfiguration.stateTopic);
@@ -285,16 +288,16 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
             }
         }
 
-        buildOptionalChannel(JSON_ATTRIBUTES_CH_ID, new TextValue(), updateListener, null, null,
-                channelConfiguration.jsonAttributesTemplate, channelConfiguration.jsonAttributesTopic);
+        buildOptionalChannel(JSON_ATTRIBUTES_CH_ID, ComponentChannelType.STRING, new TextValue(), updateListener, null,
+                null, channelConfiguration.jsonAttributesTemplate, channelConfiguration.jsonAttributesTopic);
     }
 
     @Nullable
-    private ComponentChannel buildOptionalChannel(String channelId, Value valueState,
+    private ComponentChannel buildOptionalChannel(String channelId, ComponentChannelType channelType, Value valueState,
             ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,
             @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic) {
         if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
-            return buildChannel(channelId, valueState, getName(), channelStateUpdateListener)
+            return buildChannel(channelId, channelType, valueState, getName(), channelStateUpdateListener)
                     .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
                             commandTemplate)
index 4626a9860468fe804b46d06efe40c273560777b7..ca675d346f8a1988cde7a018772fa0d653567c61 100644 (file)
@@ -145,7 +145,7 @@ public class HomeAssistantDiscovery extends AbstractMQTTDiscovery {
 
     @Override
     public Set<ThingTypeUID> getSupportedThingTypes() {
-        return typeProvider.getThingTypeUIDs();
+        return typeProvider.getThingTypes(null).stream().map(ThingType::getUID).collect(Collectors.toSet());
     }
 
     /**
@@ -206,11 +206,8 @@ public class HomeAssistantDiscovery extends AbstractMQTTDiscovery {
                     .fromString(new String(payload, StandardCharsets.UTF_8), gson);
 
             final String thingID = config.getThingId(haID.objectID);
-
-            final ThingTypeUID typeID = new ThingTypeUID(MqttBindingConstants.BINDING_ID,
-                    MqttBindingConstants.HOMEASSISTANT_MQTT_THING.getId() + "_" + thingID);
-
-            final ThingUID thingUID = new ThingUID(typeID, connectionBridge, thingID);
+            final ThingUID thingUID = new ThingUID(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, connectionBridge,
+                    thingID);
 
             thingIDPerTopic.put(topic, thingUID);
 
@@ -241,6 +238,7 @@ public class HomeAssistantDiscovery extends AbstractMQTTDiscovery {
             properties = handlerConfig.appendToProperties(properties);
             properties = config.appendToProperties(properties);
             properties.put("deviceId", thingID);
+            properties.put("newStyleChannels", "true");
 
             // Because we need the new properties map with the updated "components" list
             results.put(thingUID.getAsString(),
@@ -282,10 +280,6 @@ public class HomeAssistantDiscovery extends AbstractMQTTDiscovery {
         results.clear();
         componentsPerThingID.clear();
         for (DiscoveryResult result : localResults) {
-            final ThingTypeUID typeID = result.getThingTypeUID();
-            ThingType type = typeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING).build();
-            typeProvider.setThingTypeIfAbsent(typeID, type);
-
             thingDiscovered(result);
         }
     }
index eb6aa2f0d7f400b82149a76dbbaf96a20857a5ed..aa2ee5fcfe5e3e88518d97b9afc830122a49bb5b 100644 (file)
@@ -13,7 +13,6 @@
 package org.openhab.binding.mqtt.homeassistant.internal.handler;
 
 import java.net.URI;
-import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -24,12 +23,12 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 
 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.TransformationServiceProvider;
 import org.openhab.binding.mqtt.generic.tools.DelayedBatchProcessing;
@@ -48,7 +47,6 @@ import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationEx
 import org.openhab.core.config.core.validation.ConfigValidationException;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelGroupUID;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
@@ -56,10 +54,7 @@ import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.binding.builder.ThingBuilder;
-import org.openhab.core.thing.type.ChannelDefinition;
-import org.openhab.core.thing.type.ChannelGroupDefinition;
-import org.openhab.core.thing.type.ThingType;
-import org.openhab.core.thing.util.ThingHelper;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -86,13 +81,16 @@ import com.google.gson.GsonBuilder;
 public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
         implements ComponentDiscovered, Consumer<List<AbstractComponent<?>>> {
     public static final String AVAILABILITY_CHANNEL = "availability";
-    private static final Comparator<Channel> CHANNEL_COMPARATOR_BY_UID = Comparator
-            .comparing(channel -> channel.getUID().toString());
+    private static final Comparator<AbstractComponent<?>> COMPONENT_COMPARATOR = Comparator
+            .comparing((AbstractComponent<?> component) -> component.hasGroup())
+            .thenComparing(AbstractComponent::getName);
     private static final URI UPDATABLE_CONFIG_DESCRIPTION_URI = URI.create("thing-type:mqtt:homeassistant-updatable");
 
     private final Logger logger = LoggerFactory.getLogger(HomeAssistantThingHandler.class);
 
     protected final MqttChannelTypeProvider channelTypeProvider;
+    protected final MqttChannelStateDescriptionProvider stateDescriptionProvider;
+    protected final ChannelTypeRegistry channelTypeRegistry;
     public final int attributeReceiveTimeout;
     protected final DelayedBatchProcessing<AbstractComponent<?>> delayedProcessing;
     protected final DiscoverComponents discoverComponents;
@@ -106,6 +104,7 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
     protected final TransformationServiceProvider transformationServiceProvider;
 
     private boolean started;
+    private boolean newStyleChannels;
     private @Nullable Update updateComponent;
 
     /**
@@ -118,16 +117,22 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
      * @param attributeReceiveTimeout The timeout per attribute field subscription. In milliseconds.
      */
     public HomeAssistantThingHandler(Thing thing, MqttChannelTypeProvider channelTypeProvider,
+            MqttChannelStateDescriptionProvider stateDescriptionProvider, ChannelTypeRegistry channelTypeRegistry,
             TransformationServiceProvider transformationServiceProvider, int subscribeTimeout,
             int attributeReceiveTimeout) {
         super(thing, subscribeTimeout);
         this.gson = new GsonBuilder().registerTypeAdapterFactory(new ChannelConfigurationTypeAdapterFactory()).create();
         this.channelTypeProvider = channelTypeProvider;
+        this.stateDescriptionProvider = stateDescriptionProvider;
+        this.channelTypeRegistry = channelTypeRegistry;
         this.transformationServiceProvider = transformationServiceProvider;
         this.attributeReceiveTimeout = attributeReceiveTimeout;
         this.delayedProcessing = new DelayedBatchProcessing<>(attributeReceiveTimeout, this, scheduler);
+
+        newStyleChannels = "true".equals(thing.getProperties().get("newStyleChannels"));
+
         this.discoverComponents = new DiscoverComponents(thing.getUID(), scheduler, this, this, gson,
-                this.transformationServiceProvider);
+                this.transformationServiceProvider, newStyleChannels);
     }
 
     @Override
@@ -141,16 +146,12 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
         }
         discoveryHomeAssistantIDs.addAll(HaID.fromConfig(config));
 
+        ThingTypeUID typeID = getThing().getThingTypeUID();
         for (Channel channel : thing.getChannels()) {
             final String groupID = channel.getUID().getGroupId();
             // Already restored component?
             @Nullable
             AbstractComponent<?> component = haComponents.get(groupID);
-            if (component != null) {
-                // the types may have been removed in dispose() so we need to add them again
-                component.addChannelTypes(channelTypeProvider);
-                continue;
-            }
 
             HaID haID = HaID.fromConfig(config.basetopic, channel.getConfiguration());
             discoveryHomeAssistantIDs.add(haID);
@@ -161,29 +162,27 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
             } else {
                 try {
                     component = ComponentFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this,
-                            scheduler, gson, transformationServiceProvider);
-                    final ChannelGroupUID groupUID = component.getGroupUID();
-                    String id = null;
-                    if (groupUID != null) {
-                        id = groupUID.getId();
+                            scheduler, gson, transformationServiceProvider, newStyleChannels);
+                    if (typeID.equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) {
+                        typeID = calculateThingTypeUID(component);
                     }
-                    haComponents.put(id, component);
-                    component.addChannelTypes(channelTypeProvider);
+
+                    haComponents.put(component.getGroupId(), component);
                 } catch (ConfigurationException e) {
-                    logger.error("Cannot not restore component {}: {}", thing, e.getMessage());
+                    logger.error("Cannot restore component {}: {}", thing, e.getMessage());
                 }
             }
         }
-        updateThingType();
-
-        super.initialize();
+        if (updateThingType(typeID)) {
+            super.initialize();
+        }
     }
 
     @Override
     public void dispose() {
+        removeStateDescriptions();
         // super.dispose() calls stop()
         super.dispose();
-        haComponents.values().forEach(c -> c.removeChannelTypes(channelTypeProvider));
     }
 
     @Override
@@ -234,13 +233,21 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
 
     @Override
     public @Nullable ChannelState getChannelState(ChannelUID channelUID) {
-        String groupID = channelUID.getGroupId();
+        String componentId;
+        if (channelUID.isInGroup()) {
+            componentId = channelUID.getGroupId();
+        } else {
+            componentId = channelUID.getId();
+        }
         AbstractComponent<?> component;
         synchronized (haComponents) { // sync whenever discoverComponents is started
-            component = haComponents.get(groupID);
+            component = haComponents.get(componentId);
         }
         if (component == null) {
-            return null;
+            component = haComponents.get("");
+            if (component == null) {
+                return null;
+            }
         }
         ComponentChannel componentChannel = component.getChannel(channelUID.getIdWithoutGroup());
         if (componentChannel == null) {
@@ -269,12 +276,12 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
         }
 
         synchronized (haComponents) { // sync whenever discoverComponents is started
+            ThingTypeUID typeID = getThing().getThingTypeUID();
             for (AbstractComponent<?> discovered : discoveredComponentsList) {
-                final ChannelGroupUID groupUID = discovered.getGroupUID();
-                String id = null;
-                if (groupUID != null) {
-                    id = groupUID.getId();
+                if (typeID.equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) {
+                    typeID = calculateThingTypeUID(discovered);
                 }
+                String id = discovered.getGroupId();
                 AbstractComponent<?> known = haComponents.get(id);
                 // Is component already known?
                 if (known != null) {
@@ -288,8 +295,6 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
                     }
                 }
 
-                // Add channel and group types to the types registry
-                discovered.addChannelTypes(channelTypeProvider);
                 // Add component to the component map
                 haComponents.put(id, discovered);
                 // Start component / Subscribe to channel topics
@@ -302,47 +307,11 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
                     updateComponent = (Update) discovered;
                     updateComponent.setReleaseStateUpdateListener(this::releaseStateUpdated);
                 }
-
-                List<Channel> discoveredChannels = discovered.getChannelMap().values().stream()
-                        .map(ComponentChannel::getChannel).collect(Collectors.toList());
-                if (known != null) {
-                    // We had previously known component with different config hash
-                    // We remove all conflicting old channels, they will be re-added below based on the new discovery
-                    logger.debug(
-                            "Received component {} with slightly different config. Making sure we re-create conflicting channels...",
-                            discovered.getHaID());
-                    removeJustRediscoveredChannels(discoveredChannels);
-                }
-
-                // Add newly discovered channels. We sort the channels
-                // for (mostly) consistent jsondb serialization
-                discoveredChannels.sort(CHANNEL_COMPARATOR_BY_UID);
-                ThingHelper.addChannelsToThing(thing, discoveredChannels);
             }
-            updateThingType();
-        }
-    }
-
-    private void removeJustRediscoveredChannels(List<Channel> discoveredChannels) {
-        ArrayList<Channel> mutableChannels = new ArrayList<>(getThing().getChannels());
-        Set<ChannelUID> newChannelUIDs = discoveredChannels.stream().map(Channel::getUID).collect(Collectors.toSet());
-        // Take current channels but remove those channels that were just re-discovered
-        List<Channel> existingChannelsWithNewlyDiscoveredChannelsRemoved = mutableChannels.stream()
-                .filter(existingChannel -> !newChannelUIDs.contains(existingChannel.getUID()))
-                .collect(Collectors.toList());
-        if (existingChannelsWithNewlyDiscoveredChannelsRemoved.size() < mutableChannels.size()) {
-            // We sort the channels for (mostly) consistent jsondb serialization
-            existingChannelsWithNewlyDiscoveredChannelsRemoved.sort(CHANNEL_COMPARATOR_BY_UID);
-            updateThingChannels(existingChannelsWithNewlyDiscoveredChannelsRemoved);
+            updateThingType(typeID);
         }
     }
 
-    private void updateThingChannels(List<Channel> channelList) {
-        ThingBuilder thingBuilder = editThing();
-        thingBuilder.withChannels(channelList);
-        updateThing(thingBuilder.build());
-    }
-
     @Override
     protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
         if (availabilityTopicsSeen.orElse(messageReceived)) {
@@ -372,28 +341,72 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
         super.handleConfigurationUpdate(configurationParameters);
     }
 
-    private void updateThingType() {
+    private boolean updateThingType(ThingTypeUID typeID) {
         // if this is a dynamic type, then we update the type
-        ThingTypeUID typeID = thing.getThingTypeUID();
         if (!MqttBindingConstants.HOMEASSISTANT_MQTT_THING.equals(typeID)) {
-            List<ChannelGroupDefinition> groupDefs;
-            List<ChannelDefinition> channelDefs;
-            synchronized (haComponents) { // sync whenever discoverComponents is started
-                groupDefs = haComponents.values().stream().map(AbstractComponent::getGroupDefinition)
-                        .filter(Objects::nonNull).map(Objects::requireNonNull).collect(Collectors.toList());
-                channelDefs = haComponents.values().stream().map(AbstractComponent::getChannels).flatMap(List::stream)
-                        .collect(Collectors.toList());
+            var thingTypeBuilder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING);
+
+            if (getThing().getThingTypeUID().equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) {
+                logger.debug("Migrating Home Assistant thing {} from generic type to dynamic type {}",
+                        getThing().getUID(), typeID);
+
+                // just create an empty thing type for now; channel configurations won't follow over
+                // to the re-created Thing, so we need to re-discover them all anyway
+                channelTypeProvider.putThingType(thingTypeBuilder.build());
+                changeThingType(typeID, getConfig());
+                return false;
             }
-            var builder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING)
-                    .withChannelDefinitions(channelDefs).withChannelGroupDefinitions(groupDefs);
-            Update updateComponent = this.updateComponent;
-            if (updateComponent != null && updateComponent.isUpdatable()) {
-                builder.withConfigDescriptionURI(UPDATABLE_CONFIG_DESCRIPTION_URI);
+
+            synchronized (haComponents) { // sync whenever discoverComponents is started
+                var sortedComponents = haComponents.values().stream().sorted(COMPONENT_COMPARATOR).toList();
+
+                var channelGroupTypes = sortedComponents.stream().map(c -> c.getChannelGroupType(typeID.getId()))
+                        .filter(Objects::nonNull).map(Objects::requireNonNull).toList();
+                channelTypeProvider.updateChannelGroupTypesForPrefix(typeID.getId(), channelGroupTypes);
+
+                var groupDefs = sortedComponents.stream().map(c -> c.getGroupDefinition(typeID.getId()))
+                        .filter(Objects::nonNull).map(Objects::requireNonNull).toList();
+                var channelDefs = sortedComponents.stream().map(AbstractComponent::getChannelDefinitions)
+                        .flatMap(List::stream).toList();
+                thingTypeBuilder.withChannelDefinitions(channelDefs).withChannelGroupDefinitions(groupDefs);
+                Update updateComponent = this.updateComponent;
+                if (updateComponent != null && updateComponent.isUpdatable()) {
+                    thingTypeBuilder.withConfigDescriptionURI(UPDATABLE_CONFIG_DESCRIPTION_URI);
+                }
+
+                channelTypeProvider.putThingType(thingTypeBuilder.build());
+
+                removeStateDescriptions();
+                sortedComponents.stream().forEach(c -> c.addStateDescriptions(stateDescriptionProvider));
+
+                ThingBuilder thingBuilder = editThing().withChannels();
+
+                sortedComponents.stream().map(AbstractComponent::getChannels).flatMap(List::stream)
+                        .forEach(c -> thingBuilder.withChannel(c));
+
+                updateThing(thingBuilder.build());
             }
-            ThingType thingType = builder.build();
+        }
+        return true;
+    }
 
-            channelTypeProvider.setThingType(typeID, thingType);
+    private ThingTypeUID calculateThingTypeUID(AbstractComponent component) {
+        return new ThingTypeUID(MqttBindingConstants.BINDING_ID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING.getId()
+                + "_" + component.getChannelConfiguration().getThingId(component.getHaID().objectID));
+    }
+
+    @Override
+    public void handleRemoval() {
+        synchronized (haComponents) {
+            channelTypeProvider.removeThingType(thing.getThingTypeUID());
+            channelTypeProvider.removeChannelGroupTypesForPrefix(thing.getThingTypeUID().getId());
+            removeStateDescriptions();
         }
+        super.handleRemoval();
+    }
+
+    private void removeStateDescriptions() {
+        thing.getChannels().stream().forEach(c -> stateDescriptionProvider.remove(c.getUID()));
     }
 
     private void releaseStateUpdated(Update.ReleaseState state) {
index 64cbf21ea2977981681c1148f2b33794ffc2b62d..d84743bc7331d9bfda6420e33480f159c7b9f352 100644 (file)
@@ -7,7 +7,7 @@
        <config-description uri="channel-type:mqtt:ha-channel">
                <parameter name="component" type="text" readOnly="true" required="true">
                        <label>Component</label>
-                       <description>HomeAssistant component type (e.g. binary_sensor, switch, light)</description>
+                       <description>Home Assistant component type (e.g. binary_sensor, switch, light)</description>
                        <default></default>
                </parameter>
                <parameter name="nodeid" type="text" readOnly="true">
                </parameter>
                <parameter name="objectid" type="text" readOnly="true" required="true">
                        <label>Object ID</label>
-                       <description>Object id of the component</description>
+                       <description>Object ID of the component</description>
                        <default></default>
                </parameter>
                <parameter name="config" type="text" readOnly="true" required="true">
-                       <label>Json Configuration</label>
-                       <description>The json configuration string received by the component via MQTT.</description>
+                       <label>JSON Configuration</label>
+                       <description>The JSON configuration string received by the component via MQTT.</description>
                        <default></default>
                </parameter>
        </config-description>
index 408ba3982639b90de334397abd05efea4052e077..22cb9fb44fb4388364cfe263afa104d390d9b803 100644 (file)
@@ -9,6 +9,39 @@ thing-type.config.mqtt.homeassistant.basetopic.label = MQTT Base Prefix
 thing-type.config.mqtt.homeassistant.basetopic.description = MQTT base prefix
 thing-type.config.mqtt.homeassistant.topics.label = MQTT Config Topic
 thing-type.config.mqtt.homeassistant.topics.description = List of Home Assistant configuration topics (e.g. button/my-device/restart)
+
+# channel types
+
+channel-type.mqtt.ha-color-advanced.label = Color
+channel-type.mqtt.ha-color.label = Color
+channel-type.mqtt.ha-dimmer-advanced.label = Dimmer
+channel-type.mqtt.ha-dimmer.label = Dimmer
+channel-type.mqtt.ha-image-advanced.label = Image
+channel-type.mqtt.ha-image.label = Image
+channel-type.mqtt.ha-number-advanced.label = Number
+channel-type.mqtt.ha-number.label = Number
+channel-type.mqtt.ha-rollershutter-advanced.label = Rollershutter
+channel-type.mqtt.ha-rollershutter.label = Rollershutter
+channel-type.mqtt.ha-string-advanced.label = String
+channel-type.mqtt.ha-string.label = String
+channel-type.mqtt.ha-switch-advanced.label = Switch
+channel-type.mqtt.ha-switch.label = Switch
+channel-type.mqtt.ha-trigger-advanced.label = Trigger
+channel-type.mqtt.ha-trigger.label = Trigger
+
+# channel types config
+
+channel-type.config.mqtt.ha-channel.component.label = Component
+channel-type.config.mqtt.ha-channel.component.description = Home Assistant component type (e.g. binary_sensor, switch, light)
+channel-type.config.mqtt.ha-channel.config.label = JSON Configuration
+channel-type.config.mqtt.ha-channel.config.description = The JSON configuration string received by the component via MQTT.
+channel-type.config.mqtt.ha-channel.nodeid.label = Node ID
+channel-type.config.mqtt.ha-channel.nodeid.description = Optional node name of the component
+channel-type.config.mqtt.ha-channel.objectid.label = Object ID
+channel-type.config.mqtt.ha-channel.objectid.description = Object ID of the component
+
+# thing types config
+
 thing-type.config.mqtt.homeassistant-updatable.basetopic.label = MQTT Base Prefix
 thing-type.config.mqtt.homeassistant-updatable.basetopic.description = MQTT base prefix
 thing-type.config.mqtt.homeassistant-updatable.topics.label = MQTT Config Topic
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/thing/homeassistant-channels.xml b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/thing/homeassistant-channels.xml
new file mode 100644 (file)
index 0000000..cd46793
--- /dev/null
@@ -0,0 +1,102 @@
+<?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="ha-color">
+               <item-type>Color</item-type>
+               <label>Color</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-dimmer">
+               <item-type>Dimmer</item-type>
+               <label>Dimmer</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-image">
+               <item-type>Image</item-type>
+               <label>Image</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-number">
+               <item-type>Number</item-type>
+               <label>Number</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-rollershutter">
+               <item-type>Rollershutter</item-type>
+               <label>Rollershutter</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-string">
+               <item-type>String</item-type>
+               <label>String</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-switch">
+               <item-type>Switch</item-type>
+               <label>Switch</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-trigger">
+               <kind>trigger</kind>
+               <label>Trigger</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-color-advanced" advanced="true">
+               <item-type>Color</item-type>
+               <label>Color</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-dimmer-advanced" advanced="true">
+               <item-type>Dimmer</item-type>
+               <label>Dimmer</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-image-advanced" advanced="true">
+               <item-type>Image</item-type>
+               <label>Image</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-number-advanced" advanced="true">
+               <item-type>Number</item-type>
+               <label>Number</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-rollershutter-advanced" advanced="true">
+               <item-type>Rollershutter</item-type>
+               <label>Rollershutter</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-string-advanced" advanced="true">
+               <item-type>String</item-type>
+               <label>String</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-switch-advanced" advanced="true">
+               <item-type>Switch</item-type>
+               <label>Switch</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+
+       <channel-type id="ha-trigger-advanced" advanced="true">
+               <kind>trigger</kind>
+               <label>Trigger</label>
+               <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+       </channel-type>
+</thing:thing-descriptions>
index dbf052b2f47d3d8155f8e322d0f6fa21d35ca372..7c2d33c7b67a62d2b413f48ea1b85724eafae1ff 100644 (file)
@@ -35,12 +35,15 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
+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.handler.BrokerHandler;
+import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
 import org.openhab.core.test.java.JavaTest;
+import org.openhab.core.test.storage.VolatileStorageService;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
@@ -50,6 +53,8 @@ import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.binding.builder.BridgeBuilder;
 import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
+import org.openhab.core.thing.type.ThingType;
 import org.openhab.core.thing.type.ThingTypeBuilder;
 import org.openhab.core.thing.type.ThingTypeRegistry;
 import org.openhab.transform.jinja.internal.JinjaTransformationService;
@@ -72,17 +77,20 @@ public abstract class AbstractHomeAssistantTests extends JavaTest {
     public static final String BRIDGE_ID = UUID.randomUUID().toString();
     public static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE_UID, BRIDGE_ID);
 
-    public static final String HA_TYPE_ID = "homeassistant";
-    public static final String HA_TYPE_LABEL = "Homeassistant";
-    public static final ThingTypeUID HA_TYPE_UID = new ThingTypeUID(BINDING_ID, HA_TYPE_ID);
+    public static final String HA_TYPE_LABEL = "Home Assistant Thing";
+    public static final ThingTypeUID HA_TYPE_UID = new ThingTypeUID(BINDING_ID, "homeassistant_dynamic_type");
     public static final String HA_ID = UUID.randomUUID().toString();
-    public static final ThingUID HA_UID = new ThingUID(HA_TYPE_UID, HA_ID);
+    public static final ThingUID HA_UID = new ThingUID(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, HA_ID);
+    public static final ThingType HA_THING_TYPE = ThingTypeBuilder
+            .instance(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, HA_TYPE_LABEL).build();
 
     protected @Mock @NonNullByDefault({}) MqttBrokerConnection bridgeConnection;
     protected @Mock @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry;
     protected @Mock @NonNullByDefault({}) TransformationServiceProvider transformationServiceProvider;
 
     protected @NonNullByDefault({}) MqttChannelTypeProvider channelTypeProvider;
+    protected @NonNullByDefault({}) MqttChannelStateDescriptionProvider stateDescriptionProvider;
+    protected @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry;
 
     protected final Bridge bridgeThing = BridgeBuilder.create(BRIDGE_TYPE_UID, BRIDGE_UID).build();
     protected final BrokerHandler bridgeHandler = spy(new BrokerHandler(bridgeThing));
@@ -95,13 +103,14 @@ public abstract class AbstractHomeAssistantTests extends JavaTest {
     public void beforeEachAbstractHomeAssistantTests() {
         when(thingTypeRegistry.getThingType(BRIDGE_TYPE_UID))
                 .thenReturn(ThingTypeBuilder.instance(BRIDGE_TYPE_UID, BRIDGE_TYPE_LABEL).build());
-        when(thingTypeRegistry.getThingType(HA_TYPE_UID))
-                .thenReturn(ThingTypeBuilder.instance(HA_TYPE_UID, HA_TYPE_LABEL).build());
+        when(thingTypeRegistry.getThingType(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)).thenReturn(HA_THING_TYPE);
         when(transformationServiceProvider
                 .getTransformationService(JinjaTransformationProfile.PROFILE_TYPE_UID.getId()))
                 .thenReturn(jinjaTransformationService);
 
-        channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistry));
+        channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistry, new VolatileStorageService()));
+        stateDescriptionProvider = spy(new MqttChannelStateDescriptionProvider());
+        channelTypeRegistry = spy(new ChannelTypeRegistry());
 
         setupConnection();
 
index 4c089f72e184577cb2956696cde45ff1b5c40eb7..2910c9523b1a39edce63ac4bf1b7f3cf74c25b81 100644 (file)
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
+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.generic.values.Value;
@@ -45,6 +46,7 @@ import org.openhab.core.library.types.HSBType;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatusInfo;
 import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.type.ChannelTypeRegistry;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.State;
 
@@ -76,8 +78,8 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests
 
         when(callbackMock.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing);
 
-        thingHandler = new LatchThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
-                SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT);
+        thingHandler = new LatchThingHandler(haThing, channelTypeProvider, stateDescriptionProvider,
+                channelTypeRegistry, transformationServiceProvider, SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT);
         thingHandler.setConnection(bridgeConnection);
         thingHandler.setCallback(callbackMock);
         thingHandler = spy(thingHandler);
@@ -193,7 +195,7 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests
      */
     protected void assertTriggered(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component,
             String channelId, String trigger) {
-        verify(thingHandler).triggerChannel(eq(component.getChannel(channelId).getChannelUID()), eq(trigger));
+        verify(thingHandler).triggerChannel(eq(component.getChannel(channelId).getChannel().getUID()), eq(trigger));
     }
 
     /**
@@ -277,7 +279,7 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests
     protected void sendCommand(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component,
             String channelId, Command command) {
         var channel = Objects.requireNonNull(component.getChannel(channelId));
-        thingHandler.handleCommand(channel.getChannelUID(), command);
+        thingHandler.handleCommand(channel.getChannel().getUID(), command);
     }
 
     protected static class LatchThingHandler extends HomeAssistantThingHandler {
@@ -285,9 +287,11 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests
         private @Nullable AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoveredComponent;
 
         public LatchThingHandler(Thing thing, MqttChannelTypeProvider channelTypeProvider,
+                MqttChannelStateDescriptionProvider stateDescriptionProvider, ChannelTypeRegistry channelTypeRegistry,
                 TransformationServiceProvider transformationServiceProvider, int subscribeTimeout,
                 int attributeReceiveTimeout) {
-            super(thing, channelTypeProvider, transformationServiceProvider, subscribeTimeout, attributeReceiveTimeout);
+            super(thing, channelTypeProvider, stateDescriptionProvider, channelTypeRegistry,
+                    transformationServiceProvider, subscribeTimeout, attributeReceiveTimeout);
         }
 
         @Override
index 9f4d9bc52e0b85dbd6dda462ba46616fe4726d24..6c875e74b0cb787e79543f86dba0645b8f826d20 100644 (file)
@@ -65,9 +65,9 @@ public class BinarySensorTests extends AbstractComponentTests {
 
         assertThat(component.channels.size(), is(1));
         assertThat(component.getName(), is("onoffsensor"));
-        assertThat(component.getGroupUID().getId(), is("sn1"));
+        assertThat(component.getGroupId(), is("sn1"));
 
-        assertChannel(component, BinarySensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "value",
+        assertChannel(component, BinarySensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "onoffsensor",
                 OnOffValue.class);
 
         publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }");
index 78549ef16a7e5352130c48b971bbdc593c9156ea..c2abf74b39c07c0fe8c2fd938daf1f9774eed5b4 100644 (file)
@@ -65,7 +65,7 @@ public class SensorTests extends AbstractComponentTests {
 
         assertThat(component.channels.size(), is(1));
         assertThat(component.getName(), is("sensor1"));
-        assertThat(component.getGroupUID().getId(), is("sn1"));
+        assertThat(component.getGroupId(), is("sn1"));
 
         assertChannel(component, Sensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "sensor1",
                 NumberValue.class);
index 881294f36bd008a6ae66469a104ae53bd5c8694b..48b02abb6e6a7e4edb8ad5d5f845f0c745d8d1c6 100644 (file)
@@ -66,8 +66,8 @@ public class SwitchTests extends AbstractComponentTests {
         assertThat(component.channels.size(), is(1));
         assertThat(component.getName(), is("th1 auto lock"));
 
-        assertChannel(component, Switch.SWITCH_CHANNEL_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/auto_lock", "state",
-                OnOffValue.class);
+        assertChannel(component, Switch.SWITCH_CHANNEL_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/auto_lock",
+                "th1 auto lock", OnOffValue.class);
 
         publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"MANUAL\"}");
         assertState(component, Switch.SWITCH_CHANNEL_ID, OnOffType.OFF);
@@ -111,7 +111,7 @@ public class SwitchTests extends AbstractComponentTests {
         assertThat(component.channels.size(), is(1));
         assertThat(component.getName(), is("th1 auto lock"));
 
-        assertChannel(component, Switch.SWITCH_CHANNEL_ID, "zigbee2mqtt/th1", "", "state", OnOffValue.class);
+        assertChannel(component, Switch.SWITCH_CHANNEL_ID, "zigbee2mqtt/th1", "", "th1 auto lock", OnOffValue.class);
 
         publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"MANUAL\"}");
         assertState(component, Switch.SWITCH_CHANNEL_ID, OnOffType.OFF);
@@ -151,7 +151,7 @@ public class SwitchTests extends AbstractComponentTests {
         assertThat(component.channels.size(), is(1));
         assertThat(component.getName(), is("th1 auto lock"));
 
-        assertChannel(component, Switch.SWITCH_CHANNEL_ID, "", "zigbee2mqtt/th1/set/auto_lock", "state",
+        assertChannel(component, Switch.SWITCH_CHANNEL_ID, "", "zigbee2mqtt/th1/set/auto_lock", "th1 auto lock",
                 OnOffValue.class);
 
         component.getChannel(Switch.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
index 33e2f6e2847766aaf5079285df40f57cf870dfb8..15df43128a7f903a485c541c064284d09c7af4af 100644 (file)
@@ -37,6 +37,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.component.Sensor;
 import org.openhab.binding.mqtt.homeassistant.internal.component.Switch;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.types.StateDescription;
 
 /**
  * Tests for {@link HomeAssistantThingHandler}
@@ -73,8 +74,8 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
 
         when(callbackMock.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing);
 
-        thingHandler = new HomeAssistantThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
-                SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT);
+        thingHandler = new HomeAssistantThingHandler(haThing, channelTypeProvider, stateDescriptionProvider,
+                channelTypeRegistry, transformationServiceProvider, SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT);
         thingHandler.setConnection(bridgeConnection);
         thingHandler.setCallback(callbackMock);
         nonSpyThingHandler = thingHandler;
@@ -105,9 +106,9 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Climate.class));
 
         thingHandler.delayedProcessing.forceProcessNow();
-        assertThat(haThing.getChannels().size(), CoreMatchers.is(6));
-        verify(channelTypeProvider, times(6)).setChannelType(any(), any());
-        verify(channelTypeProvider, times(1)).setChannelGroupType(any(), any());
+        assertThat(nonSpyThingHandler.getThing().getChannels().size(), CoreMatchers.is(6));
+        verify(stateDescriptionProvider, times(6)).setDescription(any(), any(StateDescription.class));
+        verify(channelTypeProvider, times(1)).putChannelGroupType(any());
 
         configTopic = "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config";
         thingHandler.discoverComponents.processMessage(configTopic,
@@ -116,9 +117,9 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Switch.class));
 
         thingHandler.delayedProcessing.forceProcessNow();
-        assertThat(haThing.getChannels().size(), CoreMatchers.is(7));
-        verify(channelTypeProvider, times(7)).setChannelType(any(), any());
-        verify(channelTypeProvider, times(2)).setChannelGroupType(any(), any());
+        assertThat(nonSpyThingHandler.getThing().getChannels().size(), CoreMatchers.is(7));
+        verify(stateDescriptionProvider, atLeast(7)).setDescription(any(), any(StateDescription.class));
+        verify(channelTypeProvider, times(3)).putChannelGroupType(any());
     }
 
     /**
@@ -170,7 +171,7 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopicTempCorridor)), any(Sensor.class));
         thingHandler.delayedProcessing.forceProcessNow();
         waitForAssert(() -> {
-            assertThat("1 channel created", thingHandler.getThing().getChannels().size() == 1);
+            assertThat("1 channel created", nonSpyThingHandler.getThing().getChannels().size() == 1);
         });
 
         //
@@ -186,7 +187,7 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         thingHandler.delayedProcessing.forceProcessNow();
         verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopicTempOutside)), any(Sensor.class));
         waitForAssert(() -> {
-            assertThat("2 channel created", thingHandler.getThing().getChannels().size() == 2);
+            assertThat("2 channel created", nonSpyThingHandler.getThing().getChannels().size() == 2);
         });
 
         //
@@ -201,7 +202,7 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         thingHandler.delayedProcessing.forceProcessNow();
 
         waitForAssert(() -> {
-            assertThat("2 channel created", thingHandler.getThing().getChannels().size() == 2);
+            assertThat("2 channel created", nonSpyThingHandler.getThing().getChannels().size() == 2);
         });
 
         //
@@ -219,7 +220,7 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         verify(thingHandler, times(2)).componentDiscovered(eq(new HaID(configTopicTempCorridor)), any(Sensor.class));
 
         waitForAssert(() -> {
-            assertThat("2 channel created", thingHandler.getThing().getChannels().size() == 2);
+            assertThat("2 channel created", nonSpyThingHandler.getThing().getChannels().size() == 2);
         });
     }
 
@@ -239,8 +240,8 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
                 "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config",
                 getResourceAsByteArray("component/configTS0601AutoLock.json"));
         thingHandler.delayedProcessing.forceProcessNow();
-        assertThat(haThing.getChannels().size(), CoreMatchers.is(7));
-        verify(channelTypeProvider, times(7)).setChannelType(any(), any());
+        assertThat(nonSpyThingHandler.getThing().getChannels().size(), CoreMatchers.is(7));
+        verify(stateDescriptionProvider, atLeast(7)).setDescription(any(), any(StateDescription.class));
 
         // When dispose
         thingHandler.dispose();
@@ -249,9 +250,31 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
         MQTT_TOPICS.forEach(t -> {
             verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).unsubscribe(eq(t), any());
         });
+    }
+
+    @Test
+    public void testRemoveThing() {
+        thingHandler.initialize();
+
+        // Expect subscription on each topic from config
+        CONFIG_TOPICS.forEach(t -> {
+            var fullTopic = HandlerConfiguration.DEFAULT_BASETOPIC + "/" + t + "/config";
+            verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(fullTopic), any());
+        });
+        thingHandler.discoverComponents.processMessage(
+                "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config",
+                getResourceAsByteArray("component/configTS0601ClimateThermostat.json"));
+        thingHandler.discoverComponents.processMessage(
+                "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config",
+                getResourceAsByteArray("component/configTS0601AutoLock.json"));
+        thingHandler.delayedProcessing.forceProcessNow();
+        assertThat(nonSpyThingHandler.getThing().getChannels().size(), CoreMatchers.is(7));
+
+        // When dispose
+        nonSpyThingHandler.handleRemoval();
 
-        // Expect channel types removed, 6 for climate and 1 for switch
-        verify(channelTypeProvider, times(7)).removeChannelType(any());
+        // Expect channel descriptions removed, 6 for climate and 1 for switch
+        verify(stateDescriptionProvider, times(7)).remove(any());
         // Expect channel group types removed, 1 for each component
         verify(channelTypeProvider, times(2)).removeChannelGroupType(any());
     }
index cb305be7841f87891b4483f50f2a02fb9fe67b53..f4e1899b65529bf2916a13824803068a797f43a2 100644 (file)
@@ -12,6 +12,7 @@
  */
 package org.openhab.binding.mqtt.homie.internal.handler;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -19,6 +20,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledFuture;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -140,6 +142,8 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
         if (config.removetopics) {
             this.removeRetainedTopics();
         }
+        channelTypeProvider.removeThingType(thing.getThingTypeUID());
+        channelTypeProvider.removeChannelGroupTypesForPrefix(thing.getThingTypeUID().getId());
         super.handleRemoval();
     }
 
@@ -169,7 +173,6 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
         }
         delayedProcessing.join();
         device.stop();
-        channelTypeProvider.removeThingType(device.thingTypeUID);
         super.stop();
     }
 
@@ -216,7 +219,6 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
 
     @Override
     public void nodeRemoved(Node node) {
-        channelTypeProvider.removeChannelGroupType(node.channelGroupTypeUID);
         delayedProcessing.accept(node);
     }
 
@@ -228,7 +230,6 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
 
     @Override
     public void nodeAddedOrChanged(Node node) {
-        channelTypeProvider.setChannelGroupType(node.channelGroupTypeUID, node.type());
         delayedProcessing.accept(node);
     }
 
@@ -288,27 +289,42 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
     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));
+                .forEach(ct -> channelTypeProvider.putChannelType(Objects.requireNonNull(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()));
+            channelTypeProvider.updateChannelGroupTypesForPrefix(thing.getThingTypeUID().getId(), device.nodes.stream()
+                    .map(n -> n.type(thing.getThingTypeUID().getId(), channelTypeProvider)).toList());
 
-            List<ChannelGroupDefinition> groupDefs = device.nodes().stream().map(Node::getChannelGroupDefinition)
-                    .collect(Collectors.toList());
+            List<ChannelGroupDefinition> groupDefs = device.nodes.stream(nodeOrder())
+                    .map(n -> n.getChannelGroupDefinition(thing.getThingTypeUID().getId())).toList();
             var builder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMIE300_MQTT_THING)
                     .withChannelGroupDefinitions(groupDefs);
-            ThingType thingType = builder.build();
 
-            channelTypeProvider.setThingType(typeID, thingType);
+            channelTypeProvider.putThingType(builder.build());
         }
     }
 
     private void updateChannels() {
-        List<Channel> channels = device.nodes().stream().flatMap(n -> n.properties.stream())
-                .map(p -> p.getChannel(channelTypeRegistry)).collect(Collectors.toList());
+        List<Channel> channels = device.nodes.stream(nodeOrder())
+                .flatMap(node -> node.properties
+                        .stream(node.propertyOrder(thing.getThingTypeUID().getId(), channelTypeProvider))
+                        .map(p -> p.getChannel(channelTypeRegistry)))
+                .toList();
         updateThing(editThing().withChannels(channels).build());
     }
+
+    private Collection<String> nodeOrder() {
+        String[] nodes = device.attributes.nodes;
+        if (nodes != null) {
+            return Stream.of(nodes).toList();
+        }
+        ThingType thingType = channelTypeProvider.getThingType(thing.getThingTypeUID(), null);
+        if (thingType != null) {
+            return thingType.getChannelGroupDefinitions().stream().map(ChannelGroupDefinition::getId).toList();
+        }
+
+        return device.nodes.keySet();
+    }
 }
index d9d9592250e4a679c0e8b3bcc29d150ee621ade2..1d42536975f5760406b1d86e0aa5c3f9e17c5417 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.mqtt.homie.internal.homie300;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
@@ -22,6 +23,7 @@ import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
 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;
@@ -55,7 +57,6 @@ public class Node implements AbstractMqttAttributeClass.AttributeChanged {
     // Runtime
     public final DeviceCallback callback;
     protected final ChannelGroupUID channelGroupUID;
-    public final ChannelGroupTypeUID channelGroupTypeUID;
     private final String topic;
     private boolean initialized = false;
 
@@ -72,7 +73,6 @@ public class Node implements AbstractMqttAttributeClass.AttributeChanged {
         this.topic = topic + "/" + nodeID;
         this.nodeID = nodeID;
         this.callback = callback;
-        channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, UIDUtils.encode(this.topic));
         channelGroupUID = new ChannelGroupUID(thingUID, UIDUtils.encode(nodeID));
         properties = new ChildMap<>();
     }
@@ -117,15 +117,16 @@ public class Node implements AbstractMqttAttributeClass.AttributeChanged {
     /**
      * Return the channel group type for this Node.
      */
-    public ChannelGroupType type() {
-        final List<ChannelDefinition> channelDefinitions = properties.stream()
-                .map(p -> Objects.requireNonNull(p.getChannelDefinition())).collect(Collectors.toList());
-        return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, attributes.name)
+    public ChannelGroupType type(String prefix, MqttChannelTypeProvider channelTypeProvider) {
+        final List<ChannelDefinition> channelDefinitions = properties.stream(propertyOrder(prefix, channelTypeProvider))
+                .map(p -> Objects.requireNonNull(p.getChannelDefinition())).toList();
+        return ChannelGroupTypeBuilder.instance(getChannelGroupTypeUID(prefix), attributes.name)
                 .withChannelDefinitions(channelDefinitions).build();
     }
 
-    public ChannelGroupDefinition getChannelGroupDefinition() {
-        return new ChannelGroupDefinition(channelGroupUID.getId(), channelGroupTypeUID, attributes.name, null);
+    public ChannelGroupDefinition getChannelGroupDefinition(String prefix) {
+        return new ChannelGroupDefinition(channelGroupUID.getId(), getChannelGroupTypeUID(prefix), attributes.name,
+                null);
     }
 
     /**
@@ -220,4 +221,21 @@ public class Node implements AbstractMqttAttributeClass.AttributeChanged {
 
         return topics;
     }
+
+    public Collection<String> propertyOrder(String prefix, MqttChannelTypeProvider channelTypeProvider) {
+        String[] properties = attributes.properties;
+        if (properties != null) {
+            return Stream.of(properties).toList();
+        }
+        ChannelGroupType channelGroupType = channelTypeProvider.getChannelGroupType(getChannelGroupTypeUID(prefix),
+                null);
+        if (channelGroupType != null) {
+            return channelGroupType.getChannelDefinitions().stream().map(ChannelDefinition::getId).toList();
+        }
+        return this.properties.keySet();
+    }
+
+    private ChannelGroupTypeUID getChannelGroupTypeUID(String prefix) {
+        return new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, prefix + "_" + UIDUtils.encode(this.topic));
+    }
 }
index 09d09c6a2b2d2a0a82679aef52b43ccb8da4fb83..4e5eeedee25800acb66b90a2467601961b96e9b0 100644 (file)
@@ -62,6 +62,7 @@ import org.openhab.binding.mqtt.homie.internal.homie300.PropertyAttributes.DataT
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.library.types.StringType;
+import org.openhab.core.test.storage.VolatileStorageService;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
@@ -99,7 +100,8 @@ public class HomieThingHandlerTests {
     private @NonNullByDefault({}) Thing thing;
     private @NonNullByDefault({}) HomieThingHandler thingHandler;
 
-    private final MqttChannelTypeProvider channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistryMock));
+    private final MqttChannelTypeProvider channelTypeProvider = spy(
+            new MqttChannelTypeProvider(thingTypeRegistryMock, new VolatileStorageService()));
     private final MqttChannelStateDescriptionProvider stateDescriptionProvider = new MqttChannelStateDescriptionProvider();
 
     private final String deviceID = ThingChannelConstants.TEST_HOMIE_THING.getId();
index 417f79e4a8e6d8cb66c7ecba7191be65c025cb89..cc35c25ed397ae8b191549dd1b34d559b08cfa16 100644 (file)
@@ -84,7 +84,7 @@ public class DiscoverComponentsTest extends JavaOSGiTest {
         Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ChannelConfigurationTypeAdapterFactory()).create();
 
         DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.TEST_HOME_ASSISTANT_THING,
-                scheduler, channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider));
+                scheduler, channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider, true));
 
         HandlerConfiguration config = new HandlerConfiguration("homeassistant", List.of("switch/object"));
 
index 916ea59231d01de4c1d2f8d8a9a17071f76730ab..72a41d4f6456a8b696b575e742995001381f6b8b 100644 (file)
@@ -22,7 +22,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
@@ -56,7 +55,6 @@ import org.openhab.core.io.transport.mqtt.MqttConnectionState;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
-import org.openhab.core.util.UIDUtils;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -152,17 +150,14 @@ public class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
 
         ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(4);
         DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.TEST_HOME_ASSISTANT_THING,
-                scheduler, channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider));
+                scheduler, channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider, true));
 
         // The DiscoverComponents object calls ComponentDiscovered callbacks.
         // In the following implementation we add the found component to the `haComponents` map
         // and add the types to the channelTypeProvider, like in the real Thing handler.
         final CountDownLatch latch = new CountDownLatch(1);
         ComponentDiscovered cd = (haID, c) -> {
-            haComponents.put(c.getGroupUID().getId(), c);
-            c.addChannelTypes(channelTypeProvider);
-            channelTypeProvider.setChannelGroupType(Objects.requireNonNull(c.getGroupTypeUID()),
-                    Objects.requireNonNull(c.getType()));
+            haComponents.put(c.getGroupId(), c);
             latch.countDown();
         };
 
@@ -181,15 +176,10 @@ public class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
         assertNull(failure);
         assertThat(haComponents.size(), is(1));
 
-        // For the switch component we should have one channel group type and one channel type
-        // setChannelGroupType is called once above
-        verify(channelTypeProvider, times(2)).setChannelGroupType(any(), any());
-        verify(channelTypeProvider, times(1)).setChannelType(any(), any());
+        String channelGroupId = "switch_" + ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId();
+        String channelId = Switch.SWITCH_CHANNEL_ID;
 
-        String channelGroupId = UIDUtils
-                .encode("node_" + ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId() + "_switch");
-
-        State value = haComponents.get(channelGroupId).getChannel(Switch.SWITCH_CHANNEL_ID).getState().getCache()
+        State value = haComponents.get(channelGroupId).getChannel(channelGroupId).getState().getCache()
                 .getChannelState();
         assertThat(value, is(UnDefType.UNDEF));
 
@@ -203,8 +193,7 @@ public class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
         verify(channelStateUpdateListener, timeout(4000).times(1)).updateChannelState(any(), any());
 
         // Value should be ON now.
-        value = haComponents.get(channelGroupId).getChannel(Switch.SWITCH_CHANNEL_ID).getState().getCache()
-                .getChannelState();
+        value = haComponents.get(channelGroupId).getChannel(channelGroupId).getState().getCache().getChannelState();
         assertThat(value, is(OnOffType.ON));
     }
 }