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 b9f932c6d..ded70ee83 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
@@ -291,12 +291,13 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
addAvailabilityTopic(availability_topic, payload_available, payload_not_available, null, null);
}
+ @Override
public void addAvailabilityTopic(String availability_topic, String payload_available, String payload_not_available,
@Nullable String transformation_pattern,
@Nullable TransformationServiceProvider transformationServiceProvider) {
availabilityStates.computeIfAbsent(availability_topic, topic -> {
Value value = new OnOffValue(payload_available, payload_not_available);
- ChannelGroupUID groupUID = new ChannelGroupUID(getThing().getUID(), "availablility");
+ ChannelGroupUID groupUID = new ChannelGroupUID(getThing().getUID(), "availability");
ChannelUID channelUID = new ChannelUID(groupUID, UIDUtils.encode(topic));
ChannelState state = new ChannelState(ChannelConfigBuilder.create().withStateTopic(topic).build(),
channelUID, value, new ChannelStateUpdateListener() {
diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java
index 4b01cf22b..f75283ef1 100644
--- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java
+++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java
@@ -13,6 +13,7 @@
package org.openhab.binding.mqtt.generic;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
/**
* Interface to keep track of the availability of device using an availability topic or messages received
@@ -33,6 +34,23 @@ public interface AvailabilityTracker {
*/
public void addAvailabilityTopic(String availability_topic, String payload_available, String payload_not_available);
+ /**
+ * Adds an availability topic to determine the availability of a device.
+ *
+ * Availability topics are usually set by the device as LWT.
+ *
+ * @param availability_topic The MQTT topic where availability is published to.
+ * @param payload_available The value for the topic to indicate the device is online.
+ * @param payload_not_available The value for the topic to indicate the device is offline.
+ * @param transformation_pattern A transformation pattern to process the value before comparing to
+ * payload_available/payload_not_available.
+ * @param transformationServiceProvider The service provider to obtain the transformation service (required only if
+ * transformation_pattern is not null).
+ */
+ public void addAvailabilityTopic(String availability_topic, String payload_available, String payload_not_available,
+ @Nullable String transformation_pattern,
+ @Nullable TransformationServiceProvider transformationServiceProvider);
+
public void removeAvailabilityTopic(String availability_topic);
public void clearAllAvailabilityTopics();
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java
index ef46d9a87..e5d896339 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java
@@ -48,6 +48,8 @@ import org.openhab.core.thing.type.ChannelGroupTypeUID;
*/
@NonNullByDefault
public abstract class AbstractComponent {
+ private static final String JINJA_PREFIX = "JINJA:";
+
// Component location fields
private final ComponentConfiguration componentConfiguration;
protected final ChannelGroupTypeUID channelGroupTypeUID;
@@ -88,9 +90,13 @@ public abstract class AbstractComponent
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());
+ this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
+ availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
}
}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java
index f894db9f8..41aee74fe 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java
@@ -52,6 +52,8 @@ public abstract class AbstractChannelConfiguration {
protected String payloadAvailable = "online";
@SerializedName("payload_not_available")
protected String payloadNotAvailable = "offline";
+ @SerializedName("availability_template")
+ protected @Nullable String availabilityTemplate;
/**
* A list of MQTT topics subscribed to receive availability (online/offline) updates. Must not be used together with
@@ -161,6 +163,11 @@ public abstract class AbstractChannelConfiguration {
return payloadNotAvailable;
}
+ @Nullable
+ public String getAvailabilityTemplate() {
+ return availabilityTemplate;
+ }
+
@Nullable
public Device getDevice() {
return device;
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java
index 809940094..56f853887 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java
@@ -12,6 +12,8 @@
*/
package org.openhab.binding.mqtt.homeassistant.internal.config.dto;
+import org.eclipse.jdt.annotation.Nullable;
+
import com.google.gson.annotations.SerializedName;
/**
@@ -25,6 +27,8 @@ public class Availability {
protected String payloadAvailable = "online";
@SerializedName("payload_not_available")
protected String payloadNotAvailable = "offline";
+ @SerializedName("value_template")
+ protected @Nullable String valueTemplate;
protected String topic;
public String getPayloadAvailable() {
@@ -38,4 +42,8 @@ public class Availability {
public String getTopic() {
return topic;
}
+
+ public @Nullable String getValueTemplate() {
+ return valueTemplate;
+ }
}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java
index 1484868e8..cfc1df26d 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java
@@ -15,9 +15,11 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -47,6 +49,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler;
import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.types.State;
@@ -71,6 +74,12 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests
config.put(HandlerConfiguration.PROPERTY_BASETOPIC, HandlerConfiguration.DEFAULT_BASETOPIC);
config.put(HandlerConfiguration.PROPERTY_TOPICS, getConfigTopics());
+ // Plumb thing status updates through
+ doAnswer(invocation -> {
+ ((Thing) invocation.getArgument(0)).setStatusInfo((ThingStatusInfo) invocation.getArgument(1));
+ return null;
+ }).when(callbackMock).statusUpdated(any(Thing.class), any(ThingStatusInfo.class));
+
when(callbackMock.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing);
thingHandler = new LatchThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java
index a92237980..56f778081 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java
@@ -23,6 +23,7 @@ import org.openhab.binding.mqtt.generic.values.NumberValue;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.UnDefType;
/**
@@ -40,11 +41,8 @@ public class SensorTests extends AbstractComponentTests {
// @formatter:off
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
"{ " +
- " \"availability\": [ " +
- " { " +
- " \"topic\": \"zigbee2mqtt/bridge/state\" " +
- " } " +
- " ], " +
+ " \"availability_topic\": \"zigbee2mqtt/bridge/state\", " +
+ " \"availability_template\": \"{{value_json.state}}\", " +
" \"device\": { " +
" \"identifiers\": [ " +
" \"zigbee2mqtt_0x0000000000000000\" " +
@@ -70,6 +68,8 @@ public class SensorTests extends AbstractComponentTests {
assertChannel(component, Sensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "sensor1",
NumberValue.class);
+ publishMessage("zigbee2mqtt/bridge/state", "{ \"state\": \"online\" }");
+ assertThat(haThing.getStatus(), is(ThingStatus.ONLINE));
publishMessage("zigbee2mqtt/sensor/state", "10");
assertState(component, Sensor.SENSOR_CHANNEL_ID, new QuantityType<>(10, Units.WATT));
publishMessage("zigbee2mqtt/sensor/state", "20");
@@ -77,7 +77,10 @@ public class SensorTests extends AbstractComponentTests {
assertThat(component.getChannel(Sensor.SENSOR_CHANNEL_ID).getState().getCache().createStateDescription(true)
.build().getPattern(), is("%s %unit%"));
- waitForAssert(() -> assertState(component, Sensor.SENSOR_CHANNEL_ID, UnDefType.UNDEF), 10000, 200);
+ waitForAssert(() -> assertState(component, Sensor.SENSOR_CHANNEL_ID, UnDefType.UNDEF), 5000, 200);
+
+ publishMessage("zigbee2mqtt/bridge/state", "{ \"state\": \"offline\" }");
+ assertThat(haThing.getStatus(), is(ThingStatus.OFFLINE));
}
@Test