[mqtt.homeassistant] support availability_templates (#13397)

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer 2022-09-19 15:00:01 -06:00 committed by GitHub
parent b6ddb6bb0a
commit 8656ba501a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 61 additions and 9 deletions

View 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() {

View 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();

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

View 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;

View 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;
}
}

View 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,

View 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