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() {
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
*/
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();
*/
@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;
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());
}
}
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
return payloadNotAvailable;
}
+ @Nullable
+ public String getAvailabilityTemplate() {
+ return availabilityTemplate;
+ }
+
@Nullable
public Device getDevice() {
return device;
*/
package org.openhab.binding.mqtt.homeassistant.internal.config.dto;
+import org.eclipse.jdt.annotation.Nullable;
+
import com.google.gson.annotations.SerializedName;
/**
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() {
public String getTopic() {
return topic;
}
+
+ public @Nullable String getValueTemplate() {
+ return valueTemplate;
+ }
}
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;
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;
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,
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;
/**
// @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\" " +
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");
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