]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt.homeassistant] Make sensors compliant (#8591)
authorJochen Klein <git@jochen.susca.de>
Fri, 2 Oct 2020 01:07:08 +0000 (03:07 +0200)
committerGitHub <noreply@github.com>
Fri, 2 Oct 2020 01:07:08 +0000 (18:07 -0700)
Signed-off-by: Jochen Klein <git@jochen.susca.de>
18 files changed:
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/internal/handler/GenericMQTTThingHandler.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CFactory.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentBinarySensor.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCover.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentFan.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentLight.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentLock.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSensor.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSwitch.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ChannelStateUpdateListenerProxy.java [new file with mode: 0644]
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java [new file with mode: 0644]
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java [new file with mode: 0644]
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java

index 4c9dbc46913e5c274721755aed47038d0405ed4a..7d070f4c6b3a1db65ede50e2bce1b302040efd10 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.generic;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
@@ -355,16 +356,16 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
     }
 
     protected void calculateThingStatus() {
-        final boolean availabilityTopicsSeen;
+        final Optional<Boolean> availabilityTopicsSeen;
 
         if (availabilityStates.isEmpty()) {
-            availabilityTopicsSeen = true;
+            availabilityTopicsSeen = Optional.empty();
         } else {
-            availabilityTopicsSeen = availabilityStates.values().stream().allMatch(
-                    c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class)));
+            availabilityTopicsSeen = Optional.of(availabilityStates.values().stream().allMatch(
+                    c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class))));
         }
         updateThingStatus(messageReceived.get(), availabilityTopicsSeen);
     }
 
-    protected abstract void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen);
+    protected abstract void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen);
 }
index e2375aa889c8316898844946f2586de038f796a8..a2f6af120a9692687c7519ce175a01e94fec239e 100644 (file)
@@ -16,6 +16,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -186,8 +187,8 @@ public class GenericMQTTThingHandler extends AbstractMQTTThingHandler implements
     }
 
     @Override
-    protected void updateThingStatus(boolean messageReceived, boolean availibilityTopicsSeen) {
-        if (messageReceived || availibilityTopicsSeen) {
+    protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availibilityTopicsSeen) {
+        if (availibilityTopicsSeen.orElse(true)) {
             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
         } else {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);
index b82610e57c840e283a55e50a3414271c7ecc33c8..4df8760ebb99f1481bdd1e57ae2021cc37d98a4c 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.mqtt.homeassistant.internal;
 
+import java.util.concurrent.ScheduledExecutorService;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.AvailabilityTracker;
@@ -46,9 +48,10 @@ public class CFactory {
      */
     public static @Nullable AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID,
             String channelConfigurationJSON, ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
-            Gson gson, TransformationServiceProvider transformationServiceProvider) {
+            ScheduledExecutorService scheduler, Gson gson,
+            TransformationServiceProvider transformationServiceProvider) {
         ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
-                channelConfigurationJSON, gson, updateListener, tracker)
+                channelConfigurationJSON, gson, updateListener, tracker, scheduler)
                         .transformationProvider(transformationServiceProvider);
         try {
             switch (haID.component) {
@@ -86,16 +89,19 @@ public class CFactory {
         private final ChannelStateUpdateListener updateListener;
         private final AvailabilityTracker tracker;
         private final Gson gson;
+        private final ScheduledExecutorService scheduler;
         private @Nullable TransformationServiceProvider transformationServiceProvider;
 
         protected ComponentConfiguration(ThingUID thingUID, HaID haID, String configJSON, Gson gson,
-                ChannelStateUpdateListener updateListener, AvailabilityTracker tracker) {
+                ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
+                ScheduledExecutorService scheduler) {
             this.thingUID = thingUID;
             this.haID = haID;
             this.configJSON = configJSON;
             this.gson = gson;
             this.updateListener = updateListener;
             this.tracker = tracker;
+            this.scheduler = scheduler;
         }
 
         public ComponentConfiguration transformationProvider(
@@ -133,6 +139,10 @@ public class CFactory {
             return tracker;
         }
 
+        public ScheduledExecutorService getScheduler() {
+            return scheduler;
+        }
+
         public <C extends BaseChannelConfiguration> C getConfig(Class<C> clazz) {
             return BaseChannelConfiguration.fromString(configJSON, gson, clazz);
         }
index 604e067682eb308d73429ab2dc7f158336025360..de70b1acf28ea9096a2e1af75700ef5c06826319 100644 (file)
@@ -69,19 +69,16 @@ public class ComponentAlarmControlPanel extends AbstractComponent<ComponentAlarm
         String command_topic = channelConfiguration.command_topic;
         if (command_topic != null) {
             buildChannel(switchDisarmChannelID, new TextValue(new String[] { channelConfiguration.payload_disarm }),
-                    channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                            .commandTopic(command_topic, channelConfiguration.retain)//
-                            .build();
+                    channelConfiguration.name, componentConfiguration.getUpdateListener())
+                            .commandTopic(command_topic, channelConfiguration.retain).build();
 
             buildChannel(switchArmHomeChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_home }),
-                    channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                            .commandTopic(command_topic, channelConfiguration.retain)//
-                            .build();
+                    channelConfiguration.name, componentConfiguration.getUpdateListener())
+                            .commandTopic(command_topic, channelConfiguration.retain).build();
 
             buildChannel(switchArmAwayChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_away }),
-                    channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                            .commandTopic(command_topic, channelConfiguration.retain)//
-                            .build();
+                    channelConfiguration.name, componentConfiguration.getUpdateListener())
+                            .commandTopic(command_topic, channelConfiguration.retain).build();
         }
     }
 }
