[mqtt.homeassistant] Make sensors compliant (#8591)

Signed-off-by: Jochen Klein <git@jochen.susca.de>
This commit is contained in:
Jochen Klein 2020-10-02 03:07:08 +02:00 committed by GitHub
parent 3abe27b224
commit f06068a189
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 295 additions and 69 deletions

View File

@ -15,6 +15,7 @@ package org.openhab.binding.mqtt.generic;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -355,16 +356,16 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
} }
protected void calculateThingStatus() { protected void calculateThingStatus() {
final boolean availabilityTopicsSeen; final Optional<Boolean> availabilityTopicsSeen;
if (availabilityStates.isEmpty()) { if (availabilityStates.isEmpty()) {
availabilityTopicsSeen = true; availabilityTopicsSeen = Optional.empty();
} else { } else {
availabilityTopicsSeen = availabilityStates.values().stream().allMatch( availabilityTopicsSeen = Optional.of(availabilityStates.values().stream().allMatch(
c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class))); c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class))));
} }
updateThingStatus(messageReceived.get(), availabilityTopicsSeen); updateThingStatus(messageReceived.get(), availabilityTopicsSeen);
} }
protected abstract void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen); protected abstract void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen);
} }

View File

@ -16,6 +16,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -186,8 +187,8 @@ public class GenericMQTTThingHandler extends AbstractMQTTThingHandler implements
} }
@Override @Override
protected void updateThingStatus(boolean messageReceived, boolean availibilityTopicsSeen) { protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availibilityTopicsSeen) {
if (messageReceived || availibilityTopicsSeen) { if (availibilityTopicsSeen.orElse(true)) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.mqtt.homeassistant.internal; package org.openhab.binding.mqtt.homeassistant.internal;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.generic.AvailabilityTracker; import org.openhab.binding.mqtt.generic.AvailabilityTracker;
@ -46,9 +48,10 @@ public class CFactory {
*/ */
public static @Nullable AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID, public static @Nullable AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID,
String channelConfigurationJSON, ChannelStateUpdateListener updateListener, AvailabilityTracker tracker, String channelConfigurationJSON, ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
Gson gson, TransformationServiceProvider transformationServiceProvider) { ScheduledExecutorService scheduler, Gson gson,
TransformationServiceProvider transformationServiceProvider) {
ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID, ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
channelConfigurationJSON, gson, updateListener, tracker) channelConfigurationJSON, gson, updateListener, tracker, scheduler)
.transformationProvider(transformationServiceProvider); .transformationProvider(transformationServiceProvider);
try { try {
switch (haID.component) { switch (haID.component) {
@ -86,16 +89,19 @@ public class CFactory {
private final ChannelStateUpdateListener updateListener; private final ChannelStateUpdateListener updateListener;
private final AvailabilityTracker tracker; private final AvailabilityTracker tracker;
private final Gson gson; private final Gson gson;
private final ScheduledExecutorService scheduler;
private @Nullable TransformationServiceProvider transformationServiceProvider; private @Nullable TransformationServiceProvider transformationServiceProvider;
protected ComponentConfiguration(ThingUID thingUID, HaID haID, String configJSON, Gson gson, protected ComponentConfiguration(ThingUID thingUID, HaID haID, String configJSON, Gson gson,
ChannelStateUpdateListener updateListener, AvailabilityTracker tracker) { ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
ScheduledExecutorService scheduler) {
this.thingUID = thingUID; this.thingUID = thingUID;
this.haID = haID; this.haID = haID;
this.configJSON = configJSON; this.configJSON = configJSON;
this.gson = gson; this.gson = gson;
this.updateListener = updateListener; this.updateListener = updateListener;
this.tracker = tracker; this.tracker = tracker;
this.scheduler = scheduler;
} }
public ComponentConfiguration transformationProvider( public ComponentConfiguration transformationProvider(
@ -133,6 +139,10 @@ public class CFactory {
return tracker; return tracker;
} }
public ScheduledExecutorService getScheduler() {
return scheduler;
}
public <C extends BaseChannelConfiguration> C getConfig(Class<C> clazz) { public <C extends BaseChannelConfiguration> C getConfig(Class<C> clazz) {
return BaseChannelConfiguration.fromString(configJSON, gson, clazz); return BaseChannelConfiguration.fromString(configJSON, gson, clazz);
} }

View File

@ -69,19 +69,16 @@ public class ComponentAlarmControlPanel extends AbstractComponent<ComponentAlarm
String command_topic = channelConfiguration.command_topic; String command_topic = channelConfiguration.command_topic;
if (command_topic != null) { if (command_topic != null) {
buildChannel(switchDisarmChannelID, new TextValue(new String[] { channelConfiguration.payload_disarm }), buildChannel(switchDisarmChannelID, new TextValue(new String[] { channelConfiguration.payload_disarm }),
channelConfiguration.name, componentConfiguration.getUpdateListener())// channelConfiguration.name, componentConfiguration.getUpdateListener())
.commandTopic(command_topic, channelConfiguration.retain)// .commandTopic(command_topic, channelConfiguration.retain).build();
.build();
buildChannel(switchArmHomeChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_home }), buildChannel(switchArmHomeChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_home }),
channelConfiguration.name, componentConfiguration.getUpdateListener())// channelConfiguration.name, componentConfiguration.getUpdateListener())
.commandTopic(command_topic, channelConfiguration.retain)// .commandTopic(command_topic, channelConfiguration.retain).build();
.build();
buildChannel(switchArmAwayChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_away }), buildChannel(switchArmAwayChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_away }),
channelConfiguration.name, componentConfiguration.getUpdateListener())// channelConfiguration.name, componentConfiguration.getUpdateListener())
.commandTopic(command_topic, channelConfiguration.retain)// .commandTopic(command_topic, channelConfiguration.retain).build();
.build();
} }
} }
} }

