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