]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt.homeassistant] support availability_templates (#13397)
authorCody Cutrer <cody@cutrer.us>
Mon, 19 Sep 2022 21:00:01 +0000 (15:00 -0600)
committerGitHub <noreply@github.com>
Mon, 19 Sep 2022 21:00:01 +0000 (23:00 +0200)
Signed-off-by: Cody Cutrer <cody@cutrer.us>
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AbstractMQTTThingHandler.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/AvailabilityTracker.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java

index b9f932c6d24b00fcbfd88ffc44cd111a068b8d0e..ded70ee8350bdae75fdbcd02aeedde98c5e85c41 100644 (file)
@@ -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() {
index 4b01cf22bdb066317f4e921c1e1144fe194f50cc..f75283ef1ee0580db41fac15d940a82e03492b43 100644 (file)
@@ -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.
+     * <p>
+     * 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();
index ef46d9a87a3c44377a43ca2c43e2175e42484516..e5d8963394f6919a5b8f9fc4fb5b48bcf566329c 100644 (file)
@@ -48,6 +48,8 @@ import org.openhab.core.thing.type.ChannelGroupTypeUID;
  */
 @NonNullByDefault
 public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
+    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<C extends AbstractChannelConfiguration>
 
         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());
         }
     }
 
index f894db9f88a78a13215e493310dd940e3c7a6152..41aee74fe8cba68b95e7c1acfb4950fb1943e368 100644 (file)
@@ -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;
index 8099400947314f28c6d1215cb516afca5dfe55c2..56f853887b0b664bd08e3548353303bcc3105854 100644 (file)
@@ -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;
+    }
 }
index 1484868e83a0a9e0043215af89baf73d978bd191..cfc1df26d8b4c422002f3d596eb164f98d687f55 100644 (file)
@@ -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,
index a92237980edd2447cd2226565a739bdf2e2830c2..56f778081e3b251ec8336469d41e330e56a7a96f 100644 (file)
@@ -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