View File

@ -12,9 +12,15 @@
*/ */
package org.openhab.binding.mqtt.homeassistant.internal; package org.openhab.binding.mqtt.homeassistant.internal;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.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. * 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 @Nullable String device_class;
protected boolean force_update = false; 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 state_topic = "";
protected String payload_on = "ON"; protected String payload_on = "ON";
protected String payload_off = "OFF"; 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) { public ComponentBinarySensor(CFactory.ComponentConfiguration componentConfiguration) {
super(componentConfiguration, ChannelConfiguration.class); super(componentConfiguration, ChannelConfiguration.class);
if (channelConfiguration.force_update) { OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off);
throw new UnsupportedOperationException("Component:Sensor does not support forced updates");
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), return updateListener;
channelConfiguration.name, componentConfiguration.getUpdateListener())//
.stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
.build();
} }
} }

View File

@ -42,8 +42,7 @@ public class ComponentCamera extends AbstractComponent<ComponentCamera.ChannelCo
ImageValue value = new ImageValue(); ImageValue value = new ImageValue();
buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())// buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.topic)// .stateTopic(channelConfiguration.topic).build();
.build();
} }
} }

View File

@ -48,9 +48,8 @@ public class ComponentCover extends AbstractComponent<ComponentCover.ChannelConf
RollershutterValue value = new RollershutterValue(channelConfiguration.payload_open, RollershutterValue value = new RollershutterValue(channelConfiguration.payload_open,
channelConfiguration.payload_close, channelConfiguration.payload_stop); channelConfiguration.payload_close, channelConfiguration.payload_stop);
buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())// buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)// .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
.commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)// .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
.build();
} }
} }

View File

@ -45,9 +45,8 @@ public class ComponentFan extends AbstractComponent<ComponentFan.ChannelConfigur
super(componentConfiguration, ChannelConfiguration.class); super(componentConfiguration, ChannelConfiguration.class);
OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off); OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off);
buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())// buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)// .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
.commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)// .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
.build();
} }
} }

View File

@ -105,22 +105,18 @@ public class ComponentLight extends AbstractComponent<ComponentLight.ChannelConf
channelConfiguration.payload_off, 100); channelConfiguration.payload_off, 100);
// Create three MQTT subscriptions and use this class object as update listener // Create three MQTT subscriptions and use this class object as update listener
switchChannel = buildChannel(switchChannelID, value, channelConfiguration.name, this)// switchChannel = buildChannel(switchChannelID, value, channelConfiguration.name, this)
// Some lights use the value_template field for the template, most use state_value_template
.stateTopic(channelConfiguration.state_topic, channelConfiguration.state_value_template, .stateTopic(channelConfiguration.state_topic, channelConfiguration.state_value_template,
channelConfiguration.value_template)// channelConfiguration.value_template)
.commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)// .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build(false);
.build(false);
colorChannel = buildChannel(colorChannelID, value, channelConfiguration.name, this)// colorChannel = buildChannel(colorChannelID, value, channelConfiguration.name, this)
.stateTopic(channelConfiguration.rgb_state_topic, channelConfiguration.rgb_value_template)// .stateTopic(channelConfiguration.rgb_state_topic, channelConfiguration.rgb_value_template)
.commandTopic(channelConfiguration.rgb_command_topic, channelConfiguration.retain)// .commandTopic(channelConfiguration.rgb_command_topic, channelConfiguration.retain).build(false);
.build(false);
brightnessChannel = buildChannel(brightnessChannelID, value, channelConfiguration.name, this)// brightnessChannel = buildChannel(brightnessChannelID, value, channelConfiguration.name, this)
.stateTopic(channelConfiguration.brightness_state_topic, channelConfiguration.brightness_value_template)// .stateTopic(channelConfiguration.brightness_state_topic, channelConfiguration.brightness_value_template)
.commandTopic(channelConfiguration.brightness_command_topic, channelConfiguration.retain)// .commandTopic(channelConfiguration.brightness_command_topic, channelConfiguration.retain).build(false);
.build(false);
channels.put(colorChannelID, colorChannel); channels.put(colorChannelID, colorChannel);
} }

