From f06068a18952497e68cda0b77dd68c5dda0c155b Mon Sep 17 00:00:00 2001 From: Jochen Klein Date: Fri, 2 Oct 2020 03:07:08 +0200 Subject: [PATCH] [mqtt.homeassistant] Make sensors compliant (#8591) Signed-off-by: Jochen Klein --- .../generic/AbstractMQTTThingHandler.java | 11 +-- .../handler/GenericMQTTThingHandler.java | 5 +- .../mqtt/homeassistant/internal/CFactory.java | 16 ++++- .../internal/ComponentAlarmControlPanel.java | 15 ++-- .../internal/ComponentBinarySensor.java | 37 ++++++++-- .../internal/ComponentCamera.java | 5 +- .../internal/ComponentCover.java | 7 +- .../homeassistant/internal/ComponentFan.java | 7 +- .../internal/ComponentLight.java | 22 +++--- .../homeassistant/internal/ComponentLock.java | 7 +- .../internal/ComponentSensor.java | 26 +++++-- .../internal/ComponentSwitch.java | 6 +- .../internal/DiscoverComponents.java | 2 +- .../handler/HomeAssistantThingHandler.java | 9 +-- .../ChannelStateUpdateListenerProxy.java | 52 ++++++++++++++ .../listener/ExpireUpdateStateListener.java | 66 ++++++++++++++++++ .../listener/OffDelayUpdateStateListener.java | 68 +++++++++++++++++++ .../internal/handler/HomieThingHandler.java | 3 +- 18 files changed, 295 insertions(+), 69 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ChannelStateUpdateListenerProxy.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java 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 4c9dbc469..7d070f4c6 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 @@ -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 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 availabilityTopicsSeen); } 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 e2375aa88..a2f6af120 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 @@ -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 availibilityTopicsSeen) { + if (availibilityTopicsSeen.orElse(true)) { 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/CFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CFactory.java index b82610e57..4df8760eb 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CFactory.java @@ -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 getConfig(Class clazz) { return BaseChannelConfiguration.fromString(configJSON, gson, clazz); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java index 604e06768..de70b1acf 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java @@ -69,19 +69,16 @@ public class ComponentAlarmControlPanel extends AbstractComponent 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; } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java index 154874226..49edafb7d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java @@ -42,8 +42,7 @@ public class ComponentCamera extends AbstractComponent 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 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) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java index d68ab4401..14332caf1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java @@ -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 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 index 000000000..0ef81addd --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ChannelStateUpdateListenerProxy.java @@ -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. + *

+ * 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 index 000000000..40df6670a --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java @@ -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 index 000000000..9602000d2 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java @@ -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); + } + } +} diff --git a/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java b/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java index 6d974bf58..55cd9eeba 100644 --- a/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/handler/HomieThingHandler.java @@ -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 availabilityTopicsSeen) { // not used here } }