]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt] Interpet incoming NaN as UNDEF for NumberValues (#15897)
authorCody Cutrer <cody@cutrer.us>
Sun, 10 Dec 2023 10:01:15 +0000 (03:01 -0700)
committerGitHub <noreply@github.com>
Sun, 10 Dec 2023 10:01:15 +0000 (11:01 +0100)
* [mqtt] Interpet incoming NaN as UNDEF for NumberValues

Since DecimalType and QuantityType don't support NaN, but
when you're linking to a topic that the device is using
floating point, NaN might happen.

---------

Signed-off-by: Cody Cutrer <cody@cutrer.us>
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelConfig.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/NumberValue.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/Value.java
bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/config/number-channel-config.xml
bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties
bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/values/ValueTests.java

index da9117e0dd35ad47982a0ffff0b7de2729c3463e..78cb1a3534fa806512c630fe4871a7b91246fda5 100644 (file)
@@ -35,7 +35,9 @@ public class ChannelConfig {
 
     /**
      * If true, the channel state is not updated on a new message.
-     * Instead a postCommand() call is performed.
+     * Instead a postCommand() call is performed. If the message is
+     * not possible to send as a command (i.e. UNDEF), it will ignore
+     * this.
      */
     public boolean postCommand = false;
     public @Nullable Integer qos;
index 607253f17bd8245431661a5126a8333a96068876..869e6c0eccfd8ef1aae2fb0d1cba3539abe757bb 100644 (file)
@@ -32,6 +32,7 @@ import org.openhab.core.library.types.StringType;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.State;
+import org.openhab.core.types.Type;
 import org.openhab.core.types.TypeParser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -198,10 +199,10 @@ public class ChannelState implements MqttMessageSubscriber {
             return;
         }
 
-        Command parsedCommand;
+        Type parsedType;
         // Map the string to a command, update the cached value and post the command to the framework
         try {
-            parsedCommand = cachedValue.parseMessage(command);
+            parsedType = cachedValue.parseMessage(command);
         } catch (IllegalArgumentException | IllegalStateException e) {
             logger.warn("Command '{}' from channel '{}' not supported by type '{}': {}", strValue, channelUID,
                     cachedValue.getClass().getSimpleName(), e.getMessage());
@@ -209,18 +210,23 @@ public class ChannelState implements MqttMessageSubscriber {
             return;
         }
 
-        // things that are only Commands _must_ be posted as a command (like STOP)
-        if (!(parsedCommand instanceof State)) {
-            channelStateUpdateListener.postChannelCommand(channelUID, parsedCommand);
+        if (parsedType instanceof State parsedState) {
+            cachedValue.update(parsedState);
+        } else {
+            // things that are only Commands _must_ be posted as a command (like STOP)
+            channelStateUpdateListener.postChannelCommand(channelUID, (Command) parsedType);
             receivedOrTimeout();
             return;
         }
-        cachedValue.update((State) parsedCommand);
 
-        if (config.postCommand) {
-            channelStateUpdateListener.postChannelCommand(channelUID, (Command) cachedValue.getChannelState());
+        State newState = cachedValue.getChannelState();
+        // If the user explicitly wants a command sent, not an update, do that. But
+        // we have to check that the state is even possible to send as a command
+        // (i.e. not UNDEF)
+        if (config.postCommand && newState instanceof Command newCommand) {
+            channelStateUpdateListener.postChannelCommand(channelUID, newCommand);
         } else {
-            channelStateUpdateListener.updateChannelState(channelUID, cachedValue.getChannelState());
+            channelStateUpdateListener.updateChannelState(channelUID, newState);
         }
         receivedOrTimeout();
     }
index f65dd125e4036b83bddd390ce307123f38f29dc4..453d41764cb1f167fcf93b3fe03e7cee005aba16 100644 (file)
@@ -23,10 +23,13 @@ import org.openhab.core.library.CoreItemFactory;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
 import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
 import org.openhab.core.library.types.UpDownType;
 import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.Type;
+import org.openhab.core.types.UnDefType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,6 +46,8 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class NumberValue extends Value {
+    private static final String NAN = "NaN";
+
     private final Logger logger = LoggerFactory.getLogger(NumberValue.class);
     private final @Nullable BigDecimal min;
     private final @Nullable BigDecimal max;
@@ -51,7 +56,8 @@ public class NumberValue extends Value {
 
     public NumberValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step,
             @Nullable Unit<?> unit) {
-        super(CoreItemFactory.NUMBER, List.of(QuantityType.class, IncreaseDecreaseType.class, UpDownType.class));
+        super(CoreItemFactory.NUMBER,
+                List.of(QuantityType.class, IncreaseDecreaseType.class, UpDownType.class, StringType.class));
         this.min = min;
         this.max = max;
         this.step = step == null ? BigDecimal.ONE : step;
@@ -112,6 +118,14 @@ public class NumberValue extends Value {
         }
     }
 
+    @Override
+    public Type parseMessage(Command command) throws IllegalArgumentException {
+        if (command instanceof StringType && command.toString().equalsIgnoreCase(NAN)) {
+            return UnDefType.UNDEF;
+        }
+        return parseCommand(command);
+    }
+
     private BigDecimal getOldValue() {
         BigDecimal val = BigDecimal.ZERO;
         if (state instanceof DecimalType decimalCommand) {
index e5f672da28336184d06fdd7679cc128c96273029..76839cf514704b46f739e8d0455e9a14e4b55f1e 100644 (file)
@@ -27,6 +27,7 @@ import org.openhab.core.types.Command;
 import org.openhab.core.types.CommandDescriptionBuilder;
 import org.openhab.core.types.State;
 import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.Type;
 import org.openhab.core.types.UnDefType;
 
 /**
@@ -145,7 +146,7 @@ public abstract class Value {
      * @param command The command to parse.
      * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
      */
-    public Command parseMessage(Command command) throws IllegalArgumentException {
+    public Type parseMessage(Command command) throws IllegalArgumentException {
         return parseCommand(command);
     }
 
index be146d14c581313be13e3ef68b054ec97e6b965a..abd7ac5693779654e8d6ecbcec13a4af00101278 100644 (file)
@@ -74,7 +74,7 @@
                <parameter name="postCommand" type="boolean">
                        <label>Is Command</label>
                        <description>If the received MQTT value should not only update the state of linked items, but command them, enable
-                               this option.</description>
+                               this option. Note that if the value is NaN (interpreted as UNDEF), it can only update; commands are not possible.</description>
                        <default>false</default>
                        <advanced>true</advanced>
                </parameter>
index e3e20d452cfddf88f79ddc680ce3e368b0a35aeb..f02c9b5dc50dd9d354616d953db7ae53f63583e0 100644 (file)
@@ -109,7 +109,7 @@ thing-type.config.mqtt.number_channel.max.description = This configuration repre
 thing-type.config.mqtt.number_channel.min.label = Absolute Minimum
 thing-type.config.mqtt.number_channel.min.description = This configuration represents the minimum of the allowed range. For a percentage channel that equals zero percent.
 thing-type.config.mqtt.number_channel.postCommand.label = Is Command
-thing-type.config.mqtt.number_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option.
+thing-type.config.mqtt.number_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. Note that if the value is NaN (interpreted as UNDEF), it can only update; commands are not possible.
 thing-type.config.mqtt.number_channel.qos.label = QoS
 thing-type.config.mqtt.number_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection.
 thing-type.config.mqtt.number_channel.qos.option.0 = At most once (best effort delivery "fire and forget")
index 4f52814bb91f8477669cb98b5d33a9ba3bd9aeb6..5d9de8bb8476b8d2b175220eb85f5cf87289c474 100644 (file)
@@ -37,6 +37,7 @@ import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.State;
 import org.openhab.core.types.TypeParser;
+import org.openhab.core.types.UnDefType;
 
 /**
  * Test cases for the value classes. They should throw exceptions if the wrong command type is used
@@ -175,6 +176,9 @@ public class ValueTests {
         command = v.parseCommand(new QuantityType<>("20"));
         assertThat(command, is(new QuantityType<>(20, Units.WATT)));
         assertThat(v.getMQTTpublishValue(command, null), is("20"));
+
+        assertThat(v.parseMessage(new StringType("NaN")), is(UnDefType.UNDEF));
+        assertThat(v.parseMessage(new StringType("nan")), is(UnDefType.UNDEF));
     }
 
     @Test