[mqtt.homeassistant] handle multiple availability topics (#15977)
* [mqtt.homeassistant] handle multiple availability topics --------- Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
5b98e41b7a
commit
d6b19fecfe
|
@ -83,6 +83,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
||||||
|
|
||||||
private AtomicBoolean messageReceived = new AtomicBoolean(false);
|
private AtomicBoolean messageReceived = new AtomicBoolean(false);
|
||||||
private Map<String, @Nullable ChannelState> availabilityStates = new ConcurrentHashMap<>();
|
private Map<String, @Nullable ChannelState> availabilityStates = new ConcurrentHashMap<>();
|
||||||
|
private AvailabilityMode availabilityMode = AvailabilityMode.ALL;
|
||||||
|
|
||||||
public AbstractMQTTThingHandler(Thing thing, int subscribeTimeout) {
|
public AbstractMQTTThingHandler(Thing thing, int subscribeTimeout) {
|
||||||
super(thing);
|
super(thing);
|
||||||
|
@ -261,7 +262,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
||||||
@Override
|
@Override
|
||||||
public void updateChannelState(ChannelUID channelUID, State value) {
|
public void updateChannelState(ChannelUID channelUID, State value) {
|
||||||
if (messageReceived.compareAndSet(false, true)) {
|
if (messageReceived.compareAndSet(false, true)) {
|
||||||
calculateThingStatus();
|
calculateAndUpdateThingStatus(true);
|
||||||
}
|
}
|
||||||
super.updateState(channelUID, value);
|
super.updateState(channelUID, value);
|
||||||
}
|
}
|
||||||
|
@ -269,7 +270,7 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
||||||
@Override
|
@Override
|
||||||
public void triggerChannel(ChannelUID channelUID, String event) {
|
public void triggerChannel(ChannelUID channelUID, String event) {
|
||||||
if (messageReceived.compareAndSet(false, true)) {
|
if (messageReceived.compareAndSet(false, true)) {
|
||||||
calculateThingStatus();
|
calculateAndUpdateThingStatus(true);
|
||||||
}
|
}
|
||||||
super.triggerChannel(channelUID, event);
|
super.triggerChannel(channelUID, event);
|
||||||
}
|
}
|
||||||
|
@ -292,6 +293,11 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAvailabilityMode(AvailabilityMode mode) {
|
||||||
|
this.availabilityMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAvailabilityTopic(String availability_topic, String payload_available,
|
public void addAvailabilityTopic(String availability_topic, String payload_available,
|
||||||
String payload_not_available) {
|
String payload_not_available) {
|
||||||
|
@ -310,7 +316,8 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
||||||
channelUID, value, new ChannelStateUpdateListener() {
|
channelUID, value, new ChannelStateUpdateListener() {
|
||||||
@Override
|
@Override
|
||||||
public void updateChannelState(ChannelUID channelUID, State value) {
|
public void updateChannelState(ChannelUID channelUID, State value) {
|
||||||
calculateThingStatus();
|
boolean online = value.equals(OnOffType.ON);
|
||||||
|
calculateAndUpdateThingStatus(online);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -352,18 +359,23 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
||||||
@Override
|
@Override
|
||||||
public void resetMessageReceived() {
|
public void resetMessageReceived() {
|
||||||
if (messageReceived.compareAndSet(true, false)) {
|
if (messageReceived.compareAndSet(true, false)) {
|
||||||
calculateThingStatus();
|
calculateAndUpdateThingStatus(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void calculateThingStatus() {
|
protected void calculateAndUpdateThingStatus(boolean lastValue) {
|
||||||
final Optional<Boolean> availabilityTopicsSeen;
|
final Optional<Boolean> availabilityTopicsSeen;
|
||||||
|
|
||||||
if (availabilityStates.isEmpty()) {
|
if (availabilityStates.isEmpty()) {
|
||||||
availabilityTopicsSeen = Optional.empty();
|
availabilityTopicsSeen = Optional.empty();
|
||||||
} else {
|
} else {
|
||||||
availabilityTopicsSeen = Optional.of(availabilityStates.values().stream().allMatch(
|
availabilityTopicsSeen = switch (availabilityMode) {
|
||||||
|
case ALL -> 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))));
|
||||||
|
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);
|
updateThingStatus(messageReceived.get(), availabilityTopicsSeen);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Interface to keep track of the availability of device using an availability topic or messages received
|
||||||
*
|
*
|
||||||
* @author Jochen Klein - Initial contribution
|
* @author Jochen Klein - Initial contribution
|
||||||
|
* @author Cody Cutrer - Support all/any/latest
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public interface AvailabilityTracker {
|
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.
|
* Adds an availability topic to determine the availability of a device.
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class GenericMQTTThingHandler extends AbstractMQTTThingHandler implements
|
||||||
clearAllAvailabilityTopics();
|
clearAllAvailabilityTopics();
|
||||||
initializeAvailabilityTopicsFromConfig();
|
initializeAvailabilityTopicsFromConfig();
|
||||||
return channelStateByChannelUID.values().stream().map(c -> c.start(connection, scheduler, 0))
|
return channelStateByChannelUID.values().stream().map(c -> c.start(connection, scheduler, 0))
|
||||||
.collect(FutureCollector.allOf()).thenRun(this::calculateThingStatus);
|
.collect(FutureCollector.allOf()).thenRun(() -> calculateAndUpdateThingStatus(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
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.ChannelStateUpdateListener;
|
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
|
||||||
import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
|
import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
|
||||||
import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
|
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.HaID;
|
||||||
import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration;
|
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.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.binding.mqtt.homeassistant.internal.config.dto.Device;
|
||||||
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
|
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
|
||||||
import org.openhab.core.thing.ChannelGroupUID;
|
import org.openhab.core.thing.ChannelGroupUID;
|
||||||
|
@ -99,6 +102,25 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||||
|
|
||||||
this.configSeen = false;
|
this.configSeen = false;
|
||||||
|
|
||||||
|
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();
|
String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
|
||||||
if (availabilityTopic != null) {
|
if (availabilityTopic != null) {
|
||||||
String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
|
String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
|
||||||
|
@ -106,8 +128,10 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||||
availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
|
availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
|
||||||
}
|
}
|
||||||
componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
|
componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
|
||||||
this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
|
this.channelConfiguration.getPayloadAvailable(),
|
||||||
availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
|
this.channelConfiguration.getPayloadNotAvailable(), availabilityTemplate,
|
||||||
|
componentConfiguration.getTransformationServiceProvider());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue