diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AbstractMQTTThingHandler.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AbstractMQTTThingHandler.java index 2e7ccfc06..d285c92b1 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AbstractMQTTThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AbstractMQTTThingHandler.java @@ -83,6 +83,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler private AtomicBoolean messageReceived = new AtomicBoolean(false); private Map availabilityStates = new ConcurrentHashMap<>(); + private AvailabilityMode availabilityMode = AvailabilityMode.ALL; public AbstractMQTTThingHandler(Thing thing, int subscribeTimeout) { super(thing); @@ -261,7 +262,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler @Override public void updateChannelState(ChannelUID channelUID, State value) { if (messageReceived.compareAndSet(false, true)) { - calculateThingStatus(); + calculateAndUpdateThingStatus(true); } super.updateState(channelUID, value); } @@ -269,7 +270,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler @Override public void triggerChannel(ChannelUID channelUID, String event) { if (messageReceived.compareAndSet(false, true)) { - calculateThingStatus(); + calculateAndUpdateThingStatus(true); } super.triggerChannel(channelUID, event); } @@ -292,6 +293,11 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler this.connection = connection; } + @Override + public void setAvailabilityMode(AvailabilityMode mode) { + this.availabilityMode = mode; + } + @Override public void addAvailabilityTopic(String availability_topic, String payload_available, String payload_not_available) { @@ -310,7 +316,8 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler channelUID, value, new ChannelStateUpdateListener() { @Override public void updateChannelState(ChannelUID channelUID, State value) { - calculateThingStatus(); + boolean online = value.equals(OnOffType.ON); + calculateAndUpdateThingStatus(online); } @Override @@ -352,18 +359,23 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler @Override public void resetMessageReceived() { if (messageReceived.compareAndSet(true, false)) { - calculateThingStatus(); + calculateAndUpdateThingStatus(false); } } - protected void calculateThingStatus() { + protected void calculateAndUpdateThingStatus(boolean lastValue) { final Optional availabilityTopicsSeen; if (availabilityStates.isEmpty()) { availabilityTopicsSeen = Optional.empty(); } else { - availabilityTopicsSeen = Optional.of(availabilityStates.values().stream().allMatch( - c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class)))); + availabilityTopicsSeen = switch (availabilityMode) { + case ALL -> Optional.of(availabilityStates.values().stream().allMatch( + c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class)))); + case ANY -> Optional.of(availabilityStates.values().stream().anyMatch( + c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class)))); + case LATEST -> Optional.of(lastValue); + }; } updateThingStatus(messageReceived.get(), availabilityTopicsSeen); } diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java index 9aa544a27..50b48910f 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java @@ -19,9 +19,37 @@ import org.eclipse.jdt.annotation.Nullable; * Interface to keep track of the availability of device using an availability topic or messages received * * @author Jochen Klein - Initial contribution + * @author Cody Cutrer - Support all/any/latest */ @NonNullByDefault public interface AvailabilityTracker { + /** + * controls the conditions needed to set the entity to available + */ + enum AvailabilityMode { + /** + * payload_available must be received on all configured availability topics before the entity is marked as + * online + */ + ALL, + + /** + * payload_available must be received on at least one configured availability topic before the entity is marked + * as online + */ + ANY, + + /** + * the last payload_available or payload_not_available received on any configured availability topic controls + * the availability + */ + LATEST + } + + /** + * Sets how multiple availability topics are treated + */ + void setAvailabilityMode(AvailabilityMode mode); /** * Adds an availability topic to determine the availability of a device. diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/GenericMQTTThingHandler.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/GenericMQTTThingHandler.java index 9a88492ee..4ebc794e5 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/GenericMQTTThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/GenericMQTTThingHandler.java @@ -93,7 +93,7 @@ public class GenericMQTTThingHandler extends AbstractMQTTThingHandler implements clearAllAvailabilityTopics(); initializeAvailabilityTopicsFromConfig(); return channelStateByChannelUID.values().stream().map(c -> c.start(connection, scheduler, 0)) - .collect(FutureCollector.allOf()).thenRun(this::calculateThingStatus); + .collect(FutureCollector.allOf()).thenRun(() -> calculateAndUpdateThingStatus(false)); } @Override diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index ebe696ee0..2105bfc3b 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; 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.TransformationServiceProvider; @@ -32,6 +33,8 @@ import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.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.ChannelGroupUID; @@ -99,15 +102,36 @@ public abstract class AbstractComponent this.configSeen = false; - String availabilityTopic = this.channelConfiguration.getAvailabilityTopic(); - if (availabilityTopic != null) { - String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate(); - if (availabilityTemplate != null) { - availabilityTemplate = JINJA_PREFIX + availabilityTemplate; + final List availabilities = channelConfiguration.getAvailability(); + if (availabilities != null) { + AvailabilityMode mode = channelConfiguration.getAvailabilityMode(); + AvailabilityTracker.AvailabilityMode availabilityTrackerMode = switch (mode) { + case ALL -> AvailabilityTracker.AvailabilityMode.ALL; + case ANY -> AvailabilityTracker.AvailabilityMode.ANY; + case LATEST -> AvailabilityTracker.AvailabilityMode.LATEST; + }; + componentConfiguration.getTracker().setAvailabilityMode(availabilityTrackerMode); + for (Availability availability : availabilities) { + String availabilityTemplate = availability.getValueTemplate(); + if (availabilityTemplate != null) { + availabilityTemplate = JINJA_PREFIX + availabilityTemplate; + } + componentConfiguration.getTracker().addAvailabilityTopic(availability.getTopic(), + availability.getPayloadAvailable(), availability.getPayloadNotAvailable(), availabilityTemplate, + componentConfiguration.getTransformationServiceProvider()); + } + } else { + String availabilityTopic = this.channelConfiguration.getAvailabilityTopic(); + if (availabilityTopic != null) { + String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate(); + if (availabilityTemplate != null) { + availabilityTemplate = JINJA_PREFIX + availabilityTemplate; + } + componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic, + this.channelConfiguration.getPayloadAvailable(), + this.channelConfiguration.getPayloadNotAvailable(), availabilityTemplate, + componentConfiguration.getTransformationServiceProvider()); } - componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic, - this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(), - availabilityTemplate, componentConfiguration.getTransformationServiceProvider()); } }