diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelConfig.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelConfig.java index 0b2cb3f5486..d3adf267786 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelConfig.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelConfig.java @@ -59,6 +59,7 @@ public class ChannelConfig { public @Nullable String stop; public @Nullable String onState; public @Nullable String offState; + public @Nullable String nullValue; public int onBrightness = 10; public String colorMode = ColorMode.HSB.toString(); diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/NumberValue.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/NumberValue.java index 20b391a757c..cfece9d67bf 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/NumberValue.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/NumberValue.java @@ -121,9 +121,12 @@ public class NumberValue extends Value { @Override public Type parseMessage(Command command) throws IllegalArgumentException { - if (command instanceof StringType - && (command.toString().equalsIgnoreCase(NAN) || command.toString().equalsIgnoreCase(NEGATIVE_NAN))) { - return UnDefType.UNDEF; + if (command instanceof StringType) { + if (command.toString().equalsIgnoreCase(NAN) || command.toString().equalsIgnoreCase(NEGATIVE_NAN)) { + return UnDefType.UNDEF; + } else if (command.toString().isEmpty()) { + return UnDefType.NULL; + } } return parseCommand(command); } diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/RollershutterValue.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/RollershutterValue.java index f1f34e82867..b8c754387c5 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/RollershutterValue.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/RollershutterValue.java @@ -22,6 +22,8 @@ import org.openhab.core.library.types.StopMoveType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.UpDownType; import org.openhab.core.types.Command; +import org.openhab.core.types.Type; +import org.openhab.core.types.UnDefType; /** * Implements a rollershutter value. @@ -146,7 +148,10 @@ public class RollershutterValue extends Value { } @Override - public Command parseMessage(Command command) throws IllegalArgumentException { + public Type parseMessage(Command command) throws IllegalArgumentException { + if (command instanceof StringType string && string.toString().isEmpty()) { + return UnDefType.NULL; + } command = parseType(command, upStateString, downStateString); if (inverted && command instanceof PercentType percentType) { return new PercentType(100 - percentType.intValue()); diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/TextValue.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/TextValue.java index 2ed6cc4ea78..fb15998c41f 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/TextValue.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/TextValue.java @@ -29,6 +29,7 @@ import org.openhab.core.types.CommandOption; import org.openhab.core.types.State; import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; /** * Implements a text/string value. Allows to restrict the incoming value to a set of states. @@ -40,6 +41,8 @@ public class TextValue extends Value { private final @Nullable Set states; private final @Nullable Set commands; + protected @Nullable String nullValue = null; + /** * Create a string value with a limited number of allowed states and commands. * @@ -80,6 +83,10 @@ public class TextValue extends Value { this.commands = null; } + public void setNullValue(@Nullable String nullValue) { + this.nullValue = nullValue; + } + @Override public StringType parseCommand(Command command) throws IllegalArgumentException { final Set commands = this.commands; @@ -92,6 +99,10 @@ public class TextValue extends Value { @Override public State parseMessage(Command command) throws IllegalArgumentException { + if (command instanceof StringType string && string.toString().equals(nullValue)) { + return UnDefType.NULL; + } + final Set states = this.states; String valueStr = command.toString(); if (states != null && !states.contains(valueStr)) { diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/Value.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/Value.java index ffbc42feec5..e416cf824ba 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/Value.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/Value.java @@ -23,6 +23,7 @@ import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.CommandDescriptionBuilder; import org.openhab.core.types.State; @@ -147,6 +148,9 @@ public abstract class Value { * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type. */ public Type parseMessage(Command command) throws IllegalArgumentException { + if (command instanceof StringType string && string.toString().isEmpty()) { + return UnDefType.NULL; + } return parseCommand(command); } diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/ValueFactory.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/ValueFactory.java index f4295b9862a..2a361d84276 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/ValueFactory.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/ValueFactory.java @@ -36,8 +36,10 @@ public class ValueFactory { Value value; switch (channelTypeID) { case MqttBindingConstants.STRING: - value = config.allowedStates.isBlank() ? new TextValue() + TextValue textValue = config.allowedStates.isBlank() ? new TextValue() : new TextValue(config.allowedStates.split(",")); + textValue.setNullValue(config.nullValue); + value = textValue; break; case MqttBindingConstants.DATETIME: value = new DateTimeValue(); diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/config/string-channel-config.xml b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/config/string-channel-config.xml index 238c15de671..6131607abb0 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/config/string-channel-config.xml +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/config/string-channel-config.xml @@ -78,6 +78,11 @@ false true + + + If the received MQTT value matches this, treat it as NULL. + true + diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties index 80fd9264a23..655291465fb 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties @@ -185,6 +185,8 @@ thing-type.config.mqtt.string_channel.transformationPattern.label = Incoming Val thing-type.config.mqtt.string_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. thing-type.config.mqtt.string_channel.transformationPatternOut.label = Outgoing Value Transformation thing-type.config.mqtt.string_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.string_channel.nullValue.label = NULL Value +thing-type.config.mqtt.string_channel.nullValue.description = If the received MQTT value matches this, treat it as NULL. thing-type.config.mqtt.switch_channel.commandTopic.label = MQTT Command Topic thing-type.config.mqtt.switch_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. thing-type.config.mqtt.switch_channel.formatBeforePublish.label = Outgoing Value Format diff --git a/bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/values/ValueTests.java b/bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/values/ValueTests.java index 8bb5365abef..f91c919f567 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/values/ValueTests.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/values/ValueTests.java @@ -82,6 +82,8 @@ public class ValueTests { assertThat(hsb.getBrightness().intValue(), is(0)); hsb = (HSBType) v.parseCommand(p(v, "1")); assertThat(hsb.getBrightness().intValue(), is(1)); + + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); } @Test @@ -137,6 +139,8 @@ public class ValueTests { // Test custom formatting assertThat(v.getMQTTpublishValue(OnOffType.OFF, "=%s"), is("=fancyOff")); assertThat(v.getMQTTpublishValue(OnOffType.ON, "=%s"), is("=fancyON")); + + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); } @Test @@ -168,6 +172,8 @@ public class ValueTests { // Test basic formatting assertThat(v.getMQTTpublishValue(OpenClosedType.CLOSED, null), is("fancyOff")); assertThat(v.getMQTTpublishValue(OpenClosedType.OPEN, null), is("fancyON")); + + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); } @Test @@ -191,6 +197,8 @@ public class ValueTests { assertThat(v.parseMessage(new StringType("nan")), is(UnDefType.UNDEF)); assertThat(v.parseMessage(new StringType("-NaN")), is(UnDefType.UNDEF)); assertThat(v.parseMessage(new StringType("-nan")), is(UnDefType.UNDEF)); + + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); } @Test @@ -243,6 +251,8 @@ public class ValueTests { // Test parsing from MQTT assertThat(v.parseMessage(new StringType("fancyON")), is(UpDownType.UP)); assertThat(v.parseMessage(new StringType("fancyOff")), is(UpDownType.DOWN)); + + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); } @Test @@ -310,6 +320,8 @@ public class ValueTests { command = v.parseCommand(new DecimalType(i)); assertThat(v.getMQTTpublishValue(command, null), is("" + i)); } + + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); } @Test @@ -394,4 +406,20 @@ public class ValueTests { null); assertThrows(IllegalArgumentException.class, () -> v.parseCommand(new DecimalType(9.0))); } + + @Test + public void textUpdate() { + TextValue v = new TextValue(); + + assertThat(v.parseMessage(new StringType("")), is(new StringType(""))); + assertThat(v.parseMessage(new StringType("NULL")), is(new StringType("NULL"))); + + v.setNullValue(""); + assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL)); + assertThat(v.parseMessage(new StringType("NULL")), is(new StringType("NULL"))); + + v.setNullValue("NULL"); + assertThat(v.parseMessage(new StringType("NULL")), is(UnDefType.NULL)); + assertThat(v.parseMessage(new StringType("")), is(new StringType(""))); + } }