From 8656ba501adf39089ff9768d6bc3be009f395a2d Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Mon, 19 Sep 2022 15:00:01 -0600 Subject: [PATCH] [mqtt.homeassistant] support availability_templates (#13397) Signed-off-by: Cody Cutrer --- .../mqtt/generic/AbstractMQTTThingHandler.java | 3 ++- .../mqtt/generic/AvailabilityTracker.java | 18 ++++++++++++++++++ .../internal/component/AbstractComponent.java | 10 ++++++++-- .../dto/AbstractChannelConfiguration.java | 7 +++++++ .../internal/config/dto/Availability.java | 8 ++++++++ .../component/AbstractComponentTests.java | 9 +++++++++ .../internal/component/SensorTests.java | 15 +++++++++------ 7 files changed, 61 insertions(+), 9 deletions(-) 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