index 313a14078c925238ed70f6d0a39e8b882d0339d0..53f124d4c3ad4b155d6de69b7001c3105a9bb71d 100644 (file)
  */
 package org.openhab.binding.mqtt.homeassistant.internal;
 
+import java.util.List;
+
 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.OnOffValue;
+import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
+import org.openhab.binding.mqtt.homeassistant.internal.listener.OffDelayUpdateStateListener;
 
 /**
  * A MQTT BinarySensor, following the https://www.home-assistant.io/components/binary_sensor.mqtt/ specification.
@@ -35,23 +41,40 @@ public class ComponentBinarySensor extends AbstractComponent<ComponentBinarySens
 
         protected @Nullable String device_class;
         protected boolean force_update = false;
-        protected int expire_after = 0;
+        protected @Nullable Integer expire_after;
+        protected @Nullable Integer off_delay;
 
         protected String state_topic = "";
         protected String payload_on = "ON";
         protected String payload_off = "OFF";
+
+        protected @Nullable String json_attributes_topic;
+        protected @Nullable String json_attributes_template;
+        protected @Nullable List<String> json_attributes;
     }
 
     public ComponentBinarySensor(CFactory.ComponentConfiguration componentConfiguration) {
         super(componentConfiguration, ChannelConfiguration.class);
 
-        if (channelConfiguration.force_update) {
-            throw new UnsupportedOperationException("Component:Sensor does not support forced updates");
+        OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off);
+
+        buildChannel(sensorChannelID, value, "value", getListener(componentConfiguration, value))
+                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template).build();
+    }
+
+    private ChannelStateUpdateListener getListener(CFactory.ComponentConfiguration componentConfiguration,
+            Value value) {
+        ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
+
+        if (channelConfiguration.expire_after != null) {
+            updateListener = new ExpireUpdateStateListener(updateListener, channelConfiguration.expire_after, value,
+                    componentConfiguration.getTracker(), componentConfiguration.getScheduler());
+        }
+        if (channelConfiguration.off_delay != null) {
+            updateListener = new OffDelayUpdateStateListener(updateListener, channelConfiguration.off_delay, value,
+                    componentConfiguration.getScheduler());
         }
 
-        buildChannel(sensorChannelID, new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off),
-                channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                        .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
-                        .build();
+        return updateListener;
     }
 }
index 1548742268734fe0cdf6c68e6a8c9282ca28d0bb..49edafb7d9acf1d394cb062a51f2638a4e525439 100644 (file)
@@ -42,8 +42,7 @@ public class ComponentCamera extends AbstractComponent<ComponentCamera.ChannelCo
 
         ImageValue value = new ImageValue();
 
-        buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                .stateTopic(channelConfiguration.topic)//
-                .build();
+        buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
+                .stateTopic(channelConfiguration.topic).build();
     }
 }
index b732455a5b89ef191634e04fa6534882c6d18d7c..329fd035dd440853ca46937804580c31039f8c43 100644 (file)
@@ -48,9 +48,8 @@ public class ComponentCover extends AbstractComponent<ComponentCover.ChannelConf
         RollershutterValue value = new RollershutterValue(channelConfiguration.payload_open,
                 channelConfiguration.payload_close, channelConfiguration.payload_stop);
 
-        buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
-                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
-                .build();
+        buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
+                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
     }
 }
index b77869d6867244f6afd2ddd039135d692d599101..8ee834a5e824ef934f7543130988fbfa2434e3a3 100644 (file)
@@ -45,9 +45,8 @@ public class ComponentFan extends AbstractComponent<ComponentFan.ChannelConfigur
         super(componentConfiguration, ChannelConfiguration.class);
 
         OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off);
-        buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
-                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
-                .build();
+        buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
+                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
     }
 }
index c817539ffc601460463acbb950e8e5201cdc14dc..f6ea9b6bbccc8fa47c0ff93a5b574011b945b2e4 100644 (file)
@@ -105,22 +105,18 @@ public class ComponentLight extends AbstractComponent<ComponentLight.ChannelConf
                 channelConfiguration.payload_off, 100);
 
         // Create three MQTT subscriptions and use this class object as update listener
-        switchChannel = buildChannel(switchChannelID, value, channelConfiguration.name, this)//
-                // Some lights use the value_template field for the template, most use state_value_template
+        switchChannel = buildChannel(switchChannelID, value, channelConfiguration.name, this)
                 .stateTopic(channelConfiguration.state_topic, channelConfiguration.state_value_template,
-                        channelConfiguration.value_template)//
-                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
-                .build(false);
-
-        colorChannel = buildChannel(colorChannelID, value, channelConfiguration.name, this)//
-                .stateTopic(channelConfiguration.rgb_state_topic, channelConfiguration.rgb_value_template)//
-                .commandTopic(channelConfiguration.rgb_command_topic, channelConfiguration.retain)//
-                .build(false);
-
-        brightnessChannel = buildChannel(brightnessChannelID, value, channelConfiguration.name, this)//
-                .stateTopic(channelConfiguration.brightness_state_topic, channelConfiguration.brightness_value_template)//
-                .commandTopic(channelConfiguration.brightness_command_topic, channelConfiguration.retain)//
-                .build(false);
+                        channelConfiguration.value_template)
+                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build(false);
+
+        colorChannel = buildChannel(colorChannelID, value, channelConfiguration.name, this)
+                .stateTopic(channelConfiguration.rgb_state_topic, channelConfiguration.rgb_value_template)
+                .commandTopic(channelConfiguration.rgb_command_topic, channelConfiguration.retain).build(false);
+
+        brightnessChannel = buildChannel(brightnessChannelID, value, channelConfiguration.name, this)
+                .stateTopic(channelConfiguration.brightness_state_topic, channelConfiguration.brightness_value_template)
+                .commandTopic(channelConfiguration.brightness_command_topic, channelConfiguration.retain).build(false);
 
         channels.put(colorChannelID, colorChannel);
     }
index 81316989c60f29ab1ca14eae06c555ad7ff46b28..97d8b68cafea5787a90d0278d57f30f2e27662f9 100644 (file)
@@ -52,9 +52,8 @@ public class ComponentLock extends AbstractComponent<ComponentLock.ChannelConfig
 
         buildChannel(switchChannelID,
                 new OnOffValue(channelConfiguration.payload_lock, channelConfiguration.payload_unlock),
-                channelConfiguration.name, componentConfiguration.getUpdateListener())//
-                        .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
-                        .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
-                        .build();
+                channelConfiguration.name, componentConfiguration.getUpdateListener())
+                        .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+                        .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
     }
 }
index f658fc9985283a3f1a606a93964066ecd58cd329..c4848b4ded47595cc211ae84852a453588f00fd3 100644 (file)
  */
 package org.openhab.binding.mqtt.homeassistant.internal;
 
