Skip to content

Commit

Permalink
[mqtt] Use AbstractStorageBasedTypeProvider
Browse files Browse the repository at this point in the history
Includes both homie and homeassistant dynamic things.
It also fixes all ordering issues in both bindings in order to
be consistent in how types are persisted:
 * For Homie, Nodes (channel groups) and Properties (channels)
   are ordered in the way they are defined in $nodes and $properties
 * For Home Assistant, Components are ordered by label. This
   includes both single channel components that are not in a channel
   group, as well as channel groups. We also ensure that on the
   Thing itself non-grouped channels consistently sort before grouped
   channels.

Signed-off-by: Cody Cutrer <[email protected]>
  • Loading branch information
ccutrer committed Jan 19, 2024
1 parent 91ad620 commit ac4a462
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,19 @@

import java.net.URI;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.generic.internal.MqttThingHandlerFactory;
import org.openhab.core.storage.StorageService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.AbstractStorageBasedTypeProvider;
import org.openhab.core.thing.binding.ThingTypeProvider;
import org.openhab.core.thing.type.ChannelGroupType;
import org.openhab.core.thing.type.ChannelGroupTypeProvider;
import org.openhab.core.thing.type.ChannelGroupTypeUID;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeProvider;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.thing.type.ThingType;
import org.openhab.core.thing.type.ThingTypeBuilder;
import org.openhab.core.thing.type.ThingTypeRegistry;
Expand All @@ -45,89 +42,30 @@
* 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 MqttChannelTypeProvider(@Reference ThingTypeRegistry thingTypeRegistry,
@Reference StorageService storageService) {
super(storageService);
this.thingTypeRegistry = thingTypeRegistry;
}

public void setThingTypeIfAbsent(ThingTypeUID uid, ThingType type) {
things.putIfAbsent(uid, type);
public void putThingTypeIfAbsent(ThingTypeUID uid, ThingType type) {
if (getThingType(uid, null) == null) {
putThingType(type);
}
}

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())
Expand Down Expand Up @@ -155,4 +93,25 @@ public ThingTypeBuilder derive(ThingTypeUID newTypeId, ThingTypeUID baseTypeId)

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.stream().forEach(uid -> removeChannelGroupType(uid));
types.stream().forEach(t -> putChannelGroupType(t));
}

public void removeChannelGroupTypesForPrefix(String prefix) {
channelGroupTypesForPrefix(prefix).stream().forEach(cgt -> removeChannelGroupType(cgt.getUID()));
}

private Collection<ChannelGroupType> channelGroupTypesForPrefix(String prefix) {
return getChannelGroupTypes(null).stream().filter(cgt -> cgt.getUID().getId().startsWith(prefix + "_"))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,6 +67,24 @@ public Stream<T> stream() {
return map.values().stream();
}

/**
* Streams the objects in this map in the order of the given keys.
*
* Extraneous keys are ignored, and missing keys are included at the end in unspecified order.
*
* @param order The keys in the order they should be streamed
*/
public Stream<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.
*
Expand Down Expand Up @@ -134,4 +155,8 @@ public void clear() {
public void put(String key, T value) {
map.put(key, value);
}

public Set<String> keySet() {
return map.keySet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.mqtt.homeassistant.internal;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
Expand Down Expand Up @@ -59,39 +60,26 @@
public class ComponentChannel {
private static final String JINJA = "JINJA";

private final ChannelUID channelUID;
private final ChannelState channelState;
private final Channel channel;
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,
ChannelTypeUID channelTypeUID, @Nullable StateDescription stateDescription,
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.channelTypeUID = channelTypeUID;
this.stateDescription = stateDescription;
this.commandDescription = commandDescription;
this.channelStateUpdateListener = channelStateUpdateListener;
}

public ChannelUID getChannelUID() {
return channelUID;
}

public Channel getChannel() {
return channel;
}

public ChannelTypeUID getChannelTypeUID() {
return channelTypeUID;
}

public ChannelState getState() {
return channelState;
}
Expand All @@ -117,7 +105,8 @@ public ChannelState getState() {
}

public ChannelDefinition channelDefinition() {
return new ChannelDefinitionBuilder(channelUID.getId(), channelTypeUID).build();
return new ChannelDefinitionBuilder(channel.getUID().getId(),
Objects.requireNonNull(channel.getChannelTypeUID())).withLabel(channel.getLabel()).build();
}

public void resetState() {
Expand Down Expand Up @@ -271,8 +260,8 @@ public ComponentChannel build(boolean addToComponent) {
.withKind(kind).withLabel(label).withConfiguration(configuration)
.withAutoUpdatePolicy(autoUpdatePolicy).build();

ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, channelTypeUID,
stateDescription, commandDescription, channelStateUpdateListener);
ComponentChannel result = new ComponentChannel(channelState, channel, stateDescription, commandDescription,
channelStateUpdateListener);

TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider();

Expand Down
Loading

0 comments on commit ac4a462

Please sign in to comment.