]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt.homeassistant] handle multiple availability topics (#15977)
authorCody Cutrer <cody@cutrer.us>
Thu, 7 Dec 2023 19:22:03 +0000 (12:22 -0700)
committerGitHub <noreply@github.com>
Thu, 7 Dec 2023 19:22:03 +0000 (20:22 +0100)
* [mqtt.homeassistant] handle multiple availability topics

---------

Signed-off-by: Cody Cutrer <cody@cutrer.us>
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AbstractMQTTThingHandler.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/GenericMQTTThingHandler.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java

index 2e7ccfc065620eb199ebf855fa0658e557355f62..d285c92b1720e304c25ab40c7cd85434eb89beb5 100644 (file)
@@ -83,6 +83,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
 
     private AtomicBoolean messageReceived = new AtomicBoolean(false);
     private Map<String, @Nullable ChannelState> 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<Boolean> 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);
     }
index 9aa544a27a7e37106e0132884553488ac3d2af38..50b48910fc1f66e92f25c02602ce00ed56bf93dd 100644 (file)
@@ -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.
index 9a88492eecccd1bfeb369a944f0474c8f1da8cfd..4ebc794e52dd6b0db8750cee61b6ee0b8f491433 100644 (file)
@@ -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
index ebe696ee01aa5e5087dd02ac38ccc81e3116b6df..2105bfc3bf0102ea45774089363dd01c185a858f 100644 (file)
@@ -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<C extends AbstractChannelConfiguration>
 
         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<Availability> 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());
         }
     }