+import java.util.List;
 import java.util.regex.Pattern;
 
 import org.apache.commons.lang.StringUtils;
 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.NumberValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
 
 /**
  * A MQTT sensor, following the https://www.home-assistant.io/components/sensor.mqtt/ specification.
@@ -42,18 +45,18 @@ public class ComponentSensor extends AbstractComponent<ComponentSensor.ChannelCo
         protected @Nullable String unit_of_measurement;
         protected @Nullable String device_class;
         protected boolean force_update = false;
-        protected int expire_after = 0;
+        protected @Nullable Integer expire_after;
 
         protected String state_topic = "";
+
+        protected @Nullable String json_attributes_topic;
+        protected @Nullable String json_attributes_template;
+        protected @Nullable List<String> json_attributes;
     }
 
     public ComponentSensor(CFactory.ComponentConfiguration componentConfiguration) {
         super(componentConfiguration, ChannelConfiguration.class);
 
-        if (channelConfiguration.force_update) {
-            throw new UnsupportedOperationException("Component:Sensor does not support forced updates");
-        }
-
         Value value;
 
         String uom = channelConfiguration.unit_of_measurement;
@@ -68,8 +71,19 @@ public class ComponentSensor extends AbstractComponent<ComponentSensor.ChannelCo
 
         boolean trigger = triggerIcons.matcher(icon).matches();
 
-        buildChannel(sensorChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
+        buildChannel(sensorChannelID, value, channelConfiguration.name, getListener(componentConfiguration, value))
                 .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
                 .trigger(trigger).build();
     }
+
+    private ChannelStateUpdateListener getListener(CFactory.ComponentConfiguration componentConfiguration,
+            Value value) {
+        ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
+
+        if (channelConfiguration.expire_after != null) {
+            updateListener = new ExpireUpdateStateListener(updateListener, channelConfiguration.expire_after, value,
+                    componentConfiguration.getTracker(), componentConfiguration.getScheduler());
+        }
+        return updateListener;
+    }
 }
index 95a808896a001f761311a5042762212d400200ff..151d3bf1b6a325e148cd47e2a186b2edded192da 100644 (file)
@@ -66,9 +66,9 @@ public class ComponentSwitch extends AbstractComponent<ComponentSwitch.ChannelCo
         OnOffValue value = new OnOffValue(state_on, state_off, channelConfiguration.payload_on,
                 channelConfiguration.payload_off);
 
-        buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener())//
-                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
-                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos)//
+        buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener())
+                .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+                .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos)
                 .build();
     }
 }
index c630e6ffb235b547e75180d2ec9c05fd3d382311..07204fe088304a74a11fe8b010229bacf36e0806 100644 (file)
@@ -96,7 +96,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
         AbstractComponent<?> component = null;
 
         if (config.length() > 0) {
-            component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, gson,
+            component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler, gson,
                     transformationServiceProvider);
         }
         if (component != null) {
index d68ab44014c8576b98f29a72fcb5e61164821578..14332caf17cf51f8e1a480bfd9b3f6c3d0fae673 100644 (file)
@@ -17,6 +17,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
@@ -152,8 +153,8 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
             if (channelConfigurationJSON == null) {
                 logger.warn("Provided channel does not have a 'config' configuration key!");
             } else {
-                component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, gson,
-                        transformationServiceProvider);
+                component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, scheduler,
+                        gson, transformationServiceProvider);
             }
 
             if (component != null) {
@@ -296,8 +297,8 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
     }
 
     @Override
-    protected void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen) {
-        if (!messageReceived || availabilityTopicsSeen) {
+    protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
+        if (availabilityTopicsSeen.orElse(messageReceived)) {
             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
         } else {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ChannelStateUpdateListenerProxy.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ChannelStateUpdateListenerProxy.java
new file mode 100644 (file)
index 0000000..0ef81ad
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2020 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.listener;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+
+/**
+ * A proxy class for {@link ChannelStateUpdateListener} forwarding everything to the real listener.
+ * <p>
+ * This class is used to be able handle special cases like timeouts.
+ *
+ * @author Jochen Klein - Initial contribution
+ */
+@NonNullByDefault
+public abstract class ChannelStateUpdateListenerProxy implements ChannelStateUpdateListener {
+
+    private final ChannelStateUpdateListener original;
+
+    public ChannelStateUpdateListenerProxy(ChannelStateUpdateListener original) {
+        this.original = original;
+    }
+
+    @Override
+    public void updateChannelState(@NonNull ChannelUID channelUID, @NonNull State value) {
+        original.updateChannelState(channelUID, value);
+    }
+
+    @Override
+    public void postChannelCommand(@NonNull ChannelUID channelUID, @NonNull Command value) {
+        original.postChannelCommand(channelUID, value);
+    }
+
+    @Override
+    public void triggerChannel(@NonNull ChannelUID channelUID, @NonNull String eventPayload) {
+        original.triggerChannel(channelUID, eventPayload);
+    }
+}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java
new file mode 100644 (file)
index 0000000..40df667
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2010-2020 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.listener;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+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.values.Value;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * A listener to reset the channel value after a timeout.
+ *
+ * @author Jochen Klein - Initial contribution
+ */
+@NonNullByDefault
+public class ExpireUpdateStateListener extends ChannelStateUpdateListenerProxy {
+
+    private final int expireAfter;
+    private final Value value;
+    private final AvailabilityTracker tracker;
+    private final ScheduledExecutorService scheduler;
+
+    private AtomicReference<@Nullable ScheduledFuture<?>> expire = new AtomicReference<>();
+
+    public ExpireUpdateStateListener(ChannelStateUpdateListener original, int expireAfter, Value value,
+            AvailabilityTracker tracker, ScheduledExecutorService scheduler) {
+        super(original);
+        this.expireAfter = expireAfter;
+        this.value = value;
+        this.tracker = tracker;
+        this.scheduler = scheduler;
+    }
+
+    @Override
+    public void updateChannelState(final ChannelUID channelUID, State state) {
+        super.updateChannelState(channelUID, state);
+
+        ScheduledFuture<?> oldExpire = expire.getAndSet(scheduler.schedule(() -> {
+            value.resetState();
+            tracker.resetMessageReceived();
+            ExpireUpdateStateListener.super.updateChannelState(channelUID, value.getChannelState());
+        }, expireAfter, TimeUnit.SECONDS));
+
+        if (oldExpire != null) {
+            oldExpire.cancel(false);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java
new file mode 100644 (file)
index 0000000..9602000
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2020 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.listener;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+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.Value;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * A listener to set the binary sensor value to 'off' after a timeout.
+ *
+ * @author Jochen Klein - Initial contribution
+ */
+@NonNullByDefault
+public class OffDelayUpdateStateListener extends ChannelStateUpdateListenerProxy {
+
+    private final int offDelay;
+    private final Value value;
+    private final ScheduledExecutorService scheduler;
+
+    private AtomicReference<@Nullable ScheduledFuture<?>> delay = new AtomicReference<>();
+
+    public OffDelayUpdateStateListener(ChannelStateUpdateListener original, int offDelay, Value value,
+            ScheduledExecutorService scheduler) {
+        super(original);
+        this.offDelay = offDelay;
+        this.value = value;
+        this.scheduler = scheduler;
+    }
+
+    @Override
+    public void updateChannelState(final ChannelUID channelUID, State state) {
+        super.updateChannelState(channelUID, state);
+
+        ScheduledFuture<?> newDelay = null;
+
+        if (OnOffType.ON == state) {
+            newDelay = scheduler.schedule(() -> {
+                value.update(OnOffType.OFF);
+                OffDelayUpdateStateListener.super.updateChannelState(channelUID, value.getChannelState());
+            }, offDelay, TimeUnit.SECONDS);
+        }
+
+        ScheduledFuture<?> oldDelay = delay.getAndSet(newDelay);
+        if (oldDelay != null) {
+            oldDelay.cancel(false);
+        }
+    }
+}
index 6d974bf58e80b01d9fbdb2000794651ee79c3967..55cd9eeba68ee03a22793937024a61adc8678e42 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.mqtt.homie.internal.handler;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledFuture;
 import java.util.function.Consumer;
@@ -246,7 +247,7 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
     }
 
     @Override
-    protected void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen) {
+    protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
         // not used here
     }
 }