]> git.basschouten.com Git - openhab-addons.git/commitdiff
[snmp] Apply UoM to number channels from an SNMP target (#10681)
authorJames Melville <jamesmelville@gmail.com>
Sun, 20 Jun 2021 18:30:37 +0000 (19:30 +0100)
committerGitHub <noreply@github.com>
Sun, 20 Jun 2021 18:30:37 +0000 (20:30 +0200)
* Add Unit handling functionality to SNMP Reads

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Use core library for Unit parsing

Signed-off-by: James Melville <jamesmelville@gmail.com>
bundles/org.openhab.binding.snmp/README.md
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpTargetHandler.java
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpChannelConfiguration.java
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpInternalChannelConfiguration.java
bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/AbstractSnmpTargetHandlerTest.java
bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SnmpTargetHandlerTest.java

index 89e74e1d0b4331cb3e175976e0b4f8a245093258..951dcc4720f9169fff8b70277e88691f0c9a56c5 100644 (file)
@@ -94,6 +94,8 @@ For `string` channels the default `datatype` is `STRING` (i.e. the item's will b
 If it is set to `IPADDRESS`, an SNMP IP address object is constructed from the item's value.
 The `HEXSTRING` datatype converts a hexadecimal string (e.g. `aa bb 11`) to the respective octet string before sending data to the target (and vice versa for receiving data).
 
+`number`-type channels can have a parameter `unit` if their `mode` is set to `READ`. This will result in a state update applying [UoM](https://www.openhab.org/docs/concepts/units-of-measurement.html) to the received data if the UoM symbol is recognised.
+
 `switch`-type channels send a pre-defined value if they receive `ON` or `OFF` command in `WRITE` or `READ_WRITE` mode.
 In `READ`, `READ_WRITE` or `TRAP` mode they change to either `ON` or `OFF` on these values.
 The parameters used for defining the values are `onvalue` and `offvalue`.
@@ -125,7 +127,7 @@ demo.things:
 ```
 Thing snmp:target:router [ hostname="192.168.0.1", protocol="v2c" ] {
     Channels:
-        Type number : inBytes [ oid=".1.3.6.1.2.1.31.1.1.1.6.2", mode="READ" ]
+        Type number : inBytes [ oid=".1.3.6.1.2.1.31.1.1.1.6.2", mode="READ", unit="B" ]
         Type number : outBytes [ oid=".1.3.6.1.2.1.31.1.1.1.10.2", mode="READ" ]
         Type number : if4Status [ oid="1.3.6.1.2.1.2.2.1.7.4", mode="TRAP" ]
         Type switch : if4Command [ oid="1.3.6.1.2.1.2.2.1.7.4", mode="READ_WRITE", datatype="UINT32", onvalue="2", offvalue="0" ]
@@ -138,6 +140,7 @@ demo.items:
 
 ```
 Number inBytes "Router bytes in [%d]" { channel="snmp:target:router:inBytes" }
+Number inGigaBytes "Router gigabytes in [%d GB]" { channel="snmp:target:router:inBytes" }
 Number outBytes "Router bytes out [%d]" { channel="snmp:target:router:outBytes" }
 Number if4Status "Router interface 4 status [%d]" { channel="snmp:target:router:if4Status" }
 Switch if4Command "Router interface 4 switch [%s]" { channel="snmp:target:router:if4Command" }
index 1a121f72aa4c156d71988caa65dbcea601adab8b..d1c97c73c654507e0ac5895fdbc2676fc16ed801 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.snmp.internal;
 import static org.openhab.binding.snmp.internal.SnmpBindingConstants.*;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Collections;
@@ -26,6 +27,9 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import javax.measure.Unit;
+import javax.measure.format.MeasurementParseException;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.snmp.internal.config.SnmpChannelConfiguration;
@@ -33,6 +37,7 @@ import org.openhab.binding.snmp.internal.config.SnmpInternalChannelConfiguration
 import org.openhab.binding.snmp.internal.config.SnmpTargetConfiguration;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelUID;
@@ -45,6 +50,7 @@ import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
+import org.openhab.core.types.util.UnitUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.snmp4j.AbstractTarget;
@@ -257,6 +263,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
         Variable onValue = null;
         Variable offValue = null;
         State exceptionValue = UnDefType.UNDEF;
+        Unit<?> unit = null;
 
         if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
             if (datatype == null) {
@@ -268,6 +275,17 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
             if (configExceptionValue != null) {
                 exceptionValue = DecimalType.valueOf(configExceptionValue);
             }
+            if (config.unit != null) {
+                if (config.mode != SnmpChannelMode.READ) {
+                    logger.warn("units only supported for readonly channels, ignored for channel {}", channel.getUID());
+                } else {
+                    try {
+                        unit = UnitUtils.parseUnit(config.unit);
+                    } catch (MeasurementParseException e) {
+                        logger.warn("unrecognised unit '{}', ignored for channel '{}'", config.unit, channel.getUID());
+                    }
+                }
+            }
         } else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
             if (datatype == null) {
                 datatype = SnmpDatatype.STRING;
@@ -305,7 +323,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
             return null;
         }
         return new SnmpInternalChannelConfiguration(channel.getUID(), new OID(oid), config.mode, datatype, onValue,
-                offValue, exceptionValue, config.doNotLogException);
+                offValue, exceptionValue, config.doNotLogException, unit);
     }
 
     private void generateChannelConfigs() {
@@ -341,10 +359,17 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
                     state = channelConfig.exceptionValue;
                 } else if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
                     try {
+                        BigDecimal numericState;
+                        final @Nullable Unit<?> unit = channelConfig.unit;
                         if (channelConfig.datatype == SnmpDatatype.FLOAT) {
-                            state = new DecimalType(value.toString());
+                            numericState = new BigDecimal(value.toString());
+                        } else {
+                            numericState = BigDecimal.valueOf(value.toLong());
+                        }
+                        if (unit != null) {
+                            state = new QuantityType<>(numericState, unit);
                         } else {
-                            state = new DecimalType(value.toLong());
+                            state = new DecimalType(numericState);
                         }
                     } catch (UnsupportedOperationException e) {
                         logger.warn("could not convert {} to number for channel {}", value, channelUID);
index c3835bf1b27978bfe90ce9a67e532bac1b8bac1d..0bfa59e99145d67b08af0462a7981e78ea4a2c1e 100644 (file)
@@ -33,4 +33,6 @@ public class SnmpChannelConfiguration {
     public @Nullable String exceptionValue;
 
     public boolean doNotLogException = false;
+
+    public @Nullable String unit;
 }
index 37cc67695bb444dc1a226ce72e9e20cf70b95afb..f8cd7ee72e1f79093f8cf6af99f4a594a293fcb9 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.snmp.internal.config;
 
+import javax.measure.Unit;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.snmp.internal.SnmpChannelMode;
@@ -38,9 +40,11 @@ public class SnmpInternalChannelConfiguration {
     public final @Nullable Variable offValue;
     public final State exceptionValue;
     public final boolean doNotLogException;
+    public final @Nullable Unit<?> unit;
 
     public SnmpInternalChannelConfiguration(ChannelUID channelUID, OID oid, SnmpChannelMode mode, SnmpDatatype datatype,
-            @Nullable Variable onValue, @Nullable Variable offValue, State exceptionValue, boolean doNotLogException) {
+            @Nullable Variable onValue, @Nullable Variable offValue, State exceptionValue, boolean doNotLogException,
+            @Nullable Unit<?> unit) {
         this.channelUID = channelUID;
         this.oid = oid;
         this.mode = mode;
@@ -49,5 +53,6 @@ public class SnmpInternalChannelConfiguration {
         this.offValue = offValue;
         this.exceptionValue = exceptionValue;
         this.doNotLogException = doNotLogException;
+        this.unit = unit;
     }
 }
index 700936dea2ca36c892e022f2d167451519acca4e..a34b75cb7746e0670d5e897ab41a5a740a40c3ed 100644 (file)
                                <description>Value to send if an SNMP exception occurs (default: UNDEF)</description>
                                <advanced>true</advanced>
                        </parameter>
+                       <parameter name="unit" type="text">
+                               <label>Unit Of Measurement</label>
+                               <description>Unit of measurement (optional). The unit is used for representing the value in the GUI as well as for
+                                       converting incoming values (like from '°F' to '°C'). Examples: "°C", "°F"</description>
+                               <advanced>true</advanced>
+                       </parameter>
                </config-description>
        </channel-type>
 
index 85f2c231eff4e6832dfc41aef010192c4e229f77..2500b49251131111db3b58c7279023148167eb75 100644 (file)
@@ -170,6 +170,11 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
 
     protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype,
             String onValue, String offValue, String exceptionValue) {
+        setup(channelTypeUID, channelMode, datatype, onValue, offValue, exceptionValue, null);
+    }
+
+    protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype,
+            String onValue, String offValue, String exceptionValue, String unit) {
         Map<String, Object> channelConfig = new HashMap<>();
         Map<String, Object> thingConfig = new HashMap<>();
         mocks = MockitoAnnotations.openMocks(this);
@@ -195,6 +200,9 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
             if (exceptionValue != null) {
                 channelConfig.put("exceptionValue", exceptionValue);
             }
+            if (unit != null) {
+                channelConfig.put("unit", unit);
+            }
             Channel channel = ChannelBuilder.create(CHANNEL_UID, itemType).withType(channelTypeUID)
                     .withConfiguration(new Configuration(channelConfig)).build();
             thingBuilder.withChannel(channel);
index b60c483571d021a005d90234cd27a92e7eac0599..05885b04ee051a78ca87c1a744b69c250d478ea0 100644 (file)
@@ -22,7 +22,9 @@ import java.util.Collections;
 import org.junit.jupiter.api.Test;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
 import org.openhab.core.thing.ThingStatus;
 import org.snmp4j.PDU;
 import org.snmp4j.Snmp;
@@ -109,6 +111,19 @@ public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
         verifyStatus(ThingStatus.ONLINE);
     }
 
+    @Test
+    public void testNumberChannelsProperlyHandlingUnits() throws IOException {
+        setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT, null, null, null,
+                "°C");
+        PDU responsePDU = new PDU(PDU.RESPONSE,
+                Collections.singletonList(new VariableBinding(new OID(TEST_OID), new OctetString("12.4"))));
+        ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
+        thingHandler.onResponse(event);
+        verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID),
+                eq(new QuantityType<>(12.4, SIUnits.CELSIUS)));
+        verifyStatus(ThingStatus.ONLINE);
+    }
+
     @Test
     public void testCancelingAsyncRequest() {
         setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT);