mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[mqtt] Treat incoming empty string as NULL for most types (#16307)
* [mqtt] Treat incoming empty string as NULL for most types Empty strings are often received when deleting retained topics when a device goes offline, or as the result of a transformation that is missing a value (such as a "scene" event from zwave-js-ui, which sends JSON with a timestamp and the scene value, then immediately sends a value to the topic with only a timestamp). For string channels, add a configuration value to allow setting a specific string for treating as NULL, since empty string can make sense for that type. Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
d5fc6950dc
commit
5da9dda3e6
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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<String> states;
|
||||
private final @Nullable Set<String> 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<String> 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<String> states = this.states;
|
||||
String valueStr = command.toString();
|
||||
if (states != null && !states.contains(valueStr)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -78,6 +78,11 @@
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="nullValue" type="text">
|
||||
<label>NULL Value</label>
|
||||
<description>If the received MQTT value matches this, treat it as NULL.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="allowedStates" type="text">
|
||||
<label>Allowed States</label>
|
||||
|
@ -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
|
||||
|
@ -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("")));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user