mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 23:22:02 +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> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
fbb3d099fd
commit
c001db5c78
@ -59,6 +59,7 @@ public class ChannelConfig {
|
|||||||
public @Nullable String stop;
|
public @Nullable String stop;
|
||||||
public @Nullable String onState;
|
public @Nullable String onState;
|
||||||
public @Nullable String offState;
|
public @Nullable String offState;
|
||||||
|
public @Nullable String nullValue;
|
||||||
|
|
||||||
public int onBrightness = 10;
|
public int onBrightness = 10;
|
||||||
public String colorMode = ColorMode.HSB.toString();
|
public String colorMode = ColorMode.HSB.toString();
|
||||||
|
@ -121,9 +121,12 @@ public class NumberValue extends Value {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type parseMessage(Command command) throws IllegalArgumentException {
|
public Type parseMessage(Command command) throws IllegalArgumentException {
|
||||||
if (command instanceof StringType
|
if (command instanceof StringType) {
|
||||||
&& (command.toString().equalsIgnoreCase(NAN) || command.toString().equalsIgnoreCase(NEGATIVE_NAN))) {
|
if (command.toString().equalsIgnoreCase(NAN) || command.toString().equalsIgnoreCase(NEGATIVE_NAN)) {
|
||||||
return UnDefType.UNDEF;
|
return UnDefType.UNDEF;
|
||||||
|
} else if (command.toString().isEmpty()) {
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return parseCommand(command);
|
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.StringType;
|
||||||
import org.openhab.core.library.types.UpDownType;
|
import org.openhab.core.library.types.UpDownType;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.Type;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a rollershutter value.
|
* Implements a rollershutter value.
|
||||||
@ -146,7 +148,10 @@ public class RollershutterValue extends Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
command = parseType(command, upStateString, downStateString);
|
||||||
if (inverted && command instanceof PercentType percentType) {
|
if (inverted && command instanceof PercentType percentType) {
|
||||||
return new PercentType(100 - percentType.intValue());
|
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.State;
|
||||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||||
import org.openhab.core.types.StateOption;
|
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.
|
* 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> states;
|
||||||
private final @Nullable Set<String> commands;
|
private final @Nullable Set<String> commands;
|
||||||
|
|
||||||
|
protected @Nullable String nullValue = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a string value with a limited number of allowed states and commands.
|
* 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;
|
this.commands = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNullValue(@Nullable String nullValue) {
|
||||||
|
this.nullValue = nullValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StringType parseCommand(Command command) throws IllegalArgumentException {
|
public StringType parseCommand(Command command) throws IllegalArgumentException {
|
||||||
final Set<String> commands = this.commands;
|
final Set<String> commands = this.commands;
|
||||||
@ -92,6 +99,10 @@ public class TextValue extends Value {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public State parseMessage(Command command) throws IllegalArgumentException {
|
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;
|
final Set<String> states = this.states;
|
||||||
String valueStr = command.toString();
|
String valueStr = command.toString();
|
||||||
if (states != null && !states.contains(valueStr)) {
|
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.DecimalType;
|
||||||
import org.openhab.core.library.types.PercentType;
|
import org.openhab.core.library.types.PercentType;
|
||||||
import org.openhab.core.library.types.RawType;
|
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.Command;
|
||||||
import org.openhab.core.types.CommandDescriptionBuilder;
|
import org.openhab.core.types.CommandDescriptionBuilder;
|
||||||
import org.openhab.core.types.State;
|
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.
|
* @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
|
||||||
*/
|
*/
|
||||||
public Type parseMessage(Command command) throws IllegalArgumentException {
|
public Type parseMessage(Command command) throws IllegalArgumentException {
|
||||||
|
if (command instanceof StringType string && string.toString().isEmpty()) {
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
return parseCommand(command);
|
return parseCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,10 @@ public class ValueFactory {
|
|||||||
Value value;
|
Value value;
|
||||||
switch (channelTypeID) {
|
switch (channelTypeID) {
|
||||||
case MqttBindingConstants.STRING:
|
case MqttBindingConstants.STRING:
|
||||||
value = config.allowedStates.isBlank() ? new TextValue()
|
TextValue textValue = config.allowedStates.isBlank() ? new TextValue()
|
||||||
: new TextValue(config.allowedStates.split(","));
|
: new TextValue(config.allowedStates.split(","));
|
||||||
|
textValue.setNullValue(config.nullValue);
|
||||||
|
value = textValue;
|
||||||
break;
|
break;
|
||||||
case MqttBindingConstants.DATETIME:
|
case MqttBindingConstants.DATETIME:
|
||||||
value = new DateTimeValue();
|
value = new DateTimeValue();
|
||||||
|
@ -78,6 +78,11 @@
|
|||||||
<default>false</default>
|
<default>false</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</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">
|
<parameter name="allowedStates" type="text">
|
||||||
<label>Allowed States</label>
|
<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.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.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.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.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.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
|
thing-type.config.mqtt.switch_channel.formatBeforePublish.label = Outgoing Value Format
|
||||||
|
@ -82,6 +82,8 @@ public class ValueTests {
|
|||||||
assertThat(hsb.getBrightness().intValue(), is(0));
|
assertThat(hsb.getBrightness().intValue(), is(0));
|
||||||
hsb = (HSBType) v.parseCommand(p(v, "1"));
|
hsb = (HSBType) v.parseCommand(p(v, "1"));
|
||||||
assertThat(hsb.getBrightness().intValue(), is(1));
|
assertThat(hsb.getBrightness().intValue(), is(1));
|
||||||
|
|
||||||
|
assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -137,6 +139,8 @@ public class ValueTests {
|
|||||||
// Test custom formatting
|
// Test custom formatting
|
||||||
assertThat(v.getMQTTpublishValue(OnOffType.OFF, "=%s"), is("=fancyOff"));
|
assertThat(v.getMQTTpublishValue(OnOffType.OFF, "=%s"), is("=fancyOff"));
|
||||||
assertThat(v.getMQTTpublishValue(OnOffType.ON, "=%s"), is("=fancyON"));
|
assertThat(v.getMQTTpublishValue(OnOffType.ON, "=%s"), is("=fancyON"));
|
||||||
|
|
||||||
|
assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -168,6 +172,8 @@ public class ValueTests {
|
|||||||
// Test basic formatting
|
// Test basic formatting
|
||||||
assertThat(v.getMQTTpublishValue(OpenClosedType.CLOSED, null), is("fancyOff"));
|
assertThat(v.getMQTTpublishValue(OpenClosedType.CLOSED, null), is("fancyOff"));
|
||||||
assertThat(v.getMQTTpublishValue(OpenClosedType.OPEN, null), is("fancyON"));
|
assertThat(v.getMQTTpublishValue(OpenClosedType.OPEN, null), is("fancyON"));
|
||||||
|
|
||||||
|
assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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("-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
|
@Test
|
||||||
@ -243,6 +251,8 @@ public class ValueTests {
|
|||||||
// Test parsing from MQTT
|
// Test parsing from MQTT
|
||||||
assertThat(v.parseMessage(new StringType("fancyON")), is(UpDownType.UP));
|
assertThat(v.parseMessage(new StringType("fancyON")), is(UpDownType.UP));
|
||||||
assertThat(v.parseMessage(new StringType("fancyOff")), is(UpDownType.DOWN));
|
assertThat(v.parseMessage(new StringType("fancyOff")), is(UpDownType.DOWN));
|
||||||
|
|
||||||
|
assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -310,6 +320,8 @@ public class ValueTests {
|
|||||||
command = v.parseCommand(new DecimalType(i));
|
command = v.parseCommand(new DecimalType(i));
|
||||||
assertThat(v.getMQTTpublishValue(command, null), is("" + i));
|
assertThat(v.getMQTTpublishValue(command, null), is("" + i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertThat(v.parseMessage(new StringType("")), is(UnDefType.NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -394,4 +406,20 @@ public class ValueTests {
|
|||||||
null);
|
null);
|
||||||
assertThrows(IllegalArgumentException.class, () -> v.parseCommand(new DecimalType(9.0)));
|
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