private AtomicBoolean messageReceived = new AtomicBoolean(false);
private Map<String, @Nullable ChannelState> availabilityStates = new ConcurrentHashMap<>();
+ private AvailabilityMode availabilityMode = AvailabilityMode.ALL;
public AbstractMQTTThingHandler(Thing thing, int subscribeTimeout) {
super(thing);
@Override
public void updateChannelState(ChannelUID channelUID, State value) {
if (messageReceived.compareAndSet(false, true)) {
- calculateThingStatus();
+ calculateAndUpdateThingStatus(true);
}
super.updateState(channelUID, value);
}
@Override
public void triggerChannel(ChannelUID channelUID, String event) {
if (messageReceived.compareAndSet(false, true)) {
- calculateThingStatus();
+ calculateAndUpdateThingStatus(true);
}
super.triggerChannel(channelUID, event);
}
this.connection = connection;
}
+ @Override
+ public void setAvailabilityMode(AvailabilityMode mode) {
+ this.availabilityMode = mode;
+ }
+
@Override
public void addAvailabilityTopic(String availability_topic, String payload_available,
String payload_not_available) {
channelUID, value, new ChannelStateUpdateListener() {
@Override
public void updateChannelState(ChannelUID channelUID, State value) {
- calculateThingStatus();
+ boolean online = value.equals(OnOffType.ON);
+ calculateAndUpdateThingStatus(online);
}
@Override
@Override
public void resetMessageReceived() {
if (messageReceived.compareAndSet(true, false)) {
- calculateThingStatus();
+ calculateAndUpdateThingStatus(false);
}
}
- protected void calculateThingStatus() {
+ protected void calculateAndUpdateThingStatus(boolean lastValue) {
final Optional<Boolean> availabilityTopicsSeen;
if (availabilityStates.isEmpty()) {
availabilityTopicsSeen = Optional.empty();
} else {
- availabilityTopicsSeen = Optional.of(availabilityStates.values().stream().allMatch(
- c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class))));
+ availabilityTopicsSeen = switch (availabilityMode) {
+ case ALL -> Optional.of(availabilityStates.values().stream().allMatch(
+ 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);
}
* Interface to keep track of the availability of device using an availability topic or messages received
*
* @author Jochen Klein - Initial contribution
+ * @author Cody Cutrer - Support all/any/latest
*/
@NonNullByDefault
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.
clearAllAvailabilityTopics();
initializeAvailabilityTopicsFromConfig();
return channelStateByChannelUID.values().stream().map(c -> c.start(connection, scheduler, 0))
- .collect(FutureCollector.allOf()).thenRun(this::calculateThingStatus);
+ .collect(FutureCollector.allOf()).thenRun(() -> calculateAndUpdateThingStatus(false));
}
@Override
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.MqttChannelTypeProvider;
import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
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.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.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.thing.ChannelGroupUID;
this.configSeen = false;
- String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
- if (availabilityTopic != null) {
- String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
- if (availabilityTemplate != null) {
- availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
+ 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();
+ if (availabilityTopic != null) {
+ String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
+ if (availabilityTemplate != null) {
+ availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
+ }
+ componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
+ this.channelConfiguration.getPayloadAvailable(),
+ this.channelConfiguration.getPayloadNotAvailable(), availabilityTemplate,
+ componentConfiguration.getTransformationServiceProvider());
}
- componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
- this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
- availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
}
}