[snmp] Apply UoM to number channels from an SNMP target (#10681)
* 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>
This commit is contained in:
parent
22dd4aecd8
commit
f73553347e
|
@ -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" }
|
||||
|
|
|
@ -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 {
|
||||
state = new DecimalType(value.toLong());
|
||||
numericState = BigDecimal.valueOf(value.toLong());
|
||||
}
|
||||
if (unit != null) {
|
||||
state = new QuantityType<>(numericState, unit);
|
||||
} else {
|
||||
state = new DecimalType(numericState);
|
||||
}
|
||||
} catch (UnsupportedOperationException e) {
|
||||
logger.warn("could not convert {} to number for channel {}", value, channelUID);
|
||||
|
|
|
@ -33,4 +33,6 @@ public class SnmpChannelConfiguration {
|
|||
public @Nullable String exceptionValue;
|
||||
|
||||
public boolean doNotLogException = false;
|
||||
|
||||
public @Nullable String unit;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,12 @@
|
|||
<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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue