diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/MqttChannelTypeProvider.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/MqttChannelTypeProvider.java index 1991d003f7a68..322497df4b743 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/MqttChannelTypeProvider.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/MqttChannelTypeProvider.java @@ -14,22 +14,19 @@ 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 @@ * 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 types = new ConcurrentHashMap<>(); - private final Map groups = new ConcurrentHashMap<>(); - private final Map 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 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 getChannelGroupTypes(@Nullable Locale locale) { - return groups.values(); - } - - @Override - public Collection getThingTypes(@Nullable Locale locale) { - return things.values(); - } - - public Set 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 ThingTypeBuilder derive(ThingTypeUID newTypeId, ThingTypeUID baseTypeId) return result; } + + public void updateChannelGroupTypesForPrefix(String prefix, Collection types) { + Collection oldCgts = channelGroupTypesForPrefix(prefix); + + Set oldUids = oldCgts.stream().map(ChannelGroupType::getUID).collect(Collectors.toSet()); + Collection 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 channelGroupTypesForPrefix(String prefix) { + return getChannelGroupTypes(null).stream().filter(cgt -> cgt.getUID().getId().startsWith(prefix + "_")) + .toList(); + } } diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/tools/ChildMap.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/tools/ChildMap.java index 03d095059ee47..952da6e39fea6 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/tools/ChildMap.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/tools/ChildMap.java @@ -12,7 +12,10 @@ */ 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 Stream stream() { 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 stream(Collection order) { + // need to make a copy to avoid editing `map` itself + Set missingKeys = new HashSet<>(map.keySet()); + missingKeys.removeAll(order); + Stream 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 void clear() { public void put(String key, T value) { map.put(key, value); } + + public Set keySet() { + return map.keySet(); + } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttBindingConstants.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttBindingConstants.java index bd5c0ddcfc12c..7de63cc519b7c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttBindingConstants.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttBindingConstants.java @@ -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"; } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java index 44e6295669676..b28fadcab1bcd 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java @@ -18,6 +18,7 @@ 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.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 @@ @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 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 @@ private boolean isHomeassistantDynamicType(ThingTypeUID thingTypeUID) { && 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; } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index 41f66df011ea0..24fcde6834692 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -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.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.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 @@ 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 ChannelState getState() { 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 ChannelState getState() { 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 void resetState() { 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 static class Builder { 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 ComponentChannel build(boolean addToComponent) { 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 ComponentChannel build(boolean addToComponent) { 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 index 0000000000000..b99f8683ec115 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannelType.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java index 96724530bc8ef..1907e80077523 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java @@ -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 static interface ComponentDiscovered { */ 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 void processMessage(String topic, byte[] payload) { 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); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java index 7a2ca110c9e0c..829cbb54fe509 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java @@ -187,9 +187,19 @@ public String toShortTopic() { * * @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(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index ca901bcba9590..befc19a5c7d87 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -19,18 +19,18 @@ 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.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.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 // 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 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 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 clazz) { + public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class 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 AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig 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 AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig } } - 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 void setConfigSeen() { } /** - * 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 ChannelUID buildChannelUID(String channelID) { 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 int getConfigHash() { /** * 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 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 getChannelDefinitions() { + if (channelGroupUID != null) { + return List.of(); + } + return getAllChannelDefinitions(); + } + + private List getAllChannelDefinitions() { + return channels.values().stream().map(ComponentChannel::channelDefinition).toList(); } - public List getChannels() { - return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList()); + public List getChannels() { + return channels.values().stream().map(ComponentChannel::getChannel).toList(); } /** @@ -296,12 +305,15 @@ public void resetState() { /** * 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 boolean isEnabledByDefault() { public Gson getGson() { return componentConfiguration.getGson(); } + + public C getChannelConfiguration() { + return channelConfiguration; + } + + private ChannelGroupTypeUID getChannelGroupTypeUID(String prefix) { + return new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, prefix + "_" + uniqueId); + } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractRawSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractRawSchemaLight.java index 2cce0c4ce8c96..c7a43e57bbd8c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractRawSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractRawSchemaLight.java @@ -15,6 +15,7 @@ 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)); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index 961d4ee3e1e4e..026ce5048556c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java index 2d636e95254f5..b6df93f18d6cf 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java @@ -19,6 +19,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { protected @Nullable List 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(); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java index c54fcd48b5606..cff40ea8e4ade 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java index 97fddc02dffc0..c1e934d432459 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java @@ -14,6 +14,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index 2b8cc8ad97fa0..1c7726bc52ced 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -28,6 +28,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 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 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 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) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java index 367f666e48d1e..0c48a72805a91 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java @@ -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!"); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java index 55f5b632705f6..077a7b94cd51e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java @@ -17,6 +17,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { @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 Cover(ComponentFactory.ComponentConfiguration componentConfiguration) { 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 Cover(ComponentFactory.ComponentConfiguration componentConfiguration) { 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) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java index 3db47a4051dbd..9d412c1a14ba3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java @@ -22,6 +22,7 @@ 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 @@ protected void buildChannels() { @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 @@ protected void buildChannels() { } 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 @@ protected void buildChannels() { 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 @@ protected void buildChannels() { 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 @@ protected void buildChannels() { 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 @@ protected void buildChannels() { 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) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java index 0ff48e027381c..91ea0456b65a4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfigurat 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(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java index 4a0a041d11004..6b216a2bcb3c1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index b678d3830eaf0..ca016efd345d9 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -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.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 @@ protected static class Color { 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 @@ protected void buildChannels() { 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 @@ protected void buildChannels() { } 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(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java index ad1c82d6988ff..db6baee7ba164 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java @@ -245,21 +245,22 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java index eab24ceffc7d6..ce3317256a620 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java @@ -17,6 +17,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 Lock(ComponentFactory.ComponentConfiguration componentConfiguration) { 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 Lock(ComponentFactory.ComponentConfiguration componentConfiguration) { 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()) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java index 73635d99c6ed2..1d03661f880e7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java @@ -17,6 +17,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 Number(ComponentFactory.ComponentConfiguration componentConfiguration) { 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) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java index 85df85b4796a0..d6e66a2bcb258 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java index 0d54c4ca15783..b3ceccafe67c3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 Select(ComponentFactory.ComponentConfiguration componentConfiguration) { 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) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java index 26b6c704d6ae8..9472490ca68c0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java @@ -21,6 +21,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { protected @Nullable List 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(); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java index 83521651f8d79..b1288c151d374 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java @@ -15,6 +15,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { 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 Switch(ComponentFactory.ComponentConfiguration componentConfiguration) { 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()) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Update.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Update.java index 035ecd832e694..5ea64b4a24663 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Update.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Update.java @@ -21,6 +21,7 @@ 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 interface ReleaseStateListener { 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 Update(ComponentFactory.ComponentConfiguration componentConfiguration) { 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); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java index 964c24b3a9e7d..0c5185267230a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java @@ -27,6 +27,7 @@ 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 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { * * @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 Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) { 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 Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) { } 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 Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration) { } } - 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) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java index 4626a9860468f..ca675d346f8a1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java @@ -145,7 +145,7 @@ protected void unsetTypeProvider(MqttChannelTypeProvider provider) { @Override public Set getSupportedThingTypes() { - return typeProvider.getThingTypeUIDs(); + return typeProvider.getThingTypes(null).stream().map(ThingType::getUID).collect(Collectors.toSet()); } /** @@ -206,11 +206,8 @@ public void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection conn .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 void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection conn 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 @@ protected void publishResults() { 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); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java index eb6aa2f0d7f40..aa2ee5fcfe5e3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java @@ -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.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.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.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 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler implements ComponentDiscovered, Consumer>> { public static final String AVAILABILITY_CHANNEL = "availability"; - private static final Comparator CHANNEL_COMPARATOR_BY_UID = Comparator - .comparing(channel -> channel.getUID().toString()); + private static final Comparator> 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> 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 void initialize() { } 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 void initialize() { } 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 @@ protected void stop() { @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 void accept(List> discoveredComponentsList) { } 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 void accept(List> discoveredComponentsList) { } } - // 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 void accept(List> discoveredComponentsList) { updateComponent = (Update) discovered; updateComponent.setReleaseStateUpdateListener(this::releaseStateUpdated); } - - List 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 discoveredChannels) { - ArrayList mutableChannels = new ArrayList<>(getThing().getChannels()); - Set newChannelUIDs = discoveredChannels.stream().map(Channel::getUID).collect(Collectors.toSet()); - // Take current channels but remove those channels that were just re-discovered - List 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 channelList) { - ThingBuilder thingBuilder = editThing(); - thingBuilder.withChannels(channelList); - updateThing(thingBuilder.build()); - } - @Override protected void updateThingStatus(boolean messageReceived, Optional availabilityTopicsSeen) { if (availabilityTopicsSeen.orElse(messageReceived)) { @@ -372,28 +341,72 @@ public void handleConfigurationUpdate(Map configurationParameter 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 groupDefs; - List 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) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml index 64cbf21ea2977..d84743bc7331d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml @@ -7,7 +7,7 @@ - HomeAssistant component type (e.g. binary_sensor, switch, light) + Home Assistant component type (e.g. binary_sensor, switch, light) @@ -17,12 +17,12 @@ - Object id of the component + Object ID of the component - - The json configuration string received by the component via MQTT. + + The JSON configuration string received by the component via MQTT. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties index 408ba3982639b..22cb9fb44fb43 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties @@ -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 index 0000000000000..cd4679398b1dc --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/thing/homeassistant-channels.xml @@ -0,0 +1,102 @@ + + + + + Color + + + + + + Dimmer + + + + + + Image + + + + + + Number + + + + + + Rollershutter + + + + + + String + + + + + + Switch + + + + + + trigger + + + + + + Color + + + + + + Dimmer + + + + + + Image + + + + + + Number + + + + + + Rollershutter + + + + + + String + + + + + + Switch + + + + + + trigger + + + + diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index dbf052b2f47d3..7c2d33c7b67a6 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -35,12 +35,15 @@ 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.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(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 4c089f72e1845..2910c9523b1a3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -32,6 +32,7 @@ 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.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 void setupThingHandler() { 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 @@ protected void spyOnChannelUpdates(AbstractComponent<@NonNull ? extends Abstract */ 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 @@ protected boolean publishMessage(String mqttTopic, byte[] payload) { 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 @@ protected static class LatchThingHandler extends HomeAssistantThingHandler { 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 diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java index 9f4d9bc52e0b8..6c875e74b0cb7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java @@ -65,9 +65,9 @@ public void test() throws InterruptedException { 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_\" }"); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java index 78549ef16a7e5..c2abf74b39c07 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java @@ -65,7 +65,7 @@ public void test() throws InterruptedException { 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); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java index 881294f36bd00..48b02abb6e6a7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java @@ -66,8 +66,8 @@ public void testSwitchWithStateAndCommand() { 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 void testSwitchWithState() { 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 void testSwitchWithCommand() { 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); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java index 33e2f6e284776..15df43128a7f9 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java @@ -37,6 +37,7 @@ 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 void setup() { 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 void testInitialize() { 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 void testInitialize() { 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 void testDuplicateComponentPublish() throws InterruptedException { 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 void testDuplicateComponentPublish() throws InterruptedException { 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 void testDuplicateComponentPublish() throws InterruptedException { 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 void testDuplicateComponentPublish() throws InterruptedException { 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 void testDispose() { "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 void testDispose() { 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()); } diff --git a/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java b/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java index cb305be7841f8..f4e1899b65529 100644 --- a/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java @@ -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.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 void handleRemoval() { if (config.removetopics) { this.removeRetainedTopics(); } + channelTypeProvider.removeThingType(thing.getThingTypeUID()); + channelTypeProvider.removeChannelGroupTypesForPrefix(thing.getThingTypeUID().getId()); super.handleRemoval(); } @@ -169,7 +173,6 @@ protected void stop() { } delayedProcessing.join(); device.stop(); - channelTypeProvider.removeThingType(device.thingTypeUID); super.stop(); } @@ -216,7 +219,6 @@ public void readyStateChanged(ReadyState state) { @Override public void nodeRemoved(Node node) { - channelTypeProvider.removeChannelGroupType(node.channelGroupTypeUID); delayedProcessing.accept(node); } @@ -228,7 +230,6 @@ public void propertyRemoved(Property property) { @Override public void nodeAddedOrChanged(Node node) { - channelTypeProvider.setChannelGroupType(node.channelGroupTypeUID, node.type()); delayedProcessing.accept(node); } @@ -288,27 +289,42 @@ protected void updateThingStatus(boolean messageReceived, Optional avai 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 groupDefs = device.nodes().stream().map(Node::getChannelGroupDefinition) - .collect(Collectors.toList()); + List 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 channels = device.nodes().stream().flatMap(n -> n.properties.stream()) - .map(p -> p.getChannel(channelTypeRegistry)).collect(Collectors.toList()); + List 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 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(); + } } diff --git a/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Node.java b/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Node.java index d9d9592250e4a..1d42536975f57 100644 --- a/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Node.java +++ b/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Node.java @@ -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 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 Node(String topic, String nodeID, ThingUID thingUID, DeviceCallback callb 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 void nodeRestoredFromConfig() { /** * Return the channel group type for this Node. */ - public ChannelGroupType type() { - final List 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 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 List getRetainedTopics() { return topics; } + + public Collection 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)); + } } diff --git a/bundles/org.openhab.binding.mqtt.homie/src/test/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandlerTests.java b/bundles/org.openhab.binding.mqtt.homie/src/test/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandlerTests.java index 09d09c6a2b2d2..4e5eeedee2580 100644 --- a/bundles/org.openhab.binding.mqtt.homie/src/test/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandlerTests.java +++ b/bundles/org.openhab.binding.mqtt.homie/src/test/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandlerTests.java @@ -62,6 +62,7 @@ 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(); diff --git a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/DiscoverComponentsTest.java b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/DiscoverComponentsTest.java index 417f79e4a8e6d..cc35c25ed397a 100644 --- a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/DiscoverComponentsTest.java +++ b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/DiscoverComponentsTest.java @@ -84,7 +84,7 @@ public void discoveryTimeTest() throws InterruptedException, ExecutionException, 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")); diff --git a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java index 916ea59231d01..72a41d4f6456a 100644 --- a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java +++ b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java @@ -22,7 +22,6 @@ 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.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 void parseHATree() throws Exception { 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 void parseHATree() throws Exception { 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 void parseHATree() throws Exception { 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)); } }