[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:
Cody Cutrer 2023-12-07 12:22:03 -07:00 committed by GitHub
parent 5b98e41b7a
commit d6b19fecfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 16 deletions

View File

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

View File

@ -19,9 +19,37 @@ import org.eclipse.jdt.annotation.Nullable;
* Interface to keep track of the availability of device using an availability topic or messages received * 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.

View File

@ -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

View File

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