[mqtt.homeassistant] support availability_templates (#13397)
Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
b6ddb6bb0a
commit
8656ba501a
@ -291,12 +291,13 @@ public abstract class AbstractMQTTThingHandler extends BaseThingHandler
|
|||||||
addAvailabilityTopic(availability_topic, payload_available, payload_not_available, null, null);
|
addAvailabilityTopic(availability_topic, payload_available, payload_not_available, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void addAvailabilityTopic(String availability_topic, String payload_available, String payload_not_available,
|
public void addAvailabilityTopic(String availability_topic, String payload_available, String payload_not_available,
|
||||||
@Nullable String transformation_pattern,
|
@Nullable String transformation_pattern,
|
||||||
@Nullable TransformationServiceProvider transformationServiceProvider) {
|
@Nullable TransformationServiceProvider transformationServiceProvider) {
|
||||||
availabilityStates.computeIfAbsent(availability_topic, topic -> {
|
availabilityStates.computeIfAbsent(availability_topic, topic -> {
|
||||||
Value value = new OnOffValue(payload_available, payload_not_available);
|
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));
|
ChannelUID channelUID = new ChannelUID(groupUID, UIDUtils.encode(topic));
|
||||||
ChannelState state = new ChannelState(ChannelConfigBuilder.create().withStateTopic(topic).build(),
|
ChannelState state = new ChannelState(ChannelConfigBuilder.create().withStateTopic(topic).build(),
|
||||||
channelUID, value, new ChannelStateUpdateListener() {
|
channelUID, value, new ChannelStateUpdateListener() {
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.mqtt.generic;
|
package org.openhab.binding.mqtt.generic;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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
|
* 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);
|
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 removeAvailabilityTopic(String availability_topic);
|
||||||
|
|
||||||
public void clearAllAvailabilityTopics();
|
public void clearAllAvailabilityTopics();
|
||||||
|
|||||||
@ -48,6 +48,8 @@ import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
|
public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
|
||||||
|
private static final String JINJA_PREFIX = "JINJA:";
|
||||||
|
|
||||||
// Component location fields
|
// Component location fields
|
||||||
private final ComponentConfiguration componentConfiguration;
|
private final ComponentConfiguration componentConfiguration;
|
||||||
protected final ChannelGroupTypeUID channelGroupTypeUID;
|
protected final ChannelGroupTypeUID channelGroupTypeUID;
|
||||||
@ -88,9 +90,13 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
|||||||
|
|
||||||
String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
|
String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
|
||||||
if (availabilityTopic != null) {
|
if (availabilityTopic != null) {
|
||||||
|
String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
|
||||||
|
if (availabilityTemplate != null) {
|
||||||
|
availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
|
||||||
|
}
|
||||||
componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
|
componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
|
||||||
this.channelConfiguration.getPayloadAvailable(),
|
this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
|
||||||
this.channelConfiguration.getPayloadNotAvailable());
|
availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,8 @@ public abstract class AbstractChannelConfiguration {
|
|||||||
protected String payloadAvailable = "online";
|
protected String payloadAvailable = "online";
|
||||||
@SerializedName("payload_not_available")
|
@SerializedName("payload_not_available")
|
||||||
protected String payloadNotAvailable = "offline";
|
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
|
* 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;
|
return payloadNotAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getAvailabilityTemplate() {
|
||||||
|
return availabilityTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Device getDevice() {
|
public Device getDevice() {
|
||||||
return device;
|
return device;
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.mqtt.homeassistant.internal.config.dto;
|
package org.openhab.binding.mqtt.homeassistant.internal.config.dto;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +27,8 @@ public class Availability {
|
|||||||
protected String payloadAvailable = "online";
|
protected String payloadAvailable = "online";
|
||||||
@SerializedName("payload_not_available")
|
@SerializedName("payload_not_available")
|
||||||
protected String payloadNotAvailable = "offline";
|
protected String payloadNotAvailable = "offline";
|
||||||
|
@SerializedName("value_template")
|
||||||
|
protected @Nullable String valueTemplate;
|
||||||
protected String topic;
|
protected String topic;
|
||||||
|
|
||||||
public String getPayloadAvailable() {
|
public String getPayloadAvailable() {
|
||||||
@ -38,4 +42,8 @@ public class Availability {
|
|||||||
public String getTopic() {
|
public String getTopic() {
|
||||||
return topic;
|
return topic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable String getValueTemplate() {
|
||||||
|
return valueTemplate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,11 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
|
|||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
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.config.dto.AbstractChannelConfiguration;
|
||||||
import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler;
|
import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingStatusInfo;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||||
import org.openhab.core.types.State;
|
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_BASETOPIC, HandlerConfiguration.DEFAULT_BASETOPIC);
|
||||||
config.put(HandlerConfiguration.PROPERTY_TOPICS, getConfigTopics());
|
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);
|
when(callbackMock.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing);
|
||||||
|
|
||||||
thingHandler = new LatchThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
|
thingHandler = new LatchThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import org.openhab.binding.mqtt.generic.values.NumberValue;
|
|||||||
import org.openhab.binding.mqtt.generic.values.TextValue;
|
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.unit.Units;
|
import org.openhab.core.library.unit.Units;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.types.UnDefType;
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,11 +41,8 @@ public class SensorTests extends AbstractComponentTests {
|
|||||||
// @formatter:off
|
// @formatter:off
|
||||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||||
"{ " +
|
"{ " +
|
||||||
" \"availability\": [ " +
|
" \"availability_topic\": \"zigbee2mqtt/bridge/state\", " +
|
||||||
" { " +
|
" \"availability_template\": \"{{value_json.state}}\", " +
|
||||||
" \"topic\": \"zigbee2mqtt/bridge/state\" " +
|
|
||||||
" } " +
|
|
||||||
" ], " +
|
|
||||||
" \"device\": { " +
|
" \"device\": { " +
|
||||||
" \"identifiers\": [ " +
|
" \"identifiers\": [ " +
|
||||||
" \"zigbee2mqtt_0x0000000000000000\" " +
|
" \"zigbee2mqtt_0x0000000000000000\" " +
|
||||||
@ -70,6 +68,8 @@ public class SensorTests extends AbstractComponentTests {
|
|||||||
assertChannel(component, Sensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "sensor1",
|
assertChannel(component, Sensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "sensor1",
|
||||||
NumberValue.class);
|
NumberValue.class);
|
||||||
|
|
||||||
|
publishMessage("zigbee2mqtt/bridge/state", "{ \"state\": \"online\" }");
|
||||||
|
assertThat(haThing.getStatus(), is(ThingStatus.ONLINE));
|
||||||
publishMessage("zigbee2mqtt/sensor/state", "10");
|
publishMessage("zigbee2mqtt/sensor/state", "10");
|
||||||
assertState(component, Sensor.SENSOR_CHANNEL_ID, new QuantityType<>(10, Units.WATT));
|
assertState(component, Sensor.SENSOR_CHANNEL_ID, new QuantityType<>(10, Units.WATT));
|
||||||
publishMessage("zigbee2mqtt/sensor/state", "20");
|
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)
|
assertThat(component.getChannel(Sensor.SENSOR_CHANNEL_ID).getState().getCache().createStateDescription(true)
|
||||||
.build().getPattern(), is("%s %unit%"));
|
.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
|
@Test
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user