View File

@ -52,9 +52,8 @@ public class ComponentLock extends AbstractComponent<ComponentLock.ChannelConfig
buildChannel(switchChannelID, buildChannel(switchChannelID,
new OnOffValue(channelConfiguration.payload_lock, channelConfiguration.payload_unlock), new OnOffValue(channelConfiguration.payload_lock, channelConfiguration.payload_unlock),
channelConfiguration.name, componentConfiguration.getUpdateListener())// channelConfiguration.name, componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)// .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
.commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)// .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
.build();
} }
} }

View File

@ -12,14 +12,17 @@
*/ */
package org.openhab.binding.mqtt.homeassistant.internal; package org.openhab.binding.mqtt.homeassistant.internal;
import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.NumberValue;
import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.binding.mqtt.generic.values.Value; 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. * 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 unit_of_measurement;
protected @Nullable String device_class; protected @Nullable String device_class;
protected boolean force_update = false; protected boolean force_update = false;
protected int expire_after = 0; protected @Nullable Integer expire_after;
protected String state_topic = ""; 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) { public ComponentSensor(CFactory.ComponentConfiguration componentConfiguration) {
super(componentConfiguration, ChannelConfiguration.class); super(componentConfiguration, ChannelConfiguration.class);
if (channelConfiguration.force_update) {
throw new UnsupportedOperationException("Component:Sensor does not support forced updates");
}
Value value; Value value;
String uom = channelConfiguration.unit_of_measurement; String uom = channelConfiguration.unit_of_measurement;
@ -68,8 +71,19 @@ public class ComponentSensor extends AbstractComponent<ComponentSensor.ChannelCo
boolean trigger = triggerIcons.matcher(icon).matches(); 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)// .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
.trigger(trigger).build(); .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;
}
} }

View File

@ -66,9 +66,9 @@ public class ComponentSwitch extends AbstractComponent<ComponentSwitch.ChannelCo
OnOffValue value = new OnOffValue(state_on, state_off, channelConfiguration.payload_on, OnOffValue value = new OnOffValue(state_on, state_off, channelConfiguration.payload_on,
channelConfiguration.payload_off); channelConfiguration.payload_off);
buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener())// buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)// .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
.commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos)// .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos)
.build(); .build();
} }
} }

View File

@ -96,7 +96,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
AbstractComponent<?> component = null; AbstractComponent<?> component = null;
if (config.length() > 0) { if (config.length() > 0) {
component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, gson, component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler, gson,
transformationServiceProvider); transformationServiceProvider);
} }
if (component != null) { if (component != null) {

View File

@ -17,6 +17,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -152,8 +153,8 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
if (channelConfigurationJSON == null) { if (channelConfigurationJSON == null) {
logger.warn("Provided channel does not have a 'config' configuration key!"); logger.warn("Provided channel does not have a 'config' configuration key!");
} else { } else {
component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, gson, component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, scheduler,
transformationServiceProvider); gson, transformationServiceProvider);
} }
if (component != null) { if (component != null) {
@ -296,8 +297,8 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
} }
@Override @Override
protected void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen) { protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
if (!messageReceived || availabilityTopicsSeen) { if (availabilityTopicsSeen.orElse(messageReceived)) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -13,6 +13,7 @@
package org.openhab.binding.mqtt.homie.internal.handler; package org.openhab.binding.mqtt.homie.internal.handler;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -246,7 +247,7 @@ public class HomieThingHandler extends AbstractMQTTThingHandler implements Devic
} }
@Override @Override
protected void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen) { protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
// not used here // not used here
} }
} }