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;
* This provider is started on-demand only, as soon as {@link MqttThingHandlerFactory} or an extension requires it.
*
* @author David Graeff - Initial contribution
+ * @author Cody Cutrer - Use AbstractStorageBasedTypeProvider
*
*/
@NonNullByDefault
@Component(immediate = false, service = { ThingTypeProvider.class, ChannelTypeProvider.class,
ChannelGroupTypeProvider.class, MqttChannelTypeProvider.class })
-public class MqttChannelTypeProvider implements ThingTypeProvider, ChannelGroupTypeProvider, ChannelTypeProvider {
- private final ThingTypeRegistry typeRegistry;
-
- private final Map<ChannelTypeUID, ChannelType> types = new ConcurrentHashMap<>();
- private final Map<ChannelGroupTypeUID, ChannelGroupType> groups = new ConcurrentHashMap<>();
- private final Map<ThingTypeUID, ThingType> things = new ConcurrentHashMap<>();
+public class MqttChannelTypeProvider extends AbstractStorageBasedTypeProvider {
+ private final ThingTypeRegistry thingTypeRegistry;
@Activate
- public MqttChannelTypeProvider(@Reference ThingTypeRegistry typeRegistry) {
- super();
- this.typeRegistry = typeRegistry;
- }
-
- @Override
- public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
- return types.values();
- }
-
- @Override
- public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
- return types.get(channelTypeUID);
- }
-
- @Override
- public @Nullable ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID,
- @Nullable Locale locale) {
- return groups.get(channelGroupTypeUID);
- }
-
- @Override
- public Collection<ChannelGroupType> getChannelGroupTypes(@Nullable Locale locale) {
- return groups.values();
- }
-
- @Override
- public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
- return things.values();
- }
-
- public Set<ThingTypeUID> getThingTypeUIDs() {
- return things.keySet();
- }
-
- @Override
- public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) {
- return things.get(thingTypeUID);
- }
-
- public void removeChannelType(ChannelTypeUID uid) {
- types.remove(uid);
- }
-
- public void removeChannelGroupType(ChannelGroupTypeUID uid) {
- groups.remove(uid);
- }
-
- public void setChannelGroupType(ChannelGroupTypeUID uid, ChannelGroupType type) {
- groups.put(uid, type);
- }
-
- public void setChannelType(ChannelTypeUID uid, ChannelType type) {
- types.put(uid, type);
- }
-
- public void removeThingType(ThingTypeUID uid) {
- things.remove(uid);
- }
-
- public void setThingType(ThingTypeUID uid, ThingType type) {
- things.put(uid, type);
- }
-
- public void setThingTypeIfAbsent(ThingTypeUID uid, ThingType type) {
- things.putIfAbsent(uid, type);
+ public MqttChannelTypeProvider(@Reference ThingTypeRegistry thingTypeRegistry,
+ @Reference StorageService storageService) {
+ super(storageService);
+ this.thingTypeRegistry = thingTypeRegistry;
}
public ThingTypeBuilder derive(ThingTypeUID newTypeId, ThingTypeUID baseTypeId) {
- ThingType baseType = typeRegistry.getThingType(baseTypeId);
+ ThingType baseType = thingTypeRegistry.getThingType(baseTypeId);
ThingTypeBuilder result = ThingTypeBuilder.instance(newTypeId, baseType.getLabel())
.withChannelGroupDefinitions(baseType.getChannelGroupDefinitions())
return result;
}
+
+ public void updateChannelGroupTypesForPrefix(String prefix, Collection<ChannelGroupType> types) {
+ Collection<ChannelGroupType> oldCgts = channelGroupTypesForPrefix(prefix);
+
+ Set<ChannelGroupTypeUID> oldUids = oldCgts.stream().map(ChannelGroupType::getUID).collect(Collectors.toSet());
+ Collection<ChannelGroupTypeUID> uids = types.stream().map(ChannelGroupType::getUID).toList();
+
+ oldUids.removeAll(uids);
+ // oldUids now contains only UIDs that no longer exist. so remove them
+ oldUids.forEach(this::removeChannelGroupType);
+ types.forEach(this::putChannelGroupType);
+ }
+
+ public void removeChannelGroupTypesForPrefix(String prefix) {
+ channelGroupTypesForPrefix(prefix).forEach(cgt -> removeChannelGroupType(cgt.getUID()));
+ }
+
+ private Collection<ChannelGroupType> channelGroupTypesForPrefix(String prefix) {
+ return getChannelGroupTypes(null).stream().filter(cgt -> cgt.getUID().getId().startsWith(prefix + "_"))
+ .toList();
+ }
}
*/
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;
return map.values().stream();
}
+ /**
+ * Streams the objects in this map in the order of the given keys.
+ *
+ * Extraneous keys are ignored, and missing keys are included at the end in unspecified order.
+ *
+ * @param order The keys in the order they should be streamed
+ */
+ public Stream<T> stream(Collection<String> order) {
+ // need to make a copy to avoid editing `map` itself
+ Set<String> missingKeys = new HashSet<>(map.keySet());
+ missingKeys.removeAll(order);
+ Stream<T> result = order.stream().map(k -> map.get(k)).filter(Objects::nonNull).map(Objects::requireNonNull);
+ if (!missingKeys.isEmpty()) {
+ result = Stream.concat(result, missingKeys.stream().map(k -> map.get(k)).map(Objects::requireNonNull));
+ }
+ return result;
+ }
+
/**
* Modifies the map in way that it matches the entries of the given childIDs.
*
public void put(String key, T value) {
map.put(key, value);
}
+
+ public Set<String> keySet() {
+ return map.keySet();
+ }
}
// 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";
}
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;
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;
/**
@Component(service = ThingHandlerFactory.class)
@NonNullByDefault
public class MqttThingHandlerFactory extends BaseThingHandlerFactory implements TransformationServiceProvider {
- private @NonNullByDefault({}) MqttChannelTypeProvider typeProvider;
+ private final MqttChannelTypeProvider typeProvider;
+ private final MqttChannelStateDescriptionProvider stateDescriptionProvider;
+ private final ChannelTypeRegistry channelTypeRegistry;
+
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(MqttBindingConstants.HOMEASSISTANT_MQTT_THING).collect(Collectors.toSet());
+ @Activate
+ public MqttThingHandlerFactory(final @Reference MqttChannelTypeProvider typeProvider,
+ final @Reference MqttChannelStateDescriptionProvider stateDescriptionProvider,
+ final @Reference ChannelTypeRegistry channelTypeRegistry) {
+ this.typeProvider = typeProvider;
+ this.stateDescriptionProvider = stateDescriptionProvider;
+ this.channelTypeRegistry = channelTypeRegistry;
+ }
+
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || isHomeassistantDynamicType(thingTypeUID);
&& 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;
}
*/
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;
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;
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.
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;
}
return channelState;
}
+ public @Nullable StateDescription getStateDescription() {
+ return stateDescription;
+ }
+
+ public @Nullable CommandDescription getCommandDescription() {
+ return commandDescription;
+ }
+
public CompletableFuture<@Nullable Void> stop() {
return channelState.stop();
}
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() {
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;
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;
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(),
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();
--- /dev/null
+/**
+ * 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;
+ }
+}
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;
*/
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
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);
*
* @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();
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;
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;
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;
// Component location fields
protected final ComponentConfiguration componentConfiguration;
- protected final @Nullable ChannelGroupTypeUID channelGroupTypeUID;
protected final @Nullable ChannelGroupUID channelGroupUID;
protected final HaID haID;
protected final C channelConfiguration;
protected boolean configSeen;
+ protected final boolean singleChannelComponent;
+ protected final String groupId;
+ protected final String uniqueId;
+
+ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz,
+ boolean newStyleChannels) {
+ this(componentConfiguration, clazz, newStyleChannels, false);
+ }
/**
* Creates component based on generic configuration and component configuration type.
*
* @param componentConfiguration generic componentConfiguration with not parsed JSON config
* @param clazz target configuration type
+ * @param newStyleChannels if new style channels should be used
+ * @param singleChannelComponent if this component only ever has one channel, so should never be in a group
+ * (only if newStyleChannels is true)
*/
- public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
+ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz,
+ boolean newStyleChannels, boolean singleChannelComponent) {
this.componentConfiguration = componentConfiguration;
+ this.singleChannelComponent = newStyleChannels && singleChannelComponent;
this.channelConfigurationJson = componentConfiguration.getConfigJSON();
this.channelConfiguration = componentConfiguration.getConfig(clazz);
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;
}
}
- 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() {
}
/**
- * 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) {
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;
}
/**
/**
* Return the channel group type.
*/
- public @Nullable ChannelGroupType getType() {
- ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
- if (groupTypeUID == null) {
+ public @Nullable ChannelGroupType getChannelGroupType(String prefix) {
+ if (channelGroupUID == null) {
return null;
}
- final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
- .collect(Collectors.toList());
- return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
- .build();
+ return ChannelGroupTypeBuilder.instance(getChannelGroupTypeUID(prefix), getName())
+ .withChannelDefinitions(getAllChannelDefinitions()).build();
+ }
+
+ public List<ChannelDefinition> getChannelDefinitions() {
+ if (channelGroupUID != null) {
+ return List.of();
+ }
+ return getAllChannelDefinitions();
+ }
+
+ private List<ChannelDefinition> getAllChannelDefinitions() {
+ return channels.values().stream().map(ComponentChannel::channelDefinition).toList();
}
- public List<ChannelDefinition> getChannels() {
- return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
+ public List<Channel> getChannels() {
+ return channels.values().stream().map(ComponentChannel::getChannel).toList();
}
/**
/**
* 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() {
public Gson getGson() {
return componentConfiguration.getGson();
}
+
+ public C getChannelConfiguration() {
+ return channelConfiguration;
+ }
+
+ private ChannelGroupTypeUID getChannelGroupTypeUID(String prefix) {
+ return new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, prefix + "_" + uniqueId);
+ }
}
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;
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));
}
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;
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();
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;
protected @Nullable List<String> jsonAttributes;
}
- public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration) {
- super(componentConfiguration, ChannelConfiguration.class);
+ public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+ super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
- buildChannel(SENSOR_CHANNEL_ID, value, "value", getListener(componentConfiguration, value))
+ buildChannel(SENSOR_CHANNEL_ID, ComponentChannelType.SWITCH, value, getName(),
+ getListener(componentConfiguration, value))
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
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;
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();
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;
/**
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();
}
}
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;
protected Boolean sendIfOff = true;
}
- public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
- super(componentConfiguration, ChannelConfiguration.class);
+ public Climate(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+ super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
BigDecimal precision = channelConfiguration.precision != null ? channelConfiguration.precision
: channelConfiguration.temperatureUnit.getDefaultPrecision();
final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
- ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
+ ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID, ComponentChannelType.STRING,
new TextValue(ACTION_MODES.toArray(new String[0])), updateListener, null, null,
channelConfiguration.actionTemplate, channelConfiguration.actionTopic, null);
final Predicate<Command> commandFilter = channelConfiguration.sendIfOff ? null
: getCommandFilter(actionChannel);
- buildOptionalChannel(AUX_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.auxCommandTopic,
- channelConfiguration.auxStateTemplate, channelConfiguration.auxStateTopic, commandFilter);
+ buildOptionalChannel(AUX_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
+ channelConfiguration.auxCommandTopic, channelConfiguration.auxStateTemplate,
+ channelConfiguration.auxStateTopic, commandFilter);
- buildOptionalChannel(AWAY_MODE_CH_ID, new OnOffValue(), updateListener, null,
+ buildOptionalChannel(AWAY_MODE_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
channelConfiguration.awayModeCommandTopic, channelConfiguration.awayModeStateTemplate,
channelConfiguration.awayModeStateTopic, commandFilter);
- buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID,
+ buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID, ComponentChannelType.NUMBER,
new NumberValue(null, null, precision, channelConfiguration.temperatureUnit.getUnit()), updateListener,
null, null, channelConfiguration.currentTemperatureTemplate,
channelConfiguration.currentTemperatureTopic, commandFilter);
- buildOptionalChannel(FAN_MODE_CH_ID, new TextValue(channelConfiguration.fanModes.toArray(new String[0])),
- updateListener, channelConfiguration.fanModeCommandTemplate, channelConfiguration.fanModeCommandTopic,
+ buildOptionalChannel(FAN_MODE_CH_ID, ComponentChannelType.STRING,
+ new TextValue(channelConfiguration.fanModes.toArray(new String[0])), updateListener,
+ channelConfiguration.fanModeCommandTemplate, channelConfiguration.fanModeCommandTopic,
channelConfiguration.fanModeStateTemplate, channelConfiguration.fanModeStateTopic, commandFilter);
List<String> holdModes = channelConfiguration.holdModes;
if (holdModes != null && !holdModes.isEmpty()) {
- buildOptionalChannel(HOLD_CH_ID, new TextValue(holdModes.toArray(new String[0])), updateListener,
+ buildOptionalChannel(HOLD_CH_ID, ComponentChannelType.STRING,
+ new TextValue(holdModes.toArray(new String[0])), updateListener,
channelConfiguration.holdCommandTemplate, channelConfiguration.holdCommandTopic,
channelConfiguration.holdStateTemplate, channelConfiguration.holdStateTopic, commandFilter);
}
- buildOptionalChannel(MODE_CH_ID, new TextValue(channelConfiguration.modes.toArray(new String[0])),
- updateListener, channelConfiguration.modeCommandTemplate, channelConfiguration.modeCommandTopic,
+ buildOptionalChannel(MODE_CH_ID, ComponentChannelType.STRING,
+ new TextValue(channelConfiguration.modes.toArray(new String[0])), updateListener,
+ channelConfiguration.modeCommandTemplate, channelConfiguration.modeCommandTopic,
channelConfiguration.modeStateTemplate, channelConfiguration.modeStateTopic, commandFilter);
- buildOptionalChannel(SWING_CH_ID, new TextValue(channelConfiguration.swingModes.toArray(new String[0])),
- updateListener, channelConfiguration.swingCommandTemplate, channelConfiguration.swingCommandTopic,
+ buildOptionalChannel(SWING_CH_ID, ComponentChannelType.STRING,
+ new TextValue(channelConfiguration.swingModes.toArray(new String[0])), updateListener,
+ channelConfiguration.swingCommandTemplate, channelConfiguration.swingCommandTopic,
channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
- buildOptionalChannel(TEMPERATURE_CH_ID,
+ buildOptionalChannel(TEMPERATURE_CH_ID, ComponentChannelType.NUMBER,
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
updateListener, channelConfiguration.temperatureCommandTemplate,
channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
channelConfiguration.temperatureStateTopic, commandFilter);
- buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
+ buildOptionalChannel(TEMPERATURE_HIGH_CH_ID, ComponentChannelType.NUMBER,
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
updateListener, channelConfiguration.temperatureHighCommandTemplate,
channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
channelConfiguration.temperatureHighStateTopic, commandFilter);
- buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
+ buildOptionalChannel(TEMPERATURE_LOW_CH_ID, ComponentChannelType.NUMBER,
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
updateListener, channelConfiguration.temperatureLowCommandTemplate,
channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
channelConfiguration.temperatureLowStateTopic, commandFilter);
- buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null,
+ buildOptionalChannel(POWER_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
channelConfiguration.powerCommandTopic, null, null, null);
}
@Nullable
- private ComponentChannel buildOptionalChannel(String channelId, Value valueState,
+ private ComponentChannel buildOptionalChannel(String channelId, ComponentChannelType channelType, Value valueState,
ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,
@Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic,
@Nullable Predicate<Command> commandFilter) {
if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
- return buildChannel(channelId, valueState, getName(), channelStateUpdateListener)
+ return buildChannel(channelId, channelType, valueState, getName(), channelStateUpdateListener)
.stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
commandTemplate)
*/
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!");
}
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;
@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;
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));
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) {
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;
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())
@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())
}
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())
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())
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())
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())
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) {
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;
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'");
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();
}
}
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;
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)
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;
TextValue colorModeValue;
- public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder) {
- super(builder);
+ public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder, boolean newStyleChannels) {
+ super(builder, newStyleChannels);
colorModeValue = new TextValue();
}
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) {
}
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();
}
}
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
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;
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();
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())
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())
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;
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();
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)
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;
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();
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;
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();
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)
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;
protected @Nullable List<String> jsonAttributes;
}
- public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration) {
- super(componentConfiguration, ChannelConfiguration.class);
+ public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
+ super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
Value value;
String uom = channelConfiguration.unitOfMeasurement;
String sc = channelConfiguration.stateClass;
+ ComponentChannelType type;
if (uom != null && !uom.isBlank()) {
value = new NumberValue(null, null, null, UnitUtils.parseUnit(uom));
+ type = ComponentChannelType.NUMBER;
} else if (sc != null && !sc.isBlank()) {
// see state_class at https://developers.home-assistant.io/docs/core/entity/sensor#properties
// > If not None, the sensor is assumed to be numerical
value = new NumberValue(null, null, null, null);
+ type = ComponentChannelType.NUMBER;
} else {
value = new TextValue();
+ type = ComponentChannelType.STRING;
}
String icon = channelConfiguration.getIcon();
boolean trigger = TRIGGER_ICONS.matcher(icon).matches();
- buildChannel(SENSOR_CHANNEL_ID, value, getName(), getListener(componentConfiguration, value))
+ buildChannel(SENSOR_CHANNEL_ID, type, value, getName(), getListener(componentConfiguration, value))
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())//
.trigger(trigger).build();
}
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;
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();
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())
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;
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());
}
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);
}
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;
*
* @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
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()) {
}
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);
}
}
- 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)
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
- return typeProvider.getThingTypeUIDs();
+ return typeProvider.getThingTypes(null).stream().map(ThingType::getUID).collect(Collectors.toSet());
}
/**
.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);
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(),
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);
}
}
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;
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;
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;
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;
public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
implements ComponentDiscovered, Consumer<List<AbstractComponent<?>>> {
public static final String AVAILABILITY_CHANNEL = "availability";
- private static final Comparator<Channel> CHANNEL_COMPARATOR_BY_UID = Comparator
- .comparing(channel -> channel.getUID().toString());
+ private static final Comparator<AbstractComponent<?>> COMPONENT_COMPARATOR = Comparator
+ .comparing((AbstractComponent<?> component) -> component.hasGroup())
+ .thenComparing(AbstractComponent::getName);
private static final URI UPDATABLE_CONFIG_DESCRIPTION_URI = URI.create("thing-type:mqtt:homeassistant-updatable");
private final Logger logger = LoggerFactory.getLogger(HomeAssistantThingHandler.class);
protected final MqttChannelTypeProvider channelTypeProvider;
+ protected final MqttChannelStateDescriptionProvider stateDescriptionProvider;
+ protected final ChannelTypeRegistry channelTypeRegistry;
public final int attributeReceiveTimeout;
protected final DelayedBatchProcessing<AbstractComponent<?>> delayedProcessing;
protected final DiscoverComponents discoverComponents;
protected final TransformationServiceProvider transformationServiceProvider;
private boolean started;
+ private boolean newStyleChannels;
private @Nullable Update updateComponent;
/**
* @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
}
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);
} 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
@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) {
}
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) {
}
}
- // 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
updateComponent = (Update) discovered;
updateComponent.setReleaseStateUpdateListener(this::releaseStateUpdated);
}
-
- List<Channel> discoveredChannels = discovered.getChannelMap().values().stream()
- .map(ComponentChannel::getChannel).collect(Collectors.toList());
- if (known != null) {
- // We had previously known component with different config hash
- // We remove all conflicting old channels, they will be re-added below based on the new discovery
- logger.debug(
- "Received component {} with slightly different config. Making sure we re-create conflicting channels...",
- discovered.getHaID());
- removeJustRediscoveredChannels(discoveredChannels);
- }
-
- // Add newly discovered channels. We sort the channels
- // for (mostly) consistent jsondb serialization
- discoveredChannels.sort(CHANNEL_COMPARATOR_BY_UID);
- ThingHelper.addChannelsToThing(thing, discoveredChannels);
}
- updateThingType();
- }
- }
-
- private void removeJustRediscoveredChannels(List<Channel> discoveredChannels) {
- ArrayList<Channel> mutableChannels = new ArrayList<>(getThing().getChannels());
- Set<ChannelUID> newChannelUIDs = discoveredChannels.stream().map(Channel::getUID).collect(Collectors.toSet());
- // Take current channels but remove those channels that were just re-discovered
- List<Channel> existingChannelsWithNewlyDiscoveredChannelsRemoved = mutableChannels.stream()
- .filter(existingChannel -> !newChannelUIDs.contains(existingChannel.getUID()))
- .collect(Collectors.toList());
- if (existingChannelsWithNewlyDiscoveredChannelsRemoved.size() < mutableChannels.size()) {
- // We sort the channels for (mostly) consistent jsondb serialization
- existingChannelsWithNewlyDiscoveredChannelsRemoved.sort(CHANNEL_COMPARATOR_BY_UID);
- updateThingChannels(existingChannelsWithNewlyDiscoveredChannelsRemoved);
+ updateThingType(typeID);
}
}
- private void updateThingChannels(List<Channel> channelList) {
- ThingBuilder thingBuilder = editThing();
- thingBuilder.withChannels(channelList);
- updateThing(thingBuilder.build());
- }
-
@Override
protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
if (availabilityTopicsSeen.orElse(messageReceived)) {
super.handleConfigurationUpdate(configurationParameters);
}
- private void updateThingType() {
+ private boolean updateThingType(ThingTypeUID typeID) {
// if this is a dynamic type, then we update the type
- ThingTypeUID typeID = thing.getThingTypeUID();
if (!MqttBindingConstants.HOMEASSISTANT_MQTT_THING.equals(typeID)) {
- List<ChannelGroupDefinition> groupDefs;
- List<ChannelDefinition> channelDefs;
- synchronized (haComponents) { // sync whenever discoverComponents is started
- groupDefs = haComponents.values().stream().map(AbstractComponent::getGroupDefinition)
- .filter(Objects::nonNull).map(Objects::requireNonNull).collect(Collectors.toList());
- channelDefs = haComponents.values().stream().map(AbstractComponent::getChannels).flatMap(List::stream)
- .collect(Collectors.toList());
+ var thingTypeBuilder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING);
+
+ if (getThing().getThingTypeUID().equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) {
+ logger.debug("Migrating Home Assistant thing {} from generic type to dynamic type {}",
+ getThing().getUID(), typeID);
+
+ // just create an empty thing type for now; channel configurations won't follow over
+ // to the re-created Thing, so we need to re-discover them all anyway
+ channelTypeProvider.putThingType(thingTypeBuilder.build());
+ changeThingType(typeID, getConfig());
+ return false;
}
- var builder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING)
- .withChannelDefinitions(channelDefs).withChannelGroupDefinitions(groupDefs);
- Update updateComponent = this.updateComponent;
- if (updateComponent != null && updateComponent.isUpdatable()) {
- builder.withConfigDescriptionURI(UPDATABLE_CONFIG_DESCRIPTION_URI);
+
+ synchronized (haComponents) { // sync whenever discoverComponents is started
+ var sortedComponents = haComponents.values().stream().sorted(COMPONENT_COMPARATOR).toList();
+
+ var channelGroupTypes = sortedComponents.stream().map(c -> c.getChannelGroupType(typeID.getId()))
+ .filter(Objects::nonNull).map(Objects::requireNonNull).toList();
+ channelTypeProvider.updateChannelGroupTypesForPrefix(typeID.getId(), channelGroupTypes);
+
+ var groupDefs = sortedComponents.stream().map(c -> c.getGroupDefinition(typeID.getId()))
+ .filter(Objects::nonNull).map(Objects::requireNonNull).toList();
+ var channelDefs = sortedComponents.stream().map(AbstractComponent::getChannelDefinitions)
+ .flatMap(List::stream).toList();
+ thingTypeBuilder.withChannelDefinitions(channelDefs).withChannelGroupDefinitions(groupDefs);
+ Update updateComponent = this.updateComponent;
+ if (updateComponent != null && updateComponent.isUpdatable()) {
+ thingTypeBuilder.withConfigDescriptionURI(UPDATABLE_CONFIG_DESCRIPTION_URI);
+ }
+
+ channelTypeProvider.putThingType(thingTypeBuilder.build());
+
+ removeStateDescriptions();
+ sortedComponents.stream().forEach(c -> c.addStateDescriptions(stateDescriptionProvider));
+
+ ThingBuilder thingBuilder = editThing().withChannels();
+
+ sortedComponents.stream().map(AbstractComponent::getChannels).flatMap(List::stream)
+ .forEach(c -> thingBuilder.withChannel(c));
+
+ updateThing(thingBuilder.build());
}
- ThingType thingType = builder.build();
+ }
+ return true;
+ }
- channelTypeProvider.setThingType(typeID, thingType);
+ private ThingTypeUID calculateThingTypeUID(AbstractComponent component) {
+ return new ThingTypeUID(MqttBindingConstants.BINDING_ID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING.getId()
+ + "_" + component.getChannelConfiguration().getThingId(component.getHaID().objectID));
+ }
+
+ @Override
+ public void handleRemoval() {
+ synchronized (haComponents) {
+ channelTypeProvider.removeThingType(thing.getThingTypeUID());
+ channelTypeProvider.removeChannelGroupTypesForPrefix(thing.getThingTypeUID().getId());
+ removeStateDescriptions();
}
+ super.handleRemoval();
+ }
+
+ private void removeStateDescriptions() {
+ thing.getChannels().stream().forEach(c -> stateDescriptionProvider.remove(c.getUID()));
}
private void releaseStateUpdated(Update.ReleaseState state) {
<config-description uri="channel-type:mqtt:ha-channel">
<parameter name="component" type="text" readOnly="true" required="true">
<label>Component</label>
- <description>HomeAssistant component type (e.g. binary_sensor, switch, light)</description>
+ <description>Home Assistant component type (e.g. binary_sensor, switch, light)</description>
<default></default>
</parameter>
<parameter name="nodeid" type="text" readOnly="true">
</parameter>
<parameter name="objectid" type="text" readOnly="true" required="true">
<label>Object ID</label>
- <description>Object id of the component</description>
+ <description>Object ID of the component</description>
<default></default>
</parameter>
<parameter name="config" type="text" readOnly="true" required="true">
- <label>Json Configuration</label>
- <description>The json configuration string received by the component via MQTT.</description>
+ <label>JSON Configuration</label>
+ <description>The JSON configuration string received by the component via MQTT.</description>
<default></default>
</parameter>
</config-description>
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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="mqtt"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-type id="ha-color">
+ <item-type>Color</item-type>
+ <label>Color</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-dimmer">
+ <item-type>Dimmer</item-type>
+ <label>Dimmer</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-image">
+ <item-type>Image</item-type>
+ <label>Image</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-number">
+ <item-type>Number</item-type>
+ <label>Number</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-rollershutter">
+ <item-type>Rollershutter</item-type>
+ <label>Rollershutter</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-string">
+ <item-type>String</item-type>
+ <label>String</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-switch">
+ <item-type>Switch</item-type>
+ <label>Switch</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-trigger">
+ <kind>trigger</kind>
+ <label>Trigger</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-color-advanced" advanced="true">
+ <item-type>Color</item-type>
+ <label>Color</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-dimmer-advanced" advanced="true">
+ <item-type>Dimmer</item-type>
+ <label>Dimmer</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-image-advanced" advanced="true">
+ <item-type>Image</item-type>
+ <label>Image</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-number-advanced" advanced="true">
+ <item-type>Number</item-type>
+ <label>Number</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-rollershutter-advanced" advanced="true">
+ <item-type>Rollershutter</item-type>
+ <label>Rollershutter</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-string-advanced" advanced="true">
+ <item-type>String</item-type>
+ <label>String</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-switch-advanced" advanced="true">
+ <item-type>Switch</item-type>
+ <label>Switch</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+
+ <channel-type id="ha-trigger-advanced" advanced="true">
+ <kind>trigger</kind>
+ <label>Trigger</label>
+ <config-description-ref uri="channel-type:mqtt:ha-channel"/>
+ </channel-type>
+</thing:thing-descriptions>
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;
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;
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));
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();
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;
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;
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);
*/
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));
}
/**
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 {
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
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_\" }");
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);
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);
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);
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);
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}
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;
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,
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());
}
/**
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);
});
//
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);
});
//
thingHandler.delayedProcessing.forceProcessNow();
waitForAssert(() -> {
- assertThat("2 channel created", thingHandler.getThing().getChannels().size() == 2);
+ assertThat("2 channel created", nonSpyThingHandler.getThing().getChannels().size() == 2);
});
//
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);
});
}
"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();
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());
}
*/
package org.openhab.binding.mqtt.homie.internal.handler;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
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;
if (config.removetopics) {
this.removeRetainedTopics();
}
+ channelTypeProvider.removeThingType(thing.getThingTypeUID());
+ channelTypeProvider.removeChannelGroupTypesForPrefix(thing.getThingTypeUID().getId());
super.handleRemoval();
}
}
delayedProcessing.join();
device.stop();
- channelTypeProvider.removeThingType(device.thingTypeUID);
super.stop();
}
@Override
public void nodeRemoved(Node node) {
- channelTypeProvider.removeChannelGroupType(node.channelGroupTypeUID);
delayedProcessing.accept(node);
}
@Override
public void nodeAddedOrChanged(Node node) {
- channelTypeProvider.setChannelGroupType(node.channelGroupTypeUID, node.type());
delayedProcessing.accept(node);
}
private void updateThingType() {
// Make sure any dynamic channel types exist (i.e. ones created for a number channel with a specific dimension)
device.nodes.stream().flatMap(n -> n.properties.stream()).map(Property::getChannelType).filter(Objects::nonNull)
- .forEach(ct -> channelTypeProvider.setChannelType(ct.getUID(), ct));
+ .forEach(ct -> channelTypeProvider.putChannelType(Objects.requireNonNull(ct)));
// if this is a dynamic type, then we update the type
ThingTypeUID typeID = device.thingTypeUID;
if (!MqttBindingConstants.HOMIE300_MQTT_THING.equals(typeID)) {
- device.nodes.stream()
- .forEach(n -> channelTypeProvider.setChannelGroupType(n.channelGroupTypeUID, n.type()));
+ channelTypeProvider.updateChannelGroupTypesForPrefix(thing.getThingTypeUID().getId(), device.nodes.stream()
+ .map(n -> n.type(thing.getThingTypeUID().getId(), channelTypeProvider)).toList());
- List<ChannelGroupDefinition> groupDefs = device.nodes().stream().map(Node::getChannelGroupDefinition)
- .collect(Collectors.toList());
+ List<ChannelGroupDefinition> groupDefs = device.nodes.stream(nodeOrder())
+ .map(n -> n.getChannelGroupDefinition(thing.getThingTypeUID().getId())).toList();
var builder = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMIE300_MQTT_THING)
.withChannelGroupDefinitions(groupDefs);
- ThingType thingType = builder.build();
- channelTypeProvider.setThingType(typeID, thingType);
+ channelTypeProvider.putThingType(builder.build());
}
}
private void updateChannels() {
- List<Channel> channels = device.nodes().stream().flatMap(n -> n.properties.stream())
- .map(p -> p.getChannel(channelTypeRegistry)).collect(Collectors.toList());
+ List<Channel> channels = device.nodes.stream(nodeOrder())
+ .flatMap(node -> node.properties
+ .stream(node.propertyOrder(thing.getThingTypeUID().getId(), channelTypeProvider))
+ .map(p -> p.getChannel(channelTypeRegistry)))
+ .toList();
updateThing(editThing().withChannels(channels).build());
}
+
+ private Collection<String> nodeOrder() {
+ String[] nodes = device.attributes.nodes;
+ if (nodes != null) {
+ return Stream.of(nodes).toList();
+ }
+ ThingType thingType = channelTypeProvider.getThingType(thing.getThingTypeUID(), null);
+ if (thingType != null) {
+ return thingType.getChannelGroupDefinitions().stream().map(ChannelGroupDefinition::getId).toList();
+ }
+
+ return device.nodes.keySet();
+ }
}
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;
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;
// Runtime
public final DeviceCallback callback;
protected final ChannelGroupUID channelGroupUID;
- public final ChannelGroupTypeUID channelGroupTypeUID;
private final String topic;
private boolean initialized = false;
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<>();
}
/**
* Return the channel group type for this Node.
*/
- public ChannelGroupType type() {
- final List<ChannelDefinition> channelDefinitions = properties.stream()
- .map(p -> Objects.requireNonNull(p.getChannelDefinition())).collect(Collectors.toList());
- return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, attributes.name)
+ public ChannelGroupType type(String prefix, MqttChannelTypeProvider channelTypeProvider) {
+ final List<ChannelDefinition> channelDefinitions = properties.stream(propertyOrder(prefix, channelTypeProvider))
+ .map(p -> Objects.requireNonNull(p.getChannelDefinition())).toList();
+ return ChannelGroupTypeBuilder.instance(getChannelGroupTypeUID(prefix), attributes.name)
.withChannelDefinitions(channelDefinitions).build();
}
- public ChannelGroupDefinition getChannelGroupDefinition() {
- return new ChannelGroupDefinition(channelGroupUID.getId(), channelGroupTypeUID, attributes.name, null);
+ public ChannelGroupDefinition getChannelGroupDefinition(String prefix) {
+ return new ChannelGroupDefinition(channelGroupUID.getId(), getChannelGroupTypeUID(prefix), attributes.name,
+ null);
}
/**
return topics;
}
+
+ public Collection<String> propertyOrder(String prefix, MqttChannelTypeProvider channelTypeProvider) {
+ String[] properties = attributes.properties;
+ if (properties != null) {
+ return Stream.of(properties).toList();
+ }
+ ChannelGroupType channelGroupType = channelTypeProvider.getChannelGroupType(getChannelGroupTypeUID(prefix),
+ null);
+ if (channelGroupType != null) {
+ return channelGroupType.getChannelDefinitions().stream().map(ChannelDefinition::getId).toList();
+ }
+ return this.properties.keySet();
+ }
+
+ private ChannelGroupTypeUID getChannelGroupTypeUID(String prefix) {
+ return new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, prefix + "_" + UIDUtils.encode(this.topic));
+ }
}
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;
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();
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"));
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;
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;
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();
};
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));
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));
}
}